feat: 添加视频相关数字对接
This commit is contained in:
parent
f946e9d4f7
commit
c63b0c088e
@ -1,4 +1,5 @@
|
||||
import {message} from "antd";
|
||||
import {BizError} from "@/service/types.ts";
|
||||
|
||||
export function showToast(content: string, type?: 'success' | 'info' | 'warning' | 'error') {
|
||||
|
||||
@ -8,6 +9,10 @@ export function showToast(content: string, type?: 'success' | 'info' | 'warning'
|
||||
className: 'aui-toast'
|
||||
}).then();
|
||||
}
|
||||
export function showErrorToast(e:Error|BizError) {
|
||||
showToast(String(((e instanceof BizError)?e.data:'') || e.message),'error')
|
||||
}
|
||||
|
||||
|
||||
export function showLoading(content = 'Loading...') {
|
||||
const key = 'globalLoading_' + (new Date().getTime());
|
||||
|
@ -8,7 +8,7 @@ import {IconEdit, IconPlay} from "@/components/icons";
|
||||
import {Popconfirm} from "antd";
|
||||
|
||||
type Props = {
|
||||
video: VideoInfo,
|
||||
video: VideoInfo | LiveVideoInfo,
|
||||
editable?: boolean;
|
||||
sortable?: boolean;
|
||||
index?: number;
|
||||
@ -25,7 +25,6 @@ export const VideoListItem = (
|
||||
{
|
||||
index, id, video, onPlay, onRemove, checked,
|
||||
onCheckedChange, onEdit, active, editable,
|
||||
|
||||
}: Props) => {
|
||||
const {
|
||||
attributes, listeners,
|
||||
@ -42,14 +41,13 @@ export const VideoListItem = (
|
||||
className={'video-item flex items-center gap-3 mb-5'}
|
||||
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">{id}</div>
|
||||
<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 flex gap-2 flex-1 bg-gray-100 min-h-[40px] rounded-lg p-3 shadow-blue-500 ${active ? 'video-item-shadow' : ''}`}>
|
||||
<div className={'video-title leading-7 flex-1'}>{video.title}</div>
|
||||
<div className={'video-title leading-7 flex-1'}>{video.title || video.video_title}</div>
|
||||
<div className={'video-item-cover'}>
|
||||
<img className="w-[100px] rounded-md" src={video.cover || ''} alt={video.title}/>
|
||||
<img className="w-[100px] rounded-md" src={video.cover_url || ''} alt={video.video_title}/>
|
||||
</div>
|
||||
</div>
|
||||
{editable &&
|
||||
|
@ -17,9 +17,9 @@ export default function SearchForm({onSearch, onBtnStartClick}: Props) {
|
||||
timeRange: string;
|
||||
keywords: string;
|
||||
searching: boolean;
|
||||
time: string;
|
||||
time: number;
|
||||
}>({
|
||||
keywords: "", searching: false, timeRange: "", time: '-1'
|
||||
keywords: "", searching: false, timeRange: "", time: 0
|
||||
})
|
||||
const onFinish = (values: any) => {
|
||||
setState({searching: true})
|
||||
@ -52,12 +52,11 @@ export default function SearchForm({onSearch, onBtnStartClick}: Props) {
|
||||
{/*<Form.Item label={'更新时间'} name="timeRange">*/}
|
||||
{/* <DatePicker.RangePicker />*/}
|
||||
{/*</Form.Item>*/}
|
||||
{/*<Form.Item>*/}
|
||||
{/* <Space size={10}>*/}
|
||||
{/* <Button type={'primary'} htmlType={'submit'}>搜索</Button>*/}
|
||||
{/* <Button htmlType={'reset'}>重置</Button>*/}
|
||||
{/* </Space>*/}
|
||||
{/*</Form.Item>*/}
|
||||
<Form.Item>
|
||||
<Space size={10}>
|
||||
<Button type={'primary'} htmlType={'submit'}>搜索</Button>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
<Space size={10}>
|
||||
|
@ -1,15 +1,99 @@
|
||||
import React, {useState} from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {Button, message, Modal} from "antd";
|
||||
import {SortableContext, arrayMove} from '@dnd-kit/sortable';
|
||||
import {DndContext} from "@dnd-kit/core";
|
||||
|
||||
import {VideoListItem} from "@/components/video/video-list-item.tsx";
|
||||
import {getList} from "@/service/api/live.ts";
|
||||
|
||||
import styles from './style.module.scss'
|
||||
|
||||
export default function LiveIndex() {
|
||||
const [videoData, setVideoData] = useState<VideoInfo[]>()
|
||||
const [videoData, setVideoData] = useState<LiveVideoInfo[]>([])
|
||||
const [modal, contextHolder] = Modal.useModal()
|
||||
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
|
||||
const [editable,setEditable] = useState<boolean>(false)
|
||||
const [editable, setEditable] = useState<boolean>(false)
|
||||
|
||||
const [state, setState] = useState({
|
||||
activeId: -1,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
getList().then(res => {
|
||||
setVideoData([
|
||||
{
|
||||
id: 1,
|
||||
video_id: 1,
|
||||
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?',
|
||||
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
|
||||
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
|
||||
video_duration: 100,
|
||||
status: 1,
|
||||
order_no: '1'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
video_id: 1,
|
||||
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?',
|
||||
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
|
||||
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
|
||||
video_duration: 100,
|
||||
status: 1,
|
||||
order_no: '1'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
video_id: 1,
|
||||
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?',
|
||||
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
|
||||
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
|
||||
video_duration: 100,
|
||||
status: 1,
|
||||
order_no: '1'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
video_id: 1,
|
||||
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?',
|
||||
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
|
||||
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
|
||||
video_duration: 100,
|
||||
status: 1,
|
||||
order_no: '1'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
video_id: 1,
|
||||
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?',
|
||||
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
|
||||
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
|
||||
video_duration: 100,
|
||||
status: 1,
|
||||
order_no: '1'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
video_id: 1,
|
||||
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?',
|
||||
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
|
||||
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
|
||||
video_duration: 100,
|
||||
status: 1,
|
||||
order_no: '1'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
video_id: 1,
|
||||
video_title: '333专家分析丨叙土边境曼比季地理位置为何如此重要?',
|
||||
cover_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.jpg',
|
||||
video_oss_url: 'https://gachafun.oss-cn-beijing.aliyuncs.com/2021/08/13/1628840269744.mp4',
|
||||
video_duration: 100,
|
||||
status: 1,
|
||||
order_no: '1'
|
||||
}
|
||||
])
|
||||
})
|
||||
}, [])
|
||||
const processDeleteVideo = async (_idArray: number[]) => {
|
||||
message.info('删除成功!!!' + _idArray.join(''));
|
||||
}
|
||||
@ -42,19 +126,19 @@ export default function LiveIndex() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="video-list-container flex-1">
|
||||
<div className="video-list-container flex-1 ">
|
||||
<div className=" bg-white py-8 px-6 rounded py-1">
|
||||
<div className="live-control flex justify-between mb-8">
|
||||
{editable ?<>
|
||||
{editable ? <>
|
||||
<div className="flex gap-2">
|
||||
<Button type="primary" onClick={handleConfirm}>确定</Button>
|
||||
<Button onClick={()=>setEditable(false)}>退出</Button>
|
||||
<Button onClick={() => setEditable(false)}>退出</Button>
|
||||
</div>
|
||||
<div>
|
||||
<span className="cursor-pointer" onClick={handleDeleteBatch}>批量删除</span>
|
||||
</div>
|
||||
</>: <div>
|
||||
<Button type="primary" onClick={()=>setEditable(true)}>编辑</Button>
|
||||
</> : <div>
|
||||
<Button type="primary" onClick={() => setEditable(true)}>编辑</Button>
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
@ -86,7 +170,7 @@ export default function LiveIndex() {
|
||||
video={v}
|
||||
index={index + 1}
|
||||
id={v.id}
|
||||
active={index == 0}
|
||||
active={state.activeId == v.id}
|
||||
key={index}
|
||||
onCheckedChange={(checked) => {
|
||||
setCheckedIdArray(idArray => {
|
||||
|
3
src/pages/live/style.module.scss
Normal file
3
src/pages/live/style.module.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.videoListContainer{
|
||||
|
||||
}
|
@ -1,26 +1,26 @@
|
||||
import {Button, Modal} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {showToast} from "@/components/message.ts";
|
||||
import {push2article} from "@/service/api/news.ts";
|
||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||
import {push2video} from "@/service/api/article.ts";
|
||||
|
||||
|
||||
export default function ButtonPush2Video(props: { ids: Id[]}){
|
||||
const [loading,setLoading] = useState(false)
|
||||
const handlePush = ()=>{
|
||||
export default function ButtonPush2Video(props: { ids: Id[] }) {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const handlePush = () => {
|
||||
setLoading(true)
|
||||
push2article(props.ids).then(()=>{
|
||||
push2video(props.ids).then(() => {
|
||||
showToast('一键推流成功,已成功推入数字人视频生成,请前往数字人视频生成页面查看!', 'success')
|
||||
}).finally(()=>{
|
||||
}).catch(showErrorToast).finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
const onPushClick = ()=>{
|
||||
const onPushClick = () => {
|
||||
if (props.ids.length === 0) {
|
||||
showToast('请选择要开播的新闻', 'warning')
|
||||
return
|
||||
}
|
||||
Modal.confirm({
|
||||
title:'操作提示',
|
||||
title: '操作提示',
|
||||
content: '是否确定一键开播选中新闻?',
|
||||
onOk: handlePush
|
||||
})
|
||||
|
@ -95,7 +95,7 @@ export default function NewsIndex() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="title text-lg cursor-pointer" onClick={() => {
|
||||
handleViewNewsDetail(item.id)
|
||||
}}>{item.id}{item.title}</div>
|
||||
}}>{item.title}</div>
|
||||
{item.internal_article_id > 0 &&
|
||||
<div className="text-sm text-blue-500">已加入编辑界面</div>}
|
||||
</div>
|
||||
|
31
src/pages/video/components/button-push2room.tsx
Normal file
31
src/pages/video/components/button-push2room.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import {Button, Modal} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||
import {push2room} from "@/service/api/video.ts";
|
||||
|
||||
|
||||
export default function ButtonPush2Room(props: { ids: Id[]}){
|
||||
const [loading,setLoading] = useState(false)
|
||||
const handlePush = ()=>{
|
||||
setLoading(true)
|
||||
push2room(props.ids).then(()=>{
|
||||
showToast('一键推流成功,已推流至数字人直播间,请前往数字人直播间页面查看!', 'success')
|
||||
}).catch(showErrorToast).finally(()=>{
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
const onPushClick = ()=>{
|
||||
if (props.ids.length === 0) {
|
||||
showToast('请选择要推流的新闻', 'warning')
|
||||
return
|
||||
}
|
||||
Modal.confirm({
|
||||
title:'操作提示',
|
||||
content: '是否确定一键推流选中新闻视频??',
|
||||
onOk: handlePush
|
||||
})
|
||||
}
|
||||
return (
|
||||
<Button type="primary" loading={loading} onClick={onPushClick}>一键推流</Button>
|
||||
)
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
import {Button, message, Modal} from "antd";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
|
||||
import {ArticleGroupList, MockVideoDataList} from "@/_local/mock-data";
|
||||
import {message, 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 {VideoListItem} from "@/components/video/video-list-item.tsx";
|
||||
@ -10,19 +8,18 @@ import {useSetState} from "ahooks";
|
||||
import {CheckCircleFilled} from "@ant-design/icons";
|
||||
import {clsx} from "clsx";
|
||||
import {getList} from "@/service/api/video.ts";
|
||||
import {formatDuration} from "@/util/strings.ts";
|
||||
import ButtonPush2Room from "@/pages/video/components/button-push2room.tsx";
|
||||
|
||||
|
||||
export default function CreateIndex() {
|
||||
const [editNews, setEditNews] = useSetState<{
|
||||
title?: string;
|
||||
groups?: ArticleContentGroup[];
|
||||
}>({})
|
||||
export default function VideoIndex() {
|
||||
const [editId, setEditId] = useState(-1)
|
||||
|
||||
const [videoData, setVideoData] = useState<VideoInfo[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
getList({}).then((ret) => {
|
||||
setVideoData(ret.list)
|
||||
getList().then((ret) => {
|
||||
setVideoData(ret.list || [])
|
||||
})
|
||||
}, [])
|
||||
|
||||
@ -63,13 +60,19 @@ export default function CreateIndex() {
|
||||
})
|
||||
}
|
||||
|
||||
const totalDuration = useMemo(() => {
|
||||
if(!videoData || videoData.length == 0) return 0;
|
||||
// 计算总时长
|
||||
return videoData.reduce((sum, v) => sum + v.duration, 0);
|
||||
}, [videoData])
|
||||
|
||||
return (<div className="container py-10 page-live">
|
||||
{contextHolder}
|
||||
<div className="flex">
|
||||
<div className="video-list-container bg-white p-10 rounded flex-1">
|
||||
<div className="live-control flex justify-between mb-8">
|
||||
<div className="pl-[70px]">
|
||||
<span>视频时长: 00:00:29</span>
|
||||
<span>视频时长: {formatDuration(totalDuration)}</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<span className="cursor-pointer" onClick={handleDeleteBatch}>批量删除</span>
|
||||
@ -99,6 +102,7 @@ export default function CreateIndex() {
|
||||
}
|
||||
}}>
|
||||
<SortableContext items={videoData}>
|
||||
|
||||
{videoData.map((v, index) => (
|
||||
<VideoListItem
|
||||
video={v}
|
||||
@ -115,25 +119,25 @@ export default function CreateIndex() {
|
||||
}}
|
||||
onPlay={() => playVideo(v)}
|
||||
onEdit={() => {
|
||||
setEditNews({title: v.title, groups: [...ArticleGroupList]})
|
||||
setEditId(v.article_id)
|
||||
}}
|
||||
editable
|
||||
/>))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
<div className="text-right mt-10">
|
||||
<Button type="primary">一键推流</Button>
|
||||
<ButtonPush2Room ids={checkedIdArray}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="video-player-container ml-8 w-[400px] flex flex-col">
|
||||
<div className="video-player-container ml-6 w-[450px] flex flex-col">
|
||||
<div className="text-center text-base mt-10">预览视频</div>
|
||||
<div className="video-player flex items-center justify-center flex-1">
|
||||
<div className="video-player flex items-center justify-center mt-20">
|
||||
<div className=" rounded overflow-hidden">
|
||||
<video ref={videoRef} controls autoPlay className="w-full bg-white min-w-[360px]"></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ArticleEditModal title={editNews.title} groups={editNews.groups}/>
|
||||
<ArticleEditModal id={editId} onClose={() => setEditId(-1)}/>
|
||||
</div>)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import {RouteObject} from "react-router-dom";
|
||||
import ErrorBoundary from "@/routes/error.tsx";
|
||||
import UserAuth from "@/pages/user";
|
||||
import CreateIndex from "@/pages/create";
|
||||
import CreateIndex from "../pages/video";
|
||||
import LibraryIndex from "@/pages/library";
|
||||
import LiveIndex from "@/pages/live";
|
||||
import NewsIndex from "@/pages/news";
|
||||
|
@ -22,12 +22,13 @@ export function getById(id: Id) {
|
||||
}
|
||||
|
||||
export function save(title: string, content_group: BlockContent[][], id: number) {
|
||||
return post<{ content: string }>({
|
||||
url: '/spider/article',
|
||||
data: {
|
||||
title,
|
||||
content_group,
|
||||
id
|
||||
}
|
||||
return post<{ content: string }>(id && id > 0 ? '/article/modify' : '/article/create/new', {
|
||||
title,
|
||||
content_group,
|
||||
id
|
||||
})
|
||||
}
|
||||
|
||||
export function push2video(article_ids: Id[]) {
|
||||
return post('/article/push2video', {article_ids})
|
||||
}
|
20
src/service/api/live.ts
Normal file
20
src/service/api/live.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {post} from "@/service/request.ts";
|
||||
|
||||
export function playState() {
|
||||
return post<{
|
||||
id: number;
|
||||
start_time?: number;
|
||||
}>({url: '/room/playing'})
|
||||
}
|
||||
|
||||
export function getList() {
|
||||
return post<DataList<LiveVideoInfo>>('/room/list')
|
||||
}
|
||||
|
||||
export function modifyOrder(ids: Id[]) {
|
||||
return post('/video/order', {ids})
|
||||
}
|
||||
|
||||
export function deleteById(ids: Id[]) {
|
||||
return post('/video/remove', {ids})
|
||||
}
|
@ -1,10 +1,7 @@
|
||||
import {post} from "@/service/request.ts";
|
||||
|
||||
export function getList(data: {
|
||||
title?: string,
|
||||
time_flag?: number;
|
||||
}) {
|
||||
return post<DataList<VideoInfo>>({url: '/video/list', data})
|
||||
export function getList() {
|
||||
return post<DataList<VideoInfo>>('/video/list')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,9 +28,11 @@ export function getById(id: Id) {
|
||||
export function deleteById(id: Id) {
|
||||
return post({url: '/video/detail/' + id})
|
||||
}
|
||||
|
||||
export function modifyOrder(ids: Id[]) {
|
||||
return post({url: ' /video/modifyorder',data:{ids}})
|
||||
return post('/video/modifyorder', {ids})
|
||||
}
|
||||
export function push2room(ids: Id[]) {
|
||||
return post({url: ' /video/push2room',data:{ids}})
|
||||
|
||||
export function push2room(video_ids: Id[]) {
|
||||
return post('/video/push2room', {video_ids})
|
||||
}
|
@ -2,6 +2,7 @@ import axios from 'axios';
|
||||
import {stringify} from 'qs'
|
||||
import {BizError} from './types';
|
||||
import {getAuthToken} from "@/hooks/useAuth.ts";
|
||||
import {showToast} from "@/components/message.ts";
|
||||
|
||||
const JSON_FORMAT: string = 'application/json';
|
||||
const REQUEST_TIMEOUT = 300000; // 超时时长5min
|
||||
@ -23,6 +24,7 @@ Axios.interceptors.request.use(config => {
|
||||
}
|
||||
return config
|
||||
}, err => {
|
||||
console.log('请求拦截器报错',err)
|
||||
return Promise.reject(err)
|
||||
})
|
||||
|
||||
@ -46,11 +48,11 @@ export function request<T>(options: RequestOption) {
|
||||
return;
|
||||
}
|
||||
// const
|
||||
const {code, message, data, request_id} = res.data
|
||||
const {code, msg, data, trace_id} = res.data
|
||||
if (code == 0) {
|
||||
resolve(data as unknown as T)
|
||||
} else {
|
||||
reject(new BizError(message, code, request_id, data as unknown as AllType))
|
||||
reject(new BizError(msg, code, trace_id, data as unknown as AllType))
|
||||
}
|
||||
}).catch(e => {
|
||||
reject(new BizError(e.message, 500))
|
||||
@ -59,9 +61,13 @@ export function request<T>(options: RequestOption) {
|
||||
}
|
||||
|
||||
|
||||
export function post<T>(params: RequestOption) {
|
||||
export function post<T>(params: RequestOption | string, _data?: AllType) {
|
||||
const options = typeof params === 'string' ? {url: params} : params;
|
||||
if (_data) {
|
||||
options.data = _data
|
||||
}
|
||||
return request<T>({
|
||||
...params,
|
||||
...options,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
12
src/types/api.d.ts
vendored
12
src/types/api.d.ts
vendored
@ -89,3 +89,15 @@ declare interface VideoInfo {
|
||||
article_id: number;
|
||||
status: number;
|
||||
}
|
||||
// room live
|
||||
declare interface LiveVideoInfo {
|
||||
id: number;
|
||||
video_id: number;
|
||||
video_title: string;
|
||||
cover_url: string;
|
||||
video_duration: number;
|
||||
video_oss_url: string;
|
||||
status: number;
|
||||
order_no: string;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime"
|
||||
import {padStart} from "lodash";
|
||||
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
@ -80,3 +81,13 @@ export function calcContentLengthLikeWord(str:string) {
|
||||
return str.length
|
||||
}
|
||||
}
|
||||
|
||||
// 将时长转换成 时:分:秒
|
||||
export function formatDuration(duration: number) {
|
||||
const hour = Math.floor(duration / 3600);
|
||||
const minute = Math.floor((duration - hour * 3600) / 60);
|
||||
const second = duration - hour * 3600 - minute * 60;
|
||||
// 需要补0
|
||||
return padStart(hour.toString(), 2, '0') + ':' + padStart(minute.toString(), 2, '0') + ':' + padStart(second.toString(), 2, '0')
|
||||
// return `${hour}:${minute}:${second}`
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user