Compare commits

...

48 Commits

Author SHA1 Message Date
496947a001 历史视频添加 2024-12-28 15:02:02 +08:00
245ce2bc15 update: 更新退出系统菜单 2024-12-27 20:17:05 +08:00
88456e83cb 🐛 fixed: 修复详情详情宽度不足 2024-12-27 19:28:10 +08:00
68f675437d 💄 update: 更新历史视频库列表样式 2024-12-25 20:46:54 +08:00
94d300d0be update: 更新历史视频库 2024-12-25 20:37:49 +08:00
0699fa1145 fixed: 更新样式 2024-12-24 22:35:10 +08:00
8b219769fc fixed: 更新样式 2024-12-24 21:00:20 +08:00
869efaf59b fixed confirm modal style 2024-12-24 20:25:02 +08:00
e4de0d6bd2 fixed confirm modal style 2024-12-24 19:52:50 +08:00
ff6e66d752 fixed confirm modal style 2024-12-24 19:28:14 +08:00
eecd7b766f fixed 直播间因为字体大小问题导致无法对齐 2024-12-24 18:47:53 +08:00
aa456d2b18 update 2024-12-24 17:11:51 +08:00
258900db12 fixed ts build error 2024-12-24 15:31:18 +08:00
4935c0674f style update 2024-12-24 15:23:14 +08:00
b3a3757751 spin style update 2024-12-24 15:12:05 +08:00
9ae42b3ce5 update ui 2024-12-24 14:59:07 +08:00
b6dfbb00b1 update ui 2024-12-24 14:58:22 +08:00
2888a8ee12 update: 新闻素材筛选条件缓存 2024-12-24 11:32:50 +08:00
f21e8050b2 fixed: ts build error 2024-12-24 00:14:07 +08:00
6603bbf75f update ui 2024-12-24 00:04:48 +08:00
2b2fe09e71 添加播放时间更新 2024-12-24 00:02:40 +08:00
2e5893d3ab update 删除 2024-12-23 23:48:39 +08:00
caa99cea3e updated 2024-12-23 23:37:28 +08:00
a84fc4cf75 update 视频排序调整 2024-12-23 23:20:21 +08:00
f4117e0f67 update: 新闻编辑 2024-12-23 23:02:15 +08:00
3a1d19fbfd fixed: 新闻编辑无法删除 2024-12-23 22:39:35 +08:00
7bcdc03b53 fixed: 新闻素材全选bug 2024-12-23 22:25:07 +08:00
a3a2e09000 update: 添加字数和图片数;新闻编辑失去焦点自动搜索 2024-12-23 22:17:29 +08:00
91c7563cc6 fixed: 新闻编辑无法确定问题 2024-12-23 22:04:38 +08:00
7bf1378e90 update: 新闻素材页面添加全局选择 2024-12-23 22:01:15 +08:00
a5b8e9cd87 update: pinned panel 添加展开和收缩动画 2024-12-23 20:38:31 +08:00
ae132cb274 update: 更新功能及配置
* 添加登录页面背景
* 新闻素材页面时间只精确到分钟;筛选时间默认为半小时内;添加已推送标签
2024-12-23 19:43:09 +08:00
6d63bdf1a2 news detail 修改完成 2024-12-23 10:56:03 +08:00
348d23f72c fixed ts build error 2024-12-23 10:37:22 +08:00
c73911eb95 fixed 新闻编辑器样式及新增位置问题 2024-12-22 23:28:54 +08:00
f0ef2a827d update 当前播放视频时长 2024-12-22 23:16:58 +08:00
f92523c333 update 全选逻辑,批量操作按钮样式 2024-12-22 23:11:17 +08:00
7ccd5b4086 update 全选逻辑,批量操作按钮样式 2024-12-22 22:55:38 +08:00
cb316aa596 💄 update 来源筛选隐藏 2024-12-22 22:37:19 +08:00
a63c5fc1a3 💄 update 视频操作ui 2024-12-22 22:27:15 +08:00
f6f5e08aba 💄 update 新闻内容编辑器 2024-12-22 17:47:34 +08:00
c95fc4e79d 💄 update 新闻编辑样式 2024-12-22 14:51:15 +08:00
3c57d2d1bc 💄 update 来源筛选 2024-12-22 03:47:48 +08:00
2d54451052 💄 update 新闻编辑ui 2024-12-22 03:44:43 +08:00
9e274a8e46 💄 麻烦的新闻来源过滤 2024-12-22 03:20:10 +08:00
b1efffc9fe 💄 新闻素材推入编辑后直接跳转 2024-12-22 00:12:26 +08:00
e9c5e241f3 💄 新闻素材更新 2024-12-22 00:09:15 +08:00
c55c998897 💄 更新框架及登录UI 2024-12-21 16:27:01 +08:00
60 changed files with 3016 additions and 1452 deletions

19
.ide/Dockerfile Normal file
View File

@ -0,0 +1,19 @@
FROM node:20
# 以及按需安装其他软件
# RUN apt-get update && apt-get install -y git
# 安装 code-server 和 vscode 常用插件
RUN curl -fsSL https://code-server.dev/install.sh | sh \
&& code-server --install-extension redhat.vscode-yaml \
&& code-server --install-extension dbaeumer.vscode-eslint \
&& code-server --install-extension eamodio.gitlens \
&& code-server --install-extension tencent-cloud.coding-copilot \
&& echo done
# 安装 ssh 服务,用于支持 VSCode 客户端通过 Remote-SSH 访问开发环境
RUN apt-get update && apt-get install -y wget unzip openssh-server
# 指定字符集支持命令行输入中文(根据需要选择字符集)
ENV LANG C.UTF-8
ENV LANGUAGE C.UTF-8

View File

@ -1,7 +1,7 @@
{
"name": "ai-live",
"private": true,
"version": "1.0.2",
"version": "newui-0.0.1",
"type": "module",
"description": "数字人直播间",
"scripts": {
@ -20,7 +20,7 @@
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"ahooks": "^3.8.1",
"antd": "^5.22.1",
"antd": "^5.22.5",
"axios": "^1.7.7",
"clsx": "^2.1.1",
"dayjs": "^1.11.11",
@ -33,7 +33,8 @@
"react-player": "^2.16.0",
"react-router-dom": "^6.28.0",
"sass": "^1.81.0",
"tcplayer.js": "^5.2.0"
"tcplayer.js": "^5.2.0",
"zustand": "^5.0.2"
},
"devDependencies": {
"@types/file-saver": "^2.0.7",

View File

@ -1,6 +1,6 @@
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<path d="M671.584 201.142c22.034 0 39.896 17.398 39.896 38.858v163.2l207.98-141.828a20.4 20.4 0 0 1 20.666-1.322c6.602 3.35 10.732 10.002 10.732 17.252V746.7c0 7.25-4.13 13.902-10.732 17.252a20.4 20.4 0 0 1-20.668-1.32L711.48 620.8V784c0 21.46-17.86 38.858-39.896 38.858H113.04c-22.034 0-39.896-17.398-39.896-38.858V240c0-21.46 17.862-38.858 39.896-38.858h558.546z"
fill="#9C34FE"></path>
<path d="M328.478 388.784c-7.584 0-14.122 5.196-15.64 12.434l-0.32 3.07v215.346c0 5.11 2.58 9.894 6.896 12.796a16.32 16.32 0 0 0 14.73 1.736l2.912-1.398 173.748-107.712c4.106-2.56 6.786-6.806 7.276-11.532 0.49-4.724-1.264-9.408-4.764-12.714l-2.512-1.944-173.748-107.712a16.28 16.28 0 0 0-8.578-2.332v-0.038z"
fill="#FFFFFF"></path>
<svg width="37" height="34" viewBox="0 0 37 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28.9241 2.04763C29.2793 1.00838 30.7152 0.976901 31.1363 1.95321L31.1719 2.04823L31.6512 3.44974C31.761 3.77115 31.9385 4.06528 32.1716 4.31227C32.4048 4.55927 32.6882 4.75339 33.0028 4.88154L33.1316 4.92964L34.5332 5.40829C35.5724 5.76342 35.6039 7.19937 34.6282 7.62042L34.5332 7.65605L33.1316 8.1353C32.8101 8.24506 32.5159 8.42251 32.2688 8.65567C32.0217 8.88884 31.8275 9.1723 31.6993 9.48693L31.6512 9.6152L31.1725 11.0173C30.8174 12.0566 29.3814 12.088 28.961 11.1123L28.9241 11.0173L28.4455 9.61579C28.3357 9.29427 28.1583 9.00003 27.9251 8.75292C27.6919 8.50582 27.4085 8.31161 27.0939 8.1834L26.9656 8.1353L25.5641 7.65665C24.5242 7.30152 24.4928 5.86556 25.4691 5.44511L25.5641 5.40829L26.9656 4.92964C27.287 4.81981 27.5811 4.64233 27.8281 4.40916C28.0751 4.176 28.2692 3.89257 28.3974 3.57801L28.4455 3.44974L28.9241 2.04763ZM34.7992 1.07303e-07C34.9103 -1.40135e-07 35.0192 0.0311648 35.1135 0.089953C35.2077 0.148741 35.2836 0.232795 35.3325 0.332562L35.361 0.402044L35.5689 1.01135L36.1787 1.2192C36.2901 1.25702 36.3877 1.32705 36.4592 1.42041C36.5307 1.51376 36.5729 1.62624 36.5804 1.74359C36.5879 1.86094 36.5604 1.97787 36.5013 2.07957C36.4423 2.18128 36.3544 2.26317 36.2488 2.31487L36.1787 2.34337L35.5694 2.55123L35.3616 3.16112C35.3237 3.27243 35.2536 3.36998 35.1602 3.44142C35.0668 3.51286 34.9543 3.55496 34.837 3.56241C34.7197 3.56985 34.6027 3.54229 34.5011 3.48322C34.3994 3.42415 34.3176 3.33623 34.2659 3.2306L34.2374 3.16112L34.0296 2.55182L33.4197 2.34397C33.3083 2.30614 33.2107 2.23611 33.1392 2.14276C33.0677 2.0494 33.0256 1.93693 33.0181 1.81958C33.0105 1.70223 33.038 1.58529 33.0971 1.48359C33.1561 1.38189 33.244 1.3 33.3496 1.2483L33.4197 1.21979L34.029 1.01194L34.2368 0.402044C34.2769 0.284712 34.3526 0.182855 34.4535 0.110754C34.5543 0.0386538 34.6752 -7.43997e-05 34.7992 1.07303e-07Z" fill="#9C34FE"/>
<path d="M18.481 20.1202C19.2177 19.3857 19.8018 18.5128 20.2 17.5517C20.5981 16.5906 20.8023 15.5604 20.8009 14.5201C20.8009 10.1463 17.2548 6.6001 12.8809 6.6001C8.50712 6.6001 4.96094 10.1463 4.96094 14.5201C4.96094 16.7073 5.84732 18.6873 7.28084 20.1202" stroke="black" stroke-width="2" stroke-linecap="round"/>
<path d="M1 32.9999L1.66 29.6999L8.92 24.4199L12.88 28.3799L16.84 24.4199L24.1 29.6999L24.76 32.9999" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.61523 17.8246C5.62403 15.112 6.06557 13.1305 6.93985 11.88C8.25193 10.0049 9.17527 10.1429 9.84715 10.4346C10.519 10.7263 10.915 12.635 12.0377 13.1855C13.1597 13.7353 16.0333 13.8032 17.0174 14.4652C18.0008 15.1265 20.2514 16.3574 19.6904 18.4589" stroke="black" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 859 B

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -8,12 +8,14 @@
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
--main-bg-color: #f6f6f6;
--main-bg-color: #f4f7fc;
--brand-color: #43ABFF;
--navigation-width: 100vw;
--navigation-active-color: #ffe0e0;
--app-header-header: 90px;
--container-width: 1440px;
--app-header-header: 70px;
--container-width: 1800px;
--header-z-index: 99999;
--message-z-index: 100001;
}
@tailwind base;
@ -27,12 +29,12 @@
}
::-webkit-scrollbar-thumb {
background: #ccc;
background: rgba(64, 150, 255, 0.5);
height: 10px;
border-radius: 5px;
&:hover {
background: #999;
background: rgba(64, 150, 255, 1);
cursor: pointer;
}
}
@ -129,7 +131,7 @@
.page-live {
.live-player {
max-height: calc(100vh - var(--app-header-header) - 130px);
max-height: calc(100vh - var(--app-header-header) - 150px);
overflow: hidden;
iframe {
@ -146,24 +148,116 @@
}
.video-list-sort-container {
min-height: 300px;
max-height: calc(100vh - var(--app-header-header) - 300px);
overflow: auto;
padding-right: 10px;
.list-header {
}
.list-row {
@apply flex bg-white mt-2 py-1 rounded-xl gap-2 border;
border-width: 2px;
&.playing{
@apply border-primary-blue bg-[#d9eaff];
}
&.disabled{
@apply border-primary-blue bg-[#f4f7fc];
}
&.header-row{
@apply text-sm;
background: none;
.col{
height: 42px;
}
}
&.checked {
@apply border-primary-blue bg-primary-blue-bg;
}
.col {
@apply flex items-center relative pl-4 text-center justify-center;
height: 60px;
&:after {
@apply absolute;
border-right: solid 1px #e8e8e8;
content: ' ';
top: 2px;
bottom: 2px;
left: 0;
}
}
.number {
width: 70px;
padding-left: 10px;
&:after {
display: none;
}
}
.cover {
width: 120px;
img{
@apply rounded-lg;
}
}
.title {
@apply flex-1 text-base;
}
.generated-time {
width: 180px;
}
.operation {
@apply flex items-center ml-2 gap-4 text-lg text-gray-400 justify-center;
width: 120px;
padding: 0;
}
}
}
.live-video-list-sort-container {
min-height: 300px;
padding-right: 10px;
max-height: calc(100vh - var(--app-header-header) - 200px);
overflow: auto;
//min-height: 300px;
//padding-right: 10px;
//max-height: calc(100vh - var(--app-header-header) - 200px);
//overflow: auto;
}
.video-player{
.video-js{
.root-modal-confirm{
z-index: calc(var(--header-z-index) + 1) !important;
background: rgba(0, 0, 0, 0.05);
//anticon anticon-exclamation-circle
.ant-modal-confirm-title{
font-size: 20px;
margin-top: -2px;
}
.ant-modal-confirm-content{
margin-top: 10px;
margin-left: -30px;
}
.icon-warning{
}
.ant-modal-confirm-btns{
@apply mt-8;
button{
@apply rounded-2xl py-4 px-8;
}
.ant-btn-default{
@apply bg-white shadow-none text-popconfirm-btn-cancel border border-popconfirm-btn-cancel hover:border-popconfirm-btn-cancel hover:text-popconfirm-btn-cancel hover:bg-white hover:bg-popconfirm-btn-cancel/10;
}
.ant-btn-primary{
@apply bg-white shadow-none text-popconfirm-bg border border-popconfirm-bg hover:text-popconfirm-bg hover:bg-white hover:bg-popconfirm-btn-primary-hover/10;
}
}
}
.video-player {
.video-js {
@apply w-full h-full;
max-width: 100%;
max-height: 100%;
background:#fff; // hsl(210, 100%, 48%)
background: #fff; // hsl(210, 100%, 48%)
}
}
@ -171,4 +265,262 @@
@include media-breakpoint-down(md) {
display: none;
}
}
.page-action-to-top {
@apply text-right;
.btn-to-top {
@apply w-[44px] h-[44px] inline-block bg-blue-300 text-center p-0 transition hover:bg-blue-500;
min-width: 0;
border-radius: 50px;
font-size: 24px;
}
}
.list-scroller-container {
overflow: auto;
margin-right: -20px;
padding-right: 16px;
scrollbar-gutter: stable;
}
.data-list-container {
@apply list-scroller-container;
height: calc(100vh - var(--app-header-header) - 200px);
.data-list-container-inner {
}
}
.video-history-list-container{
height: calc(100vh - var(--app-header-header) - 130px);
}
.checkbox{
@apply bg-black/10 backdrop-blur border border-white hover:border-blue-500 cursor-pointer relative;
--size: 22px;
border-width: 2px;
width: var(--size);
height: var(--size);
border-radius: 2px;
&::before {
@apply absolute hidden;
border-left:solid 2px white;
border-bottom:solid 2px white;
left: 3px;
top: 4px;
content: ' ';
width: calc(var(--size) - 8px);
height: 6px;
transform: rotate(-45deg);
}
&.checked{
@apply border-blue-500 bg-blue-500;
&:before{
@apply block;
}
}
}
// override antd style
.data-list-load-spin{
.ant-spin-container::after{
opacity: 0;
}
}
.popconfirm-main{
.ant-popover-inner{
@apply bg-white px-6 py-6 rounded-xl;
min-width: 360px;
box-shadow: 0 0 10px rgba(25, 25, 25, 0.1);
}
.icon-warning{
@apply text-red-500;
font-size: 20px;
transform: translateY(5px);
margin-right: 10px;
}
.ant-popconfirm-message{
.ant-popconfirm-title{
@apply text-xl font-bold;
}
.ant-popconfirm-description{
@apply mt-4 text-gray-400 text-sm;
margin-left: -30px;
}
}
.ant-popconfirm-buttons{
@apply mt-8;
button{
@apply rounded-2xl py-4 px-8;
}
.ant-btn-default{
@apply bg-white shadow-none text-popconfirm-btn-cancel border border-popconfirm-btn-cancel hover:border-popconfirm-btn-cancel hover:text-popconfirm-btn-cancel hover:bg-white hover:bg-popconfirm-btn-cancel/10;
}
.ant-btn-primary{
@apply bg-white shadow-none text-popconfirm-bg border border-popconfirm-bg hover:text-popconfirm-bg hover:bg-white hover:bg-popconfirm-btn-primary-hover/10;
}
}
}
.ant-checkbox {
border-radius: 2px;
.ant-checkbox-inner {
border-radius: 2px;
width: 18px;
height: 18px;
&:after {
//inset-inline-start: 28%;
}
}
}
.ant-message {
z-index: var(--message-z-index);
}
.ant-modal-root {
.ant-modal-mask {
@apply bg-black/20;
backdrop-filter: blur(7px);
}
.ant-modal {
.ant-modal-content {
background: #f2f2f2;
}
.ant-modal-body {
padding: 10px 0;
}
.ant-modal-confirm-content {
color: #999;
}
.ant-modal-confirm-btns {
margin-top: 40px;
}
}
}
.article-edit-modal {
.ant-modal {
.ant-modal-content {
@apply bg-white p-0;
.ant-modal-body {
@apply p-0;
}
}
}
.article-title {
@apply px-6 pt-10 pb-6;
}
.article-body {
@apply p-6
}
.modal-control-footer {
@apply p-6
}
.input-box {
// focus-within:shadow
@apply bg-[#f8f8f8] border border-transparent w-full px-4 py-2 focus-within:bg-[#f0f0f0] focus-within:border-gray-300;
border-radius: 8px;
}
}
// 全局按钮
.page-action {
@apply fixed right-10 bottom-10 flex flex-col gap-4 z-10;
button {
@apply border-0 min-w-[120px] h-[40px] rounded-3xl pr-4 flex items-center justify-between drop-shadow;
.text {
flex: 1;
text-align: left;
padding-left: 15px;
}
&:disabled {
@apply bg-gray-400;
}
&.btn-info {
@apply bg-info text-gray-800;
}
}
}
// 时间选择
.timer-select-container {
.timer-select-value {
@apply text-blue-500 px-4 cursor-pointer h-[31px];
}
.timer-select-options {
@apply rounded-xl py-1 overflow-hidden drop-shadow absolute inset-x-0 top-[30px];
background: linear-gradient(180deg, rgb(244, 247, 252) 0%, rgb(217, 232, 255) 100%);
}
.timer-select-option-item {
@apply py-1.5 px-4 cursor-pointer text-gray-800 hover:text-blue-500;
&.selected {
@apply text-blue-500;
}
}
}
//来源选择
.tag-select-container {
.select-value {
@apply text-blue-500 px-4 cursor-pointer h-[31px];
}
.options-list-container {
@apply py-2 drop-shadow absolute top-[30px];
border-radius: 0 0 0.75rem 0.75rem;
background: linear-gradient(180deg, rgb(244, 247, 252) 0%, rgb(217, 232, 255) 100%);
.inner{
@apply overflow-auto;
max-height: 300px;
}
}
.options-list {
overflow-y: auto;
overflow-x: hidden;
}
.select-option-item {
@apply py-1.5 px-4 cursor-pointer text-gray-800 hover:text-blue-500 hover:bg-[#d9eaff];
&.selected {
@apply text-blue-500;
}
}
}
.tag-select-child-container {
.ant-popover-inner {
@apply p-0 overflow-hidden drop-shadow shadow-none;
//
background: linear-gradient(180deg, rgb(235, 242, 253) 0%, rgb(244, 247, 252) 100%);
border-radius: 0 0.75rem 0.75rem 0;
transform: translate(16px, -7px);
//backdrop-filter: 0 3px 1px 5px rgb(0, 0, 0,0.1);
}
.sub-options-list {
@apply py-1.5 top-0;
max-height: 150px;
.select-option-item {
@apply py-1.5 px-4 cursor-pointer text-gray-800 hover:text-blue-500 hover:bg-[#d9eaff];
&.selected {
@apply text-blue-500;
}
}
}
}

View File

@ -25,21 +25,27 @@ body {
.navigation-container {
//width: var(--navigation-width);
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
//box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
.nav-item {
padding: 0px 20px;
&.active{
@apply text-blue-500;
color: rgb(90, 90, 90);
font-size: 20px;
&.active {
color: rgb(19, 19, 19);
font-weight: bold;
}
}
}
.app-header {
@apply w-full navigation-container flex justify-between items-center p-basic fixed top-0 inset-x-0 z-10;
@apply w-full navigation-container flex justify-between items-center p-basic fixed top-0 inset-x-0 bg-white/90;
z-index: var(--header-z-index);
height: var(--app-header-header);
}
.app-content{
.app-content {
padding-top: var(--app-header-header);
}
@ -52,23 +58,72 @@ body {
}
.container {
max-width: 90%;
width: var(--container-width,1200px);
max-width: 95%;
width: var(--container-width, 1800px);
margin: 0 auto;
}
.article-action-icon{
.article-action-icon {
@apply cursor-pointer text-gray-500 hover:text-blue-500 block;
}
.news-detail-modal{
.ant-modal-content{
padding-right: 0;
padding-left: 0;
}
.news-detail{
video{
width: 100%;
max-width: 100%;
max-height: 400px;
}
}
}
.userinfo-drop-menu{
@apply drop-shadow bg-gray-50 pb-2 rounded px-4;
.user-profile{
@apply py-3;
.info{
min-width: 100px;
}
.user-avatar{
width: 36px;
height: 36px;
}
}
.menu-list-container{
.ant-dropdown-menu{
@apply shadow-none bg-transparent p-0 rounded-none;
.ant-dropdown-menu-item{
@apply hover:bg-gray-200 text-gray-600 hover:text-gray-800 rounded-none text-base py-2;
}
.nav-item{
@apply flex items-center;
.svg-icon{
transform: translateY(1px);
}
.nav-text{
@apply ml-2;
}
}
}
}
.logout{
@apply text-center py-2 hover:bg-gray-200 text-gray-600 hover:text-gray-800 cursor-pointer;
}
}
.news-detail-content-container{
margin-right: -30px;
padding-right: 30px;
}
img{
img {
max-width: 100%;
}
@media screen and (max-width: 2000px) {
:root {
line-height: 1.2;
--app-header-header: 70px;
}
.logo-container {
height: 36px;

View File

@ -1,10 +1,30 @@
.blockContainer {
@apply flex mb-5;
@apply relative;
:global{
.divider-container{
@apply absolute inset-x-2 z-10;
&.before{
top:-12px;
}
&.after{
bottom: -10px;
}
.ant-divider-horizontal{
margin: 0;
border-block-start: 1px rgba(5, 5, 5,0.1);
}
}
.article-action-add{
@apply text-gray-600 text-sm inline-block bg-[#cce2ff] w-[80px] justify-center flex rounded-xl cursor-pointer hover:bg-blue-300 hover:text-white;
}
}
}
.blockInner{
@apply flex px-4 py-10 hover:bg-[#e6ebf1] ;
}
.blockFooter{}
.block {
@apply border border-gray-300 border-dashed p-3 rounded flex-1;
@apply flex-1;
&:last-child {
@apply mb-0;
}
@ -23,10 +43,28 @@
}
.group {
@apply flex gap-4;
:global{
.area-title{
@apply text-gray-400 text-sm text-gray-800;
}
.digital-person{
width: 450px;
}
.panel{
@apply flex flex-col;
}
.panel-body{
@apply bg-[#f0f0f0] flex-1 rounded-xl mt-2;
}
}
}
.imagerOrText{
@apply bg-[#f8f8f8] rounded-xl py-2;
}
.imageList {
@apply grid grid-cols-4 gap-4 p-3 border border-blue-200;
@apply grid grid-cols-4 imagerOrText px-2 gap-2;
:global {
.ant-upload-wrapper {
display: block;
@ -57,19 +95,14 @@
}
}
.imageDelete{
@apply absolute flex items-center justify-center p-0.5 w-[22px] h-[22px] rounded-full border border-red-500 text-red-500 cursor-pointer z-10;
right:-10px;
top:-10px;
font-size: 14px;
&:hover{
@apply text-white bg-red-500;
}
@apply absolute flex items-center justify-center right-0 top-0 w-[22px] h-[22px] rounded-full cursor-pointer z-10 ;
font-size: 16px;
}
.uploadImage {
@apply flex justify-center items-center relative h-[100px] text-gray-400;
.uploadTips {
@apply absolute inset-0 cursor-pointer opacity-0 transition rounded flex items-center justify-center bg-black/20 text-white;
@apply absolute inset-0 cursor-pointer opacity-0 transition rounded flex items-center justify-center bg-black/50 text-white;
}
.imagePlaceholder {
@ -86,13 +119,14 @@
}
.text {
@apply border border-blue-200 overflow-hidden flex-1 rounded focus:border-blue-200 transition;
@apply imagerOrText overflow-hidden flex-1 rounded focus:border-blue-200 transition;
&:hover {
@apply border-blue-500;
}
&:focus-within {
@apply border-blue-500 shadow-md;
:global{
.ant-input{
@apply px-4;
}
}
}

View File

@ -1,8 +1,8 @@
import React from "react";
import clsx from "clsx";
import {Popconfirm} from "antd";
import {Divider, Popconfirm} from "antd";
import {IconAdd, IconDelete} from "@/components/icons";
import {IconAdd, IconAddCircle, IconDelete, IconWarningCircle} from "@/components/icons";
import ImageList from "@/components/article/list.tsx";
import { BlockText} from "./item.tsx";
@ -16,7 +16,7 @@ type Props = {
editable?: boolean;
onChange?: (blocks: BlockContent[]) => void;
onRemove?: () => void;
onAdd?: () => void;
onAdd?: (index?:number) => void;
errorMessage?: string;
}
@ -48,7 +48,6 @@ export default function ArticleBlock(
onAdd,
onChange,
index,
errorMessage
}: Props) {
const blocks = rebuildBlockArray(defaultBlocks)
@ -58,41 +57,47 @@ export default function ArticleBlock(
onChange?.(_blocks)
}
return <div className={styles.blockContainer}>
<div className={clsx(className || '', styles.block, index == 0 ? styles.blockFist : '', ' hover:bg-blue-10')}>
<div className={styles.blockBody}>
<div>
<div className={clsx(index == 0 ? '' : styles.blockItem, 'flex')}>
<BlockText
onChange={(block) => handleBlockChange(0, block)}
data={blocks[0]}
isFirstBlock={index == 0}
editable={editable}/>
return <div className={`${styles.blockContainer} group`}>
{editable && index == 1 && <div className={'divider-container before'}><Divider>
<span onClick={()=>onAdd?.(1)} className="article-action-add" title="新增分组"><IconAdd style={{fontSize: 24}}/></span>
</Divider></div> }
<div className={styles.blockInner}>
<div className={clsx(className || '', styles.block, index == 0 ? styles.blockFist : '', ' hover:bg-blue-10')}>
<div className={styles.blockBody}>
<div>
<div className={clsx(index == 0 ? '' : styles.blockItem, 'flex')}>
<BlockText
onChange={(block) => handleBlockChange(0, block)}
data={blocks[0]}
isFirstBlock={index == 0}
editable={editable}/>
</div>
</div>
{index == 0 && <div className="flex items-center text-red-500 justify-between text-sm mt-1">
<div>{errorMessage}</div>
<div></div>
</div>}
<ImageList blocks={blocks} editable={editable} onChange={onChange}/>
</div>
{index > 0 && <ImageList blocks={blocks} editable={editable} onChange={onChange}/>}
</div>
</div>
{editable && <div className="ml-2 flex flex-col justify-between ">
{
index > 0 ? <Popconfirm
title={<div style={{minWidth: 150}}><span>?</span></div>}
onConfirm={onRemove}
okText="删除"
cancelText="取消"
>
<span className="article-action-icon" title="删除此分组">
{editable && <div className="ml-2 flex items-center">
{
index > 0 ? <Popconfirm
rootClassName={'popconfirm-main'}
placement={'left'}
arrow={false}
icon={<IconWarningCircle/>}
title={<div style={{minWidth: 150}}><span>?</span></div>}
onConfirm={onRemove}
okText="删除"
cancelText="取消"
>
<span className="article-action-icon hidden group-hover:block ml-1" title="删除此分组">
<IconDelete style={{fontSize: 24}}/>
</span>
</Popconfirm> : <span></span>
}
<span onClick={onAdd} className="article-action-icon" title="新增分组"><IconAdd
style={{fontSize: 24}}/></span>
</div>}
</Popconfirm> : <span></span>
}
</div>}
</div>
{editable && <div className={'divider-container after'}><Divider>
<span onClick={()=>onAdd?.(index + 1)} className="article-action-add" title="新增分组"><IconAdd style={{fontSize: 24}}/></span>
</Divider></div> }
</div>
}

View File

@ -1,9 +1,11 @@
import {Input, Modal} from "antd";
import {Modal} from "antd";
import ArticleGroup from "@/components/article/group.tsx";
import {useEffect, useState} from "react";
import {useSetState} from "ahooks";
import * as article from "@/service/api/article.ts";
import {regenerate} from "@/service/api/video.ts";
import {push2video} from "@/service/api/article.ts";
import {showErrorToast, showToast} from "@/components/message.ts";
type Props = {
id?: number;
@ -16,12 +18,12 @@ const DEFAULT_STATE = {
open: false,
msgTitle: '',
msgGroup: '',
error:''
error: ''
}
function pushBlocksToGroup(blocks: BlockContent[],groups: BlockContent[][]){
function pushBlocksToGroup(blocks: BlockContent[], groups: BlockContent[][]) {
const lastGroup = groups[groups.length - 1]
if (lastGroup && lastGroup.filter(s=>s.type == 'text').length == 0) {
if (lastGroup && lastGroup.filter(s => s.type == 'text').length == 0) {
// 如果上一个group中没有文本则直接合并
lastGroup.push(...blocks)
} else {
@ -32,21 +34,21 @@ function pushBlocksToGroup(blocks: BlockContent[],groups: BlockContent[][]){
function rebuildGroups(groups: BlockContent[][]) {
const _groups: BlockContent[][] = [];
if (!groups || groups.length == 0) return _groups;
groups.forEach((blocks,index) => {
if(!blocks) return;
blocks = blocks.filter(s=>!!s).sort((a,b) => {
if(a.type == 'text' && b.type == 'text') return 1;
groups.forEach((blocks, index) => {
if (!blocks) return;
blocks = blocks.filter(s => !!s).sort((a, b) => {
if (a.type == 'text' && b.type == 'text') return 1;
return a.type == 'text' ? -1 : 1
})
if (blocks.length == 1) {
if(index == 0) _groups.push(blocks)
else pushBlocksToGroup(blocks,_groups)
if (index == 0) _groups.push(blocks)
else pushBlocksToGroup(blocks, _groups)
} else {
if(index == 0){
if (index == 0) {
_groups.push([blocks[0]])
_groups.push(blocks.slice(1))
}else{
pushBlocksToGroup(blocks,_groups)
} else {
pushBlocksToGroup(blocks, _groups)
}
}
});
@ -60,13 +62,15 @@ function rebuildGroups(groups: BlockContent[][]) {
}
export default function ArticleEditModal(props: Props) {
const [groups, setGroups] = useState<BlockContent[][]>([]);
const [title, setTitle] = useState('')
const [state, setState] = useSetState({
...DEFAULT_STATE
...DEFAULT_STATE,
generating:false
})
// 保存数据
const handleSave = () => {
@ -83,15 +87,28 @@ export default function ArticleEditModal(props: Props) {
setState({loading: true})
save(title, groups, props.id && props.id > 0 ? props.id : undefined).then(() => {
props.onClose?.(true)
}).catch(e=>{
}).catch(e => {
setState({error: e.data || '保存失败,请重试!'})
}).finally(() => {
setState({loading: false})
});
}
const handlePush2Video = () =>{
if(!props.id || state.generating) return;
setState({generating:true})
push2video([props.id]).then(() => {
showToast('推流成功', 'success')
// navigate('/create?state=push-success',{
// state: 'push-success'
// })
// props.onSuccess?.()
}).catch(showErrorToast).finally(()=>{
setState({generating:false})
})
}
useEffect(() => {
setState({...DEFAULT_STATE})
if (typeof(props.id) != 'undefined') {
if (typeof (props.id) != 'undefined') {
if (props.id > 0) {
article.getById(props.id).then(res => {
setGroups(rebuildGroups(res.content_group))
@ -106,35 +123,29 @@ export default function ArticleEditModal(props: Props) {
}, [props.id])
return (<Modal
title={'编辑文章'}
title={null}
centered={true}
rootClassName={"article-edit-modal"}
open={props.id != undefined && props.id >= 0}
maskClosable={false}
keyboard={false}
width={800}
onCancel={()=>props.onClose?.()}
width={'1200px'}
footer={null}
closeIcon={null}
onCancel={() => props.onClose?.()}
okButtonProps={{loading: state.loading}}
onOk={handleSave}
okText={props.type == 'news' ? '确定' : '重新生成'}
>
<div className="article-title mt-5">
<div className="title">
<span className="text text-base"></span>
<span className="require ml-1 font-bold text-red-500">*</span>
</div>
<div className="box mt-1">
<Input rootClassName={state.msgTitle ? 'border-red-500' : ''} value={title} onChange={e => {
setTitle(e.target.value)
setState({msgTitle: e.target.value ? '' : '请输入标题内容'})
}} placeholder={'请输入文章标题'}/>
</div>
<input className={'input-box text-lg'} value={title} onChange={e => {
setTitle(e.target.value)
setState({msgTitle: e.target.value ? '' : '请输入标题内容'})
}} placeholder={'请输入文章标题'}/>
<div className="text-red-500">{state.msgTitle}</div>
</div>
<div className="aricle-body mt-3">
<div className="title">
<span className="text text-base"></span>
<span className="require ml-1 font-bold text-red-500">*</span>
</div>
<div className="box mt-1">
<div className="article-body">
<div className="box">
<ArticleGroup
errorMessage={state.msgGroup} editable groups={groups}
onChange={list => {
@ -145,5 +156,12 @@ export default function ArticleEditModal(props: Props) {
</div>
{state.error && <div className="text-red-500">{state.error}</div>}
</div>
<div className="modal-control-footer flex justify-end">
<div className="flex gap-10 ">
{props.type == 'news' && props.id ? <button className="text-gray-400 hover:text-gray-800" onClick={handlePush2Video}>{state.generating?'推送中...':'生成视频'}</button> : null}
<button className="text-gray-400 hover:text-gray-800" onClick={() => props.onClose?.()}></button>
<button onClick={handleSave} className="text-gray-800 hover:text-blue-500">{props.type == 'news' ? '确定' : '重新生成'}</button>
</div>
</div>
</Modal>);
}

View File

@ -1,8 +1,9 @@
import {message} from "antd"
import {Input, message} from "antd"
import ArticleBlock from "@/components/article/block.tsx";
import styles from './article.module.scss'
import {showToast} from "@/components/message.ts";
import React from "react";
type Props = {
groups: BlockContent[][];
@ -12,7 +13,6 @@ type Props = {
}
export default function ArticleGroup({groups, editable, onChange, errorMessage}: Props) {
// const groups = rebuildGroups(_groups)
/**
@ -38,30 +38,69 @@ export default function ArticleGroup({groups, editable, onChange, errorMessage}:
}
onChange?.(_groups)
}
const handleDigitalPersonContentChange = (content:string) => {
groups[0] = [{type: 'text', content}]
onChange?.([...groups])
}
return <div className={styles.group}>
{groups.map((g, index) => (
<ArticleBlock
editable={editable}
key={index}
blocks={g}
onChange={(blocks) => {
groups[index] = blocks
onChange?.([...groups])
}}
errorMessage={errorMessage}
index={index}
onAdd={() => {
handleAddGroup?.(index + 1)
}}
onRemove={async () => {
if (groups.length == 1) {
message.warning('至少保留一个内容块')
return;
}
onChange?.(groups.filter((_, idx) => index !== idx))
}}
/>
))}
<div className={'panel digital-person'}>
<div className="area-title">
<span className=""></span>
<span className="text-gray-400"></span>
</div>
<div className="panel-body p-3">
{/* value={groups || groups[0][0].content}*/}
<div className="h-[486px] pt-2 rounded-xl overflow-hidden bg-gray-50">
{editable ? <div className="relative">
<Input.TextArea
placeholder={'请输入文本内容'}
value={groups && groups.length > 0 ? groups[0][0].content : ''}
autoSize={{minRows: 20, maxRows: 21}}
variant={"borderless"}
onChange={e => {
handleDigitalPersonContentChange(e.target.value)
}}
/>
</div> : <p className="p-2">12123</p>}
</div>
</div>
</div>
<div className={"panel groups-list flex-1"}>
<div className={"area-title"}>
<span className=""></span>
<span className="text-gray-400"></span>
</div>
<div className="panel-body py-3">
<div className="max-h-[485px] overflow-auto py-4">
{groups.map((g, index) => (
index == 0 ? null : <ArticleBlock
editable={editable}
key={index}
blocks={g}
onChange={(blocks) => {
groups[index] = blocks
onChange?.([...groups])
}}
errorMessage={errorMessage}
index={index}
onAdd={(_index) => {
handleAddGroup?.(_index ? _index :index + 1)
}}
onRemove={async () => {
if (groups.length == 1) {
message.warning('至少保留一个内容块')
return;
}
onChange?.(groups.filter((_, idx) => index !== idx))
}}
/>
))}
</div>
</div>
</div>
{groups.length == 0 && editable &&
<ArticleBlock editable onChange={blocks => onChange?.([blocks])} index={0}
blocks={[{type: 'text', content: ''}]}/>}

View File

@ -6,7 +6,7 @@ import {clsx} from "clsx";
import styles from './article.module.scss'
import {getOssPolicy} from "@/service/api/common.ts";
import {showToast} from "@/components/message.ts";
import {IconAddImage} from "@/components/icons";
import {IconAddImage, IconWarningCircle} from "@/components/icons";
type Props = {
children?: React.ReactNode;
@ -64,15 +64,7 @@ export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: Ima
}
//
return <div className={styles.image}>
{editable ? <div className={'relative'}>
{!onlyUpload && <Popconfirm
title={<div style={{minWidth: 150}}><span>?</span></div>}
onConfirm={onRemove}
okText="删除"
cancelText="取消"
>
<span className={styles.imageDelete}><CloseOutlined/></span>
</Popconfirm>}
{editable && onlyUpload ? <div className={'relative'}>
<Spin spinning={loading >= 0} percent={loading == 0 ? 'auto' : loading}>
<Upload
multiple={false} maxCount={1} data={getUploadData}
@ -84,7 +76,16 @@ export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: Ima
{data.content ? <>
<img src={data.content}/>
<div className={styles.uploadTips}>
<span></span>
{!onlyUpload && <Popconfirm
rootClassName={'popconfirm-main'}
placement={'right'}
title={<div style={{minWidth: 150}}><span>?</span></div>}
onConfirm={onRemove}
okText="删除"
cancelText="取消"
>
<span className={styles.imageDelete}><CloseOutlined/></span>
</Popconfirm>}
</div>
</> : <div className={styles.imagePlaceholder}>
<div className={'text-center'}>
@ -95,7 +96,23 @@ export function BlockImage({data, editable, onChange, onlyUpload, onRemove}: Ima
</div>
</Upload>
</Spin>
</div> : <div className={styles.uploadImage}><img src={data.content}/></div>}
</div> : <div className={styles.uploadImage}>
<img src={data.content}/>
<div className={styles.uploadTips}>
{!onlyUpload && <Popconfirm
rootClassName={'popconfirm-main'}
placement={'right'}
arrow={false}
icon={<IconWarningCircle/>}
title={<div style={{minWidth: 150}}><span>?</span></div>}
onConfirm={onRemove}
okText="删除"
cancelText="取消"
>
<span className={styles.imageDelete}><CloseOutlined/></span>
</Popconfirm>}
</div>
</div>}
</div>
}
@ -108,7 +125,7 @@ export function BlockText({data, editable, onChange, isFirstBlock}: Props) {
onChange={e => {
onChange?.({type: 'text', content: e.target.value})
}}
placeholder={'请输入文本内容'} value={data.content} autoSize={{minRows: 3, maxRows: 8}}
placeholder={'请输入文本内容'} value={data.content} autoSize={{minRows: 4, maxRows: 5}}
variant={"borderless"}/>
</div> : <p className="p-2">{data.content}</p>}
</div>

View File

@ -1,18 +1,24 @@
import React, {useState} from "react";
import {Button, Modal} from "antd";
import {App} from "antd";
import {ButtonType} from "antd/es/button";
import {showErrorToast, showToast} from "@/components/message.ts";
import {BizError} from "@/service/types.ts";
import {IconWarningCircle} from "@/components/icons";
import {LoadingOutlined} from "@ant-design/icons";
type Props = {
selected: any[],
type?: ButtonType;
emptyMessage: string,
confirmMessage: React.ReactNode,
onProcess: (ids: Id[]) => Promise<any|void>
confirmMessage?: React.ReactNode,
icon?: React.ReactNode,
onProcess: (ids: Id[]) => Promise<any | void>
successMessage?: string;
onSuccess?: () => void;
children?: React.ReactNode
children?: React.ReactNode;
title?: React.ReactNode;
className?: string;
}
/**
@ -20,10 +26,11 @@ type Props = {
*/
export default function ButtonBatch(
{
selected, emptyMessage, successMessage, children,
type, confirmMessage, onProcess,onSuccess
selected, emptyMessage, successMessage, children, icon,
title, confirmMessage, onProcess, onSuccess, className
}: Props) {
const [loading, setLoading] = useState(false)
const {modal} = App.useApp()
const onBatchProcess = async () => {
setLoading(true)
try {
@ -39,19 +46,29 @@ export default function ButtonBatch(
}
}
const handleBtnClick = () => {
if (loading) return;
if (selected.length == 0) {
showToast(emptyMessage, 'warning')
return;
}
Modal.confirm({
title: '操作提示',
centered: true,
content: confirmMessage,
onOk: onBatchProcess
})
if(confirmMessage){
modal.confirm({
wrapClassName: 'root-modal-confirm',
title: title || '操作提示',
centered: true,
icon: <span className="anticon anticon-exclamation-circle"><IconWarningCircle/></span>,
content: confirmMessage,
onOk: onBatchProcess
})
}else{
onBatchProcess().catch(showErrorToast);
}
}
return (
<Button loading={loading} type={type} onClick={handleBtnClick}>{children}</Button>
<button disabled={loading} className={className} onClick={handleBtnClick}>
<span className={'text'}>{children}</span>
{loading ? <LoadingOutlined/> : icon}
</button>
)
}

View File

@ -0,0 +1,187 @@
import React, {useEffect, useMemo, useRef} from "react";
import {Checkbox, Popover} from "antd";
import {useBoolean, useClickAway} from "ahooks";
import {CaretUpOutlined} from "@ant-design/icons";
type ValueType = Id[][];
type ValueFunc = (prev:ValueType)=>ValueType;
const TagSelect = (props: {
options: OptionItem[];
onChange: (values: Id[][]) => void;
className?: string;
defaultSelectTags?: Id[][];
}) => {
const [selectValues, __setSelectValues] = React.useState<ValueType>([])
const [checkedAll, _setCheckedAll] = React.useState<boolean>(false)
const [visible, {set}] = useBoolean(false);
const allValues = useMemo(()=>{
const values:Id[][] = []
props.options.forEach(item=>{
if(item && item.children?.length){
// eslint-disable-next-line no-unsafe-optional-chaining
values.push(...(item.children?.map(c => [item.value, c.value])))
}
})
return values
},[props.options])
const _setSelectValues = (values: (ValueType | ValueFunc)) => {
if(typeof values == 'function'){
__setSelectValues((prev)=>{
const data = values(prev);
props.onChange(data)
return data
})
}else{
__setSelectValues(values)
props.onChange(values)
}
}
const handleAllChanged = (checked: boolean) => {
_setCheckedAll(checked)
const values:Id[][] = []
if (checked){
// set(false)
values.push(...allValues)
}
_setSelectValues(values)
}
const handleLevel1Change = (checked: boolean, item: OptionItem) => {
console.log('handleLevel1Change', checked, item)
if (checked) {
const values = selectValues.filter(s => s[0] != item.value)
const checkedIds: Id[][] = [];
item.children?.forEach(c => {
checkedIds.push([item.value, c.value])
})
const _values = [...values, ...checkedIds];
_setCheckedAll(_values.length == allValues.length)
_setSelectValues(_values)
} else {
_setCheckedAll(false)
const values = selectValues.filter(s => s[0] != item.value)
_setSelectValues([...values])
}
}
const level1Checked = (item: OptionItem) => {
// 完全没有选中
if (selectValues.findIndex(s => s[0] == item.value) == -1) return -1
const myList = selectValues.filter(s => s[0] == item.value)
// 只有1个但是全选或者选中元素和子元素个数相等
if (myList.length == item.children?.length) return 1;
return 0;
}
const handleLevel2Change = (checked: boolean, item: OptionItem, parent: OptionItem) => {
// 获取一级选项的选中状态
const parentList = selectValues.filter(s => s[0] == parent.value)
_setSelectValues((prev) => {
let values = [...prev]
if (checked) {
values = [...prev,[parent.value, item.value]]
} else {
_setCheckedAll(false)
if(parentList.length == 0){ // && parentList[0].length == 1
return prev;
}
values = values.filter(s => s.length == 1 || s[1] != item.value)
}
_setCheckedAll(values.length == allValues.length)
return values
})
}
const level2Checked = (item: OptionItem, parent: OptionItem) => {
// 获取一级选项的选中状态
const parentList = selectValues.filter(s => s[0] == parent.value)
// 没有找到父级
if (parentList.length == 0) return false;
if (parentList.length == 1) {
// 只有一个 且长度为0 说明是全选
if (parentList[0].length == 1) return true;
return parentList[0][1] == item.value;
}
return parentList.findIndex(s => s[1] == item.value) != -1;
}
const ref = useRef<HTMLDivElement|null>(null)
useClickAway(()=>{
set(false)
},ref)
useEffect(()=>{
if(props.defaultSelectTags){
__setSelectValues(props.defaultSelectTags)
}
//console.log('props.defaultSelectTags',props.defaultSelectTags)
},[props.defaultSelectTags])
return (<div ref={ref} className={`tag-select-container z-10 select-none relative group ${props.className}`}>
<div className="select-value w-[120px] flex justify-center items-center"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
set(!visible)
}}
>
<span>{checkedAll || selectValues.length == 0 ? '全部来源' : '来源'}</span>
<CaretUpOutlined className={`ml-2 arrow-icon ${visible ? 'rotate-0' : 'rotate-180'}`}/>
</div>
<div className={`options-list-container absolute ${visible ? 'block' : 'hidden'}`}>
<div className="inner">
<ul className="options-list">
<li className="select-option-item relative">
<div className="option-value whitespace-nowrap flex justify-between">
<span className="text-center flex-1"
onClick={() => handleAllChanged(!checkedAll)}></span>
<Checkbox className="ml-6" checked={checkedAll}
onChange={e => handleAllChanged(e.target.checked)}/>
</div>
</li>
{props.options.filter(s => s.value != 999999).map((option) => {
const checkStatus = level1Checked(option)
return (<li key={option.value} className="select-option-item relative">
{(option.children && option.children.length > 0) ?
<Popover placement="rightTop" trigger={['hover']}
rootClassName="tag-select-child-container" arrow={false}
content={option.children && <ul className="sub-options-list select-none" onClick={(e)=>{
e.preventDefault();
e.stopPropagation()
}}>
{option.children.map((subOption) => {
const myCheckStatus = level2Checked(subOption, option)
return (<li key={subOption.value}
className="sub-option-item select-option-item whitespace-nowrap">
<div
className="option-value whitespace-nowrap flex justify-between">
<span
onClick={() => {
handleLevel2Change(!myCheckStatus, subOption, option)
}}>{subOption.label}</span>
<Checkbox className="ml-6" checked={myCheckStatus}
onChange={e => handleLevel2Change(e.target.checked, subOption, option)}/>
</div>
</li>)
})}
</ul>}>
<div className="option-value whitespace-nowrap flex justify-between">
<span className="text-center flex-1" onClick={() => {
handleLevel1Change(checkStatus == 1 ? false:true, option)
}}>{option.label}</span>
<Checkbox className="ml-6"
checked={checkStatus == 1}
indeterminate={checkStatus == 0}
onChange={e => {
handleLevel1Change(e.target.checked, option)
}}/>
</div>
</Popover> : <div className="option-value whitespace-nowrap flex justify-between">
<span>{option.label}</span>
</div>
}
</li>)
})}
</ul>
</div>
</div>
</div>
)
}
export default TagSelect

View File

@ -0,0 +1,65 @@
import {useMemo, useState} from "react";
import {CaretUpOutlined} from "@ant-design/icons";
export type TimeSelectProps = {
value: number;
className?: string;
onChange: (value: number) => void;
}
type OptionItem = {
label: string;
value: number;
}
const AllTimeOption: OptionItem[] = [
{
label: '半小时内',
value: 1
},
{
label: '一小时内',
value: 2
},
{
label: '四小时内',
value: 3
},
{
label: '一天内',
value: 4
},
{
label: '近一周',
value: 5
},
{
label: '所有时间',
value: 0
}
]
const TimeSelect = (props: TimeSelectProps) => {
const selectLabel = useMemo(() => {
return AllTimeOption.find(item => item.value == props.value)?.label || ''
}, [props.value])
const [visible, setVisible] = useState<boolean>(false);
const handleClick = (item: OptionItem) => {
setVisible(false)
props.onChange(item.value)
}
return (<div className={`timer-select-container relative group ${props.className}`}
onMouseLeave={() => setVisible(false)}>
<div className={`timer-select-value flex items-center`} onMouseEnter={() => setVisible(true)}>
<div>
<span>{selectLabel}</span>
<CaretUpOutlined className={'ml-2 arrow-icon rotate-180 group-hover:rotate-0'} />
</div>
</div>
<div className={`timer-select-options z-10 ${visible ? 'block' : 'hidden'}`}>
{AllTimeOption.map((item, index) => {
return <div className={`timer-select-option-item ${item.value == props.value?'selected':''}`} key={index}
onClick={() => handleClick(item)}>{item.label}</div>
})}
</div>
</div>)
}
export default TimeSelect

View File

@ -1,407 +1,32 @@
import React from "react";
type IconProps = { style?: React.CSSProperties }
export const IconRise = ({style}: IconProps) => {
return (
<svg className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" style={style}>
<path
d="M928 361.6v182.4c0 19.2 12.8 32 32 32s32-12.8 32-32v-224c0-35.2-28.8-64-64-64h-224c-19.2 0-32 12.8-32 32s12.8 32 32 32h176l-272 272-204.8-204.8c-6.4-6.4-16-9.6-22.4-9.6-9.6 0-16 3.2-22.4 9.6l-316.8 316.8c-12.8 12.8-12.8 32 0 44.8 12.8 12.8 32 12.8 44.8 0l294.4-294.4 204.8 204.8c6.4 6.4 16 9.6 22.4 9.6 9.6 0 16-3.2 22.4-9.6l297.6-297.6z"
fill="currentColor"></path>
</svg>
)
type IconProps = { style?: React.CSSProperties; className?: string; }
}
export const IconDown = ({style}: IconProps) => {
return (
<svg className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" style={style}>
<path
d="M886.4 736h-182.4c-19.2 0-32 12.8-32 32s12.8 32 32 32h224c35.2 0 64-28.8 64-64v-224c0-19.2-12.8-32-32-32s-32 12.8-32 32v172.8l-297.6-297.6c-6.4-6.4-16-9.6-22.4-9.6-9.6 0-16 3.2-22.4 9.6l-204.8 204.8-294.4-294.4c-12.8-12.8-32-12.8-44.8 0-12.8 12.8-12.8 32 0 44.8l316.8 316.8c6.4 6.4 16 9.6 22.4 9.6 9.6 0 16-3.2 22.4-9.6l204.8-204.8 278.4 281.6z"
fill="currentColor"></path>
</svg>
)
}
export const IconChecked = ({style}: IconProps) => {
return (
<svg className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" style={style}>
<path
d="M369.792 704.32L930.304 128 1024 223.616 369.984 896l-20.288-20.864-0.128 0.128L0 516.8 96.128 423.68l273.664 280.64z"
fill="currentColor" p-id="4399"></path>
</svg>
)
}
export const IconCheckedFill = ({style}: IconProps) => {
return (
<svg className="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" style={style}>
<path
d="M512.45568 57.344c251.35104 0 455.11168 203.76064 455.11168 455.11168 0 251.35104-203.76064 455.11168-455.11168 455.11168C261.10464 967.56736 57.344 763.80672 57.344 512.45568 57.344 261.10464 261.10464 57.344 512.45568 57.344z m272.444416 288.876544c-5.842944-5.445632-16.21504-5.953536-22.995968-1.074176l-0.315392 0.232448-301.232128 228.097024c-5.515264 4.176896-15.601664 4.72064-21.655552 1.186816l-0.320512-0.193536-113.998848-71.015424c-7.031808-4.380672-16.93696-2.89792-22.390784 3.219456l-0.24576 0.2816-13.169664 15.497216c-5.553152 6.53312-4.62848 15.625216 1.519616 21.586944l0.27136 0.258048 140.93312 131.342336c10.247168 9.550848 26.529792 10.051584 36.615168 0.9728l0.313344-0.28672 323.310592-301.310976c6.679552-6.223872 7.225344-15.606784 1.036288-21.63712l-0.254976-0.241664-7.419904-6.915072z"
fill="currentColor"></path>
</svg>
)
}
export const IconLoading = ({size, color}: { size?: string | number, color?: string; }) => (
<svg xmlns="http://www.w3.org/2000/svg" style={{
margin: 'auto', display: 'block', ...(size ? {fontSize: size} : {}), ...(color ? {color} : {})
}} width="1em" height="1em" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" r="30" stroke="#6a6a6a" strokeWidth="15" fill="none"></circle>
<circle cx="50" cy="50" r="30" stroke="#aaaaaa" strokeWidth="15" strokeLinecap="round" fill="none">
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s"
values="0 50 50;180 50 50;720 50 50" keyTimes="0;0.5;1"></animateTransform>
<animate attributeName="stroke-dasharray" repeatCount="indefinite" dur="1s"
values="18.84955592153876 169.64600329384882;94.2477796076938 94.24777960769377;18.84955592153876 169.64600329384882"
keyTimes="0;0.5;1"></animate>
</circle>
</svg>)
export const IconQRCode = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 15 15">
<path
d="M40.022 29.257C39.8411 28.9562 38.2126 26.9064 37.1812 25.2328C36.8193 24.6499 36.6565 23.9541 36.6927 23.2771C36.8555 19.4785 35.6613 15.0406 33.7614 11.9942C33.11 10.9599 32.0425 9.5872 30.4864 8.28967C32.7662 4.22783 33.7071 1.16264 32.6758 0.260012C32.6396 0.241207 32.6215 0.222402 32.5853 0.184792C32.5672 0.184792 32.5672 0.165988 32.5491 0.165988C31.4815 -0.492181 28.9665 0.842961 25.7819 3.53205C23.3211 5.63819 20.4622 8.55293 17.5852 11.9566C16.7348 12.878 8.82758 22.0924 8.89996 23.8224C8.97233 25.6653 12.5369 23.4463 11.8131 24.0105C11.1979 24.4806 8.99043 25.6841 8.14 24.8191C7.28957 23.9353 8.41141 21.5659 10.4742 18.6135C13.134 14.8149 14.5634 13.2353 16.0653 11.4865C15.2872 11.2984 14.473 11.2044 13.6225 11.2044C10.7456 11.2044 8.1219 12.3327 6.13154 14.1756C8.70092 10.8847 12.5188 8.77859 16.8976 8.77859C17.4042 8.77859 17.8928 8.79739 18.3632 8.85381C19.7746 7.29301 21.204 5.84504 22.5611 4.56631C21.4573 4.35946 20.2631 4.22783 18.9965 4.22783C17.3319 4.22783 15.7577 4.41587 14.292 4.77317C11.0532 5.65699 7.00006 8.47771 4.684 12.107C1.66226 16.9023 1.35466 22.1864 3.07361 27.0193H3.09171C3.32693 27.6586 3.61644 28.2792 3.94214 28.8621C0.667082 34.2027 -0.816645 38.3586 0.449951 39.468C1.71655 40.5963 5.48015 38.3209 10.0399 34.0146C13.8578 30.3853 19.1051 23.9353 19.2137 21.6787C19.3222 19.4221 14.7806 22.0172 15.6672 21.359C16.4272 20.7949 18.9242 19.3845 19.9193 20.4752C20.9145 21.5659 19.4127 24.6687 17.8023 27.1509C16.3005 29.5015 14.2378 32.0025 11.9217 34.466C12.4826 34.56 13.0616 34.5976 13.6406 34.5976C19.8651 34.5976 24.9133 29.3511 24.9133 22.8822C24.9133 18.3878 22.4887 14.4953 18.9242 12.5207C20.4079 10.7719 21.4212 9.68122 21.4212 9.66242L21.5478 9.53078C26.759 11.4489 30.215 16.545 30.215 22.807C30.215 22.8258 31.4273 33.0744 21.9097 39.6373C20.0822 40.8972 19.5755 42.4204 19.847 44C24.0629 43.5675 26.6323 43.9436 31.0654 43.9624C34.25 43.9624 35.1004 43.0974 35.607 41.9503C36.1318 40.8032 35.3899 39.468 35.3899 39.1672C35.3899 38.8663 35.6613 38.697 36.2584 38.0389C36.8555 37.3807 36.9098 36.6285 36.7108 36.4029C36.5298 36.1772 36.1318 35.8387 36.3127 35.8011C37.3803 35.6883 37.706 35.237 37.4888 34.8797C37.2717 34.5224 36.566 33.5445 37.1631 32.8487C37.7602 32.153 39.2259 32.1718 40.0039 31.7017C40.9086 31.1751 40.5106 30.0656 40.022 29.257ZM2.15081 37.6252C1.98796 37.6252 1.8613 37.5876 1.77083 37.5123C1.33657 37.1362 1.44513 35.0865 4.57543 29.8776C5.60681 31.3444 6.98197 32.5479 8.53807 33.3753C5.64299 36.0268 3.27265 37.6252 2.15081 37.6252ZM15.3053 22.6377L14.871 22.7318C14.2558 22.8634 13.7854 23.3523 13.6587 23.9917L13.5683 24.443C13.5321 24.5934 13.333 24.5934 13.2969 24.443L13.2064 23.9917C13.0797 23.3523 12.6093 22.8634 11.9941 22.7318L11.5598 22.6377C11.4151 22.6001 11.4151 22.3933 11.5598 22.3557L11.9941 22.2616C12.6093 22.13 13.0797 21.6411 13.2064 21.0017L13.2969 20.5504C13.333 20.4 13.5321 20.4 13.5683 20.5504L13.6587 21.0017C13.7854 21.6411 14.2558 22.13 14.871 22.2616L15.3053 22.3557C15.4501 22.3933 15.4501 22.6001 15.3053 22.6377ZM29.6902 7.6315C28.5684 6.80409 27.2656 6.03309 25.7276 5.45014C29.2198 2.32854 30.6492 1.97125 31.1197 1.97125C31.2644 1.97125 31.373 2.00886 31.4454 2.06527C31.8434 2.42256 31.6263 4.09619 29.6902 7.6315Z"
fill="#F5222D"/>
</svg>
)
export const IconQuery = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 15 15">
export const IconNavigationArrow = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-nav-arrow`} style={style} xmlns="http://www.w3.org/2000/svg"
width="0.9em" height="1em" viewBox="0 0 25 29">
<path d="M24.75 14.2894L-1.24922e-06 28.5788L11.75 14.2891L0 -1.08186e-06L24.75 14.2894Z"
fill="url(#paint0_linear_1030_7835)"/>
<defs>
<clipPath id="master_svg0_1_3654/1_0572">
<rect x="0" y="0" width="15" height="15" rx="0"/>
</clipPath>
</defs>
<g clipPath="url(#master_svg0_1_3654/1_0572)">
<g>
<path
d="M8.4375,8.20313Q8.4375,8.2607,8.43186,8.31799Q8.426210000000001,8.37528,8.41498,8.431750000000001Q8.40375,8.48821,8.387039999999999,8.5433Q8.37033,8.59839,8.3483,8.65158Q8.32626,8.70477,8.29913,8.75554Q8.27199,8.80632,8.24,8.85418Q8.20802,8.90205,8.1715,8.94655Q8.13497,8.991060000000001,8.09427,9.03177Q8.053560000000001,9.07247,8.00905,9.109Q7.96455,9.14552,7.9166799999999995,9.1775Q7.8688199999999995,9.20949,7.81804,9.23663Q7.76727,9.26376,7.71408,9.2858Q7.66089,9.30783,7.6058,9.324539999999999Q7.5507100000000005,9.34125,7.49425,9.35248Q7.43778,9.363710000000001,7.38049,9.36936Q7.3232,9.375,7.26563,9.375Q7.20805,9.375,7.15076,9.36936Q7.093468,9.363710000000001,7.037003,9.35248Q6.980539,9.34125,6.925448,9.324539999999999Q6.870356,9.30783,6.817168,9.2858Q6.76398,9.26376,6.713207,9.23663Q6.662434,9.20949,6.614566,9.1775Q6.566698,9.14552,6.522195,9.109Q6.477693,9.07247,6.436984,9.03177Q6.396276,8.991060000000001,6.3597529999999995,8.94655Q6.323231,8.90205,6.291247,8.85418Q6.259262,8.80632,6.232123,8.75554Q6.204985,8.70477,6.1829537,8.65158Q6.1609224,8.59839,6.1442105,8.5433Q6.1274987,8.48821,6.1162672,8.431750000000001Q6.1050358,8.37528,6.0993929,8.31799Q6.09375,8.2607,6.09375,8.20313Q6.09375,8.14555,6.0993929,8.08826Q6.1050358,8.030968,6.1162672,7.974503Q6.1274987,7.918039,6.1442105,7.862948Q6.1609224,7.807856,6.1829537,7.754668Q6.204985,7.70148,6.232123,7.650707Q6.259262,7.599934,6.291247,7.552066Q6.323231,7.504198,6.3597529999999995,7.459695Q6.396276,7.415193,6.436984,7.374484Q6.477693,7.333776,6.522195,7.2972529999999995Q6.566698,7.260731,6.614566,7.228747Q6.662434,7.196762,6.713207,7.169623Q6.76398,7.142485,6.817168,7.1204537Q6.870356,7.0984224,6.925448,7.0817105Q6.980539,7.0649987,7.037003,7.0537672Q7.093468,7.0425358,7.15076,7.0368929Q7.20805,7.03125,7.26563,7.03125Q7.3232,7.03125,7.38049,7.0368929Q7.43778,7.0425358,7.49425,7.0537672Q7.5507100000000005,7.0649987,7.6058,7.0817105Q7.66089,7.0984224,7.71408,7.1204537Q7.76727,7.142485,7.81804,7.169623Q7.8688199999999995,7.196762,7.9166799999999995,7.228747Q7.96455,7.260731,8.00905,7.2972529999999995Q8.053560000000001,7.333776,8.09427,7.374484Q8.13497,7.415193,8.1715,7.459695Q8.20802,7.504198,8.24,7.552066Q8.27199,7.599934,8.29913,7.650707Q8.32626,7.70148,8.3483,7.754668Q8.37033,7.807856,8.387039999999999,7.862948Q8.40375,7.918039,8.41498,7.974503Q8.426210000000001,8.030968,8.43186,8.08826Q8.4375,8.14555,8.4375,8.20313Z"
fill="currentColor"/>
</g>
<g>
<path
d="M3.28125,0.9375C2.5046049999999997,0.9375,1.875,1.567105,1.875,2.34375L1.875,12.6563C1.875,13.4329,2.5046049999999997,14.0625,3.28125,14.0625L11.71875,14.0625C12.4954,14.0625,13.125,13.4329,13.125,12.6563L13.125,5.15622L10.3125,5.15622C9.53585,5.15622,8.90625,4.526619999999999,8.90625,3.74997L8.90625,0.9375L3.28125,0.9375ZM9.375,8.20312C9.375,8.61589,9.25644,9.00097,9.05152,9.32616L10.32722,10.60187L9.66431,11.2648L8.38859,9.98906C8.06342,10.19396,7.67836,10.3125,7.26562,10.3125C6.10065,10.3125,5.15625,9.3681,5.15625,8.20312C5.15625,7.03815,6.10065,6.09375,7.26562,6.09375C8.4306,6.09375,9.375,7.03815,9.375,8.20312Z"
fill="currentColor"/>
</g>
<g>
<path
d="M12.84958,3.55950103515625C13.02483,3.73431103515625,13.1238,3.97131103515625,13.12499,4.21872103515625L10.3125,4.21872103515625C10.053619,4.21872103515625,9.84375,4.008851035156249,9.84375,3.74997103515625L9.84375,0.93756103515625C10.088203,0.94027833515625,10.322109,1.03840803515625,10.495386,1.21125403515625L12.84958,3.55950103515625Z"
fill="currentColor"/>
</g>
</g>
</svg>
)
export const IconAiChat = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon icon-ai-chat'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 32 32">
<path fillRule="evenodd" clipRule="evenodd"
d="M16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 18.9091 0.776402 21.637 2.1333 23.9875V29.8666H8.01239C10.363 31.2236 13.0908 32 16 32Z"
fill="#F5222D"/>
<path fillRule="evenodd" clipRule="evenodd"
d="M16 32C24.8366 32 32 24.8366 32 16C32 7.16344 24.8366 0 16 0C7.16344 0 0 7.16344 0 16C0 18.9091 0.776402 21.637 2.1333 23.9875V29.8666H8.01239C10.363 31.2236 13.0908 32 16 32Z"
fill="url(#paint0_linear_145_39)"/>
<circle cx="8" cy="16" r="2" fill="white"/>
<circle cx="16" cy="16" r="2" fill="white"/>
<circle cx="24" cy="16" r="2" fill="white"/>
<defs>
<linearGradient id="paint0_linear_145_39" x1="28" y1="2.5332e-07" x2="2" y2="32"
<linearGradient id="paint0_linear_1030_7835" x1="27.5" y1="15" x2="1" y2="15"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
<stop stopColor="#1890FF"/>
<stop offset="1" stopColor="#8FC9FF"/>
</linearGradient>
</defs>
</svg>
)
export const IconAiIndex = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon icon-ai-chat'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 27 27">
<path
d="M22.8219 10.5025L16.5786 4.25927C16.4139 4.09457 16.1986 4.01349 15.9832 4.01349C15.7678 4.01349 15.5525 4.09457 15.3878 4.25927L11.3058 8.34373C10.9967 8.30825 10.6851 8.29305 10.3734 8.29305C8.51867 8.29305 6.66394 8.90369 5.14114 10.125C4.75094 10.4366 4.72053 11.0219 5.07273 11.3767L9.67661 15.9805L4.21884 21.4333C4.15196 21.4997 4.11069 21.5877 4.10229 21.6816L4.01614 22.6241C3.99334 22.8623 4.18337 23.065 4.41901 23.065C4.43168 23.065 4.44435 23.065 4.45702 23.0625L5.39959 22.9763C5.49334 22.9687 5.58202 22.9256 5.6479 22.8598L11.1057 17.402L15.7096 22.0059C15.8742 22.1706 16.0896 22.2517 16.305 22.2517C16.5508 22.2517 16.794 22.1452 16.9612 21.9375C18.3878 20.1562 18.9807 17.9265 18.74 15.7702L22.8219 11.6883C23.1487 11.364 23.1487 10.8319 22.8219 10.5025Z"
fill="#F5222D"/>
<path
d="M22.8219 10.5025L16.5786 4.25927C16.4139 4.09457 16.1986 4.01349 15.9832 4.01349C15.7678 4.01349 15.5525 4.09457 15.3878 4.25927L11.3058 8.34373C10.9967 8.30825 10.6851 8.29305 10.3734 8.29305C8.51867 8.29305 6.66394 8.90369 5.14114 10.125C4.75094 10.4366 4.72053 11.0219 5.07273 11.3767L9.67661 15.9805L4.21884 21.4333C4.15196 21.4997 4.11069 21.5877 4.10229 21.6816L4.01614 22.6241C3.99334 22.8623 4.18337 23.065 4.41901 23.065C4.43168 23.065 4.44435 23.065 4.45702 23.0625L5.39959 22.9763C5.49334 22.9687 5.58202 22.9256 5.6479 22.8598L11.1057 17.402L15.7096 22.0059C15.8742 22.1706 16.0896 22.2517 16.305 22.2517C16.5508 22.2517 16.794 22.1452 16.9612 21.9375C18.3878 20.1562 18.9807 17.9265 18.74 15.7702L22.8219 11.6883C23.1487 11.364 23.1487 10.8319 22.8219 10.5025Z"
fill="url(#paint0_linear_541_309)"/>
<defs>
<linearGradient id="paint0_linear_541_309" x1="20.6854" y1="4.01349" x2="5.20628" y2="23.066"
gradientUnits="userSpaceOnUse">
<stop stop-color="#FFCB7E"/>
<stop offset="1" stop-color="#F5222D"/>
</linearGradient>
</defs>
export const IconArrowRight = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-arrow-right`} style={style} width="1em" height="1em"
viewBox="0 0 20 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 11.5L9.01987e-07 23L9.49495 11.4997L1.90735e-06 -8.74228e-07L20 11.5Z" fill="currentColor"/>
</svg>
)
export const IconFileSearch = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon icon-file-search'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none"
version="1.1"
width="1em" height="1em" viewBox="0 0 32 32">
<path
d="M26.7062 9.02188C26.8937 9.20938 27 9.4625 27 9.72812V29C27 29.5531 26.5531 30 26 30H6C5.44687 30 5 29.5531 5 29V3C5 2.44687 5.44687 2 6 2H19.2719C19.5375 2 19.7938 2.10625 19.9813 2.29375L26.7062 9.02188ZM24.6938 10.1875L18.8125 4.30625V10.1875H24.6938ZM10 15.0625C9.9337 15.0625 9.87011 15.0888 9.82322 15.1357C9.77634 15.1826 9.75 15.2462 9.75 15.3125V16.8125C9.75 16.8788 9.77634 16.9424 9.82322 16.9893C9.87011 17.0362 9.9337 17.0625 10 17.0625H22C22.0663 17.0625 22.1299 17.0362 22.1768 16.9893C22.2237 16.9424 22.25 16.8788 22.25 16.8125V15.3125C22.25 15.2462 22.2237 15.1826 22.1768 15.1357C22.1299 15.0888 22.0663 15.0625 22 15.0625H10ZM10 19.3125C9.9337 19.3125 9.87011 19.3388 9.82322 19.3857C9.77634 19.4326 9.75 19.4962 9.75 19.5625V21.0625C9.75 21.1288 9.77634 21.1924 9.82322 21.2393C9.87011 21.2862 9.9337 21.3125 10 21.3125H15.75C15.8163 21.3125 15.8799 21.2862 15.9268 21.2393C15.9737 21.1924 16 21.1288 16 21.0625V19.5625C16 19.4962 15.9737 19.4326 15.9268 19.3857C15.8799 19.3388 15.8163 19.3125 15.75 19.3125H10Z"
fill="#F5222D"/>
<path
d="M26.7062 9.02188C26.8937 9.20938 27 9.4625 27 9.72812V29C27 29.5531 26.5531 30 26 30H6C5.44687 30 5 29.5531 5 29V3C5 2.44687 5.44687 2 6 2H19.2719C19.5375 2 19.7938 2.10625 19.9813 2.29375L26.7062 9.02188ZM24.6938 10.1875L18.8125 4.30625V10.1875H24.6938ZM10 15.0625C9.9337 15.0625 9.87011 15.0888 9.82322 15.1357C9.77634 15.1826 9.75 15.2462 9.75 15.3125V16.8125C9.75 16.8788 9.77634 16.9424 9.82322 16.9893C9.87011 17.0362 9.9337 17.0625 10 17.0625H22C22.0663 17.0625 22.1299 17.0362 22.1768 16.9893C22.2237 16.9424 22.25 16.8788 22.25 16.8125V15.3125C22.25 15.2462 22.2237 15.1826 22.1768 15.1357C22.1299 15.0888 22.0663 15.0625 22 15.0625H10ZM10 19.3125C9.9337 19.3125 9.87011 19.3388 9.82322 19.3857C9.77634 19.4326 9.75 19.4962 9.75 19.5625V21.0625C9.75 21.1288 9.77634 21.1924 9.82322 21.2393C9.87011 21.2862 9.9337 21.3125 10 21.3125H15.75C15.8163 21.3125 15.8799 21.2862 15.9268 21.2393C15.9737 21.1924 16 21.1288 16 21.0625V19.5625C16 19.4962 15.9737 19.4326 15.9268 19.3857C15.8799 19.3388 15.8163 19.3125 15.75 19.3125H10Z"
fill="url(#paint0_linear_253_170)"/>
<defs>
<linearGradient id="paint0_linear_253_170" x1="24.25" y1="2" x2="1.02087" y2="24.4633"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconProfile = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon icon-profile'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 32 32">
<path
d="M27.5 3.5H4.5C3.94687 3.5 3.5 3.94687 3.5 4.5V27.5C3.5 28.0531 3.94687 28.5 4.5 28.5H27.5C28.0531 28.5 28.5 28.0531 28.5 27.5V4.5C28.5 3.94687 28.0531 3.5 27.5 3.5ZM11.875 21.75C11.1844 21.75 10.625 21.1906 10.625 20.5C10.625 19.8094 11.1844 19.25 11.875 19.25C12.5656 19.25 13.125 19.8094 13.125 20.5C13.125 21.1906 12.5656 21.75 11.875 21.75ZM11.875 17.25C11.1844 17.25 10.625 16.6906 10.625 16C10.625 15.3094 11.1844 14.75 11.875 14.75C12.5656 14.75 13.125 15.3094 13.125 16C13.125 16.6906 12.5656 17.25 11.875 17.25ZM11.875 12.75C11.1844 12.75 10.625 12.1906 10.625 11.5C10.625 10.8094 11.1844 10.25 11.875 10.25C12.5656 10.25 13.125 10.8094 13.125 11.5C13.125 12.1906 12.5656 12.75 11.875 12.75ZM21.375 21.25C21.375 21.3875 21.2625 21.5 21.125 21.5H15.375C15.2375 21.5 15.125 21.3875 15.125 21.25V19.75C15.125 19.6125 15.2375 19.5 15.375 19.5H21.125C21.2625 19.5 21.375 19.6125 21.375 19.75V21.25ZM21.375 16.75C21.375 16.8875 21.2625 17 21.125 17H15.375C15.2375 17 15.125 16.8875 15.125 16.75V15.25C15.125 15.1125 15.2375 15 15.375 15H21.125C21.2625 15 21.375 15.1125 21.375 15.25V16.75ZM21.375 12.25C21.375 12.3875 21.2625 12.5 21.125 12.5H15.375C15.2375 12.5 15.125 12.3875 15.125 12.25V10.75C15.125 10.6125 15.2375 10.5 15.375 10.5H21.125C21.2625 10.5 21.375 10.6125 21.375 10.75V12.25Z"
fill="#F5222D"/>
<path
d="M27.5 3.5H4.5C3.94687 3.5 3.5 3.94687 3.5 4.5V27.5C3.5 28.0531 3.94687 28.5 4.5 28.5H27.5C28.0531 28.5 28.5 28.0531 28.5 27.5V4.5C28.5 3.94687 28.0531 3.5 27.5 3.5ZM11.875 21.75C11.1844 21.75 10.625 21.1906 10.625 20.5C10.625 19.8094 11.1844 19.25 11.875 19.25C12.5656 19.25 13.125 19.8094 13.125 20.5C13.125 21.1906 12.5656 21.75 11.875 21.75ZM11.875 17.25C11.1844 17.25 10.625 16.6906 10.625 16C10.625 15.3094 11.1844 14.75 11.875 14.75C12.5656 14.75 13.125 15.3094 13.125 16C13.125 16.6906 12.5656 17.25 11.875 17.25ZM11.875 12.75C11.1844 12.75 10.625 12.1906 10.625 11.5C10.625 10.8094 11.1844 10.25 11.875 10.25C12.5656 10.25 13.125 10.8094 13.125 11.5C13.125 12.1906 12.5656 12.75 11.875 12.75ZM21.375 21.25C21.375 21.3875 21.2625 21.5 21.125 21.5H15.375C15.2375 21.5 15.125 21.3875 15.125 21.25V19.75C15.125 19.6125 15.2375 19.5 15.375 19.5H21.125C21.2625 19.5 21.375 19.6125 21.375 19.75V21.25ZM21.375 16.75C21.375 16.8875 21.2625 17 21.125 17H15.375C15.2375 17 15.125 16.8875 15.125 16.75V15.25C15.125 15.1125 15.2375 15 15.375 15H21.125C21.2625 15 21.375 15.1125 21.375 15.25V16.75ZM21.375 12.25C21.375 12.3875 21.2625 12.5 21.125 12.5H15.375C15.2375 12.5 15.125 12.3875 15.125 12.25V10.75C15.125 10.6125 15.2375 10.5 15.375 10.5H21.125C21.2625 10.5 21.375 10.6125 21.375 10.75V12.25Z"
fill="url(#paint0_linear_253_60)"/>
<defs>
<linearGradient id="paint0_linear_253_60" x1="25.375" y1="3.5" x2="5.0625" y2="28.5"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconInteraction = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon icon-interaction'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none"
version="1.1" width="1em" height="1em" viewBox="0 0 32 32">
<path
d="M27.5 3.5H4.5C3.94687 3.5 3.5 3.94687 3.5 4.5V27.5C3.5 28.0531 3.94687 28.5 4.5 28.5H27.5C28.0531 28.5 28.5 28.0531 28.5 27.5V4.5C28.5 3.94687 28.0531 3.5 27.5 3.5ZM22.6875 18.3031C22.6875 20.0312 21.2906 21.4312 19.5719 21.4312H13.1438V23.1C13.1438 23.2781 12.9406 23.375 12.8031 23.2656L9.39375 20.5875C9.28438 20.5031 9.28438 20.3375 9.39375 20.2531L12.8031 17.575C12.9406 17.4656 13.1438 17.5656 13.1438 17.7406V19.4094H19.5719C20.1844 19.4094 20.6812 18.9094 20.6812 18.2969V15.8313C20.6812 15.7156 20.775 15.6187 20.8937 15.6187H22.4781C22.5938 15.6187 22.6906 15.7125 22.6906 15.8313V18.3031H22.6875ZM22.6063 11.7437L19.1969 14.4219C19.0594 14.5312 18.8563 14.4313 18.8563 14.2563V12.5875H12.4281C11.8156 12.5875 11.3188 13.0875 11.3188 13.7V16.1656C11.3188 16.2812 11.225 16.3781 11.1062 16.3781H9.52188C9.40625 16.3781 9.30937 16.2844 9.30937 16.1656V13.7C9.30937 11.9719 10.7063 10.5719 12.425 10.5719H18.8531V8.90312C18.8531 8.725 19.0563 8.62813 19.1938 8.7375L22.6031 11.4156C22.7156 11.4937 22.7156 11.6594 22.6063 11.7437Z"
fill="#F5222D"/>
<path
d="M27.5 3.5H4.5C3.94687 3.5 3.5 3.94687 3.5 4.5V27.5C3.5 28.0531 3.94687 28.5 4.5 28.5H27.5C28.0531 28.5 28.5 28.0531 28.5 27.5V4.5C28.5 3.94687 28.0531 3.5 27.5 3.5ZM22.6875 18.3031C22.6875 20.0312 21.2906 21.4312 19.5719 21.4312H13.1438V23.1C13.1438 23.2781 12.9406 23.375 12.8031 23.2656L9.39375 20.5875C9.28438 20.5031 9.28438 20.3375 9.39375 20.2531L12.8031 17.575C12.9406 17.4656 13.1438 17.5656 13.1438 17.7406V19.4094H19.5719C20.1844 19.4094 20.6812 18.9094 20.6812 18.2969V15.8313C20.6812 15.7156 20.775 15.6187 20.8937 15.6187H22.4781C22.5938 15.6187 22.6906 15.7125 22.6906 15.8313V18.3031H22.6875ZM22.6063 11.7437L19.1969 14.4219C19.0594 14.5312 18.8563 14.4313 18.8563 14.2563V12.5875H12.4281C11.8156 12.5875 11.3188 13.0875 11.3188 13.7V16.1656C11.3188 16.2812 11.225 16.3781 11.1062 16.3781H9.52188C9.40625 16.3781 9.30937 16.2844 9.30937 16.1656V13.7C9.30937 11.9719 10.7063 10.5719 12.425 10.5719H18.8531V8.90312C18.8531 8.725 19.0563 8.62813 19.1938 8.7375L22.6031 11.4156C22.7156 11.4937 22.7156 11.6594 22.6063 11.7437Z"
fill="url(#paint0_linear_253_68)"/>
<defs>
<linearGradient id="paint0_linear_253_68" x1="25.375" y1="3.5" x2="5.0625" y2="28.5"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconFire = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon icon-interaction'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none"
version="1.1" width="1em" height="1em" viewBox="0 0 29 29">
<path
d="M23.3009 13.2035C22.768 12.0041 21.9933 10.9276 21.0253 10.0413L20.2266 9.30843C20.1994 9.28423 20.1668 9.26706 20.1315 9.25842C20.0962 9.24978 20.0593 9.24993 20.0241 9.25888C19.9888 9.26782 19.9563 9.28528 19.9294 9.30971C19.9025 9.33414 19.882 9.36481 19.8697 9.39902L19.5129 10.4229C19.2905 11.0652 18.8816 11.7212 18.3024 12.3663C18.2639 12.4075 18.22 12.4184 18.1898 12.4212C18.1596 12.4239 18.113 12.4184 18.0718 12.38C18.0334 12.3471 18.0142 12.2977 18.0169 12.2483C18.1185 10.5958 17.6244 8.732 16.5429 6.70349C15.648 5.0181 14.4046 3.70328 12.8509 2.78647L11.7173 2.11946C11.5691 2.03162 11.3797 2.14691 11.3879 2.31984L11.4483 3.6374C11.4894 4.53774 11.3851 5.33377 11.1381 5.9953C10.8362 6.80506 10.4025 7.55717 9.84798 8.23242C9.4621 8.7017 9.02475 9.12617 8.54414 9.49784C7.38661 10.3876 6.4454 11.5278 5.79097 12.8329C5.13815 14.1494 4.7981 15.5989 4.7973 17.0684C4.7973 18.364 5.05258 19.6184 5.55765 20.8015C6.04533 21.9405 6.74911 22.9743 7.63007 23.8456C8.51943 24.724 9.55153 25.4157 10.7017 25.8961C11.893 26.3956 13.1556 26.6482 14.4595 26.6482C15.7633 26.6482 17.026 26.3956 18.2173 25.8988C19.3646 25.4213 20.4079 24.7248 21.2889 23.8483C22.1782 22.97 22.8754 21.9434 23.3613 20.8042C23.8656 19.6244 24.1243 18.3542 24.1216 17.0711C24.1216 15.7316 23.8471 14.4305 23.3009 13.2035Z"
fill="#F5222D"/>
<path
d="M23.3009 13.2035C22.768 12.0041 21.9933 10.9276 21.0253 10.0413L20.2266 9.30843C20.1994 9.28423 20.1668 9.26706 20.1315 9.25842C20.0962 9.24978 20.0593 9.24993 20.0241 9.25888C19.9888 9.26782 19.9563 9.28528 19.9294 9.30971C19.9025 9.33414 19.882 9.36481 19.8697 9.39902L19.5129 10.4229C19.2905 11.0652 18.8816 11.7212 18.3024 12.3663C18.2639 12.4075 18.22 12.4184 18.1898 12.4212C18.1596 12.4239 18.113 12.4184 18.0718 12.38C18.0334 12.3471 18.0142 12.2977 18.0169 12.2483C18.1185 10.5958 17.6244 8.732 16.5429 6.70349C15.648 5.0181 14.4046 3.70328 12.8509 2.78647L11.7173 2.11946C11.5691 2.03162 11.3797 2.14691 11.3879 2.31984L11.4483 3.6374C11.4894 4.53774 11.3851 5.33377 11.1381 5.9953C10.8362 6.80506 10.4025 7.55717 9.84798 8.23242C9.4621 8.7017 9.02475 9.12617 8.54414 9.49784C7.38661 10.3876 6.4454 11.5278 5.79097 12.8329C5.13815 14.1494 4.7981 15.5989 4.7973 17.0684C4.7973 18.364 5.05258 19.6184 5.55765 20.8015C6.04533 21.9405 6.74911 22.9743 7.63007 23.8456C8.51943 24.724 9.55153 25.4157 10.7017 25.8961C11.893 26.3956 13.1556 26.6482 14.4595 26.6482C15.7633 26.6482 17.026 26.3956 18.2173 25.8988C19.3646 25.4213 20.4079 24.7248 21.2889 23.8483C22.1782 22.97 22.8754 21.9434 23.3613 20.8042C23.8656 19.6244 24.1243 18.3542 24.1216 17.0711C24.1216 15.7316 23.8471 14.4305 23.3009 13.2035Z"
fill="url(#paint0_linear_541_295)"/>
<defs>
<linearGradient id="paint0_linear_541_295" x1="21.7061" y1="2.08929" x2="1.3308" y2="21.8216"
gradientUnits="userSpaceOnUse">
<stop stop-color="#FFCB7E"/>
<stop offset="1" stop-color="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconAISearch = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon icon-ai-search'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none"
version="1.1" width="1em" height="1em" viewBox="0 0 32 32">
<path
d="M27.0906 5.30937L16.4719 1.69062C16.3437 1.64687 16.1719 1.625 16 1.625C15.8281 1.625 15.6562 1.64687 15.5281 1.69062L4.90938 5.30937C4.65 5.39687 4.4375 5.69688 4.4375 5.97187V21.0469C4.4375 21.3219 4.61563 21.6844 4.83125 21.8562L15.6031 30.25C15.7125 30.3344 15.8531 30.3781 15.9969 30.3781C16.1406 30.3781 16.2844 30.3344 16.3906 30.25L27.1625 21.8562C27.3781 21.6875 27.5562 21.325 27.5562 21.0469V5.97187C27.5625 5.69688 27.35 5.4 27.0906 5.30937ZM19.5875 17.3125C18.0719 18.8281 15.7438 19.0375 14.0063 17.9406L11.5844 20.3625C11.5374 20.409 11.4739 20.4351 11.4078 20.4351C11.3417 20.4351 11.2782 20.409 11.2312 20.3625L10.1687 19.3C10.1222 19.253 10.0961 19.1896 10.0961 19.1234C10.0961 19.0573 10.1222 18.9939 10.1687 18.9469L12.5906 16.525C11.4937 14.7844 11.7031 12.4594 13.2188 10.9437C14.9781 9.18437 17.8281 9.18437 19.5875 10.9437C21.3469 12.7031 21.3469 15.5531 19.5875 17.3125ZM14.6331 15.8978C14.8649 16.1332 15.141 16.3205 15.4455 16.4487C15.7499 16.5769 16.0768 16.6436 16.4072 16.6448C16.7375 16.6461 17.0649 16.582 17.3704 16.4562C17.6758 16.3303 17.9534 16.1453 18.187 15.9116C18.4205 15.678 18.6056 15.4005 18.7314 15.095C18.8572 14.7895 18.9213 14.4621 18.92 14.1318C18.9187 13.8014 18.852 13.4746 18.7237 13.1701C18.5955 12.8656 18.4083 12.5896 18.1728 12.3578C17.941 12.1224 17.665 11.9352 17.3605 11.807C17.056 11.6787 16.7291 11.6121 16.3988 11.6108C16.0684 11.6095 15.7411 11.6736 15.4356 11.7995C15.1301 11.9253 14.8526 12.1104 14.619 12.344C14.3854 12.5776 14.2003 12.8552 14.0745 13.1606C13.9487 13.4661 13.8846 13.7935 13.8859 14.1238C13.8873 14.4542 13.954 14.7811 14.0822 15.0855C14.2104 15.39 14.3977 15.6661 14.6331 15.8978Z"
fill="#F5222D"/>
<path
d="M27.0906 5.30937L16.4719 1.69062C16.3437 1.64687 16.1719 1.625 16 1.625C15.8281 1.625 15.6562 1.64687 15.5281 1.69062L4.90938 5.30937C4.65 5.39687 4.4375 5.69688 4.4375 5.97187V21.0469C4.4375 21.3219 4.61563 21.6844 4.83125 21.8562L15.6031 30.25C15.7125 30.3344 15.8531 30.3781 15.9969 30.3781C16.1406 30.3781 16.2844 30.3344 16.3906 30.25L27.1625 21.8562C27.3781 21.6875 27.5562 21.325 27.5562 21.0469V5.97187C27.5625 5.69688 27.35 5.4 27.0906 5.30937ZM19.5875 17.3125C18.0719 18.8281 15.7438 19.0375 14.0063 17.9406L11.5844 20.3625C11.5374 20.409 11.4739 20.4351 11.4078 20.4351C11.3417 20.4351 11.2782 20.409 11.2312 20.3625L10.1687 19.3C10.1222 19.253 10.0961 19.1896 10.0961 19.1234C10.0961 19.0573 10.1222 18.9939 10.1687 18.9469L12.5906 16.525C11.4937 14.7844 11.7031 12.4594 13.2188 10.9437C14.9781 9.18437 17.8281 9.18437 19.5875 10.9437C21.3469 12.7031 21.3469 15.5531 19.5875 17.3125ZM14.6331 15.8978C14.8649 16.1332 15.141 16.3205 15.4455 16.4487C15.7499 16.5769 16.0768 16.6436 16.4072 16.6448C16.7375 16.6461 17.0649 16.582 17.3704 16.4562C17.6758 16.3303 17.9534 16.1453 18.187 15.9116C18.4205 15.678 18.6056 15.4005 18.7314 15.095C18.8572 14.7895 18.9213 14.4621 18.92 14.1318C18.9187 13.8014 18.852 13.4746 18.7237 13.1701C18.5955 12.8656 18.4083 12.5896 18.1728 12.3578C17.941 12.1224 17.665 11.9352 17.3605 11.807C17.056 11.6787 16.7291 11.6121 16.3988 11.6108C16.0684 11.6095 15.7411 11.6736 15.4356 11.7995C15.1301 11.9253 14.8526 12.1104 14.619 12.344C14.3854 12.5776 14.2003 12.8552 14.0745 13.1606C13.9487 13.4661 13.8846 13.7935 13.8859 14.1238C13.8873 14.4542 13.954 14.7811 14.0822 15.0855C14.2104 15.39 14.3977 15.6661 14.6331 15.8978Z"
fill="url(#paint0_linear_253_116)"/>
<defs>
<linearGradient id="paint0_linear_253_116" x1="24.6665" y1="1.625" x2="0.80049" y2="25.2428"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconAIQuestion = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon icon-ai-question'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none"
version="1.1" width="1em" height="1em" viewBox="0 0 32 32">
<path
d="M26.7062 9.02188C26.8937 9.20938 27 9.4625 27 9.72812V29C27 29.5531 26.5531 30 26 30H6C5.44687 30 5 29.5531 5 29V3C5 2.44687 5.44687 2 6 2H19.2719C19.5375 2 19.7938 2.10625 19.9813 2.29375L26.7062 9.02188ZM24.6938 10.1875L18.8125 4.30625V10.1875H24.6938ZM12.5625 17.1562C12.5625 17.325 12.7 17.4531 12.8687 17.4531H13.8813C14.05 17.4531 14.1875 17.3219 14.1875 17.1594C14.1875 16.2781 14.9937 15.5469 16 15.5469C17.0063 15.5469 17.8125 16.2781 17.8125 17.1562C17.8125 17.9469 17.1562 18.6313 16.2719 18.7469C15.6688 18.8344 15.1938 19.3813 15.1875 20V21C15.1875 21.1719 15.3281 21.3125 15.5 21.3125H16.5C16.6719 21.3125 16.8125 21.1719 16.8125 21V20.6187C16.8125 20.4312 16.9375 20.2594 17.1156 20.2031C18.5094 19.7531 19.4594 18.5156 19.4375 17.1125C19.4125 15.3781 17.9 13.9625 16.0469 13.9375C14.1281 13.9156 12.5625 15.3625 12.5625 17.1562ZM16 24.25C16.2652 24.25 16.5196 24.1446 16.7071 23.9571C16.8946 23.7696 17 23.5152 17 23.25C17 22.9848 16.8946 22.7304 16.7071 22.5429C16.5196 22.3554 16.2652 22.25 16 22.25C15.7348 22.25 15.4804 22.3554 15.2929 22.5429C15.1054 22.7304 15 22.9848 15 23.25C15 23.5152 15.1054 23.7696 15.2929 23.9571C15.4804 24.1446 15.7348 24.25 16 24.25Z"
fill="#F5222D"/>
<path
d="M26.7062 9.02188C26.8937 9.20938 27 9.4625 27 9.72812V29C27 29.5531 26.5531 30 26 30H6C5.44687 30 5 29.5531 5 29V3C5 2.44687 5.44687 2 6 2H19.2719C19.5375 2 19.7938 2.10625 19.9813 2.29375L26.7062 9.02188ZM24.6938 10.1875L18.8125 4.30625V10.1875H24.6938ZM12.5625 17.1562C12.5625 17.325 12.7 17.4531 12.8687 17.4531H13.8813C14.05 17.4531 14.1875 17.3219 14.1875 17.1594C14.1875 16.2781 14.9937 15.5469 16 15.5469C17.0063 15.5469 17.8125 16.2781 17.8125 17.1562C17.8125 17.9469 17.1562 18.6313 16.2719 18.7469C15.6688 18.8344 15.1938 19.3813 15.1875 20V21C15.1875 21.1719 15.3281 21.3125 15.5 21.3125H16.5C16.6719 21.3125 16.8125 21.1719 16.8125 21V20.6187C16.8125 20.4312 16.9375 20.2594 17.1156 20.2031C18.5094 19.7531 19.4594 18.5156 19.4375 17.1125C19.4125 15.3781 17.9 13.9625 16.0469 13.9375C14.1281 13.9156 12.5625 15.3625 12.5625 17.1562ZM16 24.25C16.2652 24.25 16.5196 24.1446 16.7071 23.9571C16.8946 23.7696 17 23.5152 17 23.25C17 22.9848 16.8946 22.7304 16.7071 22.5429C16.5196 22.3554 16.2652 22.25 16 22.25C15.7348 22.25 15.4804 22.3554 15.2929 22.5429C15.1054 22.7304 15 22.9848 15 23.25C15 23.5152 15.1054 23.7696 15.2929 23.9571C15.4804 24.1446 15.7348 24.25 16 24.25Z"
fill="url(#paint0_linear_253_172)"/>
<defs>
<linearGradient id="paint0_linear_253_172" x1="24.25" y1="2" x2="1.02087" y2="24.4633"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconSummary = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon icon-summary'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none"
version="1.1" width="1em" height="1em" viewBox="0 0 32 32">
<path
d="M29 7H24V5.25C24 5.1125 23.8875 5 23.75 5H22C21.8625 5 21.75 5.1125 21.75 5.25V7H17.125V5.25C17.125 5.1125 17.0125 5 16.875 5H15.125C14.9875 5 14.875 5.1125 14.875 5.25V7H10.25V5.25C10.25 5.1125 10.1375 5 10 5H8.25C8.1125 5 8 5.1125 8 5.25V7H3C2.44687 7 2 7.44688 2 8V26C2 26.5531 2.44687 27 3 27H29C29.5531 27 30 26.5531 30 26V8C30 7.44688 29.5531 7 29 7ZM13.25 21.5C13.25 21.6375 13.1375 21.75 13 21.75H7.25C7.1125 21.75 7 21.6375 7 21.5V20C7 19.8625 7.1125 19.75 7.25 19.75H13C13.1375 19.75 13.25 19.8625 13.25 20V21.5ZM13.25 17.25C13.25 17.3875 13.1375 17.5 13 17.5H7.25C7.1125 17.5 7 17.3875 7 17.25V15.75C7 15.6125 7.1125 15.5 7.25 15.5H13C13.1375 15.5 13.25 15.6125 13.25 15.75V17.25ZM24.9531 14.3969L19.7969 21.5438C19.7508 21.6076 19.6903 21.6597 19.6202 21.6956C19.5501 21.7314 19.4725 21.7502 19.3937 21.7502C19.315 21.7502 19.2374 21.7314 19.1673 21.6956C19.0972 21.6597 19.0367 21.6076 18.9906 21.5438L15.4219 16.6C15.3031 16.4344 15.4219 16.2031 15.625 16.2031H17.3406C17.5 16.2031 17.65 16.2812 17.7437 16.4094L19.3937 18.6938L22.6344 14.2031C22.7281 14.0719 22.8781 13.9969 23.0375 13.9969H24.75C24.9531 14 25.0719 14.2313 24.9531 14.3969Z"
fill="#F5222D"/>
<path
d="M29 7H24V5.25C24 5.1125 23.8875 5 23.75 5H22C21.8625 5 21.75 5.1125 21.75 5.25V7H17.125V5.25C17.125 5.1125 17.0125 5 16.875 5H15.125C14.9875 5 14.875 5.1125 14.875 5.25V7H10.25V5.25C10.25 5.1125 10.1375 5 10 5H8.25C8.1125 5 8 5.1125 8 5.25V7H3C2.44687 7 2 7.44688 2 8V26C2 26.5531 2.44687 27 3 27H29C29.5531 27 30 26.5531 30 26V8C30 7.44688 29.5531 7 29 7ZM13.25 21.5C13.25 21.6375 13.1375 21.75 13 21.75H7.25C7.1125 21.75 7 21.6375 7 21.5V20C7 19.8625 7.1125 19.75 7.25 19.75H13C13.1375 19.75 13.25 19.8625 13.25 20V21.5ZM13.25 17.25C13.25 17.3875 13.1375 17.5 13 17.5H7.25C7.1125 17.5 7 17.3875 7 17.25V15.75C7 15.6125 7.1125 15.5 7.25 15.5H13C13.1375 15.5 13.25 15.6125 13.25 15.75V17.25ZM24.9531 14.3969L19.7969 21.5438C19.7508 21.6076 19.6903 21.6597 19.6202 21.6956C19.5501 21.7314 19.4725 21.7502 19.3937 21.7502C19.315 21.7502 19.2374 21.7314 19.1673 21.6956C19.0972 21.6597 19.0367 21.6076 18.9906 21.5438L15.4219 16.6C15.3031 16.4344 15.4219 16.2031 15.625 16.2031H17.3406C17.5 16.2031 17.65 16.2812 17.7437 16.4094L19.3937 18.6938L22.6344 14.2031C22.7281 14.0719 22.8781 13.9969 23.0375 13.9969H24.75C24.9531 14 25.0719 14.2313 24.9531 14.3969Z"
fill="url(#paint0_linear_253_64)"/>
<defs>
<linearGradient id="paint0_linear_253_64" x1="26.5" y1="5" x2="9.93478" y2="30.9483"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconAIPPT = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon icon-ai-ppt'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none"
version="1.1" width="1em" height="1em" viewBox="0 0 32 32">
<path
d="M26.7062 9.02188C26.8937 9.20938 27 9.4625 27 9.72812V29C27 29.5531 26.5531 30 26 30H6C5.44687 30 5 29.5531 5 29V3C5 2.44687 5.44687 2 6 2H19.2719C19.5375 2 19.7938 2.10625 19.9813 2.29375L26.7062 9.02188ZM24.6938 10.1875L18.8125 4.30625V10.1875H24.6938ZM14.6416 23.75V20.8894H16.4937C18.3866 20.8894 19.625 19.6503 19.625 17.8231C19.625 16.0038 18.3881 14.75 16.5006 14.75H13.25C13.1505 14.75 13.0552 14.7895 12.9848 14.8598C12.9145 14.9302 12.875 15.0255 12.875 15.125V23.75C12.875 23.8495 12.9145 23.9448 12.9848 24.0152C13.0552 24.0855 13.1505 24.125 13.25 24.125H14.2666C14.366 24.125 14.4614 24.0855 14.5317 24.0152C14.6021 23.9448 14.6416 23.8495 14.6416 23.75ZM14.6416 19.3959H15.7322C17.2266 19.3959 17.8319 18.9919 17.8319 17.8231C17.8319 16.8216 17.2656 16.2569 16.2734 16.2569H14.6416V19.3959Z"
fill="#F5222D"/>
<path
d="M26.7062 9.02188C26.8937 9.20938 27 9.4625 27 9.72812V29C27 29.5531 26.5531 30 26 30H6C5.44687 30 5 29.5531 5 29V3C5 2.44687 5.44687 2 6 2H19.2719C19.5375 2 19.7938 2.10625 19.9813 2.29375L26.7062 9.02188ZM24.6938 10.1875L18.8125 4.30625V10.1875H24.6938ZM14.6416 23.75V20.8894H16.4937C18.3866 20.8894 19.625 19.6503 19.625 17.8231C19.625 16.0038 18.3881 14.75 16.5006 14.75H13.25C13.1505 14.75 13.0552 14.7895 12.9848 14.8598C12.9145 14.9302 12.875 15.0255 12.875 15.125V23.75C12.875 23.8495 12.9145 23.9448 12.9848 24.0152C13.0552 24.0855 13.1505 24.125 13.25 24.125H14.2666C14.366 24.125 14.4614 24.0855 14.5317 24.0152C14.6021 23.9448 14.6416 23.8495 14.6416 23.75ZM14.6416 19.3959H15.7322C17.2266 19.3959 17.8319 18.9919 17.8319 17.8231C17.8319 16.8216 17.2656 16.2569 16.2734 16.2569H14.6416V19.3959Z"
fill="url(#paint0_linear_253_52)"/>
<defs>
<linearGradient id="paint0_linear_253_52" x1="24.25" y1="2" x2="1.02087" y2="24.4633"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconArticleReview = ({style}: { style?: React.CSSProperties }) => (
<svg className={'svg-icon icon-article-review'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none"
version="1.1" width="1em" height="1em" viewBox="0 0 32 32">
<path fillRule="evenodd" clipRule="evenodd"
d="M27 9.72813C27 9.4625 26.8937 9.20938 26.7062 9.02188L19.9813 2.29375C19.7938 2.10625 19.5375 2 19.2719 2H6C5.44688 2 5 2.44688 5 3V29C5 29.5531 5.44688 30 6 30H26C26.5531 30 27 29.5531 27 29V9.72813ZM18.8125 4.30625L24.6938 10.1875H18.8125V4.30625ZM8.04545 13.1843C8.09233 13.1375 8.15592 13.1111 8.22222 13.1111H20.2222C20.2885 13.1111 20.3521 13.1375 20.399 13.1843C20.4459 13.2312 20.4722 13.2948 20.4722 13.3611V14.8611C20.4722 14.9274 20.4459 14.991 20.399 15.0379C20.3521 15.0848 20.2885 15.1111 20.2222 15.1111H8.22222C8.15592 15.1111 8.09233 15.0848 8.04545 15.0379C7.99856 14.991 7.97222 14.9274 7.97222 14.8611V13.3611C7.97222 13.2948 7.99856 13.2312 8.04545 13.1843ZM8.04545 18.4968C8.09233 18.45 8.15592 18.4236 8.22222 18.4236H13.9722C14.0385 18.4236 14.1021 18.45 14.149 18.4968C14.1959 18.5437 14.2222 18.6073 14.2222 18.6736V20.1736C14.2222 20.2399 14.1959 20.3035 14.149 20.3504C14.1021 20.3973 14.0385 20.4236 13.9722 20.4236H8.22222C8.15592 20.4236 8.09233 20.3973 8.04545 20.3504C7.99856 20.3035 7.97222 20.2399 7.97222 20.1736V18.6736C7.97222 18.6073 7.99856 18.5437 8.04545 18.4968ZM24.8524 19.6923L19.7174 26.5129C19.6813 26.5605 19.6341 26.5993 19.5795 26.6261C19.525 26.6528 19.4646 26.6667 19.4035 26.6667C19.3423 26.6667 19.2819 26.6528 19.2274 26.6261C19.1728 26.5993 19.1256 26.5605 19.0895 26.5129L16.0365 22.4583C15.9448 22.3348 16.0365 22.1623 16.1935 22.1623H17.5268C17.65 22.1623 17.7683 22.2206 17.8408 22.3161L19.4035 24.39L23.0481 19.5478C23.1206 19.4522 23.2365 19.394 23.3621 19.394H24.6954C24.8524 19.3963 24.9441 19.5688 24.8524 19.6923Z"
fill="#F5222D"/>
<path fillRule="evenodd" clipRule="evenodd"
d="M27 9.72813C27 9.4625 26.8937 9.20938 26.7062 9.02188L19.9813 2.29375C19.7938 2.10625 19.5375 2 19.2719 2H6C5.44688 2 5 2.44688 5 3V29C5 29.5531 5.44688 30 6 30H26C26.5531 30 27 29.5531 27 29V9.72813ZM18.8125 4.30625L24.6938 10.1875H18.8125V4.30625ZM8.04545 13.1843C8.09233 13.1375 8.15592 13.1111 8.22222 13.1111H20.2222C20.2885 13.1111 20.3521 13.1375 20.399 13.1843C20.4459 13.2312 20.4722 13.2948 20.4722 13.3611V14.8611C20.4722 14.9274 20.4459 14.991 20.399 15.0379C20.3521 15.0848 20.2885 15.1111 20.2222 15.1111H8.22222C8.15592 15.1111 8.09233 15.0848 8.04545 15.0379C7.99856 14.991 7.97222 14.9274 7.97222 14.8611V13.3611C7.97222 13.2948 7.99856 13.2312 8.04545 13.1843ZM8.04545 18.4968C8.09233 18.45 8.15592 18.4236 8.22222 18.4236H13.9722C14.0385 18.4236 14.1021 18.45 14.149 18.4968C14.1959 18.5437 14.2222 18.6073 14.2222 18.6736V20.1736C14.2222 20.2399 14.1959 20.3035 14.149 20.3504C14.1021 20.3973 14.0385 20.4236 13.9722 20.4236H8.22222C8.15592 20.4236 8.09233 20.3973 8.04545 20.3504C7.99856 20.3035 7.97222 20.2399 7.97222 20.1736V18.6736C7.97222 18.6073 7.99856 18.5437 8.04545 18.4968ZM24.8524 19.6923L19.7174 26.5129C19.6813 26.5605 19.6341 26.5993 19.5795 26.6261C19.525 26.6528 19.4646 26.6667 19.4035 26.6667C19.3423 26.6667 19.2819 26.6528 19.2274 26.6261C19.1728 26.5993 19.1256 26.5605 19.0895 26.5129L16.0365 22.4583C15.9448 22.3348 16.0365 22.1623 16.1935 22.1623H17.5268C17.65 22.1623 17.7683 22.2206 17.8408 22.3161L19.4035 24.39L23.0481 19.5478C23.1206 19.4522 23.2365 19.394 23.3621 19.394H24.6954C24.8524 19.3963 24.9441 19.5688 24.8524 19.6923Z"
fill="url(#paint0_linear_253_91)"/>
<defs>
<linearGradient id="paint0_linear_253_91" x1="24.25" y1="2" x2="1.02087" y2="24.4633"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconDataManagement = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-data-management`} style={style} xmlns="http://www.w3.org/2000/svg" fill="none"
version="1.1" width="1em" height="1em" viewBox="0 0 32 32">
<path
d="M26 2H6C5.44687 2 5 2.44687 5 3V10H27V3C27 2.44687 26.5531 2 26 2ZM9 7.25C8.30938 7.25 7.75 6.69063 7.75 6C7.75 5.30938 8.30938 4.75 9 4.75C9.69063 4.75 10.25 5.30938 10.25 6C10.25 6.69063 9.69063 7.25 9 7.25ZM5 29C5 29.5531 5.44687 30 6 30H26C26.5531 30 27 29.5531 27 29V22H5V29ZM9 24.75C9.69063 24.75 10.25 25.3094 10.25 26C10.25 26.6906 9.69063 27.25 9 27.25C8.30938 27.25 7.75 26.6906 7.75 26C7.75 25.3094 8.30938 24.75 9 24.75ZM5 20H27V12H5V20ZM9 14.75C9.69063 14.75 10.25 15.3094 10.25 16C10.25 16.6906 9.69063 17.25 9 17.25C8.30938 17.25 7.75 16.6906 7.75 16C7.75 15.3094 8.30938 14.75 9 14.75Z"
fill="#F5222D"/>
<path
d="M26 2H6C5.44687 2 5 2.44687 5 3V10H27V3C27 2.44687 26.5531 2 26 2ZM9 7.25C8.30938 7.25 7.75 6.69063 7.75 6C7.75 5.30938 8.30938 4.75 9 4.75C9.69063 4.75 10.25 5.30938 10.25 6C10.25 6.69063 9.69063 7.25 9 7.25ZM5 29C5 29.5531 5.44687 30 6 30H26C26.5531 30 27 29.5531 27 29V22H5V29ZM9 24.75C9.69063 24.75 10.25 25.3094 10.25 26C10.25 26.6906 9.69063 27.25 9 27.25C8.30938 27.25 7.75 26.6906 7.75 26C7.75 25.3094 8.30938 24.75 9 24.75ZM5 20H27V12H5V20ZM9 14.75C9.69063 14.75 10.25 15.3094 10.25 16C10.25 16.6906 9.69063 17.25 9 17.25C8.30938 17.25 7.75 16.6906 7.75 16C7.75 15.3094 8.30938 14.75 9 14.75Z"
fill="url(#paint0_linear_253_47)"/>
<defs>
<linearGradient id="paint0_linear_253_47" x1="24.25" y1="2" x2="1.02087" y2="24.4633"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconIdCard = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-id-card`} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 32 32">
<path
d="M11.6562 12.8438C10.7656 12.8438 10.0406 13.5719 10.0406 14.4688C10.0406 15.3656 10.7656 16.0938 11.6562 16.0938C12.5469 16.0938 13.2719 15.3656 13.2719 14.4688C13.2719 13.5719 12.5469 12.8438 11.6562 12.8438ZM29 5H3C2.44687 5 2 5.44687 2 6V26C2 26.5531 2.44687 27 3 27H29C29.5531 27 30 26.5531 30 26V6C30 5.44687 29.5531 5 29 5ZM19 13.125C19 12.9875 19.0312 12.875 19.0719 12.875H22.9281C22.9688 12.875 23 12.9875 23 13.125V14.625C23 14.7625 22.9688 14.875 22.9281 14.875H19.0719C19.0312 14.875 19 14.7625 19 14.625V13.125ZM16.3125 21.0312H14.9406C14.8094 21.0312 14.7031 20.9281 14.6938 20.7969C14.575 19.2188 13.2563 17.9688 11.6562 17.9688C10.0562 17.9688 8.7375 19.2188 8.61875 20.7969C8.60937 20.9281 8.50312 21.0312 8.37187 21.0312H7C6.96609 21.0313 6.93253 21.0244 6.90135 21.0111C6.87017 20.9978 6.84204 20.9782 6.81865 20.9537C6.79527 20.9291 6.77712 20.9001 6.76532 20.8683C6.75352 20.8365 6.7483 20.8026 6.75 20.7687C6.8375 19.1031 7.75 17.6531 9.08125 16.8281C8.49418 16.1828 8.16979 15.3412 8.17188 14.4688C8.17188 12.5344 9.73125 10.9688 11.6531 10.9688C13.575 10.9688 15.1344 12.5344 15.1344 14.4688C15.1344 15.3781 14.7906 16.2031 14.225 16.8281C15.5594 17.6562 16.4688 19.1031 16.5562 20.7687C16.5687 20.9125 16.4563 21.0312 16.3125 21.0312ZM25.0281 19.375H19.2219C19.1 19.375 19 19.2625 19 19.125V17.625C19 17.4875 19.1 17.375 19.2219 17.375H25.025C25.1469 17.375 25.2469 17.4875 25.2469 17.625V19.125H25.25C25.25 19.2625 25.15 19.375 25.0281 19.375Z"
fill="#F5222D"/>
<path
d="M11.6562 12.8438C10.7656 12.8438 10.0406 13.5719 10.0406 14.4688C10.0406 15.3656 10.7656 16.0938 11.6562 16.0938C12.5469 16.0938 13.2719 15.3656 13.2719 14.4688C13.2719 13.5719 12.5469 12.8438 11.6562 12.8438ZM29 5H3C2.44687 5 2 5.44687 2 6V26C2 26.5531 2.44687 27 3 27H29C29.5531 27 30 26.5531 30 26V6C30 5.44687 29.5531 5 29 5ZM19 13.125C19 12.9875 19.0312 12.875 19.0719 12.875H22.9281C22.9688 12.875 23 12.9875 23 13.125V14.625C23 14.7625 22.9688 14.875 22.9281 14.875H19.0719C19.0312 14.875 19 14.7625 19 14.625V13.125ZM16.3125 21.0312H14.9406C14.8094 21.0312 14.7031 20.9281 14.6938 20.7969C14.575 19.2188 13.2563 17.9688 11.6562 17.9688C10.0562 17.9688 8.7375 19.2188 8.61875 20.7969C8.60937 20.9281 8.50312 21.0312 8.37187 21.0312H7C6.96609 21.0313 6.93253 21.0244 6.90135 21.0111C6.87017 20.9978 6.84204 20.9782 6.81865 20.9537C6.79527 20.9291 6.77712 20.9001 6.76532 20.8683C6.75352 20.8365 6.7483 20.8026 6.75 20.7687C6.8375 19.1031 7.75 17.6531 9.08125 16.8281C8.49418 16.1828 8.16979 15.3412 8.17188 14.4688C8.17188 12.5344 9.73125 10.9688 11.6531 10.9688C13.575 10.9688 15.1344 12.5344 15.1344 14.4688C15.1344 15.3781 14.7906 16.2031 14.225 16.8281C15.5594 17.6562 16.4688 19.1031 16.5562 20.7687C16.5687 20.9125 16.4563 21.0312 16.3125 21.0312ZM25.0281 19.375H19.2219C19.1 19.375 19 19.2625 19 19.125V17.625C19 17.4875 19.1 17.375 19.2219 17.375H25.025C25.1469 17.375 25.2469 17.4875 25.2469 17.625V19.125H25.25C25.25 19.2625 25.15 19.375 25.0281 19.375Z"
fill="url(#paint0_linear_253_56)"/>
<defs>
<linearGradient id="paint0_linear_253_56" x1="26.5" y1="5" x2="9.93478" y2="30.9483"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconKnowledge = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-knowledge`} style={style} xmlns="http://www.w3.org/2000/svg" fill="none"
version="1.1"
width="1em" height="1em" viewBox="0 0 32 32">
<path
d="M27 4.5H17.5C17.225 4.5 17 4.725 17 5V14.5C17 14.775 17.225 15 17.5 15H27C27.275 15 27.5 14.775 27.5 14.5V5C27.5 4.725 27.275 4.5 27 4.5ZM27 17H17.5C17.225 17 17 17.225 17 17.5V27C17 27.275 17.225 27.5 17.5 27.5H27C27.275 27.5 27.5 27.275 27.5 27V17.5C27.5 17.225 27.275 17 27 17ZM14.5 4.5H5C4.725 4.5 4.5 4.725 4.5 5V14.5C4.5 14.775 4.725 15 5 15H14.5C14.775 15 15 14.775 15 14.5V5C15 4.725 14.775 4.5 14.5 4.5ZM14.5 17H5C4.725 17 4.5 17.225 4.5 17.5V27C4.5 27.275 4.725 27.5 5 27.5H14.5C14.775 27.5 15 27.275 15 27V17.5C15 17.225 14.775 17 14.5 17Z"
fill="#F5222D"/>
<path
d="M27 4.5H17.5C17.225 4.5 17 4.725 17 5V14.5C17 14.775 17.225 15 17.5 15H27C27.275 15 27.5 14.775 27.5 14.5V5C27.5 4.725 27.275 4.5 27 4.5ZM27 17H17.5C17.225 17 17 17.225 17 17.5V27C17 27.275 17.225 27.5 17.5 27.5H27C27.275 27.5 27.5 27.275 27.5 27V17.5C27.5 17.225 27.275 17 27 17ZM14.5 4.5H5C4.725 4.5 4.5 4.725 4.5 5V14.5C4.5 14.775 4.725 15 5 15H14.5C14.775 15 15 14.775 15 14.5V5C15 4.725 14.775 4.5 14.5 4.5ZM14.5 17H5C4.725 17 4.5 17.225 4.5 17.5V27C4.5 27.275 4.725 27.5 5 27.5H14.5C14.775 27.5 15 27.275 15 27V17.5C15 17.225 14.775 17 14.5 17Z"
fill="url(#paint0_linear_253_41)"/>
<defs>
<linearGradient id="paint0_linear_253_41" x1="24.625" y1="4.5" x2="5.9375" y2="27.5"
gradientUnits="userSpaceOnUse">
<stop stopColor="#FFCB7E"/>
<stop offset="1" stopColor="#F5222D"/>
</linearGradient>
</defs>
</svg>
)
export const IconAudio = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-audio`} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 20 20">
<path
d="M10 12.1875C11.834 12.1875 13.3203 10.7188 13.3203 8.90625V4.53125C13.3203 2.71875 11.834 1.25 10 1.25C8.16602 1.25 6.67969 2.71875 6.67969 4.53125V8.90625C6.67969 10.7188 8.16602 12.1875 10 12.1875ZM16.4453 8.86719C16.4453 8.78125 16.375 8.71094 16.2891 8.71094H15.1172C15.0312 8.71094 14.9609 8.78125 14.9609 8.86719C14.9609 11.6074 12.7402 13.8281 10 13.8281C7.25977 13.8281 5.03906 11.6074 5.03906 8.86719C5.03906 8.78125 4.96875 8.71094 4.88281 8.71094H3.71094C3.625 8.71094 3.55469 8.78125 3.55469 8.86719C3.55469 12.1621 6.02734 14.8809 9.21875 15.2656V17.2656H6.38086C6.11328 17.2656 5.89844 17.5449 5.89844 17.8906V18.5938C5.89844 18.6797 5.95313 18.75 6.01953 18.75H13.9805C14.0469 18.75 14.1016 18.6797 14.1016 18.5938V17.8906C14.1016 17.5449 13.8867 17.2656 13.6191 17.2656H10.7031V15.2754C13.9316 14.9238 16.4453 12.1895 16.4453 8.86719Z"
fill="#515151"/>
</svg>
)
export const IconSend = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-audio`} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 24 24">
<path
d="M23.2348 11.6535L0.828538 0.419538C0.737466 0.374002 0.633002 0.363288 0.533895 0.387395C0.423901 0.414587 0.329163 0.484257 0.270425 0.581149C0.211687 0.678041 0.193736 0.79426 0.220502 0.904359L2.52943 10.3383C2.56425 10.4803 2.66872 10.5954 2.808 10.641L6.76425 11.999L2.81068 13.357C2.67139 13.4053 2.56693 13.5178 2.53479 13.6597L0.220502 23.107C0.196395 23.2061 0.207109 23.3106 0.252645 23.399C0.357109 23.6106 0.614252 23.6963 0.828538 23.5919L23.2348 12.4222C23.3178 12.382 23.3848 12.3124 23.4276 12.232C23.5321 12.0178 23.4464 11.7606 23.2348 11.6535ZM2.86157 20.4231L4.2089 14.916L12.116 12.2026C12.1776 12.1811 12.2285 12.1329 12.25 12.0686C12.2875 11.9561 12.2285 11.8356 12.116 11.7954L4.2089 9.08472L2.86693 3.599L19.6884 12.0338L2.86157 20.4231Z"
fill="currentColor"/>
</svg>
)
export const IconSync = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-sync`} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 16 16">
<g clipPath="url(#clip0_144_44)">
<path
d="M1.85719 7.86072C1.87504 7.08036 2.03576 6.32322 2.33754 5.61072C2.64647 4.87857 3.08933 4.22321 3.65362 3.65714C4.2179 3.09107 4.87504 2.64821 5.60719 2.33929C6.36433 2.01964 7.1679 1.85714 7.99826 1.85714C8.82862 1.85714 9.63219 2.01964 10.3875 2.33929C11.1176 2.64758 11.7805 3.09521 12.3393 3.65714C12.5161 3.83393 12.6822 4.02143 12.8358 4.21786L11.7608 5.05714C11.7395 5.0736 11.7233 5.09572 11.714 5.12097C11.7048 5.14622 11.7028 5.17356 11.7084 5.19987C11.714 5.22617 11.7269 5.25036 11.7457 5.26965C11.7644 5.28895 11.7882 5.30256 11.8143 5.30893L14.9518 6.07679C15.0411 6.09822 15.1286 6.03036 15.1286 5.93929L15.1429 2.70893C15.1429 2.58929 15.0054 2.52143 14.9125 2.59643L13.9054 3.38393C12.5322 1.62679 10.3965 0.5 7.99647 0.5C3.9054 0.5 0.57683 3.77857 0.500045 7.85357C0.499568 7.87263 0.502911 7.89159 0.509876 7.90934C0.516842 7.92709 0.527289 7.94326 0.540603 7.9569C0.553916 7.97055 0.569826 7.98139 0.587395 7.9888C0.604964 7.9962 0.623837 8.00001 0.642902 8H1.71433C1.7929 8 1.8554 7.9375 1.85719 7.86072ZM15.3572 8H14.2858C14.2072 8 14.1447 8.0625 14.1429 8.13929C14.125 8.91964 13.9643 9.67679 13.6625 10.3893C13.3536 11.1214 12.9108 11.7786 12.3465 12.3429C11.7769 12.9148 11.0998 13.3683 10.3541 13.6772C9.60843 13.9861 8.80897 14.1444 8.00183 14.1429C7.19496 14.1444 6.39578 13.9861 5.6504 13.6771C4.90502 13.3682 4.22819 12.9147 3.65897 12.3429C3.48219 12.1661 3.31612 11.9786 3.16254 11.7821L4.23754 10.9429C4.25881 10.9264 4.27501 10.9043 4.28426 10.879C4.29352 10.8538 4.29546 10.8264 4.28986 10.8001C4.28427 10.7738 4.27136 10.7496 4.25263 10.7304C4.2339 10.7111 4.2101 10.6974 4.18397 10.6911L1.04647 9.92322C0.957188 9.90179 0.869687 9.96964 0.869687 10.0607L0.857188 13.2929C0.857188 13.4125 0.994688 13.4804 1.08754 13.4054L2.09469 12.6179C3.4679 14.3732 5.60362 15.5 8.00362 15.5C12.0965 15.5 15.4233 12.2196 15.5 8.14643C15.5005 8.12737 15.4972 8.10841 15.4902 8.09066C15.4832 8.07292 15.4728 8.05674 15.4595 8.0431C15.4462 8.02945 15.4303 8.01861 15.4127 8.01121C15.3951 8.00381 15.3763 8 15.3572 8Z"
fill="currentColor"/>
</g>
<defs>
<clipPath id="clip0_144_44">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>
)
export const IconCopy = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-copy`} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
export const IconCopy = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-copy`} style={style} xmlns="http://www.w3.org/2000/svg"
fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 16 16">
<g clipPath="url(#clip0_144_46)">
<path
@ -415,46 +40,65 @@ export const IconCopy = ({style,className}: { style?: React.CSSProperties;classN
</defs>
</svg>
)
export const IconShare = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-share`} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
export const IconShare = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-share`} style={style} xmlns="http://www.w3.org/2000/svg"
fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 16 16">
<path
d="M12.2856 10.7143C11.7766 10.7143 11.307 10.8929 10.9391 11.1911L7.23915 8.51429C7.3011 8.17423 7.3011 7.82577 7.23915 7.48572L10.9391 4.80893C11.307 5.10714 11.7766 5.28572 12.2856 5.28572C13.4677 5.28572 14.4284 4.325 14.4284 3.14286C14.4284 1.96071 13.4677 1 12.2856 1C11.1034 1 10.1427 1.96071 10.1427 3.14286C10.1427 3.35 10.1713 3.54821 10.2266 3.7375L6.71236 6.28214C6.19093 5.59107 5.36236 5.14286 4.42843 5.14286C2.84986 5.14286 1.57129 6.42143 1.57129 8C1.57129 9.57857 2.84986 10.8571 4.42843 10.8571C5.36236 10.8571 6.19093 10.4089 6.71236 9.71786L10.2266 12.2625C10.1713 12.4518 10.1427 12.6518 10.1427 12.8571C10.1427 14.0393 11.1034 15 12.2856 15C13.4677 15 14.4284 14.0393 14.4284 12.8571C14.4284 11.675 13.4677 10.7143 12.2856 10.7143ZM12.2856 2.21429C12.7981 2.21429 13.2141 2.63036 13.2141 3.14286C13.2141 3.65536 12.7981 4.07143 12.2856 4.07143C11.7731 4.07143 11.357 3.65536 11.357 3.14286C11.357 2.63036 11.7731 2.21429 12.2856 2.21429ZM4.42843 9.57143C3.56236 9.57143 2.857 8.86607 2.857 8C2.857 7.13393 3.56236 6.42857 4.42843 6.42857C5.2945 6.42857 5.99986 7.13393 5.99986 8C5.99986 8.86607 5.2945 9.57143 4.42843 9.57143ZM12.2856 13.7857C11.7731 13.7857 11.357 13.3696 11.357 12.8571C11.357 12.3446 11.7731 11.9286 12.2856 11.9286C12.7981 11.9286 13.2141 12.3446 13.2141 12.8571C13.2141 13.3696 12.7981 13.7857 12.2856 13.7857Z"
fill="currentColor"/>
</svg>
)
export const IconDownload = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-download`} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
width="1em" height="1em" viewBox="0 0 16 16">
export const IconDownload = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-download`} style={style} xmlns="http://www.w3.org/2000/svg"
fill="none" version="1.1" width="1em" height="1em" viewBox="0 0 20 24">
<path
d="M7.88736 10.6575C7.90072 10.6745 7.9178 10.6883 7.93729 10.6978C7.95678 10.7073 7.97818 10.7123 7.99986 10.7123C8.02154 10.7123 8.04294 10.7073 8.06243 10.6978C8.08192 10.6883 8.099 10.6745 8.11236 10.6575L10.1124 8.1271C10.1856 8.03424 10.1195 7.89674 9.99986 7.89674H8.67665V1.85389C8.67665 1.77531 8.61236 1.71103 8.53379 1.71103H7.46236C7.38379 1.71103 7.3195 1.77531 7.3195 1.85389V7.89496H5.99986C5.88022 7.89496 5.81415 8.03246 5.88736 8.12532L7.88736 10.6575ZM14.5356 10.0325H13.4641C13.3856 10.0325 13.3213 10.0967 13.3213 10.1753V12.9253H2.67843V10.1753C2.67843 10.0967 2.61415 10.0325 2.53557 10.0325H1.46415C1.38557 10.0325 1.32129 10.0967 1.32129 10.1753V13.711C1.32129 14.0271 1.57665 14.2825 1.89272 14.2825H14.107C14.4231 14.2825 14.6784 14.0271 14.6784 13.711V10.1753C14.6784 10.0967 14.6141 10.0325 14.5356 10.0325Z"
d="M16.5571 8.47059H14.2857V1.41176C14.2857 0.635294 13.6429 0 12.8571 0H7.14286C6.35714 0 5.71429 0.635294 5.71429 1.41176V8.47059H3.44286C2.17143 8.47059 1.52857 9.99529 2.42857 10.8847L8.98571 17.3647C9.54286 17.9153 10.4429 17.9153 11 17.3647L17.5571 10.8847C18.4571 9.99529 17.8286 8.47059 16.5571 8.47059ZM0 22.5882C0 23.3647 0.642857 24 1.42857 24H18.5714C19.3571 24 20 23.3647 20 22.5882C20 21.8118 19.3571 21.1765 18.5714 21.1765H1.42857C0.642857 21.1765 0 21.8118 0 22.5882Z"
fill="currentColor"/>
</svg>
)
export const IconDelete = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 1024 1024" version="1.1">
export const IconPin = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-download`} style={style} xmlns="http://www.w3.org/2000/svg"
fill="none" version="1.1" width="0.6em" height="1em" viewBox="0 0 12 21">
<path
d="M765.505691 191.942567H639.627772c0-35.32453-28.636201-63.960731-63.96073-63.960731H447.74558c-35.32453 0-63.960731 28.636201-63.96073 63.960731H257.905908c-36.452213 0-66.00325 29.551036-66.00325 66.00325v59.875692c0 36.452213 29.551036 66.00325 66.00325 66.00325h-2.042519v445.681572c0 36.452213 29.551036 66.00325 66.003249 66.00325h379.679346c36.452213 0 66.00325-29.551036 66.00325-66.00325V383.823736h-2.04252c36.452213 0 66.00325-29.551036 66.00325-66.00325v-59.875693c-0.001023-36.452213-29.551036-66.002226-66.004273-66.002226z m-61.918211 611.470479c-0.101307 3.123131-1.743714 27.813462-27.961842 28.134781H347.905688c-27.988448-0.343831-27.969005-28.459169-27.969005-28.459169l-0.112564 0.031722V383.823736h383.763361v419.58931z m31.980365-483.550041H287.843754c-17.662265 0-31.980365-14.3181-31.980365-31.980365 0-17.662265 14.3181-31.980365 31.980365-31.980366H735.568868c17.662265 0 31.980365 14.3181 31.980366 31.980366-0.001023 17.662265-14.319124 31.980365-31.981389 31.980365z"
fill="currentColor"/>
<path
d="M447.74558 767.588119c17.662265 0 31.980365-14.3181 31.980366-31.980365V479.764831c0-17.662265-14.3181-31.980365-31.980366-31.980365-17.662265 0-31.980365 14.3181-31.980365 31.980365v255.842923c0 17.662265 14.3181 31.980365 31.980365 31.980365zM575.667042 767.588119c17.662265 0 31.980365-14.3181 31.980365-31.980365V479.764831c0-17.662265-14.3181-31.980365-31.980365-31.980365-17.662265 0-31.980365 14.3181-31.980366 31.980365v255.842923c0 17.662265 14.3181 31.980365 31.980366 31.980365z"
d="M10 10.5V2.5H11V0.5H1V2.5H2V10.5L0 12.5V14.5H5.2V20.5H6.8V14.5H12V12.5L10 10.5ZM2.8 12.5L4 11.3V2.5H8V11.3L9.2 12.5H2.8Z"
fill="currentColor"/>
</svg>
)
export const IconAddText = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
export const IconDelete = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="0.86em" height="1em" viewBox="0 0 20 23" version="1.1">
<path
d="M3.66667 22.3828C2.99444 22.3828 2.41918 22.1437 1.94089 21.6654C1.46259 21.1871 1.22304 20.6114 1.22222 19.9384V4.04948H0V1.60503H6.11111V0.382812H13.4444V1.60503H19.5556V4.04948H18.3333V19.9384C18.3333 20.6106 18.0942 21.1863 17.6159 21.6654C17.1376 22.1445 16.5619 22.3836 15.8889 22.3828H3.66667ZM15.8889 4.04948H3.66667V19.9384H15.8889V4.04948ZM6.11111 17.4939H8.55555V6.49392H6.11111V17.4939ZM11 17.4939H13.4444V6.49392H11V17.4939Z"
fill="currentColor"/>
</svg>
)
export const IconAddText = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 1024 1024" version="1.1">
<path
d="M714.688 512a224 224 0 1 1 0 448 224 224 0 0 1 0-448z m96-448a64 64 0 0 1 64 64v284.992a36.032 36.032 0 0 1-71.424 5.76l-0.64-5.76V136h-624v752h212.032a35.968 35.968 0 1 1 0 72H170.688a64 64 0 0 1-64-64V128a64 64 0 0 1 64-64h640z m-96 512a160 160 0 1 0 0 320 160 160 0 0 0 0-320z m0 28.032c15.424 0 27.968 12.48 27.968 27.968v76.032h76.032a28.032 28.032 0 0 1 0 55.936h-76.032v76.032a28.032 28.032 0 0 1-56 0v-76.032H610.688a27.968 27.968 0 1 1 0-55.936h75.968V631.936c0-15.488 12.544-28.032 28.032-28.032zM401.664 672a32 32 0 1 1 0 64h-112a32 32 0 1 1 0-64h112z m57.984-192a32 32 0 0 1 0 64H289.664a32 32 0 1 1 0-64h169.984z m230.016-192a32 32 0 0 1 0 64h-400a32 32 0 1 1 0-64h400z"
fill="currentColor"/>
<path
d="M12.2222 0.382812C12.5337 0.383158 12.8334 0.502443 13.0599 0.716294C13.2864 0.930145 13.4227 1.22242 13.441 1.53341C13.4592 1.84439 13.3581 2.15061 13.1581 2.3895C12.9582 2.62838 12.6746 2.78191 12.3652 2.8187L12.2222 2.82726H2.44444V19.9384H19.5556V10.1606C19.5559 9.84907 19.6752 9.54944 19.889 9.32292C20.1029 9.0964 20.3952 8.96008 20.7061 8.94182C21.0171 8.92357 21.3234 9.02475 21.5622 9.22469C21.8011 9.42463 21.9547 9.70825 21.9914 10.0176L22 10.1606V19.9384C22.0002 20.5551 21.7673 21.1491 21.3479 21.6013C20.9286 22.0535 20.3539 22.3305 19.7389 22.3767L19.5556 22.3828H2.44444C1.82774 22.383 1.23375 22.1501 0.781553 21.7308C0.329353 21.3114 0.0523642 20.7367 0.00611137 20.1217L1.2255e-07 19.9384V2.82726C-0.000195041 2.21055 0.232719 1.61656 0.652052 1.16436C1.07138 0.712165 1.64614 0.435177 2.26111 0.388924L2.44444 0.382812H12.2222ZM19.8526 0.802035C20.0725 0.582832 20.3676 0.455567 20.678 0.446089C20.9884 0.436611 21.2908 0.545631 21.5237 0.751005C21.7566 0.95638 21.9026 1.24271 21.932 1.55184C21.9615 1.86096 21.8722 2.16971 21.6822 2.41537L21.5808 2.53148L9.48078 14.6303C9.26083 14.8495 8.96569 14.9767 8.65531 14.9862C8.34493 14.9957 8.04257 14.8867 7.80966 14.6813C7.57675 14.4759 7.43074 14.1896 7.40129 13.8805C7.37184 13.5713 7.46116 13.2626 7.65111 13.0169L7.75256 12.902L19.8526 0.802035Z"
fill="currentColor"/>
</svg>
)
export const IconVideo = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-video`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 21 20" version="1.1">
<path fillRule="evenodd" clipRule="evenodd" d="M18 1.00268e-07C18.5046 -0.000159579 18.9906 0.190406 19.3605 0.533497C19.7305 0.876588 19.9572 1.34684 19.995 1.85L20 2V16C20.0002 16.5046 19.8096 16.9906 19.4665 17.3605C19.1234 17.7305 18.6532 17.9572 18.15 17.995L18 18H2C1.49542 18.0002 1.00943 17.8096 0.639452 17.4665C0.269471 17.1234 0.0428434 16.6532 0.00500021 16.15L1.00268e-07 16V2C-0.000159579 1.49542 0.190406 1.00943 0.533497 0.639452C0.876588 0.269471 1.34684 0.0428434 1.85 0.00500021L2 1.00268e-07H18ZM18 2H2V16H18V2ZM8.34 4.638L8.858 4.868L9.196 5.028L9.583 5.218L10.013 5.436L10.483 5.686L10.99 5.966L11.256 6.118L11.774 6.423L12.248 6.715L12.678 6.988L13.058 7.241L13.538 7.571L13.902 7.834L13.997 7.904C14.1513 8.01883 14.2767 8.16816 14.363 8.34005C14.4494 8.51194 14.4943 8.70164 14.4943 8.894C14.4943 9.08636 14.4494 9.27606 14.363 9.44795C14.2767 9.61984 14.1513 9.76917 13.997 9.884L13.674 10.119L13.234 10.427L12.878 10.666L12.473 10.929L12.02 11.212L11.521 11.512L10.987 11.821L10.478 12.103L10.007 12.353L9.577 12.573L9.191 12.761L8.569 13.049L8.339 13.149C8.16242 13.2251 7.97051 13.2589 7.77856 13.2476C7.58662 13.2364 7.39995 13.1805 7.23346 13.0843C7.06696 12.9881 6.92524 12.8544 6.8196 12.6937C6.71396 12.5331 6.64732 12.35 6.625 12.159L6.567 11.594L6.535 11.22L6.493 10.556L6.47 10.048L6.455 9.493L6.451 9.199L6.449 8.894C6.449 8.68733 6.451 8.48733 6.455 8.294L6.47 7.739L6.493 7.232L6.52 6.775L6.55 6.374L6.625 5.63C6.64719 5.43882 6.71376 5.25547 6.81939 5.09458C6.92502 4.93369 7.0668 4.79972 7.2334 4.70335C7.4 4.60698 7.58682 4.55089 7.77896 4.53954C7.97109 4.5282 8.16321 4.56191 8.34 4.638ZM8.951 7.139L8.515 6.921L8.486 7.408L8.464 7.959L8.451 8.569L8.449 8.894L8.451 9.219L8.464 9.828L8.474 10.111L8.5 10.631L8.515 10.866L8.949 10.648L9.436 10.392L9.971 10.098L10.255 9.936L10.806 9.61L11.3 9.304L11.736 9.024L11.932 8.894L11.525 8.624L11.059 8.33C10.7938 8.16584 10.5261 8.00582 10.256 7.85L9.973 7.689L9.439 7.395L8.951 7.139Z" fill="black"/>
</svg>
)
export const IconAddImage = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
export const IconAddImage = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 1024 1024" version="1.1">
<path
d="M160 128a96 96 0 0 0-96 96v576a96 96 0 0 0 96 96h262.72a374.464 374.464 0 0 1-25.216-64H160a31.872 31.872 0 0 1-32-32v-59.264l227.52-256.512 74.496 78.336c10.752-19.008 23.232-36.992 37.248-53.504L353.92 389.76 128 644.224V224c0-17.728 14.272-32 32-32h704c17.728 0 32 14.272 32 32v198.72c22.72 11.776 44.48 25.536 64 41.792V224a96 96 0 0 0-96-96z m544 128c-35.2 0-64 28.8-64 64s28.8 64 64 64 64-28.8 64-64-28.8-64-64-64z m32 192c-158.72 0-288 129.28-288 288s129.28 288 288 288 288-129.28 288-288-129.28-288-288-288z m0 64c124.032 0 224 100.032 224 224 0 124.032-100.032 224-224 224a223.616 223.616 0 0 1-224-224c0-124.032 100.032-224 224-224z m-32 64v128H576v64h128v128h64V768h128v-64H768V576z"
@ -462,8 +106,8 @@ export const IconAddImage = ({style,className}: { style?: React.CSSProperties;cl
</svg>
)
export const IconAdd = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
export const IconAddCircle = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 1024 1024" version="1.1">
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m271.5 719.5c-35.3 35.3-76.3 63-122.1 82.3-47.3 20-97.6 30.2-149.5 30.2-51.9 0-102.1-10.1-149.5-30.2-45.7-19.3-86.8-47-122.1-82.3s-63-76.3-82.3-122.1c-20-47.3-30.2-97.6-30.2-149.5s10.1-102.1 30.2-149.5c19.3-45.7 47-86.8 82.3-122.1s76.3-63 122.1-82.3c47.3-20 97.6-30.2 149.5-30.2 51.9 0 102.1 10.1 149.5 30.2 45.7 19.3 86.8 47 122.1 82.3s63 76.3 82.3 122.1c20 47.3 30.2 97.6 30.2 149.5S885.9 614 865.8 661.4c-19.3 45.8-47 86.9-82.3 122.1z"
@ -475,8 +119,58 @@ export const IconAdd = ({style,className}: { style?: React.CSSProperties;classNa
</svg>
)
export const IconPlay = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
export const IconWarningCircle = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-warning`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 22 22" version="1.1">
<g>
<path
d="M9.625 12.375V5.5H12.375V12.375H9.625ZM11 16.5C11.3647 16.5 11.7144 16.3551 11.9723 16.0973C12.2301 15.8394 12.375 15.4897 12.375 15.125C12.375 14.7603 12.2301 14.4106 11.9723 14.1527C11.7144 13.8949 11.3647 13.75 11 13.75C10.6353 13.75 10.2856 13.8949 10.0277 14.1527C9.76987 14.4106 9.625 14.7603 9.625 15.125C9.625 15.4897 9.76987 15.8394 10.0277 16.0973C10.2856 16.3551 10.6353 16.5 11 16.5Z"
fill="currentColor"/>
<path
d="M0 11C0 8.08262 1.15893 5.28473 3.22183 3.22183C5.28473 1.15893 8.08262 0 11 0C13.9174 0 16.7153 1.15893 18.7782 3.22183C20.8411 5.28473 22 8.08262 22 11C22 13.9174 20.8411 16.7153 18.7782 18.7782C16.7153 20.8411 13.9174 22 11 22C8.08262 22 5.28473 20.8411 3.22183 18.7782C1.15893 16.7153 0 13.9174 0 11ZM11 2.75C9.91659 2.75 8.8438 2.96339 7.84286 3.37799C6.84193 3.7926 5.93245 4.40029 5.16637 5.16637C4.40029 5.93245 3.7926 6.84193 3.37799 7.84286C2.96339 8.8438 2.75 9.91659 2.75 11C2.75 12.0834 2.96339 13.1562 3.37799 14.1571C3.7926 15.1581 4.40029 16.0675 5.16637 16.8336C5.93245 17.5997 6.84193 18.2074 7.84286 18.622C8.8438 19.0366 9.91659 19.25 11 19.25C13.188 19.25 15.2865 18.3808 16.8336 16.8336C18.3808 15.2865 19.25 13.188 19.25 11C19.25 8.81196 18.3808 6.71354 16.8336 5.16637C15.2865 3.61919 13.188 2.75 11 2.75Z"
fill="currentColor"/>
</g>
</svg>
)
export const IconAdd = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 1024 1024" version="1.1">
<path d="M544 288v448c0 17.6-14.4 32-32 32s-32-14.4-32-32V288c0-17.6 14.4-32 32-32s32 14.4 32 32z"
fill="currentColor"/>
<path d="M736 544H288c-17.6 0-32-14.4-32-32s14.4-32 32-32h448c17.6 0 32 14.4 32 32s-14.4 32-32 32z"
fill="currentColor"/>
</svg>
)
export const IconLocked = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="0.81em" height="1em" viewBox="0 0 18 22" version="1.1">
<path fillRule="evenodd" clipRule="evenodd"
d="M5.625 5.5C5.625 4.62479 5.98058 3.78542 6.61351 3.16655C7.24645 2.54768 8.10489 2.2 9 2.2C9.89511 2.2 10.7536 2.54768 11.3865 3.16655C12.0194 3.78542 12.375 4.62479 12.375 5.5V7.7H5.625V5.5ZM3.375 7.7V5.5C3.375 4.04131 3.96763 2.64236 5.02252 1.61091C6.07742 0.579463 7.50816 0 9 0C10.4918 0 11.9226 0.579463 12.9775 1.61091C14.0324 2.64236 14.625 4.04131 14.625 5.5V7.7H16.875C17.1734 7.7 17.4595 7.81589 17.6705 8.02218C17.8815 8.22847 18 8.50826 18 8.8V18.7C18 19.5752 17.6444 20.4146 17.0115 21.0335C16.3786 21.6523 15.5201 22 14.625 22H3.375C2.47989 22 1.62145 21.6523 0.988515 21.0335C0.355579 20.4146 0 19.5752 0 18.7V8.8C0 8.50826 0.118527 8.22847 0.329505 8.02218C0.540484 7.81589 0.826631 7.7 1.125 7.7H3.375ZM10.125 14.85C10.125 14.4124 10.3028 13.9927 10.6193 13.6833C10.9357 13.3738 11.3649 13.2 11.8125 13.2H11.8238C12.2713 13.2 12.7005 13.3738 13.017 13.6833C13.3335 13.9927 13.5113 14.4124 13.5113 14.85V14.861C13.5113 15.2986 13.3335 15.7183 13.017 16.0277C12.7005 16.3372 12.2713 16.511 11.8238 16.511H11.8125C11.3649 16.511 10.9357 16.3372 10.6193 16.0277C10.3028 15.7183 10.125 15.2986 10.125 14.861V14.85Z"
fill="currentColor"/>
</svg>
)
export const IconUnlock = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 24 24" version="1.1">
<path fillRule="evenodd" clipRule="evenodd"
d="M11.418 7.68673V5.50001C11.418 4.04132 10.8166 2.64237 9.7459 1.61092C8.67525 0.579463 7.22314 0 5.70902 0C4.19489 0 2.74278 0.579463 1.67213 1.61092C0.601484 2.64237 0 4.04132 0 5.50001V7.68676H2.28361V5.50001C2.28361 4.62479 2.6445 3.78542 3.28689 3.16655C3.92927 2.54768 4.80054 2.2 5.70902 2.2C6.61749 2.2 7.48876 2.54768 8.13115 3.16655C8.77354 3.78542 9.13443 4.62479 9.13443 5.50001V7.68676H9.15656V7.69998H6.87295C6.57012 7.69998 6.2797 7.81587 6.06557 8.02216C5.85144 8.22845 5.73115 8.50824 5.73115 8.79998V18.7C5.73115 19.5752 6.09204 20.4146 6.73443 21.0335C7.37681 21.6523 8.24808 22 9.15656 22H20.5746C21.4831 22 22.3543 21.6523 22.9967 21.0335C23.6391 20.4146 24 19.5752 24 18.7V8.79998C24 8.50824 23.8797 8.22845 23.6656 8.02216C23.4514 7.81587 23.161 7.69998 22.8582 7.69998H20.5746V7.68673H18.291V7.69998H11.4402V7.68673H11.418ZM16.0074 14.85C16.0074 14.4124 16.1878 13.9927 16.509 13.6833C16.8302 13.3738 17.2658 13.2 17.7201 13.2H17.7315C18.1857 13.2 18.6214 13.3738 18.9426 13.6833C19.2638 13.9927 19.4442 14.4124 19.4442 14.85V14.861C19.4442 15.2986 19.2638 15.7183 18.9426 16.0277C18.6214 16.3372 18.1857 16.511 17.7315 16.511H17.7201C17.2658 16.511 16.8302 16.3372 16.509 16.0277C16.1878 15.7183 16.0074 15.2986 16.0074 14.861V14.85Z"
fill="currentColor"/>
</svg>
)
export const IconPlaying = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 32 30" version="1.1">
<path d="M1 11.7057V18.2943M7 6.76424V23.2358M13 1V29M19 7.22275V22.7772M25 11.1114V18.8886M31 13.3528V16.6472"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
)
export const IconPlay = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 1024 1024" version="1.1">
<path
d="M512 97.52381c228.912762 0 414.47619 185.563429 414.47619 414.47619s-185.563429 414.47619-414.47619 414.47619S97.52381 740.912762 97.52381 512 283.087238 97.52381 512 97.52381z m-57.782857 268.190476c-21.942857 0-39.740952 17.798095-39.740953 39.740952v209.13981a39.740952 39.740952 0 0 0 61.196191 33.426285l164.352-105.593904a39.740952 39.740952 0 0 0-0.292571-67.047619l-164.352-103.545905a39.740952 39.740952 0 0 0-21.187048-6.119619z"
@ -484,8 +178,8 @@ export const IconPlay = ({style,className}: { style?: React.CSSProperties;classN
</svg>
)
export const IconLive = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
export const IconLive = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 1024 1024" version="1.1">
<path
d="M772.437333 97.52381l51.712 51.712-126.342095 126.342095H828.952381a73.142857 73.142857 0 0 1 73.142857 73.142857v487.619048a73.142857 73.142857 0 0 1-73.142857 73.142857H195.047619a73.142857 73.142857 0 0 1-73.142857-73.142857v-487.619048a73.142857 73.142857 0 0 1 73.142857-73.142857h131.120762L199.850667 149.23581 251.562667 97.52381l178.054095 178.054095h164.742095L772.437333 97.52381zM828.952381 348.720762H195.047619v487.619048h633.904762v-487.619048z m-280.380952 73.142857v341.333333h-73.142858v-341.333333h73.142858z m-134.095239 73.142857v195.047619h-73.142857v-195.047619h73.142857z m268.190477 24.380953v146.285714h-73.142857v-146.285714h73.142857z"
@ -493,11 +187,11 @@ export const IconLive = ({style,className}: { style?: React.CSSProperties;classN
</svg>
)
export const IconEdit = ({style,className}: { style?: React.CSSProperties;className?:string; }) => (
<svg className={`svg-icon ${className||''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 1024 1024" version="1.1">
export const IconEdit = ({style, className}: IconProps) => (
<svg className={`svg-icon ${className || ''} icon-delete`} style={style} xmlns="http://www.w3.org/2000/svg"
width="1em" height="1em" viewBox="0 0 23 24" version="1.1">
<path
d="M804.6 689.8l64-64c10-10 27.4-3 27.4 11.4V928c0 53-43 96-96 96H96c-53 0-96-43-96-96V224c0-53 43-96 96-96h547c14.2 0 21.4 17.2 11.4 27.4l-64 64c-3 3-7 4.6-11.4 4.6H96v704h704V701c0-4.2 1.6-8.2 4.6-11.2z m313.2-403.6L592.6 811.4l-180.8 20c-52.4 5.8-97-38.4-91.2-91.2l20-180.8L865.8 34.2c45.8-45.8 119.8-45.8 165.4 0l86.4 86.4c45.8 45.8 45.8 120 0.2 165.6zM920.2 348L804 231.8 432.4 603.6l-14.6 130.6 130.6-14.6L920.2 348z m129.6-159.4l-86.4-86.4c-8.2-8.2-21.6-8.2-29.6 0L872 164l116.2 116.2 61.8-61.8c8-8.4 8-21.6-0.2-29.8z"
d="M12.2222 0.382812C12.5337 0.383158 12.8334 0.502443 13.0599 0.716294C13.2864 0.930145 13.4227 1.22242 13.441 1.53341C13.4592 1.84439 13.3581 2.15061 13.1581 2.3895C12.9582 2.62838 12.6746 2.78191 12.3652 2.8187L12.2222 2.82726H2.44444V19.9384H19.5556V10.1606C19.5559 9.84907 19.6752 9.54944 19.889 9.32292C20.1029 9.0964 20.3952 8.96008 20.7061 8.94182C21.0171 8.92357 21.3234 9.02475 21.5622 9.22469C21.8011 9.42463 21.9547 9.70825 21.9914 10.0176L22 10.1606V19.9384C22.0002 20.5551 21.7673 21.1491 21.3479 21.6013C20.9286 22.0535 20.3539 22.3305 19.7389 22.3767L19.5556 22.3828H2.44444C1.82774 22.383 1.23375 22.1501 0.781553 21.7308C0.329353 21.3114 0.0523642 20.7367 0.00611137 20.1217L1.2255e-07 19.9384V2.82726C-0.000195041 2.21055 0.232719 1.61656 0.652052 1.16436C1.07138 0.712165 1.64614 0.435177 2.26111 0.388924L2.44444 0.382812H12.2222ZM19.8526 0.802035C20.0725 0.582832 20.3676 0.455567 20.678 0.446089C20.9884 0.436611 21.2908 0.545631 21.5237 0.751005C21.7566 0.95638 21.9026 1.24271 21.932 1.55184C21.9615 1.86096 21.8722 2.16971 21.6822 2.41537L21.5808 2.53148L9.48078 14.6303C9.26083 14.8495 8.96569 14.9767 8.65531 14.9862C8.34493 14.9957 8.04257 14.8867 7.80966 14.6813C7.57675 14.4759 7.43074 14.1896 7.40129 13.8805C7.37184 13.5713 7.46116 13.2626 7.65111 13.0169L7.75256 12.902L19.8526 0.802035Z"
fill="currentColor"/>
</svg>
)

View File

@ -3,29 +3,29 @@ import useConfig from "@/hooks/useConfig.ts";
const AppLogo = ({style}: { style?: React.CSSProperties, theme?: 'origin' | 'color' }) => {
return (
<svg style={style} viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<svg style={style} width="1em" height="0.95em" viewBox="0 0 37 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M671.584 201.142c22.034 0 39.896 17.398 39.896 38.858v163.2l207.98-141.828a20.4 20.4 0 0 1 20.666-1.322c6.602 3.35 10.732 10.002 10.732 17.252V746.7c0 7.25-4.13 13.902-10.732 17.252a20.4 20.4 0 0 1-20.668-1.32L711.48 620.8V784c0 21.46-17.86 38.858-39.896 38.858H113.04c-22.034 0-39.896-17.398-39.896-38.858V240c0-21.46 17.862-38.858 39.896-38.858h558.546z"
fill="#9C34FE"></path>
d="M28.9241 2.04763C29.2793 1.00838 30.7152 0.976901 31.1363 1.95321L31.1719 2.04823L31.6512 3.44974C31.761 3.77115 31.9385 4.06528 32.1716 4.31227C32.4048 4.55927 32.6882 4.75339 33.0028 4.88154L33.1316 4.92964L34.5332 5.40829C35.5724 5.76342 35.6039 7.19937 34.6282 7.62042L34.5332 7.65605L33.1316 8.1353C32.8101 8.24506 32.5159 8.42251 32.2688 8.65567C32.0217 8.88884 31.8275 9.1723 31.6993 9.48693L31.6512 9.6152L31.1725 11.0173C30.8174 12.0566 29.3814 12.088 28.961 11.1123L28.9241 11.0173L28.4455 9.61579C28.3357 9.29427 28.1583 9.00003 27.9251 8.75292C27.6919 8.50582 27.4085 8.31161 27.0939 8.1834L26.9656 8.1353L25.5641 7.65665C24.5242 7.30152 24.4928 5.86556 25.4691 5.44511L25.5641 5.40829L26.9656 4.92964C27.287 4.81981 27.5811 4.64233 27.8281 4.40916C28.0751 4.176 28.2692 3.89257 28.3974 3.57801L28.4455 3.44974L28.9241 2.04763ZM34.7992 1.07303e-07C34.9103 -1.40135e-07 35.0192 0.0311648 35.1135 0.089953C35.2077 0.148741 35.2836 0.232795 35.3325 0.332562L35.361 0.402044L35.5689 1.01135L36.1787 1.2192C36.2901 1.25702 36.3877 1.32705 36.4592 1.42041C36.5307 1.51376 36.5729 1.62624 36.5804 1.74359C36.5879 1.86094 36.5604 1.97787 36.5013 2.07957C36.4423 2.18128 36.3544 2.26317 36.2488 2.31487L36.1787 2.34337L35.5694 2.55123L35.3616 3.16112C35.3237 3.27243 35.2536 3.36998 35.1602 3.44142C35.0668 3.51286 34.9543 3.55496 34.837 3.56241C34.7197 3.56985 34.6027 3.54229 34.5011 3.48322C34.3994 3.42415 34.3176 3.33623 34.2659 3.2306L34.2374 3.16112L34.0296 2.55182L33.4197 2.34397C33.3083 2.30614 33.2107 2.23611 33.1392 2.14276C33.0677 2.0494 33.0256 1.93693 33.0181 1.81958C33.0105 1.70223 33.038 1.58529 33.0971 1.48359C33.1561 1.38189 33.244 1.3 33.3496 1.2483L33.4197 1.21979L34.029 1.01194L34.2368 0.402044C34.2769 0.284712 34.3526 0.182855 34.4535 0.110754C34.5543 0.0386538 34.6752 -7.43997e-05 34.7992 1.07303e-07Z"
fill="#9C34FE"/>
<path
d="M328.478 388.784c-7.584 0-14.122 5.196-15.64 12.434l-0.32 3.07v215.346c0 5.11 2.58 9.894 6.896 12.796a16.32 16.32 0 0 0 14.73 1.736l2.912-1.398 173.748-107.712c4.106-2.56 6.786-6.806 7.276-11.532 0.49-4.724-1.264-9.408-4.764-12.714l-2.512-1.944-173.748-107.712a16.28 16.28 0 0 0-8.578-2.332v-0.038z"
fill="#FFFFFF"></path>
d="M18.481 20.1202C19.2177 19.3857 19.8018 18.5128 20.2 17.5517C20.5981 16.5906 20.8023 15.5604 20.8009 14.5201C20.8009 10.1463 17.2548 6.6001 12.8809 6.6001C8.50712 6.6001 4.96094 10.1463 4.96094 14.5201C4.96094 16.7073 5.84732 18.6873 7.28084 20.1202"
stroke="black" strokeWidth="2" strokeLinecap="round"/>
<path d="M1 32.9999L1.66 29.6999L8.92 24.4199L12.88 28.3799L16.84 24.4199L24.1 29.6999L24.76 32.9999"
stroke="black"
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path
d="M5.61523 17.8246C5.62403 15.112 6.06557 13.1305 6.93985 11.88C8.25193 10.0049 9.17527 10.1429 9.84715 10.4346C10.519 10.7263 10.915 12.635 12.0377 13.1855C13.1597 13.7353 16.0333 13.8032 17.0174 14.4652C18.0008 15.1265 20.2514 16.3574 19.6904 18.4589"
stroke="black" strokeWidth="2"/>
</svg>
)
}
export const LogoText = ({style}: { style?: React.CSSProperties, theme?: 'origin' | 'color' }) => {
export const LogoText = ({style, className}: { style?: React.CSSProperties, className?: string }) => {
const {appName} = useConfig()
return (
<div className={'align-middle flex items-center h-full'}>
<svg style={style} viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="44" height="44">
<path
d="M671.584 201.142c22.034 0 39.896 17.398 39.896 38.858v163.2l207.98-141.828a20.4 20.4 0 0 1 20.666-1.322c6.602 3.35 10.732 10.002 10.732 17.252V746.7c0 7.25-4.13 13.902-10.732 17.252a20.4 20.4 0 0 1-20.668-1.32L711.48 620.8V784c0 21.46-17.86 38.858-39.896 38.858H113.04c-22.034 0-39.896-17.398-39.896-38.858V240c0-21.46 17.862-38.858 39.896-38.858h558.546z"
fill="#9C34FE"></path>
<path
d="M328.478 388.784c-7.584 0-14.122 5.196-15.64 12.434l-0.32 3.07v215.346c0 5.11 2.58 9.894 6.896 12.796a16.32 16.32 0 0 0 14.73 1.736l2.912-1.398 173.748-107.712c4.106-2.56 6.786-6.806 7.276-11.532 0.49-4.724-1.264-9.408-4.764-12.714l-2.512-1.944-173.748-107.712a16.28 16.28 0 0 0-8.578-2.332v-0.038z"
fill="#FFFFFF"></path>
</svg>
<span className={'ml-4 text-xl'}>{appName}</span>
<div className={`flex h-full ${className}`}>
<AppLogo style={style}/>
<span className={'ml-2 text-lg relative top-1'}>{appName}</span>
</div>
)
}

View File

@ -4,7 +4,6 @@ import React from "react";
export const UserAvatar = ({className, style}: { style?: React.CSSProperties; className?: string }) => {
return (
<span>
{/* <img src={Avatar} style={style} className={className}/> */}
<svg style={style} className={className} viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" width="128" height="128">
<path

View File

@ -0,0 +1,24 @@
import {ArrowUpOutlined} from "@ant-design/icons";
type ButtonToTopProps = {
onClick?: () => void;
visible?: boolean;
container?: HTMLElement | string;
}
export default function ButtonToTop(props: ButtonToTopProps) {
return (
<div className={'page-action-to-top'}>
{props.visible && <button className="btn-to-top text-white" onClick={()=>{
if(props.onClick){
props.onClick()
}else if(props.container){
const container = typeof(props.container) == 'string'? document.querySelector(props.container):props.container
if(container){
container.scrollTop = 0
}
}
}}><ArrowUpOutlined/></button>}
</div>
)
}

View File

@ -0,0 +1,78 @@
import React, {CSSProperties, useCallback, useEffect, useImperativeHandle, useRef} from "react";
import {useInViewport, useScroll} from "ahooks";
import { LoadingOutlined } from '@ant-design/icons';
import {Spin} from "antd";
export type InfiniteScrollerRef = {
scrollToPosition: (top: number) => void
};
export type InfiniteScrollerProps = {
children?: React.ReactNode;
className?: string;
rootClassName?: string;
style?: CSSProperties;
loadingPlaceholder?: React.ReactNode;
onCallback?: (page: number, prevPage) => void;
onScroll?: (top: number) => void;
empty?: React.ReactNode;
loading?: boolean;
pagination?: {
page: number;
limit: number;
total: number;
};
}
const InfiniteScroller = React.forwardRef<InfiniteScrollerRef, InfiniteScrollerProps>((props, ref) => {
const {pagination} = props;
const [inView] = useInViewport(() => document.querySelector('.data-load-control-element'))
const scrollContainerRef = useRef<HTMLDivElement | null>(null)
const scrollPosition = useScroll(scrollContainerRef);
const scrollToPosition = useCallback((top: number) => {
if (scrollContainerRef.current) {
scrollContainerRef.current!.scrollTo({
top,
behavior: 'smooth'
})
}
}, [scrollContainerRef])
useImperativeHandle(ref, () => {
return {
scrollToPosition
}
})
useEffect(() => {
if (scrollPosition && props.onScroll) {
props.onScroll(scrollPosition.top)
}
}, [scrollPosition])
useEffect(() => {
if (!pagination) return;
if (inView && !props.loading && pagination.total > 0) {
const maxPage = Math.ceil((pagination.total || 0) / pagination.limit)
const currentPage = pagination.page
if (maxPage > currentPage) {
props.onCallback?.(currentPage + 1, currentPage)
}
}
}, [inView])
return (<div ref={scrollContainerRef} className={`data-list-container ${props.rootClassName}`} style={props.style}>
<Spin wrapperClassName="data-list-load-spin" spinning={props.loading} indicator={<LoadingOutlined style={{fontSize:30}} spin />}>
<div className={`data-list-container-inner ${props.className}`}>{props.children}</div>
{props.loading && <div style={{minHeight:'30vh'}}></div>}
{props?.pagination && props.pagination.total > props.pagination.limit * props.pagination.page && (props.loadingPlaceholder ||
<div className="data-load-control-element py-10 text-center">
<div className="loading-text">...</div>
</div>)}
{props?.empty && !props.loading && props.pagination?.total == 0 && <div className="flex justify-center text-center pt-20">
<div className="rounded-lg px-4 py-10">
{props.empty}
</div>
</div>}
</Spin>
</div>);
}) //(props: InfiniteScrollerProps) =>{}
export default InfiniteScroller

View File

@ -2,6 +2,7 @@
// import {PauseOutlined, PlayCircleOutlined, FullscreenOutlined, FullscreenExitOutlined} from "@ant-design/icons"
// import {Progress} from "antd";
import React, {useEffect, useState} from "react";
import {PlayerInstance} from "@/hooks/useCache.ts";
import TCPlayer from 'tcplayer.js';
import 'tcplayer.js/dist/tcplayer.min.css';
@ -21,10 +22,13 @@ type Props = {
url?: string; cover?: string; showControls?: boolean; className?: string;
poster?: string;
onChange?: (state: State) => void;
onProgress?: (current:number,duration:number) => void;
muted?: boolean;
autoPlay?: boolean;
}
export type PlayerInstance = {
play: (url: string, currentTime: number) => void;
pause: () => void;
getState: () => State;
}
export const Player = React.forwardRef<PlayerInstance, Props>((props, ref) => {
@ -56,6 +60,10 @@ export const Player = React.forwardRef<PlayerInstance, Props>((props, ref) => {
},[props.url, tcPlayer])
useEffect(() => {
if(PlayerInstance.length != 0){
PlayerInstance.forEach(player => player.pause())
PlayerInstance.length = 0
}
const playerVideo = document.createElement('video');
const playerId = `player-container-${Date.now().toString(16)}`;
playerVideo.setAttribute('id', playerId)
@ -63,6 +71,8 @@ export const Player = React.forwardRef<PlayerInstance, Props>((props, ref) => {
playerVideo.setAttribute('playsInline', 'true')
playerVideo.setAttribute('webkit-playsinline', 'true')
if(props.className) playerVideo.setAttribute('className', props.className)
playerVideo.classList.add('digital-video-player')
PlayerInstance.push(playerVideo)
document.querySelector('.video-player-container-inner')!.appendChild(playerVideo)
const player = TCPlayer(playerId, {
@ -70,7 +80,7 @@ export const Player = React.forwardRef<PlayerInstance, Props>((props, ref) => {
controls: props.showControls,
// muted:props.muted,
poster: props.poster,
autoplay: true,
autoplay: typeof(props.autoPlay) != 'undefined' ? props.autoPlay : true,
licenseUrl: 'https://license.vod2.myqcloud.com/license/v2/1328581896_1/v_cube.license'
}
)
@ -83,19 +93,35 @@ export const Player = React.forwardRef<PlayerInstance, Props>((props, ref) => {
player.on('ended', () => {
setState({end: true, playing: false, error: false})
})
player.on('timeupdate', () => {
props.onProgress?.(player.currentTime(), player.duration())
})
player.on('error', () => {
setState({end: false, playing: false, error: true})
})
setTcPlayer(() => player)
return () => {
if (tcPlayer) {
tcPlayer.pause()
tcPlayer.unload()
// if (tcPlayer) {
// tcPlayer.pause()
// tcPlayer.unload()
// }else{
// playerVideo.pause()
// }
console.log('destroy video')
try{
Array.from(document.querySelectorAll('video')).forEach(v => v.pause())
}catch (e){
console.log(e)
}
playerVideo.parentElement?.removeChild(playerVideo)
}
}, [])
React.useImperativeHandle(ref, () => {
return {
pause(){
if (!tcPlayer) return;
tcPlayer.pause()
},
play: (url, currentTime = 0) => {
console.log('play', url, currentTime)
if (!tcPlayer) return;

View File

@ -1,13 +1,12 @@
import {useSortable} from "@dnd-kit/sortable";
import {useSetState} from "ahooks";
import React, {useEffect} from "react";
import {clsx} from "clsx";
import {Popconfirm} from "antd";
import {CheckCircleFilled, MenuOutlined, MinusCircleFilled,LoadingOutlined} from "@ant-design/icons";
import {Checkbox, Popconfirm} from "antd";
import ImageCover from '@/assets/images/cover.png'
import {IconEdit, IconPlay} from "@/components/icons";
import {IconDelete, IconEdit, IconPlaying, IconWarningCircle} from "@/components/icons";
import {VideoStatus} from "@/service/api/video.ts";
import {formatTime} from "@/util/strings.ts";
type Props = {
video: VideoInfo | LiveVideoInfo,
@ -16,20 +15,22 @@ type Props = {
index?: number;
checked?: boolean;
active?: boolean;
playing?: boolean;
onCheckedChange?: (checked: boolean) => void;
onPlay?: () => void;
onEdit?: () => void;
onItemClick?: () => void;
onRemove?: () => void;
id: number;
className?: string;
type?:'live'|'create'
type?: 'live' | 'create'
}
export const VideoListItem = (
{
id, video, onPlay, onRemove, checked,
id, video, onRemove, checked,playing,
onCheckedChange, onEdit, active, editable,
className, sortable,type
className, sortable, type, index,onItemClick
}: Props) => {
const {
attributes, listeners,
@ -42,48 +43,84 @@ export const VideoListItem = (
setState({checked})
}, [checked])
return <div
className={`video-item flex items-center gap-3 ${className}`}
ref={setNodeRef} style={{transform: `translateY(${transform?.y || 0}px)`,}}>
{/*{index && index > 0 && <div className="flex items-center px-2">*/}
{/* <div className="index-value w-[40px] h-[40px] flex items-center justify-center bg-gray-100 rounded-3xl">{index}</div>*/}
{/*</div>}*/}
<div
className={`video-item-info relative flex gap-2 flex-1 bg-gray-100 h-[80px] overflow-hidden rounded-lg p-3 shadow-blue-500 ${active ? 'video-item-shadow' : ''}`}>
<div className={'video-title leading-7 flex-1'}>{video.title || video.video_title}</div>
<div className={'video-item-cover bg-white rounded-md overflow-hidden'}>
<img className="w-[100px] h-[56px] object-cover" src={video.cover || ImageCover} />
</div>
{type == 'create' && video.status == VideoStatus.Generating && <div className={'absolute inset-0 bg-black/30 text-white flex items-center justify-center'}>
<span className="ml-1"></span>
</div>}
</div>
<div className="operation flex items-center ml-2 gap-3 text-lg text-gray-400 w-[116px]">
{sortable && (!active ? <button className="hover:text-blue-500 cursor-move" {...attributes} {...listeners}>
<MenuOutlined/>
</button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>)}
{onPlay &&<button className="hover:text-blue-500" onClick={onPlay} style={{fontSize: '1.3em'}}><IconPlay/></button>}
const generating = (type == 'create' && video.status == VideoStatus.Generating )
{editable && <>
{onEdit &&
<button className="hover:text-blue-500" onClick={onEdit} style={{fontSize: '1.1em'}}><IconEdit/>
</button>}
<button className="hover:text-blue-300" onClick={() => {
if (onCheckedChange) {
onCheckedChange(!state.checked)
} else {
setState({checked: !state.checked})
return <div
className={`video-item ${className}`}
ref={setNodeRef} style={{transform: `translateY(${transform?.y || 0}px)`,}}
>
<div className={`list-row ${generating ? 'disabled' : ''} ${active?'playing':''}`}>
<div
className="col number"
{... (sortable && !generating?listeners:{})}
{... (sortable && !generating?attributes:{})}
>{index}</div>
<div className="col cover cursor-pointer" onClick={onItemClick}>
<div className="relative">
<img className="w-[100px] h-[56px] object-cover" src={video.cover || ImageCover}/>
{generating &&
<div className={'absolute inset-0 bg-black/30 text-white flex items-center justify-center'}>
<span className="ml-1"></span>
</div>
}
}}><CheckCircleFilled className={clsx({'text-blue-500': state.checked})}/></button>
{onRemove && <Popconfirm
title={<div style={{minWidth: 150}}><span>?</span></div>}
onConfirm={onRemove}
okText="删除"
cancelText="取消"
>
<button className="hover:text-blue-500"><MinusCircleFilled/></button>
</Popconfirm>}
</>}
{/* && active*/}
{!generating && playing && <div className={'absolute rounded inset-0 bg-black/30 text-sm text-white flex items-center justify-center'}>
<div className="text-center">
<IconPlaying className="inline-block text-xl" />
<div></div>
</div>
</div>}
</div>
</div>
<div
className="col title"
{... (sortable && !generating?listeners:{})}
{... (sortable && !generating?attributes:{})}
>
<div className="line-clamp-2">
{video.title || video.video_title}
</div>
</div>
<div
className="col generated-time"
{... (sortable && !generating?listeners:{})}
{... (sortable && !generating?attributes:{})}
>{video.ctime ? formatTime(video.ctime,'min') : '-'}</div>
<div className="col operation">
{/*{sortable && !generating && (!active ?*/}
{/* <button className="hover:text-blue-500 cursor-move">*/}
{/* <MenuOutlined/>*/}
{/* </button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>)}*/}
<div className={"flex items-center gap-4"}>
{editable && !generating && <>
{onEdit &&
<button className="hover:text-blue-500" onClick={e=>{
e.preventDefault()
e.stopPropagation()
onEdit?.()
}} style={{fontSize: '1.1em'}}>
<IconEdit/>
</button>}
{onRemove && <Popconfirm
rootClassName={'popconfirm-main'}
placement={'left'}
arrow={false}
icon={<IconWarningCircle/>}
title={'你确定要删除此视频吗?'}
// description={`删除后需从重新${type == 'create' ? '生成' : '推流'}`}
onConfirm={onRemove}
><button className="hover:text-blue-500"><IconDelete/></button></Popconfirm>}
<Checkbox checked={state.checked} onChange={() => {
if (onCheckedChange) {
onCheckedChange(!state.checked)
} else {
setState({checked: !state.checked})
}
}} />
</>}
</div>
</div>
</div>
</div>
}

View File

33
src/hooks/useCache.ts Normal file
View File

@ -0,0 +1,33 @@
import {create} from "zustand"
export const PlayerInstance: HTMLVideoElement[] = [];
export const defaultCache:{
firstLoadPath?: string
} = {};
type StoreInstance<T> = {
cache: T[];
clear: () => void;
set: (values: T[]) => void;
add: (value: T) => void;
remove: (value: T) => void
}
export const useIndexArrayCache = create<StoreInstance<Id>>((set) => ({
cache: [],
set: (values: Id[]) => {
set({cache: values})
},
clear: () => {
set({cache: []})
},
add: (id: Id) => {
set((state) => ({
cache: [...(state.cache || []), id]
}))
},
remove: (id: Id) => {
set((state) => ({
cache: (state.cache || []).filter((item) => item != id)
}))
}
}))

View File

@ -0,0 +1,3 @@
function useInfiniteScroller<T>(fetch: (page: number) => Promise<T[]>, deps: any[]) {
}

View File

@ -1,56 +1,57 @@
import {Button, Form, Input, Select, Space} from "antd";
import {Input} from "antd";
import {useSetState} from "ahooks";
import {PlayCircleOutlined} from "@ant-design/icons";
import {SearchListTimes} from "@/pages/news/components/news-source.ts";
import {SearchOutlined} from "@ant-design/icons";
import React from "react";
import TimeSelect from "@/components/form/time-select.tsx";
type Props = {
onSearch?: (params: VideoSearchParams) => void;
onBtnStartClick?: () => Promise<void>;
loading?:boolean;
loading?: boolean;
}
export default function SearchForm({onSearch, onBtnStartClick,loading}: Props) {
export default function SearchForm({onSearch}: Props) {
const [state, setState] = useSetState<{
pushing?: boolean;
}>({})
const onFinish = (values) => {
time_flag: number;
title?: string;
}>({
time_flag: 0
})
const onFinish = (params: Partial<VideoSearchParams>) => {
onSearch?.({
...values,
pagination: {page: 1, limit: 10}
time_flag: params.time_flag,
title: params.title,
pagination: {page: 1, limit: 12}
})
//console.log(values)
}
return (<div className={'search-panel'}>
<div className="flex justify-between items-center">
<div className="search-form">
<Form<VideoSearchParams> className={""} layout="inline" onFinish={onFinish} initialValues={{title:'',time_flag:0}}>
<Form.Item name="title">
<Input className="w-[200px]" allowClear placeholder={'请输入搜索信息'}/>
</Form.Item>
<Form.Item label={'更新时间'} name="time_flag" className="w-[250px]">
<Select
options={SearchListTimes}
optionRender={(option) => (
<div className="flex items-center">
<span role="icon" className={`radio-icon`}></span>
<span role="listitem" aria-label={String(option.label)}>{option.label}</span>
</div>
)}
/>
</Form.Item>
<Form.Item>
<Space size={10}>
<Button loading={loading} type={'primary'} htmlType={'submit'}></Button>
</Space>
</Form.Item>
</Form>
const handleTimeFilter = (time_flag: number) => {
setState({time_flag})
onFinish({
title: state.title, time_flag
})
}
return (<div className={'search-panel pt-6 pb-2'}>
<div className="search-form">
<div className="flex items-center gap-4">
<Input
className="w-[270px] rounded-3xl"
prefix={<SearchOutlined/>}
onChange={e => setState({title: e.target.value})}
onPressEnter={() => onFinish(state)}
onBlur={() => onFinish(state)}
allowClear
placeholder={'请输入视频标题关键字进行信息'}
/>
<TimeSelect
className="w-[120px] ml-1"
value={state.time_flag}
onChange={handleTimeFilter}
/>
</div>
<Space size={10}>
<Button
loading={state.pushing} type={'primary'}
onClick={onBtnStartClick} icon={<PlayCircleOutlined/>}
></Button>
</Space>
</div>
</div>)
}

View File

@ -0,0 +1,30 @@
.videoItem {
border: solid 3px transparent;
:global {
.video-bottom {
}
}
}
.videoChecked {
@apply border-blue-500;
}
.playIcon {
--size: 40px;
@apply bg-black/70 flex items-center justify-center;
border: solid 2px rgba(255, 255, 255, 0.5);
border-radius: var(--size);
width: var(--size);
height: var(--size);
color: white;
cursor: pointer;
&:hover{
@apply bg-blue-500;
}
svg{
font-size: 24px;
transform: translate(2px);
}
}

View File

@ -1,21 +1,17 @@
import {Button, Input, Modal} from "antd";
import {Modal} from "antd";
import {saveAs} from "file-saver";
import {useEffect, useState} from "react";
import {useSetState} from "ahooks";
import {Player} from "@/components/video/player.tsx";
import ArticleGroup from "@/components/article/group";
import * as article from "@/service/api/article.ts";
import {push2room} from "@/service/api/video.ts";
import {showErrorToast, showToast} from "@/components/message.ts";
type Props = {
video?: VideoInfo;
autoPlay?: boolean;
onClose?: () => void
}
export default function VideoDetail({video, onClose}: Props) {
const [groups, setGroups] = useState<BlockContent[][]>([]);
export default function VideoDetail({video, onClose,autoPlay}: Props) {
const [state, setState] = useSetState({
exporting: false,
pushing: false,
@ -40,53 +36,26 @@ export default function VideoDetail({video, onClose}: Props) {
}
}
useEffect(() => {
if (video) {
if (video.id > 0) {
article.getById(video.id).then(res => {
setGroups(res.content_group)
})
}
}
}, [video])
return (<>
<Modal open={!!video} title="新闻视频详情" width={1000} footer={null} onCancel={onClose}>
<div className="flex gap-2 my-5">
<div className="news-video w-[350px]">
<div className="video-container bg-gray-100 rounded overflow-hidden h-[640px]">
<Player url={video?.oss_video_url} poster={video?.cover} showControls={true}
className="w-[360px] h-[640px] bg-white"/>
</div>
<div className="video-info text-right text-sm text-gray-600 mt-3">
<span>创建时间: 5小时前</span>
</div>
</div>
<div className="detail flex-1 ml-5">
<div className="text-lg"></div>
<div className="article-title mt-5 items-center flex">
<span className="text text-base"></span>
<span className="ml-4 flex-1">
<Input value={video?.title}/>
</span>
</div>
<div className="aricle-body mt-3">
<div className="title">
<span className="text text-base"></span>
</div>
<div className="box mt-1">
<ArticleGroup groups={groups}/>
</div>
<Modal
open={!!video} width={390} closeIcon={null} title={null} footer={null} onCancel={onClose}
rootClassName={"article-edit-modal"}
>
<div className="flex gap-2 px-6 pt-6">
<div className="news-video w-[340px]">
<div className="video-container bg-gray-100 rounded overflow-hidden">
<Player autoPlay={autoPlay} url={video?.oss_video_url} poster={video?.cover} showControls={true}
className="w-[340px] h-[600px] bg-white"/>
</div>
</div>
</div>
<div className="footer flex justify-between">
<div className="action flex gap-2">
<Button loading={state.pushing} type="primary" onClick={pushToRoom}></Button>
<Button onClick={downloadVideo}></Button>
</div>
<div className="close">
<Button onClick={onClose}></Button>
<div className="flex justify-end modal-control-footer">
<div className="flex gap-4">
<button disabled={state.pushing} className="text-gray-400 hover:text-gray-800 " type="text" onClick={pushToRoom}></button>
<button disabled={state.exporting} className="text-gray-400 hover:text-gray-800 " onClick={downloadVideo}
type="text">
</button>
<button onClick={onClose} type="text" className="text-gray-800 hover:text-blue-500"></button>
</div>
</div>
</Modal>

View File

@ -1,49 +1,41 @@
import {Checkbox, Image, Tag} from "antd";
import {IconDelete} from "@/components/icons";
import {useState} from "react";
import clsx from "clsx";
import {CaretRightOutlined} from "@ant-design/icons"
import {timeFromNow} from "@/util/strings.ts";
import ImageCover from './cover.png'
import {formatDuration, timeFromNow} from "@/util/strings.ts";
import dayjs from "dayjs";
import styles from './style.module.scss'
type VideoItemProps = {
videoInfo: VideoInfo;
onLive?: boolean;
onClick?: () => void;
onClick?: (autoPlay:boolean) => void;
onRemove?: () => void;
onCheckedChange?: (checked:boolean) => void;
onCheckedChange?: (checked: boolean) => void;
checked?: boolean;
}
export default function VideoItem(props: VideoItemProps) {
const [state, setState] = useState({
checked: false
})
const handleCheckedChange = (checked:boolean) => {
setState({checked})
if (props.onCheckedChange) {
props.onCheckedChange(checked)
}
}
return <div className={'video-item bg-gray-100 hover:drop-shadow-md rounded overflow-hidden relative group'}>
<div className={`controls absolute top-1 right-1 z-[2] p-1 rounded items-center gap-2 bg-white/80 ${state.checked?'flex':'hidden'} group-hover:flex`}>
<span onClick={props.onRemove} className={'cursor-pointer text-blue-500 text-2xl cursor-pointer'}><IconDelete /></span>
{!props.onLive && <Checkbox onChange={e=>handleCheckedChange(e.target.checked)} />}
export default function VideoItem(props: VideoItemProps) {
return <div
className={clsx(styles.videoItem, `rounded-lg h-[240px] overflow-hidden relative group ${props.checked ? styles.videoChecked : ''}`)}>
<div className={`controls absolute top-1 right-1 z-[2] rounded items-center gap-2`}>
{/*<span onClick={props.onRemove} className={'cursor-pointer text-blue-500 text-2xl cursor-pointer'}><IconDelete /></span>*/}
<div className={clsx("checkbox", {checked: props.checked})}
onClick={() => props.onCheckedChange?.(!props.checked)}></div>
</div>
<div className="cover" onClick={props.onClick}>
<img className={'w-full cursor-pointer h-[180px] object-cover'} src={props.videoInfo.cover}/>
</div>
<div className="text-sm py-2 px-3">
<div className="title my-1 cursor-pointer" onClick={props.onClick}>{props.videoInfo.title}</div>
<div className="info flex justify-between gap-2 text-sm">
<div className="video-time-info text-gray-500">
<span>: {formatDuration(Math.ceil(props.videoInfo.duration / 1000))}</span>
<span className="ml-1">{timeFromNow(props.videoInfo.publish_time)}</span>
</div>
{props.videoInfo.status == 3 && <div className="live-info">
<Tag color="processing" className="mr-0"></Tag>
</div>}
<div className="cover">
<img className={'w-full cursor-pointer object-cover'} src={props.videoInfo.cover}/>
<div className={'absolute inset-x-0 top-0 flex items-center justify-center bottom-[36px]'}>
<div className={styles.playIcon} onClick={()=>props.onClick?.(true)}><CaretRightOutlined /></div>
</div>
</div>
<div
className="video-bottom bg-black/30 backdrop-blur-[2px] text-sm absolute inset-x-0 bottom-0 text-white py-2 px-3 items-center flex justify-between">
<div className="title cursor-pointer flex-1 text-nowrap overflow-hidden text-ellipsis min-w-0 mr-4"
onClick={()=>props.onClick?.(false)}>{props.videoInfo.title}</div>
<div className="video-time-info">{timeFromNow(props.videoInfo.ctime)}</div>
</div>
<div
className={"absolute top-1 left-1 bg-black/50 rounded-3xl text-white px-3 py-0.5"}>{Math.ceil(props.videoInfo.duration / 1000)}s
</div>
</div>
}

View File

@ -1,25 +1,52 @@
import {useState} from "react";
import {Empty, Modal, Pagination} from "antd";
import {useRequest} from "ahooks";
import React, {useEffect, useRef, useState} from "react";
import {Checkbox, Modal, Space} from "antd";
import {useRequest, useSetState} from "ahooks";
import VideoItem from "@/pages/library/components/video-item.tsx";
import SearchForm from "@/pages/library/components/search-form.tsx";
import VideoDetail from "@/pages/library/components/video-detail.tsx";
import {search} from "@/service/api/video.ts";
import {deleteHistories, push2room, search} from "@/service/api/video.ts";
import {getList} from "@/service/api/live.ts";
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
import ButtonBatch from "@/components/button-batch.tsx";
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
import {IconArrowRight, IconDelete} from "@/components/icons";
const DEFAULT_PAGE_LIMIT = {
page: 1,
limit: 12
}
export default function LibraryIndex() {
const [modal, contextHolder] = Modal.useModal();
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
const [params, setParams] = useState<VideoSearchParams>({
time_flag: 0,
pagination: {
page: 1,
limit: 10
pagination: {...DEFAULT_PAGE_LIMIT}
})
const [state, setState] = useSetState({
checkedAll: false,
loading: false,
pushedCount: 0,
showToTop: false
})
const [data, setData] = useState<DataList<VideoInfo>>()
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
const {loading} = useRequest(() => search(params), {
refreshDeps: [params],
onSuccess: (data) => {
setData(prev => {
// 判断页码是否是第1页
if (data.pagination.page == 1) return data;
return {
list: [...(prev?.list || []), ...(data?.list || [])],
pagination: data.pagination
}
})
}
})
const {data,loading} = useRequest(() => search(params), {
refreshDeps: [params]
})
const handleRemove = (video: VideoInfo) => {
modal.confirm({
title: '删除提示',
@ -39,53 +66,108 @@ export default function LibraryIndex() {
}
})
}
const [detailVideo, setDetailVideo] = useState<VideoInfo>()
const [detailVideo, setDetailVideo] = useState<{
video: VideoInfo,
autoPlay: boolean
}>()
const handleAllCheckedChange = (checked: boolean) => {
setCheckedIdArray(checked ? data.list.map(v => v.id) : [])
setState({
checkedAll: !state.checkedAll
})
}
const loadPushedState = () => {
getList().then((ret) => {
if (ret.list) {
setState({pushedCount: ret.list.length})
}
})
}
const refresh = () => {
loadPushedState();
setParams(prev => ({...prev, pagination: {page: 1, limit: DEFAULT_PAGE_LIMIT.limit}, request_time: Date.now()}))
}
useEffect(loadPushedState, [])
return (<>
<div className={'container py-20'}>
<div className={'container pb-5'}>
{contextHolder}
<div className="search-form-container mb-5">
<div className="search-form-container">
<SearchForm
onSearch={setParams}
onBtnStartClick={handleLive}
loading={loading}
/>
</div>
<div className="bg-white rounded p-5">
<div className={'video-list-container grid gap-5 grid-cols-4'}>
{data?.list?.map((it, idx) => (
<VideoItem
onLive={idx == 2} key={it.id}
videoInfo={it}
onRemove={() => handleRemove(it)}
onClick={() => setDetailVideo(it)}
onCheckedChange={(checked) => {
setCheckedIdArray(idArray => {
return checked ? idArray.concat(it.id) : idArray.filter(id => id != it.id);
})
}}
/>
))}
</div>
<div className="video-page-container flex justify-center mt-5">
{data?.pagination && data?.pagination.total > 0 ? <div className="flex justify-center mt-10">
<Pagination
current={params.pagination.page}
total={data?.pagination.total}
pageSize={data?.pagination.limit}
showSizeChanger={false}
simple={true}
rootClassName={'simple-pagination'}
onChange={(page) => setParams(prev=>({...prev,pagination: {page, limit: 10}}))}
/>
</div> : <div className="py-10">
<Empty />
<div className="">
<div className="live-control flex justify-between mb-2">
<div className="pl-[70px]"></div>
<div className="flex items-center">
<Space className="text-gray-400">
<span> {data?.list.length || 0} </span>
<span> {state.pushedCount} </span>
<span className={'text-blue-500'}> {checkedIdArray.length} </span>
</Space>
<button className="hover:text-blue-300 text-gray-400 ml-2"
onClick={() => handleAllCheckedChange(checkedIdArray.length != data?.list.length)}>
<span className="text-sm mr-2"></span>
{/*<CheckCircleFilled className={clsx({'text-blue-500': state.checkedAll})}/>*/}
</button>
<Checkbox checked={checkedIdArray.length == data?.list.length}
onChange={e => handleAllCheckedChange(e.target.checked)}/>
</div>
}
{/*<Pagination defaultCurrent={1} total={50}/>*/}
</div>
<InfiniteScroller
ref={scrollerRef} loading={loading} rootClassName="video-history-list-container"
pagination={data?.pagination} onCallback={(page) => {
setParams(prev => ({
...prev,
pagination: {page, limit: DEFAULT_PAGE_LIMIT.limit}
}))
}} onScroll={(top) => setState({showToTop: top > 30})}
>
<div className={'video-list-container grid gap-4 grid-cols-3 xl:grid-cols-4'}>
{data?.list?.map((it, idx) => (
<VideoItem
onLive={idx == 2}
key={idx}
videoInfo={it}
onRemove={() => handleRemove(it)}
onClick={(autoPlay) => setDetailVideo({video: it, autoPlay})}
checked={checkedIdArray.includes(it.id)}
onCheckedChange={(checked) => {
setCheckedIdArray(idArray => {
return checked ? idArray.concat(it.id) : idArray.filter(id => id != it.id);
})
}}
/>
))}
</div>
</InfiniteScroller>
</div>
</div>
{detailVideo && <VideoDetail video={detailVideo} onClose={() => setDetailVideo(undefined)}/>}
{detailVideo && <VideoDetail video={detailVideo.video} autoPlay={detailVideo.autoPlay}
onClose={() => setDetailVideo(undefined)}/>}
<div className="page-action">
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
{checkedIdArray?.length > 0 && <ButtonBatch
selected={checkedIdArray}
onSuccess={refresh}
className='bg-gray-300 hover:bg-gray-400 text-white'
icon={<IconDelete className=""/>}
title={`你确定要删除选择的 ${checkedIdArray.length} 条视频吗?`}
confirmMessage={'删除后需重新生成视频'}
onProcess={deleteHistories}
></ButtonBatch>}
{checkedIdArray?.length > 0 && <ButtonBatch
selected={checkedIdArray}
onSuccess={refresh}
className='bg-[#4096ff] hover:bg-blue-600 text-white'
icon={<IconArrowRight className={'text-white'}/>}
onProcess={push2room}
></ButtonBatch>}
</div>
</>)
}

View File

@ -1,5 +1,5 @@
import React, {useEffect, useMemo, useRef, useState} from "react";
import {Button, Modal} from "antd";
import {Checkbox, Empty, Modal, Space} from "antd";
import {SortableContext, arrayMove} from '@dnd-kit/sortable';
import {DndContext} from "@dnd-kit/core";
@ -12,20 +12,29 @@ import FlvJs from "flv.js";
import {formatDuration} from "@/util/strings.ts";
import {useSetState} from "ahooks";
import {Player, PlayerInstance} from "@/components/video/player.tsx";
import {IconDelete, IconLocked, IconUnlock} from "@/components/icons";
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
const cache: { flvPlayer?: FlvJs.Player, timerPlayNext?: any, timerLoadState?: any, prevUrl?: string } = {}
export default function LiveIndex() {
const videoRef = useRef<HTMLVideoElement | null>(null)
const player = useRef<PlayerInstance | null>(null)
const [videoData, setVideoData] = useState<LiveVideoInfo[]>([])
const [modal, contextHolder] = Modal.useModal()
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
const [editable, setEditable] = useState<boolean>(false)
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
const [state, setState] = useSetState({
activeIndex: -1,
muted: true,
showToTop: false,
checkedAll: false,
originSort: '',
playProgress: 0,
loading:false
})
const activeIndex = useRef(state.activeIndex)
useEffect(() => {
@ -103,17 +112,33 @@ export default function LiveIndex() {
const loadList = () => {
clearAllTimer();
setState({loading: true})
getList().then(res => {
// console.log('origin list', res.list.map(s => s.id))
setVideoData(() => (res.list || []))
setState({
originSort: res.list ? res.list.map(s => s.id).join(',') : ''
})
setCheckedIdArray([])
}).catch(showErrorToast).finally(()=>{
setState({loading: false})
});
}
useEffect(initPlayingState, [videoData])
useEffect(() => {
loadList()
return clearAllTimer;
return ()=>{
clearAllTimer();
setTimeout(()=>{
console.log('pause all video')
try{
Array.from(document.querySelectorAll('video')).forEach(v => v.pause())
}catch (e){
console.log(e)
}
},20)
}
}, [])
const processDeleteVideo = async (ids: Id[]) => {
@ -123,31 +148,39 @@ export default function LiveIndex() {
}).catch(showErrorToast)
}
const handleConfirm = () => {
if (!editable) {
setEditable(true)
return;
}
const newSort = videoData.map(s => s.id).join(',')
if (newSort == state.originSort) {
setEditable(false)
return;
}
modal.confirm({
title: '提示',
content: '是否采纳移动视频位置操作?',
centered: true,
onOk: () => {
//showToast('编辑成功!!!', 'info');
modifyOrder(videoData.map(s => s.id)).then(() => {
showToast('已完成直播队列的修改!', 'success')
setEditable(false)
loadList()
}).catch(() => {
showToast('调整视频顺序失败,请重试!')
showToast('调整视频顺序失败,请重试!', 'warning')
})
// showToast('编辑成功!!!', 'info');
// console.log('origin list', videoData.map(s => s.id))
},
onCancel: () => {
showToast('退出并恢复之前的直播队列!', 'info');
loadList()
setEditable(false)
}
})
}
const handleCancelConfirm = () => {
modal.confirm({
title: '提示',
content: '是否取消移动视频位置操作?',
onOk: () => {
showToast('退出并清除移动视频位置操作!', 'info');
loadList()
setEditable(false)
},
const handleAllCheckedChange = () => {
setCheckedIdArray(state.checkedAll ? [] : videoData.map(v => v.id))
setState({
checkedAll: !state.checkedAll
})
}
@ -156,53 +189,91 @@ export default function LiveIndex() {
// 计算总时长
return videoData.reduce((sum, v) => sum + Math.ceil(v.video_duration / 1000), 0);
}, [videoData])
// 根据当前播放index计算已经播放时长
const currentTotalDuration = useMemo(() => {
if (state.activeIndex == -1 || !videoData || videoData.length == 0) return 0;
// 计算总时长
return videoData
.filter((_, index) => (index < state.activeIndex))
.reduce((sum, v) => sum + Math.ceil(v.video_duration / 1000), 0) + state.playProgress
;
}, [videoData, state.playProgress])
return (<div className="container py-10 page-live">
{contextHolder}
const currentSelectedId = useMemo(() => {
if (state.activeIndex < 0 || state.activeIndex >= videoData.length) return [];
const currentId = videoData[state.activeIndex];
return checkedIdArray.filter(id => currentId.id != id)
}, [checkedIdArray, state.activeIndex])
return (<div className="container py-5 page-live">
<div className="h-[36px]"></div>
<div className="flex">
<div className="video-player-container mr-8 flex flex-col">
<div className="text-center text-base"></div>
<div className="video-player flex justify-center flex-1 mt-5">
<div className="live-player relative rounded overflow-hidden w-[360px] h-[636px]"
style={{backgroundColor: 'hsl(210, 100%, 48%)'}}>
<Player ref={player} className="w-[360px] h-[636px] bg-white" muted={true}/>
<div className="video-player-container mr-16 flex items-center">
<div>
<div className="text-center text-base text-gray-400"></div>
<div className="video-player flex justify-center flex-1 mt-1">
<div className="live-player relative rounded overflow-hidden w-[360px] h-[636px]"
style={{backgroundColor: 'hsl(210, 100%, 48%)'}}>
<Player
ref={player} className="w-[360px] h-[636px] bg-white"
muted={true}
onProgress={(progress) => {
setState({playProgress: progress})
}}
/>
</div>
</div>
<div className="text-center text-sm mt-4 text-gray-400">
<span>: {formatDuration(currentTotalDuration)} / {formatDuration(totalDuration)}</span>
</div>
</div>
<div className="mt-4 text-center text-sm">
<span>: {formatDuration(totalDuration)}</span>
</div>
</div>
<div className="video-list-container flex-1">
<div className=" bg-white py-8 px-6 rounded">
<div className="live-control flex justify-between mb-4">
{editable ? <>
<div className="flex gap-2">
<Button type="primary" onClick={handleConfirm}></Button>
<Button onClick={handleCancelConfirm}>退</Button>
</div>
</> : <div>
<Button type="primary" onClick={() => setEditable(true)}></Button>
</div>}
{!editable && <div>
<ButtonBatch
selected={checkedIdArray}
emptyMessage={`请选择要删除的新闻视频`}
confirmMessage={`是否删除当前的${checkedIdArray.length}个新闻视频?`}
onSuccess={loadList}
onProcess={processDeleteVideo}
></ButtonBatch>
</div>}
<div className="video-list-container video-list-sort-container flex flex-col flex-1 mt-2">
<div className="live-control flex justify-between mb-1">
<div>
<Space>
{/*<span className={"text-blue-500"}>视频正在播放{state.activeIndex == -1 ? '' : `到 ${state.activeIndex + 1} 条`}</span>*/}
<span> {videoData.length} </span>
</Space>
</div>
<div className="live-video-list-sort-container">
<div className="flex">
<div className="sort-number-container mr-2">
{videoData.map((v, index) => (
<div key={index} className="flex items-center px-2 h-[80px] mt-3 mb-2">
<div
className="index-value w-[40px] h-[40px] flex items-center justify-center bg-gray-100 rounded-3xl">{index + 1}</div>
</div>
))}
</div>
<div className="flex items-center">
<div className={'flex items-center text-gray-400 cursor-pointer select-none'}
onClick={handleConfirm}>
<span>{editable ? '已解锁' : '锁定状态不可排序'}</span>
<span className="ml-2 text-sm">
{editable ? <IconUnlock/> : <IconLocked/>}
</span>
</div>
<div className="check-all ml-10">
<button className="hover:text-blue-300 text-gray-400"
onClick={handleAllCheckedChange}>
<span className="text-sm mr-2 whitespace-nowrap"></span>
{/*<CheckCircleFilled className={clsx({'text-blue-500': state.checkedAll})}/>*/}
</button>
<Checkbox checked={state.checkedAll} onChange={() => handleAllCheckedChange()}/>
</div>
</div>
</div>
<div className="list-header">
<div className="list-row header-row">
<div className="col number">No.</div>
<div className="col cover"></div>
<div className="col title"></div>
<div className="col generated-time"></div>
<div className="col operation"></div>
</div>
</div>
<div className="">
<div className="live-video-list-sort-container ">
<InfiniteScroller
ref={scrollerRef}
loading={state.loading}
onScroll={top => setState({showToTop: top > 30})}
onCallback={() => {
}}
>
{videoData.length == 0 && <div className="m-auto py-16"><Empty/></div>}
<div className="sort-list-container flex-1">
<DndContext onDragEnd={(e) => {
const {active, over} = e;
@ -223,24 +294,43 @@ export default function LiveIndex() {
id={v.id}
key={index}
active={state.activeIndex == index}
playing={state.activeIndex == index}
className={`list-item-${index} mt-3 mb-2`}
checked={checkedIdArray.includes(v.id)}
onCheckedChange={(checked) => {
setCheckedIdArray(idArray => {
return checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
})
const newIdArray = checked ? checkedIdArray.concat(v.id) : checkedIdArray.filter(id => id != v.id);
setState({checkedAll: newIdArray.length == videoData.length})
setCheckedIdArray(newIdArray)
// setCheckedIdArray(idArray => {
// return checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
// })
}}
onRemove={() => processDeleteVideo([v.id])}
editable={!editable}
sortable={editable}
editable={!editable && state.activeIndex != index}
sortable={editable && state.activeIndex != index}
/>))}
</SortableContext>
</DndContext>
</div>
</div>
</InfiniteScroller>
</div>
</div>
</div>
</div>
<div className="page-action">
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
{checkedIdArray.length > 0 && <ButtonBatch
className='bg-gray-300 hover:bg-gray-400 text-white'
selected={checkedIdArray}
emptyMessage={`请选择要删除的视频`}
confirmMessage={`是否删除当前的${checkedIdArray.length}条视频?`}
onSuccess={loadList}
onProcess={processDeleteVideo}
>
<span className={'text'}></span>
<IconDelete/>
</ButtonBatch>}
</div>
{contextHolder}
</div>)
}

View File

@ -1,5 +1,5 @@
import {Cascader} from "antd";
import React, {useEffect, useMemo} from "react";
import React, {useEffect} from "react";
const prevSelectValues: Id[][] = [];
@ -71,9 +71,6 @@ export default function ArticleCascader(props: {
// 清除上一次的选中值
prevSelectValues.length = 0;
}, [])
// const allOptionValue = useMemo(() => {
// return getAllValue(props.options)
// }, [props.options])
const setSelectValues = (value: Id[][]) => {
_setSelectValues(value)
@ -81,33 +78,6 @@ export default function ArticleCascader(props: {
props.onChange?.(value)
}
const handleChange = (values: Id[][]) => {
// const fullValues = buildValues(props.options, values)
// const diffValue = getValuesDiff(fullValues, prevSelectValues);
// const isIncrease = fullValues.length > prevSelectValues.length;
// prevSelectValues.length = 0;
//
// if(values.length == 0){
// setSelectValues([])
// return;
// }
// // 判断操作的是否是全部
// if(diffValue?.length == 1 && diffValue[0] == -1){
// if(isIncrease) prevSelectValues.push(...allOptionValue);
// setSelectValues(isIncrease ? [...allOptionValue] : [])
// return;
// }
// // if(fullValues.length != allOptionValue.length){
// // setSelectValues(fullValues.filter(s=>s.length == 1 && s[0] != -1))
// // }else{
// //
// // }
//
// if(fullValues.filter(s=>s.length > 1 || s[0] != -1).length == allOptionValue.length - 1){
// prevSelectValues.push(...allOptionValue);
// setSelectValues( [...allOptionValue])
// return;
// }
// prevSelectValues.push(...fullValues);
setSelectValues(values.filter(s=>s.length > 1 || s[0] != -1))
}

View File

@ -0,0 +1,48 @@
import {App} from "antd";
import {showToast} from "@/components/message.ts";
import React, {useState} from "react";
import {IconDelete, IconWarningCircle} from "@/components/icons";
import {deleteByIds} from "@/service/api/article.ts";
export default function ButtonDeleteBatch(props: { ids: Id[];onSuccess?: () => void; }) {
const {modal} = App.useApp();
const [loading, setLoading] = useState(false)
const handlePush = () => {
setLoading(true)
deleteByIds(props.ids).then(() => {
props.onSuccess?.();
showToast('删除成功', 'success')
}).catch(() => {
showToast('删除失败', 'error')
}).finally(() => {
setLoading(false)
})
}
const onPushClick = () => {
if(loading) return;
if (props.ids.length === 0) {
showToast('请选择要删除的新闻', 'warning')
return
}
modal.confirm({
wrapClassName:'root-modal-confirm',
icon: <span className="anticon anticon-exclamation-circle"><IconWarningCircle/></span>,
title: `你确定要删除选择的 ${props.ids.length} 条新闻吗?`,
content: '删除后需从新闻素材中重新选择',
onOk: handlePush,
centered: true
})
}
return (
<div>
<button
disabled={loading}
onClick={onPushClick}
className='bg-gray-300 hover:bg-gray-400 text-white'
>
<span className={'text'}></span>
<IconDelete className=""/>
</button>
</div>
)
}

View File

@ -6,6 +6,7 @@ import {useState} from "react";
import {getById} from "@/service/api/news.ts";
import {showToast} from "@/components/message.ts";
import {IconDownload} from "@/components/icons";
/**
@ -58,16 +59,13 @@ async function downloadAsZip(list: NewsInfo[]) {
})
const content = await zip.generateAsync({type: "blob"});
saveAs(content, "news.zip");
// .then(function (content) {
//
// }).finally(() => {
// setLoading(false)
// });
}
export default function ButtonNewsDownload(props: { ids: Id[] }) {
const [loading, setLoading] = useState(false)
const onDownloadClick = async (ids: Id[]) => {
if(loading) return;
if (props.ids.length === 0) {
showToast('请选择要下载的新闻', 'warning')
return
@ -81,9 +79,15 @@ export default function ButtonNewsDownload(props: { ids: Id[] }) {
} finally {
setLoading(false)
}
}
return (
<Button loading={loading} onClick={() => onDownloadClick(props.ids)}></Button>
<button
disabled={loading}
className={'btn-action bg-[#eef5ff] text-gray-800 hover:bg-[#d2e3ff]'}
onClick={() => onDownloadClick(props.ids)}
>
<span className="text"></span>
<IconDownload />
</button>
)
}

View File

@ -1,14 +1,21 @@
import {Button, Modal} from "antd";
import {showToast} from "@/components/message.ts";
import {useState} from "react";
import {push2article} from "@/service/api/news.ts";
import {IconArrowRight} from "@/components/icons";
import {useNavigate} from "react-router-dom";
import {useIndexArrayCache} from "@/hooks/useCache.ts";
export default function ButtonPushNews2Article(props: { ids: Id[] }) {
export default function ButtonPushNews2Article(props: { ids: Id[]; }) {
// const {modal} = App.useApp();
const [loading,setLoading] = useState(false)
const navigate = useNavigate();
const {set} = useIndexArrayCache();
const handlePush = () => {
setLoading(true)
push2article(props.ids).then(() => {
showToast('推送成功', 'success')
set([])
navigate('/edit')
}).catch(() => {
showToast('推送失败', 'error')
}).finally(() => {
@ -16,21 +23,27 @@ export default function ButtonPushNews2Article(props: { ids: Id[] }) {
})
}
const onPushClick = () => {
if(loading) return;
if (props.ids.length === 0) {
showToast('请选择要推的新闻', 'warning')
showToast('请选择要推入编辑的新闻', 'warning')
return
}
Modal.confirm({
title: '操作提示',
content: '是否确定推入素材编辑界面?',
onOk: handlePush
})
handlePush();
// modal.confirm({
// title: '操作提示',
// content: '是否确定推入素材编辑界面?',
// onOk: handlePush,
// centered: true
// })
}
return (
<Button
type={'primary'}
loading={loading}
<button
disabled={loading}
onClick={onPushClick}
></Button>
className='bg-[#4096ff] hover:bg-blue-600 text-white'
>
<span className={'text'}></span>
<IconArrowRight className={'text-white'} />
</button>
)
}

View File

@ -1,32 +1,48 @@
import {Button, Modal} from "antd";
import React, {useState} from "react";
import {showErrorToast, showToast} from "@/components/message.ts";
import {push2video} from "@/service/api/article.ts";
import {IconArrowRight} from "@/components/icons";
import {useNavigate} from "react-router-dom";
export default function ButtonPush2Video(props: { ids: Id[];onSuccess?:()=>void; }) {
export default function ButtonPush2Video(props: { ids: Id[]; onSuccess?: () => void; }) {
const [loading, setLoading] = useState(false)
const navigate = useNavigate()
const handlePush = () => {
setLoading(true)
push2video(props.ids).then(() => {
showToast('一键推流成功,已成功推入数字人视频生成,请前往数字人视频生成页面查看!', 'success')
props.onSuccess?.()
showToast('推流成功', 'success')
navigate('/create?state=push-success',{
state: 'push-success'
})
// props.onSuccess?.()
}).catch(showErrorToast).finally(() => {
setLoading(false)
})
}
const onPushClick = () => {
if (loading) return;
if (props.ids.length === 0) {
showToast('请选择要开播的新闻', 'warning')
return
}
Modal.confirm({
title: '操作提示',
content: '是否确定一键开播选中新闻?',
onOk: handlePush
})
// Modal.confirm({
// title: '操作提示',
// content: '是否确定一键开播选中新闻?',
// onOk: handlePush
// })
handlePush();
}
return (
<Button type="primary" loading={loading} onClick={onPushClick}></Button>
<div>
<button
disabled={loading}
className='bg-[#4096ff] hover:bg-blue-600 text-white'
onClick={onPushClick}
>
<span className={'text'}>{loading?'推送中...':'生成视频'}</span>
<IconArrowRight className={'text-white'}/>
</button>
</div>
)
}

View File

@ -1,21 +1,28 @@
import {Button, Input} from "antd";
import {Input} from "antd";
import {SearchOutlined} from "@ant-design/icons";
import ArticleCascader from "@/pages/news/components/article-cascader.tsx";
import React, {useState} from "react";
import React, {useEffect, useState} from "react";
import {useSetState} from "ahooks";
import useArticleTags from "@/hooks/useArticleTags.ts";
import TagSelect from "@/components/form/tag-select.tsx";
export default function EditSearchForm(props: {
onSubmit: (values: ApiArticleSearchParams) => void;
defaultParams?: Partial<ApiArticleSearchParams>;
}) {
const articleTags = useArticleTags()
const [tags, setTags] = useState<Id[][]>([]);
const [tags, _setTags] = useState<Id[][]>([]);
const [prevSearchName, setPrevSearchName] = useState<string>(props.defaultParams?.title||'')
const [params, setParams] = useSetState<ApiArticleSearchParams>({
pagination: {limit: 10, page: 1},
title:props.defaultParams?.title||''
});
const handleSubmit = () => {
params.tags = tags.length == 0 ? undefined : tags.map(it => {
const handleSubmit = (_tags?:Id[][],from?:'input') => {
if (from == 'input' && (params.title == prevSearchName || (!params.title && !prevSearchName))) return
params.title = prevSearchName;
setParams({title: prevSearchName})
const __tags = _tags || tags;
params.tags = __tags.length == 0 ? undefined : __tags.map(it => {
if (Array.isArray(it)) {
return {
level1: it[0],
@ -35,23 +42,44 @@ export default function EditSearchForm(props: {
}
})
}
useEffect(()=>{
const {defaultParams} = props;
if(!defaultParams){
return;
}
const tags:Id[][] = []
if(defaultParams.tags){
defaultParams.tags.forEach(it=>{
tags.push([it.level1, it.level2])
})
_setTags(tags)
}
},[articleTags])
const setTags = (_tags: Id[][])=>{
console.log(_tags)
_setTags(_tags)
handleSubmit(_tags)
}
return (
<div className="search-form-input flex gap-2 items-center">
<Input
onChange={(e) => {
setParams({title: e.target.value})
}}
allowClear
type="text" className="rounded px-3 w-[250px]"
suffix={<SearchOutlined/>}
placeholder="请输入你先搜索的关键词"
value={prevSearchName}
onChange={e => setPrevSearchName(e.target.value)}
type="text" className="rounded-3xl px-3 w-[270px]"
prefix={<SearchOutlined/>}
placeholder="请输入新闻标题关键词进行搜索"
onPressEnter={()=>handleSubmit(undefined,'input')}
onBlur={()=>handleSubmit(undefined,'input')}
/>
<span className="ml-5 text-sm"></span>
<ArticleCascader
options={articleTags}
onChange={setTags}
/>
<Button type="primary" onClick={handleSubmit}></Button>
{/*<span className="ml-5 text-sm">来源</span>*/}
{/*<ArticleCascader*/}
{/* options={articleTags}*/}
{/* onChange={setTags}*/}
{/*/>*/}
<TagSelect defaultSelectTags={tags} onChange={setTags} options={articleTags}/>
</div>
)
}

View File

View File

@ -1,113 +1,244 @@
import {Button, Input, Select} from "antd";
import {useSetState} from "ahooks";
import {useState} from "react";
import {Input} from "antd";
import {useBoolean, useLocalStorageState, useSetState,useClickAway} from "ahooks";
import {useCallback, useEffect, useMemo, useRef, useState} from "react";
import {clsx} from "clsx";
import useArticleTags from "@/hooks/useArticleTags.ts";
import {SearchListTimes} from "@/pages/news/components/news-source.ts";
import {UpOutlined, MenuOutlined, SearchOutlined} from "@ant-design/icons";
import TimeSelect from "@/components/form/time-select.tsx";
import styles from './style.module.scss'
import {IconPin} from "@/components/icons";
type SearchPanelProps = {
onSearch?: (params: ApiArticleSearchParams) => void;
defaultParams?: Partial<ApiArticleSearchParams>;
}
const pagination = {
limit: 10, page: 1
limit: 12, page: 1
}
const DEFAULT_STATE = {
tag_level_1_id: -1,
tag_level_2_id: -1,
subOptions: []
}
export default function SearchPanel({onSearch}: SearchPanelProps) {
export default function SearchPanel({onSearch,defaultParams}: SearchPanelProps) {
const tags = useArticleTags();
const [params, setParams] = useSetState<ApiArticleSearchParams>({
pagination
pagination,
time_flag:1,
...(defaultParams || {})
});
const [prevSearchName, setPrevSearchName] = useState<string>(defaultParams?.title||'')
const [state, setState] = useSetState<{
tag_level_1_id: number;
tag_level_2_id: number;
subOptions: (string | number)[]
}>({...DEFAULT_STATE})
}>({
...DEFAULT_STATE,
...(defaultParams&&defaultParams.tag_level_1_id?{tag_level_1_id:defaultParams.tag_level_1_id}: {}),
...(defaultParams&&defaultParams.tag_level_2_id?{tag_level_2_id:defaultParams.tag_level_2_id}: {})
})
useEffect(()=>{
if(!defaultParams){
return;
}
const _state = {
tag_level_1_id: -1,
tag_level_2_id: -1,
}
if(defaultParams.tag_level_1_id){
_state.tag_level_1_id = defaultParams.tag_level_1_id
if(tags && tags.length > 0){
const tag = tags.find(s => s.value == defaultParams.tag_level_1_id)
setSubOptions(tag?.children || [])
}
}
if(defaultParams.tag_level_2_id){
_state.tag_level_2_id = defaultParams.tag_level_2_id
}
setState(_state)
},[tags])
const [pinnedTag, setPinnedTag] = useLocalStorageState<number[]>(
'user-pinned-tag-list',
{
defaultValue: [],
},
);
// 二级分类
const [subOptions, setSubOptions] = useState<OptionItem[]>([])
const onFinish = () => {
if (params.title == prevSearchName || (!params.title && !prevSearchName)) return
params.title = prevSearchName;
setParams({title: prevSearchName})
onSearch?.({
...params,
tag_level_1_id: state.tag_level_1_id > 0?state.tag_level_1_id:undefined,
tag_level_2_id: state.tag_level_2_id > 0?state.tag_level_2_id:undefined,
title: prevSearchName,
tag_level_1_id: state.tag_level_1_id > 0 ? state.tag_level_1_id : undefined,
tag_level_2_id: state.tag_level_2_id > 0 ? state.tag_level_2_id : undefined,
pagination
})
}
// 重置
const onReset = () => {
setParams({pagination, title: ''})
setState({...DEFAULT_STATE})
setSubOptions([])
onSearch?.({pagination})
const handleTimeFilter = (time_flag: number) => {
const searchParams = {
...params,
time_flag,
pagination
}
setParams(searchParams)
onSearch?.(searchParams)
}
const handleFilter = (_params: Partial<ApiArticleSearchParams>) => {
const searchParams = {
...params,
..._params,
pagination
}
setParams(searchParams)
setState({
...state,
tag_level_1_id: _params.tag_level_1_id || -1,
tag_level_2_id: _params.tag_level_2_id || -1,
})
onSearch?.(searchParams)
}
const pinnedList = useMemo(() => {
if (tags?.length > 0) {
const pinnedList = pinnedTag && pinnedTag?.length > 0 ? pinnedTag : tags.map(s => s.value)
return pinnedList.filter(it => tags.findIndex(s => s.value == it) != -1)
.sort((a, b) => Number(a) - Number(b))
.map(it => (tags.find(s => s.value == it) as OptionItem))
}
return [] as OptionItem[];
}, [pinnedTag, tags])
const pinnedManagePanel = useRef<HTMLDivElement|null>(null)
return (<div className={'search-panel'}>
const togglePinnedManagePanel = useCallback((visible: boolean) => {
if(!pinnedManagePanel.current){
return;
}
const _target = pinnedManagePanel.current!;
if(visible){
_target.style.height = 'auto'
const {height} = _target.getBoundingClientRect()
_target.style.height = '38px'
requestAnimationFrame(()=>{
_target.style.height = `${height}px`
})
}else{
requestAnimationFrame(()=>{
_target.style.height = '0'
})
}
},[pinnedManagePanel])
const setTrue = ()=> togglePinnedManagePanel(true)
const setFalse = ()=>togglePinnedManagePanel(false)
useClickAway(() => setFalse(), pinnedManagePanel)
return (<div className={`${styles.searchPanel} pt-6 pb-2`}>
<div className="flex justify-between items-center">
<div className="search-form flex items-center gap-4">
<Input
value={params.title}
onChange={e => setParams({title: e.target.value})}
className="w-[240px]"
placeholder={'请输入新闻标题开始查找新闻'}
value={prevSearchName}
onChange={e => setPrevSearchName(e.target.value)}
className="w-[270px] rounded-3xl"
placeholder={'请输入新闻标题关键词进行搜索'}
onPressEnter={onFinish}
onBlur={onFinish}
prefix={<SearchOutlined/>}
/>
<TimeSelect
className="w-[120px] ml-1"
value={typeof(params.time_flag) != "undefined" ? params.time_flag : 1}
onChange={handleTimeFilter}
/>
<div className={'flex items-center ml-2'}>
<span className="text-sm whitespace-nowrap mr-1"></span>
<Select
className="w-[150px]"
value={params.time_flag || 0}
onChange={value => setParams({time_flag: value})}
options={SearchListTimes}
optionRender={(option) => (
<div className="flex items-center">
<span role="icon" className={`radio-icon`}></span>
<span role="listitem" aria-label={String(option.label)}>{option.label}</span>
</div>
)}
/>
</div>
<Button type={'primary'} onClick={onFinish}></Button>
<Button onClick={onReset}></Button>
</div>
</div>
<div className="filter-container flex items-start mt-5">
<div className="list-container flex-1">
<div className="news-source-lv-1 flex flex-wrap">
<div
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_1_id == -1 ? 'bg-blue-500 text-white' : 'hover:bg-gray-100'}`}
onClick={() => {
setState({...DEFAULT_STATE})
setSubOptions([])
}}></div>
{
tags.filter(s=>s.value !== 999999).map(it => (
<div
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_1_id == it.value ? 'bg-blue-500 text-white' : 'hover:bg-gray-100'}`}
<div className="filter-container mt-5">
<div className="list-container relative">
<div className="justify-between flex items-start border-b pb-2 overflow-hidden">
<div className="pinned-tag-list flex flex-wrap flex-1 min-w-0">
<div
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_1_id == -1 ? 'selected' : ''}`}
onClick={() => {
handleFilter({tag_level_1_id: -1, tag_level_2_id: -1})
setSubOptions([])
}}>
</div>
{pinnedList.filter(s => (Number(s.value) !== 999999)).map(it => (
<span
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_1_id == it.value ? 'selected' : ''}`}
key={it.value}
onClick={() => {
setState({tag_level_1_id: Number(it.value),tag_level_2_id:-1})
handleFilter({tag_level_1_id: Number(it.value), tag_level_2_id: -1})
setSubOptions(it.children || [])
}}>{it.label}</div>)
)
}
}}>{it.label}</span>)
)}
</div>
<div className="pinned-menu mt-2">
<span className={'cursor-pointer block hover:text-blue-500'} onClick={e=>{
e.stopPropagation();
e.preventDefault();
setTrue();
}}>
<MenuOutlined style={{fontSize: 20}}/>
</span>
</div>
</div>
{state.tag_level_1_id != -1 && subOptions.length > 0 && <div className="news-source-lv-2 bg-gray-100 p-2 rounded mt-2 flex flex-wrap">
{
subOptions.map(it => (
<div
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_2_id == it.value ? 'bg-blue-500 text-white' : 'hover:bg-gray-100'}`}
key={it.value}
onClick={() => {
setState({tag_level_2_id: Number(it.value)})
}}>{it.label}</div>)
)
}
</div>}
<div ref={pinnedManagePanel} className={clsx(styles.pinnedManagePanelContainer)}>
{/* 固定新闻来源 */}
<div className={clsx(styles.pinnedManagePanel)}>
<div className="header flex justify-between">
<div className="title font-bold"></div>
<div className={'cursor-pointer block hover:text-blue-500'} onClick={setFalse}>
<UpOutlined style={{fontSize: 20}}/>
</div>
</div>
<div className="tags-list-container">
{
tags.filter(s => s.value !== 999999).map(it => {
const currentPinned = pinnedTag?.includes(Number(it.value));
return (<div
className={`filter-item border flex items-center px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${currentPinned?'bg-gray-100':''} hover:border-gray-400`}
key={it.value}
onClick={() => {
const value = Number(it.value)
if (pinnedTag && pinnedTag.includes(value)) {
setPinnedTag(pinnedTag.filter(s => s != value))
} else {
setPinnedTag([...(pinnedTag || []), value])
}
}}>
<span>{it.label}</span>
{currentPinned &&
<span className={'ml-2'}><IconPin/></span>}
</div>)
})
}
</div>
</div>
</div>
{/* 二级目录 */}
{state.tag_level_1_id != -1 && subOptions.length > 0 &&
<div
className="absolute news-source-lv-2 flex items-center absolute left-0 right-0">
{
subOptions.map(it => (
<div
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_2_id == it.value ? 'text-black' : ' text-gray-400 hover:text-gray-600'}`}
key={it.value}
onClick={() => {
handleFilter({tag_level_1_id:state.tag_level_1_id,tag_level_2_id: Number(it.value)})
}}>{it.label}</div>)
)
}
</div>}
</div>
</div>

View File

@ -0,0 +1,95 @@
.searchPanel {
:global {
.filter-item {
@apply relative mr-5;
&:after {
@apply absolute bottom-1 left-0 w-full;
height: 6px;
content: ' ';
}
&.selected:after {
background: linear-gradient(to top, rgba(28, 122, 255,1),rgba(28, 122, 255,0));
}
}
}
}
.pinnedManagePanelContainer {
@apply absolute bg-white top-0 rounded shadow-md z-10;
height: 0;
overflow: hidden;
transition: height 0.2s ease-in-out;
inset-inline: -20px;
}
.pinnedManagePanel {
@apply px-4 pt-2 pb-4 grid;
:global {
.btn-panel {
@apply cursor-pointer;
}
.tags-list-container {
@apply flex flex-wrap gap-2 mt-2;
}
}
}
.newListTable{
:global{
.row{
@apply bg-white mt-2 py-2 px-4 rounded-xl gap-2 border;
border-width: 2px;
&.checked{
@apply border-primary-blue bg-primary-blue-bg;
}
}
.col{
@apply flex items-center justify-center relative pl-6;
height: 54px;
&:after{
@apply absolute;
border-right: solid 1px #e8e8e8;
content: ' ';
top:2px;
bottom: 2px;
left:0;
}
}
.title{
@apply flex-1 pl-0 justify-start;
&:after{
display: none;
}
}
.source{
width: 180px;
}
.count-picture,.count-words{
width: 120px;
text-align: center;
}
.time{
width: 150px;
}
.operations{
@apply gap-4;
width: 100px;
}
.header{
@apply bg-primary-bg;
.col{
@apply text-sm;
height: 42px;
}
.operations{
}
}
.body{}
.icon-btn{
@apply text-gray-400 hover:text-blue-500 cursor-pointer;
font-size: 18px;
}
}
}

View File

@ -1,103 +1,176 @@
import {Button, Pagination, Table, TableColumnsType, TableProps, Typography} from "antd";
import {Checkbox, Popconfirm, Space} from "antd";
import {Card} from "@/components/card";
import React, {useState} from "react";
import React, {useRef, useState} from "react";
import {useRequest} from "ahooks";
import {formatTime} from "@/util/strings.ts";
import ArticleEditModal from "@/components/article/edit-modal.tsx";
import {getList} from "@/service/api/article.ts";
import {deleteByIds, getList} from "@/service/api/article.ts";
import EditSearchForm from "@/pages/news/components/edit-search-form.tsx";
import ButtonPush2Video from "@/pages/news/components/button-push2video.tsx";
import {Key} from "antd/es/table/interface";
import styles from './components/style.module.scss'
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
import {IconDelete, IconEdit, IconWarningCircle} from "@/components/icons";
import {clsx} from "clsx";
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
import ButtonDeleteBatch from "@/pages/news/components/button-delete-batch.tsx";
import {showErrorToast, showToast} from "@/components/message.ts";
const FilterCache: Partial<ApiArticleSearchParams> = {
tags: [],
}
export default function NewEdit() {
const [state, setState] = useState<{
checkAll?: boolean;
showToTop?: boolean;
}>({})
const [editId, setEditId] = useState(-1)
const [selectedRowKeys, setSelectedRowKeys] = useState<Id[]>([])
const [params, setParams] = useState<ApiArticleSearchParams>({
pagination: {page: 1, limit: 10}
pagination: {page: 1, limit: 10},
...FilterCache
})
const [data, setData] = useState<DataList<ListArticleItem>>()
const {refresh, loading} = useRequest(() => getList(params), {
refreshDeps: [params],
onSuccess: (data) => {
FilterCache.title = params.title;
FilterCache.tags = params.tags;
setData(prev => {
// 判断页码是否是第1页
if (data.pagination.page == 1) return data;
return {
list: [...(prev?.list || []), ...(data?.list || [])],
pagination: data.pagination
}
})
}
})
const {data, refresh} = useRequest(() => getList(params), {refreshDeps: [params]})
const columns: TableColumnsType<ListArticleItem> = [
{
title: '标题',
minWidth: 300,
dataIndex: 'title',
},
{
title: '内容',
dataIndex: 'summary',
render: (value) => (<Typography.Paragraph style={{marginBottom: 0}} ellipsis={{
rows: 2, expandable: true, symbol: 'More'
}}>{value}</Typography.Paragraph>)
},
{
title: '来源',
minWidth: 150,
dataIndex: 'media_name',
},
{
title: '时间',
width: 150,
dataIndex: 'time',
render: (_, record) => {
return formatTime(record.publish_time, 'YYYY-MM-DD HH:mm')
}
},
{
title: '操作',
width: 80,
align: 'center',
render: (_, record) => (<Button type="link" onClick={() => {
setEditId(record.id)
}}></Button>),
},
];
const rowSelection: TableProps<ListArticleItem>['rowSelection'] = {
onChange: (selectedRowKeys: Key[]) => {
setSelectedRowKeys(selectedRowKeys as Id[])
},
};
const handleItemChecked = (checked: boolean, item: ListArticleItem) => {
if (checked) {
setSelectedRowKeys(prev => [...prev, item.id])
} else {
setSelectedRowKeys(prev => prev.filter(it => it != item.id))
}
}
const handleCheckAll = (checked: boolean) => {
setState({checkAll: checked})
if (checked) {
setSelectedRowKeys(data?.list?.map(item => item.id) || [])
} else {
setSelectedRowKeys([])
}
}
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
const handleDelete = (id) => {
deleteByIds([id]).then(() => {
refresh()
showToast('删除成功', 'success')
}).catch(showErrorToast)
}
return (<div className="container pb-5 news-edit">
<Card className="search-panel-container my-5">
<div className="search-form flex gap-5 justify-between">
<EditSearchForm onSubmit={setParams}/>
<Button type="primary" onClick={() => setEditId(0)}></Button>
<div className="search-panel-container my-5">
<div className="search-form flex pt-1 gap-5 justify-between">
<EditSearchForm defaultParams={params} onSubmit={setParams}/>
{/*<Button type="primary" onClick={() => setEditId(0)}>手动新增</Button>*/}
</div>
<div className="news-list-container mt-5">
<Table<ListArticleItem>
rowSelection={{type: 'checkbox', ...rowSelection}}
columns={columns}
dataSource={data?.list || []}
rowKey={'id'}
bordered
pagination={false}
/>
{data?.pagination && data?.pagination.total > 0 && <div className="footer flex justify-between items-center mt-5">
<Pagination
current={params.pagination.page}
total={data?.pagination.total}
pageSize={10}
showSizeChanger={false}
simple={true}
rootClassName={'simple-pagination'}
onChange={(page) => setParams(prev => ({
<div className="news-list-container mt-2">
<div className="controls flex justify-end mb-3 gap-2">
<Space>
<span> {data?.list?.length || 0} </span>
<span className={'text-blue-500'}> {selectedRowKeys.length} </span>
</Space>
<div>
<span className={'inline-block cursor-pointer mr-2'} onClick={() => {
handleCheckAll(!state.checkAll)
}}></span>
<Checkbox checked={state.checkAll && selectedRowKeys.length == data?.list.length}
onChange={e => {
handleCheckAll(e.target.checked)
}}/>
</div>
</div>
<div className={styles.newListTable}>
<div className="header row flex">
<div className="col title"></div>
<div className="col source source"></div>
<div className="col count-picture"></div>
<div className="col count-words"></div>
<div className="col time"></div>
<div className="col operations"></div>
</div>
<InfiniteScroller ref={scrollerRef} onCallback={(page) => {
setParams(prev => ({
...prev,
pagination: {page, limit: 10}
}))}
/>
<ButtonPush2Video ids={selectedRowKeys} onSuccess={refresh}/>
</div>}
}))
}} onScroll={(top) => setState({showToTop: top > 30})} loading={loading}
pagination={data?.pagination}>
<div className="body">
{data?.list?.map((item, i) => {
const checked = selectedRowKeys.includes(item.id)
return <div key={i} className={clsx("row flex", {checked})}>
<div className="col title cursor-pointer" onClick={() => setEditId(item.id)}>
<div>
<div className="text-base">{item.title}</div>
<div
className="summary text-xs text-gray-400 line-clamp-1">{item.summary}</div>
</div>
</div>
<div className="col source">
<div className="text-sm line-clamp-1">{item.media_name}-{item.column_name}</div>
</div>
<div className="col count-picture">
<div className="text-sm">{item.img_num}</div>
</div>
<div className="col count-words">
<div className="text-sm">{item.content_word_count}</div>
</div>
<div className="col time">
<div
className="text-sm">{formatTime(item.publish_time, 'YYYY-MM-DD HH:mm')}</div>
</div>
<div className="col operations">
<span className="icon-btn" onClick={()=>setEditId(item.id)}><IconEdit/></span>
<Popconfirm
rootClassName={'popconfirm-main'}
placement={'left'}
arrow={false}
icon={<IconWarningCircle/>}
title={'你确定要删除吗?'}
description={'删除后需从新闻素材中重新选择'}
onConfirm={() => {
handleDelete(item.id)
}}
>
<span className="icon-btn"><IconDelete/></span>
</Popconfirm>
<Checkbox checked={checked}
onChange={e => handleItemChecked(e.target.checked, item)}/>
</div>
</div>
})}
</div>
</InfiniteScroller>
</div>
<div className="page-action">
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
{selectedRowKeys?.length > 0 && <ButtonDeleteBatch ids={selectedRowKeys} onSuccess={refresh}/>}
<ButtonPush2Video ids={selectedRowKeys} onSuccess={refresh}/>
</div>
</div>
<ArticleEditModal
type="news" id={editId}
type="news"
id={editId}
onClose={(saved) => {
setEditId(-1)
if (saved) refresh()
}}/>
</Card>
</div>
</div>)
}

View File

@ -1,36 +1,56 @@
import {useState} from "react";
import {Checkbox, Empty, Modal, Pagination, Space} from "antd";
import React, {useMemo, useRef, useState} from "react";
import {Checkbox, Divider, Empty, Modal, Space} from "antd";
import {useRequest} from "ahooks";
import {Card} from "@/components/card";
import {CloseOutlined} from "@ant-design/icons"
import {clsx} from "clsx";
import SearchPanel from "@/pages/news/components/search-panel.tsx";
import styles from './style.module.scss'
import {getById,getList} from "@/service/api/news.ts";
import {getById, getList} from "@/service/api/news.ts";
import {showLoading} from "@/components/message.ts";
import {formatTime} from "@/util/strings.ts";
import ButtonPushNews2Article from "@/pages/news/components/button-push-news2article.tsx";
import ButtonNewsDownload from "@/pages/news/components/button-news-download.tsx";
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
import {useIndexArrayCache} from "@/hooks/useCache.ts";
const FilterCache: Partial<ApiArticleSearchParams> = {
time_flag: 1,
}
export default function NewsIndex() {
const [params, setParams] = useState<ApiArticleSearchParams>({
pagination: {
page: 1,
limit: 10
}
pagination: {page: 1, limit: 12},
...FilterCache
})
const [checkedId, setCheckedId] = useState<Id[]>([])
// const [checkedId, setCheckedId] = useState<Id[]>([])
const {cache: checkedId, set: setCheckedId} = useIndexArrayCache()
const [activeNews, setActiveNews] = useState<NewsInfo>()
const [state, setState] = useState<{
checkAll?: boolean;
showToTop?: boolean;
}>({})
const {data} = useRequest(() => getList(params), {
const [data, setData] = useState<DataList<ListCrawlerNewsItem>>();
const {loading} = useRequest(() => getList(params), {
refreshDeps: [params],
onSuccess: () => {
setCheckedId([])
setState({checkAll:false})
onSuccess: (_data) => {
FilterCache.tag_level_1_id = params.tag_level_1_id;
FilterCache.tag_level_2_id = params.tag_level_2_id;
FilterCache.title = params.title;
FilterCache.time_flag = params.time_flag;
console.log('success',FilterCache)
if (params.pagination.page === 1) {
setData(_data)
setState({checkAll: checkedId && _data.list && checkedId.length === _data.list.length})
} else {
setData({
pagination: _data.pagination,
list: [...(data?.list || []), ..._data.list]
})
}
}
})
@ -38,100 +58,148 @@ export default function NewsIndex() {
const {update, close} = showLoading('获取新闻详情...')
getById(id).then(res => {
close()
setActiveNews(res)
setActiveNews({...res, id})
}).catch(() => {
update('获取新闻详情失败', 'info')
})
}
const currentEnabledList = useMemo(() => {
if (data?.list && data?.list?.length > 0) {
return data.list.filter(s => s.internal_article_id == 0)
}
return [];
}, [data?.list])
const handleCheckAll = (checked: boolean) => {
setState({checkAll: checked})
if (checked) {
setCheckedId(currentEnabledList.map(item => item.id) || [])
} else {
setCheckedId([])
}
}
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
const handleCheckChange = (id: number) => {
if (checkedId.includes(id)) {
setCheckedId(checkedId.filter(id => id != id))
} else {
setCheckedId([...checkedId, id])
}
}
return (<div className={'container pb-5'}>
<Card className="search-panel-container my-5">
<SearchPanel onSearch={setParams}/>
</Card>
<Card className="news-list-container">
{activeNews && <Modal open={true} width={1000} footer={null} onCancel={() => setActiveNews(undefined)}>
<div className="news-detail px-3 pb-5">
<SearchPanel defaultParams={params} onSearch={setParams}/>
{activeNews && <Modal
rootClassName={'news-detail-modal'}
closeIcon={null} open={true} width={1000}
footer={null} onCancel={() => setActiveNews(undefined)}
>
<div className="news-detail pl-16 pr-1 flex pb-5">
<div className="px-4 py-6 bg-white flex-1">
<div className="new-title text-2xl">{activeNews?.title}</div>
<div className="info mt-2 mb-5 text-sm flex gap-3">
<span className="source text-blue-700">{activeNews?.media_name}</span>
<div className="info mt-2 mb-2 text-sm flex gap-3">
<span className="source text-blue-400">{activeNews?.media_name}</span>
<span className="create-time text-gray-400">{formatTime(activeNews?.publish_time)}</span>
</div>
<div className="overflow-auto leading-7 text-base"
style={{maxHeight: 1000}} dangerouslySetInnerHTML={{__html: activeNews?.content || ''}}></div>
<Divider className={'my-2'}/>
<div className="overflow-auto leading-7 text-base news-detail-content-container"
style={{maxHeight: 500}} dangerouslySetInnerHTML={{__html: activeNews?.content || ''}}></div>
</div>
</Modal>}
<div className="controls flex justify-between mb-1">
<div>
<Checkbox checked={state.checkAll} onChange={e => {
setState({checkAll: e.target.checked})
if (e.target.checked) {
setCheckedId(data?.list?.map(item => item.id) || [])
} else {
setCheckedId([])
}
}}></Checkbox>
</div>
<Space size={10}>
<ButtonPushNews2Article ids={checkedId}/>
<ButtonNewsDownload ids={checkedId}/>
<div className="actions ml-3">
<div className="close">
<CloseOutlined className="text-xl text-gray-400 hover:text-gray-800"
onClick={() => setActiveNews(undefined)}/>
</div>
<div className="whitespace-nowrap text-sm mt-2">
<Checkbox
checked={checkedId.includes(activeNews!.id)}
onChange={() => handleCheckChange(activeNews!.id)}
><span className="ml-[-4px]"></span></Checkbox>
</div>
</div>
</div>
</Modal>}
<div className="news-list-container">
<div className="controls flex justify-end mb-3 gap-2">
<Space>
<span> {data?.list?.length || 0} </span>
<span className={'text-blue-500'}> {checkedId.length} </span>
</Space>
<div>
<span className={'inline-block cursor-pointer mr-2'} onClick={() => {
handleCheckAll(!state.checkAll)
}}></span>
<Checkbox checked={state.checkAll && checkedId.length == currentEnabledList.length} onChange={e => {
handleCheckAll(e.target.checked)
}}></Checkbox>
</div>
</div>
<div className={styles.newsList}>
<InfiniteScroller
className="grid grid-cols-2 gap-4 xl:grid-cols-4 md:grid-cols-3 pb-2"
pagination={data?.pagination}
loading={loading}
ref={scrollerRef}
onScroll={(top) => setState({showToTop: top > 30})}
onCallback={(page) => {
setParams({...params, pagination: {...params.pagination, page}})
}}
empty={<Empty/>}
>
{data?.list?.map(item => (
<div key={item.id} className={`py-3 flex items-start border-b border-gray-100 group`}>
<div
className={`checkbox mt-[2px] mr-2 ${checkedId.includes(item.id) ? '' : 'opacity-0'} group-hover:opacity-100`}>
{item.internal_article_id > 0 ? <span className={"inline-block w-[16px] " }></span> :<Checkbox checked={checkedId.includes(item.id)} onChange={() => {
if (checkedId.includes(item.id)) {
setCheckedId(checkedId.filter(id => id != item.id))
} else {
setCheckedId([...checkedId, item.id])
}
}}/> }
</div>
<div key={item.id}
className={clsx(`p-4 flex items-start group rounded border border-transparent`, {
'bg-news-to-edit': item.internal_article_id > 0,
'bg-white': !item.internal_article_id && !checkedId.includes(item.id),
'bg-blue-500/20 border-[#d9eaff]': checkedId.includes(item.id)
})}>
<div className="news-content flex-1">
<div className="flex items-center justify-between">
<div className="title text-lg cursor-pointer" onClick={() => {
handleViewNewsDetail(item.id)
}}>{item.title}</div>
{item.internal_article_id > 0 &&
<div className="text-sm text-blue-500"></div>}
</div>
<div className="title h-[60px] line-clamp-2 text-lg cursor-pointer hover:text-blue-500"
onClick={() => {
handleViewNewsDetail(item.id)
}}>{item.title}</div>
<div className="content flex gap-3 mt-2 mb-3">
{item.cover && <div
className="cover border border-gray-100 flex items-center rounded overflow-hidden"
style={{width: 100, height: 100}}>
<img className="w-full h-full object-cover" src={item.cover}/>
</div>}
<div className="text text-gray-600 text-sm leading-6 flex-1 text-justify">
<div
className="text text-gray-600 text-sm h-[70px] line-clamp-3 overflow-hidden text-ellipsis text-justify leading-6 flex-1 text-justify break-all text-wrap">
{item.summary}
</div>
{item.cover && <div
className="cover border border-gray-100 flex items-center rounded overflow-hidden"
style={{width: 100, height: 70}}>
<img className="w-full h-full object-cover" src={item.cover}/>
</div>}
</div>
<div className="info text-gray-300 flex items-center justify-between gap-3 text-sm">
<div>: <span>{item.media_name}</span></div>
{/*<Divider type="vertical" />*/}
<div>: <span>{formatTime(item.publish_time)}</span></div>
<div className="info text-gray-400 mt-4 text-sm">
<div className="line-clamp-1">: <span>{item.data_source_name}</span></div>
<div className="extras flex items-center justify-between gap-3">
<div><span>{formatTime(item.publish_time, 'min')}</span></div>
<div><span>: {item.img_num}</span></div>
<div><span>: {item.content_word_count}</span></div>
<div
className={`checkbox mt-1`}>
{item.internal_article_id > 0 ?
<span className={"inline-block text-gray-600"}></span> :
<Checkbox checked={checkedId.includes(item.id)} onChange={() => {
handleCheckChange(item.id)
}}/>}
</div>
</div>
</div>
</div>
</div>
))}
</InfiniteScroller>
</div>
<div className="page-action">
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
<div>
<ButtonNewsDownload ids={checkedId}/>
</div>
<div>
<ButtonPushNews2Article ids={checkedId}/>
</div>
{data?.pagination && data?.pagination.total > 0 ? <div className="flex justify-center mt-10">
<Pagination
current={params.pagination.page}
total={data?.pagination.total}
pageSize={data?.pagination.limit}
showSizeChanger={false}
simple={true}
rootClassName={'simple-pagination'}
onChange={(page) => setParams(prev=>({...prev,pagination: {page, limit: 10}}))}
/>
</div> : <div className="py-10">
<Empty />
</div>
}
</Card>
</div>
</div>)
}

View File

@ -1,3 +0,0 @@
.newsList{
}

View File

@ -1,9 +1,9 @@
import {useState} from "react";
import {useNavigate, useSearchParams} from "react-router-dom";
import type {FormProps} from 'antd';
import {LockOutlined, UserOutlined} from '@ant-design/icons';
import {Button, Checkbox, Divider, Flex, Form, Input} from 'antd';
import {Button, Form, Input} from 'antd';
import {clsx} from "clsx";
import styles from './../style.module.scss'
import useAuth from "@/hooks/useAuth.ts";
import {useSmsCode} from "@/components/form/sms-code.tsx";
@ -24,6 +24,7 @@ export default function FormLogin() {
const [phone, setPhone] = useState<string>()
const {sending, countdown, sendCode} = useSmsCode()
const onFinish: FormProps<FieldType>['onFinish'] = (values) => {
if(disabled || loading) return
if (!values.username || !/^1\d{10}$/.test(values.username)) {
setError('账号或密码错误')
return
@ -37,7 +38,7 @@ export default function FormLogin() {
};
return (<div className="form">
<Divider className=" pb-8 pt-4"><div className={'text-center text-2xl'}></div></Divider>
<div className={'text-center text-xl pb-6 pt-8'}></div>
<Form<FieldType>
name="basic"
style={{maxWidth: 600}}
@ -51,21 +52,17 @@ export default function FormLogin() {
autoComplete="off"
>
<Form.Item<FieldType> name="username">
<div
className="border border-gray-300 rounded-3xl mt-2 flex items-center px-3 focus-within:border-blue-500 focus-within:shadow focus-within:shadow-blue-200">
<UserOutlined/>
<div className={styles.loginBox}>
<Input size={'large'} variant={'borderless'} placeholder="请输入账号"/>
</div>
</Form.Item>
<Form.Item name="password">
<div
className="border border-gray-300 rounded-3xl mt-2 flex items-center px-3 focus-within:border-blue-500 focus-within:shadow focus-within:shadow-blue-200">
<LockOutlined/>
<div className={styles.loginBox}>
<Input style={{borderRadius: 20}} size={'large'} variant={'borderless'}
placeholder="请输入验证码"/>
<span
className={clsx(`text-nowrap text-sm ${countdown > 0 || sending || !phone ? 'text-gray-400 cursor-not-allowed' : 'text-blue-500 cursor-pointer'}`)}
className={clsx(`text-nowrap mr-1 text-sm ${countdown > 0 || sending || !phone ? 'text-gray-400 cursor-not-allowed' : 'text-blue-500 cursor-pointer'}`)}
onClick={() => sendCode(phone)}>
{sending ? '发送中...' : (countdown > 0 ? `${Math.ceil(countdown / 1000)} s` : '获取验证码')}
</span>
@ -73,10 +70,10 @@ export default function FormLogin() {
</Form.Item>
<Form.Item className={"mt-14"}>
<Form.Item className={"mt-16"}>
<div className="absolute text-red-500 text-center inset-x-0" style={{top: -30}}>{error}</div>
<Button disabled={disabled || loading} loading={loading} type="primary" size={'large'} htmlType="submit"
<div className="absolute text-red-500 text-center inset-x-0" style={{top: -34}}>{error}</div>
<Button loading={loading} type="primary" size={'large'} htmlType="submit"
block shape={'round'}>
{loading ? '登录中...' : '立即登录'}
</Button>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 447 KiB

View File

@ -2,7 +2,6 @@ import {useNavigate, useSearchParams} from "react-router-dom";
import {useEffect} from "react";
import useAuth from "@/hooks/useAuth.ts";
import MainBgImage from './components/bg.png'
import styles from './style.module.scss'
import FormLogin from "./components/form-login.tsx";
@ -18,7 +17,7 @@ export default function UserIndex(){
return (<div className={styles.main}>
<div className={"flex-1 ml-[15%]"}>
{/*<h2 className="text-4xl mb-10 text-white/90">数字人直播间</h2>*/}
<img className="w-[450px]" src={MainBgImage} alt=""/>
{/*<img className="w-[450px]" src={MainBgImage} alt=""/>*/}
</div>
<div className={styles.boxLogin}>
<FormLogin />

View File

@ -1,13 +1,14 @@
.main {
@apply py-10;
background-image: url(components/main-bg.jpg);
background-size: 100% 100%;
height: 100vh;
background: url(./components/main-bg.jpg) no-repeat center;
background-size: cover;
height: calc(100vh - var(--app-header-header));
min-height: 500px;
align-items: center;
display: flex;
overflow: hidden;
justify-content: right;
}
.boxLogin {
@ -24,4 +25,7 @@
padding: 30px 40px;
//position: relative;
//width: 400px;
}
.loginBox{
@apply border border-gray-50 bg-gray-50 rounded-3xl mt-2 flex items-center pr-3 pl-1 focus-within:border-gray-300; // focus-within:shadow
}

View File

@ -2,6 +2,7 @@ import {Button, Modal} from "antd";
import React, {useState} from "react";
import {showErrorToast, showToast} from "@/components/message.ts";
import {push2room, VideoStatus} from "@/service/api/video.ts";
import {IconArrowRight, IconWarningCircle} from "@/components/icons";
export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[];onSuccess?:()=>void; }) {
@ -22,17 +23,30 @@ export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[];on
})
}
const onPushClick = () => {
if(loading) return;
if (props.ids.length === 0) {
showToast('请选择要推流的新闻', 'warning')
return
}
Modal.confirm({
wrapClassName:'root-modal-confirm',
title: '操作提示',
icon: <span className="anticon anticon-exclamation-circle"><IconWarningCircle/></span>,
content: '是否确定一键推流选中新闻视频??',
onOk: handlePush
})
}
return (
<Button type="primary" loading={loading} onClick={onPushClick}></Button>
<div>
<button
disabled={loading}
className='bg-[#4096ff] hover:bg-blue-600 text-white'
onClick={onPushClick}
>
<span className={'text'}></span>
<IconArrowRight />
</button>
</div>
)
}

View File

@ -1,33 +1,44 @@
import {Empty, Modal} from "antd";
import {Checkbox, Empty, Space} from "antd";
import React, {useEffect, useMemo, useRef, useState} from "react";
import {DndContext} from "@dnd-kit/core";
import {arrayMove, SortableContext} from "@dnd-kit/sortable";
import {useSetState} from "ahooks";
import {CheckCircleFilled} from "@ant-design/icons";
import {clsx} from "clsx";
import {VideoListItem} from "@/components/video/video-list-item.tsx";
import ArticleEditModal from "@/components/article/edit-modal.tsx";
import {deleteByIds, getList, modifyOrder, VideoStatus} from "@/service/api/video.ts";
import {formatDuration} from "@/util/strings.ts";
import ButtonBatch from "@/components/button-batch.tsx";
import {showToast} from "@/components/message.ts";
import {showErrorToast, showToast} from "@/components/message.ts";
import {Player, PlayerInstance} from "@/components/video/player.tsx";
import ButtonPush2Room from "@/pages/video/components/button-push2room.tsx";
import ButtonToTop from "@/components/scoller/button-to-top.tsx";
import InfiniteScroller, {InfiniteScrollerRef} from "@/components/scoller/infinite-scroller.tsx";
import {IconDelete} from "@/components/icons";
import {useLocation} from "react-router-dom";
export default function VideoIndex() {
const [editId, setEditId] = useState(-1)
const loc = useLocation()
const [videoData, setVideoData] = useState<VideoInfo[]>([])
const [modal, contextHolder] = Modal.useModal()
const player = useRef<PlayerInstance|null>(null)
const player = useRef<PlayerInstance | null>(null)
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
const [state, setState] = useSetState({
checkedAll: false,
playingIndex: -1,
showToTop: false,
showStatePos: false,
playState: {
current: -1,
total: -1
},
loading:false
})
const [checkedIdArray, setCheckedIdArray] = useState<Id[]>([])
// 加载列表
const loadList = (needReset = true) => {
setState({loading: true})
getList().then((ret) => {
const list = ret.list || []
setVideoData(list)
@ -40,11 +51,23 @@ export default function VideoIndex() {
// 每5s重新获取一次最新数据
setTimeout(() => loadList(false), 5000)
}
}).catch(showErrorToast)
.finally(()=>{
setState({loading: false})
})
}
// 播放视频
const playVideo = (video: VideoInfo, playingIndex: number) => {
if (state.playingIndex == playingIndex) {
player.current?.pause();
setState({playingIndex: -1})
return;
}
if (video.status == VideoStatus.Generating) return;
// setState({playingIndex})
// player.current?.play('https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-17/1186196465916190720.flv', 30)
//
if (video.oss_video_url && video.status !== 1) {
setState({playingIndex})
player.current?.play(video.oss_video_url, 0)
@ -57,137 +80,184 @@ export default function VideoIndex() {
checkedAll: !state.checkedAll
})
}
const handleModifySort = () => {
setVideoData((items) => {
modifyOrder(items.map(s => s.id)).catch(() => {
showToast('调整视频顺序失败,请重试!')
}).finally(loadList)
return items;
const handleModifySort = (items: VideoInfo[]) => {
modifyOrder(items.map(s => s.id)).then(() => {
showToast('调整视频顺序成功!', 'success')
}).catch(() => {
loadList();
showToast('调整视频顺序失败,请重试!', 'warning')
})
return ()=>{
try{
Array.from(document.querySelectorAll('video')).forEach(v => v.pause())
}catch (e){
console.log(e)
}
}
}
//
useEffect(loadList, [])
const totalDuration = useMemo(() => {
if (!videoData || videoData.length == 0) return 0;
if (state.playingIndex == -1 || state.playingIndex >= videoData.length) return 0
const v = videoData[state.playingIndex] as VideoInfo;
return Math.ceil(v.duration / 1000)
// 计算总时长
return videoData.reduce((sum, v) => sum + Math.ceil(v.duration / 1000), 0);
}, [videoData])
//return videoData.reduce((sum, v) => sum + Math.ceil(v.duration / 1000), 0);
}, [videoData, state.playingIndex])
return (<div className="container py-10 page-live">
{contextHolder}
useEffect(() => {
if (loc.state == 'push-success' && !state.showStatePos && videoData.length && scrollerRef.current) {
const generatingItem = document.querySelector(`.list-item-state-${VideoStatus.Generating}`)
if (generatingItem) {
generatingItem.scrollIntoView({behavior: 'smooth'})
setState({showStatePos: true})
}
}
}, [videoData, scrollerRef])
const processDeleteVideo = async (ids: Id[]) => {
deleteByIds(ids).then(() => {
showToast('删除成功!', 'success')
loadList()
}).catch(showErrorToast)
}
return (<div className="container py-5 page-live">
<div className="h-[36px]"></div>
<div className="flex">
<div className="video-list-container bg-white p-10 rounded flex flex-col flex-1">
<div className="live-control flex justify-between mb-5">
<div className="pl-[70px]">
<span>: {formatDuration(totalDuration)}</span>
<div className="video-player-container mr-16 w-[360px] flex items-center">
<div>
<div className="text-center text-base text-gray-400"> - </div>
<div className="video-player flex items-center mt-2">
<div className=" w-[360px] h-[636px] rounded overflow-hidden">
<Player
ref={player} url={videoData[state.playingIndex]?.oss_video_url}
onChange={(state) => {
console.log(state)
if (state.end || state.error) setState({playingIndex: -1})
}}
onProgress={(current, duration) => {
setState({
playState: {
current: current,
total: duration
}
})
}}
className="w-[360px] h-[640px] bg-white"/>
</div>
</div>
<div className="flex gap-2 items-center pr-[10px]">
<ButtonBatch
onProcess={deleteByIds}
selected={checkedIdArray}
emptyMessage={`请选择要删除的新闻视频`}
confirmMessage={`是否删除当前的${checkedIdArray.length}个新闻视频?`}
onSuccess={() => {
showToast('删除成功!', 'success')
loadList()
}}
></ButtonBatch>
<button className="ml-5 hover:text-blue-300 text-gray-400 text-lg"
onClick={handleAllCheckedChange}>
<CheckCircleFilled className={clsx({'text-blue-500': state.checkedAll})}/>
</button>
</div>
</div>
<div className={'video-list-sort-container flex-1'}>
<div className="flex my-2">
{videoData.length == 0 ? <div className="m-auto"><Empty/></div> : <>
<div className="sort-number-container mr-2">
{videoData.map((v, index) => (
<div key={index} className="flex items-center px-2 h-[80px] mt-3 mb-2">
<div
className="index-value w-[40px] h-[40px] flex items-center justify-center bg-gray-100 rounded-3xl">{index + 1}</div>
</div>
))}
</div>
<div className="sort-list-container flex-1">
<DndContext onDragEnd={(e) => {
const {active, over} = e;
if (over && active.id !== over.id) {
let oldIndex = -1, newIndex = -1;
const originArr = [...videoData]
console.log(originArr.map(s => s.id))
setVideoData((items) => {
oldIndex = items.findIndex(s => s.id == active.id);
newIndex = items.findIndex(s => s.id == over.id);
return arrayMove(items, oldIndex, newIndex);
});
modal.confirm({
title: '提示',
content: '是否要移动到指定位置',
onOk: handleModifySort,
onCancel: () => {
setVideoData(originArr);
}
})
}
}}>
<SortableContext items={videoData}>
{videoData.map((v, index) => (
<VideoListItem
video={v}
index={index + 1}
id={v.id}
key={index}
type={'create'}
active={state.playingIndex == index}
checked={checkedIdArray.includes(v.id)}
className={`list-item-${index} mt-3 mb-2`}
onCheckedChange={(checked) => {
setCheckedIdArray(idArray => {
const newArr = checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
setState({checkedAll: newArr.length == videoData.length})
return newArr;
})
}}
onPlay={v.status == VideoStatus.Generating ? undefined :() => playVideo(v, index)}
onEdit={v.status == VideoStatus.Generating ? undefined : () => {
setEditId(v.article_id)
}}
editable={v.status != VideoStatus.Generating}
sortable={v.status != VideoStatus.Generating}
/>))}
</SortableContext>
</DndContext>
</div>
</>}
</div>
</div>
<div className="text-right mt-5">
{/*<ButtonBatch*/}
{/* type={'primary'}*/}
{/* onProcess={push2room}*/}
{/* selected={checkedIdArray}*/}
{/* emptyMessage={`请选择要推流的新闻`}*/}
{/* confirmMessage={`是否确定一键推流选中新闻视频?`}*/}
{/* onSuccess={loadList}*/}
{/*>一键推流</ButtonBatch>*/}
<ButtonPush2Room ids={checkedIdArray} list={videoData} onSuccess={loadList}/>
<div className="text-center text-sm mt-4 text-gray-400">{formatDuration(state.playState.current)} / {formatDuration(state.playState.total)}</div>
</div>
</div>
<div className="video-player-container ml-16 w-[360px] flex flex-col">
<div className="text-center text-base"></div>
<div className="video-player flex items-center mt-2">
<div className=" w-[360px] h-[630px] rounded overflow-hidden">
{/*<video ref={videoRef} poster={videoData[state.playingIndex]?.cover} preload="auto" playsinline webkit-playsinline className="w-full bg-white w-[360px] h-[640px]"></video>*/}
<Player
ref={player} url={videoData[state.playingIndex]?.oss_video_url}
onChange={(state) => {
if (state.end || state.end) setState({playingIndex: -1})
}}
className="w-[360px] h-[640px] bg-white"/>
<div className="video-list-container rounded mt-2 flex flex-col flex-1">
<div className="live-control flex justify-between">
<div className="pl-[70px]"></div>
<div className="flex items-center">
<Space>
<span> {videoData.length || 0} </span>
<span className={'text-blue-500'}> {checkedIdArray.length} </span>
</Space>
<button className="hover:text-blue-300 text-gray-400 ml-2"
onClick={handleAllCheckedChange}>
<span className="text-sm mr-2"></span>
{/*<CheckCircleFilled className={clsx({'text-blue-500': state.checkedAll})}/>*/}
</button>
<Checkbox checked={state.checkedAll} onChange={() => handleAllCheckedChange()}/>
</div>
</div>
<div className={'video-list-sort-container flex-1 mt-1'}>
<div className="list-header">
<div className="list-row header-row">
<div className="col number">No.</div>
<div className="col cover"></div>
<div className="col title"></div>
<div className="col generated-time"></div>
<div className="col operation"></div>
</div>
</div>
<InfiniteScroller loading={state.loading} ref={scrollerRef} onScroll={top => setState({showToTop: top > 30})}>
{
videoData.length == 0 ? <div className="m-auto py-16"><Empty/></div> :
<div className="sort-list-container flex-1">
<DndContext onDragEnd={(e) => {
const {active, over} = e;
if (over && active.id !== over.id) {
let oldIndex = -1, newIndex = -1;
const originArr = [...videoData]
console.log(originArr.map(s => s.id))
setVideoData((items) => {
oldIndex = items.findIndex(s => s.id == active.id);
newIndex = items.findIndex(s => s.id == over.id);
const newSorts = arrayMove(items, oldIndex, newIndex);
handleModifySort(newSorts)
return newSorts;
});
// modal.confirm({
// title: '提示',
// content: '是否要移动到指定位置',
// onOk: handleModifySort,
// onCancel: () => {
// setVideoData(originArr);
// }
// })
}
}}>
<SortableContext items={videoData}>
{videoData.map((v, index) => (
<VideoListItem
video={v}
index={index + 1}
id={v.id}
key={index}
type={'create'}
active={checkedIdArray.includes(v.id)}
playing={state.playingIndex == index}
checked={checkedIdArray.includes(v.id)}
className={`list-item-${index} mt-3 mb-2 list-item-state-${v.status}`}
onCheckedChange={(checked) => {
setCheckedIdArray(idArray => {
const newArr = checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
setState({checkedAll: newArr.length == videoData.length})
return newArr;
})
}}
onItemClick={() => playVideo(v, index)}
onRemove={() => processDeleteVideo([v.id])}
onEdit={v.status == VideoStatus.Generating ? undefined : () => {
setEditId(v.article_id)
}}
editable={v.status != VideoStatus.Generating}
sortable={v.status != VideoStatus.Generating}
/>))}
</SortableContext>
</DndContext>
</div>
}
<div className="h-[100px]"></div>
</InfiniteScroller>
</div>
</div>
<div className="page-action">
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
{checkedIdArray.length > 0 && <ButtonBatch
onProcess={deleteByIds}
selected={checkedIdArray}
emptyMessage={`请选择要删除的新闻视频`}
title={`已选择${checkedIdArray.length}条,确定要全部删除吗?`}
className='bg-gray-300 hover:bg-gray-400 text-white'
confirmMessage={`删除后需重新生成视频`}
onSuccess={() => {
showToast('删除成功!', 'success')
loadList()
}}
>
<span className="text"></span>
<IconDelete/>
</ButtonBatch>}
<ButtonPush2Room ids={checkedIdArray} list={videoData} onSuccess={loadList}/>
</div>
</div>
<ArticleEditModal type={'video'} id={editId} onClose={() => setEditId(-1)}/>

View File

@ -1,15 +1,17 @@
import {createBrowserRouter, RouterProvider,} from "react-router-dom";
import {Suspense,} from "react";
import {ConfigProvider} from "antd";
import {ConfigProvider,App} from "antd";
import zhCN from 'antd/locale/zh_CN';
// for date-picker i18n
import dayjs from "dayjs";
import 'dayjs/locale/zh-cn';
import ErrorBoundary from "./error.tsx";
import Loader from "@/components/loader.tsx";
import routes from "@/routes/routes.tsx";
dayjs.locale('zh-cn');
const router = createBrowserRouter([
...routes,
{path: '*', element: <ErrorBoundary/>}
@ -31,12 +33,16 @@ const AppRouter = () => {
theme={{
token: {
borderRadius: 4,
},
}}
>
<Suspense fallback={<Loader/>}>
<RouterProvider future={{v7_startTransition: true}} router={router}/>
</Suspense>
<App>
<Suspense fallback={<Loader/>}>
<RouterProvider future={{v7_startTransition: true}} router={router}/>
</Suspense>
</App>
</ConfigProvider>)
}

View File

@ -13,7 +13,8 @@ const AuthGuard = ({ children }:BasicComponentProps) => {
const location = useLocation();
useEffect(() => {
if (isInitialized && !isLoggedIn) {
if (isInitialized && !isLoggedIn && location.pathname !== '/user') {
console.log(location)
navigate(`/user?from=${location.pathname}`, {
state: {
from: location.pathname

View File

@ -1,6 +1,6 @@
import {Outlet, useNavigate} from "react-router-dom";
import {Dropdown, MenuProps} from "antd";
import React from "react";
import {Outlet, useLocation, useNavigate} from "react-router-dom";
import {Divider, Dropdown, MenuProps} from "antd";
import React, {useEffect} from "react";
import AuthGuard from "@/routes/layout/auth-guard.tsx";
import {LogoText} from "@/components/icons/logo.tsx";
@ -10,33 +10,67 @@ import {DashboardNavigation} from "@/routes/layout/dashboard-navigation.tsx";
import useAuth from "@/hooks/useAuth.ts";
import {hidePhone} from "@/util/strings.ts";
import {defaultCache} from "@/hooks/useCache.ts";
import {IconVideo} from "@/components/icons";
type LayoutProps = {
children: React.ReactNode
}
const NavigationUserContainer = () => {
const {logout,user} = useAuth()
const {logout, user} = useAuth()
const navigate = useNavigate()
const handleLogout = ()=>{
logout().then(() => navigate('/user'))
}
const items: MenuProps['items'] = [
{
key: 'profile',
label: '个人中心',
},
{
key: 'logout',
label: <div onClick={()=>{
logout().then(()=>navigate('/user'))
}}>退</div>,
label: <div className="nav-item" onClick={() => navigate('/history')}>
<IconVideo />
<span className={"nav-text"}></span>
</div>,
},
// {
// key: 'logout',
// label: <div onClick={handleLogout}>退出</div>,
// },
];
const UserButton = () => (<div
className={`flex items-center rounded-3xl ${user ? 'bg-[#e3eeff]' : 'bg-primary-blue'} p-1 pr-2 cursor-pointer rounded`}>
<UserAvatar className="user-avatar size-7"/>
{user ? <span className={"username ml-2 text-sm"}>{hidePhone(user.nickname)}</span> : (
<span className="text-sm mx-2 text-white"></span>
)}
</div>)
return (<div className={"flex items-center justify-between gap-2 ml-10"}>
<Dropdown menu={{items}} placement="bottom" arrow>
<div className="flex items-center hover:bg-gray-100 px-2 py-1 cursor-pointer rounded">
<UserAvatar className="user-avatar size-8"/>
<span className={"username ml-2 text-sm"}>{hidePhone(user?.nickname)}</span>
</div>
</Dropdown>
{user ? <Dropdown
rootClassName={'z-[999999] userinfo-drop-menu'}
menu={{items}} placement="bottomRight"
dropdownRender={(menu)=>(
<div>
<div className="user-profile flex gap-4">
<div className="avatar"><UserAvatar className="user-avatar"/></div>
<div className="info">
<div>{user?.nickname}</div>
<div>ID: {user?.id}</div>
</div>
</div>
<Divider style={{ margin: 0 }} />
<div className="menu-list-container">
{menu}
</div>
<Divider style={{ margin: 0 }} />
<div className="logout">
<div onClick={handleLogout}>退</div>
</div>
</div>
)}
>
<div><UserButton/></div>
</Dropdown> : <UserButton/>}
</div>)
}
export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
@ -44,10 +78,10 @@ export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
<div className="min-h-screen w-full">
<div className="app-header">
<div className="logo-container">
<LogoText/>
<LogoText style={{fontSize: 30}}/>
</div>
<DashboardNavigation/>
<div className="flex items-center">
<DashboardNavigation/>
<NavigationUserContainer/>
</div>
</div>
@ -62,7 +96,16 @@ export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
const DashboardLayout: React.FC<{ children?: React.ReactNode }> = ({children}) => {
const loc = useLocation()
const navigate = useNavigate()
useEffect(()=>{
if(!defaultCache.firstLoadPath && loc.pathname == '/live'){
defaultCache.firstLoadPath = loc.pathname;
navigate('/')
}
},[])
return <AuthGuard>
<div className="fixed">first path:{defaultCache.firstLoadPath}</div>
<BaseLayout>
{children ? children : <Outlet/>}
</BaseLayout>

View File

@ -1,47 +1,52 @@
import {clsx} from "clsx";
import {NavLink} from "react-router-dom";
import {IconNavigationArrow} from "@/components/icons";
import useAuth from "@/hooks/useAuth.ts";
const NavItems = [
{
key: 'news',
name: '新闻素材',
icon: 'news',
path:'/'
path: '/'
},
{
key: 'video',
name: '新闻编辑',
icon: 'e',
path:'/edit'
path: '/edit'
},
{
key: 'create',
name: '视频生成',
icon: 'ai',
path:'/create'
},
{
key: 'library',
name: '视频库',
icon: '+',
path:'/library'
path: '/create'
},
// {
// key: 'library',
// name: '视频库',
// icon: '+',
// path:'/library'
// },
{
key: 'live',
name: '数字人直播间',
icon: 'v',
path:'/live'
path: '/live'
}
]
export function DashboardNavigation() {
const {user} = useAuth()
return (<div className={'flex app-main-navigation'}>
{NavItems.map((it, idx) => (
<NavLink to={it.path} key={idx} className={clsx('nav-item cursor-pointer items-center')}>
<span className="menu-text ml-1">{it.name}</span>
</NavLink>
))}
{NavItems.map((it, idx) => (<div key={idx} className={"flex items-center"}>
{user ? <NavLink to={it.path} className={clsx('nav-item cursor-pointer items-center')}>
<span className="menu-text ml-2">{it.name}</span>
</NavLink> : <div className={clsx('nav-item cursor-pointer items-center')}>
<span className="menu-text ml-2">{it.name}</span>
</div>}
{idx !== NavItems.length - 1 && <IconNavigationArrow className="ml-2" />}
</div>))}
</div>
);
}

View File

@ -13,10 +13,7 @@ const NewsIndex = React.lazy(() => import("@/pages/news"))
const NewsEdit = React.lazy(() => import("@/pages/news/edit.tsx"))
const routes: RouteObject[] = [
{
path: '/user',
element: <UserAuth/>,
},
{
path: '/',
element: <DashboardLayout/>,
@ -26,6 +23,10 @@ const routes: RouteObject[] = [
path: '',
element: <NewsIndex/>
},
{
path: 'user',
element: <UserAuth/>,
},
{
path: 'edit',
element: <NewsEdit/>
@ -35,7 +36,7 @@ const routes: RouteObject[] = [
element: <CreateVideoIndex/>
},
{
path: 'library',
path: 'history',
element: <LibraryIndex/>
},
{

View File

@ -12,9 +12,8 @@ export function getList(data: ApiArticleSearchParams) {
*
* @param id
*/
export function deleteById(id: Id) {
throw new Error('Not implement')
return post<{ article: any }>({url: '/article/delete/' + id})
export function deleteByIds(article_ids: Id[]) {
return post('/article/remove',{article_ids})
}
export function getById(id: Id) {

View File

@ -6,6 +6,15 @@ export function getList() {
export function search(params:VideoSearchParams) {
return post<DataList<VideoInfo>>('/video/search',params)
}
export function deleteHistories(ids: Id[]) {
console.log('deleteHistories',ids)
return new Promise<number>((resolve)=>{
setTimeout(()=>{
resolve(1)
},2000)
})
}
/**
*
* @param title

9
src/types/api.d.ts vendored
View File

@ -2,7 +2,8 @@ declare interface ApiRequestPageParams {
pagination: {
page: number;
limit: number;
}
};
request_time?: number;
}
declare interface ApiArticleSearchParams extends ApiRequestPageParams{
@ -60,6 +61,9 @@ interface BasicArticleInfo {
summary: string;
publish_time: string;
media_name: string;
column_name?: string;
img_num?: number;
content_word_count?: number;
media_id: number;
fanwen_column_id: number;
}
@ -95,6 +99,7 @@ declare interface VideoInfo {
article_id: number;
status: number;
publish_time?: number|string;
ctime?: number|string;
}
// room live
declare interface LiveVideoInfo {
@ -107,6 +112,8 @@ declare interface LiveVideoInfo {
video_oss_url: string;
status: number;
order_no: string;
publish_time?: number|string;
ctime?: number|string;
}
declare interface LiveState{

1
src/types/core.d.ts vendored
View File

@ -35,6 +35,7 @@ declare interface ArticleDetail {
}
declare interface NewsInfo {
id: number;
title: string;
content: string;
media_name: string;

View File

@ -55,8 +55,13 @@ function getDayjs(time:any){
return dayjs(time);
}
export function formatTime(time:any,template = 'YYYY-MM-DD HH:mm:ss') {
if(!time) return '-';
export function formatTime(time: any, template: 'min' | 'date' | string = 'YYYY-MM-DD HH:mm:ss') {
if (!time) return '-';
if (template == 'min') {
template = 'YYYY-MM-DD HH:mm'
} else if (template == 'date') {
template = 'YYYY-MM-DD'
}
return getDayjs(time).format(template)
}
@ -85,6 +90,10 @@ export function calcContentLengthLikeWord(str:string) {
// 将时长转换成 时:分:秒
export function formatDuration(duration: number) {
if(duration < 0 || isNaN(duration)){
return '00:00:00';
}
duration = Math.ceil(duration);
const hour = Math.floor(duration / 3600);
const minute = Math.floor((duration - hour * 3600) / 60);
const second = duration - hour * 3600 - minute * 60;

View File

@ -2,10 +2,20 @@
const themeConfig = {
colors:{
'primary':'#7356f6',
'primary-bg': '#f6f6f6',
'primary-blue':'rgb(64, 150, 255)',
'primary-blue-bg':'#d9eaff',
'primary-bg': 'rgb(244, 247, 252)',
'info':'rgba(238, 245, 255, 1)',
'news-to-edit':'#ececec',
'active': '#FFE0E0',
'primary-red':'#F5222D',
'primary-red-70':'rgba(245,34,45,0.7)',
'popconfirm-bg':'#ff5C5C',
'popconfirm-btn-primary-hover':'#f15656',
'popconfirm-btn-cancel':'#818181',
'popconfirm-btn-cancel-hover':'rgba(71, 71, 71, 1)',
},
widths:{
'chat-avatar-size': '32px',
@ -43,6 +53,9 @@ export default {
},
backgroundColor: {
...themeConfig.colors,
},
textColor: {
...themeConfig.colors,
}
},
screens: {

View File

@ -6,7 +6,7 @@ import dayjs from "dayjs";
// https://vitejs.dev/config/
export default defineConfig(({mode}) => {
const devServerHost = mode == 'test' ? '124.220.14.192' : '192.168.0.231:9999'
const devServerHost = mode == 'test' ? 'https://fm-admin.starbitech.com' : 'http://192.168.0.231:9999'
const AUTH_TOKEN_KEY = mode == 'production' ? 'digital-person-token' : `digital-person-token_${mode}`
if (mode !== 'production') {
@ -41,7 +41,7 @@ export default defineConfig(({mode}) => {
port: 10021,
proxy: {
'/mgmt': {
target: `http://${devServerHost}`, // http://124.220.14.192/ 192.168.0.231:9999
target: `${devServerHost}`, // http://124.220.14.192/ 192.168.0.231:9999
changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, '')
},

176
yarn.lock
View File

@ -22,10 +22,10 @@
dependencies:
"@ctrl/tinycolor" "^3.6.1"
"@ant-design/cssinjs-utils@^1.1.1":
version "1.1.1"
resolved "https://registry.npmmirror.com/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.1.tgz#57abb43671023f937348bd33442862c60ac8e8b2"
integrity sha512-2HAiyGGGnM0es40SxdszeQAU5iWp41wBIInq+ONTCKjlSKOrzQfnw4JDtB8IBmqE6tQaEKwmzTP2LGdt5DSwYQ==
"@ant-design/cssinjs-utils@^1.1.3":
version "1.1.3"
resolved "https://registry.npmmirror.com/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz#5dd79126057920a6992d57b38dd84e2c0b707977"
integrity sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==
dependencies:
"@ant-design/cssinjs" "^1.21.0"
"@babel/runtime" "^7.23.2"
@ -56,10 +56,10 @@
resolved "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz#ed2be7fb4d82ac7e1d45a54a5b06d6cecf8be6f6"
integrity sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==
"@ant-design/icons@^5.5.1":
version "5.5.1"
resolved "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.5.1.tgz#4ff57b2a0d3bafae3d990c2781fd857ead36c935"
integrity sha512-0UrM02MA2iDIgvLatWrj6YTCYe0F/cwXvVE0E2SqGrL7PZireQwgEKTKBisWpZyal5eXZLvuM98kju6YtYne8w==
"@ant-design/icons@^5.5.2":
version "5.5.2"
resolved "https://registry.npmmirror.com/@ant-design/icons/-/icons-5.5.2.tgz#c4567943cc2b7c6dbe9cae68c06ffa35f755dc0d"
integrity sha512-xc53rjVBl9v2BqFxUjZGti/RfdDeA8/6KYglmInM2PNqSXc/WfuGDTifJI/ZsokJK0aeKvOIbXc9y2g8ILAhEA==
dependencies:
"@ant-design/colors" "^7.0.0"
"@ant-design/icons-svg" "^4.4.0"
@ -687,7 +687,7 @@
classnames "^2.3.2"
rc-util "^5.24.4"
"@rc-component/trigger@^2.0.0", "@rc-component/trigger@^2.1.1", "@rc-component/trigger@^2.2.5":
"@rc-component/trigger@^2.0.0", "@rc-component/trigger@^2.1.1":
version "2.2.5"
resolved "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.2.5.tgz#5ebe383e563e667b3fa24b6b32afedbab378a92e"
integrity sha512-F1EJ4KjFpGAHAjuKvOyZB/6IZDkVx0bHl0M4fQM5wXcmm7lgTgVSSnR3bXwdmS6jOJGHOqfDxIJW3WUvwMIXhQ==
@ -699,6 +699,18 @@
rc-resize-observer "^1.3.1"
rc-util "^5.38.0"
"@rc-component/trigger@^2.2.6":
version "2.2.6"
resolved "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.2.6.tgz#bfe6602313b3fadd659687746511f813299d5ea4"
integrity sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==
dependencies:
"@babel/runtime" "^7.23.2"
"@rc-component/portal" "^1.1.0"
classnames "^2.3.2"
rc-motion "^2.0.0"
rc-resize-observer "^1.3.1"
rc-util "^5.44.0"
"@remix-run/router@1.21.0":
version "1.21.0"
resolved "https://registry.npmmirror.com/@remix-run/router/-/router-1.21.0.tgz#c65ae4262bdcfe415dbd4f64ec87676e4a56e2b5"
@ -1028,15 +1040,15 @@ ansi-styles@^6.1.0:
resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
antd@^5.22.1:
version "5.22.1"
resolved "https://registry.npmmirror.com/antd/-/antd-5.22.1.tgz#726c9d4465a1f59079ef60896418fcdbb07a796c"
integrity sha512-itq8AZwe3IfawZH6SMM5XdbTz1xXGTTqA7sNN0qpEdxcoTpD5nRsCBAMIy+PhwcWFobgFc6ZlF8d7f8eicn0SQ==
antd@^5.22.5:
version "5.22.5"
resolved "https://registry.npmmirror.com/antd/-/antd-5.22.5.tgz#e959381faca86c984cc853a0ab5cb3f140178336"
integrity sha512-+0UP8w+ULVv2OIzCDVz7j6I0UfH6mMLHSWO6qzpBc+9psOoVQLRbyAE21XnZM/eGrt2MNsEDL5fmlhXL/V8JyQ==
dependencies:
"@ant-design/colors" "^7.1.0"
"@ant-design/cssinjs" "^1.21.1"
"@ant-design/cssinjs-utils" "^1.1.1"
"@ant-design/icons" "^5.5.1"
"@ant-design/cssinjs-utils" "^1.1.3"
"@ant-design/icons" "^5.5.2"
"@ant-design/react-slick" "~1.1.2"
"@babel/runtime" "^7.25.7"
"@ctrl/tinycolor" "^3.6.1"
@ -1044,7 +1056,7 @@ antd@^5.22.1:
"@rc-component/mutate-observer" "^1.1.0"
"@rc-component/qrcode" "~1.0.0"
"@rc-component/tour" "~1.15.1"
"@rc-component/trigger" "^2.2.5"
"@rc-component/trigger" "^2.2.6"
classnames "^2.5.1"
copy-to-clipboard "^3.3.3"
dayjs "^1.11.11"
@ -1053,33 +1065,33 @@ antd@^5.22.1:
rc-collapse "~3.9.0"
rc-dialog "~9.6.0"
rc-drawer "~7.2.0"
rc-dropdown "~4.2.0"
rc-field-form "~2.5.0"
rc-dropdown "~4.2.1"
rc-field-form "~2.7.0"
rc-image "~7.11.0"
rc-input "~1.6.3"
rc-input "~1.6.4"
rc-input-number "~9.3.0"
rc-mentions "~2.17.0"
rc-menu "~9.16.0"
rc-motion "^2.9.3"
rc-motion "^2.9.4"
rc-notification "~5.6.2"
rc-pagination "~4.3.0"
rc-picker "~4.8.0"
rc-pagination "~5.0.0"
rc-picker "~4.8.3"
rc-progress "~4.0.0"
rc-rate "~2.13.0"
rc-resize-observer "^1.4.0"
rc-resize-observer "^1.4.1"
rc-segmented "~2.5.0"
rc-select "~14.16.3"
rc-select "~14.16.4"
rc-slider "~11.1.7"
rc-steps "~6.0.1"
rc-switch "~4.1.0"
rc-table "~7.48.1"
rc-table "~7.49.0"
rc-tabs "~15.4.0"
rc-textarea "~1.8.2"
rc-tooltip "~6.2.1"
rc-tree "~5.10.1"
rc-tree-select "~5.24.4"
rc-tree-select "~5.24.5"
rc-upload "~4.8.1"
rc-util "^5.43.0"
rc-util "^5.44.2"
scroll-into-view-if-needed "^3.1.0"
throttle-debounce "^5.0.2"
@ -2533,10 +2545,20 @@ rc-dropdown@~4.2.0:
classnames "^2.2.6"
rc-util "^5.17.0"
rc-field-form@~2.5.0:
version "2.5.1"
resolved "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-2.5.1.tgz#30f3c529f86aec6af27589052df9c66cec94ceb4"
integrity sha512-33hunXwynQJyeae7LS3hMGTXNeRBjiPyPYgB0824EbmLHiXC1EBGyUwRh6xjLRy9c+en5WARYN0gJz5+JAqwig==
rc-dropdown@~4.2.1:
version "4.2.1"
resolved "https://registry.npmmirror.com/rc-dropdown/-/rc-dropdown-4.2.1.tgz#44729eb2a4272e0353d31ac060da21e606accb1c"
integrity sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==
dependencies:
"@babel/runtime" "^7.18.3"
"@rc-component/trigger" "^2.0.0"
classnames "^2.2.6"
rc-util "^5.44.1"
rc-field-form@~2.7.0:
version "2.7.0"
resolved "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-2.7.0.tgz#22413e793f35bfc1f35b0ec462774d7277f5a399"
integrity sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==
dependencies:
"@babel/runtime" "^7.18.0"
"@rc-component/async-validator" "^5.0.3"
@ -2565,7 +2587,7 @@ rc-input-number@~9.3.0:
rc-input "~1.6.0"
rc-util "^5.40.1"
rc-input@~1.6.0, rc-input@~1.6.3:
rc-input@~1.6.0:
version "1.6.3"
resolved "https://registry.npmmirror.com/rc-input/-/rc-input-1.6.3.tgz#f1708fc3d5e68f95cb20faeb3eed1df8543cd444"
integrity sha512-wI4NzuqBS8vvKr8cljsvnTUqItMfG1QbJoxovCgL+DX4eVUcHIjVwharwevIxyy7H/jbLryh+K7ysnJr23aWIA==
@ -2574,6 +2596,15 @@ rc-input@~1.6.0, rc-input@~1.6.3:
classnames "^2.2.1"
rc-util "^5.18.1"
rc-input@~1.6.4:
version "1.6.4"
resolved "https://registry.npmmirror.com/rc-input/-/rc-input-1.6.4.tgz#08d91460f6b75b3fa5294154e89775784c233129"
integrity sha512-lBZhfRD4NSAUW0zOKLUeI6GJuXkxeZYi0hr8VcJgJpyTNOvHw1ysrKWAHcEOAAHj7guxgmWYSi6xWrEdfrSAsA==
dependencies:
"@babel/runtime" "^7.11.1"
classnames "^2.2.1"
rc-util "^5.18.1"
rc-mentions@~2.17.0:
version "2.17.0"
resolved "https://registry.npmmirror.com/rc-mentions/-/rc-mentions-2.17.0.tgz#d16dd5c8e4db87862c1007f7195b0aea9247cdcd"
@ -2599,7 +2630,7 @@ rc-menu@~9.16.0:
rc-overflow "^1.3.1"
rc-util "^5.27.0"
rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.3, rc-motion@^2.4.4, rc-motion@^2.6.1, rc-motion@^2.6.2, rc-motion@^2.9.0, rc-motion@^2.9.3:
rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.3, rc-motion@^2.4.4, rc-motion@^2.6.1, rc-motion@^2.6.2, rc-motion@^2.9.0:
version "2.9.3"
resolved "https://registry.npmmirror.com/rc-motion/-/rc-motion-2.9.3.tgz#b1bdaf816f1ccb3e4b3b0c531c3037a59286379e"
integrity sha512-rkW47ABVkic7WEB0EKJqzySpvDqwl60/tdkY7hWP7dYnh5pm0SzJpo54oW3TDUGXV5wfxXFmMkxrzRRbotQ0+w==
@ -2608,6 +2639,15 @@ rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motio
classnames "^2.2.1"
rc-util "^5.43.0"
rc-motion@^2.9.4:
version "2.9.5"
resolved "https://registry.npmmirror.com/rc-motion/-/rc-motion-2.9.5.tgz#12c6ead4fd355f94f00de9bb4f15df576d677e0c"
integrity sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==
dependencies:
"@babel/runtime" "^7.11.1"
classnames "^2.2.1"
rc-util "^5.44.0"
rc-notification@~5.6.2:
version "5.6.2"
resolved "https://registry.npmmirror.com/rc-notification/-/rc-notification-5.6.2.tgz#8525b32d49dd96ec974acae61d1d1eabde61463a"
@ -2628,19 +2668,19 @@ rc-overflow@^1.3.1, rc-overflow@^1.3.2:
rc-resize-observer "^1.0.0"
rc-util "^5.37.0"
rc-pagination@~4.3.0:
version "4.3.0"
resolved "https://registry.npmmirror.com/rc-pagination/-/rc-pagination-4.3.0.tgz#c6022f820aa3a45fd734ae33a2915d39597dce1d"
integrity sha512-UubEWA0ShnroQ1tDa291Fzw6kj0iOeF26IsUObxYTpimgj4/qPCWVFl18RLZE+0Up1IZg0IK4pMn6nB3mjvB7g==
rc-pagination@~5.0.0:
version "5.0.0"
resolved "https://registry.npmmirror.com/rc-pagination/-/rc-pagination-5.0.0.tgz#7633e1f0ff372ad78c03e86bcef78b660374d196"
integrity sha512-QjrPvbAQwps93iluvFM62AEYglGYhWW2q/nliQqmvkTi4PXP4HHoh00iC1Sa5LLVmtWQHmG73fBi2x6H6vFHRg==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.3.2"
rc-util "^5.38.0"
rc-picker@~4.8.0:
version "4.8.1"
resolved "https://registry.npmmirror.com/rc-picker/-/rc-picker-4.8.1.tgz#105cfae323bf1db5e9f9e6fdc773ff3250e837de"
integrity sha512-lj9hXXMSkbjFUIhfQh8XH698ybxnoBOfq7pdM1FvfSyDwdFhdQa7dvsIYwo6Uz7Zp1wVkfw5rOJO3MpdWzoHsg==
rc-picker@~4.8.3:
version "4.8.3"
resolved "https://registry.npmmirror.com/rc-picker/-/rc-picker-4.8.3.tgz#06cffd5a2201fc8d274e12f7ee32ea8ba6f3f60f"
integrity sha512-hJ45qoEs4mfxXPAJdp1n3sKwADul874Cd0/HwnsEOE60H+tgiJUGgbOD62As3EG/rFVNS5AWRfBCDJJfmRqOVQ==
dependencies:
"@babel/runtime" "^7.24.7"
"@rc-component/trigger" "^2.0.0"
@ -2677,6 +2717,16 @@ rc-resize-observer@^1.0.0, rc-resize-observer@^1.1.0, rc-resize-observer@^1.3.1,
rc-util "^5.38.0"
resize-observer-polyfill "^1.5.1"
rc-resize-observer@^1.4.1:
version "1.4.3"
resolved "https://registry.npmmirror.com/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz#4fd41fa561ba51362b5155a07c35d7c89a1ea569"
integrity sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==
dependencies:
"@babel/runtime" "^7.20.7"
classnames "^2.2.1"
rc-util "^5.44.1"
resize-observer-polyfill "^1.5.1"
rc-segmented@~2.5.0:
version "2.5.0"
resolved "https://registry.npmmirror.com/rc-segmented/-/rc-segmented-2.5.0.tgz#3b5423adf57459345c77c39c7581fde786a16c11"
@ -2687,7 +2737,7 @@ rc-segmented@~2.5.0:
rc-motion "^2.4.4"
rc-util "^5.17.0"
rc-select@~14.16.2, rc-select@~14.16.3:
rc-select@~14.16.2:
version "14.16.3"
resolved "https://registry.npmmirror.com/rc-select/-/rc-select-14.16.3.tgz#cd5395ef724d693a8f782ddd7bcc6253c31c65a9"
integrity sha512-51+j6s3fJJJXB7E+B6W1hM4Tjzv1B/Decooz9ilgegDBt3ZAth1b/xMwYCTrT5BbG2e53XACQsyDib2+3Ro1fg==
@ -2700,6 +2750,19 @@ rc-select@~14.16.2, rc-select@~14.16.3:
rc-util "^5.16.1"
rc-virtual-list "^3.5.2"
rc-select@~14.16.4:
version "14.16.4"
resolved "https://registry.npmmirror.com/rc-select/-/rc-select-14.16.4.tgz#a98840c4cfb96e263c750e59334ea0a2862e04fc"
integrity sha512-jP6qf7+vjnxGvPpfPWbGYfFlSl3h8L2XcD4O7g2GYXmEeBC0mw+nPD7i++OOE8v3YGqP8xtYjRKAWCMLfjgxlw==
dependencies:
"@babel/runtime" "^7.10.1"
"@rc-component/trigger" "^2.1.1"
classnames "2.x"
rc-motion "^2.0.1"
rc-overflow "^1.3.1"
rc-util "^5.16.1"
rc-virtual-list "^3.5.2"
rc-slider@~11.1.7:
version "11.1.7"
resolved "https://registry.npmmirror.com/rc-slider/-/rc-slider-11.1.7.tgz#3de333b1ec84d53a7bda2f816bb4779423628f09"
@ -2727,10 +2790,10 @@ rc-switch@~4.1.0:
classnames "^2.2.1"
rc-util "^5.30.0"
rc-table@~7.48.1:
version "7.48.1"
resolved "https://registry.npmmirror.com/rc-table/-/rc-table-7.48.1.tgz#16ee3d82fa17284628d7883933b5d481238ea013"
integrity sha512-Z4mDKjWg+xz/Ezdw6ivWcbqRpaJ0QfCORRoRrlrw65KSGZLK8OcTdacH22/fyGb8L4It/0/9qcMm8VrVAk/WBw==
rc-table@~7.49.0:
version "7.49.0"
resolved "https://registry.npmmirror.com/rc-table/-/rc-table-7.49.0.tgz#f5a4880d9527d2c9e42f5f721b5423e7a1ca475b"
integrity sha512-/FoPLX94muAQOxVpi1jhnpKjOIqUbT81eELQPAzSXOke4ky4oCWYUXOcVpL31ZCO90xScwVSXRd7coqtgtB1Ng==
dependencies:
"@babel/runtime" "^7.10.1"
"@rc-component/context" "^1.4.0"
@ -2772,10 +2835,10 @@ rc-tooltip@~6.2.1:
"@rc-component/trigger" "^2.0.0"
classnames "^2.3.1"
rc-tree-select@~5.24.4:
version "5.24.4"
resolved "https://registry.npmmirror.com/rc-tree-select/-/rc-tree-select-5.24.4.tgz#6789b8761daf53a8b91b75e9e6c4f720a7e8cc92"
integrity sha512-MzljkSkk7weKOcE853UtYlXB6uyUEzcEQhhpaCwE6jQPbmBUgGiRURuKWpYUnM/dXrwTTlCK969M6Pgjj35MLA==
rc-tree-select@~5.24.5:
version "5.24.5"
resolved "https://registry.npmmirror.com/rc-tree-select/-/rc-tree-select-5.24.5.tgz#a1bf85c7d5e4979880cfb0748bb6bab937ed3483"
integrity sha512-PnyR8LZJWaiEFw0SHRqo4MNQWyyZsyMs8eNmo68uXZWjxc7QqeWcjPPoONN0rc90c3HZqGF9z+Roz+GLzY5GXA==
dependencies:
"@babel/runtime" "^7.25.7"
classnames "2.x"
@ -2811,6 +2874,14 @@ rc-util@^5.0.1, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.2.
"@babel/runtime" "^7.18.3"
react-is "^18.2.0"
rc-util@^5.44.0, rc-util@^5.44.1, rc-util@^5.44.2:
version "5.44.2"
resolved "https://registry.npmmirror.com/rc-util/-/rc-util-5.44.2.tgz#6bc5db0e96ebdb515eb5977a7371887e5413a6f8"
integrity sha512-uGSk3hpPBLa3/0QAcKhCjgl4SFnhQCJDLvvpoLdbR6KgDuXrujG+dQaUeUvBJr2ZWak1O/9n+cYbJiWmmk95EQ==
dependencies:
"@babel/runtime" "^7.18.3"
react-is "^18.2.0"
rc-virtual-list@^3.14.2, rc-virtual-list@^3.5.1, rc-virtual-list@^3.5.2:
version "3.15.0"
resolved "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.15.0.tgz#45c5b1ef1363287214e0a3c17af29ee0c3764764"
@ -3442,3 +3513,8 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zustand@^5.0.2:
version "5.0.2"
resolved "https://registry.npmmirror.com/zustand/-/zustand-5.0.2.tgz#f7595ada55a565f1fd6464f002a91e701ee0cfca"
integrity sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==