|
|
@ -1,11 +1,11 @@ |
|
|
|
import { useTranslation } from '@/i18n.ts' |
|
|
|
import { getToken } from '@/store/system.ts' |
|
|
|
import { Button, DatePicker, Form, Image, Popconfirm } from 'antd' |
|
|
|
import { Button, DatePicker, Form, Image, Popconfirm, Divider, Space, Tooltip, Badge } from 'antd' |
|
|
|
import dayjs from 'dayjs' |
|
|
|
import { useAtom, useAtomValue, useSetAtom } from 'jotai' |
|
|
|
import { |
|
|
|
deleteVideoAtom, getTypeName, |
|
|
|
saveOrUpdateVideoAtom, videosAtom, videoSearchAtom, videoTypes |
|
|
|
saveOrUpdateVideoAtom, videoAtom, videosAtom, videoSearchAtom, videoTypes |
|
|
|
} from '@/store/videos/video.ts' |
|
|
|
import { useEffect, useMemo, useState } from 'react' |
|
|
|
import Action from '@/components/action/Action.tsx' |
|
|
@ -19,21 +19,28 @@ import { |
|
|
|
import ListPageLayout from '@/layout/ListPageLayout.tsx' |
|
|
|
import { categoryByIdAtom, categoryIdAtom } from '@/store/videos/category.ts' |
|
|
|
import TagPro from '@/components/tag-pro/TagPro.tsx' |
|
|
|
import TagValue from '@/components/tag-value/TagValue.tsx' |
|
|
|
import { useStyle } from './style' |
|
|
|
import { FilterOutlined } from '@ant-design/icons' |
|
|
|
import { getValueCount } from '@/utils' |
|
|
|
|
|
|
|
const i18nPrefix = 'videos.list' |
|
|
|
|
|
|
|
const Video = () => { |
|
|
|
|
|
|
|
// const { styles } = useStyle()
|
|
|
|
const { styles, cx } = useStyle() |
|
|
|
const { t } = useTranslation() |
|
|
|
const [ form ] = Form.useForm() |
|
|
|
const [ filterForm ] = Form.useForm() |
|
|
|
const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateVideoAtom) |
|
|
|
const [ search, setSearch ] = useAtom(videoSearchAtom) |
|
|
|
const [ currentVideo, setVideo ] = useAtom(videoAtom) |
|
|
|
const { data, isFetching, isLoading, refetch } = useAtomValue(videosAtom) |
|
|
|
const { mutate: deleteVideo, isPending: isDeleting } = useAtomValue(deleteVideoAtom) |
|
|
|
const setCategoryId = useSetAtom(categoryIdAtom) |
|
|
|
const { data: category, isLoading: isCategoryFetching } = useAtomValue(categoryByIdAtom) |
|
|
|
const [ open, setOpen ] = useState(false) |
|
|
|
const [ openFilter, setFilterOpen ] = useState(false) |
|
|
|
|
|
|
|
const columns = useMemo(() => { |
|
|
|
return [ |
|
|
@ -84,7 +91,25 @@ const Video = () => { |
|
|
|
style: { width: '100%' } |
|
|
|
}, |
|
|
|
render: (_dom, record) => { |
|
|
|
return t(`${i18nPrefix}.type_id.${record.type_id}`) |
|
|
|
return <TagValue |
|
|
|
tags={[ { label: t(`${i18nPrefix}.type_id.${record.type_id}`), value: record.type_id } ]} |
|
|
|
wrap={currentVideo?.id === record.id} |
|
|
|
value={search?.type_id} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch((prev: any) => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
type_id: values[0], |
|
|
|
} |
|
|
|
}) |
|
|
|
setCategoryId(values[0]) |
|
|
|
const typeName = getTypeName(values[0]) |
|
|
|
form.setFieldsValue({ |
|
|
|
class_name: typeName, |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}} |
|
|
|
/> |
|
|
|
}, |
|
|
|
colProps: { |
|
|
|
span: 8 |
|
|
@ -110,16 +135,38 @@ const Video = () => { |
|
|
|
}, |
|
|
|
valueType: 'dateYear', |
|
|
|
colProps: { |
|
|
|
span: 4 |
|
|
|
span: 6 |
|
|
|
}, |
|
|
|
render: (_dom, record) => { |
|
|
|
return record.year |
|
|
|
return <TagValue |
|
|
|
tags={[ record.year ]} |
|
|
|
wrap={currentVideo?.id === record.id} |
|
|
|
value={search?.year} |
|
|
|
single={true} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch((prev: any) => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
year: values[0], |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}} |
|
|
|
/> |
|
|
|
}, |
|
|
|
renderFormItem: (_schema, config) => { |
|
|
|
const props = { ...config } as any |
|
|
|
delete props.mode |
|
|
|
const isForm = config.type === 'form' |
|
|
|
let value = isForm && config.value ? dayjs().set('year', config.value) : undefined |
|
|
|
if (config.value?.$isDayjsObject) { |
|
|
|
value = config.value as dayjs.Dayjs |
|
|
|
} |
|
|
|
return <DatePicker |
|
|
|
{..._schema.formItemProps} |
|
|
|
{...props} |
|
|
|
picker={'year'} |
|
|
|
{...config} |
|
|
|
value={dayjs().set('year', config.value || new Date().getFullYear())} |
|
|
|
value={value} |
|
|
|
/> |
|
|
|
} |
|
|
|
|
|
|
@ -131,9 +178,24 @@ const Video = () => { |
|
|
|
fieldProps: { |
|
|
|
style: { width: '100%' } |
|
|
|
}, |
|
|
|
|
|
|
|
colProps: { |
|
|
|
span: 12 |
|
|
|
span: 10 |
|
|
|
}, |
|
|
|
render: (_dom, record) => { |
|
|
|
return <TagValue |
|
|
|
tags={record.class_name?.split(',')} |
|
|
|
wrap={currentVideo?.id === record.id} |
|
|
|
value={search?.class_name} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch((prev: any) => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
class_name: values, |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}} |
|
|
|
/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
@ -142,6 +204,21 @@ const Video = () => { |
|
|
|
hideInTable: true, |
|
|
|
colProps: { |
|
|
|
span: 6 |
|
|
|
}, render: (_dom, record) => { |
|
|
|
return <TagValue |
|
|
|
tags={[ record.douban_id ]} |
|
|
|
wrap={currentVideo?.id === record.id} |
|
|
|
value={search?.douban_id} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch((prev: any) => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
douban_id: values[0], |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}} |
|
|
|
/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
@ -150,6 +227,21 @@ const Video = () => { |
|
|
|
hideInTable: true, |
|
|
|
colProps: { |
|
|
|
span: 6 |
|
|
|
}, render: (_dom, record) => { |
|
|
|
return <TagValue |
|
|
|
tags={[ record.imdb_id ]} |
|
|
|
wrap={currentVideo?.id === record.id} |
|
|
|
value={search?.imdb_id} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch((prev: any) => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
imdb_id: values[0], |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}} |
|
|
|
/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
@ -158,6 +250,21 @@ const Video = () => { |
|
|
|
hideInTable: true, |
|
|
|
colProps: { |
|
|
|
span: 6 |
|
|
|
}, render: (_dom, record) => { |
|
|
|
return <TagValue |
|
|
|
tags={[ record.rt_id ]} |
|
|
|
wrap={currentVideo?.id === record.id} |
|
|
|
value={search?.rt_id} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch((prev: any) => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
rt_id: values[0], |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}} |
|
|
|
/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
@ -166,13 +273,28 @@ const Video = () => { |
|
|
|
hideInTable: true, |
|
|
|
colProps: { |
|
|
|
span: 6 |
|
|
|
}, render: (_dom, record) => { |
|
|
|
return <TagValue |
|
|
|
tags={[ record.mal_id ]} |
|
|
|
wrap={currentVideo?.id === record.id} |
|
|
|
value={search?.mal_id} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch((prev: any) => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
mal_id: values[0], |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}} |
|
|
|
/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
'title': t(`${i18nPrefix}.columns.img`, '封面'), |
|
|
|
'dataIndex': 'pic', |
|
|
|
hideInSearch: true, |
|
|
|
hideInTable: true, |
|
|
|
width: 80, |
|
|
|
colProps: { |
|
|
|
span: 24 |
|
|
|
}, |
|
|
@ -189,10 +311,14 @@ const Video = () => { |
|
|
|
fileList={[]} |
|
|
|
action="/api/v1/videos/image/upload" |
|
|
|
/> |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
render: (_dom, record) => { |
|
|
|
const url = `/api/v1/videos/image/${record.video_id}` |
|
|
|
return <Image src={`${url}?width=60&height=80`} alt="cover" preview={{ |
|
|
|
src: url, |
|
|
|
}} style={{ width: 80 }}/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
'title': t(`${i18nPrefix}.columns.actor`, 'Actor'), |
|
|
|
'dataIndex': 'actor', |
|
|
@ -201,7 +327,22 @@ const Video = () => { |
|
|
|
fieldProps: { |
|
|
|
style: { width: '100%' } |
|
|
|
}, |
|
|
|
|
|
|
|
render: (_dom, record) => { |
|
|
|
return <TagValue |
|
|
|
tags={record.actor?.split(',') ?? []} |
|
|
|
wrap={currentVideo?.id === record.id} |
|
|
|
value={search?.actor ?? []} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch((prev: any) => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
actor: values, |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}} |
|
|
|
/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
'title': t(`${i18nPrefix}.columns.director`, 'Director'), |
|
|
@ -210,6 +351,22 @@ const Video = () => { |
|
|
|
fieldProps: { |
|
|
|
style: { width: '100%' } |
|
|
|
}, |
|
|
|
render: (_dom, record) => { |
|
|
|
return <TagValue |
|
|
|
tags={record.director?.split(',') ?? []} |
|
|
|
wrap={!currentVideo?.id === record.id} |
|
|
|
value={search?.director ?? []} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch(prev => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
director: (values as Array<any>) |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}} |
|
|
|
/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
'title': t(`${i18nPrefix}.columns.writer`, 'Writer'), |
|
|
@ -218,6 +375,21 @@ const Video = () => { |
|
|
|
fieldProps: { |
|
|
|
style: { width: '100%' } |
|
|
|
}, |
|
|
|
render: (_dom, record) => { |
|
|
|
return <TagValue |
|
|
|
tags={record.writer?.split(',') ?? []} |
|
|
|
wrap={!currentVideo?.id === record.id} |
|
|
|
value={search?.writer ?? []} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch(prev => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
writer: (values as Array<any>) |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}}/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
'title': t(`${i18nPrefix}.columns.content`, 'Content'), |
|
|
@ -247,7 +419,22 @@ const Video = () => { |
|
|
|
renderFormItem: (schema, config) => { |
|
|
|
return <TagPro loading={isCategoryFetching} |
|
|
|
tags={category?.extend?.class?.split(',') ?? []} {...config} {...schema.fieldProps} /> |
|
|
|
}, |
|
|
|
render: (_dom, record) => { |
|
|
|
return <TagValue |
|
|
|
tags={record.tag?.split(',') ?? []} |
|
|
|
wrap={!currentVideo?.id === record.id} |
|
|
|
value={search?.tag ?? []} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch(prev => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
tag: (values as Array<any>) |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}}/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
'title': t(`${i18nPrefix}.columns.area`, 'Area'), |
|
|
@ -260,7 +447,23 @@ const Video = () => { |
|
|
|
renderFormItem: (schema, config) => { |
|
|
|
return <TagPro loading={isCategoryFetching} |
|
|
|
tags={category?.extend?.area?.split(',') ?? []} {...config} {...schema.fieldProps} /> |
|
|
|
}, |
|
|
|
render: (_dom, record) => { |
|
|
|
return <TagValue |
|
|
|
tags={record.area?.split(',') ?? []} |
|
|
|
wrap={!currentVideo?.id === record.id} |
|
|
|
value={search?.area ?? []} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch(prev => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
area: (values as Array<any>) |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}} |
|
|
|
/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
'title': t(`${i18nPrefix}.columns.lang`, 'Lang'), |
|
|
@ -273,7 +476,22 @@ const Video = () => { |
|
|
|
renderFormItem: (schema, config) => { |
|
|
|
return <TagPro loading={isCategoryFetching} |
|
|
|
tags={category?.extend?.lang?.split(',') ?? []} {...config} {...schema.fieldProps} /> |
|
|
|
}, |
|
|
|
render: (_dom, record) => { |
|
|
|
return <TagValue |
|
|
|
tags={record.lang?.split(',') ?? []} |
|
|
|
wrap={!currentVideo?.id === record.id} |
|
|
|
value={search?.lang ?? []} |
|
|
|
onChange={(values) => { |
|
|
|
setSearch(prev => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
lang: (values as Array<any>) |
|
|
|
} |
|
|
|
}) |
|
|
|
setFilterOpen(true) |
|
|
|
}}/> |
|
|
|
}, |
|
|
|
}, |
|
|
|
/*{ |
|
|
|
'title': t(`${i18nPrefix}.columns.version`, 'Version'), |
|
|
@ -309,8 +527,6 @@ const Video = () => { |
|
|
|
formItemProps: { hidden: true }, |
|
|
|
hideInTable: true, |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
{ |
|
|
|
title: t(`${i18nPrefix}.columns.option`, '操作'), |
|
|
|
key: 'option', |
|
|
@ -338,7 +554,13 @@ const Video = () => { |
|
|
|
] |
|
|
|
} |
|
|
|
] as ProColumns[] |
|
|
|
}, [ isDeleting, category, isCategoryFetching ]) |
|
|
|
}, [ isDeleting, category, isCategoryFetching, currentVideo, search ]) |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
|
|
filterForm.setFieldsValue(search) |
|
|
|
|
|
|
|
}, [ search ]) |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
if (isSuccess) { |
|
|
@ -347,7 +569,7 @@ const Video = () => { |
|
|
|
}, [ isSuccess ]) |
|
|
|
|
|
|
|
return ( |
|
|
|
<ListPageLayout> |
|
|
|
<ListPageLayout className={styles.container}> |
|
|
|
<ProTable |
|
|
|
rowKey="id" |
|
|
|
headerTitle={t(`${i18nPrefix}.title`, '视频管理')} |
|
|
@ -364,8 +586,18 @@ const Video = () => { |
|
|
|
placeholder: t(`${i18nPrefix}.placeholder`, '输入视频名称') |
|
|
|
}, |
|
|
|
actions: [ |
|
|
|
<Tooltip key={'filter'} title={t(`${i18nPrefix}.filter.tooltip`, '高级查询')}> |
|
|
|
<Badge count={ getValueCount(search) }> |
|
|
|
<Button |
|
|
|
onClick={() => { |
|
|
|
setFilterOpen(true) |
|
|
|
}} |
|
|
|
icon={<FilterOutlined/>} shape={'circle'} size={'small'}/> |
|
|
|
</Badge> |
|
|
|
</Tooltip>, |
|
|
|
<Divider type={'vertical'} key={'divider'}/>, |
|
|
|
<Button key={'add'} |
|
|
|
onClick={() => { |
|
|
|
form.resetFields() |
|
|
|
form.setFieldsValue({ |
|
|
|
id: 0, |
|
|
@ -378,10 +610,21 @@ const Video = () => { |
|
|
|
scroll={{ |
|
|
|
x: 2500, y: 'calc(100vh - 290px)' |
|
|
|
}} |
|
|
|
search={false} |
|
|
|
onRow={(record) => { |
|
|
|
return { |
|
|
|
className: cx({ |
|
|
|
'ant-table-row-selected': currentVideo?.id === record.id |
|
|
|
}), |
|
|
|
onClick: () => { |
|
|
|
setVideo(record) |
|
|
|
} |
|
|
|
} |
|
|
|
}} |
|
|
|
dateFormatter="string" |
|
|
|
loading={isLoading || isFetching} |
|
|
|
dataSource={data?.rows ?? []} |
|
|
|
columns={columns} |
|
|
|
search={false} |
|
|
|
options={{ |
|
|
|
reload: () => { |
|
|
|
refetch() |
|
|
@ -437,6 +680,83 @@ const Video = () => { |
|
|
|
|
|
|
|
}} |
|
|
|
columns={columns as ProFormColumnsType[]}/> |
|
|
|
<BetaSchemaForm |
|
|
|
title={t(`${i18nPrefix}.filter.title`, '视频高级查询')} |
|
|
|
grid={true} |
|
|
|
shouldUpdate={false} |
|
|
|
width={500} |
|
|
|
form={filterForm} |
|
|
|
open={openFilter} |
|
|
|
onOpenChange={open => { |
|
|
|
setFilterOpen(open) |
|
|
|
}} |
|
|
|
layout={'vertical'} |
|
|
|
scrollToFirstError={true} |
|
|
|
// colProps={{ span: 24 }}
|
|
|
|
// labelCol={{ span: 8 }}
|
|
|
|
// wrapperCol={{ span: 14 }}
|
|
|
|
layoutType={'DrawerForm'} |
|
|
|
drawerProps={{ |
|
|
|
maskClosable: false, |
|
|
|
mask: false, |
|
|
|
}} |
|
|
|
submitter={{ |
|
|
|
searchConfig: { |
|
|
|
resetText: t(`${i18nPrefix}.filter.reset`, '清空'), |
|
|
|
submitText: t(`${i18nPrefix}.filter.submit`, '查询'), |
|
|
|
}, |
|
|
|
onReset: () => { |
|
|
|
filterForm.resetFields() |
|
|
|
}, |
|
|
|
render: (props,) => { |
|
|
|
return ( |
|
|
|
<div style={{ textAlign: 'right' }}> |
|
|
|
<Space> |
|
|
|
<Button onClick={() => { |
|
|
|
props.reset() |
|
|
|
}}>{props.searchConfig?.resetText}</Button> |
|
|
|
<Button type="primary" |
|
|
|
onClick={() => { |
|
|
|
props.submit() |
|
|
|
}} |
|
|
|
>{props.searchConfig?.submitText}</Button> |
|
|
|
</Space> |
|
|
|
</div> |
|
|
|
) |
|
|
|
}, |
|
|
|
|
|
|
|
}} |
|
|
|
onValuesChange={(values) => { |
|
|
|
if (values.type_id) { |
|
|
|
setCategoryId(values.type_id) |
|
|
|
const typeName = getTypeName(values.type_id) |
|
|
|
filterForm.setFieldsValue({ |
|
|
|
class_name: typeName, |
|
|
|
}) |
|
|
|
} |
|
|
|
}} |
|
|
|
|
|
|
|
onFinish={async (values) => { |
|
|
|
// console.log('values', values)
|
|
|
|
//处理,变成数组
|
|
|
|
Object.keys(values).forEach(key => { |
|
|
|
if (typeof values[key] === 'string' && values[key].includes(',')) { |
|
|
|
values[key] = values[key].split(',') |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
if (Object.keys(values).length === 0) { |
|
|
|
setSearch({}) |
|
|
|
} else { |
|
|
|
setSearch(prev => { |
|
|
|
return { |
|
|
|
...prev, |
|
|
|
...values |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
}} |
|
|
|
columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/> |
|
|
|
</ListPageLayout> |
|
|
|
) |
|
|
|
} |
|
|
|