index.tsx 11 KB

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