This commit is contained in:
LittleBoy 2025-04-11 19:20:06 +08:00
parent be34a8bc9b
commit cea77ea231
12 changed files with 236 additions and 92 deletions

View File

@ -26,7 +26,7 @@
"delete_confirm": "Are you sure you want to delete this video?",
"push_success": "Streaming success",
"search_key": "Please enter title keywords",
"text": "Video history"
"text": "Recycle Bin"
},
"history.pushed": "Streaming: {{count}}",
"live": {
@ -134,6 +134,18 @@
"title_word_count": "Word count",
"word_count": "Words"
},
"order": {
"left_time": "Remaining time",
"list": {
"consume_time": "Duration",
"cover": "Cover",
"id": "No.",
"operator": "User",
"order_time": "Time stamp",
"title": "Title"
},
"text": "Orders"
},
"select": {
"pushed": "Pushed: {{count}}",
"select_all": "Select all",

View File

@ -26,7 +26,7 @@
"delete_confirm": "是否要删除该视频",
"push_success": "一键推流成功,已推流至数字人直播间,请查看!",
"search_key": "请输入视频标题关键字进行信息",
"text": "历史视频"
"text": "回收站"
},
"history.pushed": "已推送 {{count}} 条",
"live": {
@ -134,6 +134,18 @@
"title_word_count": "字数",
"word_count": "字数"
},
"order": {
"left_time": "当前剩余时长",
"list": {
"consume_time": "消费时长",
"cover": "缩略图",
"id": "订单编号",
"operator": "操作人",
"order_time": "下单时间",
"title": "标题"
},
"text": "订单记录"
},
"select": {
"pushed": "已推送: {{count}} 条",
"select_all": "全选",

View File

@ -1,6 +1,6 @@
import {Input} from "antd";
import {useBoolean, useLocalStorageState, useSetState,useClickAway} from "ahooks";
import {useCallback, useEffect, useMemo, useRef, useState} from "react";
import {useLocalStorageState, useSetState, useClickAway} from "ahooks";
import React, {useCallback, useEffect, useMemo, useRef, useState} from "react";
import {clsx} from "clsx";
import useArticleTags from "@/hooks/useArticleTags.ts";
@ -14,6 +14,8 @@ import {useTranslation} from "react-i18next";
type SearchPanelProps = {
onSearch?: (params: ApiArticleSearchParams) => void;
defaultParams?: Partial<ApiArticleSearchParams>;
hideNewsSource?: boolean;
rightRender?: React.ReactNode;
}
const pagination = {
limit: 12, page: 1
@ -23,7 +25,7 @@ const DEFAULT_STATE = {
tag_level_2_id: -1,
subOptions: []
}
export default function SearchPanel({onSearch,defaultParams}: SearchPanelProps) {
export default function SearchPanel({onSearch, defaultParams, hideNewsSource,rightRender}: SearchPanelProps) {
const tags = useArticleTags();
const {t} = useTranslation()
const [params, setParams] = useSetState<ApiArticleSearchParams>({
@ -159,8 +161,9 @@ export default function SearchPanel({onSearch,defaultParams}: SearchPanelProps)
onChange={handleTimeFilter}
/>
</div>
{rightRender && <div className="right-placeholder">{rightRender}</div>}
</div>
<div className="filter-container mt-5">
{!hideNewsSource && <div className="filter-container mt-5">
<div className="list-container relative">
<div className="justify-between flex items-start border-b pb-2 overflow-hidden">
<div className="pinned-tag-list flex flex-wrap flex-1 min-w-0">
@ -243,6 +246,6 @@ export default function SearchPanel({onSearch,defaultParams}: SearchPanelProps)
</div>}
</div>
</div>
</div>}
</div>)
}

View File

@ -89,7 +89,11 @@ export default function NewsIndex() {
}
}
return (<div className={'container pb-5'}>
<SearchPanel defaultParams={params} onSearch={setParams}/>
<SearchPanel defaultParams={params} onSearch={(params)=>{
// 滚动到顶部
scrollerRef.current?.scrollToPosition(0)
setParams(params)
}}/>
{activeNews && <Modal
rootClassName={'news-detail-modal'}
closeIcon={null} open={true} width={1000}

81
src/pages/order/index.tsx Normal file
View File

@ -0,0 +1,81 @@
import SearchPanel from "@/pages/news/components/search-panel.tsx";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import styles from "@/pages/news/components/style.module.scss";
import {formatDurationToTime, formatTime} from "@/util/strings.ts";
import {IconDelete, IconEdit, IconWarningCircle} from "@/components/icons";
import {Popconfirm} from "antd";
import {useSetState} from "ahooks";
const mockList: OrderInfo[] = Array(10).fill(0).map((_, id) => (
{
id: id + 1,
cover: "https://staticplus.gachafun.com/fengmang/imgs/20241216/3fa3da5027cce22acb03283e8d688749.jpg",
title: `我国成功发射卫星互联网低轨卫星 ${id}`,
order_time: "2025-03-25 11:11:11",
consume_time: 60,
operator: "张三"
}
))
function OrderIndex() {
const {t} = useTranslation()
const [params, setParams] = useState<ApiArticleSearchParams>({
pagination: {page: 1, limit: 12},
time_flag: 1,
})
const [dataList, setDataList] = useState<OrderInfo[]>([...mockList])
const [state, setState] = useSetState({
loading: false,
leftTime: 2000,
})
return <div className="container pb-5 page-order-index">
<SearchPanel
hideNewsSource={true} defaultParams={params} onSearch={setParams}
rightRender={<div>{t('order.left_time')}: <span className={`${state.leftTime < 3600 ? 'text-red-600':''}`}>{formatDurationToTime(state.leftTime)}</span> </div>}
/>
<div className=" mt-2">
<div className={styles.newListTable}>
<div className="header row flex">
<div className="col w-[160px]">{t('order.list.id')}</div>
<div className="col w-[180px]">{t('order.list.cover')}</div>
<div className="col title">{t('order.list.title')}</div>
<div className="col w-[180px]">{t('order.list.order_time')}</div>
<div className="col w-[180px]">{t('order.list.consume_time')}</div>
<div className="col w-[180px]">{t('order.list.operator')}</div>
</div>
<div className="data-list-container">
{dataList?.map((item, i) => {
return <div key={i} className="row flex">
<div className="col w-[160px] text-center">
<div className="flex-1">
<div className="text-base">{item.id}</div>
</div>
</div>
<div className="col w-[180px]">
<img src={item.cover} className="rounded w-[140px] h-[60px]" alt=""/>
</div>
<div className="col flex-1">
<div className="text-sm line-clamp-2">{item.title}</div>
</div>
<div className="col w-[180px]">
<div className="text-sm">{formatTime(item.order_time, 'YYYY-MM-DD HH:mm')}</div>
</div>
<div className="col w-[180px]">
<div
className="text-sm">{formatTime(item.consume_time, 'YYYY-MM-DD HH:mm')}</div>
</div>
<div className="col w-[180px]">
<div className="text-sm">{item.operator}</div>
</div>
</div>
})}
</div>
</div>
</div>
</div>
}
export default OrderIndex

View File

@ -249,6 +249,7 @@ export default function VideoIndex() {
playing={state.playingId == v.id}
checked={checkedIdArray.includes(v.id)}
className={`list-item-${index} mt-3 mb-2 list-item-state-${v.status} `}
downloadVisible={true}
onCheckedChange={(checked) => {
setCheckedIdArray(idArray => {
const newArr = checked ? idArray.concat(v.id) : idArray.filter(id => id != v.id);

View File

@ -9,7 +9,6 @@ import ErrorBoundary from "./error.tsx";
import Loader from "@/components/loader.tsx";
import routes from "@/routes/routes.tsx";
import {DocumentTitle} from "@/components/document.tsx";
import useConfig from "@/hooks/useConfig.ts";
import {useTranslation} from "react-i18next";
import useGlobalConfig from "@/hooks/useGlobalConfig.ts";

View File

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

View File

@ -1,6 +1,7 @@
import {Outlet, useLocation, useNavigate, useSearchParams} from "react-router-dom";
import {Button, Divider, Dropdown, MenuProps} from "antd";
import React, {useEffect} from "react";
import {useTranslation} from "react-i18next";
import AuthGuard from "@/routes/layout/auth-guard.tsx";
import {LogoText} from "@/components/icons/logo.tsx";
@ -12,8 +13,6 @@ import useAuth from "@/hooks/useAuth.ts";
import {hidePhone} from "@/util/strings.ts";
import {defaultCache} from "@/hooks/useCache.ts";
import {IconVideo} from "@/components/icons";
import {useTranslation} from "react-i18next";
import useConfig from "@/hooks/useConfig.ts";
type LayoutProps = {
@ -29,12 +28,19 @@ const NavigationUserContainer = () => {
}
const items: MenuProps['items'] = [
{
key: 'profile',
key: 'history',
label: <div className="nav-item" onClick={() => navigate('/history')}>
<IconVideo />
<span className={"nav-text"}>{t('history.text')}</span>
</div>,
},
{
key: 'order',
label: <div className="nav-item" onClick={() => navigate('/order')}>
<IconVideo />
<span className={"nav-text"}>{t('order.text')}</span>
</div>,
},
// {
// key: 'logout',
// label: <div onClick={handleLogout}>退出</div>,

View File

@ -1,16 +1,16 @@
import {RouteObject} from "react-router-dom";
import ErrorBoundary from "@/routes/error.tsx";
;
import DashboardLayout from "@/routes/layout/dashboard-layout.tsx";
import React from "react";
import ErrorBoundary from "@/routes/error.tsx";
import DashboardLayout from "@/routes/layout/dashboard-layout.tsx";
const UserAuth = React.lazy(() => import("@/pages/user"))
const CreateVideoIndex = React.lazy(() => import("@/pages/video"))
const LibraryIndex = React.lazy(() => import("@/pages/library"))
const LiveIndex = React.lazy(() => import("@/pages/live"))
const NewsIndex = React.lazy(() => import("@/pages/news"))
const NewsEdit = React.lazy(() => import("@/pages/news/edit.tsx"))
const OrderIndex = React.lazy(() => import("@/pages/order/index.tsx"))
const routes: RouteObject[] = [
@ -39,6 +39,10 @@ const routes: RouteObject[] = [
path: 'history',
element: <LibraryIndex/>
},
{
path: 'order',
element: <OrderIndex/>
},
{
path: 'live',
element: <LiveIndex/>

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

@ -127,3 +127,16 @@ declare interface LiveState{
id: number;
live_start_time: number;
}
declare interface OrderInfo {
id: number| string;
// 缩略图
cover: string;
// 标题
title: string;
// 下单时间
order_time: number | string;
// 消费时长
consume_time: number;
// 操作人
operator: string;
}

View File

@ -54,6 +54,16 @@ function getDayjs(time:any){
}
return dayjs(time);
}
// 将时长(秒)转换成时间
export function formatDurationToTime(duration: number) {
if (duration < 0 || isNaN(duration)) return '00:00';
duration = Math.ceil(duration);
const hour = Math.floor(duration / 3600);
const minute = Math.floor((duration - hour * 3600) / 60);
const second = duration - hour * 3600 - minute * 60;
// 需要补0
return padStart(hour.toString(), 2, '0') + ':' + padStart(minute.toString(), 2, '0') + ':' + padStart(second.toString(), 2, '0')
}
export function formatTime(time: any, template: 'min' | 'date' | string = 'YYYY-MM-DD HH:mm:ss') {
if (!time) return '-';