Browse Source

完善CMS的视频管理

main
dark 4 months ago
parent
commit
ed9352f135
  1. 37
      src/components/tag-value/TagValue.tsx
  2. 2
      src/locales/lang/pages/cms/video/zh-CN.ts
  3. 2
      src/locales/lang/pages/cms/videoCloud/zh-CN.ts
  4. 2
      src/locales/lang/pages/cms/videoMagnet/zh-CN.ts
  5. 435
      src/pages/cms/video/index.tsx
  6. 26
      src/pages/cms/video/style.ts
  7. 560
      src/pages/cms/video_cloud/index.tsx
  8. 26
      src/pages/cms/video_cloud/style.ts
  9. 562
      src/pages/cms/video_magnet/index.tsx
  10. 26
      src/pages/cms/video_magnet/style.ts
  11. 3
      src/pages/videos/list/index.tsx
  12. 14
      src/store/cms/video.ts
  13. 2
      src/store/cms/video_cloud.ts
  14. 2
      src/store/cms/video_magnet.ts

37
src/components/tag-value/TagValue.tsx

@ -18,11 +18,38 @@ const parserValue = (value?: ValueType | ValueType[]) => {
return []
}
if (Array.isArray(value)) {
return value
return value.map(i=>{
if (typeof i === 'string' ) {
return i.trim()
}
if (typeof i === 'number') {
return i
}
return ((i as any).value ?? i)
})
}
if (typeof value === 'string' && value.includes(',')) {
return value.split(',').map(item => item.trim())
}
if (typeof value === 'number') {
return [ value ]
}
return [ value ]
}
const parserTag = (tag: ValueType) => {
if (typeof tag === 'string') {
return tag.trim()
}
if (typeof tag === 'number') {
return tag
}
if (typeof tag === 'object') {
return ((tag as any).value ?? tag)
}
return tag
}
const TagValue = (
{
value, onChange, checked = true, tags = [], wrap, single,
@ -32,7 +59,7 @@ const TagValue = (
const [ innerValue, setValue ] = useState(() => {
return parserValue(value).filter(item => {
return tags?.some(t => {
return ((t as any).value ?? t) === item
return parserTag(t) === item
})
})
})
@ -40,7 +67,7 @@ const TagValue = (
useEffect(() => {
const val = parserValue(value).filter(item => {
return tags?.some(t => {
return ((t as any).value ?? t) === item
return parserTag(t) === item
})
})
setValue(val)
@ -50,10 +77,10 @@ const TagValue = (
<Space size={0} wrap={wrap}>
{tags?.map(item => {
if (item == null || item === '' || item === undefined) return null
const val = (item as any).value ?? item
const val = parserTag(item)
const selected = innerValue.includes(val)
return (
<Com key={String((item as any).value ?? item)}
<Com key={String(val)}
onChange={checked ? () => {
const prevValue = parserValue(value)
const index = prevValue?.indexOf(val)

2
src/locales/lang/pages/cms/video/zh-CN.ts

@ -6,7 +6,7 @@ export default {
edit: '编辑',
delete: '删除',
type_id: [
'在线观看', '下载', '网盘'
'在线观看', '磁链', '网盘'
],
lock: [
'未锁', '锁定'

2
src/locales/lang/pages/cms/videoCloud/zh-CN.ts

@ -6,7 +6,7 @@ export default {
edit: '编辑',
delete: '删除',
type_id: [
'在线观看', '下载', '网盘'
'在线观看', '磁链', '网盘'
],
lock: [
'未锁', '锁定'

2
src/locales/lang/pages/cms/videoMagnet/zh-CN.ts

@ -6,7 +6,7 @@ export default {
edit: '编辑',
delete: '删除',
type_id: [
'在线观看', '下载', '网盘'
'在线观看', '磁链', '网盘'
],
lock: [
'未锁', '锁定'

435
src/pages/cms/video/index.tsx

@ -1,9 +1,9 @@
import { useTranslation } from '@/i18n.ts'
import { Button, Form, Image, Popconfirm } from 'antd'
import { useAtom, useAtomValue } from 'jotai'
import { Badge, Button, DatePicker, Divider, Form, Image, Popconfirm, Space, Tooltip } from 'antd'
import { useAtom, useAtomValue,useSetAtom } from 'jotai'
import {
deleteVideoAtom,
saveOrUpdateVideoAtom, videosAtom, videoSearchAtom, videoTypes
saveOrUpdateVideoAtom, videoAtom, videosAtom, videoSearchAtom, videoTypes
} from '@/store/cms/video.ts'
import { useEffect, useMemo, useState } from 'react'
import Switch from '@/components/switch'
@ -11,26 +11,35 @@ import Action from '@/components/action/Action.tsx'
import { BetaSchemaForm, ProColumns, ProFormColumnsType, ProTable } from '@ant-design/pro-components'
import ListPageLayout from '@/layout/ListPageLayout.tsx'
import TagPro from '@/components/tag-pro/TagPro.tsx'
import { useSetAtom } from 'jotai/index'
import { categoriesAtom, categoryByIdAtom, categoryIdAtom } from '@/store/cms/category.ts'
import { getValueCount } from '@/utils'
import { FilterOutlined } from '@ant-design/icons'
import TagValue from '@/components/tag-value/TagValue.tsx'
import dayjs from 'dayjs'
import { useStyle } from './style.ts'
const i18nPrefix = 'cms.video'
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: categories, isLoading: isCateLoading } = useAtomValue(categoriesAtom)
const { data: category, isLoading: isCategoryFetching } = useAtomValue(categoryByIdAtom)
const [ open, setOpen ] = useState(false)
const [ openFilter, setFilterOpen ] = useState(false)
const [ searchKey, setSearchKey ] = useState(search?.title)
const columns = useMemo(() => {
return [
{
title: 'ID',
@ -57,7 +66,13 @@ const Video = () => {
}
},
colProps: {
span: 8
span: 12
},
render: (_text, record) => {
//高亮搜索关键字, 从search.title中获取
const title = record.title?.replace?.(new RegExp(`(${search?.title})`, 'ig'), '<span class="ant-pro-table-highlight">$1</span>')
return <span dangerouslySetInnerHTML={{ __html: title }}></span>
}
},
{
@ -69,7 +84,7 @@ const Video = () => {
}
},
colProps: {
span: 8
span: 12
}
},
{
@ -79,13 +94,10 @@ const Video = () => {
fieldProps: {
options: videoTypes,
},
hideInForm: true,
render: (_dom, record) => {
return t(`${i18nPrefix}.type_id.${record.type_id}`)
},
colProps: {
span: 8
}
},
{
'title': t(`${i18nPrefix}.columns.source_url`, 'SourceUrl'),
@ -97,13 +109,15 @@ const Video = () => {
width: 200,
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.letter`, 'Letter'),
'dataIndex': 'letter',
colProps: {
span: 4
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.lock`, 'Lock'),
@ -114,7 +128,8 @@ const Video = () => {
},
colProps: {
span: 4
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.copyright`, 'Copyright'),
@ -125,7 +140,8 @@ const Video = () => {
},
colProps: {
span: 4
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.is_end`, 'IsEnd'),
@ -136,7 +152,8 @@ const Video = () => {
},
colProps: {
span: 4
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.status`, 'Status'),
@ -167,7 +184,8 @@ const Video = () => {
},
colProps: {
span: 4
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.pic`, 'Pic'),
@ -177,7 +195,8 @@ const Video = () => {
},
colProps: {
span: 20
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.category_id`, 'CategoryId'),
@ -186,126 +205,345 @@ const Video = () => {
fieldProps: {
loading: isCategoryFetching,
options: categories?.rows ?? [],
fieldNames:{
fieldNames: {
label: 'name',
value: 'id'
}
},
render: (_dom, record) => {
return <TagValue
tags={categories?.rows.filter(item=>item.id === record.category_id).map(item=>{
return {
label: item.name,
value: item.id
}
}) ?? []}
wrap={currentVideo?.id === record.id}
value={search?.category_id ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
category_id: values,
}
})
setCategoryId(values[0])
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.actor`, 'Actor'),
'dataIndex': 'actor',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
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'),
'dataIndex': 'director'
'dataIndex': 'director',
width: 200,
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: any) => {
return {
...prev,
director: values,
}
})
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.writer`, 'Writer'),
'dataIndex': 'writer'
'dataIndex': 'writer',
width: 200,
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: any) => {
return {
...prev,
writer: values,
}
})
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.remarks`, 'Remarks'),
'dataIndex': 'remarks'
'dataIndex': 'remarks',
hideInSearch: true,
},
{
/* {
'title': t(`${i18nPrefix}.columns.pubdate`, 'Pubdate'),
'dataIndex': 'pubdate',
valueType: 'dateTime',
colProps: {
span: 4
}
},
},*/
{
'title': t(`${i18nPrefix}.columns.total`, 'Total'),
'dataIndex': 'total',
valueType: 'digit',
colProps: {
span: 4
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.serial`, 'Serial'),
'dataIndex': 'serial',
colProps: {
span: 4
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.duration`, 'Duration'),
'dataIndex': 'duration',
colProps: {
span: 4
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.year`, 'Year'),
'dataIndex': 'year',
valueType: 'dateYear',
fieldProps:{
style:{
width: '100%'
}
},
colProps: {
span: 4
span: openFilter? 12: 4,
},
render: (_dom, record) => {
if (record.year === undefined || record.year === null || record.year === 0) {
return null
}
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 && config.value > 0 ? dayjs().set('year', config.value) : undefined
if (config.value?.$isDayjsObject) {
value = config.value as dayjs.Dayjs
}
return <DatePicker
{..._schema.formItemProps}
{...props}
picker={'year'}
value={value}
/>
}
},
{
'title': t(`${i18nPrefix}.columns.tag`, 'Tag'),
'dataIndex': 'tag',
valueType: 'textarea',
ellipsis: true,
onHeaderCell: () => {
return {
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.class?.split(',') ?? []} {...config} {...schema.fieldProps} />
},
width: 200,
}
fieldProps: {
style: { width: '100%' }
},
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: any) => {
return {
...prev,
tag: values,
}
})
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.area`, 'Area'),
'dataIndex': 'area',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.area?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.area ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
area: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching} tags={category?.extend?.area?.split(',') ?? []} {...config} {...schema.fieldProps} />
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.area?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
'title': t(`${i18nPrefix}.columns.lang`, 'Lang'),
'dataIndex': 'lang',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.lang?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.lang ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
lang: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching} tags={category?.extend?.lang?.split(',') ?? []} {...config} {...schema.fieldProps} />
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.lang?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
'title': t(`${i18nPrefix}.columns.version`, 'Version'),
'dataIndex': 'version',
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.version?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.version ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
version: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching} tags={category?.extend?.version?.split(',') ?? []} {...config} {...schema.fieldProps} />
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.version?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
'title': t(`${i18nPrefix}.columns.state`, 'State'),
'dataIndex': 'state',
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.state?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.state ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
state: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching} tags={category?.extend?.state?.split(',') ?? []} {...config} {...schema.fieldProps} />
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.state?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
@ -348,6 +586,7 @@ const Video = () => {
onHeaderCell: () => ({
width: 200,
}),
hideInSearch: true,
},
{
@ -377,7 +616,14 @@ const Video = () => {
]
}
] as ProColumns[]
}, [ isDeleting, category, isCategoryFetching, categories, isCateLoading ])
}, [ isDeleting, category, isCategoryFetching, categories, isCateLoading, category, currentVideo, search, openFilter ])
useEffect(() => {
setSearchKey(search?.title)
filterForm.setFieldsValue(search)
}, [ search ])
useEffect(() => {
if (isSuccess) {
@ -386,7 +632,7 @@ const Video = () => {
}, [ isSuccess ])
return (
<ListPageLayout>
<ListPageLayout className={styles.container}>
<ProTable
rowKey="id"
headerTitle={t(`${i18nPrefix}.title`, '视频管理')}
@ -399,15 +645,30 @@ const Video = () => {
key: value
}))
},
onChange: (e) => {
setSearchKey(e.target?.value)
},
value: searchKey,
allowClear: true,
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
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
type_id: 0,
})
setOpen(true)
}}
@ -415,8 +676,19 @@ const Video = () => {
]
}}
scroll={{
x: 3500,
x: 3500, y: 'calc(100vh - 290px)'
}}
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}
@ -461,8 +733,8 @@ const Video = () => {
setOpen(open)
}}
loading={isSubmitting}
onValuesChange={(values)=>{
if(values.category_id) {
onValuesChange={(values) => {
if (values.category_id) {
setCategoryId(values.category_id)
}
}}
@ -472,6 +744,73 @@ const Video = () => {
}}
columns={columns as ProFormColumnsType[]}/>
<BetaSchemaForm
title={t(`${i18nPrefix}.filter.title`, '高级查询')}
grid={true}
shouldUpdate={false}
width={500}
name={'filterForm'}
form={filterForm}
open={openFilter}
onOpenChange={open => {
setFilterOpen(open)
}}
layout={'vertical'}
scrollToFirstError={true}
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.category_id) {
setCategoryId(values.category_id)
}
}}
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(',')
}
})
setSearch(values)
}}
columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/>
</ListPageLayout>
)
}

26
src/pages/cms/video/style.ts

@ -0,0 +1,26 @@
import { createStyles } from '@/theme'
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => {
const prefix = `${prefixCls}-${token?.proPrefix}-video-local-list-page`
const container = css`
.ant-table-cell{
.ant-tag{
padding-inline: 3px;
margin-inline-end: 3px;
}
}
.ant-table-empty {
.ant-table-body{
height: calc(100vh - 350px)
}
}
.ant-pro-table-highlight{
color: red;
}
`
return {
container: cx(prefix, props?.className, container),
}
})

560
src/pages/cms/video_cloud/index.tsx

@ -1,5 +1,5 @@
import { useTranslation } from '@/i18n.ts'
import { Button, Form, Image, Popconfirm } from 'antd'
import { Badge, Button, DatePicker, Divider, Form, Image, Popconfirm, Space, Tooltip } from 'antd'
import { useAtom, useAtomValue } from 'jotai'
import { useEffect, useMemo, useState } from 'react'
import Switch from '@/components/switch'
@ -10,25 +10,41 @@ import {
deleteVideoCloudAtom,
saveOrUpdateVideoCloudAtom,
videoCloudsAtom,
videoCloudSearchAtom
videoCloudSearchAtom,
videoCloudAtom,
} from '@/store/cms/video_cloud.ts'
import TagValue from '@/components/tag-value/TagValue.tsx'
import dayjs from 'dayjs'
import TagPro from '@/components/tag-pro/TagPro.tsx'
import { useSetAtom } from 'jotai/index'
import { categoriesAtom, categoryByIdAtom, categoryIdAtom } from '@/store/cms/category.ts'
import { videoTypes } from '@/store/cms/video.ts'
import { useStyle} from './style'
import { getValueCount } from '@/utils'
import { FilterOutlined } from '@ant-design/icons'
const i18nPrefix = 'cms.videoCloud'
const VideoCloud = () => {
// 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(saveOrUpdateVideoCloudAtom)
const [ search, setSearch ] = useAtom(videoCloudSearchAtom)
const [ currentVideo, setVideo ] = useAtom(videoCloudAtom)
const { data, isFetching, isLoading, refetch } = useAtomValue(videoCloudsAtom)
const { mutate: deleteVideo, isPending: isDeleting } = useAtomValue(deleteVideoCloudAtom)
const setCategoryId = useSetAtom(categoryIdAtom)
const { data: categories, isLoading: isCateLoading } = useAtomValue(categoriesAtom)
const { data: category, isLoading: isCategoryFetching } = useAtomValue(categoryByIdAtom)
const [ open, setOpen ] = useState(false)
const [ openFilter, setFilterOpen ] = useState(false)
const [ searchKey, setSearchKey ] = useState(search?.title)
const columns = useMemo(() => {
return [
{
title: 'ID',
@ -38,6 +54,15 @@ const VideoCloud = () => {
formItemProps: { hidden: true }
},
{
'title': t(`${i18nPrefix}.columns.collect_id`, 'CollectId'),
'dataIndex': 'collect_id',
hideInTable: true,
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
},
{
'title': t(`${i18nPrefix}.columns.title`, 'Title'),
'dataIndex': 'title',
onHeaderCell: () => {
@ -45,6 +70,15 @@ const VideoCloud = () => {
width: 200,
}
},
colProps: {
span: 12
},
render: (_text, record) => {
//高亮搜索关键字, 从search.title中获取
const title = record.title?.replace?.(new RegExp(`(${search?.title})`, 'ig'), '<span class="ant-pro-table-highlight">$1</span>')
return <span dangerouslySetInnerHTML={{ __html: title }}></span>
}
},
{
'title': t(`${i18nPrefix}.columns.title_sub`, 'TitleSub'),
@ -54,26 +88,10 @@ const VideoCloud = () => {
width: 200,
}
},
},
{
'title': t(`${i18nPrefix}.columns.source_url`, 'SourceUrl'),
'dataIndex': 'source_url',
ellipsis: true,
copyable: true,
onHeaderCell: () => {
return {
width: 200,
colProps: {
span: 12
}
},
},
{
'title': t(`${i18nPrefix}.columns.collect_id`, 'CollectId'),
'dataIndex': 'collect_id',
hideInTable: true,
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
},
{
'title': t(`${i18nPrefix}.columns.type_id`, 'TypeId'),
'dataIndex': 'type_id',
@ -81,24 +99,30 @@ const VideoCloud = () => {
fieldProps: {
options: videoTypes,
},
hideInForm: true,
render: (_dom, record) => {
return t(`${i18nPrefix}.type_id.${record.type_id}`)
},
},
{
'title': t(`${i18nPrefix}.columns.letter`, 'Letter'),
'dataIndex': 'letter'
},
{
'title': t(`${i18nPrefix}.columns.tag`, 'Tag'),
'dataIndex': 'tag',
valueType: 'textarea',
'title': t(`${i18nPrefix}.columns.source_url`, 'SourceUrl'),
'dataIndex': 'source_url',
ellipsis: true,
copyable: true,
onHeaderCell: () => {
return {
width: 200,
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.letter`, 'Letter'),
'dataIndex': 'letter',
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.lock`, 'Lock'),
@ -107,6 +131,10 @@ const VideoCloud = () => {
render: (_dom, record) => {
return <Switch value={record.lock} size={'small'}/>
},
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.copyright`, 'Copyright'),
@ -115,6 +143,10 @@ const VideoCloud = () => {
render: (_dom, record) => {
return <Switch value={record.lock} size={'small'}/>
},
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.is_end`, 'IsEnd'),
@ -123,6 +155,10 @@ const VideoCloud = () => {
render: (_dom, record) => {
return <Switch value={record.lock} size={'small'}/>
},
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.status`, 'Status'),
@ -131,12 +167,30 @@ const VideoCloud = () => {
render: (_dom, record) => {
return <Switch value={record.lock} size={'small'}/>
},
colProps: {
span: 4
}
},
{
'title': t(`${i18nPrefix}.columns.category_id`, 'CategoryId'),
'dataIndex': 'category_id',
'title': t(`${i18nPrefix}.columns.pic_local`, 'PicLocal'),
'dataIndex': 'pic_local',
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
hideInTable: true,
},
{
'title': t(`${i18nPrefix}.columns.pic_status`, 'PicStatus'),
'dataIndex': 'pic_status',
valueType: 'switch',
render: (_dom, record) => {
return <Switch value={record.lock} size={'small'}/>
},
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.pic`, 'Pic'),
@ -144,103 +198,366 @@ const VideoCloud = () => {
render: (_dom, record) => {
return <Image src={record.pic} height={40}/>
},
colProps: {
span: 20
},
{
'title': t(`${i18nPrefix}.columns.pic_local`, 'PicLocal'),
'dataIndex': 'pic_local',
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
hideInTable: true,
},
{
'title': t(`${i18nPrefix}.columns.category_id`, 'CategoryId'),
'dataIndex': 'category_id',
valueType: 'select',
fieldProps: {
loading: isCategoryFetching,
options: categories?.rows ?? [],
fieldNames: {
label: 'name',
value: 'id'
}
},
render: (_dom, record) => {
return <TagValue
tags={categories?.rows.filter(item=>item.id === record.category_id).map(item=>{
return {
label: item.name,
value: item.id
}
}) ?? []}
wrap={currentVideo?.id === record.id}
value={search?.category_id ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
category_id: values,
}
})
setCategoryId(values[0])
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.actor`, 'Actor'),
'dataIndex': 'actor',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
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'),
'dataIndex': 'director',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
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: any) => {
return {
...prev,
director: values,
}
})
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.writer`, 'Writer'),
'dataIndex': 'writer',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
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: any) => {
return {
...prev,
writer: values,
}
})
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.remarks`, 'Remarks'),
'dataIndex': 'remarks',
hideInSearch: true,
},
/* {
'title': t(`${i18nPrefix}.columns.pubdate`, 'Pubdate'),
'dataIndex': 'pubdate',
valueType: 'dateTime'
},
valueType: 'dateTime',
colProps: {
span: 4
}
},*/
{
'title': t(`${i18nPrefix}.columns.total`, 'Total'),
'dataIndex': 'total',
valueType: 'digit'
valueType: 'digit',
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.serial`, 'Serial'),
'dataIndex': 'serial',
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.duration`, 'Duration'),
'dataIndex': 'duration',
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.year`, 'Year'),
'dataIndex': 'year',
valueType: 'dateYear',
fieldProps:{
style:{
width: '100%'
}
},
colProps: {
span: openFilter? 12: 4,
},
render: (_dom, record) => {
if (record.year === undefined || record.year === null || record.year === 0) {
return null
}
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 && config.value > 0 ? dayjs().set('year', config.value) : undefined
if (config.value?.$isDayjsObject) {
value = config.value as dayjs.Dayjs
}
return <DatePicker
{..._schema.formItemProps}
{...props}
picker={'year'}
value={value}
/>
}
},
{
'title': t(`${i18nPrefix}.columns.class`, 'Class'),
'dataIndex': 'class',
'title': t(`${i18nPrefix}.columns.tag`, 'Tag'),
'dataIndex': 'tag',
valueType: 'textarea',
ellipsis: true,
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.class?.split(',') ?? []} {...config} {...schema.fieldProps} />
},
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.tag?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.tag ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
tag: values,
}
})
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.area`, 'Area'),
'dataIndex': 'area',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.area?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.area ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
area: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.area?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
'title': t(`${i18nPrefix}.columns.lang`, 'Lang'),
'dataIndex': 'lang',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.lang?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.lang ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
lang: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.lang?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
'title': t(`${i18nPrefix}.columns.version`, 'Version'),
'dataIndex': 'version'
'dataIndex': 'version',
width: 200,
fieldProps: {
style: { width: '100%' }
},
{
'title': t(`${i18nPrefix}.columns.year`, 'Year'),
'dataIndex': 'year',
valueType: 'dateYear'
render: (_dom, record) => {
return <TagValue
tags={record.version?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.version ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
version: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.version?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
'title': t(`${i18nPrefix}.columns.state`, 'State'),
'dataIndex': 'state'
'dataIndex': 'state',
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.state?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.state ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
state: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.state?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
'title': t(`${i18nPrefix}.columns.douban_score`, 'DoubanScore'),
'dataIndex': 'douban_score'
'dataIndex': 'douban_score',
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
hideInTable: true,
},
{
'title': t(`${i18nPrefix}.columns.douban_id`, 'DoubanId'),
@ -252,7 +569,11 @@ const VideoCloud = () => {
},
{
'title': t(`${i18nPrefix}.columns.imdb_score`, 'ImdbScore'),
'dataIndex': 'imdb_score'
'dataIndex': 'imdb_score',
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
hideInTable: true,
},
{
'title': t(`${i18nPrefix}.columns.imdb_id`, 'ImdbId'),
@ -270,6 +591,7 @@ const VideoCloud = () => {
onHeaderCell: () => ({
width: 200,
}),
hideInSearch: true,
},
{
@ -281,6 +603,7 @@ const VideoCloud = () => {
<Action key="edit"
as={'a'}
onClick={() => {
setCategoryId(record.category_id)
form.setFieldsValue(record)
setOpen(true)
}}>{t('actions.edit')}</Action>,
@ -298,7 +621,14 @@ const VideoCloud = () => {
]
}
] as ProColumns[]
}, [ isDeleting ])
}, [ isDeleting, category, isCategoryFetching, categories, isCateLoading, category, currentVideo, search, openFilter ])
useEffect(() => {
setSearchKey(search?.title)
filterForm.setFieldsValue(search)
}, [ search ])
useEffect(() => {
if (isSuccess) {
@ -307,7 +637,7 @@ const VideoCloud = () => {
}, [ isSuccess ])
return (
<ListPageLayout>
<ListPageLayout className={styles.container}>
<ProTable
rowKey="id"
headerTitle={t(`${i18nPrefix}.title`, '云盘视频管理')}
@ -320,24 +650,51 @@ const VideoCloud = () => {
key: value
}))
},
onChange: (e) => {
setSearchKey(e.target?.value)
},
value: searchKey,
allowClear: true,
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
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
type_id: 2,
})
setOpen(true)
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '添加')}</Button>
]
}}
scroll={{
x: 3500,
x: 3500, y: 'calc(100vh - 290px)'
}}
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}
@ -364,14 +721,15 @@ const VideoCloud = () => {
}}
/>
<BetaSchemaForm
grid={true}
shouldUpdate={false}
width={600}
width={1000}
form={form}
layout={'vertical'}
scrollToFirstError={true}
title={t(`${i18nPrefix}.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? '云盘视频编辑' : '云盘视频添加')}
// colProps={{ span: 24 }}
labelCol={{ span: 6 }}
// labelCol={{ span: 6 }}
// wrapperCol={{ span: 14 }}
layoutType={'DrawerForm'}
open={open}
@ -388,6 +746,72 @@ const VideoCloud = () => {
}}
columns={columns as ProFormColumnsType[]}/>
<BetaSchemaForm
title={t(`${i18nPrefix}.filter.title`, '高级查询')}
grid={true}
shouldUpdate={false}
width={500}
name={'filterForm'}
form={filterForm}
open={openFilter}
onOpenChange={open => {
setFilterOpen(open)
}}
layout={'vertical'}
scrollToFirstError={true}
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.category_id) {
setCategoryId(values.category_id)
}
}}
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(',')
}
})
setSearch(values)
}}
columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/>
</ListPageLayout>
)
}

26
src/pages/cms/video_cloud/style.ts

@ -0,0 +1,26 @@
import { createStyles } from '@/theme'
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => {
const prefix = `${prefixCls}-${token?.proPrefix}-video-cloud-list-page`
const container = css`
.ant-table-cell{
.ant-tag{
padding-inline: 3px;
margin-inline-end: 3px;
}
}
.ant-table-empty {
.ant-table-body{
height: calc(100vh - 350px)
}
}
.ant-pro-table-highlight{
color: red;
}
`
return {
container: cx(prefix, props?.className, container),
}
})

562
src/pages/cms/video_magnet/index.tsx

@ -1,5 +1,5 @@
import { useTranslation } from '@/i18n.ts'
import { Button, Form, Popconfirm, Image } from 'antd'
import { Button, Form, Popconfirm, Image, Tooltip, Badge, Divider, DatePicker, Space } from 'antd'
import { useAtom, useAtomValue } from 'jotai'
import { useEffect, useMemo, useState } from 'react'
import Switch from '@/components/switch'
@ -9,25 +9,42 @@ import ListPageLayout from '@/layout/ListPageLayout.tsx'
import { videoTypes } from '@/store/cms/video.ts'
import {
deleteVideoMagnetAtom,
saveOrUpdateVideoMagnetAtom,
saveOrUpdateVideoMagnetAtom, videoMagnetAtom,
videoMagnetsAtom,
videoMagnetSearchAtom
} from '@/store/cms/video_magnet.ts'
import { getValueCount } from '@/utils'
import { FilterOutlined } from '@ant-design/icons'
import { useSetAtom } from 'jotai/index'
import { categoriesAtom, categoryByIdAtom, categoryIdAtom } from '@/store/cms/category.ts'
import TagValue from '@/components/tag-value/TagValue.tsx'
import dayjs from 'dayjs'
import TagPro from '@/components/tag-pro/TagPro.tsx'
import { useStyle } from './style'
const i18nPrefix = 'cms.videoMagnet'
const VideoMagnet = () => {
// 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(saveOrUpdateVideoMagnetAtom)
const [ search, setSearch ] = useAtom(videoMagnetSearchAtom)
const [ currentVideo, setVideo ] = useAtom(videoMagnetAtom)
const { data, isFetching, isLoading, refetch } = useAtomValue(videoMagnetsAtom)
const { mutate: deleteVideo, isPending: isDeleting } = useAtomValue(deleteVideoMagnetAtom)
const setCategoryId = useSetAtom(categoryIdAtom)
const { data: categories, isLoading: isCateLoading } = useAtomValue(categoriesAtom)
const { data: category, isLoading: isCategoryFetching } = useAtomValue(categoryByIdAtom)
const [ open, setOpen ] = useState(false)
const [ openFilter, setFilterOpen ] = useState(false)
const [ searchKey, setSearchKey ] = useState(search?.title)
const columns = useMemo(() => {
return [
{
title: 'ID',
@ -37,6 +54,15 @@ const VideoMagnet = () => {
formItemProps: { hidden: true }
},
{
'title': t(`${i18nPrefix}.columns.collect_id`, 'CollectId'),
'dataIndex': 'collect_id',
hideInTable: true,
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
},
{
'title': t(`${i18nPrefix}.columns.title`, 'Title'),
'dataIndex': 'title',
onHeaderCell: () => {
@ -44,6 +70,15 @@ const VideoMagnet = () => {
width: 200,
}
},
colProps: {
span: 12
},
render: (_text, record) => {
//高亮搜索关键字, 从search.title中获取
const title = record.title?.replace?.(new RegExp(`(${search?.title})`, 'ig'), '<span class="ant-pro-table-highlight">$1</span>')
return <span dangerouslySetInnerHTML={{ __html: title }}></span>
}
},
{
'title': t(`${i18nPrefix}.columns.title_sub`, 'TitleSub'),
@ -53,26 +88,10 @@ const VideoMagnet = () => {
width: 200,
}
},
},
{
'title': t(`${i18nPrefix}.columns.source_url`, 'SourceUrl'),
'dataIndex': 'source_url',
ellipsis: true,
copyable: true,
onHeaderCell: () => {
return {
width: 200,
colProps: {
span: 12
}
},
},
{
'title': t(`${i18nPrefix}.columns.collect_id`, 'CollectId'),
'dataIndex': 'collect_id',
hideInTable: true,
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
},
{
'title': t(`${i18nPrefix}.columns.type_id`, 'TypeId'),
'dataIndex': 'type_id',
@ -80,24 +99,30 @@ const VideoMagnet = () => {
fieldProps: {
options: videoTypes,
},
hideInForm: true,
render: (_dom, record) => {
return t(`${i18nPrefix}.type_id.${record.type_id}`)
},
},
{
'title': t(`${i18nPrefix}.columns.letter`, 'Letter'),
'dataIndex': 'letter'
},
{
'title': t(`${i18nPrefix}.columns.tag`, 'Tag'),
'dataIndex': 'tag',
valueType: 'textarea',
'title': t(`${i18nPrefix}.columns.source_url`, 'SourceUrl'),
'dataIndex': 'source_url',
ellipsis: true,
copyable: true,
onHeaderCell: () => {
return {
width: 200,
}
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.letter`, 'Letter'),
'dataIndex': 'letter',
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.lock`, 'Lock'),
@ -106,6 +131,10 @@ const VideoMagnet = () => {
render: (_dom, record) => {
return <Switch value={record.lock} size={'small'}/>
},
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.copyright`, 'Copyright'),
@ -114,6 +143,10 @@ const VideoMagnet = () => {
render: (_dom, record) => {
return <Switch value={record.lock} size={'small'}/>
},
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.is_end`, 'IsEnd'),
@ -122,6 +155,10 @@ const VideoMagnet = () => {
render: (_dom, record) => {
return <Switch value={record.lock} size={'small'}/>
},
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.status`, 'Status'),
@ -130,12 +167,30 @@ const VideoMagnet = () => {
render: (_dom, record) => {
return <Switch value={record.lock} size={'small'}/>
},
colProps: {
span: 4
}
},
{
'title': t(`${i18nPrefix}.columns.category_id`, 'CategoryId'),
'dataIndex': 'category_id',
'title': t(`${i18nPrefix}.columns.pic_local`, 'PicLocal'),
'dataIndex': 'pic_local',
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
hideInTable: true,
},
{
'title': t(`${i18nPrefix}.columns.pic_status`, 'PicStatus'),
'dataIndex': 'pic_status',
valueType: 'switch',
render: (_dom, record) => {
return <Switch value={record.lock} size={'small'}/>
},
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.pic`, 'Pic'),
@ -143,104 +198,366 @@ const VideoMagnet = () => {
render: (_dom, record) => {
return <Image src={record.pic} height={40}/>
},
colProps: {
span: 20
},
{
'title': t(`${i18nPrefix}.columns.pic_local`, 'PicLocal'),
'dataIndex': 'pic_local',
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
hideInTable: true,
},
{
'title': t(`${i18nPrefix}.columns.category_id`, 'CategoryId'),
'dataIndex': 'category_id',
valueType: 'select',
fieldProps: {
loading: isCategoryFetching,
options: categories?.rows ?? [],
fieldNames: {
label: 'name',
value: 'id'
}
},
render: (_dom, record) => {
return <TagValue
tags={categories?.rows.filter(item => item.id === record.category_id).map(item => {
return {
label: item.name,
value: item.id
}
}) ?? []}
wrap={currentVideo?.id === record.id}
value={search?.category_id ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
category_id: values,
}
})
setCategoryId(values[0])
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.actor`, 'Actor'),
'dataIndex': 'actor',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
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'),
'dataIndex': 'director',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
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: any) => {
return {
...prev,
director: values,
}
})
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.writer`, 'Writer'),
'dataIndex': 'writer',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
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: any) => {
return {
...prev,
writer: values,
}
})
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.remarks`, 'Remarks'),
'dataIndex': 'remarks',
hideInSearch: true,
},
/* {
'title': t(`${i18nPrefix}.columns.pubdate`, 'Pubdate'),
'dataIndex': 'pubdate',
valueType: 'dateTime'
},
valueType: 'dateTime',
colProps: {
span: 4
}
},*/
{
'title': t(`${i18nPrefix}.columns.total`, 'Total'),
'dataIndex': 'total',
valueType: 'digit'
valueType: 'digit',
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.serial`, 'Serial'),
'dataIndex': 'serial',
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.duration`, 'Duration'),
'dataIndex': 'duration',
colProps: {
span: 4
},
hideInSearch: true,
},
{
'title': t(`${i18nPrefix}.columns.year`, 'Year'),
'dataIndex': 'year',
valueType: 'dateYear',
fieldProps: {
style: {
width: '100%'
}
},
colProps: {
span: openFilter ? 12 : 4,
},
render: (_dom, record) => {
if (record.year === undefined || record.year === null || record.year === 0) {
return null
}
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 && config.value > 0 ? dayjs().set('year', config.value) : undefined
if (config.value?.$isDayjsObject) {
value = config.value as dayjs.Dayjs
}
return <DatePicker
{..._schema.formItemProps}
{...props}
picker={'year'}
value={value}
/>
}
},
{
'title': t(`${i18nPrefix}.columns.class`, 'Class'),
'dataIndex': 'class',
'title': t(`${i18nPrefix}.columns.tag`, 'Tag'),
'dataIndex': 'tag',
valueType: 'textarea',
ellipsis: true,
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.class?.split(',') ?? []} {...config} {...schema.fieldProps} />
},
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.tag?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.tag ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
tag: values,
}
})
setFilterOpen(true)
}}
/>
},
},
{
'title': t(`${i18nPrefix}.columns.area`, 'Area'),
'dataIndex': 'area',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.area?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.area ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
area: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.area?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
'title': t(`${i18nPrefix}.columns.lang`, 'Lang'),
'dataIndex': 'lang',
ellipsis: true,
onHeaderCell: () => {
return {
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.lang?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.lang ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
lang: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.lang?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
'title': t(`${i18nPrefix}.columns.version`, 'Version'),
'dataIndex': 'version'
'dataIndex': 'version',
width: 200,
fieldProps: {
style: { width: '100%' }
},
{
'title': t(`${i18nPrefix}.columns.year`, 'Year'),
'dataIndex': 'year',
valueType: 'dateYear'
render: (_dom, record) => {
return <TagValue
tags={record.version?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.version ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
version: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.version?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
'title': t(`${i18nPrefix}.columns.state`, 'State'),
'dataIndex': 'state'
'dataIndex': 'state',
width: 200,
fieldProps: {
style: { width: '100%' }
},
render: (_dom, record) => {
return <TagValue
tags={record.state?.split(',') ?? []}
wrap={currentVideo?.id === record.id}
value={search?.state ?? []}
onChange={(values) => {
setSearch((prev: any) => {
return {
...prev,
state: values,
}
})
setFilterOpen(true)
}}
/>
},
renderFormItem: (schema, config) => {
return <TagPro loading={isCategoryFetching}
tags={category?.extend?.state?.split(',') ?? []} {...config} {...schema.fieldProps} />
}
},
{
'title': t(`${i18nPrefix}.columns.douban_score`, 'DoubanScore'),
'dataIndex': 'douban_score'
'dataIndex': 'douban_score',
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
hideInTable: true,
},
{
'title': t(`${i18nPrefix}.columns.douban_id`, 'DoubanId'),
@ -252,7 +569,11 @@ const VideoMagnet = () => {
},
{
'title': t(`${i18nPrefix}.columns.imdb_score`, 'ImdbScore'),
'dataIndex': 'imdb_score'
'dataIndex': 'imdb_score',
hideInSearch: true,
hideInSetting: true,
formItemProps: { hidden: true },
hideInTable: true,
},
{
'title': t(`${i18nPrefix}.columns.imdb_id`, 'ImdbId'),
@ -270,9 +591,9 @@ const VideoMagnet = () => {
onHeaderCell: () => ({
width: 200,
}),
hideInSearch: true,
},
{
title: t(`${i18nPrefix}.columns.option`, '操作'),
key: 'option',
@ -282,6 +603,7 @@ const VideoMagnet = () => {
<Action key="edit"
as={'a'}
onClick={() => {
setCategoryId(record.category_id)
form.setFieldsValue(record)
setOpen(true)
}}>{t('actions.edit')}</Action>,
@ -299,7 +621,14 @@ const VideoMagnet = () => {
]
}
] as ProColumns[]
}, [ isDeleting ])
}, [ isDeleting, category, isCategoryFetching, categories, isCateLoading, category, currentVideo, search, openFilter ])
useEffect(() => {
setSearchKey(search?.title)
filterForm.setFieldsValue(search)
}, [ search ])
useEffect(() => {
if (isSuccess) {
@ -308,7 +637,7 @@ const VideoMagnet = () => {
}, [ isSuccess ])
return (
<ListPageLayout>
<ListPageLayout className={styles.container}>
<ProTable
rowKey="id"
headerTitle={t(`${i18nPrefix}.title`, '磁链视频管理')}
@ -321,15 +650,30 @@ const VideoMagnet = () => {
key: value
}))
},
onChange: (e) => {
setSearchKey(e.target?.value)
},
value: searchKey,
allowClear: true,
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
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
type_id: 1,
})
setOpen(true)
}}
@ -337,8 +681,19 @@ const VideoMagnet = () => {
]
}}
scroll={{
x: 3500,
x: 3500, y: 'calc(100vh - 290px)'
}}
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}
@ -365,14 +720,15 @@ const VideoMagnet = () => {
}}
/>
<BetaSchemaForm
grid={true}
shouldUpdate={false}
width={600}
width={1000}
form={form}
layout={'vertical'}
scrollToFirstError={true}
title={t(`${i18nPrefix}.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? '磁链视频编辑' : '磁链视频添加')}
// colProps={{ span: 24 }}
labelCol={{ span: 6 }}
// labelCol={{ span: 6 }}
// wrapperCol={{ span: 14 }}
layoutType={'DrawerForm'}
open={open}
@ -389,6 +745,74 @@ const VideoMagnet = () => {
}}
columns={columns as ProFormColumnsType[]}/>
<BetaSchemaForm
title={t(`${i18nPrefix}.filter.title`, '高级查询')}
grid={true}
shouldUpdate={false}
width={500}
name={'filterForm'}
form={filterForm}
open={openFilter}
onOpenChange={open => {
setFilterOpen(open)
}}
layout={'vertical'}
scrollToFirstError={true}
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.category_id) {
setCategoryId(values.category_id)
}
}}
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(',')
}
})
setSearch(values)
}}
columns={columns.filter(item => !item.hideInSearch) as ProFormColumnsType[]}/>
</ListPageLayout>
)
}

26
src/pages/cms/video_magnet/style.ts

@ -0,0 +1,26 @@
import { createStyles } from '@/theme'
export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => {
const prefix = `${prefixCls}-${token?.proPrefix}-video-magnet-list-page`
const container = css`
.ant-table-cell{
.ant-tag{
padding-inline: 3px;
margin-inline-end: 3px;
}
}
.ant-table-empty {
.ant-table-body{
height: calc(100vh - 350px)
}
}
.ant-pro-table-highlight{
color: red;
}
`
return {
container: cx(prefix, props?.className, container),
}
})

3
src/pages/videos/list/index.tsx

@ -126,7 +126,7 @@ const Video = () => {
},
colProps: {
span: 8
}
},
},
// {
// 'title': t(`${i18nPrefix}.columns.source_url`, 'SourceUrl'),
@ -451,6 +451,7 @@ const Video = () => {
fieldProps: {
style: { width: '100%' }
},
hideInSearch: true,
},
// {
// 'title': t(`${i18nPrefix}.columns.remarks`, 'Remarks'),

14
src/store/cms/video.ts

@ -10,7 +10,7 @@ const i18nPrefix = 'cms.video'
type SearchParams = IPage & {
key?: string
}
} & Partial<Cms.IVideo>
export const videoTypes = [
{ label: t(`${i18nPrefix}.type_id.0`), value: 0 },
@ -37,7 +37,17 @@ export const videosAtom = atomWithQuery((get) => {
return {
queryKey: [ 'videos', get(videoSearchAtom) ],
queryFn: async ({ queryKey: [ , params ] }) => {
return await cmsServ.video.list(params as SearchParams)
//处理数组,转成,分隔的字符串
const p = {} as SearchParams
Object.keys(params as any).forEach(key=>{
const value =(params as any)[key]
if (Array.isArray(value)) {
p[key] = value.join(',')
} else {
p[key] = value
}
})
return await cmsServ.video.list(p)
},
select: res => {
const data = res.data

2
src/store/cms/video_cloud.ts

@ -9,7 +9,7 @@ import cmsServ from '@/service/cms.ts'
type SearchParams = IPage & {
key?: string
}
} & Partial<Cms.IVideoCloud>
export const videoCloudIdAtom = atom(0)

2
src/store/cms/video_magnet.ts

@ -9,7 +9,7 @@ import cmsServ from '@/service/cms.ts'
type SearchParams = IPage & {
key?: string
}
} & Partial<Cms.IVideoMagnet>
export const videoMagnetIdAtom = atom(0)

Loading…
Cancel
Save