You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
13 KiB

  1. import { useTranslation } from '@/i18n.ts'
  2. import { getToken } from '@/store/system.ts'
  3. import { Button, DatePicker, Form, Image, Popconfirm } from 'antd'
  4. import dayjs from 'dayjs'
  5. import { useAtom, useAtomValue, useSetAtom } from 'jotai'
  6. import {
  7. deleteVideoAtom, getTypeName,
  8. saveOrUpdateVideoAtom, videosAtom, videoSearchAtom, videoTypes
  9. } from '@/store/videos/video.ts'
  10. import { useEffect, useMemo, useState } from 'react'
  11. import Switch from '@/components/switch'
  12. import Action from '@/components/action/Action.tsx'
  13. import {
  14. BetaSchemaForm,
  15. ProColumns,
  16. ProFormColumnsType,
  17. ProFormDatePicker,
  18. ProFormUploadButton,
  19. ProTable
  20. } from '@ant-design/pro-components'
  21. import ListPageLayout from '@/layout/ListPageLayout.tsx'
  22. import { categoryByIdAtom, categoryIdAtom } from '@/store/videos/category.ts'
  23. import TagPro from '@/components/tag-pro/TagPro.tsx'
  24. const i18nPrefix = 'videos.list'
  25. const Video = () => {
  26. // const { styles } = useStyle()
  27. const { t } = useTranslation()
  28. const [ form ] = Form.useForm()
  29. const { mutate: saveOrUpdate, isPending: isSubmitting, isSuccess } = useAtomValue(saveOrUpdateVideoAtom)
  30. const [ search, setSearch ] = useAtom(videoSearchAtom)
  31. const { data, isFetching, isLoading, refetch } = useAtomValue(videosAtom)
  32. const { mutate: deleteVideo, isPending: isDeleting } = useAtomValue(deleteVideoAtom)
  33. const setCategoryId = useSetAtom(categoryIdAtom)
  34. const { data: category, isLoading: isCategoryFetching } = useAtomValue(categoryByIdAtom)
  35. const [ open, setOpen ] = useState(false)
  36. const columns = useMemo(() => {
  37. return [
  38. {
  39. title: 'ID',
  40. dataIndex: 'id',
  41. hideInTable: true,
  42. hideInSearch: true,
  43. formItemProps: { hidden: true }
  44. },
  45. {
  46. 'title': t(`${i18nPrefix}.columns.video_id`, 'VideoId'),
  47. 'dataIndex': 'video_id',
  48. hideInTable: true,
  49. hideInSearch: true,
  50. hideInSetting: true,
  51. formItemProps: { hidden: true },
  52. },
  53. {
  54. 'title': t(`${i18nPrefix}.columns.title`, 'Title'),
  55. 'dataIndex': 'title',
  56. onHeaderCell: () => {
  57. return {
  58. width: 200,
  59. }
  60. },
  61. colProps: {
  62. span: 8
  63. }
  64. },
  65. {
  66. 'title': t(`${i18nPrefix}.columns.title_sub`, 'TitleSub'),
  67. 'dataIndex': 'title_sub',
  68. onHeaderCell: () => {
  69. return {
  70. width: 200,
  71. }
  72. },
  73. colProps: {
  74. span: 8
  75. }
  76. },
  77. {
  78. 'title': t(`${i18nPrefix}.columns.type_id`, 'TypeId'),
  79. 'dataIndex': 'type_id',
  80. valueType: 'select',
  81. fieldProps: {
  82. options: videoTypes,
  83. },
  84. render: (_dom, record) => {
  85. return t(`${i18nPrefix}.type_id.${record.type_id}`)
  86. },
  87. colProps: {
  88. span: 8
  89. }
  90. },
  91. {
  92. 'title': t(`${i18nPrefix}.columns.source_url`, 'SourceUrl'),
  93. 'dataIndex': 'source_url',
  94. ellipsis: true,
  95. copyable: true,
  96. onHeaderCell: () => {
  97. return {
  98. width: 200,
  99. }
  100. },
  101. },
  102. {
  103. 'title': t(`${i18nPrefix}.columns.year`, 'Year'),
  104. 'dataIndex': 'year',
  105. valueType: 'dateYear',
  106. colProps: {
  107. span: 4
  108. },
  109. render: (_dom, record) => {
  110. return record.year
  111. },
  112. renderFormItem: (_schema, config) => {
  113. return <DatePicker
  114. picker={'year'}
  115. {...config}
  116. value={dayjs().set('year', config.value || new Date().getFullYear())}
  117. />
  118. }
  119. },
  120. {
  121. 'title': t(`${i18nPrefix}.columns.category_id`, 'CategoryId'),
  122. 'dataIndex': 'class_name',
  123. colProps: {
  124. span: 4
  125. },
  126. },
  127. {
  128. 'title': t(`${i18nPrefix}.columns.img`, '封面'),
  129. 'dataIndex': 'pic',
  130. hideInSearch: true,
  131. hideInTable: true,
  132. colProps: {
  133. span: 4
  134. },
  135. renderFormItem: (_schema, _config, form) => {
  136. return <ProFormUploadButton
  137. fieldProps={{
  138. name: 'file',
  139. listType: 'picture-card',
  140. data: { videoId: form.getFieldValue('video_id') },
  141. headers: {
  142. 'Authorization': `Bearer ${getToken()}`
  143. },
  144. }}
  145. fileList={[]}
  146. action="/api/v1/videos/image/upload"
  147. />
  148. }
  149. },
  150. {
  151. 'title': t(`${i18nPrefix}.columns.actor`, 'Actor'),
  152. 'dataIndex': 'actor',
  153. ellipsis: true,
  154. onHeaderCell: () => {
  155. return {
  156. width: 200,
  157. }
  158. },
  159. },
  160. {
  161. 'title': t(`${i18nPrefix}.columns.director`, 'Director'),
  162. 'dataIndex': 'director'
  163. },
  164. {
  165. 'title': t(`${i18nPrefix}.columns.content`, 'Content'),
  166. 'dataIndex': 'content',
  167. valueType: 'textarea',
  168. ellipsis: true,
  169. onHeaderCell: () => ({
  170. width: 200,
  171. }),
  172. },
  173. {
  174. 'title': t(`${i18nPrefix}.columns.writer`, 'Writer'),
  175. 'dataIndex': 'writer'
  176. },
  177. {
  178. 'title': t(`${i18nPrefix}.columns.remarks`, 'Remarks'),
  179. 'dataIndex': 'remarks'
  180. },
  181. {
  182. 'title': t(`${i18nPrefix}.columns.tag`, 'Tag'),
  183. 'dataIndex': 'tag',
  184. valueType: 'textarea',
  185. ellipsis: true,
  186. onHeaderCell: () => {
  187. return {
  188. width: 200,
  189. }
  190. },
  191. renderFormItem: (schema, config) => {
  192. return <TagPro loading={isCategoryFetching}
  193. tags={category?.extend?.class?.split(',') ?? []} {...config} {...schema.fieldProps} />
  194. }
  195. },
  196. {
  197. 'title': t(`${i18nPrefix}.columns.area`, 'Area'),
  198. 'dataIndex': 'area',
  199. ellipsis: true,
  200. onHeaderCell: () => {
  201. return {
  202. width: 200,
  203. }
  204. },
  205. renderFormItem: (schema, config) => {
  206. return <TagPro loading={isCategoryFetching}
  207. tags={category?.extend?.area?.split(',') ?? []} {...config} {...schema.fieldProps} />
  208. }
  209. },
  210. {
  211. 'title': t(`${i18nPrefix}.columns.lang`, 'Lang'),
  212. 'dataIndex': 'lang',
  213. ellipsis: true,
  214. onHeaderCell: () => {
  215. return {
  216. width: 200,
  217. }
  218. },
  219. renderFormItem: (schema, config) => {
  220. return <TagPro loading={isCategoryFetching}
  221. tags={category?.extend?.lang?.split(',') ?? []} {...config} {...schema.fieldProps} />
  222. }
  223. },
  224. /*{
  225. 'title': t(`${i18nPrefix}.columns.version`, 'Version'),
  226. 'dataIndex': 'version',
  227. renderFormItem: (schema, config) => {
  228. return <TagPro loading={isCategoryFetching}
  229. tags={category?.extend?.version?.split(',') ?? []} {...config} {...schema.fieldProps} />
  230. }
  231. },
  232. {
  233. 'title': t(`${i18nPrefix}.columns.state`, 'State'),
  234. 'dataIndex': 'state',
  235. renderFormItem: (schema, config) => {
  236. return <TagPro loading={isCategoryFetching}
  237. tags={category?.extend?.state?.split(',') ?? []} {...config} {...schema.fieldProps} />
  238. }
  239. },*/
  240. {
  241. 'title': t(`${i18nPrefix}.columns.douban_score`, 'DoubanScore'),
  242. 'dataIndex': 'douban_score',
  243. hideInSearch: true,
  244. hideInSetting: true,
  245. formItemProps: { hidden: true },
  246. hideInTable: true,
  247. },
  248. {
  249. 'title': t(`${i18nPrefix}.columns.douban_id`, 'DoubanId'),
  250. 'dataIndex': 'douban_id',
  251. hideInSearch: true,
  252. hideInSetting: true,
  253. formItemProps: { hidden: true },
  254. hideInTable: true,
  255. },
  256. {
  257. 'title': t(`${i18nPrefix}.columns.imdb_score`, 'ImdbScore'),
  258. 'dataIndex': 'imdb_score',
  259. hideInSearch: true,
  260. hideInSetting: true,
  261. formItemProps: { hidden: true },
  262. hideInTable: true,
  263. },
  264. {
  265. 'title': t(`${i18nPrefix}.columns.imdb_id`, 'ImdbId'),
  266. 'dataIndex': 'imdb_id',
  267. hideInSearch: true,
  268. hideInSetting: true,
  269. formItemProps: { hidden: true },
  270. hideInTable: true,
  271. },
  272. {
  273. title: t(`${i18nPrefix}.columns.option`, '操作'),
  274. key: 'option',
  275. valueType: 'option',
  276. fixed: 'right',
  277. render: (_, record) => [
  278. <Action key="edit"
  279. as={'a'}
  280. onClick={() => {
  281. setCategoryId(record.type_id)
  282. form.setFieldsValue(record)
  283. setOpen(true)
  284. }}>{t('actions.edit')}</Action>,
  285. <Popconfirm
  286. key={'del_confirm'}
  287. disabled={isDeleting}
  288. onConfirm={() => {
  289. deleteVideo([ record.id ])
  290. }}
  291. title={t('message.deleteConfirm')}>
  292. <a key="del">
  293. {t('actions.delete', '删除')}
  294. </a>
  295. </Popconfirm>
  296. ]
  297. }
  298. ] as ProColumns[]
  299. }, [ isDeleting, category, isCategoryFetching ])
  300. useEffect(() => {
  301. if (isSuccess) {
  302. setOpen(false)
  303. }
  304. }, [ isSuccess ])
  305. return (
  306. <ListPageLayout>
  307. <ProTable
  308. rowKey="id"
  309. headerTitle={t(`${i18nPrefix}.title`, '视频管理')}
  310. toolbar={{
  311. search: {
  312. loading: isFetching && !!search.key,
  313. onSearch: (value: string) => {
  314. setSearch(prev => ({
  315. ...prev,
  316. key: value
  317. }))
  318. },
  319. allowClear: true,
  320. placeholder: t(`${i18nPrefix}.placeholder`, '输入视频名称')
  321. },
  322. actions: [
  323. <Button
  324. onClick={() => {
  325. form.resetFields()
  326. form.setFieldsValue({
  327. id: 0,
  328. })
  329. setOpen(true)
  330. }}
  331. type={'primary'}>{t(`${i18nPrefix}.add`, '添加')}</Button>
  332. ]
  333. }}
  334. scroll={{
  335. x: 3500,
  336. }}
  337. loading={isLoading || isFetching}
  338. dataSource={data?.rows ?? []}
  339. columns={columns}
  340. search={false}
  341. options={{
  342. reload: () => {
  343. refetch()
  344. },
  345. }}
  346. pagination={{
  347. total: data?.total,
  348. pageSize: search.pageSize,
  349. current: search.page,
  350. onChange: (current, pageSize) => {
  351. setSearch(prev => {
  352. return {
  353. ...prev,
  354. page: current,
  355. pageSize: pageSize,
  356. }
  357. })
  358. },
  359. }}
  360. />
  361. <BetaSchemaForm
  362. grid={true}
  363. shouldUpdate={false}
  364. width={1000}
  365. form={form}
  366. layout={'vertical'}
  367. scrollToFirstError={true}
  368. title={t(`${i18nPrefix}.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? '视频编辑' : '视频添加')}
  369. // colProps={{ span: 24 }}
  370. // labelCol={{ span: 8 }}
  371. // wrapperCol={{ span: 14 }}
  372. layoutType={'DrawerForm'}
  373. open={open}
  374. drawerProps={{
  375. maskClosable: false,
  376. }}
  377. onOpenChange={(open) => {
  378. setOpen(open)
  379. }}
  380. loading={isSubmitting}
  381. onValuesChange={(values) => {
  382. if (values.type_id) {
  383. setCategoryId(values.type_id)
  384. const typeName = getTypeName(values.type_id)
  385. form.setFieldsValue({
  386. class_name: typeName,
  387. })
  388. }
  389. }}
  390. onFinish={async (values) => {
  391. // console.log('values', values)
  392. saveOrUpdate(values)
  393. }}
  394. columns={columns as ProFormColumnsType[]}/>
  395. </ListPageLayout>
  396. )
  397. }
  398. export default Video