Compare commits
5 Commits
4d5449364a
...
661b6f38da
Author | SHA1 | Date | |
---|---|---|---|
661b6f38da | |||
a2770765d8 | |||
8e6d7fe702 | |||
e1a4005e27 | |||
1acdc2a99d |
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
## 数值人直播间
|
||||||
|
### Start
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone git@e.coding.net:starbite/aixiaodui/fengmang-backend.git
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
打开 [http://localhost:10021/](http://localhost:10021/) 查看效果
|
||||||
|
### Deploy
|
||||||
|
**直接部署**
|
||||||
|
> 需要配置 /mgmt 及 /api 反向代理
|
||||||
|
```shell
|
||||||
|
; 如果需要指定前缀(CDN)或者需要运行在相对路径中,不需要则跳过此命令
|
||||||
|
export PUBLIC_PATH=xxxxxxxx(相应路径)
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
生成的资源在dist目录中,将此目录中所有文件放置在待部署web目录即可。
|
||||||
|
|
||||||
|
**使用docker**
|
||||||
|
|
||||||
|
[x] TODO
|
||||||
|
```shell
|
||||||
|
dockercompose up -d
|
||||||
|
```
|
@ -6,6 +6,7 @@
|
|||||||
"description": "数字人直播间",
|
"description": "数字人直播间",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
|
"dev-test": "vite --host --mode=test",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"build-test": "tsc && vite build --mode=test",
|
"build-test": "tsc && vite build --mode=test",
|
||||||
"build-relative": "tsc && vite build --mode=relative",
|
"build-relative": "tsc && vite build --mode=relative",
|
||||||
@ -31,7 +32,8 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-player": "^2.16.0",
|
"react-player": "^2.16.0",
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router-dom": "^6.28.0",
|
||||||
"sass": "^1.81.0"
|
"sass": "^1.81.0",
|
||||||
|
"tcplayer.js": "^5.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
|
@ -158,6 +158,14 @@
|
|||||||
max-height: calc(100vh - var(--app-header-header) - 200px);
|
max-height: calc(100vh - var(--app-header-header) - 200px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
.video-player{
|
||||||
|
.video-js{
|
||||||
|
@apply w-full h-full;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
background:#fff; // hsl(210, 100%, 48%)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.app-main-navigation {
|
.app-main-navigation {
|
||||||
@include media-breakpoint-down(md) {
|
@include media-breakpoint-down(md) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import {Popconfirm, Space} from "antd";
|
import {Popconfirm} from "antd";
|
||||||
|
|
||||||
import {IconAdd, IconDelete} from "@/components/icons";
|
import {IconAdd, IconDelete} from "@/components/icons";
|
||||||
import ImageList from "@/components/article/list.tsx";
|
import ImageList from "@/components/article/list.tsx";
|
||||||
@ -10,7 +10,7 @@ import styles from './article.module.scss'
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
index?: number;
|
index: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
blocks: BlockContent[];
|
blocks: BlockContent[];
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
@ -51,22 +51,6 @@ export default function ArticleBlock(
|
|||||||
errorMessage
|
errorMessage
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const blocks = rebuildBlockArray(defaultBlocks)
|
const blocks = rebuildBlockArray(defaultBlocks)
|
||||||
const handleBlockRemove = (index: number) => {
|
|
||||||
// 删除当前项
|
|
||||||
onChange?.(blocks.filter((_, idx) => index !== idx))
|
|
||||||
}
|
|
||||||
const firstTextBlockIndex = blocks.findIndex(it => it.type === 'text')
|
|
||||||
// 新增
|
|
||||||
const handleAddBlock = (type: 'text' | 'image', insertIndex: number = -1) => {
|
|
||||||
const newBlock: BlockContent = type === 'text' ? {type: 'text', content: ''} : {type: 'image', content: ''};
|
|
||||||
const _blocks = [...blocks]
|
|
||||||
if (insertIndex == -1 || insertIndex >= blocks.length) { // -1或者越界表示新增
|
|
||||||
_blocks.push(newBlock)
|
|
||||||
} else {
|
|
||||||
_blocks.splice(insertIndex, 0, newBlock)
|
|
||||||
}
|
|
||||||
onChange?.(_blocks)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleBlockChange = (index: number, block: BlockContent) => {
|
const handleBlockChange = (index: number, block: BlockContent) => {
|
||||||
const _blocks = [...blocks]
|
const _blocks = [...blocks]
|
||||||
|
@ -39,7 +39,7 @@ export default function ArticleEditModal(props: Props) {
|
|||||||
}
|
}
|
||||||
const save = props.type == 'news' ? article.save : regenerate
|
const save = props.type == 'news' ? article.save : regenerate
|
||||||
setState({loading: true})
|
setState({loading: true})
|
||||||
save(title, groups, props.id > 0 ? props.id : undefined).then(() => {
|
save(title, groups, props.id && props.id > 0 ? props.id : undefined).then(() => {
|
||||||
props.onClose?.(true)
|
props.onClose?.(true)
|
||||||
}).catch(e=>{
|
}).catch(e=>{
|
||||||
setState({error: e.data || '保存失败,请重试!'})
|
setState({error: e.data || '保存失败,请重试!'})
|
||||||
@ -65,7 +65,7 @@ export default function ArticleEditModal(props: Props) {
|
|||||||
|
|
||||||
return (<Modal
|
return (<Modal
|
||||||
title={'编辑文章'}
|
title={'编辑文章'}
|
||||||
open={props.id >= 0}
|
open={!!props.id && props.id >= 0}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
keyboard={false}
|
keyboard={false}
|
||||||
width={800}
|
width={800}
|
||||||
|
@ -14,7 +14,7 @@ type Props = {
|
|||||||
|
|
||||||
function pushBlocksToGroup(blocks: BlockContent[],groups: BlockContent[][]){
|
function pushBlocksToGroup(blocks: BlockContent[],groups: BlockContent[][]){
|
||||||
const lastGroup = groups[groups.length - 1]
|
const lastGroup = groups[groups.length - 1]
|
||||||
if (lastGroup && lastGroup.filter(s=>s.type == 'text') == 0) {
|
if (lastGroup && lastGroup.filter(s=>s.type == 'text').length == 0) {
|
||||||
// 如果上一个group中没有文本则直接合并
|
// 如果上一个group中没有文本则直接合并
|
||||||
lastGroup.push(...blocks)
|
lastGroup.push(...blocks)
|
||||||
} else {
|
} else {
|
||||||
@ -27,6 +27,7 @@ function rebuildGroups(groups: BlockContent[][]) {
|
|||||||
if (!groups || groups.length == 0) return _groups;
|
if (!groups || groups.length == 0) return _groups;
|
||||||
groups.forEach((blocks,index) => {
|
groups.forEach((blocks,index) => {
|
||||||
if(!blocks) return;
|
if(!blocks) return;
|
||||||
|
blocks.sort((a) => a.type == 'text' ? -1 : 1)
|
||||||
if (blocks.length == 1) {
|
if (blocks.length == 1) {
|
||||||
if(index == 0) _groups.push(blocks)
|
if(index == 0) _groups.push(blocks)
|
||||||
else pushBlocksToGroup(blocks,_groups)
|
else pushBlocksToGroup(blocks,_groups)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, {useState} from "react";
|
import React, {useState} from "react";
|
||||||
import {Input, Popconfirm, Spin, Upload, UploadProps} from "antd";
|
import {Input, Popconfirm, Spin, Upload, UploadProps} from "antd";
|
||||||
import {CloseOutlined,CloudUploadOutlined} from "@ant-design/icons";
|
import {CloseOutlined} from "@ant-design/icons";
|
||||||
import {clsx} from "clsx";
|
import {clsx} from "clsx";
|
||||||
|
|
||||||
import styles from './article.module.scss'
|
import styles from './article.module.scss'
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
|
|
||||||
import {BlockImage} from "@/components/article/item.tsx";
|
import {BlockImage} from "@/components/article/item.tsx";
|
||||||
|
|
||||||
import styles from './article.module.scss'
|
import styles from './article.module.scss'
|
||||||
|
@ -2,13 +2,14 @@ import React, {useState} from "react";
|
|||||||
import {Button, Modal} from "antd";
|
import {Button, Modal} from "antd";
|
||||||
import {ButtonType} from "antd/es/button";
|
import {ButtonType} from "antd/es/button";
|
||||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||||
|
import {BizError} from "@/service/types.ts";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
selected: any[],
|
selected: any[],
|
||||||
type?: ButtonType;
|
type?: ButtonType;
|
||||||
emptyMessage: string,
|
emptyMessage: string,
|
||||||
confirmMessage: React.ReactNode,
|
confirmMessage: React.ReactNode,
|
||||||
onProcess: (ids: Id[]) => Promise<void>
|
onProcess: (ids: Id[]) => Promise<any|void>
|
||||||
successMessage?: string;
|
successMessage?: string;
|
||||||
onSuccess?: () => void;
|
onSuccess?: () => void;
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
@ -32,7 +33,7 @@ export default function ButtonBatch(
|
|||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorToast(e)
|
showErrorToast(e as unknown as BizError)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,35 @@
|
|||||||
import ReactPlayer from 'react-player'
|
// import ReactPlayer from 'react-player'
|
||||||
import { PauseOutlined, PlayCircleOutlined, FullscreenOutlined, FullscreenExitOutlined } from "@ant-design/icons"
|
// import {PauseOutlined, PlayCircleOutlined, FullscreenOutlined, FullscreenExitOutlined} from "@ant-design/icons"
|
||||||
import { Progress } from "antd";
|
// import {Progress} from "antd";
|
||||||
import {useState} from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
|
import TCPlayer from 'tcplayer.js';
|
||||||
|
import 'tcplayer.js/dist/tcplayer.min.css';
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
playing: boolean
|
playing: boolean
|
||||||
muted: boolean
|
muted: boolean
|
||||||
|
end?: boolean
|
||||||
|
error?: boolean
|
||||||
fullscreen: boolean
|
fullscreen: boolean
|
||||||
progress: number
|
progress: number
|
||||||
playedSeconds: number
|
playedSeconds: number
|
||||||
duration: number
|
duration: number
|
||||||
}
|
}
|
||||||
type StateUpdate = Partial<State> | ((prev: State) => Partial<State>)
|
type StateUpdate = Partial<State> | ((prev: State) => Partial<State>)
|
||||||
export function Player({ url, cover, simple, showControls }: { url: string; cover?: string; simple?: boolean; showControls?: boolean }) {
|
|
||||||
|
type Props = {
|
||||||
|
url?: string; cover?: string; showControls?: boolean; className?: string;
|
||||||
|
poster?: string;
|
||||||
|
onChange?: (state: State) => void;
|
||||||
|
muted?: boolean;
|
||||||
|
}
|
||||||
|
export type PlayerInstance = {
|
||||||
|
play: (url: string, currentTime: number) => void;
|
||||||
|
getState: () => State;
|
||||||
|
}
|
||||||
|
export const Player = React.forwardRef<PlayerInstance, Props>((props, ref) => {
|
||||||
|
const [tcPlayer, setTcPlayer] = useState<TCPlayer | null>(null)
|
||||||
|
const [prevUrl, setPrevUrl] = useState<string | undefined>();
|
||||||
const [state, _setState] = useState<State>({
|
const [state, _setState] = useState<State>({
|
||||||
playing: false,
|
playing: false,
|
||||||
muted: false,
|
muted: false,
|
||||||
@ -22,51 +39,82 @@ export function Player({ url, cover, simple, showControls }: { url: string; cove
|
|||||||
playedSeconds: 0,
|
playedSeconds: 0,
|
||||||
duration: 0
|
duration: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
const setState = (data: StateUpdate) => {
|
const setState = (data: StateUpdate) => {
|
||||||
|
console.log('playstate change', data)
|
||||||
_setState(prev => {
|
_setState(prev => {
|
||||||
if (typeof(data) === 'function') return { ...prev, ...data(prev) }
|
const _state = typeof (data) === 'function' ? {...prev, ...data(prev)} : {...prev, ...data}
|
||||||
return { ...prev, ...data }
|
props.onChange?.(_state)
|
||||||
|
return _state
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return <div className="video-player">
|
|
||||||
{simple ? <div>
|
useEffect(()=>{
|
||||||
<video style={{width:400,height:400}} preload={'metadata'} src={url} poster={cover} controls={showControls}></video>
|
if(props.url && tcPlayer){
|
||||||
</div> : <>
|
tcPlayer.src(props.url)
|
||||||
<ReactPlayer
|
}
|
||||||
url={url}
|
},[props.url, tcPlayer])
|
||||||
controls={true}
|
|
||||||
light={cover}
|
useEffect(() => {
|
||||||
width="100%"
|
const playerVideo = document.createElement('video');
|
||||||
height="250px"
|
const playerId = `player-container-${Date.now().toString(16)}`;
|
||||||
onPlay={() => setState({ playing: true })}
|
playerVideo.setAttribute('id', playerId)
|
||||||
onEnded={() => setState({ playing: false })}
|
playerVideo.setAttribute('preload', 'auto')
|
||||||
onPause={() => setState({ playing: false })}
|
playerVideo.setAttribute('playsInline', 'true')
|
||||||
onReady={(_player) => {
|
playerVideo.setAttribute('webkit-playsinline', 'true')
|
||||||
setState({duration: _player.getDuration() })
|
if(props.className) playerVideo.setAttribute('className', props.className)
|
||||||
}}
|
document.querySelector('.video-player-container-inner').appendChild(playerVideo)
|
||||||
onProgress={(_) => {
|
|
||||||
setState(_prev=>({
|
const player = TCPlayer(playerId, {
|
||||||
playedSeconds: _.playedSeconds,
|
//sources: [{src: props.url}],
|
||||||
progress: Math.floor(_.playedSeconds / _prev.duration * 100)
|
controls: props.showControls,
|
||||||
}))
|
// muted:props.muted,
|
||||||
}}
|
poster: props.poster,
|
||||||
/>
|
autoplay: true,
|
||||||
<div className="video-control p-2 flex items-center gap-2">
|
licenseUrl: 'https://license.vod2.myqcloud.com/license/v2/1328581896_1/v_cube.license'
|
||||||
<button>
|
}
|
||||||
{state.playing ? <PauseOutlined /> : <PlayCircleOutlined />}
|
)
|
||||||
</button>
|
player.on('pause', () => {
|
||||||
<div className="whitespace-nowrap flex items-center text-sm">
|
setState({playing: false, end: false, error: false})
|
||||||
<span>00:00</span>
|
})
|
||||||
<span>/</span>
|
player.on('playing', () => {
|
||||||
<span>00:00</span>
|
setState({playing: true, end: false, error: false})
|
||||||
</div>
|
})
|
||||||
<div className="flex-1">
|
player.on('ended', () => {
|
||||||
<Progress size="small" percent={state.progress} showInfo={false} />
|
setState({end: true, playing: false, error: false})
|
||||||
</div>
|
})
|
||||||
<button>
|
player.on('error', () => {
|
||||||
{state.fullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
|
setState({end: false, playing: false, error: true})
|
||||||
</button>
|
})
|
||||||
</div>
|
setTcPlayer(() => player)
|
||||||
</>}
|
return () => {
|
||||||
|
if (tcPlayer) {
|
||||||
|
tcPlayer.pause()
|
||||||
|
tcPlayer.unload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
React.useImperativeHandle(ref, () => {
|
||||||
|
return {
|
||||||
|
play: (url, currentTime = 0) => {
|
||||||
|
console.log('play', url, currentTime)
|
||||||
|
if (!tcPlayer) return;
|
||||||
|
const player = tcPlayer
|
||||||
|
if (prevUrl == url) {
|
||||||
|
player.currentTime(0)
|
||||||
|
} else {
|
||||||
|
player.src(url)
|
||||||
|
}
|
||||||
|
player.play()
|
||||||
|
setPrevUrl(url)
|
||||||
|
if (currentTime > 0) {
|
||||||
|
player.currentTime(currentTime)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getState: () => state
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return <div className={`video-player relative ${props.className} video-player-container-inner`}>
|
||||||
</div>
|
</div>
|
||||||
}
|
})
|
@ -2,11 +2,12 @@ import {useSortable} from "@dnd-kit/sortable";
|
|||||||
import {useSetState} from "ahooks";
|
import {useSetState} from "ahooks";
|
||||||
import React, {useEffect} from "react";
|
import React, {useEffect} from "react";
|
||||||
import {clsx} from "clsx";
|
import {clsx} from "clsx";
|
||||||
import {Image, Popconfirm} from "antd";
|
import {Popconfirm} from "antd";
|
||||||
import {CheckCircleFilled, MenuOutlined, MinusCircleFilled} from "@ant-design/icons";
|
import {CheckCircleFilled, MenuOutlined, MinusCircleFilled,LoadingOutlined} from "@ant-design/icons";
|
||||||
|
|
||||||
import ImageCover from '@/assets/images/cover.png'
|
import ImageCover from '@/assets/images/cover.png'
|
||||||
import {IconEdit, IconPlay} from "@/components/icons";
|
import {IconEdit, IconPlay} from "@/components/icons";
|
||||||
|
import {VideoStatus} from "@/service/api/video.ts";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
video: VideoInfo | LiveVideoInfo,
|
video: VideoInfo | LiveVideoInfo,
|
||||||
@ -21,14 +22,14 @@ type Props = {
|
|||||||
onRemove?: () => void;
|
onRemove?: () => void;
|
||||||
id: number;
|
id: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
type?:'live'|'create'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VideoListItem = (
|
export const VideoListItem = (
|
||||||
{
|
{
|
||||||
// index,
|
|
||||||
id, video, onPlay, onRemove, checked,
|
id, video, onPlay, onRemove, checked,
|
||||||
onCheckedChange, onEdit, active, editable,
|
onCheckedChange, onEdit, active, editable,
|
||||||
className, sortable
|
className, sortable,type
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const {
|
const {
|
||||||
attributes, listeners,
|
attributes, listeners,
|
||||||
@ -51,7 +52,7 @@ export const VideoListItem = (
|
|||||||
className={`video-item-info flex gap-2 flex-1 bg-gray-100 h-[80px] overflow-hidden rounded-lg p-3 shadow-blue-500 ${active ? 'video-item-shadow' : ''}`}>
|
className={`video-item-info 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-title leading-7 flex-1'}>{video.title || video.video_title}</div>
|
||||||
<div className={'video-item-cover bg-white rounded-md overflow-hidden'}>
|
<div className={'video-item-cover bg-white rounded-md overflow-hidden'}>
|
||||||
<img className="w-[100px] h-[56px] object-cover" src={video.cover || ImageCover} alt={video.video_title}/>
|
<img className="w-[100px] h-[56px] object-cover" src={video.cover || ImageCover} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="operation flex items-center ml-2 gap-3 text-lg text-gray-400">
|
<div className="operation flex items-center ml-2 gap-3 text-lg text-gray-400">
|
||||||
@ -59,8 +60,16 @@ export const VideoListItem = (
|
|||||||
<MenuOutlined/>
|
<MenuOutlined/>
|
||||||
</button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>)}
|
</button> : <button disabled className="cursor-not-allowed"><MenuOutlined/></button>)}
|
||||||
{onPlay &&
|
{onPlay &&
|
||||||
<button className="hover:text-blue-500" onClick={onPlay} style={{fontSize: '1.3em'}}><IconPlay/>
|
<>
|
||||||
</button>}
|
{(
|
||||||
|
type == 'create' && video.status == VideoStatus.Generating
|
||||||
|
) ? <button title="视频生成中" className="flex items-center justify-center">
|
||||||
|
<LoadingOutlined className="block text-gray-500" style={{fontSize: '0.85em'}}/>
|
||||||
|
</button>: <button className="hover:text-blue-500" onClick={onPlay} style={{fontSize: '1.3em'}}><IconPlay/>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</>}
|
||||||
|
|
||||||
{editable && <>
|
{editable && <>
|
||||||
{onEdit &&
|
{onEdit &&
|
||||||
<button className="hover:text-blue-500" onClick={onEdit} style={{fontSize: '1.1em'}}><IconEdit/>
|
<button className="hover:text-blue-500" onClick={onEdit} style={{fontSize: '1.1em'}}><IconEdit/>
|
||||||
|
@ -2,19 +2,10 @@ import React, {createContext, useEffect, useReducer} from "react";
|
|||||||
|
|
||||||
import Loader from "@/components/loader";
|
import Loader from "@/components/loader";
|
||||||
import {getAuthToken, setAuthToken} from "@/hooks/useAuth.ts";
|
import {getAuthToken, setAuthToken} from "@/hooks/useAuth.ts";
|
||||||
import {auth, getUserInfo} from "@/service/api/user.ts";
|
import {auth} from "@/service/api/user.ts";
|
||||||
|
|
||||||
|
|
||||||
const UserRoleStorageKey = 'user-current-role';
|
const UserRoleStorageKey = 'user-current-role';
|
||||||
|
|
||||||
function getCurrentRole() {
|
|
||||||
return (localStorage.getItem(UserRoleStorageKey)) as UserRole
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setCurrentRole(role: UserRole) {
|
|
||||||
localStorage.setItem(UserRoleStorageKey, role)
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeRoleStorage() {
|
function removeRoleStorage() {
|
||||||
localStorage.removeItem(UserRoleStorageKey)
|
localStorage.removeItem(UserRoleStorageKey)
|
||||||
}
|
}
|
||||||
@ -87,7 +78,7 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
|
|||||||
}
|
}
|
||||||
// 登出
|
// 登出
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
setAuthToken(null)
|
setAuthToken(null,null,-1)
|
||||||
removeRoleStorage()
|
removeRoleStorage()
|
||||||
dispatch({
|
dispatch({
|
||||||
action: 'logout',
|
action: 'logout',
|
||||||
|
@ -18,7 +18,7 @@ const clearAuth = () => {
|
|||||||
localStorage.removeItem(AppConfig.AUTH_TOKEN_KEY);
|
localStorage.removeItem(AppConfig.AUTH_TOKEN_KEY);
|
||||||
localStorage.removeItem(AppConfig.AUTHED_PERSON_DATA_KEY);
|
localStorage.removeItem(AppConfig.AUTHED_PERSON_DATA_KEY);
|
||||||
}
|
}
|
||||||
export const setAuthToken = (token: string | null,profileData:UserProfile, expiry_time = -1) => {
|
export const setAuthToken = (token: string | null,profileData:UserProfile|null, expiry_time = -1) => {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
clearAuth();
|
clearAuth();
|
||||||
return;
|
return;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
|
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
|
@ -3,44 +3,33 @@ import {useSetState} from "ahooks";
|
|||||||
import {PlayCircleOutlined} from "@ant-design/icons";
|
import {PlayCircleOutlined} from "@ant-design/icons";
|
||||||
import {SearchListTimes} from "@/pages/news/components/news-source.ts";
|
import {SearchListTimes} from "@/pages/news/components/news-source.ts";
|
||||||
|
|
||||||
type SearchParams = {
|
|
||||||
keywords?: string;
|
|
||||||
date?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onSearch?: (params: SearchParams) => Promise<void>;
|
onSearch?: (params: VideoSearchParams) => void;
|
||||||
onBtnStartClick?: () => Promise<void>;
|
onBtnStartClick?: () => Promise<void>;
|
||||||
|
loading?:boolean;
|
||||||
}
|
}
|
||||||
export default function SearchForm({onSearch, onBtnStartClick}: Props) {
|
export default function SearchForm({onSearch, onBtnStartClick,loading}: Props) {
|
||||||
const [state, setState] = useSetState<{
|
const [state, setState] = useSetState<{
|
||||||
timeRange: string;
|
pushing?: boolean;
|
||||||
keywords: string;
|
}>({})
|
||||||
searching: boolean;
|
const onFinish = (values) => {
|
||||||
time: number;
|
|
||||||
}>({
|
|
||||||
keywords: "", searching: false, timeRange: "", time: 0
|
|
||||||
})
|
|
||||||
const onFinish = (values: any) => {
|
|
||||||
setState({searching: true})
|
|
||||||
onSearch?.({
|
onSearch?.({
|
||||||
keywords: values.keywords,
|
...values,
|
||||||
date: values.timeRange.join('-'),
|
pagination: {page: 1, limit: 10}
|
||||||
}).finally(() => {
|
|
||||||
setState({searching: false})
|
|
||||||
})
|
})
|
||||||
|
//console.log(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<div className={'search-panel'}>
|
return (<div className={'search-panel'}>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="search-form">
|
<div className="search-form">
|
||||||
<Form className={""} layout="inline" onFinish={onFinish}>
|
<Form<VideoSearchParams> className={""} layout="inline" onFinish={onFinish} initialValues={{title:'',time_flag:0}}>
|
||||||
<Form.Item name="keywords">
|
<Form.Item name="title">
|
||||||
<Input className="w-[200px]" placeholder={'请输入搜索信息'}/>
|
<Input className="w-[200px]" allowClear placeholder={'请输入搜索信息'}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={'更新时间'} name="date" className="w-[250px]">
|
<Form.Item label={'更新时间'} name="time_flag" className="w-[250px]">
|
||||||
<Select
|
<Select
|
||||||
defaultValue={state.time} options={SearchListTimes}
|
options={SearchListTimes}
|
||||||
optionRender={(option) => (
|
optionRender={(option) => (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<span role="icon" className={`radio-icon`}></span>
|
<span role="icon" className={`radio-icon`}></span>
|
||||||
@ -49,19 +38,16 @@ export default function SearchForm({onSearch, onBtnStartClick}: Props) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{/*<Form.Item label={'更新时间'} name="timeRange">*/}
|
|
||||||
{/* <DatePicker.RangePicker />*/}
|
|
||||||
{/*</Form.Item>*/}
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Space size={10}>
|
<Space size={10}>
|
||||||
<Button type={'primary'} htmlType={'submit'}>搜索</Button>
|
<Button loading={loading} type={'primary'} htmlType={'submit'}>搜索</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
<Space size={10}>
|
<Space size={10}>
|
||||||
<Button
|
<Button
|
||||||
loading={state.searching} type={'primary'}
|
loading={state.pushing} type={'primary'}
|
||||||
onClick={onBtnStartClick} icon={<PlayCircleOutlined/>}
|
onClick={onBtnStartClick} icon={<PlayCircleOutlined/>}
|
||||||
>一键推流</Button>
|
>一键推流</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -1,43 +1,88 @@
|
|||||||
import {Button, Modal} from "antd";
|
import {Button, Input, Modal} from "antd";
|
||||||
|
import {saveAs} from "file-saver";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {useSetState} from "ahooks";
|
||||||
import {Player} from "@/components/video/player.tsx";
|
import {Player} from "@/components/video/player.tsx";
|
||||||
|
|
||||||
import { ArticleGroupList } from "@/_local/mock-data";
|
|
||||||
import ArticleGroup from "@/components/article/group";
|
import ArticleGroup from "@/components/article/group";
|
||||||
|
import * as article from "@/service/api/article.ts";
|
||||||
|
import {push2room} from "@/service/api/video.ts";
|
||||||
|
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
video?: VideoInfo;
|
video?: VideoInfo;
|
||||||
onClose?: () => void
|
onClose?: () => void
|
||||||
}
|
}
|
||||||
export default function VideoDetail({video, onClose}: Props) {
|
export default function VideoDetail({video, onClose}: Props) {
|
||||||
const startStream = () => {
|
const [groups, setGroups] = useState<BlockContent[][]>([]);
|
||||||
|
|
||||||
|
const [state, setState] = useSetState({
|
||||||
|
exporting: false,
|
||||||
|
pushing: false,
|
||||||
|
})
|
||||||
|
// 将视频推送到数字人直播间
|
||||||
|
const pushToRoom = () => {
|
||||||
|
if (video) {
|
||||||
|
if (state.pushing) return
|
||||||
|
setState({pushing: true})
|
||||||
|
push2room([video.id]).then(() => {
|
||||||
|
showToast('一键推流成功,已推流至数字人直播间,请查看!', 'success')
|
||||||
|
}).catch(showErrorToast).finally(() => {
|
||||||
|
setState({pushing: false})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// 下载视频
|
||||||
const downloadVideo = () => {
|
const downloadVideo = () => {
|
||||||
|
if (video?.oss_video_url) {
|
||||||
|
const filename = video.oss_video_url.split('/').pop() || `${video.id}.flv`
|
||||||
|
saveAs(video.oss_video_url, filename)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (video) {
|
||||||
|
if (video.id > 0) {
|
||||||
|
article.getById(video.id).then(res => {
|
||||||
|
setGroups(res.content_group)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [video])
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<Modal open={!!video} title="新闻视频详情" width={1000} footer={null} onCancel={onClose}>
|
<Modal open={!!video} title="新闻视频详情" width={1000} footer={null} onCancel={onClose}>
|
||||||
<div className="flex gap-2 my-5">
|
<div className="flex gap-2 my-5">
|
||||||
<div className="news-video w-[350px]">
|
<div className="news-video w-[350px]">
|
||||||
<div className="video-container bg-gray-100 rounded overflow-hidden h-[400px]">
|
<div className="video-container bg-gray-100 rounded overflow-hidden h-[640px]">
|
||||||
<Player url={'https://file.wx.wm-app.xyz/os/media/ymca.mp4'} simple showControls />
|
<Player url={video?.oss_video_url} poster={video?.cover} showControls={true}
|
||||||
|
className="w-[360px] h-[640px] bg-white"/>
|
||||||
</div>
|
</div>
|
||||||
<div className="video-info text-right text-sm text-gray-600 mt-3">
|
<div className="video-info text-right text-sm text-gray-600 mt-3">
|
||||||
<span>创建时间: 5小时前</span>
|
<span>创建时间: 5小时前</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="detail flex-1 ml-5">
|
<div className="detail flex-1 ml-5">
|
||||||
<div className="title">
|
<div className="text-lg">新闻内容</div>
|
||||||
<span>标题: xxxxxxxx</span>
|
<div className="article-title mt-5 items-center flex">
|
||||||
|
<span className="text text-base">标题</span>
|
||||||
|
<span className="ml-4 flex-1">
|
||||||
|
<Input value={video?.title}/>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="content-container max-h-[500px] overflow-auto pr-1 mt-4">
|
<div className="aricle-body mt-3">
|
||||||
<ArticleGroup groups={ArticleGroupList}/>
|
<div className="title">
|
||||||
|
<span className="text text-base">正文</span>
|
||||||
|
</div>
|
||||||
|
<div className="box mt-1">
|
||||||
|
<ArticleGroup groups={groups}/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="footer flex justify-between">
|
<div className="footer flex justify-between">
|
||||||
<div className="action flex gap-2">
|
<div className="action flex gap-2">
|
||||||
<Button type="primary" onClick={startStream}>一键推流</Button>
|
<Button loading={state.pushing} type="primary" onClick={pushToRoom}>一键推流</Button>
|
||||||
<Button onClick={downloadVideo}>下载视频</Button>
|
<Button onClick={downloadVideo}>下载视频</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="close">
|
<div className="close">
|
||||||
|
@ -4,6 +4,8 @@ import {useState} from "react";
|
|||||||
|
|
||||||
|
|
||||||
import ImageCover from './cover.png'
|
import ImageCover from './cover.png'
|
||||||
|
import {formatDuration, timeFromNow} from "@/util/strings.ts";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
type VideoItemProps = {
|
type VideoItemProps = {
|
||||||
videoInfo: VideoInfo;
|
videoInfo: VideoInfo;
|
||||||
@ -32,13 +34,13 @@ export default function VideoItem(props: VideoItemProps) {
|
|||||||
<Image className={'w-full cursor-pointer'} preview={false} src={ImageCover}/>
|
<Image className={'w-full cursor-pointer'} preview={false} src={ImageCover}/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm py-2 px-3">
|
<div className="text-sm py-2 px-3">
|
||||||
<div className="title my-1 cursor-pointer" onClick={props.onClick}>把丰碑立在人民心中</div>
|
<div className="title my-1 cursor-pointer" onClick={props.onClick}>{props.videoInfo.title}</div>
|
||||||
<div className="info flex justify-between gap-2 text-sm">
|
<div className="info flex justify-between gap-2 text-sm">
|
||||||
<div className="video-time-info text-gray-500">
|
<div className="video-time-info text-gray-500">
|
||||||
<span>时长: 2年半</span>
|
<span>时长: {formatDuration(Math.ceil(props.videoInfo.duration / 1000))}</span>
|
||||||
<span className="ml-1">16小时前</span>
|
<span className="ml-1">{timeFromNow(props.videoInfo.publish_time)}</span>
|
||||||
</div>
|
</div>
|
||||||
{props.onLive && <div className="live-info">
|
{props.videoInfo.status == 3 && <div className="live-info">
|
||||||
<Tag color="processing" className="mr-0">已在直播间</Tag>
|
<Tag color="processing" className="mr-0">已在直播间</Tag>
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {Modal, Pagination} from "antd";
|
import {Empty, Modal, Pagination} from "antd";
|
||||||
import {useRequest} from "ahooks";
|
import {useRequest} from "ahooks";
|
||||||
|
|
||||||
import VideoItem from "@/pages/library/components/video-item.tsx";
|
import VideoItem from "@/pages/library/components/video-item.tsx";
|
||||||
import SearchForm from "@/pages/library/components/search-form.tsx";
|
import SearchForm from "@/pages/library/components/search-form.tsx";
|
||||||
import VideoDetail from "@/pages/library/components/video-detail.tsx";
|
import VideoDetail from "@/pages/library/components/video-detail.tsx";
|
||||||
import {getList} from "@/service/api/video.ts";
|
import {search} from "@/service/api/video.ts";
|
||||||
|
|
||||||
export default function LibraryIndex() {
|
export default function LibraryIndex() {
|
||||||
const [modal, contextHolder] = Modal.useModal();
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
|
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
|
||||||
const {data} = useRequest(()=>getList(),{
|
const [params, setParams] = useState<VideoSearchParams>({
|
||||||
|
time_flag: 0,
|
||||||
|
pagination: {
|
||||||
|
page: 1,
|
||||||
|
limit: 10
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const {data,loading} = useRequest(() => search(params), {
|
||||||
|
refreshDeps: [params]
|
||||||
})
|
})
|
||||||
const handleRemove = (video: VideoInfo) => {
|
const handleRemove = (video: VideoInfo) => {
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
@ -39,9 +46,9 @@ export default function LibraryIndex() {
|
|||||||
{contextHolder}
|
{contextHolder}
|
||||||
<div className="search-form-container mb-5">
|
<div className="search-form-container mb-5">
|
||||||
<SearchForm
|
<SearchForm
|
||||||
onSearch={async () => {
|
onSearch={setParams}
|
||||||
}}
|
|
||||||
onBtnStartClick={handleLive}
|
onBtnStartClick={handleLive}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white rounded p-5">
|
<div className="bg-white rounded p-5">
|
||||||
@ -61,10 +68,24 @@ export default function LibraryIndex() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="video-page-container flex justify-center mt-5">
|
<div className="video-page-container flex justify-center mt-5">
|
||||||
<Pagination defaultCurrent={1} total={50}/>
|
{data?.pagination && data?.pagination.total > 0 ? <div className="flex justify-center mt-10">
|
||||||
|
<Pagination
|
||||||
|
current={params.pagination.page}
|
||||||
|
total={data?.pagination.total}
|
||||||
|
pageSize={data?.pagination.limit}
|
||||||
|
showSizeChanger={false}
|
||||||
|
simple={true}
|
||||||
|
rootClassName={'simple-pagination'}
|
||||||
|
onChange={(page) => setParams(prev=>({...prev,pagination: {page, limit: 10}}))}
|
||||||
|
/>
|
||||||
|
</div> : <div className="py-10">
|
||||||
|
<Empty />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{/*<Pagination defaultCurrent={1} total={50}/>*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<VideoDetail video={detailVideo} onClose={() => setDetailVideo(undefined)}/>
|
{detailVideo && <VideoDetail video={detailVideo} onClose={() => setDetailVideo(undefined)}/>}
|
||||||
</>)
|
</>)
|
||||||
}
|
}
|
@ -1,122 +0,0 @@
|
|||||||
import React, {useState} from 'react';
|
|
||||||
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
|
|
||||||
import {
|
|
||||||
DndContext,
|
|
||||||
closestCenter,
|
|
||||||
KeyboardSensor,
|
|
||||||
PointerSensor,
|
|
||||||
useSensor,
|
|
||||||
useSensors,
|
|
||||||
} from '@dnd-kit/core';
|
|
||||||
|
|
||||||
import {
|
|
||||||
useSortable, arrayMove,
|
|
||||||
SortableContext,
|
|
||||||
sortableKeyboardCoordinates,
|
|
||||||
verticalListSortingStrategy,
|
|
||||||
} from '@dnd-kit/sortable';
|
|
||||||
|
|
||||||
type TestData = {
|
|
||||||
id: number;
|
|
||||||
}
|
|
||||||
type SortableItemProps = {
|
|
||||||
data: TestData;
|
|
||||||
active?: boolean;
|
|
||||||
id: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SortableItem(props: SortableItemProps) {
|
|
||||||
const {
|
|
||||||
attributes,
|
|
||||||
listeners,
|
|
||||||
setNodeRef,
|
|
||||||
transform,
|
|
||||||
transition,
|
|
||||||
} = useSortable({
|
|
||||||
resizeObserverConfig: {},
|
|
||||||
id: props.data.id
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={setNodeRef} style={{
|
|
||||||
transform: `translateY(${transform ? transform?.y:0}px)`,
|
|
||||||
transition,
|
|
||||||
// marginTop:10,
|
|
||||||
// marginBottom:10
|
|
||||||
}} className={props.active ? 'drop-shadow shadow-blue-400 drop-shadow-md' : ''}>
|
|
||||||
<div className="h-[100px] mb-5 border p-5 rounded bg-white flex justify-between items-center">
|
|
||||||
<div className="flex-1">
|
|
||||||
<div>
|
|
||||||
{JSON.stringify(props.data)}
|
|
||||||
</div>
|
|
||||||
<div>{JSON.stringify(transform)}</div>
|
|
||||||
</div>
|
|
||||||
<button {...attributes} {...listeners} className="cursor-move">Move</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SortDemo() {
|
|
||||||
const [items, setItems] = useState<TestData[]>([
|
|
||||||
{id: 1},
|
|
||||||
{id: 2},
|
|
||||||
{id: 3},
|
|
||||||
{id: 4},
|
|
||||||
{id: 5},
|
|
||||||
]);
|
|
||||||
const [activeId, setActiveId] = useState<number>();
|
|
||||||
|
|
||||||
function handleDragEnd(event) {
|
|
||||||
const {active, over} = event;
|
|
||||||
setActiveId(undefined)
|
|
||||||
console.log(JSON.stringify({
|
|
||||||
items,
|
|
||||||
active: active.id,
|
|
||||||
over: over.id,
|
|
||||||
}))
|
|
||||||
if (active.id !== over.id) {
|
|
||||||
setItems((items) => {
|
|
||||||
const oldIndex = items.findIndex(s=>s.id == active.id);
|
|
||||||
const newIndex = items.findIndex(s=>s.id == over.id);
|
|
||||||
const _newArr = arrayMove(items, oldIndex, newIndex);
|
|
||||||
console.log(JSON.stringify({
|
|
||||||
_newArr,
|
|
||||||
items,
|
|
||||||
oldIndex,
|
|
||||||
newIndex
|
|
||||||
}))
|
|
||||||
return _newArr;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// const sensors = useSensors(
|
|
||||||
// useSensor(PointerSensor),
|
|
||||||
// useSensor(KeyboardSensor, {
|
|
||||||
// coordinateGetter: sortableKeyboardCoordinates,
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>{JSON.stringify(items)}</div>
|
|
||||||
<DndContext
|
|
||||||
modifiers={[restrictToVerticalAxis]}
|
|
||||||
onDragEnd={handleDragEnd}
|
|
||||||
onDragStart={e => {
|
|
||||||
if (e.active && e.active.id) {
|
|
||||||
setActiveId(Number(e.active.id))
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SortableContext
|
|
||||||
items={items}
|
|
||||||
>
|
|
||||||
{items.map(it => <SortableItem active={it.id == activeId} key={it.id} data={it} id={it.id}/>)}
|
|
||||||
</SortableContext>
|
|
||||||
</DndContext>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
@ -11,10 +11,13 @@ import ButtonBatch from "@/components/button-batch.tsx";
|
|||||||
import FlvJs from "flv.js";
|
import FlvJs from "flv.js";
|
||||||
import {formatDuration} from "@/util/strings.ts";
|
import {formatDuration} from "@/util/strings.ts";
|
||||||
import {useSetState} from "ahooks";
|
import {useSetState} from "ahooks";
|
||||||
|
import {Player, PlayerInstance} from "@/components/video/player.tsx";
|
||||||
|
|
||||||
const cache: { flvPlayer?: FlvJs.Player,timerPlayNext?:any,timerLoadState?:any,prevUrl?:string } = {}
|
const cache: { flvPlayer?: FlvJs.Player, timerPlayNext?: any, timerLoadState?: any, prevUrl?: string } = {}
|
||||||
export default function LiveIndex() {
|
export default function LiveIndex() {
|
||||||
const videoRef = useRef<HTMLVideoElement | null>(null)
|
const videoRef = useRef<HTMLVideoElement | null>(null)
|
||||||
|
|
||||||
|
const player = useRef<PlayerInstance | null>(null)
|
||||||
const [videoData, setVideoData] = useState<LiveVideoInfo[]>([])
|
const [videoData, setVideoData] = useState<LiveVideoInfo[]>([])
|
||||||
const [modal, contextHolder] = Modal.useModal()
|
const [modal, contextHolder] = Modal.useModal()
|
||||||
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
|
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
|
||||||
@ -25,9 +28,9 @@ export default function LiveIndex() {
|
|||||||
muted: true,
|
muted: true,
|
||||||
})
|
})
|
||||||
const activeIndex = useRef(state.activeIndex)
|
const activeIndex = useRef(state.activeIndex)
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
activeIndex.current = state.activeIndex
|
activeIndex.current = state.activeIndex
|
||||||
},[state.activeIndex])
|
}, [state.activeIndex])
|
||||||
|
|
||||||
const showVideoItem = (index: number) => {
|
const showVideoItem = (index: number) => {
|
||||||
// 找到对应video item 并显示在视图可见区域
|
// 找到对应video item 并显示在视图可见区域
|
||||||
@ -51,7 +54,7 @@ export default function LiveIndex() {
|
|||||||
const activeToNext = (index?: number) => {
|
const activeToNext = (index?: number) => {
|
||||||
const endToFirst = index != undefined && index > -1 ? false : activeIndex.current >= videoData.length - 1
|
const endToFirst = index != undefined && index > -1 ? false : activeIndex.current >= videoData.length - 1
|
||||||
const _activeIndex = index != undefined && index > -1 ? index : (endToFirst ? 0 : activeIndex.current + 1)
|
const _activeIndex = index != undefined && index > -1 ? index : (endToFirst ? 0 : activeIndex.current + 1)
|
||||||
setState({activeIndex:_activeIndex})
|
setState({activeIndex: _activeIndex})
|
||||||
if (endToFirst) {
|
if (endToFirst) {
|
||||||
showToast('即将播放第一条视频');
|
showToast('即将播放第一条视频');
|
||||||
}
|
}
|
||||||
@ -60,51 +63,25 @@ export default function LiveIndex() {
|
|||||||
return _activeIndex;
|
return _activeIndex;
|
||||||
}
|
}
|
||||||
const playVideo = (video: LiveVideoInfo, liveState: LiveState) => {
|
const playVideo = (video: LiveVideoInfo, liveState: LiveState) => {
|
||||||
if (videoRef.current && video.video_oss_url) {
|
if (player.current && video.video_oss_url) {
|
||||||
if(cache.timerPlayNext) clearTimeout(cache.timerPlayNext)
|
if (cache.timerPlayNext) clearTimeout(cache.timerPlayNext)
|
||||||
const duration = Math.ceil(video.video_duration / 1000)
|
const duration = Math.ceil(video.video_duration / 1000)
|
||||||
const playedTime =( Date.now() / 1000 >> 0) - liveState.live_start_time
|
const playedTime = (Date.now() / 1000 >> 0) - liveState.live_start_time
|
||||||
if (playedTime < 0 || playedTime > duration) { // 已播放时间大于总时长了
|
if (playedTime < 0 || playedTime > duration) { // 已播放时间大于总时长了
|
||||||
//initPlayingState() // 重新获取播放状态
|
//initPlayingState() // 重新获取播放状态
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (/mp4$/i.test(video.video_oss_url)) {
|
player.current?.play(video.video_oss_url, playedTime)
|
||||||
videoRef.current!.src = video.video_oss_url
|
cache.timerPlayNext = setTimeout(() => {
|
||||||
if(liveState.live_start_time > 0 && playedTime > 0) videoRef.current!.currentTime = playedTime
|
const index = activeToNext(), nextVideo = videoData[index]
|
||||||
videoRef.current!.play()
|
playVideo(nextVideo, {live_start_time: (Date.now() / 1000 >> 0), id: nextVideo.id})
|
||||||
return;
|
}, (duration - playedTime) * 1000)
|
||||||
}
|
|
||||||
if (FlvJs.isSupported()) {
|
|
||||||
if(cache.prevUrl !== video.video_oss_url) {
|
|
||||||
// 已经有播放实例 则销毁
|
|
||||||
if (cache.flvPlayer) {
|
|
||||||
cache.flvPlayer.pause()
|
|
||||||
cache.flvPlayer.unload()
|
|
||||||
}
|
|
||||||
cache.prevUrl = video.video_oss_url
|
|
||||||
cache.flvPlayer = FlvJs.createPlayer({
|
|
||||||
type: 'flv',
|
|
||||||
url: video.video_oss_url
|
|
||||||
})
|
|
||||||
|
|
||||||
cache.flvPlayer.attachMediaElement(videoRef.current!)
|
|
||||||
cache.flvPlayer.load()
|
|
||||||
}
|
|
||||||
|
|
||||||
if(liveState.live_start_time > 0 && playedTime > 0) videoRef.current!.currentTime = playedTime
|
|
||||||
|
|
||||||
cache.flvPlayer!.play()
|
|
||||||
|
|
||||||
cache.timerPlayNext = setTimeout(()=>{
|
|
||||||
const index = activeToNext(),nextVideo = videoData[index]
|
|
||||||
playVideo(nextVideo,{live_start_time:(Date.now() / 1000 >> 0),id:nextVideo.id})
|
|
||||||
},(duration - playedTime) * 1000)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const initPlayingState = () => {
|
const initPlayingState = () => {
|
||||||
if(cache.timerLoadState) clearTimeout(cache.timerLoadState)
|
if (cache.timerLoadState) clearTimeout(cache.timerLoadState)
|
||||||
if(videoData.length == 0) {
|
if (videoData.length == 0) {
|
||||||
cache.timerLoadState = setTimeout(initPlayingState, 1000)
|
cache.timerLoadState = setTimeout(initPlayingState, 1000)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -119,27 +96,27 @@ export default function LiveIndex() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const clearAllTimer = ()=>{
|
const clearAllTimer = () => {
|
||||||
if(cache.timerPlayNext) clearTimeout(cache.timerPlayNext)
|
if (cache.timerPlayNext) clearTimeout(cache.timerPlayNext)
|
||||||
if(cache.timerLoadState) clearTimeout(cache.timerLoadState)
|
if (cache.timerLoadState) clearTimeout(cache.timerLoadState)
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadList = () => {
|
const loadList = () => {
|
||||||
clearAllTimer();
|
clearAllTimer();
|
||||||
getList().then(res => {
|
getList().then(res => {
|
||||||
// console.log('origin list', res.list.map(s => s.id))
|
// console.log('origin list', res.list.map(s => s.id))
|
||||||
setVideoData(()=>(res.list || []))
|
setVideoData(() => (res.list || []))
|
||||||
setCheckedIdArray([])
|
setCheckedIdArray([])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(initPlayingState,[videoData])
|
useEffect(initPlayingState, [videoData])
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
loadList()
|
loadList()
|
||||||
return clearAllTimer;
|
return clearAllTimer;
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const processDeleteVideo = async (ids: number[]) => {
|
const processDeleteVideo = async (ids: Id[]) => {
|
||||||
deleteByIds(ids).then(() => {
|
deleteByIds(ids).then(() => {
|
||||||
showToast('删除成功!', 'success')
|
showToast('删除成功!', 'success')
|
||||||
loadList()
|
loadList()
|
||||||
@ -177,7 +154,7 @@ export default function LiveIndex() {
|
|||||||
const totalDuration = useMemo(() => {
|
const totalDuration = useMemo(() => {
|
||||||
if (!videoData || videoData.length == 0) return 0;
|
if (!videoData || videoData.length == 0) return 0;
|
||||||
// 计算总时长
|
// 计算总时长
|
||||||
return videoData.reduce((sum, v) => sum + v.video_duration, 0);
|
return videoData.reduce((sum, v) => sum + Math.ceil(v.video_duration / 1000), 0);
|
||||||
}, [videoData])
|
}, [videoData])
|
||||||
|
|
||||||
return (<div className="container py-10 page-live">
|
return (<div className="container py-10 page-live">
|
||||||
@ -186,15 +163,9 @@ export default function LiveIndex() {
|
|||||||
<div className="video-player-container mr-8 flex flex-col">
|
<div className="video-player-container mr-8 flex flex-col">
|
||||||
<div className="text-center text-base">数字人直播间</div>
|
<div className="text-center text-base">数字人直播间</div>
|
||||||
<div className="video-player flex justify-center flex-1 mt-5">
|
<div className="video-player flex justify-center flex-1 mt-5">
|
||||||
<div className="live-player relative rounded overflow-hidden w-[360px] h-[636px]" style={{backgroundColor:'hsl(210, 100%, 48%)'}}>
|
<div className="live-player relative rounded overflow-hidden w-[360px] h-[636px]"
|
||||||
<video ref={videoRef} autoPlay muted={state.muted}
|
style={{backgroundColor: 'hsl(210, 100%, 48%)'}}>
|
||||||
className="w-[360px] rounded overflow-hidden h-full object-contain"></video>
|
<Player ref={player} className="w-[360px] h-[636px] bg-white" muted={true}/>
|
||||||
{state.muted && state.activeIndex != -1 && <div className="absolute inset-0 flex items-center justify-center">
|
|
||||||
<Button onClick={()=>{
|
|
||||||
setState({muted: false})
|
|
||||||
videoRef.current!.muted= false;
|
|
||||||
}}>开启声音</Button>
|
|
||||||
</div>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 text-center text-sm">
|
<div className="mt-4 text-center text-sm">
|
||||||
|
@ -4,63 +4,63 @@ import React, {useEffect, useMemo} from "react";
|
|||||||
|
|
||||||
const prevSelectValues: Id[][] = [];
|
const prevSelectValues: Id[][] = [];
|
||||||
|
|
||||||
function buildValues(options: OptionItem[], selectedValues: Id[][], allValue = -1) {
|
// function buildValues(options: OptionItem[], selectedValues: Id[][], allValue = -1) {
|
||||||
const values: Id[][] = []
|
// const values: Id[][] = []
|
||||||
selectedValues.forEach(item => {
|
// selectedValues.forEach(item => {
|
||||||
if (item.length === 0) {
|
// if (item.length === 0) {
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
if (item.length == 1) {
|
// if (item.length == 1) {
|
||||||
if (item[0] == allValue) {
|
// if (item[0] == allValue) {
|
||||||
values.push([allValue]);
|
// values.push([allValue]);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
// 只有1个值 表示选择了一级分类下的所有二级分类
|
// // 只有1个值 表示选择了一级分类下的所有二级分类
|
||||||
const op = options.find(option => option.value === item[0]);
|
// const op = options.find(option => option.value === item[0]);
|
||||||
if (!op || !op.children || op.children.length === 0) {
|
// if (!op || !op.children || op.children.length === 0) {
|
||||||
// 没有找到或者没有二级分类
|
// // 没有找到或者没有二级分类
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
// 只有一级分类
|
// // 只有一级分类
|
||||||
op.children.forEach(child => {
|
// op.children.forEach(child => {
|
||||||
values.push([item[0], child.value]);
|
// values.push([item[0], child.value]);
|
||||||
})
|
// })
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
values.push(item)
|
// values.push(item)
|
||||||
})
|
// })
|
||||||
return values
|
// return values
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 获取两个数组的差集
|
// 获取两个数组的差集
|
||||||
function getValuesDiff(values: Id[][], prevValues: Id[][]) {
|
// function getValuesDiff(values: Id[][], prevValues: Id[][]) {
|
||||||
if (values.length != prevValues.length) {
|
// if (values.length != prevValues.length) {
|
||||||
const moreItems = values.length > prevValues.length ? values : prevValues;
|
// const moreItems = values.length > prevValues.length ? values : prevValues;
|
||||||
const lessItems = values.length > prevValues.length ? prevValues : values;
|
// const lessItems = values.length > prevValues.length ? prevValues : values;
|
||||||
const lessItemsKeys = lessItems.map(s => s.join('-'));
|
// const lessItemsKeys = lessItems.map(s => s.join('-'));
|
||||||
for (let i = 0; i < moreItems.length; i++) {
|
// for (let i = 0; i < moreItems.length; i++) {
|
||||||
const item = moreItems[i], index = lessItemsKeys.indexOf(item.join('-'));
|
// const item = moreItems[i], index = lessItemsKeys.indexOf(item.join('-'));
|
||||||
if (index === -1) {
|
// if (index === -1) {
|
||||||
return item;
|
// return item;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
function getAllValue(options: OptionItem[]) {
|
// function getAllValue(options: OptionItem[]) {
|
||||||
const values: Id[][] = []
|
// const values: Id[][] = []
|
||||||
options.forEach(option => {
|
// options.forEach(option => {
|
||||||
if (option.children && option.children.length > 0) {
|
// if (option.children && option.children.length > 0) {
|
||||||
option.children.forEach(child => {
|
// option.children.forEach(child => {
|
||||||
values.push([option.value, child.value]);
|
// values.push([option.value, child.value]);
|
||||||
})
|
// })
|
||||||
} else {
|
// } else {
|
||||||
values.push([option.value]);
|
// values.push([option.value]);
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
return values
|
// return values
|
||||||
}
|
// }
|
||||||
|
|
||||||
export default function ArticleCascader(props: {
|
export default function ArticleCascader(props: {
|
||||||
options: OptionItem[];
|
options: OptionItem[];
|
||||||
@ -71,9 +71,9 @@ export default function ArticleCascader(props: {
|
|||||||
// 清除上一次的选中值
|
// 清除上一次的选中值
|
||||||
prevSelectValues.length = 0;
|
prevSelectValues.length = 0;
|
||||||
}, [])
|
}, [])
|
||||||
const allOptionValue = useMemo(() => {
|
// const allOptionValue = useMemo(() => {
|
||||||
return getAllValue(props.options)
|
// return getAllValue(props.options)
|
||||||
}, [props.options])
|
// }, [props.options])
|
||||||
|
|
||||||
const setSelectValues = (value: Id[][]) => {
|
const setSelectValues = (value: Id[][]) => {
|
||||||
_setSelectValues(value)
|
_setSelectValues(value)
|
||||||
|
@ -4,12 +4,13 @@ import {showErrorToast, showToast} from "@/components/message.ts";
|
|||||||
import {push2video} from "@/service/api/article.ts";
|
import {push2video} from "@/service/api/article.ts";
|
||||||
|
|
||||||
|
|
||||||
export default function ButtonPush2Video(props: { ids: Id[] }) {
|
export default function ButtonPush2Video(props: { ids: Id[];onSuccess?:()=>void; }) {
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const handlePush = () => {
|
const handlePush = () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
push2video(props.ids).then(() => {
|
push2video(props.ids).then(() => {
|
||||||
showToast('一键推流成功,已成功推入数字人视频生成,请前往数字人视频生成页面查看!', 'success')
|
showToast('一键推流成功,已成功推入数字人视频生成,请前往数字人视频生成页面查看!', 'success')
|
||||||
|
props.onSuccess?.()
|
||||||
}).catch(showErrorToast).finally(() => {
|
}).catch(showErrorToast).finally(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
|
@ -11,6 +11,11 @@ type SearchPanelProps = {
|
|||||||
const pagination = {
|
const pagination = {
|
||||||
limit: 10, page: 1
|
limit: 10, page: 1
|
||||||
}
|
}
|
||||||
|
const DEFAULT_STATE = {
|
||||||
|
tag_level_1_id: -1,
|
||||||
|
tag_level_2_id: -1,
|
||||||
|
subOptions: []
|
||||||
|
}
|
||||||
export default function SearchPanel({onSearch}: SearchPanelProps) {
|
export default function SearchPanel({onSearch}: SearchPanelProps) {
|
||||||
const tags = useArticleTags();
|
const tags = useArticleTags();
|
||||||
const [params, setParams] = useSetState<ApiArticleSearchParams>({
|
const [params, setParams] = useSetState<ApiArticleSearchParams>({
|
||||||
@ -18,36 +23,26 @@ export default function SearchPanel({onSearch}: SearchPanelProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [state, setState] = useSetState<{
|
const [state, setState] = useSetState<{
|
||||||
source: string | number;
|
tag_level_1_id: number;
|
||||||
|
tag_level_2_id: number;
|
||||||
subOptions: (string | number)[]
|
subOptions: (string | number)[]
|
||||||
}>({
|
}>({...DEFAULT_STATE})
|
||||||
source: -1,
|
|
||||||
subOptions: []
|
|
||||||
})
|
|
||||||
|
|
||||||
// 二级分类
|
// 二级分类
|
||||||
const [subOptions, setSubOptions] = useState<OptionItem[]>([])
|
const [subOptions, setSubOptions] = useState<OptionItem[]>([])
|
||||||
const onFinish = () => {
|
const onFinish = () => {
|
||||||
if(state.source != -1){
|
|
||||||
params.tags = [];
|
|
||||||
state.subOptions.forEach(level2 => {
|
|
||||||
params.tags!.push({
|
|
||||||
level1: state.source,
|
|
||||||
level2
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
params.tags = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearch?.({
|
onSearch?.({
|
||||||
...params
|
...params,
|
||||||
|
tag_level_1_id: state.tag_level_1_id > 0?state.tag_level_1_id:undefined,
|
||||||
|
tag_level_2_id: state.tag_level_2_id > 0?state.tag_level_2_id:undefined,
|
||||||
|
pagination
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 重置
|
// 重置
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
setParams({pagination, title: ''})
|
setParams({pagination, title: ''})
|
||||||
setState({source: -1,subOptions: []})
|
setState({...DEFAULT_STATE})
|
||||||
setSubOptions([])
|
setSubOptions([])
|
||||||
onSearch?.({pagination})
|
onSearch?.({pagination})
|
||||||
}
|
}
|
||||||
@ -84,37 +79,31 @@ export default function SearchPanel({onSearch}: SearchPanelProps) {
|
|||||||
<div className="list-container flex-1">
|
<div className="list-container flex-1">
|
||||||
<div className="news-source-lv-1 flex flex-wrap">
|
<div className="news-source-lv-1 flex flex-wrap">
|
||||||
<div
|
<div
|
||||||
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.source == -1 ? 'bg-blue-500 text-white' : 'hover:bg-gray-100'}`}
|
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_1_id == -1 ? 'bg-blue-500 text-white' : 'hover:bg-gray-100'}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setState({source: -1, subOptions: []})
|
setState({...DEFAULT_STATE})
|
||||||
setSubOptions([])
|
setSubOptions([])
|
||||||
}}>全部</div>
|
}}>全部</div>
|
||||||
{
|
{
|
||||||
tags.filter(s=>s.value !== 999999).map(it => (
|
tags.filter(s=>s.value !== 999999).map(it => (
|
||||||
<div
|
<div
|
||||||
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.source == it.value ? 'bg-blue-500 text-white' : 'hover:bg-gray-100'}`}
|
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_1_id == it.value ? 'bg-blue-500 text-white' : 'hover:bg-gray-100'}`}
|
||||||
key={it.value}
|
key={it.value}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setState({source: it.value, subOptions: []})
|
setState({tag_level_1_id: Number(it.value),tag_level_2_id:-1})
|
||||||
setSubOptions(it.children || [])
|
setSubOptions(it.children || [])
|
||||||
}}>{it.label}</div>)
|
}}>{it.label}</div>)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{state.source != -1 && subOptions.length > 0 && <div className="news-source-lv-2 bg-gray-100 p-2 rounded mt-2 flex flex-wrap">
|
{state.tag_level_1_id != -1 && subOptions.length > 0 && <div className="news-source-lv-2 bg-gray-100 p-2 rounded mt-2 flex flex-wrap">
|
||||||
{
|
{
|
||||||
subOptions.map(it => (
|
subOptions.map(it => (
|
||||||
<div
|
<div
|
||||||
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.subOptions.includes(it.value) ? 'bg-blue-500 text-white' : 'hover:bg-gray-100'}`}
|
className={`filter-item whitespace-nowrap px-2 py-1 mt-1 text-sm mr-1 cursor-pointer rounded ${state.tag_level_2_id == it.value ? 'bg-blue-500 text-white' : 'hover:bg-gray-100'}`}
|
||||||
key={it.value}
|
key={it.value}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const options = [...state.subOptions]
|
setState({tag_level_2_id: Number(it.value)})
|
||||||
if (options.includes(it.value)) {
|
|
||||||
options.splice(options.indexOf(it.value), 1)
|
|
||||||
} else {
|
|
||||||
options.push(it.value)
|
|
||||||
}
|
|
||||||
setState({subOptions: options})
|
|
||||||
}}>{it.label}</div>)
|
}}>{it.label}</div>)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import {Button, Pagination, Table, TableColumnsType, TableProps, Typography} from "antd";
|
import {Button, Pagination, Table, TableColumnsType, TableProps, Typography} from "antd";
|
||||||
|
|
||||||
import {Card} from "@/components/card";
|
import {Card} from "@/components/card";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, {useState} from "react";
|
||||||
import {useRequest} from "ahooks";
|
import {useRequest} from "ahooks";
|
||||||
import {formatTime} from "@/util/strings.ts";
|
import {formatTime} from "@/util/strings.ts";
|
||||||
import ArticleEditModal from "@/components/article/edit-modal.tsx";
|
import ArticleEditModal from "@/components/article/edit-modal.tsx";
|
||||||
import {getList} from "@/service/api/article.ts";
|
import {getList} from "@/service/api/article.ts";
|
||||||
import EditSearchForm from "@/pages/news/components/edit-search-form.tsx";
|
import EditSearchForm from "@/pages/news/components/edit-search-form.tsx";
|
||||||
import ButtonPush2Video from "@/pages/news/components/button-push2video.tsx";
|
import ButtonPush2Video from "@/pages/news/components/button-push2video.tsx";
|
||||||
|
import {Key} from "antd/es/table/interface";
|
||||||
|
|
||||||
|
|
||||||
export default function NewEdit() {
|
export default function NewEdit() {
|
||||||
@ -55,8 +56,8 @@ export default function NewEdit() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const rowSelection: TableProps<ListArticleItem>['rowSelection'] = {
|
const rowSelection: TableProps<ListArticleItem>['rowSelection'] = {
|
||||||
onChange: (selectedRowKeys: Id[]) => {
|
onChange: (selectedRowKeys: Key[]) => {
|
||||||
setSelectedRowKeys(selectedRowKeys)
|
setSelectedRowKeys(selectedRowKeys as Id[])
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ export default function NewEdit() {
|
|||||||
bordered
|
bordered
|
||||||
pagination={false}
|
pagination={false}
|
||||||
/>
|
/>
|
||||||
{data?.pagination.total > 0 && <div className="footer flex justify-between items-center mt-5">
|
{data?.pagination && data?.pagination.total > 0 && <div className="footer flex justify-between items-center mt-5">
|
||||||
<Pagination
|
<Pagination
|
||||||
current={params.pagination.page}
|
current={params.pagination.page}
|
||||||
total={data?.pagination.total}
|
total={data?.pagination.total}
|
||||||
@ -88,7 +89,7 @@ export default function NewEdit() {
|
|||||||
pagination: {page, limit: 10}
|
pagination: {page, limit: 10}
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
<ButtonPush2Video ids={selectedRowKeys}/>
|
<ButtonPush2Video ids={selectedRowKeys} onSuccess={refresh}/>
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
<ArticleEditModal
|
<ArticleEditModal
|
||||||
|
@ -20,7 +20,7 @@ export default function NewsIndex() {
|
|||||||
limit: 10
|
limit: 10
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const [checkedId, setCheckedId] = useState<number[]>([])
|
const [checkedId, setCheckedId] = useState<Id[]>([])
|
||||||
const [activeNews, setActiveNews] = useState<NewsInfo>()
|
const [activeNews, setActiveNews] = useState<NewsInfo>()
|
||||||
|
|
||||||
const [state, setState] = useState<{
|
const [state, setState] = useState<{
|
||||||
@ -66,7 +66,7 @@ export default function NewsIndex() {
|
|||||||
<Checkbox checked={state.checkAll} onChange={e => {
|
<Checkbox checked={state.checkAll} onChange={e => {
|
||||||
setState({checkAll: e.target.checked})
|
setState({checkAll: e.target.checked})
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setCheckedId(data.list.map(item => item.id))
|
setCheckedId(data?.list?.map(item => item.id) || [])
|
||||||
} else {
|
} else {
|
||||||
setCheckedId([])
|
setCheckedId([])
|
||||||
}
|
}
|
||||||
@ -111,14 +111,14 @@ export default function NewsIndex() {
|
|||||||
<div className="info text-gray-300 flex items-center justify-between gap-3 text-sm">
|
<div className="info text-gray-300 flex items-center justify-between gap-3 text-sm">
|
||||||
<div>来源: <span>{item.media_name}</span></div>
|
<div>来源: <span>{item.media_name}</span></div>
|
||||||
{/*<Divider type="vertical" />*/}
|
{/*<Divider type="vertical" />*/}
|
||||||
<div>发布时间: <span>{item.publish_time}</span></div>
|
<div>发布时间: <span>{formatTime(item.publish_time)}</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data?.pagination.total > 0 ? <div className="flex justify-center mt-10">
|
{data?.pagination && data?.pagination.total > 0 ? <div className="flex justify-center mt-10">
|
||||||
<Pagination
|
<Pagination
|
||||||
current={params.pagination.page}
|
current={params.pagination.page}
|
||||||
total={data?.pagination.total}
|
total={data?.pagination.total}
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
import {useRef, useState} from "react";
|
|
||||||
import {Button} from "antd";
|
|
||||||
import FlvJs from "flv.js";
|
|
||||||
|
|
||||||
const list = [
|
|
||||||
{
|
|
||||||
"id": 10,
|
|
||||||
"cover_url": "",
|
|
||||||
"video_id": 51,
|
|
||||||
"video_title": "以军称在加沙地带打死一名哈马斯高级官员",
|
|
||||||
"video_duration": 31910,
|
|
||||||
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185251497659736064.flv",
|
|
||||||
"status": 4,
|
|
||||||
"order_no": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 8,
|
|
||||||
"cover_url": "",
|
|
||||||
"video_id": 43,
|
|
||||||
"video_title": "历时12天史上第三人 尹锡悦总统弹劾案获通过 一文梳理韩国政坛众生相",
|
|
||||||
"video_duration": 728840,
|
|
||||||
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185229869001351168.flv",
|
|
||||||
"status": 4,
|
|
||||||
"order_no": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 9,
|
|
||||||
"cover_url": "",
|
|
||||||
"video_id": 44,
|
|
||||||
"video_title": "推动房地产市场止跌回稳,发力重点在哪里?",
|
|
||||||
"video_duration": 57500,
|
|
||||||
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185229857764810752.flv",
|
|
||||||
"status": 4,
|
|
||||||
"order_no": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 11,
|
|
||||||
"cover_url": "",
|
|
||||||
"video_id": 52,
|
|
||||||
"video_title": "以军称在加沙地带打死一名哈马斯高级官员",
|
|
||||||
"video_duration": 37980,
|
|
||||||
"video_oss_url": "https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185251495390617600.flv",
|
|
||||||
"status": 4,
|
|
||||||
"order_no": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const cache:{
|
|
||||||
flvPlayer?: FlvJs.Player
|
|
||||||
} = {
|
|
||||||
|
|
||||||
}
|
|
||||||
export default function Test() {
|
|
||||||
const videoRef = useRef<HTMLVideoElement | null>(null)
|
|
||||||
const [index, setIndex] = useState(-1)
|
|
||||||
const load = (url: string) => {
|
|
||||||
if (FlvJs.isSupported()) {
|
|
||||||
if(cache.flvPlayer){
|
|
||||||
cache.flvPlayer.pause()
|
|
||||||
cache.flvPlayer.unload()
|
|
||||||
}
|
|
||||||
cache.flvPlayer = FlvJs.createPlayer({
|
|
||||||
type: 'flv',
|
|
||||||
url: url
|
|
||||||
})
|
|
||||||
|
|
||||||
cache.flvPlayer.attachMediaElement(videoRef.current!)
|
|
||||||
cache.flvPlayer.load()
|
|
||||||
cache.flvPlayer.play()
|
|
||||||
}
|
|
||||||
// const url = 'https://staticplus.gachafun.com/ai-collect/composite_video/2024-12-14/1185229869001351168.flv'
|
|
||||||
// if (videoRef.current) {
|
|
||||||
// videoRef.current!.src = url
|
|
||||||
// videoRef.current?.play()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
const play = () => {
|
|
||||||
const next = index >= list.length - 1 ? 0 : index + 1
|
|
||||||
load(list[next].video_oss_url)
|
|
||||||
setIndex(next)
|
|
||||||
}
|
|
||||||
return (<div className="test container m-auto">
|
|
||||||
<div className="my-10">
|
|
||||||
<video controls className="border w-[400px] max-h-[600px]" ref={videoRef}></video>
|
|
||||||
</div>
|
|
||||||
<Button onClick={play}>load {index > -1 ? <span>
|
|
||||||
{index} {list[index].video_title}
|
|
||||||
</span>:''}</Button>
|
|
||||||
</div>)
|
|
||||||
}
|
|
BIN
src/pages/user/components/bg.png
Normal file
BIN
src/pages/user/components/bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
@ -2,7 +2,7 @@ import {useState} from "react";
|
|||||||
import {useNavigate, useSearchParams} from "react-router-dom";
|
import {useNavigate, useSearchParams} from "react-router-dom";
|
||||||
import type {FormProps} from 'antd';
|
import type {FormProps} from 'antd';
|
||||||
import {LockOutlined, UserOutlined} from '@ant-design/icons';
|
import {LockOutlined, UserOutlined} from '@ant-design/icons';
|
||||||
import {Button, Checkbox, Flex, Form, Input} from 'antd';
|
import {Button, Checkbox, Divider, Flex, Form, Input} from 'antd';
|
||||||
import {clsx} from "clsx";
|
import {clsx} from "clsx";
|
||||||
|
|
||||||
import useAuth from "@/hooks/useAuth.ts";
|
import useAuth from "@/hooks/useAuth.ts";
|
||||||
@ -37,7 +37,7 @@ export default function FormLogin() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (<div className="form">
|
return (<div className="form">
|
||||||
<h1 className={'text-center text-2xl pb-10 pt-4'}>欢迎登录</h1>
|
<Divider className=" pb-8 pt-4"><div className={'text-center text-2xl'}>欢迎登录</div></Divider>
|
||||||
<Form<FieldType>
|
<Form<FieldType>
|
||||||
name="basic"
|
name="basic"
|
||||||
style={{maxWidth: 600}}
|
style={{maxWidth: 600}}
|
||||||
@ -72,20 +72,13 @@ export default function FormLogin() {
|
|||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
|
||||||
<div className="absolute text-red-500 text-center inset-x-0" style={{top: -20}}>{error}</div>
|
|
||||||
<Flex justify="space-between" align="center">
|
|
||||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
|
||||||
<Checkbox>记住密码</Checkbox>
|
|
||||||
</Form.Item>
|
|
||||||
<a href="">忘记密码</a>
|
|
||||||
</Flex>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item className={"mt-14"}>
|
||||||
|
|
||||||
|
<div className="absolute text-red-500 text-center inset-x-0" style={{top: -30}}>{error}</div>
|
||||||
<Button disabled={disabled || loading} loading={loading} type="primary" size={'large'} htmlType="submit"
|
<Button disabled={disabled || loading} loading={loading} type="primary" size={'large'} htmlType="submit"
|
||||||
block shape={'round'}>
|
block shape={'round'}>
|
||||||
{login ? '登录中' : '立即登录'}
|
{loading ? '登录中...' : '立即登录'}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
BIN
src/pages/user/components/main-bg.jpg
Normal file
BIN
src/pages/user/components/main-bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
@ -1,9 +1,11 @@
|
|||||||
import styles from './style.module.scss'
|
|
||||||
import FormLogin from "./components/form-login.tsx";
|
|
||||||
import useAuth from "@/hooks/useAuth.ts";
|
|
||||||
import {useNavigate, useSearchParams} from "react-router-dom";
|
import {useNavigate, useSearchParams} from "react-router-dom";
|
||||||
import {useEffect} from "react";
|
import {useEffect} from "react";
|
||||||
|
|
||||||
|
import useAuth from "@/hooks/useAuth.ts";
|
||||||
|
import MainBgImage from './components/bg.png'
|
||||||
|
import styles from './style.module.scss'
|
||||||
|
import FormLogin from "./components/form-login.tsx";
|
||||||
|
|
||||||
export default function UserIndex(){
|
export default function UserIndex(){
|
||||||
const {user} = useAuth();
|
const {user} = useAuth();
|
||||||
const navigate = useNavigate() ;
|
const navigate = useNavigate() ;
|
||||||
@ -14,6 +16,10 @@ export default function UserIndex(){
|
|||||||
}
|
}
|
||||||
}, [user])
|
}, [user])
|
||||||
return (<div className={styles.main}>
|
return (<div className={styles.main}>
|
||||||
|
<div className={"flex-1 ml-[15%]"}>
|
||||||
|
{/*<h2 className="text-4xl mb-10 text-white/90">数字人直播间</h2>*/}
|
||||||
|
<img className="w-[450px]" src={MainBgImage} alt=""/>
|
||||||
|
</div>
|
||||||
<div className={styles.boxLogin}>
|
<div className={styles.boxLogin}>
|
||||||
<FormLogin />
|
<FormLogin />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.main {
|
.main {
|
||||||
@apply py-10;
|
@apply py-10;
|
||||||
background-image: url(https://lf-webcast-platform.bytetos.com/obj/webcast-platform-cdn/ies/webcast_union_platform/static/image/bg.71a36267.png);
|
background-image: url(components/main-bg.jpg);
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
min-height: 500px;
|
min-height: 500px;
|
||||||
|
@ -1,26 +1,32 @@
|
|||||||
import {Button, Modal} from "antd";
|
import {Button, Modal} from "antd";
|
||||||
import React, {useState} from "react";
|
import React, {useState} from "react";
|
||||||
import {showErrorToast, showToast} from "@/components/message.ts";
|
import {showErrorToast, showToast} from "@/components/message.ts";
|
||||||
import {push2room} from "@/service/api/video.ts";
|
import {push2room, VideoStatus} from "@/service/api/video.ts";
|
||||||
|
|
||||||
|
|
||||||
export default function ButtonPush2Room(props: { ids: Id[]}){
|
export default function ButtonPush2Room(props: { ids: Id[]; list: VideoInfo[] }) {
|
||||||
const [loading,setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const handlePush = ()=>{
|
const handlePush = () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
push2room(props.ids).then(()=>{
|
// 只需要已经生成视频的数据id
|
||||||
showToast('一键推流成功,已推流至数字人直播间,请前往数字人直播间页面查看!', 'success')
|
const vids = props.list.filter(v => v.status == VideoStatus.Generated && props.ids.includes(v.id)).map(v => v.id)
|
||||||
}).catch(showErrorToast).finally(()=>{
|
push2room(vids).then(() => {
|
||||||
|
if(props.ids.length == vids.length){
|
||||||
|
showToast('一键推流成功,已推流至数字人直播间,请前往数字人直播间页面查看!', 'success')
|
||||||
|
}else{
|
||||||
|
showToast('选择视频中有部分视频还在生成中无法推送,推流成功视频前往数字人直播间页面查看!', 'success')
|
||||||
|
}
|
||||||
|
}).catch(showErrorToast).finally(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const onPushClick = ()=>{
|
const onPushClick = () => {
|
||||||
if (props.ids.length === 0) {
|
if (props.ids.length === 0) {
|
||||||
showToast('请选择要推流的新闻', 'warning')
|
showToast('请选择要推流的新闻', 'warning')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title:'操作提示',
|
title: '操作提示',
|
||||||
content: '是否确定一键推流选中新闻视频??',
|
content: '是否确定一键推流选中新闻视频??',
|
||||||
onOk: handlePush
|
onOk: handlePush
|
||||||
})
|
})
|
||||||
|
@ -8,18 +8,18 @@ import {clsx} from "clsx";
|
|||||||
|
|
||||||
import {VideoListItem} from "@/components/video/video-list-item.tsx";
|
import {VideoListItem} from "@/components/video/video-list-item.tsx";
|
||||||
import ArticleEditModal from "@/components/article/edit-modal.tsx";
|
import ArticleEditModal from "@/components/article/edit-modal.tsx";
|
||||||
import {deleteByIds, getList, modifyOrder, push2room} from "@/service/api/video.ts";
|
import {deleteByIds, getList, modifyOrder, VideoStatus} from "@/service/api/video.ts";
|
||||||
import {formatDuration} from "@/util/strings.ts";
|
import {formatDuration} from "@/util/strings.ts";
|
||||||
import ButtonBatch from "@/components/button-batch.tsx";
|
import ButtonBatch from "@/components/button-batch.tsx";
|
||||||
import {showToast} from "@/components/message.ts";
|
import {showToast} from "@/components/message.ts";
|
||||||
import FlvJs from "flv.js";
|
import {Player, PlayerInstance} from "@/components/video/player.tsx";
|
||||||
|
import ButtonPush2Room from "@/pages/video/components/button-push2room.tsx";
|
||||||
|
|
||||||
const cache:{flvPlayer?: FlvJs.Player} = {}
|
|
||||||
export default function VideoIndex() {
|
export default function VideoIndex() {
|
||||||
const [editId, setEditId] = useState(-1)
|
const [editId, setEditId] = useState(-1)
|
||||||
const [videoData, setVideoData] = useState<VideoInfo[]>([])
|
const [videoData, setVideoData] = useState<VideoInfo[]>([])
|
||||||
const [modal, contextHolder] = Modal.useModal()
|
const [modal, contextHolder] = Modal.useModal()
|
||||||
const videoRef = useRef<HTMLVideoElement | null>(null)
|
const player = useRef<PlayerInstance|null>(null)
|
||||||
const [state, setState] = useSetState({
|
const [state, setState] = useSetState({
|
||||||
checkedAll: false,
|
checkedAll: false,
|
||||||
playingIndex: -1,
|
playingIndex: -1,
|
||||||
@ -27,34 +27,27 @@ export default function VideoIndex() {
|
|||||||
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
|
const [checkedIdArray, setCheckedIdArray] = useState<number[]>([])
|
||||||
|
|
||||||
// 加载列表
|
// 加载列表
|
||||||
const loadList = () => {
|
const loadList = (needReset = true) => {
|
||||||
getList().then((ret) => {
|
getList().then((ret) => {
|
||||||
setCheckedIdArray([])
|
const list = ret.list || []
|
||||||
setVideoData(ret.list || [])
|
setVideoData(list)
|
||||||
setState({checkedAll: false, playingIndex: -1})
|
if (needReset) {
|
||||||
|
setCheckedIdArray([])
|
||||||
|
setState({checkedAll: false, playingIndex: -1})
|
||||||
|
}
|
||||||
|
// 判断是否有生成中的视频
|
||||||
|
if (list.filter(s => s.status == VideoStatus.Generating).length > 0) {
|
||||||
|
// 每5s重新获取一次最新数据
|
||||||
|
setTimeout(() => loadList(false), 5000)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 播放视频
|
// 播放视频
|
||||||
const playVideo = (video: VideoInfo, playingIndex: number) => {
|
const playVideo = (video: VideoInfo, playingIndex: number) => {
|
||||||
if (videoRef.current && video.oss_video_url) {
|
if (video.oss_video_url && video.status !== 1) {
|
||||||
setState({playingIndex})
|
setState({playingIndex})
|
||||||
if (FlvJs.isSupported()) {
|
player.current?.play(video.oss_video_url, 0)
|
||||||
// 已经有播放实例 则销毁
|
|
||||||
if(cache.flvPlayer){
|
|
||||||
cache.flvPlayer.pause()
|
|
||||||
cache.flvPlayer.unload()
|
|
||||||
}
|
|
||||||
cache.flvPlayer = FlvJs.createPlayer({
|
|
||||||
type: 'flv',
|
|
||||||
url: video.oss_video_url
|
|
||||||
})
|
|
||||||
|
|
||||||
cache.flvPlayer.attachMediaElement(videoRef.current!)
|
|
||||||
cache.flvPlayer.load()
|
|
||||||
cache.flvPlayer.play()
|
|
||||||
}
|
|
||||||
videoRef.current!.src = video.oss_video_url
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 处理全选
|
// 处理全选
|
||||||
@ -77,13 +70,13 @@ export default function VideoIndex() {
|
|||||||
const totalDuration = useMemo(() => {
|
const totalDuration = useMemo(() => {
|
||||||
if (!videoData || videoData.length == 0) return 0;
|
if (!videoData || videoData.length == 0) return 0;
|
||||||
// 计算总时长
|
// 计算总时长
|
||||||
return videoData.reduce((sum, v) => sum + v.duration, 0);
|
return videoData.reduce((sum, v) => sum + Math.ceil(v.duration / 1000), 0);
|
||||||
}, [videoData])
|
}, [videoData])
|
||||||
|
|
||||||
return (<div className="container py-10 page-live">
|
return (<div className="container py-10 page-live">
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="video-list-container bg-white p-10 rounded flex-1">
|
<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="live-control flex justify-between mb-5">
|
||||||
<div className="pl-[70px]">
|
<div className="pl-[70px]">
|
||||||
<span>视频时长: {formatDuration(totalDuration)}</span>
|
<span>视频时长: {formatDuration(totalDuration)}</span>
|
||||||
@ -94,8 +87,8 @@ export default function VideoIndex() {
|
|||||||
selected={checkedIdArray}
|
selected={checkedIdArray}
|
||||||
emptyMessage={`请选择要删除的新闻视频`}
|
emptyMessage={`请选择要删除的新闻视频`}
|
||||||
confirmMessage={`是否删除当前的${checkedIdArray.length}个新闻视频?`}
|
confirmMessage={`是否删除当前的${checkedIdArray.length}个新闻视频?`}
|
||||||
onSuccess={()=>{
|
onSuccess={() => {
|
||||||
showToast('删除成功!','success')
|
showToast('删除成功!', 'success')
|
||||||
loadList()
|
loadList()
|
||||||
}}
|
}}
|
||||||
>批量删除</ButtonBatch>
|
>批量删除</ButtonBatch>
|
||||||
@ -106,7 +99,7 @@ export default function VideoIndex() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'video-list-sort-container'}>
|
<div className={'video-list-sort-container flex-1'}>
|
||||||
<div className="flex my-2">
|
<div className="flex my-2">
|
||||||
{videoData.length == 0 ? <div className="m-auto"><Empty/></div> : <>
|
{videoData.length == 0 ? <div className="m-auto"><Empty/></div> : <>
|
||||||
<div className="sort-number-container mr-2">
|
<div className="sort-number-container mr-2">
|
||||||
@ -146,6 +139,7 @@ export default function VideoIndex() {
|
|||||||
index={index + 1}
|
index={index + 1}
|
||||||
id={v.id}
|
id={v.id}
|
||||||
key={index}
|
key={index}
|
||||||
|
type={'create'}
|
||||||
active={state.playingIndex == index}
|
active={state.playingIndex == index}
|
||||||
checked={checkedIdArray.includes(v.id)}
|
checked={checkedIdArray.includes(v.id)}
|
||||||
className={`list-item-${index} mt-3 mb-2`}
|
className={`list-item-${index} mt-3 mb-2`}
|
||||||
@ -170,22 +164,28 @@ export default function VideoIndex() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right mt-5">
|
<div className="text-right mt-5">
|
||||||
<ButtonBatch
|
{/*<ButtonBatch*/}
|
||||||
type={'primary'}
|
{/* type={'primary'}*/}
|
||||||
onProcess={push2room}
|
{/* onProcess={push2room}*/}
|
||||||
selected={checkedIdArray}
|
{/* selected={checkedIdArray}*/}
|
||||||
emptyMessage={`请选择要推流的新闻`}
|
{/* emptyMessage={`请选择要推流的新闻`}*/}
|
||||||
confirmMessage={`是否确定一键推流选中新闻视频?`}
|
{/* confirmMessage={`是否确定一键推流选中新闻视频?`}*/}
|
||||||
onSuccess={loadList}
|
{/* onSuccess={loadList}*/}
|
||||||
>一键推流</ButtonBatch>
|
{/*>一键推流</ButtonBatch>*/}
|
||||||
{/*<ButtonPush2Room ids={checkedIdArray}/>*/}
|
<ButtonPush2Room ids={checkedIdArray}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="video-player-container ml-16 min-w-[360px] flex flex-col">
|
<div className="video-player-container ml-16 w-[360px] flex flex-col">
|
||||||
<div className="text-center text-base mt-10">预览视频</div>
|
<div className="text-center text-base">预览视频</div>
|
||||||
<div className="video-player flex items-center mt-20">
|
<div className="video-player flex items-center mt-2">
|
||||||
<div className=" rounded overflow-hidden">
|
<div className=" w-[360px] h-[640px] rounded overflow-hidden">
|
||||||
<video ref={videoRef} controls autoPlay className="w-full bg-white min-w-[360px]"></video>
|
{/*<video ref={videoRef} poster={videoData[state.playingIndex]?.cover} preload="auto" playsinline webkit-playsinline className="w-full bg-white w-[360px] h-[640px]"></video>*/}
|
||||||
|
<Player
|
||||||
|
ref={player} url={videoData[state.playingIndex]?.oss_video_url}
|
||||||
|
onChange={(state) => {
|
||||||
|
if (state.end || state.end) setState({playingIndex: -1})
|
||||||
|
}}
|
||||||
|
className="w-[360px] h-[640px] bg-white"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +17,7 @@ const NavItems = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'create',
|
key: 'create',
|
||||||
name: 'AI视频',
|
name: '视频生成',
|
||||||
icon: 'ai',
|
icon: 'ai',
|
||||||
path:'/create'
|
path:'/create'
|
||||||
},
|
},
|
||||||
|
@ -1,23 +1,22 @@
|
|||||||
import {RouteObject} from "react-router-dom";
|
import {RouteObject} from "react-router-dom";
|
||||||
import ErrorBoundary from "@/routes/error.tsx";
|
import ErrorBoundary from "@/routes/error.tsx";
|
||||||
import UserAuth from "@/pages/user";
|
|
||||||
import Test from "@/pages/test";
|
;
|
||||||
import CreateIndex from "../pages/video";
|
|
||||||
import LibraryIndex from "@/pages/library";
|
|
||||||
import LiveIndex from "@/pages/live";
|
|
||||||
import NewsIndex from "@/pages/news";
|
|
||||||
import NewsEdit from "@/pages/news/edit.tsx";
|
|
||||||
import DashboardLayout from "@/routes/layout/dashboard-layout.tsx";
|
import DashboardLayout from "@/routes/layout/dashboard-layout.tsx";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const UserAuth = React.lazy(() => import("@/pages/user"))
|
||||||
|
const CreateVideoIndex = React.lazy(() => import("@/pages/video"))
|
||||||
|
const LibraryIndex = React.lazy(() => import("@/pages/library"))
|
||||||
|
const LiveIndex = React.lazy(() => import("@/pages/live"))
|
||||||
|
const NewsIndex = React.lazy(() => import("@/pages/news"))
|
||||||
|
const NewsEdit = React.lazy(() => import("@/pages/news/edit.tsx"))
|
||||||
|
|
||||||
const routes: RouteObject[] = [
|
const routes: RouteObject[] = [
|
||||||
{
|
{
|
||||||
path: '/user',
|
path: '/user',
|
||||||
element: <UserAuth/>,
|
element: <UserAuth/>,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/test',
|
|
||||||
element: <Test/>,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: <DashboardLayout/>,
|
element: <DashboardLayout/>,
|
||||||
@ -33,7 +32,7 @@ const routes: RouteObject[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'create',
|
path: 'create',
|
||||||
element: <CreateIndex/>
|
element: <CreateVideoIndex/>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'library',
|
path: 'library',
|
||||||
|
@ -3,7 +3,9 @@ import {post} from "@/service/request.ts";
|
|||||||
export function getList() {
|
export function getList() {
|
||||||
return post<DataList<VideoInfo>>('/video/list')
|
return post<DataList<VideoInfo>>('/video/list')
|
||||||
}
|
}
|
||||||
|
export function search(params:VideoSearchParams) {
|
||||||
|
return post<DataList<VideoInfo>>('/video/search',params)
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 视频列表的文章编辑(需要重新生成视频)
|
* 视频列表的文章编辑(需要重新生成视频)
|
||||||
* @param title
|
* @param title
|
||||||
@ -36,4 +38,11 @@ export function modifyOrder(ids: Id[]) {
|
|||||||
|
|
||||||
export function push2room(video_ids: Id[]) {
|
export function push2room(video_ids: Id[]) {
|
||||||
return post('/video/push2room', {video_ids})
|
return post('/video/push2room', {video_ids})
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum VideoStatus {
|
||||||
|
// 生成中
|
||||||
|
Generating = 1,
|
||||||
|
// 已生成
|
||||||
|
Generated = 2,
|
||||||
}
|
}
|
@ -2,7 +2,6 @@ import axios from 'axios';
|
|||||||
import {stringify} from 'qs'
|
import {stringify} from 'qs'
|
||||||
import {BizError} from './types';
|
import {BizError} from './types';
|
||||||
import {getAuthToken} from "@/hooks/useAuth.ts";
|
import {getAuthToken} from "@/hooks/useAuth.ts";
|
||||||
import {showToast} from "@/components/message.ts";
|
|
||||||
|
|
||||||
const JSON_FORMAT: string = 'application/json';
|
const JSON_FORMAT: string = 'application/json';
|
||||||
const REQUEST_TIMEOUT = 300000; // 超时时长5min
|
const REQUEST_TIMEOUT = 300000; // 超时时长5min
|
||||||
|
18
src/types/api.d.ts
vendored
18
src/types/api.d.ts
vendored
@ -6,10 +6,10 @@ declare interface ApiRequestPageParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare interface ApiArticleSearchParams extends ApiRequestPageParams{
|
declare interface ApiArticleSearchParams extends ApiRequestPageParams{
|
||||||
// // 1级标签id
|
// 1级标签id
|
||||||
// tag_level_1_id?: number;
|
tag_level_1_id?: number;
|
||||||
// // 2级标签id 没有则为0
|
// 2级标签id 没有则为0
|
||||||
// tag_level_2_id?: number;
|
tag_level_2_id?: number;
|
||||||
tags?: {
|
tags?: {
|
||||||
level1: Id;
|
level1: Id;
|
||||||
level2: Id;
|
level2: Id;
|
||||||
@ -80,18 +80,26 @@ declare interface ListCrawlerNewsItem extends BasicArticleInfo {
|
|||||||
// 内部文章关联id
|
// 内部文章关联id
|
||||||
internal_article_id: number;
|
internal_article_id: number;
|
||||||
}
|
}
|
||||||
|
declare interface VideoSearchParams extends ApiRequestPageParams{
|
||||||
|
// 标题
|
||||||
|
title?: string;
|
||||||
|
time_flag?: number;
|
||||||
|
}
|
||||||
declare interface VideoInfo {
|
declare interface VideoInfo {
|
||||||
id: number;
|
id: number;
|
||||||
|
video_title: string;
|
||||||
title: string;
|
title: string;
|
||||||
cover: string;
|
cover: string;
|
||||||
oss_video_url: string;
|
oss_video_url: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
article_id: number;
|
article_id: number;
|
||||||
status: number;
|
status: number;
|
||||||
|
publish_time?: number|string;
|
||||||
}
|
}
|
||||||
// room live
|
// room live
|
||||||
declare interface LiveVideoInfo {
|
declare interface LiveVideoInfo {
|
||||||
id: number;
|
id: number;
|
||||||
|
title: string;
|
||||||
video_id: number;
|
video_id: number;
|
||||||
video_title: string;
|
video_title: string;
|
||||||
cover: string;
|
cover: string;
|
||||||
@ -103,5 +111,5 @@ declare interface LiveVideoInfo {
|
|||||||
|
|
||||||
declare interface LiveState{
|
declare interface LiveState{
|
||||||
id: number;
|
id: number;
|
||||||
live_start_time?: number;
|
live_start_time: number;
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,7 @@ export function formatTime(time:any,template = 'YYYY-MM-DD HH:mm:ss') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function timeFromNow(time: any) {
|
export function timeFromNow(time: any) {
|
||||||
|
if(!time) return '';
|
||||||
return getDayjs(time).fromNow();
|
return getDayjs(time).fromNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,9 @@
|
|||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": false,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
|
@ -4,44 +4,50 @@ import {resolve} from "path";
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(({mode}) => {
|
export default defineConfig(({mode}) => {
|
||||||
return {
|
const devServerHost = mode == 'test' ? '124.220.14.192' : '192.168.0.231:9999'
|
||||||
plugins: [react()],
|
const AUTH_TOKEN_KEY = mode == 'production' ? 'digital-person-token' : `digital-person-token_${mode}`
|
||||||
base: process.env.PUBLIC_PATH || (mode == 'relative' ? './' : '/'),
|
|
||||||
define: {
|
if (mode !== 'production') {
|
||||||
AppConfig: JSON.stringify({
|
console.log('dev server is', devServerHost,mode)
|
||||||
SITE_URL: process.env.APP_SITE_URL || null,
|
}
|
||||||
API_PREFIX: process.env.APP_API_PREFIX || '/mgmt/v1/metahuman',
|
return {
|
||||||
AUTH_TOKEN_KEY: process.env.AUTH_TOKEN_KEY || 'digital-person-token',
|
plugins: [react()],
|
||||||
AUTHED_PERSON_DATA_KEY: process.env.AUTHED_PERSON_DATA_KEY || 'digital-person-user-info',
|
base: process.env.PUBLIC_PATH || (mode == 'relative' ? './' : '/'),
|
||||||
}),
|
define: {
|
||||||
AppMode: JSON.stringify(mode)
|
AppConfig: JSON.stringify({
|
||||||
},
|
SITE_URL: process.env.APP_SITE_URL || null,
|
||||||
resolve: {
|
API_PREFIX: process.env.APP_API_PREFIX || '/mgmt/v1/metahuman',
|
||||||
alias: {
|
AUTH_TOKEN_KEY: process.env.AUTH_TOKEN_KEY || AUTH_TOKEN_KEY,
|
||||||
'@': resolve(__dirname, './src')
|
AUTHED_PERSON_DATA_KEY: process.env.AUTHED_PERSON_DATA_KEY || 'digital-person-user-info',
|
||||||
}
|
}),
|
||||||
},
|
AppMode: JSON.stringify(mode)
|
||||||
css:{
|
},
|
||||||
preprocessorOptions:{
|
resolve: {
|
||||||
scss:{
|
alias: {
|
||||||
api:'modern'
|
'@': resolve(__dirname, './src')
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
css: {
|
||||||
server: {
|
preprocessorOptions: {
|
||||||
port: 10021,
|
scss: {
|
||||||
proxy: {
|
api: 'modern'
|
||||||
'/mgmt': {
|
}
|
||||||
target: 'http://192.168.0.231:9999',
|
}
|
||||||
changeOrigin: true,
|
},
|
||||||
// rewrite: (path) => path.replace(/^\/api/, '')
|
server: {
|
||||||
},
|
port: 10021,
|
||||||
'/api': {
|
proxy: {
|
||||||
target: 'http://192.168.0.231:9999',
|
'/mgmt': {
|
||||||
changeOrigin: true,
|
target: `http://${devServerHost}`, // http://124.220.14.192/ 192.168.0.231:9999
|
||||||
// rewrite: (path) => path.replace(/^\/api/, '')
|
changeOrigin: true,
|
||||||
}
|
// rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
}
|
},
|
||||||
}
|
'/api': {
|
||||||
}
|
target: `http://${devServerHost}`,
|
||||||
|
changeOrigin: true,
|
||||||
|
// rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
214
yarn.lock
214
yarn.lock
@ -1137,6 +1137,14 @@ axios@^1.7.7:
|
|||||||
form-data "^4.0.0"
|
form-data "^4.0.0"
|
||||||
proxy-from-env "^1.1.0"
|
proxy-from-env "^1.1.0"
|
||||||
|
|
||||||
|
babel-runtime@^6.9.2:
|
||||||
|
version "6.26.0"
|
||||||
|
resolved "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
|
||||||
|
integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==
|
||||||
|
dependencies:
|
||||||
|
core-js "^2.4.0"
|
||||||
|
regenerator-runtime "^0.11.0"
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
@ -1147,6 +1155,11 @@ binary-extensions@^2.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
||||||
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
|
integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
|
||||||
|
|
||||||
|
blueimp-md5@^2.10.0:
|
||||||
|
version "2.19.0"
|
||||||
|
resolved "https://registry.npmmirror.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0"
|
||||||
|
integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==
|
||||||
|
|
||||||
brace-expansion@^1.1.7:
|
brace-expansion@^1.1.7:
|
||||||
version "1.1.11"
|
version "1.1.11"
|
||||||
resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||||
@ -1213,6 +1226,11 @@ chalk@^4.0.0:
|
|||||||
ansi-styles "^4.1.0"
|
ansi-styles "^4.1.0"
|
||||||
supports-color "^7.1.0"
|
supports-color "^7.1.0"
|
||||||
|
|
||||||
|
charenc@0.0.2:
|
||||||
|
version "0.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
||||||
|
integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==
|
||||||
|
|
||||||
chokidar@^3.6.0:
|
chokidar@^3.6.0:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
||||||
@ -1291,6 +1309,11 @@ copy-to-clipboard@^3.3.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
toggle-selection "^1.0.6"
|
toggle-selection "^1.0.6"
|
||||||
|
|
||||||
|
core-js@^2.4.0:
|
||||||
|
version "2.6.12"
|
||||||
|
resolved "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
|
||||||
|
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
|
||||||
|
|
||||||
core-util-is@~1.0.0:
|
core-util-is@~1.0.0:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
||||||
@ -1305,6 +1328,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
|||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
which "^2.0.1"
|
||||||
|
|
||||||
|
crypt@0.0.2:
|
||||||
|
version "0.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
|
||||||
|
integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==
|
||||||
|
|
||||||
cssesc@^3.0.0:
|
cssesc@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||||
@ -1327,6 +1355,11 @@ debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.3"
|
ms "^2.1.3"
|
||||||
|
|
||||||
|
decode-uri-component@^0.2.0:
|
||||||
|
version "0.2.2"
|
||||||
|
resolved "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
|
||||||
|
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
|
||||||
|
|
||||||
deep-is@^0.1.3:
|
deep-is@^0.1.3:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||||
@ -1380,6 +1413,11 @@ doctrine@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
|
|
||||||
|
dom-walk@^0.1.0:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.npmmirror.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84"
|
||||||
|
integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==
|
||||||
|
|
||||||
eastasianwidth@^0.2.0:
|
eastasianwidth@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||||
@ -1412,6 +1450,11 @@ es-errors@^1.3.0:
|
|||||||
resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
|
||||||
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
|
||||||
|
|
||||||
|
es5-shim@^4.5.1:
|
||||||
|
version "4.6.7"
|
||||||
|
resolved "https://registry.npmmirror.com/es5-shim/-/es5-shim-4.6.7.tgz#bc67ae0fc3dd520636e0a1601cc73b450ad3e955"
|
||||||
|
integrity sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==
|
||||||
|
|
||||||
es6-promise@^4.2.8:
|
es6-promise@^4.2.8:
|
||||||
version "4.2.8"
|
version "4.2.8"
|
||||||
resolved "https://registry.npmmirror.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
|
resolved "https://registry.npmmirror.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
|
||||||
@ -1734,6 +1777,22 @@ glob@^7.1.3:
|
|||||||
once "^1.3.0"
|
once "^1.3.0"
|
||||||
path-is-absolute "^1.0.0"
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
|
global@4.3.2, global@~4.3.0:
|
||||||
|
version "4.3.2"
|
||||||
|
resolved "https://registry.npmmirror.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f"
|
||||||
|
integrity sha512-/4AybdwIDU4HkCUbJkZdWpe4P6vuw/CUtu+0I1YlLIPe7OlUO7KNJ+q/rO70CW2/NW6Jc6I62++Hzsf5Alu6rQ==
|
||||||
|
dependencies:
|
||||||
|
min-document "^2.19.0"
|
||||||
|
process "~0.5.1"
|
||||||
|
|
||||||
|
global@^4.3.1:
|
||||||
|
version "4.4.0"
|
||||||
|
resolved "https://registry.npmmirror.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
|
||||||
|
integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==
|
||||||
|
dependencies:
|
||||||
|
min-document "^2.19.0"
|
||||||
|
process "^0.11.10"
|
||||||
|
|
||||||
globals@^11.1.0:
|
globals@^11.1.0:
|
||||||
version "11.12.0"
|
version "11.12.0"
|
||||||
resolved "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
resolved "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
|
||||||
@ -1827,6 +1886,11 @@ imurmurhash@^0.1.4:
|
|||||||
resolved "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
resolved "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||||
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
|
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
|
||||||
|
|
||||||
|
individual@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/individual/-/individual-2.0.0.tgz#833b097dad23294e76117a98fb38e0d9ad61bb97"
|
||||||
|
integrity sha512-pWt8hBCqJsUWI/HtcfWod7+N9SgAqyPEaF7JQjwzjn5vGrpg6aQ5qeAFQ7dx//UH4J1O+7xqew+gCeeFt6xN/g==
|
||||||
|
|
||||||
inflight@^1.0.4:
|
inflight@^1.0.4:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||||
@ -1852,6 +1916,11 @@ is-binary-path@~2.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
binary-extensions "^2.0.0"
|
binary-extensions "^2.0.0"
|
||||||
|
|
||||||
|
is-buffer@~1.1.6:
|
||||||
|
version "1.1.6"
|
||||||
|
resolved "https://registry.npmmirror.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||||
|
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
|
||||||
|
|
||||||
is-core-module@^2.13.0:
|
is-core-module@^2.13.0:
|
||||||
version "2.15.1"
|
version "2.15.1"
|
||||||
resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37"
|
resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37"
|
||||||
@ -1869,6 +1938,11 @@ is-fullwidth-code-point@^3.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||||
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
|
||||||
|
|
||||||
|
is-function@^1.0.1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08"
|
||||||
|
integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==
|
||||||
|
|
||||||
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
|
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
|
||||||
@ -1927,6 +2001,11 @@ js-yaml@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
argparse "^2.0.1"
|
argparse "^2.0.1"
|
||||||
|
|
||||||
|
jsencrypt@^3.2.0:
|
||||||
|
version "3.3.2"
|
||||||
|
resolved "https://registry.npmmirror.com/jsencrypt/-/jsencrypt-3.3.2.tgz#b0f1a2278810c7ba1cb8957af11195354622df7c"
|
||||||
|
integrity sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A==
|
||||||
|
|
||||||
jsesc@^3.0.2:
|
jsesc@^3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.npmmirror.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
|
resolved "https://registry.npmmirror.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e"
|
||||||
@ -2047,6 +2126,15 @@ lru-cache@^5.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist "^3.0.2"
|
yallist "^3.0.2"
|
||||||
|
|
||||||
|
md5@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.npmmirror.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f"
|
||||||
|
integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==
|
||||||
|
dependencies:
|
||||||
|
charenc "0.0.2"
|
||||||
|
crypt "0.0.2"
|
||||||
|
is-buffer "~1.1.6"
|
||||||
|
|
||||||
memoize-one@^5.1.1:
|
memoize-one@^5.1.1:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.npmmirror.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
resolved "https://registry.npmmirror.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
|
||||||
@ -2077,6 +2165,13 @@ mime-types@^2.1.12:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mime-db "1.52.0"
|
mime-db "1.52.0"
|
||||||
|
|
||||||
|
min-document@^2.19.0:
|
||||||
|
version "2.19.0"
|
||||||
|
resolved "https://registry.npmmirror.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
|
||||||
|
integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==
|
||||||
|
dependencies:
|
||||||
|
dom-walk "^0.1.0"
|
||||||
|
|
||||||
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||||
@ -2140,7 +2235,7 @@ normalize-range@^0.1.2:
|
|||||||
resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
|
resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
|
||||||
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
|
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
|
||||||
|
|
||||||
object-assign@^4.0.1, object-assign@^4.1.1:
|
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||||
@ -2205,6 +2300,11 @@ parent-module@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
callsites "^3.0.0"
|
callsites "^3.0.0"
|
||||||
|
|
||||||
|
parse-headers@^2.0.0:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.npmmirror.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9"
|
||||||
|
integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==
|
||||||
|
|
||||||
path-exists@^4.0.0:
|
path-exists@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
resolved "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
||||||
@ -2321,6 +2421,16 @@ process-nextick-args@~2.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||||
|
|
||||||
|
process@^0.11.10:
|
||||||
|
version "0.11.10"
|
||||||
|
resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
|
||||||
|
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
|
||||||
|
|
||||||
|
process@~0.5.1:
|
||||||
|
version "0.5.2"
|
||||||
|
resolved "https://registry.npmmirror.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
|
||||||
|
integrity sha512-oNpcutj+nYX2FjdEW7PGltWhXulAnFlM0My/k48L90hARCOJtvBbQXc/6itV2jDvU5xAAtonP+r6wmQgCcbAUA==
|
||||||
|
|
||||||
prop-types@^15.7.2:
|
prop-types@^15.7.2:
|
||||||
version "15.8.1"
|
version "15.8.1"
|
||||||
resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||||
@ -2347,6 +2457,15 @@ qs@^6.12.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
side-channel "^1.0.6"
|
side-channel "^1.0.6"
|
||||||
|
|
||||||
|
query-string@^5.0.1:
|
||||||
|
version "5.1.1"
|
||||||
|
resolved "https://registry.npmmirror.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
|
||||||
|
integrity sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==
|
||||||
|
dependencies:
|
||||||
|
decode-uri-component "^0.2.0"
|
||||||
|
object-assign "^4.1.0"
|
||||||
|
strict-uri-encode "^1.0.0"
|
||||||
|
|
||||||
queue-microtask@^1.2.2:
|
queue-microtask@^1.2.2:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||||
@ -2795,6 +2914,11 @@ readdirp@~3.6.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
picomatch "^2.2.1"
|
picomatch "^2.2.1"
|
||||||
|
|
||||||
|
regenerator-runtime@^0.11.0:
|
||||||
|
version "0.11.1"
|
||||||
|
resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||||
|
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
||||||
|
|
||||||
regenerator-runtime@^0.14.0:
|
regenerator-runtime@^0.14.0:
|
||||||
version "0.14.1"
|
version "0.14.1"
|
||||||
resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
|
resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f"
|
||||||
@ -2865,11 +2989,25 @@ run-parallel@^1.1.9:
|
|||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask "^1.2.2"
|
queue-microtask "^1.2.2"
|
||||||
|
|
||||||
|
rust-result@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/rust-result/-/rust-result-1.0.0.tgz#34c75b2e6dc39fe5875e5bdec85b5e0f91536f72"
|
||||||
|
integrity sha512-6cJzSBU+J/RJCF063onnQf0cDUOHs9uZI1oroSGnHOph+CQTIJ5Pp2hK5kEQq1+7yE/EEWfulSNXAQ2jikPthA==
|
||||||
|
dependencies:
|
||||||
|
individual "^2.0.0"
|
||||||
|
|
||||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||||
|
|
||||||
|
safe-json-parse@4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.npmmirror.com/safe-json-parse/-/safe-json-parse-4.0.0.tgz#7c0f578cfccd12d33a71c0e05413e2eca171eaac"
|
||||||
|
integrity sha512-RjZPPHugjK0TOzFrLZ8inw44s9bKox99/0AZW9o/BEQVrJfhI+fIHMErnPyRa89/yRXUUr93q+tiN6zhoVV4wQ==
|
||||||
|
dependencies:
|
||||||
|
rust-result "^1.0.0"
|
||||||
|
|
||||||
sass@^1.81.0:
|
sass@^1.81.0:
|
||||||
version "1.81.0"
|
version "1.81.0"
|
||||||
resolved "https://registry.npmmirror.com/sass/-/sass-1.81.0.tgz#a9010c0599867909dfdbad057e4a6fbdd5eec941"
|
resolved "https://registry.npmmirror.com/sass/-/sass-1.81.0.tgz#a9010c0599867909dfdbad057e4a6fbdd5eec941"
|
||||||
@ -2964,6 +3102,21 @@ slash@^3.0.0:
|
|||||||
resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||||
|
|
||||||
|
store2@^2.7.1:
|
||||||
|
version "2.14.3"
|
||||||
|
resolved "https://registry.npmmirror.com/store2/-/store2-2.14.3.tgz#24077d7ba110711864e4f691d2af941ec533deb5"
|
||||||
|
integrity sha512-4QcZ+yx7nzEFiV4BMLnr/pRa5HYzNITX2ri0Zh6sT9EyQHbBHacC6YigllUPU9X3D0f/22QCgfokpKs52YRrUg==
|
||||||
|
|
||||||
|
store@^2.0.12:
|
||||||
|
version "2.0.12"
|
||||||
|
resolved "https://registry.npmmirror.com/store/-/store-2.0.12.tgz#8c534e2a0b831f72b75fc5f1119857c44ef5d593"
|
||||||
|
integrity sha512-eO9xlzDpXLiMr9W1nQ3Nfp9EzZieIQc10zPPMP5jsVV7bLOziSFFBP0XoDXACEIFtdI+rIz0NwWVA/QVJ8zJtw==
|
||||||
|
|
||||||
|
strict-uri-encode@^1.0.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||||
|
integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==
|
||||||
|
|
||||||
string-convert@^0.2.0:
|
string-convert@^0.2.0:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
|
resolved "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97"
|
||||||
@ -3072,6 +3225,26 @@ tailwindcss@^3.4.7:
|
|||||||
resolve "^1.22.8"
|
resolve "^1.22.8"
|
||||||
sucrase "^3.35.0"
|
sucrase "^3.35.0"
|
||||||
|
|
||||||
|
tcplayer.js@^5.2.0:
|
||||||
|
version "5.2.0"
|
||||||
|
resolved "https://registry.npmmirror.com/tcplayer.js/-/tcplayer.js-5.2.0.tgz#95a1cf8e5548a831d1eb945a3585cfd7e9f4480b"
|
||||||
|
integrity sha512-rXxHhS9ajp07maE81OXNrsOQuPbvrW/GVn+Z+aS9Oxpl8yC/pIg8wthUZUK/C1Va3GNZt6bE2oFoQTPut3bRww==
|
||||||
|
dependencies:
|
||||||
|
babel-runtime "^6.9.2"
|
||||||
|
blueimp-md5 "^2.10.0"
|
||||||
|
global "4.3.2"
|
||||||
|
jsencrypt "^3.2.0"
|
||||||
|
md5 "^2.3.0"
|
||||||
|
query-string "^5.0.1"
|
||||||
|
safe-json-parse "4.0.0"
|
||||||
|
store "^2.0.12"
|
||||||
|
store2 "^2.7.1"
|
||||||
|
tsml "1.0.1"
|
||||||
|
videojs-font "2.1.0"
|
||||||
|
videojs-ie8 "1.1.2"
|
||||||
|
videojs-vtt.js "0.12.4"
|
||||||
|
xhr "2.4.0"
|
||||||
|
|
||||||
text-table@^0.2.0:
|
text-table@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
resolved "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||||
@ -3123,6 +3296,11 @@ tslib@^2.0.0, tslib@^2.4.1:
|
|||||||
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||||
|
|
||||||
|
tsml@1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.npmmirror.com/tsml/-/tsml-1.0.1.tgz#89f8218b9d9e257f47d7f6b56d01c5a4d2c68fc3"
|
||||||
|
integrity sha512-3KmepnH9SUsoOVtg013CRrL7c+AK7ECaquAsJdvu4288EDJuraqBlP4PDXT/rLEJ9YDn4jqLAzRJsnFPx+V6lg==
|
||||||
|
|
||||||
type-check@^0.4.0, type-check@~0.4.0:
|
type-check@^0.4.0, type-check@~0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||||
@ -3165,6 +3343,25 @@ util-deprecate@^1.0.2, util-deprecate@~1.0.1:
|
|||||||
resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||||
|
|
||||||
|
videojs-font@2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.npmmirror.com/videojs-font/-/videojs-font-2.1.0.tgz#a25930a67f6c9cfbf2bb88dacb8c6b451f093379"
|
||||||
|
integrity sha512-zFqWpLrXf1q8NtYx5qtZhMC6SLUFScDmR6j+UGPogobxR21lvXShhnzcNNMdOxJUuFLiToJ/BPpFUQwX4xhpvA==
|
||||||
|
|
||||||
|
videojs-ie8@1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.npmmirror.com/videojs-ie8/-/videojs-ie8-1.1.2.tgz#a23d3d8608ad7192b69c6077fc4eb848998d35d9"
|
||||||
|
integrity sha512-0Zb2T4MLkpfZbeGMK/Z93b8Lrepr+rLFoHgQV1CoDeFqXvH7b+Vsd/VHoILGxQrgCSHFQ7mAODR6oyMjuiD4/g==
|
||||||
|
dependencies:
|
||||||
|
es5-shim "^4.5.1"
|
||||||
|
|
||||||
|
videojs-vtt.js@0.12.4:
|
||||||
|
version "0.12.4"
|
||||||
|
resolved "https://registry.npmmirror.com/videojs-vtt.js/-/videojs-vtt.js-0.12.4.tgz#38f2499e31efb3fa93590ddad4cb663275a4b161"
|
||||||
|
integrity sha512-JQ5eozH5SLOL5xI8ALb1aWf9HjcewQmOytf1gPIsFBTQlSgtSdJ8E8x0GO0ZEXVtFCaPDFiYWAhrjuTI125tBQ==
|
||||||
|
dependencies:
|
||||||
|
global "^4.3.1"
|
||||||
|
|
||||||
vite@^5.2.0:
|
vite@^5.2.0:
|
||||||
version "5.4.11"
|
version "5.4.11"
|
||||||
resolved "https://registry.npmmirror.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5"
|
resolved "https://registry.npmmirror.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5"
|
||||||
@ -3216,6 +3413,21 @@ wrappy@1:
|
|||||||
resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||||
|
|
||||||
|
xhr@2.4.0:
|
||||||
|
version "2.4.0"
|
||||||
|
resolved "https://registry.npmmirror.com/xhr/-/xhr-2.4.0.tgz#e16e66a45f869861eeefab416d5eff722dc40993"
|
||||||
|
integrity sha512-TUbBsdAuJbX8olk9hsDwGK8P1ri1XlV+PdEWkYw+HQQbpkiBR8PLgD1F3kQDPBs9l4Px34hP9rCYAZOCCAENbw==
|
||||||
|
dependencies:
|
||||||
|
global "~4.3.0"
|
||||||
|
is-function "^1.0.1"
|
||||||
|
parse-headers "^2.0.0"
|
||||||
|
xtend "^4.0.0"
|
||||||
|
|
||||||
|
xtend@^4.0.0:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||||
|
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||||
|
|
||||||
yallist@^3.0.2:
|
yallist@^3.0.2:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
resolved "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user