|
|
import { useTranslation } from '@/i18n.ts' 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' import Action from '@/components/action/Action.tsx' import { BetaSchemaForm, ProColumns, ProFormColumnsType, ProTable } from '@ant-design/pro-components' import ListPageLayout from '@/layout/ListPageLayout.tsx' import { deleteVideoCloudAtom, saveOrUpdateVideoCloudAtom, videoCloudsAtom, 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, 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', dataIndex: 'id', hideInTable: true, hideInSearch: true, 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', fixed: 'left', ellipsis: true, width: 250, fieldProps: { style: { width: '100%' } }, 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'), 'dataIndex': 'title_sub', ellipsis: true, width: 250, fieldProps: { style: { width: '100%' } }, colProps: { span: 12 } }, { 'title': t(`${i18nPrefix}.columns.type_id`, 'TypeId'), 'dataIndex': 'type_id', valueType: 'select', fieldProps: { options: videoTypes, }, hideInForm: true, render: (_dom, record) => { return t(`${i18nPrefix}.type_id.${record.type_id}`) }, }, { 'title': t(`${i18nPrefix}.columns.source_url`, 'SourceUrl'), 'dataIndex': 'source_url', ellipsis: true, copyable: true, width: 200, fieldProps: { style: { width: '100%' } }, hideInSearch: true, }, { 'title': t(`${i18nPrefix}.columns.letter`, 'Letter'), 'dataIndex': 'letter', colProps: { span: 4 }, hideInSearch: true, }, { 'title': t(`${i18nPrefix}.columns.lock`, 'Lock'), 'dataIndex': 'lock', valueType: 'switch', render: (_dom, record) => { return <Switch value={record.lock} size={'small'}/> }, colProps: { span: 4 }, hideInSearch: true, }, { 'title': t(`${i18nPrefix}.columns.copyright`, 'Copyright'), 'dataIndex': 'copyright', valueType: 'switch', render: (_dom, record) => { return <Switch value={record.lock} size={'small'}/> }, colProps: { span: 4 }, hideInSearch: true, }, { 'title': t(`${i18nPrefix}.columns.is_end`, 'IsEnd'), 'dataIndex': 'is_end', valueType: 'switch', render: (_dom, record) => { return <Switch value={record.lock} size={'small'}/> }, colProps: { span: 4 }, hideInSearch: true, }, { 'title': t(`${i18nPrefix}.columns.status`, 'Status'), 'dataIndex': 'status', valueType: 'switch', render: (_dom, record) => { return <Switch value={record.lock} size={'small'}/> }, colProps: { span: 4 }
}, { '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'), 'dataIndex': 'pic', render: (_dom, record) => { return <Image src={record.pic} height={40}/> }, colProps: { span: 20 }, hideInSearch: 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, 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', 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', 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', 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: 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, 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, 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, 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',
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} /> } }, { '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} /> } }, { 'title': t(`${i18nPrefix}.columns.douban_score`, 'DoubanScore'), 'dataIndex': 'douban_score', hideInSearch: true, hideInSetting: true, formItemProps: { hidden: true }, hideInTable: true, }, { 'title': t(`${i18nPrefix}.columns.douban_id`, 'DoubanId'), 'dataIndex': 'douban_id', hideInSearch: true, hideInSetting: true, formItemProps: { hidden: true }, hideInTable: true, }, { 'title': t(`${i18nPrefix}.columns.imdb_score`, 'ImdbScore'), 'dataIndex': 'imdb_score', hideInSearch: true, hideInSetting: true, formItemProps: { hidden: true }, hideInTable: true, }, { 'title': t(`${i18nPrefix}.columns.imdb_id`, 'ImdbId'), 'dataIndex': 'imdb_id', hideInSearch: true, hideInSetting: true, formItemProps: { hidden: true }, hideInTable: true, }, { 'title': t(`${i18nPrefix}.columns.content`, 'Content'), 'dataIndex': 'content', valueType: 'textarea', ellipsis: true, onHeaderCell: () => ({ width: 200, }), hideInSearch: true, },
{ title: t(`${i18nPrefix}.columns.option`, '操作'), key: 'option', valueType: 'option', fixed: 'right', render: (_, record) => [ <Action key="edit" as={'a'} onClick={() => { setCategoryId(record.category_id) form.setFieldsValue(record) setOpen(true) }}>{t('actions.edit')}</Action>, <Popconfirm key={'del_confirm'} disabled={isDeleting} onConfirm={() => { deleteVideo([ record.id ]) }} title={t('message.deleteConfirm')}> <a key="del"> {t('actions.delete', '删除')} </a> </Popconfirm> ] } ] as ProColumns[] }, [ isDeleting, category, isCategoryFetching, categories, isCateLoading, category, currentVideo, search, openFilter ])
useEffect(() => {
setSearchKey(search?.title) filterForm.setFieldsValue(search)
}, [ search ])
useEffect(() => { if (isSuccess) { setOpen(false) } }, [ isSuccess ])
return ( <ListPageLayout className={styles.container}> <ProTable rowKey="id" headerTitle={t(`${i18nPrefix}.title`, '云盘视频管理')} toolbar={{ search: { loading: isFetching && !!search.key, onSearch: (value: string) => { setSearch(prev => ({ ...prev, title: 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: 3800, 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} search={false} options={{ reload: () => { refetch() }, }} pagination={{ total: data?.total, pageSize: search.pageSize, current: search.page, onChange: (current, pageSize) => { setSearch(prev => { return { ...prev, page: current, pageSize: pageSize, } }) },
}} /> <BetaSchemaForm grid={true} shouldUpdate={false} 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 }}
// wrapperCol={{ span: 14 }}
layoutType={'DrawerForm'} open={open} drawerProps={{ maskClosable: false, }} onOpenChange={(open) => { setOpen(open) }} loading={isSubmitting} onFinish={async (values) => { // console.log('values', values)
saveOrUpdate(values)
}} 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> ) }
export default VideoCloud
|