Ver Fonte

小程序投流 恢复备注 + Excel 导出:
1) 备注:publishPlanModal 加回 Input.TextArea(可选, maxLength 200), 列表加「备注」列(width 180, ellipsis), 提交 payload 透传 remark;后端表/PO/Mapper 一直保留, 仅前端在 4147437 误删, 现一并恢复
2) 导出:工具栏加「已选视频导出」按钮(DownloadOutlined), 弹出 Modal 选日期范围(不选默认当天), 沿用顶部 audiencePackage/title 过滤, POST /contentPlatform/plan/xcx/export 返 CDN URL 后 a 标签触发下载;权限沿用页面级 requireType=[2,3], 与创建发布一致
3) 时间字段用 ms 时间戳 (.startOf('day').valueOf()) 与 xcx 列表保持一致, 不沿用 qw 的日期字符串

刘立冬 há 3 dias atrás
pai
commit
0ee1a45b98

+ 1 - 0
src/http/api.ts

@@ -45,6 +45,7 @@ export const getXcxPlanListApi = `${import.meta.env.VITE_API_URL}/contentPlatfor
 export const saveXcxPlanApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/xcx/save`
 export const deleteXcxPlanApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/xcx/delete`
 export const xcxPlanMultiLinkApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/xcx/multiLink`
+export const xcxPlanExportApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/xcx/export`
 export const getXcxSharePicApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/xcx/getSharePic`
 export const getXcxAudiencePackageListApi = `${import.meta.env.VITE_API_URL}/contentPlatform/plan/xcx/getAudiencePackageList`
 

+ 10 - 1
src/views/publishContent/xcxTouliu/components/publishPlanModal/index.tsx

@@ -1,5 +1,5 @@
 import React, { useEffect, useState } from 'react';
-import { Modal, Form, Select, Button, Card, Typography, message } from 'antd';
+import { Modal, Form, Input, Select, Button, Card, Typography, message } from 'antd';
 import { CloseOutlined, PlusOutlined, EditOutlined, CaretRightFilled } from '@ant-design/icons';
 import VideoSelectModal from '@src/views/publishContent/weGZH/components/videoSelectModal';
 import EditTitleCoverModal from '@src/views/publishContent/weGZH/components/editTitleCoverModal';
@@ -140,6 +140,15 @@ const XcxPublishPlanModal: React.FC<XcxPublishPlanModalProps> = ({
 							onChange={() => setSelectedVideos([])}
 						/>
 					</Form.Item>
+					<Form.Item
+						name="remark"
+						label="备注"
+						labelCol={{ span: 4 }}
+						labelAlign="left"
+						layout="horizontal"
+					>
+						<Input.TextArea placeholder="可选,内部备注" className="!w-80" rows={2} maxLength={200} />
+					</Form.Item>
 					<Form.Item label="发布内容" required>
 						<div className="flex flex-wrap gap-4 max-h-[660px] overflow-y-auto pr-2">
 							{selectedVideos.map((video) => (

+ 66 - 1
src/views/publishContent/xcxTouliu/index.tsx

@@ -1,5 +1,6 @@
 import React, { useEffect, useMemo, useState } from 'react';
 import { Space, Table, Button, Input, Select, DatePicker, message, Typography, Spin, Popconfirm, Modal, InputNumber } from 'antd';
+import { DownloadOutlined } from '@ant-design/icons';
 import type { TableProps } from 'antd';
 import dayjs, { Dayjs } from 'dayjs';
 import copy from 'copy-to-clipboard';
@@ -12,7 +13,7 @@ import { useXcxPlanList, XcxPlanDataType } from './hooks/useXcxPlanList';
 import { useAudiencePackageOptions } from './hooks/useAudiencePackageOptions';
 import { VideoItem } from '@src/views/publishContent/weGZH/components/types';
 import http from '@src/http';
-import { deleteXcxPlanApi, getShareQrLink, saveXcxPlanApi, xcxPlanMultiLinkApi } from '@src/http/api';
+import { deleteXcxPlanApi, getShareQrLink, saveXcxPlanApi, xcxPlanExportApi, xcxPlanMultiLinkApi } from '@src/http/api';
 import { getUserInfo } from '@src/http/sso';
 
 const { RangePicker } = DatePicker;
@@ -31,6 +32,9 @@ const XcxTouliuContent: React.FC = () => {
 	const [multiLinkPlan, setMultiLinkPlan] = useState<XcxPlanDataType | null>(null);
 	const [multiLinkCount, setMultiLinkCount] = useState<number>(10);
 	const [multiLinkLoading, setMultiLinkLoading] = useState<boolean>(false);
+	const [isExportModalVisible, setIsExportModalVisible] = useState<boolean>(false);
+	const [exportDateRange, setExportDateRange] = useState<[Dayjs, Dayjs] | undefined>();
+	const [isExporting, setIsExporting] = useState<boolean>(false);
 	const { xcxPlanList, getXcxPlanList, totalSize } = useXcxPlanList();
 	const { options: audiencePackageOptions } = useAudiencePackageOptions();
 	const userType = useMemo(() => getUserInfo()?.type, []);
@@ -67,6 +71,13 @@ const XcxTouliuContent: React.FC = () => {
 					<img src={record.cover} referrerPolicy="no-referrer" className="w-[80px] h-auto" />
 				) : '-',
 		},
+		{
+			title: '备注',
+			dataIndex: 'remark',
+			key: 'remark',
+			width: 180,
+			ellipsis: true,
+		},
 		{
 			title: '计划创建时间',
 			dataIndex: 'createTimestamp',
@@ -203,12 +214,42 @@ const XcxTouliuContent: React.FC = () => {
 		setIsShowAddPunlishPlan(true);
 	};
 
+	const handleExport = () => {
+		setIsExporting(true);
+		http.post<string>(xcxPlanExportApi, {
+			audiencePackage: audiencePackage || undefined,
+			title: videoTitle || undefined,
+			createTimestampStart: exportDateRange?.[0] ? exportDateRange[0].startOf('day').valueOf() : undefined,
+			createTimestampEnd: exportDateRange?.[1] ? exportDateRange[1].add(1, 'day').startOf('day').valueOf() : undefined,
+		}).then(res => {
+			if (res.code === 0 && res.data) {
+				const a = document.createElement('a');
+				a.href = res.data;
+				a.target = '_blank';
+				a.rel = 'noopener noreferrer';
+				document.body.appendChild(a);
+				a.click();
+				document.body.removeChild(a);
+				message.success('已开始下载');
+				setIsExportModalVisible(false);
+				setExportDateRange(undefined);
+			} else {
+				message.error(res.msg || '导出失败');
+			}
+		}).catch(err => {
+			message.error(err?.msg || '导出失败');
+		}).finally(() => {
+			setIsExporting(false);
+		});
+	};
+
 	const handleAddPunlishPlan = async (
 		params: XcxPlanDataType & { videoList: VideoItem[] }
 	) => {
 		setIsSubmiting(true);
 		const payload = {
 			audiencePackage: params.audiencePackage,
+			remark: params.remark,
 			videoList: (params.videoList || []).map((v) => ({
 				videoId: v.videoId,
 				title: v.customTitle || v.title,
@@ -302,6 +343,7 @@ const XcxTouliuContent: React.FC = () => {
 
 					<Button type="primary" className="ml-2" onClick={handleSearch}>搜索</Button>
 					<Button type="primary" onClick={addPunlishPlan}>+ 创建发布</Button>
+					<Button type="primary" icon={<DownloadOutlined />} onClick={() => setIsExportModalVisible(true)}>已选视频导出</Button>
 				</div>
 
 				<Table
@@ -376,6 +418,29 @@ const XcxTouliuContent: React.FC = () => {
 						</div>
 					)}
 				</Modal>
+
+				<Modal
+					title="已选视频导出"
+					open={isExportModalVisible}
+					onCancel={() => setIsExportModalVisible(false)}
+					onOk={handleExport}
+					confirmLoading={isExporting}
+					okText="确认导出"
+					cancelText="取消"
+				>
+					<div className="flex items-center gap-2 my-4">
+						<span className="text-gray-600">日期范围:</span>
+						<RangePicker
+							placeholder={['开始日期', '结束日期']}
+							value={exportDateRange}
+							onChange={(dates) => setExportDateRange(dates as [Dayjs, Dayjs] | undefined)}
+							allowClear
+						/>
+					</div>
+					<div className="text-gray-400 text-sm">
+						不选日期默认导出当天数据;最多导出 2000 条;会沿用当前页的「人群包 / 视频标题」筛选条件。
+					</div>
+				</Modal>
 			</div>
 		</Spin>
 	);