index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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 !== '发布场景');
  106. const deletePlan = async (record: GzhPlanDataType) => {
  107. setIsLoading(true);
  108. const res = await http.post(deleteGzhPlanApi, {
  109. id: record.id,
  110. }).catch(err => {
  111. message.error(err?.msg || '删除失败');
  112. })
  113. if (res?.code === 0) {
  114. message.success('删除成功');
  115. getGzhPlanList({
  116. type: planType,
  117. pageNum: 1,
  118. pageSize: 10,
  119. selectVideoType: selectVideoType,
  120. });
  121. } else {
  122. message.error(res?.msg || '删除失败');
  123. }
  124. setIsLoading(false);
  125. }
  126. const editPlan = (record: GzhPlanDataType) => {
  127. setEditPlanData(record);
  128. setActionType('edit');
  129. setIsShowAddPunlishPlan(true);
  130. };
  131. const editPlanDetail = (record: GzhPlanDataType) => {
  132. setEditPlanData(record);
  133. setActionType('edit');
  134. setIsShowAddPunlishDetailPlan(true);
  135. }
  136. const addPunlishPlan = () => {
  137. setActionType('add');
  138. setEditPlanData(undefined);
  139. setIsShowAddPunlishPlan(true);
  140. }
  141. const handleAddPunlishPlan = async (params: GzhPlanDataType) => {
  142. setIsSubmiting(true);
  143. if (params.type !== planType) {
  144. setPlanType(params.type as GzhPlanType);
  145. }
  146. const res = await http.post<GzhPlanDataType>(saveGzhPlanApi, params)
  147. .catch(err => {
  148. message.error(err.msg);
  149. })
  150. .finally(() => {
  151. setIsSubmiting(false);
  152. });
  153. if (res?.code === 0) {
  154. message.success('发布计划创建成功');
  155. getGzhPlanList({
  156. type: params.type,
  157. pageNum: 1,
  158. pageSize: 10,
  159. });
  160. setIsShowAddPunlishPlan(false);
  161. } else {
  162. message.error(res?.msg);
  163. }
  164. }
  165. useEffect(() => {
  166. setSelectedAccount(undefined);
  167. setVideoTitle('');
  168. setSelectedPublisher(undefined);
  169. setSelectVideoType(undefined);
  170. setDateRange(undefined);
  171. setPageNum(1);
  172. getAccountList({ accountType: planType });
  173. getGzhPlanList({
  174. type: planType,
  175. pageNum: 1,
  176. pageSize: 10,
  177. });
  178. }, [planType]);
  179. const handleSearch = () => {
  180. getGzhPlanList({
  181. pageNum: 1,
  182. pageSize: 10,
  183. title: videoTitle,
  184. type: planType,
  185. accountId: selectedAccount ? parseInt(selectedAccount) : undefined,
  186. publishStage: selectedPublisher,
  187. selectVideoType: selectVideoType,
  188. createTimestampStart: dateRange?.[0]?.unix() ? dateRange[0].unix() * 1000 : undefined,
  189. createTimestampEnd: dateRange?.[1]?.unix() ? dateRange[1].unix() * 1000 : undefined,
  190. });
  191. }
  192. return (
  193. <Spin spinning={isLoading || isSubmiting}>
  194. <div className="rounded-lg">
  195. <div className="text-lg font-medium mb-3">公众号内容</div>
  196. {/* 搜索区域 */}
  197. <div className="flex flex-wrap gap-4 mb-3">
  198. <div className="flex items-center gap-2">
  199. <span className="text-gray-600">{ planType === GzhPlanType.自动回复 ? '公众号名称' : '服务号名称' }:</span>
  200. <Select
  201. placeholder={planType === GzhPlanType.自动回复 ? '选择公众号' : '选择服务号'}
  202. style={{ width: 150 }}
  203. value={selectedAccount}
  204. onChange={setSelectedAccount}
  205. allowClear
  206. showSearch
  207. filterOption={(input, option) =>
  208. (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
  209. }
  210. options={accountOptions?.map(item => ({ label: item.name, value: item.id })) || []}
  211. />
  212. </div>
  213. <div className="flex items-center gap-2">
  214. <span className="text-gray-600">视频标题:</span>
  215. <Input
  216. placeholder="搜索视频标题"
  217. style={{ width: 200 }}
  218. value={videoTitle}
  219. allowClear
  220. onPressEnter={handleSearch}
  221. onChange={e => setVideoTitle(e.target.value)}
  222. />
  223. </div>
  224. <div className="flex items-center gap-2">
  225. <span className="text-gray-600">发布方式:</span>
  226. <Select
  227. placeholder="选择发布方式"
  228. style={{ width: 140 }}
  229. value={selectedPublisher}
  230. onChange={setSelectedPublisher}
  231. allowClear
  232. options={[
  233. { label: '平台发布', value: 0 },
  234. { label: '用户获取路径', value: 1 },
  235. ]}
  236. />
  237. </div>
  238. <div className="flex items-center gap-2">
  239. <span className="text-gray-600">视频选取方式:</span>
  240. <Select
  241. placeholder="视频选取方式"
  242. style={{ width: 130 }}
  243. value={selectVideoType}
  244. onChange={setSelectVideoType}
  245. allowClear
  246. options={[
  247. { label: '手动选取', value: 0 },
  248. { label: '自动选取', value: 1 },
  249. ]}
  250. />
  251. </div>
  252. <div className="flex items-center gap-2">
  253. <RangePicker
  254. placeholder={['开始时间', '结束时间']}
  255. style={{ width: 270 }}
  256. allowClear
  257. value={dateRange}
  258. onChange={(dates) => {
  259. setDateRange(dates || undefined);
  260. }}
  261. />
  262. </div>
  263. <Button type="primary" className="ml-2" onClick={ handleSearch}>搜索</Button>
  264. </div>
  265. <Tabs
  266. defaultActiveKey={ GzhPlanType.自动回复}
  267. type="card"
  268. size="large"
  269. className={styles.antTableTab}
  270. items={[
  271. { label: '自动回复', key: GzhPlanType.自动回复 },
  272. { label: '服务号推送', key: GzhPlanType.服务号推送 },
  273. ]}
  274. activeKey={planType}
  275. onChange={(key) => setPlanType(key as GzhPlanType)}
  276. tabBarExtraContent={
  277. { right: <Button type="primary" onClick={addPunlishPlan}>+ 创建发布</Button> }}
  278. />
  279. {/* 表格区域 */}
  280. <Table
  281. rowKey={(record) => record.id}
  282. className={styles.antTable}
  283. columns={planType === GzhPlanType.自动回复 ? columns : cloumns2}
  284. dataSource={gzhPlanList}
  285. scroll={{ x: 'max-content', y: TableHeight }}
  286. pagination={{
  287. total: totalSize,
  288. pageSize: 10,
  289. current: pageNum,
  290. showTotal: (total) => `共 ${total} 条`,
  291. onChange: (page) => getGzhPlanList({
  292. pageNum: page,
  293. pageSize: 10,
  294. title: videoTitle,
  295. type: planType,
  296. selectVideoType: selectVideoType,
  297. accountId: selectedAccount ? parseInt(selectedAccount) : undefined,
  298. publishStage: selectedPublisher,
  299. createTimestampStart: dateRange?.[0]?.unix() ? dateRange[0].unix() * 1000 : undefined,
  300. createTimestampEnd: dateRange?.[1]?.unix() ? dateRange[1].unix() * 1000 : undefined,
  301. }),
  302. }}
  303. />
  304. <PunlishPlanModal
  305. visible={isShowAddPunlishPlan}
  306. onCancel={() => {
  307. setEditPlanData(undefined);
  308. setIsShowAddPunlishPlan(false);
  309. }}
  310. onOk={handleAddPunlishPlan}
  311. actionType={actionType}
  312. editPlanData={editPlanData}
  313. isSubmiting={isSubmiting}
  314. planType={ planType}
  315. />
  316. <PunlishPlanDetailModal
  317. visible={isShowAddPunlishDetailPlan}
  318. onCancel={() => {
  319. setEditPlanData(undefined);
  320. setIsShowAddPunlishDetailPlan(false);
  321. }}
  322. planData={editPlanData as GzhPlanDataType}
  323. />
  324. </div>
  325. </Spin>
  326. );
  327. };
  328. export default WeGZHContent;