index.tsx 8.0 KB

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