feat: 统一样式
This commit is contained in:
parent
cea77ea231
commit
4e23bb623f
@ -230,34 +230,7 @@
|
||||
//max-height: calc(100vh - var(--app-header-header) - 200px);
|
||||
//overflow: auto;
|
||||
}
|
||||
.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 {
|
||||
@ -342,35 +315,72 @@
|
||||
}
|
||||
.popconfirm-main{
|
||||
.ant-popover-inner{
|
||||
@apply bg-white px-6 py-6 rounded-xl;
|
||||
border-radius: 4px;
|
||||
padding: 20px 24px;
|
||||
min-width: 360px;
|
||||
background-color: #f2f2f2;
|
||||
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;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.ant-popconfirm-message{
|
||||
.ant-popconfirm-title{
|
||||
@apply text-xl font-bold;
|
||||
@apply text-xl;
|
||||
font-weight: 400;
|
||||
}
|
||||
.ant-popconfirm-description{
|
||||
@apply mt-4 text-gray-400 text-sm;
|
||||
margin-left: -30px;
|
||||
@apply mt-2 text-gray-600 text-sm;
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
.ant-popconfirm-buttons{
|
||||
@apply mt-8;
|
||||
@apply mt-6;
|
||||
button{
|
||||
@apply rounded-2xl py-4 px-8;
|
||||
font-size: 14px;
|
||||
line-height: 1.5714285714285714;
|
||||
height: 32px;
|
||||
padding: 4px 15px;
|
||||
border-radius: 4px;
|
||||
min-width: 88px;
|
||||
}
|
||||
.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-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;
|
||||
//}
|
||||
}
|
||||
}
|
||||
.root-modal-confirm{
|
||||
z-index: calc(var(--header-z-index) + 1) !important;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
//anticon anticon-exclamation-circle
|
||||
.icon-warning{
|
||||
@apply text-red-500;
|
||||
font-size: 20px;
|
||||
transform: translateY(5px);
|
||||
margin-right: 20px;
|
||||
}
|
||||
.ant-modal-confirm-title{
|
||||
font-size: 20px;
|
||||
}
|
||||
.ant-modal-confirm-content{
|
||||
margin-top: 10px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
.ant-modal-confirm-btns{
|
||||
@apply mt-6;
|
||||
button{
|
||||
font-size: 14px;
|
||||
line-height: 1.5714285714285714;
|
||||
height: 32px;
|
||||
padding: 4px 15px;
|
||||
border-radius: 4px;
|
||||
min-width: 88px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -412,7 +422,7 @@
|
||||
}
|
||||
|
||||
.ant-modal-confirm-btns {
|
||||
margin-top: 40px;
|
||||
@apply mt-6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ function HotNews({news, mode, onValueChange}: HotNewsProps) {
|
||||
'例: 丁薛祥出席全国高校毕业生等青年就业创业工作视频...',
|
||||
'例:俄称乌方再度袭击俄能源设施 乌称击退俄军进攻',
|
||||
] : [
|
||||
'please type hot news',
|
||||
'please type hot news',
|
||||
'please type hot news',
|
||||
'eg.China\'s tax policies invigorate private economy',
|
||||
'eg.China\'s central bank conducts reverse repos Wednesday',
|
||||
'eg.China intensifies law enforcement in cyberspace',
|
||||
]
|
||||
},[i18n.language])
|
||||
const handleValueChange = (value: string, index: number) => {
|
||||
|
@ -249,7 +249,7 @@ export default function ArticleEditModal(props: Props) {
|
||||
setState({msgTitle: e.target.value ? '' : t('news.edit_notice_enter_article_title1')})
|
||||
}} placeholder={t('news.edit_notice_enter_article_title')}/>
|
||||
</div>
|
||||
<div className="text-red-500">{state.msgTitle}</div>
|
||||
<div className="text-red-500 mt-2">{state.msgTitle}</div>
|
||||
</div>
|
||||
<div className="article-body">
|
||||
<div className="box">
|
||||
@ -264,9 +264,9 @@ export default function ArticleEditModal(props: Props) {
|
||||
setState({msgGroup: (list.length == 0 || list[0].length == 0 || !list[0][0].content) ? t('news.edit_empty_human_content') : ''});
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-500">{state.msgGroup}</div>
|
||||
<div className="text-red-500 mt-2">{state.msgGroup}</div>
|
||||
</div>
|
||||
{state.error && <div className="text-red-500">{state.error}</div>}
|
||||
{state.error && <div className="text-red-500 mt-2">{state.error}</div>}
|
||||
</div>
|
||||
<div className="modal-control-footer flex justify-end">
|
||||
<div className="flex gap-10 ">
|
||||
|
@ -7,6 +7,7 @@ import {BizError} from "@/service/types.ts";
|
||||
import {IconWarningCircle} from "@/components/icons";
|
||||
import {LoadingOutlined} from "@ant-design/icons";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import ModalWarning from "@/components/icons/ModalWarning.tsx";
|
||||
|
||||
type Props = {
|
||||
selected: any[],
|
||||
@ -57,10 +58,12 @@ export default function ButtonBatch(
|
||||
if(confirmMessage){
|
||||
modal.confirm({
|
||||
wrapClassName: 'root-modal-confirm',
|
||||
title: <span dangerouslySetInnerHTML={{__html:title || t('confirm.title')}}></span>,
|
||||
title: <ModalWarning.Title />,
|
||||
centered: true,
|
||||
icon: <span className="anticon anticon-exclamation-circle"><IconWarningCircle/></span>,
|
||||
content: confirmMessage,
|
||||
icon: <ModalWarning.Icon />,
|
||||
content: <div>
|
||||
<div>{confirmMessage}</div>
|
||||
</div>,
|
||||
onOk: onBatchProcess
|
||||
})
|
||||
}else{
|
||||
|
17
src/components/icons/ModalWarning.tsx
Normal file
17
src/components/icons/ModalWarning.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import {IconWarningCircle} from "@/components/icons/index.tsx";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
export function ModalWarningIcon({size=24}:{size?:number}) {
|
||||
return <IconWarningCircle
|
||||
style={{fontSize: size, color: 'rgba(250, 173, 20, 1)'}}/>
|
||||
}
|
||||
export function ModalWarningTitle(){
|
||||
const {t} = useTranslation()
|
||||
return <span className="text-base">{t('modal.warning')}</span>
|
||||
}
|
||||
const ModalWarning = {
|
||||
Icon: ModalWarningIcon,
|
||||
Title: ModalWarningTitle
|
||||
}
|
||||
export default ModalWarning
|
@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import RecycleIndex from "@/pages/recycle";
|
||||
|
||||
type IconProps = { style?: React.CSSProperties; className?: string; }
|
||||
|
||||
@ -59,6 +60,32 @@ export const IconDownload = ({style, className}: IconProps) => (
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconOrderFill = ({style, className}: IconProps) => (
|
||||
<svg
|
||||
className={`svg-icon ${className || ''} icon-download`} style={style}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" width="1em" height="1em" viewBox="0 0 22 22"
|
||||
>
|
||||
<path
|
||||
d="M1.15485 4.02687C0.839494 4.03116 0.539152 4.16202 0.322478 4.39157C0.105803 4.61897 -0.0100427 4.92575 0.000683739 5.2411C-0.00146155 5.88898 0.506973 6.42531 1.15485 6.45534H3.46533C3.78069 6.45105 4.08103 6.32019 4.29771 6.09064C4.51438 5.86324 4.63023 5.55646 4.6195 5.2411C4.62165 4.59323 4.11321 4.0569 3.46533 4.02687H1.15485ZM1.15485 9.98006C0.839494 9.98435 0.539152 10.1152 0.322478 10.3448C0.105803 10.5722 -0.0100427 10.8789 0.000683739 11.1943C-0.00146155 11.8422 0.506973 12.3785 1.15485 12.4085H3.46533C3.78069 12.4042 4.08103 12.2734 4.29771 12.0438C4.51438 11.8164 4.63023 11.5097 4.6195 11.1943C4.62165 10.5464 4.11321 10.0101 3.46533 9.98006H1.15485ZM1.15485 15.9912C0.532717 16.0555 0.0628972 16.5811 0.0628973 17.2054C0.0628973 17.8297 0.534862 18.3531 1.15485 18.4196H3.46533C4.08747 18.3531 4.55729 17.8297 4.55729 17.2054C4.55729 16.5811 4.08532 16.0577 3.46533 15.9912H1.15485ZM20.8186 0.0216038H3.40741C3.09205 0.0258944 2.79171 0.156757 2.57504 0.386304C2.35836 0.613705 2.24252 0.920482 2.25324 1.23584V2.81263H3.40741C4.69244 2.84481 5.71789 3.896 5.71789 5.18104C5.72862 5.80317 5.49049 6.40171 5.05714 6.84578C4.62379 7.28986 4.02955 7.543 3.40956 7.54944H2.25539V8.76368H3.40956C4.69459 8.79585 5.72004 9.84705 5.72004 11.1321C5.73076 11.7542 5.49264 12.3528 5.05929 12.7968C4.62594 13.2409 4.03169 13.494 3.4117 13.5005H2.25753V14.7147H3.4117C4.69244 14.749 5.71789 15.8002 5.71789 17.0853C5.72862 17.7074 5.49049 18.3059 5.05714 18.75C4.62379 19.1941 4.02955 19.4472 3.40956 19.4537H2.25539V20.7752C2.25324 21.4231 2.76168 21.9594 3.40956 21.9894H20.8208C21.1361 21.9851 21.4365 21.8543 21.6531 21.6247C21.8698 21.3973 21.9857 21.0905 21.9749 20.7752V1.06636C21.9234 0.467825 21.4172 0.0130226 20.8186 0.0216038ZM17.7015 8.87738C18.1799 8.87738 18.5682 9.26567 18.5682 9.74407C18.5682 10.2225 18.1799 10.6108 17.7015 10.6108H15.1014V11.6512H17.6994C18.1778 11.6512 18.5661 12.0395 18.5661 12.5179C18.5661 12.9963 18.1778 13.3846 17.6994 13.3846H15.1014V16.2743C15.1143 16.5919 14.9512 16.89 14.6788 17.0509C14.4063 17.214 14.0652 17.214 13.7928 17.0509C13.5203 16.8879 13.3573 16.5897 13.3702 16.2743V13.3954H10.7701C10.2917 13.3954 9.90336 13.0071 9.90336 12.5287C9.90336 12.0503 10.2917 11.662 10.7701 11.662H13.368V10.6215H10.7701C10.2917 10.6215 9.90336 10.2332 9.90336 9.7548C9.90336 9.2764 10.2917 8.8881 10.7701 8.8881H12.0401L10.1372 6.98094C9.90765 6.76641 9.81111 6.44461 9.88834 6.13998C9.96557 5.83535 10.2037 5.59722 10.5083 5.51999C10.813 5.44276 11.1348 5.53715 11.3493 5.7667L14.0052 8.42472L14.0631 8.48264H14.4106L14.4685 8.42472L17.1244 5.7667C17.4634 5.45349 17.989 5.46207 18.3151 5.7903C18.6411 6.11638 18.6497 6.64198 18.3365 6.98094L16.4336 8.87738H17.7015Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconRecycleFill = ({style, className}: IconProps) => (
|
||||
<svg
|
||||
className={`svg-icon ${className || ''} icon-download`} style={style}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" width="1em" height="1em" viewBox="0 0 22 22"
|
||||
>
|
||||
<path
|
||||
d="M21.1832 3.92852H17.262V2.35739C17.2638 1.73447 17.019 1.1363 16.5814 0.694283C16.1437 0.252265 15.549 0.00255176 14.9279 0H7.08268C6.45926 0.000364354 5.86146 0.248804 5.42051 0.690782C4.97956 1.13276 4.73149 1.73216 4.73076 2.35739V3.92852H0.816386C0.675283 3.92266 0.535222 3.95514 0.411003 4.02252C0.286784 4.0899 0.183019 4.18968 0.110673 4.31132C0.0382409 4.43311 0 4.57228 0 4.71409C0 4.8559 0.0382409 4.99507 0.110673 5.11686C0.183019 5.23849 0.286784 5.33828 0.411003 5.40566C0.535222 5.47304 0.675283 5.50551 0.816386 5.49966H21.1832C21.2888 5.50409 21.3943 5.48706 21.4932 5.44958C21.5921 5.4121 21.6824 5.35495 21.7587 5.28157C21.835 5.20818 21.8957 5.12008 21.9371 5.02256C21.9786 4.92504 22 4.82011 22 4.71409C22 4.60807 21.9786 4.50314 21.9371 4.40562C21.8957 4.3081 21.835 4.21999 21.7587 4.14661C21.6824 4.07323 21.5921 4.01608 21.4932 3.9786C21.3943 3.94112 21.2888 3.92409 21.1832 3.92852ZM18.0542 6.87801H3.95091C3.74286 6.87801 3.54331 6.9608 3.39607 7.10822C3.24883 7.25563 3.16593 7.45561 3.16556 7.66427V19.644C3.16665 20.2687 3.41469 20.8676 3.85531 21.3092C4.29592 21.7509 4.89317 21.9993 5.51611 22H16.4849C17.1078 21.9993 17.7051 21.7509 18.1457 21.3092C18.5863 20.8676 18.8343 20.2687 18.8354 19.644V7.69456C18.8366 7.4827 18.7554 7.27873 18.609 7.12599C18.4626 6.97326 18.2626 6.8838 18.0514 6.87664L18.0542 6.87801ZM5.66576 16.0845C5.60496 15.9767 5.56272 15.8594 5.54082 15.7375C5.53845 15.6251 5.56797 15.5143 5.62595 15.418C5.62595 15.418 6.26439 14.3082 6.27262 14.2972C6.28086 14.2862 5.66988 13.9309 5.66988 13.9309L7.75682 13.4614L8.65338 15.6976L8.06299 15.3561L7.20351 16.7468C7.04274 17.0229 6.94126 17.3297 6.90557 17.6474C6.87595 17.9301 6.9234 18.2157 7.04287 18.4736L5.66576 16.0845ZM7.70465 18.9073C7.60718 18.8801 7.51453 18.8378 7.43005 18.782C7.30763 18.6826 7.21302 18.5531 7.15545 18.4061C7.04694 18.1357 7.00915 17.8419 7.04568 17.5527C7.0822 17.2635 7.19183 16.9885 7.36415 16.7537H10.1307V18.9114H7.70465V18.9073ZM9.61309 12.8307L7.7527 11.7525L8.96642 9.63886C9.03647 9.56856 9.11766 9.5104 9.20669 9.46673C9.35308 9.41038 9.51138 9.39237 9.66664 9.41441C9.98643 9.46441 10.287 9.59945 10.5371 9.80547C10.7304 9.97913 10.8864 10.1904 10.9957 10.4265L9.61309 12.8307ZM11.0836 10.2805C10.9264 10.0033 10.7126 9.76262 10.4561 9.57414C10.2286 9.40715 9.96083 9.30401 9.68037 9.27533H12.4263C12.5495 9.2769 12.6716 9.29924 12.7874 9.34143C12.8859 9.39611 12.9669 9.47778 13.0208 9.57689C13.0208 9.57689 13.6593 10.6867 13.6634 10.7005C13.6634 10.6922 14.2799 10.359 14.2799 10.359L13.6346 12.4093L11.2524 12.0651L11.8469 11.7236L11.0836 10.2805ZM15.0295 18.5286C14.9669 18.6352 14.8871 18.7306 14.7934 18.8109C14.6971 18.8696 14.5861 18.8992 14.4735 18.8963H13.1829C13.1677 18.8963 13.1691 19.6027 13.1691 19.6027L11.7151 18.0205L13.2007 16.1203V16.8088L14.8304 16.8597C15.1488 16.8615 15.4641 16.7963 15.7558 16.6683C16.0132 16.554 16.2351 16.3723 16.3984 16.1423L15.0254 18.5273L15.0295 18.5286ZM16.2844 16.0666C16.0817 16.3194 15.8146 16.5126 15.5114 16.6256C15.2657 16.707 15.0058 16.737 14.7481 16.7138L13.3627 14.3137L15.2217 13.2341L16.4354 15.3464C16.4721 15.47 16.477 15.6008 16.4497 15.7267C16.4223 15.8527 16.3636 15.9696 16.2789 16.0666H16.2844Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconDownloadOutline = ({style, className}: IconProps)=>(
|
||||
<svg
|
||||
className={`svg-icon ${className || ''} icon-download`} style={style} xmlns="http://www.w3.org/2000/svg"
|
||||
|
21
src/components/message/confirm.tsx
Normal file
21
src/components/message/confirm.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import {Popconfirm} from "antd";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import ModalWarning from "@/components/icons/ModalWarning.tsx";
|
||||
|
||||
export function DeleteItemPopoverConfirm({children,description,onConfirm}: {
|
||||
onConfirm: ((e?: React.MouseEvent<HTMLElement>) => void) | undefined
|
||||
description?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
}){
|
||||
const {t} = useTranslation()
|
||||
return <Popconfirm
|
||||
rootClassName={'popconfirm-main'}
|
||||
placement={'left'}
|
||||
arrow={false}
|
||||
icon={<ModalWarning.Icon />}
|
||||
title={<ModalWarning.Title />}
|
||||
description={<div dangerouslySetInnerHTML={{__html:description || t('modal.delete_item_confirm')}}></div>}
|
||||
onConfirm={onConfirm}
|
||||
>{children}</Popconfirm>
|
||||
}
|
@ -16,6 +16,7 @@ import {VideoStatus} from "@/service/api/video.ts";
|
||||
import {formatTime} from "@/util/strings.ts";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {saveAs} from "file-saver";
|
||||
import {DeleteItemPopoverConfirm} from "@/components/message/confirm.tsx";
|
||||
|
||||
type Props = {
|
||||
video: VideoInfo | LiveVideoInfo,
|
||||
@ -34,7 +35,7 @@ type Props = {
|
||||
onRegenerate?: () => void;
|
||||
hideCheckBox?: boolean;
|
||||
onItemClick?: () => void;
|
||||
onRemove?: (action?:'delete' | 'rollback') => void;
|
||||
onRemove?: (action?: 'delete' | 'rollback') => void;
|
||||
removeIcon?: React.ReactNode;
|
||||
id: number;
|
||||
className?: string;
|
||||
@ -43,10 +44,10 @@ type Props = {
|
||||
|
||||
export const VideoListItem = (
|
||||
{
|
||||
id, video, onRemove,removeIcon, checked,playing,
|
||||
onCheckedChange, onEdit, active, editable,downloadUrl,
|
||||
className, sortable, type, index,onItemClick,
|
||||
additionOperationAfter,additionOperationBefore,onRegenerate,hideCheckBox
|
||||
id, video, onRemove, removeIcon, checked, playing,
|
||||
onCheckedChange, onEdit, active, editable, downloadUrl,
|
||||
className, sortable, type, index, onItemClick,
|
||||
additionOperationAfter, additionOperationBefore, onRegenerate, hideCheckBox
|
||||
}: Props) => {
|
||||
const {
|
||||
attributes, listeners,
|
||||
@ -60,55 +61,59 @@ export const VideoListItem = (
|
||||
}, [checked])
|
||||
|
||||
const generating = (type == 'create' && video.status == VideoStatus.Generating)
|
||||
const failed = (type == 'create' && (video.status != VideoStatus.Generating && video.status != VideoStatus.Generated) )
|
||||
const handleDownloadVideo = ()=>{
|
||||
if(downloadUrl && video.status == VideoStatus.Generated){
|
||||
const failed = (type == 'create' && (video.status != VideoStatus.Generating && video.status != VideoStatus.Generated))
|
||||
const handleDownloadVideo = () => {
|
||||
if (downloadUrl && video.status == VideoStatus.Generated) {
|
||||
const ext = downloadUrl.substring(downloadUrl.lastIndexOf('.'))
|
||||
saveAs(downloadUrl,`${video.title || video.video_title}${ext}`)
|
||||
saveAs(downloadUrl, `${video.title || video.video_title}${ext}`)
|
||||
}
|
||||
}
|
||||
return <div
|
||||
className={`video-item ${className}`}
|
||||
ref={setNodeRef} style={{transform: `translateY(${transform?.y || 0}px)`,}}
|
||||
>
|
||||
<div className={`list-row ${generating ? ' status-generating' : ''} ${failed ? 'status-generate-failed' : ''} ${active?'playing':''}`}>
|
||||
<div
|
||||
className={`list-row ${generating ? ' status-generating' : ''} ${failed ? 'status-generate-failed' : ''} ${active ? 'playing' : ''}`}>
|
||||
<div
|
||||
className="col number"
|
||||
{... (sortable && !generating?listeners:{})}
|
||||
{... (sortable && !generating?attributes:{})}
|
||||
{...(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 rounded inset-0 bg-black/40 backdrop-blur-[1px] text-white flex items-center justify-center'}>
|
||||
<div className="text-center">
|
||||
<IconGenerating className="inline-block text-xl" />
|
||||
<div className="text-xs">{t('video.generating')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={'absolute rounded inset-0 bg-black/40 backdrop-blur-[1px] text-white flex items-center justify-center'}>
|
||||
<div className="text-center">
|
||||
<IconGenerating className="inline-block text-xl"/>
|
||||
<div className="text-xs">{t('video.generating')}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{failed &&
|
||||
<div className={'absolute rounded inset-0 bg-black/40 backdrop-blur-[1px] text-white flex items-center justify-center'}>
|
||||
<div className="text-center">
|
||||
<IconGenerateFailed className="inline-block text-xl" />
|
||||
<div className="text-xs">{t('video.generate_failed')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={'absolute rounded inset-0 bg-black/40 backdrop-blur-[1px] text-white flex items-center justify-center'}>
|
||||
<div className="text-center">
|
||||
<IconGenerateFailed className="inline-block text-xl"/>
|
||||
<div className="text-xs">{t('video.generate_failed')}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{/* && active*/}
|
||||
{!generating && !failed && playing && <div className={'absolute rounded inset-0 backdrop-blur-[1px] bg-black/40 text-white flex items-center justify-center'}>
|
||||
<div className="text-center">
|
||||
<IconPlaying className="inline-block text-xl" />
|
||||
<div className="text-xs">{t('video.playing')}</div>
|
||||
</div>
|
||||
</div>}
|
||||
{!generating && !failed && playing && <div
|
||||
className={'absolute rounded inset-0 backdrop-blur-[1px] bg-black/40 text-white flex items-center justify-center'}>
|
||||
<div className="text-center">
|
||||
<IconPlaying className="inline-block text-xl"/>
|
||||
<div className="text-xs">{t('video.playing')}</div>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="col title"
|
||||
{... (sortable && !generating?listeners:{})}
|
||||
{... (sortable && !generating?attributes:{})}
|
||||
{...(sortable && !generating ? listeners : {})}
|
||||
{...(sortable && !generating ? attributes : {})}
|
||||
>
|
||||
<div className="line-clamp-2">
|
||||
{video.title || video.video_title}
|
||||
@ -116,9 +121,9 @@ export const VideoListItem = (
|
||||
</div>
|
||||
<div
|
||||
className="col generated-time"
|
||||
{... (sortable && !generating?listeners:{})}
|
||||
{... (sortable && !generating?attributes:{})}
|
||||
>{video.ctime ? formatTime(video.ctime,'min') : '-'}</div>
|
||||
{...(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">*/}
|
||||
@ -126,49 +131,52 @@ export const VideoListItem = (
|
||||
{/* </button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>)}*/}
|
||||
<div className={"flex items-center justify-start gap-6"}>
|
||||
{downloadUrl && video.status == VideoStatus.Generated &&
|
||||
<button className="hover:text-blue-500" onClick={e=>{
|
||||
<button className="hover:text-blue-500" onClick={e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleDownloadVideo?.()
|
||||
handleDownloadVideo?.()
|
||||
}} style={{fontSize: '1.1em'}}>
|
||||
<IconDownloadOutline/>
|
||||
</button>}
|
||||
{additionOperationBefore}
|
||||
{editable && !generating && <>
|
||||
{onEdit &&
|
||||
<button className="hover:text-blue-500" onClick={e=>{
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onEdit?.()
|
||||
}} style={{fontSize: '1.1em'}}>
|
||||
<IconEdit/>
|
||||
</button>}
|
||||
{onRegenerate &&
|
||||
<button className="text-red-400 hover:text-blue-500" onClick={e=>{
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onRegenerate?.()
|
||||
}} style={{fontSize: '1.1em'}}>
|
||||
<IconRegenerate/>
|
||||
</button>}
|
||||
{onEdit && <button
|
||||
className="hover:text-blue-500" onClick={e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onEdit?.()
|
||||
}} style={{fontSize: '1.1em'}}>
|
||||
<IconEdit/>
|
||||
</button>}
|
||||
{onRegenerate && <button
|
||||
className="text-red-400 hover:text-blue-500" onClick={e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
onRegenerate?.()
|
||||
}} style={{fontSize: '1.1em'}}>
|
||||
<IconRegenerate/>
|
||||
</button>}
|
||||
|
||||
{onRemove && <Popconfirm
|
||||
rootClassName={'popconfirm-main'}
|
||||
placement={'left'}
|
||||
arrow={false}
|
||||
icon={<IconWarningCircle/>}
|
||||
title={t('video.delete_confirm_title')}
|
||||
// description={`删除后需从重新${type == 'create' ? '生成' : '推流'}`}
|
||||
onConfirm={() => onRemove(failed ? 'rollback' : 'delete')}
|
||||
><button className="hover:text-blue-500">{removeIcon?removeIcon:(failed?<IconRollbackCircle />:<IconDelete/>)}</button></Popconfirm>}
|
||||
{hideCheckBox ? <span className={"inline-block w-[18px] h-1"}></span> : <Checkbox checked={state.checked} onChange={() => {
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(!state.checked)
|
||||
} else {
|
||||
setState({checked: !state.checked})
|
||||
}
|
||||
}} />}
|
||||
</>}
|
||||
{onRemove && <DeleteItemPopoverConfirm
|
||||
description={failed ? t('video.rollback_confirm_title') : undefined}
|
||||
onConfirm={() => onRemove(failed ? 'rollback' : 'delete')}>
|
||||
<span className="icon-btn">
|
||||
<button className="hover:text-blue-500">
|
||||
{removeIcon ? removeIcon : (failed ?
|
||||
<IconRollbackCircle/> :
|
||||
<IconDelete/>)}
|
||||
</button>
|
||||
</span>
|
||||
</DeleteItemPopoverConfirm>}
|
||||
{hideCheckBox ? <span className={"inline-block w-[18px] h-1"}></span> :
|
||||
<Checkbox checked={state.checked} onChange={() => {
|
||||
if (onCheckedChange) {
|
||||
onCheckedChange(!state.checked)
|
||||
} else {
|
||||
setState({checked: !state.checked})
|
||||
}
|
||||
}}/>}
|
||||
</>}
|
||||
{additionOperationAfter}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -53,12 +53,13 @@
|
||||
"save_success": "Save success"
|
||||
},
|
||||
"modal": {
|
||||
"delete_item_confirm": "Are you sure you want to delete this item?",
|
||||
"hot_news": {
|
||||
"edit_auto": "Manual Fill",
|
||||
"edit_manual": "Manual Fill",
|
||||
"empty_notice_message": "The manually edited \"Hot News\" have not been completed.<br/>Please fill in all, or turn on smart fill",
|
||||
"empty_notice_title": "Notice",
|
||||
"title": "Hot news"
|
||||
"edit_auto": "Customize",
|
||||
"edit_manual": "Customize",
|
||||
"empty_notice_message": "Some live news items are incomplete. Please update them or disable the customization feature.",
|
||||
"empty_notice_title": "Warning",
|
||||
"title": "Live news"
|
||||
},
|
||||
"push_article": {
|
||||
"action_all": "Still generating",
|
||||
@ -68,7 +69,7 @@
|
||||
"content_error_single": "<span class=\"modal-count-normal\">{{count}}</span> news is selected, and the metahuman content is too short in this news. Do you want to transfer it to a video?",
|
||||
"content_normal": "<span class=\"modal-count-normal\">{{count}}</span> news are selected, Do you want to transfer them into videos?",
|
||||
"content_normal_single": "<span class=\"modal-count-normal\">{{count}}</span> news is selected. Do you want to transfer it to a video?",
|
||||
"empty_notice_title": "Notice",
|
||||
"empty_notice_title": "Warning",
|
||||
"error_title": "Abnormal news"
|
||||
},
|
||||
"warning": "Warning"
|
||||
@ -92,7 +93,7 @@
|
||||
"edit_delete_group": "Delete Group",
|
||||
"edit_delete_group_confirm": "Are you sure you want to delete the group?",
|
||||
"edit_digital_text": "Metahuman Material",
|
||||
"edit_empty_group_content": "Please other media material",
|
||||
"edit_empty_group_content": "To generate a fully Metahuman video, ensure that no other media materials are included. For all other cases, both text and images must be provided in the media material section.",
|
||||
"edit_empty_human_content": "Please enter meta human material",
|
||||
"edit_form_search": "Please enter title keywords",
|
||||
"edit_generate_again": "Regenerate",
|
||||
@ -127,7 +128,7 @@
|
||||
"pushed": "Editing",
|
||||
"search_key_title": "Please enter title keywords",
|
||||
"source": "Source",
|
||||
"title": "Content",
|
||||
"title": "Title",
|
||||
"title_image_count": "No. of images",
|
||||
"title_operate": "",
|
||||
"title_time": "Time stamp",
|
||||
@ -146,6 +147,11 @@
|
||||
},
|
||||
"text": "Orders"
|
||||
},
|
||||
"recycle": {
|
||||
"remove_forever": "Remove Forever",
|
||||
"restore_video": "Restore"
|
||||
},
|
||||
"save_operation": "Save",
|
||||
"select": {
|
||||
"pushed": "Pushed: {{count}}",
|
||||
"select_all": "Select all",
|
||||
@ -163,6 +169,7 @@
|
||||
"past_4_hour": "Past 4 hour",
|
||||
"past_hour": "Past 1 hour"
|
||||
},
|
||||
"title": "Title",
|
||||
"upload": {
|
||||
"delete_confirm": "Are you sure delete the picture?",
|
||||
"upload_failed": "Upload failed",
|
||||
@ -188,6 +195,7 @@
|
||||
"push_failed": "some video streaming failed!",
|
||||
"push_success": "Streaming success,please goto \"Streaming\"!",
|
||||
"push_to_live": "Streaming",
|
||||
"rollback_confirm_title": "Are you sure you want to revert this video?",
|
||||
"sort_modify_confirm": "Are you change video sequence?",
|
||||
"sort_modify_failed": "Video sequence change failed",
|
||||
"sort_modify_live_success": "Video sequence changed",
|
||||
|
@ -53,6 +53,7 @@
|
||||
"save_success": "保存成功"
|
||||
},
|
||||
"modal": {
|
||||
"delete_item_confirm": "您确定要删除吗?",
|
||||
"hot_news": {
|
||||
"edit_auto": "自定义",
|
||||
"edit_manual": "自定义",
|
||||
@ -80,7 +81,7 @@
|
||||
"materials": "新闻素材"
|
||||
},
|
||||
"news": {
|
||||
"delete_confirm": "你确定要删除吗?",
|
||||
"delete_confirm": "您确定要删除吗?",
|
||||
"delete_confirm_count": "你确定要删除选择的 {{count}} 条新闻吗?",
|
||||
"delete_description": "删除后需从新闻素材中重新选择",
|
||||
"delete_description_count": "删除后需从新闻素材中重新选择",
|
||||
@ -146,6 +147,11 @@
|
||||
},
|
||||
"text": "订单记录"
|
||||
},
|
||||
"recycle": {
|
||||
"remove_forever": "彻底删除",
|
||||
"restore_video": "还原视频"
|
||||
},
|
||||
"save_operation": "保存操作",
|
||||
"select": {
|
||||
"pushed": "已推送: {{count}} 条",
|
||||
"select_all": "全选",
|
||||
@ -163,6 +169,7 @@
|
||||
"past_4_hour": "四小时内",
|
||||
"past_hour": "一小时内"
|
||||
},
|
||||
"title": "标题",
|
||||
"upload": {
|
||||
"delete_confirm": "请确认删除此图片?",
|
||||
"upload_failed": "上传图片失败,请重试",
|
||||
@ -188,6 +195,7 @@
|
||||
"push_failed": "选择视频中有部分视频还在生成中无法推送,推流成功视频前往数字人直播间页面查看!",
|
||||
"push_success": "一键推流成功,已推流至数字人直播间,请前往数字人直播间页面查看!",
|
||||
"push_to_live": "一键推流",
|
||||
"rollback_confirm_title": "您确定要回退此视频吗?",
|
||||
"sort_modify_confirm": "是否采纳移动视频位置操作?",
|
||||
"sort_modify_failed": "调整视频顺序失败,请重试!",
|
||||
"sort_modify_live_success": "已完成直播队列的修改",
|
||||
|
@ -164,17 +164,20 @@ export default function LiveIndex() {
|
||||
// loadList()
|
||||
// }).catch(showErrorToast)
|
||||
}
|
||||
const resetState = (editable: boolean)=>{
|
||||
setEditable(editable)
|
||||
setCheckedIdArray([])
|
||||
setRollbackIds(()=>[])
|
||||
setDelIds(()=>[])
|
||||
setState({checkedAll: false})
|
||||
}
|
||||
// 状态:锁定->解锁
|
||||
const handleSetEditable = ()=>{
|
||||
setEditable(true)
|
||||
setCheckedIdArray([])
|
||||
setState({checkedAll: false})
|
||||
resetState(true)
|
||||
}
|
||||
//
|
||||
const handleCancel = ()=>{
|
||||
setEditable(false)
|
||||
setRollbackIds(()=>[])
|
||||
setDelIds(()=>[])
|
||||
resetState(false)
|
||||
}
|
||||
const handleRollback = (v:LiveVideoInfo)=>{
|
||||
setRollbackIds(_=>[v.id,..._])
|
||||
@ -201,12 +204,8 @@ export default function LiveIndex() {
|
||||
console.log(e)
|
||||
showToast(t('message.save_failed'), 'error')
|
||||
}finally {
|
||||
setCheckedIdArray([])
|
||||
setState({checkedAll: false})
|
||||
setRollbackIds(()=>[])
|
||||
setDelIds(()=>[])
|
||||
loadList()
|
||||
setEditable(false)
|
||||
resetState(false)
|
||||
}
|
||||
}
|
||||
const handleAllCheckedChange = () => {
|
||||
@ -282,8 +281,8 @@ export default function LiveIndex() {
|
||||
<div className="flex items-center">
|
||||
<div className={'flex items-center text-gray-400 cursor-pointer select-none'}>
|
||||
{editable ? (<Space size={15}>
|
||||
<button className={styles.btnDefault} onClick={handleCancel}>取消</button>
|
||||
<button className={styles.btn} onClick={handleConfirm}>保存操作</button>
|
||||
<button className={styles.btnDefault} onClick={handleCancel}>{t('cancel')}</button>
|
||||
<button className={styles.btn} onClick={handleConfirm}>{t('save_operation')}</button>
|
||||
</Space>):(<div className="flex items-center " onClick={handleSetEditable}>
|
||||
{t('live.edit_locked')}
|
||||
<span className="ml-2 text-sm"><IconLocked/></span>
|
||||
|
@ -5,6 +5,7 @@ import {IconDelete, IconWarningCircle} from "@/components/icons";
|
||||
import {deleteByIds} from "@/service/api/article.ts";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {divide} from "lodash";
|
||||
import ModalWarning from "@/components/icons/ModalWarning.tsx";
|
||||
|
||||
export default function ButtonDeleteBatch(props: { ids: Id[];onSuccess?: () => void; }) {
|
||||
const {t} = useTranslation()
|
||||
@ -29,9 +30,12 @@ export default function ButtonDeleteBatch(props: { ids: Id[];onSuccess?: () => v
|
||||
}
|
||||
modal.confirm({
|
||||
wrapClassName:'root-modal-confirm',
|
||||
icon: <span className="anticon anticon-exclamation-circle"><IconWarningCircle/></span>,
|
||||
title: t(props.ids.length == 1 ?'news.delete_confirm':'news.delete_confirm_count',{count:props.ids.length}),
|
||||
content: <span dangerouslySetInnerHTML={{__html:props.ids.length == 1 ?t('news.delete_description') :t('news.delete_description_count')}}></span>,
|
||||
icon: <ModalWarning.Icon />,
|
||||
title: <ModalWarning.Title />,
|
||||
content: <div>
|
||||
<div>{t(props.ids.length == 1 ?'news.delete_confirm':'news.delete_confirm_count',{count:props.ids.length})}</div>
|
||||
<div><span dangerouslySetInnerHTML={{__html:props.ids.length == 1 ?t('news.delete_description') :t('news.delete_description_count')}}></span></div>
|
||||
</div>,
|
||||
onOk: handlePush,
|
||||
centered: true
|
||||
})
|
||||
|
@ -6,7 +6,8 @@ import {useSetState} from "ahooks";
|
||||
|
||||
import {showToast} from "@/components/message.ts";
|
||||
import {push2video} from "@/service/api/article.ts";
|
||||
import {IconArrowRight, IconWarningCircle} from "@/components/icons";
|
||||
import {IconArrowRight} from "@/components/icons";
|
||||
import ModalWarning from "@/components/icons/ModalWarning.tsx";
|
||||
|
||||
export enum ProcessResult {
|
||||
All,
|
||||
@ -133,8 +134,7 @@ export default function ButtonPush2Video(props: PushVideoProps) {
|
||||
width={440}
|
||||
>
|
||||
<div className="modal-title flex items-center">
|
||||
<div className="anticon anticon-exclamation-circle text-red-400 w-10"><IconWarningCircle
|
||||
style={{fontSize: 24, color: 'rgba(250, 173, 20, 1)'}}/></div>
|
||||
<div className="anticon anticon-exclamation-circle text-red-400 w-10"><ModalWarning.Icon/></div>
|
||||
<div className="text-base">{t('modal.warning')}</div>
|
||||
</div>
|
||||
<div className="confirm-message-wrapper flex mt-2">
|
||||
|
@ -16,6 +16,8 @@ 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";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {ModalWarningTitle,ModalWarningIcon} from "@/components/icons/ModalWarning.tsx";
|
||||
import {DeleteItemPopoverConfirm} from "@/components/message/confirm.tsx";
|
||||
|
||||
const FilterCache: Partial<ApiArticleSearchParams> = {
|
||||
tags: [],
|
||||
@ -147,19 +149,9 @@ export default function NewEdit() {
|
||||
</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={t('news.delete_confirm')}
|
||||
description={<span dangerouslySetInnerHTML={{__html:t('news.delete_description')}}></span>}
|
||||
onConfirm={() => {
|
||||
handleDelete(item.id)
|
||||
}}
|
||||
>
|
||||
<DeleteItemPopoverConfirm onConfirm={() => {handleDelete(item.id)}}>
|
||||
<span className="icon-btn"><IconDelete/></span>
|
||||
</Popconfirm>
|
||||
</DeleteItemPopoverConfirm>
|
||||
<Checkbox checked={checked}
|
||||
onChange={e => handleItemChecked(e.target.checked, item)}/>
|
||||
</div>
|
||||
|
@ -5,19 +5,10 @@ import styles from "@/pages/news/components/style.module.scss";
|
||||
|
||||
import {formatDurationToTime, formatTime} from "@/util/strings.ts";
|
||||
import {IconDelete, IconEdit, IconWarningCircle} from "@/components/icons";
|
||||
import {Popconfirm} from "antd";
|
||||
import {useSetState} from "ahooks";
|
||||
import {Empty, Popconfirm} from "antd";
|
||||
import {useRequest, useSetState} from "ahooks";
|
||||
import {getList} from "@/service/api/order.ts";
|
||||
|
||||
const mockList: OrderInfo[] = Array(10).fill(0).map((_, id) => (
|
||||
{
|
||||
id: id + 1,
|
||||
cover: "https://staticplus.gachafun.com/fengmang/imgs/20241216/3fa3da5027cce22acb03283e8d688749.jpg",
|
||||
title: `我国成功发射卫星互联网低轨卫星 ${id}`,
|
||||
order_time: "2025-03-25 11:11:11",
|
||||
consume_time: 60,
|
||||
operator: "张三"
|
||||
}
|
||||
))
|
||||
|
||||
function OrderIndex() {
|
||||
const {t} = useTranslation()
|
||||
@ -25,16 +16,16 @@ function OrderIndex() {
|
||||
pagination: {page: 1, limit: 12},
|
||||
time_flag: 1,
|
||||
})
|
||||
const [dataList, setDataList] = useState<OrderInfo[]>([...mockList])
|
||||
const [state, setState] = useSetState({
|
||||
loading: false,
|
||||
leftTime: 2000,
|
||||
const {data} = useRequest(()=>getList(params),{
|
||||
refreshDeps:[params],
|
||||
})
|
||||
|
||||
return <div className="container pb-5 page-order-index">
|
||||
<SearchPanel
|
||||
hideNewsSource={true} defaultParams={params} onSearch={setParams}
|
||||
rightRender={<div>{t('order.left_time')}: <span className={`${state.leftTime < 3600 ? 'text-red-600':''}`}>{formatDurationToTime(state.leftTime)}</span> </div>}
|
||||
rightRender={<div>{t('order.left_time')}: <span
|
||||
className={`${!data?.remaining_duration || Number(data?.remaining_duration) < 3600 ? 'text-red-600' : ''}`}>{formatDurationToTime(data?.remaining_duration)}</span>
|
||||
</div>}
|
||||
/>
|
||||
<div className=" mt-2">
|
||||
<div className={styles.newListTable}>
|
||||
@ -47,15 +38,18 @@ function OrderIndex() {
|
||||
<div className="col w-[180px]">{t('order.list.operator')}</div>
|
||||
</div>
|
||||
<div className="data-list-container">
|
||||
{dataList?.map((item, i) => {
|
||||
{data?.list.length === 0 && <div style={{marginTop:50}}>
|
||||
<Empty />
|
||||
</div>}
|
||||
{data?.list.map((item, i) => {
|
||||
return <div key={i} className="row flex">
|
||||
<div className="col w-[160px] text-center">
|
||||
<div className="flex-1">
|
||||
<div className="text-base">{item.id}</div>
|
||||
<div className="text-base">{item.order_id}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col w-[180px]">
|
||||
<img src={item.cover} className="rounded w-[140px] h-[60px]" alt=""/>
|
||||
<img src={item.img_url} className="rounded w-[140px] h-[60px]" alt=""/>
|
||||
</div>
|
||||
<div className="col flex-1">
|
||||
<div className="text-sm line-clamp-2">{item.title}</div>
|
||||
@ -65,7 +59,7 @@ function OrderIndex() {
|
||||
</div>
|
||||
<div className="col w-[180px]">
|
||||
<div
|
||||
className="text-sm">{formatTime(item.consume_time, 'YYYY-MM-DD HH:mm')}</div>
|
||||
className="text-sm">{formatTime(item.consumption_duration, 'YYYY-MM-DD HH:mm')}</div>
|
||||
</div>
|
||||
<div className="col w-[180px]">
|
||||
<div className="text-sm">{item.operator}</div>
|
||||
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
@ -1,25 +1,25 @@
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import React, {useEffect, useMemo, useRef, useState} from "react";
|
||||
import {Checkbox, Modal, Space} from "antd";
|
||||
import {useRequest, useSetState} from "ahooks";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
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 {deleteHistories, push2room, search} from "@/service/api/video.ts";
|
||||
import {getList} from "@/service/api/live.ts";
|
||||
import VideoItem from "@/pages/recycle/components/video-item.tsx";
|
||||
import SearchForm from "@/pages/recycle/components/search-form.tsx";
|
||||
import VideoDetail from "@/pages/recycle/components/video-detail.tsx";
|
||||
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";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {showToast} from "@/components/message.ts";
|
||||
import {BizError} from "@/service/types.ts";
|
||||
import {getList as getLiveList} from "@/service/api/live.ts";
|
||||
import {getList, remove, restore} from "@/service/api/recycle.ts";
|
||||
|
||||
const DEFAULT_PAGE_LIMIT = {
|
||||
page: 1,
|
||||
limit: 12
|
||||
}
|
||||
export default function LibraryIndex() {
|
||||
export default function RecycleIndex() {
|
||||
const {t} = useTranslation()
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
|
||||
@ -31,20 +31,24 @@ export default function LibraryIndex() {
|
||||
checkedAll: false,
|
||||
loading: false,
|
||||
pushedCount: 0,
|
||||
pushedList: [-1],
|
||||
showToTop: false
|
||||
})
|
||||
const [data, setData] = useState<DataList<VideoInfo>>()
|
||||
const scrollerRef = useRef<InfiniteScrollerRef | null>(null)
|
||||
|
||||
const {loading} = useRequest(() => search(params), {
|
||||
const {loading} = useRequest(() => getList(params), {
|
||||
refreshDeps: [params],
|
||||
onSuccess: (data) => {
|
||||
setData(prev => {
|
||||
// 判断页码是否是第1页
|
||||
if (data.pagination.page == 1) return data;
|
||||
//if (data.pagination.page == 1) return data;
|
||||
return {
|
||||
list: [...(prev?.list || []), ...(data?.list || [])],
|
||||
pagination: data.pagination
|
||||
list: data?.list || [], //[...(prev?.list || []), ...(data?.list || [])],
|
||||
pagination: data.pagination || {
|
||||
page: 1,
|
||||
limit: DEFAULT_PAGE_LIMIT.limit
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -81,9 +85,9 @@ export default function LibraryIndex() {
|
||||
})
|
||||
}
|
||||
const loadPushedState = () => {
|
||||
getList().then((ret) => {
|
||||
getLiveList().then((ret) => {
|
||||
if (ret.list) {
|
||||
setState({pushedCount: ret.list.length})
|
||||
setState({pushedCount: ret.list.length, pushedList: ret.list.map(s => s.id)})
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -91,6 +95,10 @@ export default function LibraryIndex() {
|
||||
loadPushedState();
|
||||
setParams(prev => ({...prev, pagination: {page: 1, limit: DEFAULT_PAGE_LIMIT.limit}, request_time: Date.now()}))
|
||||
}
|
||||
// const pusdedCount = useMemo(() => {
|
||||
// if (state.pushedCount == 0 || !data || !data.list || data.list.length == 0) return 0;
|
||||
// return data.list.filter(s => state.pushedList.includes(s.id)).length
|
||||
// }, [state.pushedList, state.pushedCount, data])
|
||||
|
||||
useEffect(loadPushedState, [])
|
||||
|
||||
@ -109,9 +117,9 @@ export default function LibraryIndex() {
|
||||
<div className="pl-[70px]"></div>
|
||||
<div className="flex items-center">
|
||||
<Space className="text-gray-400" size={20}>
|
||||
<span>{t('select.total',{count:data?.list?.length || 0})}</span>
|
||||
<span>{t('history.pushed',{count:state.pushedCount})}</span>
|
||||
<span className={'text-blue-500'}>{t('select.selected_some',{count:checkedIdArray.length})}</span>
|
||||
<span>{t('select.total', {count: data?.list?.length || 0})}</span>
|
||||
<span>{t('history.pushed', {count: state.pushedCount})}</span>
|
||||
<span className={'text-blue-500'}>{t('select.selected_some', {count: checkedIdArray.length})}</span>
|
||||
</Space>
|
||||
<button className="hover:text-blue-300 text-gray-400 ml-4"
|
||||
onClick={() => handleAllCheckedChange(checkedIdArray.length != data?.list.length)}>
|
||||
@ -152,39 +160,39 @@ export default function LibraryIndex() {
|
||||
</div>
|
||||
</div>
|
||||
{detailVideo && <VideoDetail video={detailVideo.video} autoPlay={detailVideo.autoPlay}
|
||||
onClose={() => setDetailVideo(undefined)}/>}
|
||||
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 == 1
|
||||
? t('video.delete_description',{count:checkedIdArray.length})
|
||||
: t('video.delete_description_count',{count:checkedIdArray.length})
|
||||
}
|
||||
emptyMessage={t('video.delete_empty')}
|
||||
confirmMessage={<span dangerouslySetInnerHTML={{
|
||||
__html: checkedIdArray.length == 1
|
||||
? t('video.delete_confirm')
|
||||
: t('video.delete_confirm_count',{count:checkedIdArray.length})
|
||||
}}></span>}
|
||||
onProcess={deleteHistories}
|
||||
>{t('delete_batch')}</ButtonBatch>}
|
||||
selected={checkedIdArray}
|
||||
onSuccess={refresh}
|
||||
className='bg-gray-300 hover:bg-gray-400 text-white'
|
||||
icon={<IconDelete className=""/>}
|
||||
title={
|
||||
checkedIdArray.length == 1
|
||||
? t('video.delete_description', {count: checkedIdArray.length})
|
||||
: t('video.delete_description_count', {count: checkedIdArray.length})
|
||||
}
|
||||
emptyMessage={t('video.delete_empty')}
|
||||
confirmMessage={<span dangerouslySetInnerHTML={{
|
||||
__html: checkedIdArray.length == 1
|
||||
? t('video.delete_confirm')
|
||||
: t('video.delete_confirm_count', {count: checkedIdArray.length})
|
||||
}}></span>}
|
||||
onProcess={remove}
|
||||
>{t('recycle.remove_forever')}</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}
|
||||
emptyMessage={t('video.push_empty')}
|
||||
onError={e=>{
|
||||
showToast(String((e as BizError).data || e.message),'error')
|
||||
}}
|
||||
>{t('video.push_to_live')}</ButtonBatch>}
|
||||
selected={checkedIdArray}
|
||||
onSuccess={refresh}
|
||||
className='bg-[#4096ff] hover:bg-blue-600 text-white'
|
||||
icon={<IconArrowRight className={'text-white'}/>}
|
||||
onProcess={restore}
|
||||
emptyMessage={t('video.push_empty')}
|
||||
onError={e => {
|
||||
showToast(String((e as BizError).data || e.message), 'error')
|
||||
}}
|
||||
>{t('recycle.restore_video')}</ButtonBatch>}
|
||||
</div>
|
||||
</>)
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
import {Button, Modal} from "antd";
|
||||
import {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";
|
||||
import {IconArrowRight} from "@/components/icons";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import ModalWarning from "@/components/icons/ModalWarning.tsx";
|
||||
|
||||
|
||||
export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[];onSuccess?:()=>void; }) {
|
||||
export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[]; onSuccess?: () => void; }) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const {t} = useTranslation()
|
||||
const handlePush = () => {
|
||||
@ -15,9 +16,9 @@ export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[];on
|
||||
const vids = props.list.filter(v => v.status == VideoStatus.Generated && props.ids.includes(v.id)).map(v => v.id)
|
||||
push2room(vids).then(() => {
|
||||
props.onSuccess?.()
|
||||
if(props.ids.length == vids.length){
|
||||
if (props.ids.length == vids.length) {
|
||||
showToast(t("video.push_success"), 'success')
|
||||
}else{
|
||||
} else {
|
||||
showToast(t("video.push_failed"), 'success')
|
||||
}
|
||||
}).catch(showErrorToast).finally(() => {
|
||||
@ -25,17 +26,17 @@ export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[];on
|
||||
})
|
||||
}
|
||||
const onPushClick = () => {
|
||||
if(loading) return;
|
||||
if (loading) return;
|
||||
if (props.ids.length === 0) {
|
||||
showToast(t("video.push_empty"), 'warning')
|
||||
return
|
||||
}
|
||||
Modal.confirm({
|
||||
wrapClassName:'root-modal-confirm',
|
||||
title: '操作提示',
|
||||
icon: <span className="anticon anticon-exclamation-circle"><IconWarningCircle/></span>,
|
||||
title: <ModalWarning.Title/>,
|
||||
icon: <ModalWarning.Icon/>,
|
||||
content: t("video.push_confirm"),
|
||||
onOk: handlePush
|
||||
onOk: handlePush,
|
||||
centered: true
|
||||
})
|
||||
}
|
||||
return (
|
||||
@ -47,7 +48,7 @@ export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[];on
|
||||
onClick={onPushClick}
|
||||
>
|
||||
<span className={'text'}>{t("video.push_to_live")}</span>
|
||||
<IconArrowRight />
|
||||
<IconArrowRight/>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
@ -249,7 +249,6 @@ export default function VideoIndex() {
|
||||
playing={state.playingId == v.id}
|
||||
checked={checkedIdArray.includes(v.id)}
|
||||
className={`list-item-${index} mt-3 mb-2 list-item-state-${v.status} `}
|
||||
downloadVisible={true}
|
||||
onCheckedChange={(checked) => {
|
||||
setCheckedIdArray(idArray => {
|
||||
const newArr = checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);
|
||||
@ -263,7 +262,7 @@ export default function VideoIndex() {
|
||||
setEditId(v.article_id)
|
||||
}:undefined}
|
||||
onRegenerate={v.status != VideoStatus.Generating && v.status != VideoStatus.Generated?()=>{
|
||||
processGenerateVideo(v)
|
||||
processGenerateVideo(v).catch(console.log)
|
||||
}:undefined}
|
||||
downloadUrl={v.oss_video_mp4_url}
|
||||
hideCheckBox={v.status != VideoStatus.Generating && v.status != VideoStatus.Generated}
|
||||
@ -280,28 +279,30 @@ export default function VideoIndex() {
|
||||
</div>
|
||||
<div className="page-action">
|
||||
<ButtonToTop visible={state.showToTop} onClick={() => scrollerRef.current?.scrollToPosition(0)}/>
|
||||
{checkedIdArray.length > 0 && <ButtonBatch
|
||||
onProcess={deleteFromList}
|
||||
selected={checkedIdArray}
|
||||
emptyMessage={t('video.delete_empty')}
|
||||
title={
|
||||
checkedIdArray.length == 1 ? t('video.delete_description',{count:checkedIdArray.length}):
|
||||
t('video.delete_description_count',{count:checkedIdArray.length})
|
||||
}
|
||||
className='bg-gray-300 hover:bg-gray-400 text-white'
|
||||
confirmMessage={<span dangerouslySetInnerHTML={{
|
||||
__html:checkedIdArray.length == 1?
|
||||
t('video.delete_confirm',{count:checkedIdArray.length}):
|
||||
t('video.delete_confirm_count',{count:checkedIdArray.length})
|
||||
}}></span>}
|
||||
onSuccess={() => {
|
||||
showToast(t('delete_success'), 'success')
|
||||
loadList()
|
||||
}}
|
||||
>
|
||||
<span className="text">{t('delete_batch')}</span>
|
||||
<IconDelete/>
|
||||
</ButtonBatch>}
|
||||
{
|
||||
checkedIdArray.length > 0 && <ButtonBatch
|
||||
onProcess={deleteFromList}
|
||||
selected={checkedIdArray}
|
||||
emptyMessage={t('video.delete_empty')}
|
||||
title={
|
||||
checkedIdArray.length == 1 ? t('video.delete_description', {count: checkedIdArray.length}) :
|
||||
t('video.delete_description_count', {count: checkedIdArray.length})
|
||||
}
|
||||
className='bg-gray-300 hover:bg-gray-400 text-white'
|
||||
confirmMessage={<span dangerouslySetInnerHTML={{
|
||||
__html: checkedIdArray.length == 1 ?
|
||||
t('video.delete_confirm', {count: checkedIdArray.length}) :
|
||||
t('video.delete_confirm_count', {count: checkedIdArray.length})
|
||||
}}></span>}
|
||||
onSuccess={() => {
|
||||
showToast(t('delete_success'), 'success')
|
||||
loadList()
|
||||
}}
|
||||
>
|
||||
<span className="text">{t('delete_batch')}</span>
|
||||
<IconDelete/>
|
||||
</ButtonBatch>
|
||||
}
|
||||
<ButtonPush2Room ids={checkedIdArray} list={videoData} onSuccess={loadList}/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -12,7 +12,7 @@ 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";
|
||||
import {IconOrderFill, IconRecycleFill} from "@/components/icons";
|
||||
|
||||
|
||||
type LayoutProps = {
|
||||
@ -20,25 +20,25 @@ type LayoutProps = {
|
||||
}
|
||||
|
||||
const NavigationUserContainer = () => {
|
||||
const {t } = useTranslation()
|
||||
const {t} = useTranslation()
|
||||
const {logout, user} = useAuth()
|
||||
const navigate = useNavigate()
|
||||
const handleLogout = ()=>{
|
||||
const handleLogout = () => {
|
||||
logout().then(() => navigate('/user'))
|
||||
}
|
||||
const items: MenuProps['items'] = [
|
||||
{
|
||||
key: 'history',
|
||||
label: <div className="nav-item" onClick={() => navigate('/history')}>
|
||||
<IconVideo />
|
||||
<span className={"nav-text"}>{t('history.text')}</span>
|
||||
key: 'order',
|
||||
label: <div className="nav-item" onClick={() => navigate('/order')}>
|
||||
<IconOrderFill/>
|
||||
<span className={"nav-text"}>{t('order.text')}</span>
|
||||
</div>,
|
||||
},
|
||||
{
|
||||
key: 'order',
|
||||
label: <div className="nav-item" onClick={() => navigate('/order')}>
|
||||
<IconVideo />
|
||||
<span className={"nav-text"}>{t('order.text')}</span>
|
||||
key: 'recycle',
|
||||
label: <div className="nav-item" onClick={() => navigate('/recycle')}>
|
||||
<IconRecycleFill/>
|
||||
<span className={"nav-text"}>{t('history.text')}</span>
|
||||
</div>,
|
||||
},
|
||||
// {
|
||||
@ -57,7 +57,7 @@ const NavigationUserContainer = () => {
|
||||
{user ? <Dropdown
|
||||
rootClassName={'z-[999999] userinfo-drop-menu'}
|
||||
menu={{items}} placement="bottomRight"
|
||||
dropdownRender={(menu)=>(
|
||||
dropdownRender={(menu) => (
|
||||
<div>
|
||||
<div className="user-profile flex gap-4">
|
||||
<div className="avatar"><UserAvatar className="user-avatar"/></div>
|
||||
@ -66,11 +66,11 @@ const NavigationUserContainer = () => {
|
||||
<div>ID: {user?.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<Divider style={{margin: 0}}/>
|
||||
<div className="menu-list-container">
|
||||
{menu}
|
||||
</div>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<Divider style={{margin: 0}}/>
|
||||
<div className="logout">
|
||||
<div onClick={handleLogout}>{t('user.logout')}</div>
|
||||
</div>
|
||||
@ -94,14 +94,14 @@ export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
||||
<DashboardNavigation/>
|
||||
<div className="flex items-center">
|
||||
{(params.get('lang') == 'yes' || AppConfig.APP_LANG == 'multiple') && <div>
|
||||
{
|
||||
i18n.language == 'zh-CN'?(
|
||||
<Button className="ml-2" onClick={()=>i18n.changeLanguage('en-US')}>Change To EN</Button>
|
||||
):(
|
||||
<Button className="ml-2" onClick={()=>i18n.changeLanguage('zh-CN')}>显示中文</Button>
|
||||
)
|
||||
}
|
||||
</div>}
|
||||
{
|
||||
i18n.language == 'zh-CN' ? (
|
||||
<Button className="ml-2" onClick={() => i18n.changeLanguage('en-US')}>Change To EN</Button>
|
||||
) : (
|
||||
<Button className="ml-2" onClick={() => i18n.changeLanguage('zh-CN')}>显示中文</Button>
|
||||
)
|
||||
}
|
||||
</div>}
|
||||
<NavigationUserContainer/>
|
||||
</div>
|
||||
</div>
|
||||
@ -118,12 +118,12 @@ 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'){
|
||||
useEffect(() => {
|
||||
if (!defaultCache.firstLoadPath && loc.pathname == '/live') {
|
||||
defaultCache.firstLoadPath = loc.pathname;
|
||||
navigate('/')
|
||||
}
|
||||
},[])
|
||||
}, [])
|
||||
return <AuthGuard>
|
||||
<div className="fixed">{defaultCache.firstLoadPath}</div>
|
||||
<BaseLayout>
|
||||
|
@ -6,7 +6,7 @@ import DashboardLayout from "@/routes/layout/dashboard-layout.tsx";
|
||||
|
||||
const UserAuth = React.lazy(() => import("@/pages/user"))
|
||||
const CreateVideoIndex = React.lazy(() => import("@/pages/video"))
|
||||
const LibraryIndex = React.lazy(() => import("@/pages/library"))
|
||||
const RecycleIndex = React.lazy(() => import("../pages/recycle"))
|
||||
const LiveIndex = React.lazy(() => import("@/pages/live"))
|
||||
const NewsIndex = React.lazy(() => import("@/pages/news"))
|
||||
const NewsEdit = React.lazy(() => import("@/pages/news/edit.tsx"))
|
||||
@ -36,8 +36,8 @@ const routes: RouteObject[] = [
|
||||
element: <CreateVideoIndex/>
|
||||
},
|
||||
{
|
||||
path: 'history',
|
||||
element: <LibraryIndex/>
|
||||
path: 'recycle',
|
||||
element: <RecycleIndex/>
|
||||
},
|
||||
{
|
||||
path: 'order',
|
||||
|
11
src/service/api/order.ts
Normal file
11
src/service/api/order.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {post} from "@/service/request.ts";
|
||||
|
||||
export function getList(params: OrderSearchParam) {
|
||||
return post<{
|
||||
list: OrderInfo[];
|
||||
remaining_duration: string | number;
|
||||
}>('/order/list', {
|
||||
page_num: params.pagination.page || 1,
|
||||
page_size: params.pagination.limit || 10,
|
||||
})
|
||||
}
|
14
src/service/api/recycle.ts
Normal file
14
src/service/api/recycle.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import {post} from "@/service/request.ts";
|
||||
|
||||
export function getList(params: VideoSearchParams) {
|
||||
return post<DataList<VideoInfo>>('/recycle/list', {
|
||||
page_num: params.pagination.page || 1,
|
||||
page_size: params.pagination.limit || 10,
|
||||
})
|
||||
}
|
||||
export function remove(ids: Id[]) {
|
||||
return post('/recycle/remove', {ids})
|
||||
}
|
||||
export function restore(ids: Id[]) {
|
||||
return post('/recycle/restore', {ids})
|
||||
}
|
13
src/types/api.d.ts
vendored
13
src/types/api.d.ts
vendored
@ -127,16 +127,23 @@ declare interface LiveState{
|
||||
id: number;
|
||||
live_start_time: number;
|
||||
}
|
||||
|
||||
// order
|
||||
declare interface OrderSearchParam extends ApiRequestPageParams{
|
||||
// 标题
|
||||
title?: string;
|
||||
time_flag?: number;
|
||||
}
|
||||
declare interface OrderInfo {
|
||||
id: number| string;
|
||||
order_id: number| string;
|
||||
// 缩略图
|
||||
cover: string;
|
||||
img_url: string;
|
||||
// 标题
|
||||
title: string;
|
||||
// 下单时间
|
||||
order_time: number | string;
|
||||
// 消费时长
|
||||
consume_time: number;
|
||||
consumption_duration: number;
|
||||
// 操作人
|
||||
operator: string;
|
||||
}
|
||||
|
@ -55,9 +55,11 @@ function getDayjs(time:any){
|
||||
return dayjs(time);
|
||||
}
|
||||
// 将时长(秒)转换成时间
|
||||
export function formatDurationToTime(duration: number) {
|
||||
if (duration < 0 || isNaN(duration)) return '00:00';
|
||||
export function formatDurationToTime(duration?: number|string) {
|
||||
duration = duration ? Number(duration) : 0;
|
||||
if (!duration || isNaN(duration) || duration < 0) return '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;
|
||||
|
@ -30,7 +30,7 @@ export default defineConfig(({mode}) => {
|
||||
AUTH_TOKEN_KEY: process.env.AUTH_TOKEN_KEY || AUTH_TOKEN_KEY,
|
||||
AUTHED_PERSON_DATA_KEY: process.env.AUTHED_PERSON_DATA_KEY || 'digital-person-user-info',
|
||||
ONLY_LIVE: process.env.ONLY_LIVE || 'no',
|
||||
APP_LANG: process.env.APP_LANGUAGE || 'zh-CN'
|
||||
APP_LANG: process.env.APP_LANGUAGE || 'multiple'
|
||||
}),
|
||||
AppMode: JSON.stringify(mode),
|
||||
AppBuildVersion: JSON.stringify(AppPackage.name + '-' + AppPackage.version + '-' + dayjs().format('YYYYMMDDHH_mmss'))
|
||||
|
Loading…
x
Reference in New Issue
Block a user