💄 update 视频操作ui
This commit is contained in:
parent
d4e9dde7df
commit
2db8d7aac8
@ -131,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 {
|
||||
@ -148,17 +148,80 @@
|
||||
}
|
||||
|
||||
.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-2 px-4 rounded-xl gap-2 border;
|
||||
border-width: 2px;
|
||||
&.playing{
|
||||
@apply border-primary-blue bg-[#d9eaff];
|
||||
}
|
||||
&.disabled{
|
||||
@apply border-primary-blue bg-[#f4f7fc];
|
||||
}
|
||||
&.header-row{
|
||||
background: none;
|
||||
.col{
|
||||
height: 42px;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&.checked {
|
||||
@apply border-primary-blue bg-primary-blue-bg;
|
||||
}
|
||||
|
||||
.col {
|
||||
@apply flex items-center relative pl-4 text-center justify-center;
|
||||
height: 80px;
|
||||
|
||||
&:after {
|
||||
@apply absolute;
|
||||
border-right: solid 1px #e8e8e8;
|
||||
content: ' ';
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.number {
|
||||
width: 50px;
|
||||
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-between;
|
||||
width: 180px;
|
||||
padding: 0 20px 0 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
@ -185,7 +248,8 @@
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
.list-scroller-container{
|
||||
|
||||
.list-scroller-container {
|
||||
overflow: auto;
|
||||
margin-right: -20px;
|
||||
padding-right: 16px;
|
||||
@ -196,7 +260,6 @@
|
||||
@apply list-scroller-container;
|
||||
height: calc(100vh - var(--app-header-header) - 200px);
|
||||
|
||||
|
||||
.data-list-container-inner {
|
||||
|
||||
}
|
||||
@ -235,34 +298,41 @@
|
||||
.ant-modal-body {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.ant-modal-confirm-content{
|
||||
|
||||
.ant-modal-confirm-content {
|
||||
color: #999;
|
||||
}
|
||||
.ant-modal-confirm-btns{
|
||||
|
||||
.ant-modal-confirm-btns {
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.article-edit-modal{
|
||||
|
||||
.article-edit-modal {
|
||||
|
||||
.ant-modal {
|
||||
.ant-modal-content {
|
||||
@apply bg-white p-0;
|
||||
.ant-modal-body{
|
||||
.ant-modal-body {
|
||||
@apply p-0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.article-title{
|
||||
|
||||
.article-title {
|
||||
@apply px-6 pt-10 pb-6;
|
||||
}
|
||||
.article-body{
|
||||
|
||||
.article-body {
|
||||
@apply p-6
|
||||
}
|
||||
.modal-control-footer{
|
||||
|
||||
.modal-control-footer {
|
||||
@apply p-6
|
||||
}
|
||||
.input-box{
|
||||
|
||||
.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;
|
||||
@ -288,6 +358,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 时间选择
|
||||
.timer-select-container {
|
||||
.timer-select-value {
|
||||
@apply text-blue-500 px-4 cursor-pointer h-[31px];
|
||||
@ -306,6 +377,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
//来源选择
|
||||
.tag-select-container {
|
||||
.select-value {
|
||||
@apply text-blue-500 px-4 cursor-pointer h-[31px];
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, {useState} from "react";
|
||||
import {Button, Modal} from "antd";
|
||||
import {Modal} from "antd";
|
||||
import {ButtonType} from "antd/es/button";
|
||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||
import {BizError} from "@/service/types.ts";
|
||||
@ -12,7 +12,9 @@ type Props = {
|
||||
onProcess: (ids: Id[]) => Promise<any|void>
|
||||
successMessage?: string;
|
||||
onSuccess?: () => void;
|
||||
children?: React.ReactNode
|
||||
children?: React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
className?:string;
|
||||
|
||||
}
|
||||
/**
|
||||
@ -21,7 +23,7 @@ type Props = {
|
||||
export default function ButtonBatch(
|
||||
{
|
||||
selected, emptyMessage, successMessage, children,
|
||||
type, confirmMessage, onProcess,onSuccess
|
||||
title, confirmMessage, onProcess,onSuccess,className
|
||||
}: Props) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const onBatchProcess = async () => {
|
||||
@ -44,7 +46,7 @@ export default function ButtonBatch(
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '操作提示',
|
||||
title: title || '操作提示',
|
||||
centered: true,
|
||||
content: confirmMessage,
|
||||
onOk: onBatchProcess
|
||||
@ -52,6 +54,6 @@ export default function ButtonBatch(
|
||||
}
|
||||
|
||||
return (
|
||||
<Button loading={loading} type={type} onClick={handleBtnClick}>{children}</Button>
|
||||
<button disabled={loading} className={className} onClick={handleBtnClick}>{children}</button>
|
||||
)
|
||||
}
|
@ -63,7 +63,9 @@ 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="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"/>
|
||||
<path
|
||||
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>
|
||||
)
|
||||
@ -72,7 +74,7 @@ export const IconDelete = ({style, className}: IconProps) => (
|
||||
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" />
|
||||
fill="currentColor"/>
|
||||
|
||||
</svg>
|
||||
)
|
||||
@ -82,8 +84,9 @@ export const IconAddText = ({style, className}: IconProps) => (
|
||||
<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"/>
|
||||
<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>
|
||||
)
|
||||
|
||||
@ -118,8 +121,32 @@ export const IconAdd = ({style, className}: IconProps) => (
|
||||
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 fill-rule="evenodd" clip-rule="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 fill-rule="evenodd" clip-rule="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"
|
||||
@ -142,7 +169,8 @@ export const IconLive = ({style, className}: IconProps) => (
|
||||
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="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"/>
|
||||
<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>
|
||||
)
|
@ -2,12 +2,13 @@ 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 {CheckCircleFilled, MenuOutlined, MinusCircleFilled, LoadingOutlined} from "@ant-design/icons";
|
||||
|
||||
import ImageCover from '@/assets/images/cover.png'
|
||||
import {IconEdit, IconPlay} from "@/components/icons";
|
||||
import {IconEdit, IconPlay, IconPlaying} from "@/components/icons";
|
||||
import {VideoStatus} from "@/service/api/video.ts";
|
||||
import {formatTime} from "@/util/strings.ts";
|
||||
|
||||
type Props = {
|
||||
video: VideoInfo | LiveVideoInfo,
|
||||
@ -19,17 +20,18 @@ type Props = {
|
||||
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,
|
||||
onCheckedChange, onEdit, active, editable,
|
||||
className, sortable,type
|
||||
className, sortable, type, index,onItemClick
|
||||
}: Props) => {
|
||||
const {
|
||||
attributes, listeners,
|
||||
@ -42,48 +44,71 @@ 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)`,}}
|
||||
onClick={onItemClick}
|
||||
>
|
||||
<div className={`list-row ${generating ? 'disabled' : ''} ${active?'playing':''}`}>
|
||||
<div className="col number">{index}</div>
|
||||
<div className="col cover">
|
||||
<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 && active && <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">
|
||||
<div className="line-clamp-2">
|
||||
{video.title || video.video_title}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col generated-time">{video.publish_time ? formatTime(video.publish_time) : ''}</div>
|
||||
<div className="col operation">
|
||||
{sortable && !generating && (!active ?
|
||||
<button className="hover:text-blue-500 cursor-move" {...attributes} {...listeners}>
|
||||
<MenuOutlined/>
|
||||
</button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>)}
|
||||
|
||||
{editable && !generating && <>
|
||||
{onEdit &&
|
||||
<button className="hover:text-blue-500" onClick={e=>{
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onEdit?.()
|
||||
}} style={{fontSize: '1.1em'}}>
|
||||
<IconEdit/>
|
||||
</button>}
|
||||
<Checkbox checked={state.checked} onChange={() => {
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(!state.checked)
|
||||
} else {
|
||||
setState({checked: !state.checked})
|
||||
}
|
||||
}} />
|
||||
|
||||
{onRemove && <Popconfirm
|
||||
title={<div style={{minWidth: 150}}><span>请确认删除此视频?</span></div>}
|
||||
onConfirm={onRemove}
|
||||
okText="删除"
|
||||
cancelText="取消"
|
||||
>
|
||||
<button className="hover:text-blue-500"><MinusCircleFilled/></button>
|
||||
</Popconfirm>}
|
||||
</>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
0
src/components/video/video-list.tsx
Normal file
0
src/components/video/video-list.tsx
Normal file
@ -1,5 +1,5 @@
|
||||
import React, {useEffect, useMemo, useRef, useState} from "react";
|
||||
import {Button, Modal} from "antd";
|
||||
import {Checkbox, Modal} from "antd";
|
||||
import {SortableContext, arrayMove} from '@dnd-kit/sortable';
|
||||
import {DndContext} from "@dnd-kit/core";
|
||||
|
||||
@ -12,20 +12,25 @@ 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,
|
||||
})
|
||||
const activeIndex = useRef(state.activeIndex)
|
||||
useEffect(() => {
|
||||
@ -123,6 +128,10 @@ export default function LiveIndex() {
|
||||
}).catch(showErrorToast)
|
||||
}
|
||||
const handleConfirm = () => {
|
||||
if(!editable){
|
||||
setEditable(true)
|
||||
return;
|
||||
}
|
||||
modal.confirm({
|
||||
title: '提示',
|
||||
content: '是否采纳移动视频位置操作?',
|
||||
@ -134,20 +143,18 @@ export default function LiveIndex() {
|
||||
}).catch(() => {
|
||||
showToast('调整视频顺序失败,请重试!')
|
||||
})
|
||||
// showToast('编辑成功!!!', 'info');
|
||||
// console.log('origin list', videoData.map(s => s.id))
|
||||
}
|
||||
})
|
||||
}
|
||||
const handleCancelConfirm = () => {
|
||||
modal.confirm({
|
||||
title: '提示',
|
||||
content: '是否取消移动视频位置操作?',
|
||||
onOk: () => {
|
||||
},
|
||||
onCancel: () => {
|
||||
showToast('退出并清除移动视频位置操作!', 'info');
|
||||
loadList()
|
||||
setEditable(false)
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
const handleAllCheckedChange = () => {
|
||||
setCheckedIdArray(state.checkedAll ? [] : videoData.map(v => v.id))
|
||||
setState({
|
||||
checkedAll: !state.checkedAll
|
||||
})
|
||||
}
|
||||
|
||||
@ -157,6 +164,12 @@ export default function LiveIndex() {
|
||||
return videoData.reduce((sum, v) => sum + Math.ceil(v.video_duration / 1000), 0);
|
||||
}, [videoData])
|
||||
|
||||
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-10 page-live">
|
||||
{contextHolder}
|
||||
<div className="flex">
|
||||
@ -172,37 +185,44 @@ export default function LiveIndex() {
|
||||
<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-1">
|
||||
<div className="live-control flex justify-between mb-4">
|
||||
<div className="text-sm">
|
||||
<span>当前{state.activeIndex == -1?'暂未播放':`播放到${state.activeIndex}条`},</span>
|
||||
<span>共{videoData.length}条</span>
|
||||
</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 gap-2 items-center text-sm">
|
||||
<div className={'flex items-center text-gray-400 cursor-pointer select-none'}
|
||||
onClick={handleConfirm}>
|
||||
<span>{editable?'已解锁':'锁定状态不可排序'}</span>
|
||||
<span className="ml-2">
|
||||
{editable ? <IconUnlock/> : <IconLocked/>}
|
||||
</span>
|
||||
</div>
|
||||
<div className="check-all ml-10">
|
||||
<button className="hover:text-blue-300 text-gray-400 text-lg"
|
||||
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>
|
||||
<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} onScroll={top=> setState({showToTop: top > 30})}>
|
||||
<div className="sort-list-container flex-1">
|
||||
<DndContext onDragEnd={(e) => {
|
||||
const {active, over} = e;
|
||||
@ -226,21 +246,37 @@ export default function LiveIndex() {
|
||||
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)}/>
|
||||
<ButtonBatch
|
||||
className='bg-gray-300 hover:bg-gray-400'
|
||||
selected={currentSelectedId}
|
||||
emptyMessage={`请选择要删除的视频`}
|
||||
confirmMessage={`是否删除当前的${currentSelectedId.length}条视频?`}
|
||||
onSuccess={loadList}
|
||||
onProcess={processDeleteVideo}
|
||||
>
|
||||
<span className={'text'}>批量删除</span>
|
||||
<IconDelete className={'text-white'} /></ButtonBatch>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
@ -40,7 +40,7 @@
|
||||
}
|
||||
.col{
|
||||
@apply flex items-center relative pl-6;
|
||||
height: 44px;
|
||||
height: 54px;
|
||||
&:after{
|
||||
@apply absolute;
|
||||
border-right: solid 1px #e8e8e8;
|
||||
@ -68,6 +68,10 @@
|
||||
}
|
||||
.header{
|
||||
@apply bg-primary-bg;
|
||||
.col{
|
||||
|
||||
height: 42px;
|
||||
}
|
||||
.operations{
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ export default function NewEdit() {
|
||||
setSelectedRowKeys([])
|
||||
}
|
||||
}
|
||||
const scrollerRef = useRef<InfiniteScrollerRef>(null)
|
||||
const scrollerRef = useRef<InfiniteScrollerRef|null>(null)
|
||||
|
||||
|
||||
return (<div className="container pb-5 news-edit">
|
||||
|
@ -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} from "@/components/icons";
|
||||
|
||||
|
||||
export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[];onSuccess?:()=>void; }) {
|
||||
@ -33,6 +34,9 @@ export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[];on
|
||||
})
|
||||
}
|
||||
return (
|
||||
<Button type="primary" loading={loading} onClick={onPushClick}>一键推流</Button>
|
||||
<Button type="primary" loading={loading} onClick={onPushClick}>
|
||||
<span className="text">一键推流</span>
|
||||
<IconArrowRight />
|
||||
</Button>
|
||||
)
|
||||
}
|
@ -1,10 +1,8 @@
|
||||
import {Empty, Modal} from "antd";
|
||||
import {Checkbox, Empty, Modal} 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";
|
||||
@ -14,15 +12,20 @@ import ButtonBatch from "@/components/button-batch.tsx";
|
||||
import {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";
|
||||
|
||||
export default function VideoIndex() {
|
||||
const [editId, setEditId] = useState(-1)
|
||||
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
|
||||
})
|
||||
const [checkedIdArray, setCheckedIdArray] = useState<Id[]>([])
|
||||
|
||||
@ -45,6 +48,8 @@ export default function VideoIndex() {
|
||||
|
||||
// 播放视频
|
||||
const playVideo = (video: VideoInfo, playingIndex: number) => {
|
||||
console.log(video)
|
||||
if(video.status == VideoStatus.Generating ) return;
|
||||
if (video.oss_video_url && video.status !== 1) {
|
||||
setState({playingIndex})
|
||||
player.current?.play(video.oss_video_url, 0)
|
||||
@ -76,110 +81,10 @@ export default function VideoIndex() {
|
||||
return (<div className="container py-10 page-live">
|
||||
{contextHolder}
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
<div className="video-player-container ml-16 w-[360px] flex flex-col">
|
||||
<div className="text-center text-base">预览视频</div>
|
||||
<div className="video-player-container mr-16 w-[360px] flex flex-col">
|
||||
<div className="text-center text-base text-gray-400">预览视频 - 点击视频列表播放</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) => {
|
||||
@ -188,6 +93,106 @@ export default function VideoIndex() {
|
||||
className="w-[360px] h-[640px] bg-white"/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center text-sm mt-4 text-gray-400">视频时长: {formatDuration(totalDuration)}</div>
|
||||
</div>
|
||||
<div className="video-list-container rounded flex flex-col flex-1">
|
||||
<div className="live-control flex justify-between">
|
||||
<div className="pl-[70px]"></div>
|
||||
<div className="flex items-center pr-[10px]">
|
||||
<button className="hover:text-blue-300 text-gray-400 text-lg"
|
||||
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'}>
|
||||
<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 ref={scrollerRef} onScroll={top=> setState({showToTop: top > 30})}>
|
||||
{
|
||||
videoData.length == 0 ? <div className="m-auto"><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);
|
||||
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;
|
||||
})
|
||||
}}
|
||||
onItemClick={ () => 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>
|
||||
}
|
||||
</InfiniteScroller>
|
||||
</div>
|
||||
</div>
|
||||
<div className="page-action">
|
||||
<ButtonToTop visible={state.showToTop} onClick={()=>scrollerRef.current?.scrollToPosition(0)}/>
|
||||
{/*<ButtonDeleteBatch ids={checkedIdArray} onSuccess={loadList}/>*/}
|
||||
<ButtonBatch
|
||||
onProcess={deleteByIds}
|
||||
selected={checkedIdArray}
|
||||
emptyMessage={`请选择要删除的新闻视频`}
|
||||
title={`已选择${checkedIdArray.length}条,确定要全部删除吗?`}
|
||||
className='bg-gray-300 hover:bg-gray-400'
|
||||
confirmMessage={`删除后需从新闻素材中`}
|
||||
onSuccess={() => {
|
||||
showToast('删除成功!', 'success')
|
||||
loadList()
|
||||
}}
|
||||
>
|
||||
<span className="text">批量删除</span>
|
||||
<IconDelete className={'text-white'} />
|
||||
</ButtonBatch>
|
||||
<ButtonPush2Room ids={checkedIdArray} list={videoData} onSuccess={loadList}/>
|
||||
</div>
|
||||
</div>
|
||||
<ArticleEditModal type={'video'} id={editId} onClose={() => setEditId(-1)}/>
|
||||
|
@ -42,7 +42,7 @@ const NavigationUserContainer = () => {
|
||||
</div>)
|
||||
|
||||
return (<div className={"flex items-center justify-between gap-2 ml-10"}>
|
||||
{user ? <Dropdown menu={{items}} placement="bottom" arrow>
|
||||
{user ? <Dropdown rootClassName={'z-[99999]'} menu={{items}} placement="bottom" arrow>
|
||||
<div><UserButton/></div>
|
||||
</Dropdown> : <UserButton/>}
|
||||
</div>)
|
||||
|
1
src/types/api.d.ts
vendored
1
src/types/api.d.ts
vendored
@ -107,6 +107,7 @@ declare interface LiveVideoInfo {
|
||||
video_oss_url: string;
|
||||
status: number;
|
||||
order_no: string;
|
||||
publish_time?: number|string;
|
||||
}
|
||||
|
||||
declare interface LiveState{
|
||||
|
Loading…
x
Reference in New Issue
Block a user