index.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. import React, { useEffect, useState } from 'react';
  2. import { Space, Table, Button, Input, Select, DatePicker, Tabs, message, Typography, Spin } from 'antd';
  3. import type { TableProps } from 'antd';
  4. import dayjs, { Dayjs } from 'dayjs';
  5. import styles from './index.module.css';
  6. import PunlishPlanModal from './components/publishPlanModal';
  7. const { RangePicker } = DatePicker;
  8. import { useAccountOptions } from '@src/views/publishContent/weGZH/hooks/useAccountOptions';
  9. import { useGzhPlanList, GzhPlanDataType, GzhPlanType } from '@src/views/publishContent/weGZH/hooks/useGzhPlanList';
  10. import http from '@src/http';
  11. import { deleteGzhPlanApi, saveGzhPlanApi } from '@src/http/api';
  12. import PunlishPlanDetailModal from './components/PunlishPlanDetailModal';
  13. const TableHeight = window.innerHeight - 380;
  14. const WeGZHContent: React.FC = () => {
  15. const [planType, setPlanType] = useState<GzhPlanType>(GzhPlanType.自动回复);
  16. // 状态管理
  17. const [selectedAccount, setSelectedAccount] = useState<string>();
  18. const [videoTitle, setVideoTitle] = useState<string>('');
  19. const [selectedPublisher, setSelectedPublisher] = useState<number>();
  20. const [dateRange, setDateRange] = useState<[Dayjs | null, Dayjs | null]>();
  21. const [isShowAddPunlishPlan, setIsShowAddPunlishPlan] = useState<boolean>(false);
  22. const [actionType, setActionType] = useState<'add' | 'edit'>('add');
  23. const [editPlanData, setEditPlanData] = useState<GzhPlanDataType>();
  24. const [selectVideoType, setSelectVideoType] = useState<number>();
  25. const [isSubmiting, setIsSubmiting] = useState<boolean>(false);
  26. const [isLoading, setIsLoading] = useState<boolean>(false);
  27. const { accountOptions, getAccountList } = useAccountOptions();
  28. const { gzhPlanList, getGzhPlanList, totalSize } = useGzhPlanList();
  29. const [pageNum, setPageNum] = useState<number>(1);
  30. const [isShowAddPunlishDetailPlan, setIsShowAddPunlishDetailPlan] = useState<boolean>(false);
  31. // 表格列配置
  32. const columns: TableProps<GzhPlanDataType>['columns'] = [
  33. {
  34. title: '公众号名称',
  35. dataIndex: 'accountName',
  36. key: 'accountName',
  37. width: 160,
  38. },
  39. {
  40. title: '发布方式',
  41. dataIndex: 'publishStage',
  42. key: 'publishStage',
  43. width: 120,
  44. render: (_, record) => {
  45. return record.publishStage === 0 ? '平台发布' : '用户获取路径';
  46. }
  47. },
  48. {
  49. title: '视频选取方式',
  50. dataIndex: 'selectVideoType',
  51. key: 'selectVideoType',
  52. width: 120,
  53. render: (_, record) => {
  54. return record.selectVideoType === 0 ? '手动选取' : '自动选取';
  55. }
  56. },
  57. {
  58. title: '发布场景',
  59. dataIndex: 'scene',
  60. key: 'scene',
  61. width: 120,
  62. render: (_, record) => {
  63. return record.scene === 0 ? '关注回复' : '自动回复';
  64. }
  65. },
  66. {
  67. title: '视频数量',
  68. dataIndex: 'videoCount',
  69. key: 'videoCount',
  70. width: 100,
  71. },
  72. {
  73. title: '视频标题',
  74. dataIndex: 'title',
  75. key: 'title',
  76. ellipsis: true,
  77. render: (_, record) => {
  78. return record?.videoList?.map(video => {
  79. return <Typography.Paragraph style={{ maxWidth: '300px' }} ellipsis={{ rows: 1, tooltip: true }} key={video.videoId}>{video.customTitle || video.title}</Typography.Paragraph>
  80. })
  81. }
  82. },
  83. {
  84. title: '计划创建时间',
  85. dataIndex: 'createTimestamp',
  86. key: 'createTimestamp',
  87. width: 200,
  88. render: (_, record) => {
  89. return record.createTimestamp ? dayjs(record.createTimestamp).format('YYYY-MM-DD HH:mm:ss') : '';
  90. }
  91. },
  92. {
  93. title: '操作',
  94. key: 'action',
  95. fixed: 'right',
  96. render: (_, record) => (
  97. <Space size="middle">
  98. <Button type="link" onClick={() => editPlan(record)}>编辑</Button>
  99. <Button type="link" onClick={() => editPlanDetail(record)}>详情</Button>
  100. <Button type="link" onClick={() => deletePlan(record)}>删除</Button>
  101. </Space>
  102. ),
  103. },
  104. ];
  105. const cloumns2: TableProps<GzhPlanDataType>['columns'] = columns.filter(item => item.title !== '发布场景').map(item => {
  106. if (item.title === '公众号名称') {
  107. return {
  108. ...item,
  109. title: '服务号名称',
  110. }
  111. }
  112. return item;
  113. });
  114. const deletePlan = async (record: GzhPlanDataType) => {
  115. setIsLoading(true);
  116. const res = await http.post(deleteGzhPlanApi, {
  117. id: record.id,
  118. }).catch(err => {
  119. message.error(err?.msg || '删除失败');
  120. })
  121. if (res?.code === 0) {
  122. message.success('删除成功');
  123. getGzhPlanList({
  124. type: planType,
  125. pageNum: 1,
  126. pageSize: 10,
  127. selectVideoType: selectVideoType,
  128. });
  129. } else {
  130. message.error(res?.msg || '删除失败');
  131. }
  132. setIsLoading(false);
  133. }
  134. const editPlan = (record: GzhPlanDataType) => {
  135. setEditPlanData(record);
  136. setActionType('edit');
  137. setIsShowAddPunlishPlan(true);
  138. };
  139. const editPlanDetail = (record: GzhPlanDataType) => {
  140. setEditPlanData(record);
  141. setActionType('edit');
  142. setIsShowAddPunlishDetailPlan(true);
  143. }
  144. const addPunlishPlan = () => {
  145. setActionType('add');
  146. setEditPlanData(undefined);
  147. setIsShowAddPunlishPlan(true);
  148. }
  149. const handleAddPunlishPlan = async (params: GzhPlanDataType) => {
  150. setIsSubmiting(true);
  151. if (params.type !== planType) {
  152. setPlanType(params.type as GzhPlanType);
  153. }
  154. const res = await http.post<GzhPlanDataType>(saveGzhPlanApi, {...params, type: +params.type})
  155. .catch(err => {
  156. message.error(err.msg);
  157. })
  158. .finally(() => {
  159. setIsSubmiting(false);
  160. });
  161. if (res?.code === 0) {
  162. message.success('发布计划创建成功');
  163. getGzhPlanList({
  164. type: params.type,
  165. pageNum: 1,
  166. pageSize: 10,
  167. });
  168. setIsShowAddPunlishPlan(false);
  169. } else {
  170. message.error(res?.msg);
  171. }
  172. }
  173. useEffect(() => {
  174. setSelectedAccount(undefined);
  175. setVideoTitle('');
  176. setSelectedPublisher(undefined);
  177. setSelectVideoType(undefined);
  178. setDateRange(undefined);
  179. setPageNum(1);
  180. getAccountList({ accountType: planType });
  181. getGzhPlanList({
  182. type: planType,
  183. pageNum: 1,
  184. pageSize: 10,
  185. });
  186. }, [planType]);
  187. const handleSearch = () => {
  188. getGzhPlanList({
  189. pageNum: 1,
  190. pageSize: 10,
  191. title: videoTitle,
  192. type: planType,
  193. accountId: selectedAccount ? parseInt(selectedAccount) : undefined,
  194. publishStage: selectedPublisher,
  195. selectVideoType: selectVideoType,
  196. createTimestampStart: dateRange?.[0]?.unix() ? dateRange[0].unix() * 1000 : undefined,
  197. createTimestampEnd: dateRange?.[1]?.unix() ? dateRange[1].unix() * 1000 : undefined,
  198. });
  199. }
  200. return (
  201. <Spin spinning={isLoading || isSubmiting}>
  202. <div className="rounded-lg">
  203. <div className="text-lg font-medium mb-3">公众号内容</div>
  204. {/* 搜索区域 */}
  205. <div className="flex flex-wrap gap-4 mb-3">
  206. <div className="flex items-center gap-2">
  207. <span className="text-gray-600">{ planType === GzhPlanType.自动回复 ? '公众号名称' : '服务号名称' }:</span>
  208. <Select
  209. placeholder={planType === GzhPlanType.自动回复 ? '选择公众号' : '选择服务号'}
  210. style={{ width: 150 }}
  211. value={selectedAccount}
  212. onChange={setSelectedAccount}
  213. allowClear
  214. showSearch
  215. filterOption={(input, option) =>
  216. (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
  217. }
  218. options={accountOptions?.map(item => ({ label: item.name, value: item.id })) || []}
  219. />
  220. </div>
  221. <div className="flex items-center gap-2">
  222. <span className="text-gray-600">视频标题:</span>
  223. <Input
  224. placeholder="搜索视频标题"
  225. style={{ width: 200 }}
  226. value={videoTitle}
  227. allowClear
  228. onPressEnter={handleSearch}
  229. onChange={e => setVideoTitle(e.target.value)}
  230. />
  231. </div>
  232. <div className="flex items-center gap-2">
  233. <span className="text-gray-600">发布方式:</span>
  234. <Select
  235. placeholder="选择发布方式"
  236. style={{ width: 140 }}
  237. value={selectedPublisher}
  238. onChange={setSelectedPublisher}
  239. allowClear
  240. options={[
  241. { label: '平台发布', value: 0 },
  242. { label: '用户获取路径', value: 1 },
  243. ]}
  244. />
  245. </div>
  246. <div className="flex items-center gap-2">
  247. <span className="text-gray-600">视频选取方式:</span>
  248. <Select
  249. placeholder="视频选取方式"
  250. style={{ width: 130 }}
  251. value={selectVideoType}
  252. onChange={setSelectVideoType}
  253. allowClear
  254. options={[
  255. { label: '手动选取', value: 0 },
  256. { label: '自动选取', value: 1 },
  257. ]}
  258. />
  259. </div>
  260. <div className="flex items-center gap-2">
  261. <RangePicker
  262. placeholder={['开始时间', '结束时间']}
  263. style={{ width: 270 }}
  264. allowClear
  265. value={dateRange}
  266. onChange={(dates) => {
  267. setDateRange(dates || undefined);
  268. }}
  269. />
  270. </div>
  271. <Button type="primary" className="ml-2" onClick={ handleSearch}>搜索</Button>
  272. </div>
  273. <Tabs
  274. defaultActiveKey={ GzhPlanType.自动回复}
  275. type="card"
  276. size="large"
  277. className={styles.antTableTab}
  278. items={[
  279. { label: '自动回复', key: GzhPlanType.自动回复 },
  280. { label: '服务号推送', key: GzhPlanType.服务号推送 },
  281. ]}
  282. activeKey={planType}
  283. onChange={(key) => setPlanType(key as GzhPlanType)}
  284. tabBarExtraContent={
  285. { right: <Button type="primary" onClick={addPunlishPlan}>+ 创建发布</Button> }}
  286. />
  287. {/* 表格区域 */}
  288. <Table
  289. rowKey={(record) => record.id}
  290. className={styles.antTable}
  291. columns={planType === GzhPlanType.自动回复 ? columns : cloumns2}
  292. dataSource={gzhPlanList}
  293. scroll={{ x: 'max-content', y: TableHeight }}
  294. pagination={{
  295. total: totalSize,
  296. pageSize: 10,
  297. current: pageNum,
  298. showTotal: (total) => `共 ${total} 条`,
  299. onChange: (page) => getGzhPlanList({
  300. pageNum: page,
  301. pageSize: 10,
  302. title: videoTitle,
  303. type: planType,
  304. selectVideoType: selectVideoType,
  305. accountId: selectedAccount ? parseInt(selectedAccount) : undefined,
  306. publishStage: selectedPublisher,
  307. createTimestampStart: dateRange?.[0]?.unix() ? dateRange[0].unix() * 1000 : undefined,
  308. createTimestampEnd: dateRange?.[1]?.unix() ? dateRange[1].unix() * 1000 : undefined,
  309. }),
  310. }}
  311. />
  312. <PunlishPlanModal
  313. visible={isShowAddPunlishPlan}
  314. onCancel={() => {
  315. setEditPlanData(undefined);
  316. setIsShowAddPunlishPlan(false);
  317. }}
  318. onOk={handleAddPunlishPlan}
  319. actionType={actionType}
  320. editPlanData={editPlanData}
  321. isSubmiting={isSubmiting}
  322. planType={ planType}
  323. />
  324. <PunlishPlanDetailModal
  325. visible={isShowAddPunlishDetailPlan}
  326. onCancel={() => {
  327. setEditPlanData(undefined);
  328. setIsShowAddPunlishDetailPlan(false);
  329. }}
  330. planData={editPlanData as GzhPlanDataType}
  331. />
  332. </div>
  333. </Spin>
  334. );
  335. };
  336. export default WeGZHContent;