diff --git a/video-gen-app/src/App.tsx b/video-gen-app/src/App.tsx index 6c62ed35..fdc6133a 100644 --- a/video-gen-app/src/App.tsx +++ b/video-gen-app/src/App.tsx @@ -17,19 +17,14 @@ import InitialReplication from './pages/InitialReplication'; import InitialInfo from './pages/InitialInfo'; import RemoveLens from './pages/RemoveLens'; import GeneratedRecord from './pages/GeneratedRecord'; +import PreTest from './pages/PreTest'; import AuthorizationPage from './pages/AuthorizationPage'; import RemoveInfo from './pages/RemoveInfo'; import ConsumePage from './pages/ConsumePage'; - - - - - import { useAuthStore } from './store/useAuthStore'; const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { const { user, loading, checkAuth } = useAuthStore(); - useEffect(() => { const token = localStorage.getItem('auth_token'); if (!token && !loading && !user) { @@ -108,6 +103,7 @@ const App = () => { } /> } /> } /> + } /> } /> } /> diff --git a/video-gen-app/src/api/index.ts b/video-gen-app/src/api/index.ts index 96f28bcc..598e53b4 100644 --- a/video-gen-app/src/api/index.ts +++ b/video-gen-app/src/api/index.ts @@ -392,3 +392,93 @@ export async function getthree(projectId: string, stepId: string ,params: any): export async function getfour(projectId: string, stepId: string ,params: any): Promise { return api.post(`/hot-opening-replications/tasks/${projectId}/steps/${stepId}/generate-video`, params); } + +// 获取授权链接 +export interface RequestOAuthParams { + open_type: number; +} +export async function requestOAuth(params: RequestOAuthParams): Promise { + return api.post(`/user-oauth/request_oauth`, params); +} + +// 巨量授权回调 +export interface JuliangCallbackParams { + auth_code: string; + state: string; +} +export async function juliang_callback(params: JuliangCallbackParams): Promise { + const query = new URLSearchParams(); + query.set('auth_code', params.auth_code); + query.set('state', params.state); + return api.get(`/user-oauth/juliang_callback?${query.toString()}`); +} + +// 授权列表 +export interface OAuthListParams { + account_userid?: string; + open_type?: number; + account_id?: string; + page?: number; + page_size?: number; +} +export async function getOAuthList(params: OAuthListParams): Promise { + const query = new URLSearchParams(); + if (params.account_userid) query.set('account_userid', params.account_userid); + if (params.open_type !== undefined) query.set('open_type', String(params.open_type)); + if (params.account_id) query.set('account_id', params.account_id); + if (params.page !== undefined) query.set('page', String(params.page)); + if (params.page_size !== undefined) query.set('page_size', String(params.page_size)); + return api.get(`/user-oauth/oauth_list?${query.toString()}`); +} + +// 前测模板列表 +// /api/pre-test-template/list +export interface PreTestListParams { + page?: number; + pageSize?: number; +} + +export async function getPreTestList(params?: PreTestListParams): Promise { + if (USE_MOCK) return mock.mockGetPreTestList(params); + const page = params?.page || 1; + const pageSize = Math.min(params?.pageSize || 10, 100); + return api.get(`/pre-test-template/list?page=${page}&page_size=${pageSize}`); +} + +// 获取前测字段列表 +// /api/pre-test-template/fields +export async function getPreTestFields(): Promise { + if (USE_MOCK) return mock.mockGetPreTestFields(); + return api.get(`/pre-test-template/fields`); +} + +// 创建前测模板 +// /api/pre-test-template/create +export async function createPreTest(params: any): Promise { + if (USE_MOCK) return mock.mockCreatePreTest(params); + return api.post(`/pre-test-template/create`, params); +} + +// 前测模板详情 +// /api/pre-test-template/select/{template_id} +export async function getPreTestDetail(templateId: string): Promise { + return api.get(`/pre-test-template/select/${templateId}`); +} + +// 更新前测模板 +// /api/pre-test-template/update/{template_id} +export async function updatePreTest(templateId: string, params: any): Promise { + return api.post(`/pre-test-template/update/${templateId}`, params); +} + +// 删除前测模板 +// /api/pre-test-template/delete/{template_id} +export async function deletePreTest(templateId: string): Promise { + return api.get(`/pre-test-template/delete/${templateId}`); +} + +// 获取默认前测模板 +// /api/pre-test-template/default +export async function getDefaultPreTest(): Promise { + return api.get(`/pre-test-template/default`); +} \ No newline at end of file diff --git a/video-gen-app/src/api/mock.ts b/video-gen-app/src/api/mock.ts index 333dbcf5..bea7a751 100644 --- a/video-gen-app/src/api/mock.ts +++ b/video-gen-app/src/api/mock.ts @@ -383,3 +383,63 @@ export async function mockCreateGenerationTask(params: any): Promise { ...params, }; } + +// ── PreTest Mock ──────────────────────────────────────────── + +const MOCK_PRETEST_FIELDS = [ + { id: 'title', name: 'title', label: '标题', description: '视频标题字段' }, + { id: 'description', name: 'description', label: '描述', description: '视频描述字段' }, + { id: 'image', name: 'image', label: '图片', description: '视频封面或配图' }, + { id: 'content', name: 'content', label: '内容', description: '视频正文内容' }, + { id: 'date', name: 'date', label: '日期', description: '活动或发布日期' }, + { id: 'step', name: 'step', label: '步骤', description: '操作步骤说明' }, + { id: 'duration', name: 'duration', label: '时长', description: '视频时长' }, + { id: 'brand', name: 'brand', label: '品牌', description: '品牌名称' }, + { id: 'slogan', name: 'slogan', label: '标语', description: '品牌宣传语' }, + { id: 'name', name: 'name', label: '姓名', description: '人物姓名' }, + { id: 'story', name: 'story', label: '故事', description: '故事内容' }, +]; + +const MOCK_PRETEST_RECORDS = [ + { id: 'PT001', authorizationId: '1867060028363785', templateName: '产品介绍视频', selectedFields: ['title', 'description', 'image'], status: 'completed', createdAt: '2024-01-15 10:30:00' }, + { id: 'PT002', authorizationId: '1867060028363785', templateName: '活动宣传视频', selectedFields: ['title', 'content', 'date'], status: 'pending', createdAt: '2024-01-15 11:20:00' }, + { id: 'PT003', authorizationId: '1867059808785418', templateName: '教程演示视频', selectedFields: ['step', 'duration', 'image'], status: 'completed', createdAt: '2024-01-14 14:45:00' }, + { id: 'PT004', authorizationId: '1867060028363785', templateName: '品牌推广视频', selectedFields: ['brand', 'slogan'], status: 'testing', createdAt: '2024-01-14 09:15:00' }, + { id: 'PT005', authorizationId: '1867059757929740', templateName: '用户案例视频', selectedFields: ['name', 'story', 'image'], status: 'completed', createdAt: '2024-01-13 16:00:00' }, +]; + +export async function mockGetPreTestFields(): Promise { + await delay(300); + return [...MOCK_PRETEST_FIELDS]; +} + +export async function mockGetPreTestList(params?: { page?: number; pageSize?: number }): Promise { + await delay(400); + const page = params?.page || 1; + const pageSize = Math.min(params?.pageSize || 10, 100); + + const start = (page - 1) * pageSize; + const end = start + pageSize; + const items = MOCK_PRETEST_RECORDS.slice(start, end); + + return { + page, + pageSize, + total: MOCK_PRETEST_RECORDS.length, + items, + }; +} + +export async function mockCreatePreTest(params: any): Promise { + await delay(500); + const newRecord = { + id: `PT-${Date.now()}`, + authorizationId: '', + templateName: params.templateName, + selectedFields: params.selectedFields, + status: params.status || 'pending', + createdAt: new Date().toLocaleString('zh-CN'), + }; + MOCK_PRETEST_RECORDS.unshift(newRecord); + return newRecord; +} diff --git a/video-gen-app/src/pages/AuthorizationPage.tsx b/video-gen-app/src/pages/AuthorizationPage.tsx index 1e3228fc..73a957bb 100644 --- a/video-gen-app/src/pages/AuthorizationPage.tsx +++ b/video-gen-app/src/pages/AuthorizationPage.tsx @@ -1,16 +1,30 @@ import React, { useEffect, useState } from 'react'; -import { Button, Table, Tag, message } from 'antd'; +import { Button, Table, Tag, Modal, Select, App, Input } from 'antd'; import { PlusOutlined, CheckCircleOutlined, ClockCircleOutlined, CiCircleOutlined } from '@ant-design/icons'; -import { useNavigate } from 'react-router-dom'; +import { getOAuthList, juliang_callback, requestOAuth } from '../api'; + +const OPEN_TYPE_MAP: Record = { + 1: '千川', + 2: '广告', + 3: '本地推', + 4: '星图', + 5: '快手代理商', + 6: '巨量星图', + 7: '巨量服务单', + 8: '腾讯服务单', + 9: '腾讯营销K2', + 10: '腾讯营销K3', +}; -// 授权数据类型 interface AuthorizationData { id: string; status: string; description: string; + account_userid?: string; + open_type?: number; + account_id?: string; } -// 状态配置 const statusConfig = { active: { label: '已授权', color: 'green', icon: CheckCircleOutlined }, pending: { label: '待授权', color: 'gold', icon: ClockCircleOutlined }, @@ -18,62 +32,72 @@ const statusConfig = { revoked: { label: '已撤销', color: 'gray', icon: CiCircleOutlined }, }; -interface ApiResponse { - code: number; - message: string; - data: AuthorizationData[]; -} - -// 模拟授权列表接口 -const mockApiResponse: ApiResponse = { - code: 200, - message: 'success', - data: [ - { id: '1867060028363785', status: 'active', description: '用户张三的API授权' }, - { id: '1867059757929740', status: 'pending', description: '用户李四的API授权' }, - { id: '1867059808785418', status: 'active', description: '用户王五的API授权' }, - { id: '1867060028363786', status: 'expired', description: '用户赵六的API授权' }, - { id: '1867060028363787', status: 'revoked', description: '用户钱七的API授权' }, - ], -}; - -// 模拟调用接口 /api/admin/user-oauth-apps/list -const fetchAuthorizationList = async (): Promise => { - return new Promise((resolve) => { - setTimeout(() => { - resolve(mockApiResponse); - }, 800); - }); -}; - const AuthorizationPage: React.FC = () => { - const navigate = useNavigate(); + const { message } = App.useApp(); const [authorizations, setAuthorizations] = useState([]); - const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [loading, setLoading] = useState(false); const [listLoading, setListLoading] = useState(false); + const [showModal, setShowModal] = useState(false); + const [selectedOpenType, setSelectedOpenType] = useState(undefined); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [total, setTotal] = useState(0); + const [searchParams, setSearchParams] = useState({ + account_userid: '', + open_type: undefined as number | undefined, + account_id: '', + }); - // 页面初始化时获取授权列表 useEffect(() => { - const loadData = async () => { - setListLoading(true); - try { - const response = await fetchAuthorizationList(); - if (response.code === 200) { - setAuthorizations(response.data); - } else { - message.error(response.message); - } - } catch (error) { - message.error('获取授权列表失败'); - } finally { - setListLoading(false); - } - }; - loadData(); + loadOAuthList(); + handleCallback(); }, []); - // 状态标签渲染 + const loadOAuthList = async (page = 1, pageSize = 10, params = searchParams) => { + setListLoading(true); + try { + const response = await getOAuthList({ + page, + page_size: pageSize, + account_userid: params.account_userid || undefined, + open_type: params.open_type, + account_id: params.account_id || undefined, + }); + if (response) { + if (response.data) { + setAuthorizations(response.data.data || response.data); + } + if (response.pagination) { + setTotal(response.pagination.total || 0); + setCurrentPage(response.pagination.page || 1); + setPageSize(response.pagination.pageSize || 10); + } + } else { + setAuthorizations(response || []); + } + } catch (error) { + message.error('获取授权列表失败'); + } finally { + setListLoading(false); + } + }; + + const handleCallback = async () => { + const params = new URLSearchParams(window.location.search); + const authCode = params.get('auth_code'); + const state = params.get('state'); + if (authCode && state) { + try { + await juliang_callback({ auth_code: authCode, state }); + message.success('授权成功'); + window.history.replaceState({}, document.title, window.location.pathname); + loadOAuthList(); + } catch (error) { + message.error('授权回调失败'); + } + } + }; + const renderStatus = (status: string) => { const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.expired; const Icon = config.icon; @@ -85,32 +109,74 @@ const AuthorizationPage: React.FC = () => { ); }; - // 跳转到消耗记录页面 - const handleGoToConsume = () => { - navigate('/consume'); + const handleAuthorize = () => { + console.log('handleAuthorize'); + setShowModal(true); }; - // 处理点击授权按钮 - const handleAuthorize = () => { - if (selectedRowKeys.length === 0) { - message.warning('请先选择需要授权的记录'); + const handleConfirm = async () => { + if (!selectedOpenType) { + message.warning('请选择开户方式'); return; } setLoading(true); - // 模拟授权操作 - setTimeout(() => { - setAuthorizations(prev => - prev.map(item => - selectedRowKeys.includes(item.id) ? { ...item, status: 'active' } : item - ) - ); - setSelectedRowKeys([]); + try { + const response = await requestOAuth({ open_type: selectedOpenType }); + console.log(response); + if (response.authUrl) { + window.open(response.authUrl, '_blank'); + } else { + message.error('获取授权链接失败'); + } + } catch (error) { + message.error('请求授权失败'); + } finally { setLoading(false); - message.success(`成功授权 ${selectedRowKeys.length} 条记录`); - }, 800); + setShowModal(false); + setSelectedOpenType(undefined); + } }; - // 表格列配置 + // account_id + // : + // "1836691333531724" + // account_name + // : + // "BP-1836691333531724" + // account_role + // : + // "CUSTOMER_ADMIN" + // account_userid + // : + // null + // account_username + // : + // "a***3@minzhong.cn" + // appid + // : + // "1866261617455433" + // created_at + // : + // "2026-06-15T10:00:22.734565Z" + // id + // : + // "0019ecab9b8bc57d964" + // material_auth_status + // : + // true + // open_type + // : + // 2 + // port_type + // : + // 1 + // updated_at + // : + // "2026-06-17T06:41:33.012018Z" + // user_id + // : + // "0019e96d23e57a808e2" + const columns = [ { title: '序号', @@ -128,6 +194,13 @@ const AuthorizationPage: React.FC = () => { {text} ), }, + { + title: '开户方式', + dataIndex: 'open_type', + key: 'open_type', + width: 140, + render: (text: number) => {OPEN_TYPE_MAP[text] || text}, + }, { title: '授权状态', dataIndex: 'status', @@ -136,24 +209,12 @@ const AuthorizationPage: React.FC = () => { render: (text: string) => renderStatus(text), }, { - title: '操作', - dataIndex: 'operation', - key: 'operation', - width: 120, - render: (_: any, record: AuthorizationData) => ( - - 查看消耗 - - ), - } + title: '描述', + dataIndex: 'description', + key: 'description', + ellipsis: true, + render: (text: string) => {text}, + }, ]; const tableData = authorizations.map((item, index) => ({ @@ -163,16 +224,57 @@ const AuthorizationPage: React.FC = () => { })); return ( - - {/* 页面标题 */} + - + 授权管理 - {/* 操作栏 */} - + + + setSearchParams(prev => ({ ...prev, account_userid: e.target.value }))} + style={{ width: 180 }} + onPressEnter={() => { setCurrentPage(1); loadOAuthList(1, pageSize); }} + /> + setSearchParams(prev => ({ ...prev, open_type: value }))} + style={{ width: 140 }} + options={Object.entries(OPEN_TYPE_MAP).map(([key, value]) => ({ + value: Number(key), + label: value, + }))} + /> + setSearchParams(prev => ({ ...prev, account_id: e.target.value }))} + style={{ width: 180 }} + onPressEnter={() => { setCurrentPage(1); loadOAuthList(1, pageSize); }} + /> + { setCurrentPage(1); loadOAuthList(1, pageSize); }} + > + 搜索 + + { + setSearchParams({ account_userid: '', open_type: undefined, account_id: '' }); + setCurrentPage(1); + loadOAuthList(1, pageSize); + }} + > + 重置 + + { - {/* 表格 */} `共 ${total} 条记录`, + onChange: (page, size) => { + setCurrentPage(page); + setPageSize(size); + loadOAuthList(page, size); + }, }} rowKey="id" bordered={false} @@ -206,8 +314,32 @@ const AuthorizationPage: React.FC = () => { scroll={{ x: 'max-content' }} /> + + { + setShowModal(false); + setSelectedOpenType(undefined); + }} + okText="确认授权" + cancelText="取消" + confirmLoading={loading} + > + setSelectedOpenType(value)} + style={{ width: '100%' }} + options={Object.entries(OPEN_TYPE_MAP).map(([key, value]) => ({ + value: Number(key), + label: value, + }))} + /> + ); }; -export default AuthorizationPage; +export default AuthorizationPage; \ No newline at end of file diff --git a/video-gen-app/src/pages/PreTest.tsx b/video-gen-app/src/pages/PreTest.tsx new file mode 100644 index 00000000..c4ec2850 --- /dev/null +++ b/video-gen-app/src/pages/PreTest.tsx @@ -0,0 +1,433 @@ +import React, { useState, useEffect } from 'react'; +import { Table, Tag, Button, Typography, Space, Modal, Form, Input, Select, message } from 'antd'; +import { PlusOutlined, EditOutlined } from '@ant-design/icons'; +import { getPreTestList, createPreTest } from '../api'; + +interface PreTestRecord { + id: string; + authorizationId: string; + templateName: string; + selectedFields: string[]; + status: string; + createdAt: string; +} + +interface PreTestField { + id: string; + name: string; + label: string; + description?: string; +} + +const statusConfig = { + completed: { label: '已完成', color: 'success' }, + pending: { label: '待测试', color: 'default' }, + testing: { label: '测试中', color: 'processing' }, + failed: { label: '测试失败', color: 'error' }, +}; + +const PRETEST_FIELDS: PreTestField[] = [ + { id: 'AD_APP_ACTIVATE', name: 'AD_APP_ACTIVATE', label: 'AD:应用-激活', description: '应用激活行为' }, + { id: 'AD_APP_AUTH', name: 'AD_APP_AUTH', label: 'AD:应用-授信', description: '应用授信行为' }, + { id: 'AD_APP_BOOK', name: 'AD_APP_BOOK', label: 'AD:应用-预约表单', description: '应用预约表单' }, + { id: 'AD_APP_BUY', name: 'AD_APP_BUY', label: 'AD:应用-APP内付费', description: 'APP内付费行为' }, + { id: 'AD_APP_CLICKS', name: 'AD_APP_CLICKS', label: 'AD:应用-点击量', description: '应用点击量' }, + { id: 'AD_APP_DETAIL', name: 'AD_APP_DETAIL', label: 'AD:应用-APP内详情页到站UV', description: 'APP内详情页到站UV' }, + { id: 'AD_APP_DOWNLOADED', name: 'AD_APP_DOWNLOADED', label: 'AD:应用-下载完成', description: '应用下载完成' }, + { id: 'AD_APP_INSTALLED', name: 'AD_APP_INSTALLED', label: 'AD:应用-安装完成', description: '应用安装完成' }, + { id: 'AD_APP_KEY_BEHAVIOR', name: 'AD_APP_KEY_BEHAVIOR', label: 'AD:应用-关键行为', description: '应用关键行为' }, + { id: 'AD_APP_ORDER', name: 'AD_APP_ORDER', label: 'AD:应用-APP内下单', description: 'APP内下单行为' }, + { id: 'AD_APP_PAY', name: 'AD_APP_PAY', label: 'AD:应用-付费', description: '应用付费行为' }, + { id: 'AD_APP_PRE_AUTH', name: 'AD_APP_PRE_AUTH', label: 'AD:应用-预授信', description: '应用预授信行为' }, + { id: 'AD_APP_PRE_DOWNLOAD', name: 'AD_APP_PRE_DOWNLOAD', label: 'AD:应用-预约下载', description: '应用预约下载' }, + { id: 'AD_APP_PUSH_ORDER', name: 'AD_APP_PUSH_ORDER', label: 'AD:应用-首次发单(乘客)', description: '首次发单行为' }, + { id: 'AD_APP_REGISTER', name: 'AD_APP_REGISTER', label: 'AD:应用-注册', description: '应用注册行为' }, + { id: 'AD_APP_SHOW', name: 'AD_APP_SHOW', label: 'AD:应用-展示量', description: '应用展示量' }, + { id: 'AD_APP_SUBMIT', name: 'AD_APP_SUBMIT', label: 'AD:应用-提交认证', description: '应用提交认证' }, + { id: 'AD_APP_VIEW', name: 'AD_APP_VIEW', label: 'AD:应用-APP内访问', description: 'APP内访问行为' }, + { id: 'AD_CLUE_AUTH', name: 'AD_CLUE_AUTH', label: 'AD:销售线索收集-授信', description: '销售线索授信' }, + { id: 'AD_CLUE_BUTTON', name: 'AD_CLUE_BUTTON', label: 'AD:销售线索收集-按钮跳转', description: '按钮跳转' }, + { id: 'AD_CLUE_CLICKS', name: 'AD_CLUE_CLICKS', label: 'AD:销售线索收集-点击量', description: '点击量' }, + { id: 'AD_CLUE_CONFIRM', name: 'AD_CLUE_CONFIRM', label: 'AD:销售线索收集-回访-信息确认', description: '回访信息确认' }, + { id: 'AD_CLUE_CONSULT', name: 'AD_CLUE_CONSULT', label: 'AD:销售线索收集-有效咨询', description: '有效咨询' }, + { id: 'AD_CLUE_CONSULT_MSG', name: 'AD_CLUE_CONSULT_MSG', label: 'AD:销售线索收集-留资咨询', description: '留资咨询' }, + { id: 'AD_CLUE_COUPON', name: 'AD_CLUE_COUPON', label: 'AD:销售线索收集-卡券领取', description: '卡券领取' }, + { id: 'AD_CLUE_CUSTOMER', name: 'AD_CLUE_CUSTOMER', label: 'AD:销售线索收集-有效获客', description: '有效获客' }, + { id: 'AD_CLUE_CVT', name: 'AD_CLUE_CVT', label: 'AD:销售线索收集-多转化', description: '多转化' }, + { id: 'AD_CLUE_DONE', name: 'AD_CLUE_DONE', label: 'AD:销售线索收集-完件', description: '完件' }, + { id: 'AD_CLUE_FORM', name: 'AD_CLUE_FORM', label: 'AD:销售线索收集-表单提交', description: '表单提交' }, + { id: 'AD_CLUE_FRIENDS', name: 'AD_CLUE_FRIENDS', label: 'AD:销售线索收集-回访-加为好友', description: '回访加为好友' }, + { id: 'AD_CLUE_INSURANCE', name: 'AD_CLUE_INSURANCE', label: 'AD:销售线索收集-保险支付', description: '保险支付' }, + { id: 'AD_CLUE_INTENTION', name: 'AD_CLUE_INTENTION', label: 'AD:销售线索收集-存在意向', description: '存在意向' }, + { id: 'AD_CLUE_INTENTION_FORM', name: 'AD_CLUE_INTENTION_FORM', label: 'AD:销售线索收集-意向表单', description: '意向表单' }, + { id: 'AD_CLUE_INTENTION_TEL', name: 'AD_CLUE_INTENTION_TEL', label: 'AD:销售线索收集-意向话单', description: '意向话单' }, + { id: 'AD_CLUE_MESSAGE', name: 'AD_CLUE_MESSAGE', label: 'AD:销售线索收集-私信消息', description: '私信消息' }, + { id: 'AD_CLUE_MONEY', name: 'AD_CLUE_MONEY', label: 'AD:销售线索收集-放款', description: '放款' }, + { id: 'AD_CLUE_MSG', name: 'AD_CLUE_MSG', label: 'AD:销售线索收集-私信留资', description: '私信留资' }, + { id: 'AD_CLUE_PAGE', name: 'AD_CLUE_PAGE', label: 'AD:销售线索收集-访问目标页面', description: '访问目标页面' }, + { id: 'AD_CLUE_PAY', name: 'AD_CLUE_PAY', label: 'AD:销售线索收集-付费', description: '付费' }, + { id: 'AD_CLUE_PRE_AUTH', name: 'AD_CLUE_PRE_AUTH', label: 'AD:销售线索收集-预授信', description: '预授信' }, + { id: 'AD_CLUE_PROTENTIAL_DEAL', name: 'AD_CLUE_PROTENTIAL_DEAL', label: 'AD:销售线索收集-回访-高潜成交', description: '回访高潜成交' }, + { id: 'AD_CLUE_PUSH_ORDER', name: 'AD_CLUE_PUSH_ORDER', label: 'AD:销售线索收集-首次发单(乘客)', description: '首次发单' }, + { id: 'AD_CLUE_REGISTER', name: 'AD_CLUE_REGISTER', label: 'AD:销售线索收集-注册', description: '注册' }, + { id: 'AD_CLUE_SHOW', name: 'AD_CLUE_SHOW', label: 'AD:销售线索收集-展示量', description: '展示量' }, + { id: 'AD_CLUE_SUBMIT', name: 'AD_CLUE_SUBMIT', label: 'AD:销售线索收集-提交认证', description: '提交认证' }, + { id: 'AD_CLUE_TEL', name: 'AD_CLUE_TEL', label: 'AD:销售线索收集-智能电话确认接通', description: '智能电话确认接通' }, + { id: 'AD_CLUE_TEL_CALL', name: 'AD_CLUE_TEL_CALL', label: 'AD:销售线索收集-电话接通', description: '电话接通' }, + { id: 'AD_CLUE_WX_ADD', name: 'AD_CLUE_WX_ADD', label: 'AD:销售线索收集-微信-添加企业微信', description: '添加企业微信' }, + { id: 'AD_CLUE_WX_COPY', name: 'AD_CLUE_WX_COPY', label: 'AD:销售线索收集-微信复制', description: '微信复制' }, + { id: 'AD_CLUE_WX_MSG', name: 'AD_CLUE_WX_MSG', label: 'AD:销售线索收集-微信-用户首次消息', description: '用户首次消息' }, + { id: 'AD_ECP_APP_BUY', name: 'AD_ECP_APP_BUY', label: 'AD:电商-app内下单', description: '电商app内下单' }, + { id: 'AD_ECP_APP_DETAIL', name: 'AD_ECP_APP_DETAIL', label: 'AD:电商-app内详情页到站uv', description: '电商app内详情页到站uv' }, + { id: 'AD_ECP_APP_VIEW', name: 'AD_ECP_APP_VIEW', label: 'AD:电商-app内访问', description: '电商app内访问' }, + { id: 'AD_ECP_BUTTON', name: 'AD_ECP_BUTTON', label: 'AD:电商-按钮跳转', description: '电商按钮跳转' }, + { id: 'AD_ECP_INTEREST', name: 'AD_ECP_INTEREST', label: 'AD:电商-引流电商种草', description: '引流电商种草' }, + { id: 'AD_ECP_SHOP', name: 'AD_ECP_SHOP', label: 'AD:电商-调起店铺', description: '调起店铺' }, + { id: 'AD_ECP_SHOP_STAY', name: 'AD_ECP_SHOP_STAY', label: 'AD:电商-店铺停留', description: '店铺停留' }, + { id: 'AD_MINIAPP_ACTIVATE', name: 'AD_MINIAPP_ACTIVATE', label: 'AD:快应用-激活', description: '快应用激活' }, + { id: 'AD_MINIAPP_KEY_BEHAVIOR', name: 'AD_MINIAPP_KEY_BEHAVIOR', label: 'AD:快应用-关键行为', description: '快应用关键行为' }, + { id: 'AD_MINIAPP_PAY', name: 'AD_MINIAPP_PAY', label: 'AD:快应用-付费', description: '快应用付费' }, + { id: 'AD_MINIAPP_REGISTER', name: 'AD_MINIAPP_REGISTER', label: 'AD:快应用-注册', description: '快应用注册' }, + { id: 'AD_NATIVE_ACTIVATE', name: 'AD_NATIVE_ACTIVATE', label: 'AD:原声互动-激活', description: '原声互动激活' }, + { id: 'AD_NATIVE_CLICKS', name: 'AD_NATIVE_CLICKS', label: 'AD:原声互动-组件点击', description: '组件点击' }, + { id: 'AD_NATIVE_FANS_GROUP', name: 'AD_NATIVE_FANS_GROUP', label: 'AD:原声互动-粉丝入群', description: '粉丝入群' }, + { id: 'AD_NATIVE_FOLLOW', name: 'AD_NATIVE_FOLLOW', label: 'AD:原声互动-帐号关注', description: '帐号关注' }, + { id: 'AD_NATIVE_INTERACTIVE', name: 'AD_NATIVE_INTERACTIVE', label: 'AD:原声互动-互动', description: '互动' }, + { id: 'AD_NATIVE_LIVE', name: 'AD_NATIVE_LIVE', label: 'AD:原声互动-预约直播', description: '预约直播' }, + { id: 'AD_NATIVE_LIVE_DONATE', name: 'AD_NATIVE_LIVE_DONATE', label: 'AD:原声互动-直播间营销捐赠', description: '直播间营销捐赠' }, + { id: 'AD_NATIVE_LIVE_PAY', name: 'AD_NATIVE_LIVE_PAY', label: 'AD:原声互动-直播间打赏', description: '直播间打赏' }, + { id: 'AD_NATIVE_LIVE_STAY', name: 'AD_NATIVE_LIVE_STAY', label: 'AD:原声互动-直播间停留', description: '直播间停留' }, + { id: 'AD_NATIVE_LIVE_VIEW', name: 'AD_NATIVE_LIVE_VIEW', label: 'AD:原声互动-直播间观看', description: '直播间观看' }, + { id: 'AD_NATIVE_PAY', name: 'AD_NATIVE_PAY', label: 'AD:原声互动-付费', description: '原声互动付费' }, + { id: 'AD_PRODUCT_ACTIVATE', name: 'AD_PRODUCT_ACTIVATE', label: 'AD:商品-激活', description: '商品激活' }, + { id: 'AD_PRODUCT_APP_BUY', name: 'AD_PRODUCT_APP_BUY', label: 'AD:商品-app内下单', description: '商品app内下单' }, + { id: 'AD_PRODUCT_APP_DETAIL', name: 'AD_PRODUCT_APP_DETAIL', label: 'AD:商品-app内详情页到站uv', description: '商品app内详情页到站uv' }, + { id: 'AD_PRODUCT_APP_PAY', name: 'AD_PRODUCT_APP_PAY', label: 'AD:商品-app内付费', description: '商品app内付费' }, + { id: 'AD_PRODUCT_APP_VIEW', name: 'AD_PRODUCT_APP_VIEW', label: 'AD:商品-app内访问', description: '商品app内访问' }, + { id: 'AD_PRODUCT_FORM', name: 'AD_PRODUCT_FORM', label: 'AD:商品-表单提交', description: '商品表单提交' }, + { id: 'AD_PRODUCT_KEY_BEHAVIOR', name: 'AD_PRODUCT_KEY_BEHAVIOR', label: 'AD:商品-关键行为', description: '商品关键行为' }, + { id: 'AD_PRODUCT_PAY', name: 'AD_PRODUCT_PAY', label: 'AD:商品-付费', description: '商品付费' }, + { id: 'AD_TINYAPP_ACTIVATE', name: 'AD_TINYAPP_ACTIVATE', label: '千川:小程序-激活', description: '小程序激活' }, + { id: 'AD_TINYAPP_KEY_BEHAVIOR', name: 'AD_TINYAPP_KEY_BEHAVIOR', label: '千川:小程序-关键行为', description: '小程序关键行为' }, + { id: 'AD_TINYAPP_PAY', name: 'AD_TINYAPP_PAY', label: '千川:小程序-付费', description: '小程序付费' }, + { id: 'QC_LIVE_BUY', name: 'QC_LIVE_BUY', label: '千川:直播投放-直播间下单', description: '直播间下单' }, + { id: 'QC_LIVE_CHECK', name: 'QC_LIVE_CHECK', label: '千川:直播投放-直播间结算', description: '直播间结算' }, + { id: 'QC_LIVE_COMMENTS', name: 'QC_LIVE_COMMENTS', label: '千川:直播投放-直播间评论', description: '直播间评论' }, + { id: 'QC_LIVE_DEAL', name: 'QC_LIVE_DEAL', label: '千川:直播投放-直播间成交', description: '直播间成交' }, + { id: 'QC_LIVE_ENTRY', name: 'QC_LIVE_ENTRY', label: '千川:直播投放-进入直播间', description: '进入直播间' }, + { id: 'QC_LIVE_FANS', name: 'QC_LIVE_FANS', label: '千川:直播投放-直播间粉丝提升', description: '直播间粉丝提升' }, + { id: 'QC_LIVE_HIT', name: 'QC_LIVE_HIT', label: '千川:直播投放-直播加热', description: '直播加热' }, + { id: 'QC_LIVE_PRODUCT_CLICKS', name: 'QC_LIVE_PRODUCT_CLICKS', label: '千川:直播投放-直播间商品点击', description: '直播间商品点击' }, + { id: 'QC_LIVE_ROI_CHECK', name: 'QC_LIVE_ROI_CHECK', label: '千川:直播投放-结算roi', description: '结算roi' }, + { id: 'QC_LIVE_ROI_DEAL', name: 'QC_LIVE_ROI_DEAL', label: '千川:直播投放-支付roi-直播间成交', description: '支付roi直播间成交' }, + { id: 'QC_LIVE_ROI_QC', name: 'QC_LIVE_ROI_QC', label: '千川:直播投放-支付roi-千川直接+间接订单', description: '支付roi千川订单' }, + { id: 'QC_PRODUCT_BUY', name: 'QC_PRODUCT_BUY', label: '千川:商品投放-商品购买', description: '商品购买' }, + { id: 'QC_PRODUCT_COMMENTS', name: 'QC_PRODUCT_COMMENTS', label: '千川:商品投放-点赞评论', description: '点赞评论' }, + { id: 'QC_PRODUCT_FANS', name: 'QC_PRODUCT_FANS', label: '千川:商品投放-粉丝提升', description: '粉丝提升' }, + { id: 'QC_PRODUCT_INTEREST', name: 'QC_PRODUCT_INTEREST', label: '千川:商品投放-人群种草', description: '人群种草' }, + { id: 'QC_PRODUCT_QC', name: 'QC_PRODUCT_QC', label: '千川:商品投放-千川直接+间接订单', description: '千川订单' }, + { id: 'QC_PRODUCT_ROI', name: 'QC_PRODUCT_ROI', label: '千川:商品投放-商品支付roi', description: '商品支付roi' }, +]; + +const columns = [ + { + title: '序号', + dataIndex: 'index', + key: 'index', + width: 70, + render: (text: number) => {text}, + }, + { + title: '前测模板ID', + dataIndex: 'id', + key: 'id', + ellipsis: true, + width: 100, + render: (text: string) => {text}, + }, + { + title: '模板名称', + dataIndex: 'templateName', + key: 'templateName', + ellipsis: true, + width: 150, + render: (text: string) => ( + {text} + ), + }, + { + title: '选中的字段', + dataIndex: 'selectedFields', + key: 'selectedFields', + render: (fields: string[]) => ( + + {fields.map((field) => { + const fieldInfo = PRETEST_FIELDS.find(f => f.name === field); + return ( + + {fieldInfo?.label || field} + + ); + })} + + ), + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 100, + render: (text: string) => { + const config = statusConfig[text as keyof typeof statusConfig] || statusConfig.pending; + return ( + + {config.label} + + ); + }, + }, + { + title: '创建时间', + dataIndex: 'createdAt', + key: 'createdAt', + width: 160, + render: (text: string) => {text}, + }, + { + title: '操作', + dataIndex: 'operation', + key: 'operation', + width: 140, + render: (_: any, record: PreTestRecord) => ( + + } + style={{ color: '#6366f1', fontSize: 12 }} + > + 编辑 + + + ), + }, +]; + +const PreTest: React.FC = () => { + const [records, setRecords] = useState([]); + const [total, setTotal] = useState(0); + const [loading, setLoading] = useState(false); + const [modalOpen, setModalOpen] = useState(false); + const [form] = Form.useForm(); + const [submitLoading, setSubmitLoading] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + + useEffect(() => { + loadRecords(); + }, [currentPage, pageSize]); + + const loadRecords = async () => { + setLoading(true); + try { + const data = await getPreTestList({ page: currentPage, pageSize }); + setRecords(data.items || []); + setTotal(data.total || 0); + } catch (error) { + message.error('获取前测列表失败'); + } finally { + setLoading(false); + } + }; + + const tableData = records.map((item, index) => ({ + ...item, + index: (currentPage - 1) * pageSize + index + 1, + key: item.id, + })); + + const handleOpenModal = () => { + form.resetFields(); + setModalOpen(true); + }; + + const handleCloseModal = () => { + setModalOpen(false); + form.resetFields(); + }; + + const handleSubmit = async () => { + try { + const values = await form.validateFields(); + setSubmitLoading(true); + + const result = await createPreTest({ + templateName: values.templateName, + selectedFields: values.selectedFields, + status: values.status, + }); + + message.success('前测模板创建成功'); + setModalOpen(false); + form.resetFields(); + loadRecords(); + } catch (error) { + message.error('创建失败,请重试'); + } finally { + setSubmitLoading(false); + } + }; + + const handlePageChange = (page: number, size: number) => { + setCurrentPage(page); + setPageSize(size); + }; + + return ( + + + + 前测管理 + + + + } + loading={loading} + onClick={handleOpenModal} + style={{ + borderRadius: 12, + fontSize: 14, + background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', + border: 'none', + boxShadow: '0 4px 14px rgba(99,102,241,0.3)', + }} + > + 新增前测 + + + + `共 ${total} 条记录`, + pageSizeOptions: ['10', '20', '50', '100'], + onChange: handlePageChange, + style: { padding: '16px 24px', borderTop: '1px solid #f1f5f9' }, + }} + rowKey="id" + bordered={false} + style={{ padding: 0 }} + scroll={{ x: 'max-content' }} + onRow={() => ({ + style: { + cursor: 'pointer', + transition: 'background-color 0.15s', + }, + onMouseEnter: (e: React.MouseEvent) => { + (e.currentTarget as HTMLElement).style.backgroundColor = '#f8fafc'; + }, + onMouseLeave: (e: React.MouseEvent) => { + (e.currentTarget as HTMLElement).style.backgroundColor = 'transparent'; + }, + })} + /> + + + 新增前测模板} + open={modalOpen} + onCancel={handleCloseModal} + onOk={handleSubmit} + okText="提交" + cancelText="取消" + width={520} + confirmLoading={submitLoading} + style={{ borderRadius: 16 }} + > + + + + + + + ({ + value: field.name, + label: ( + + {field.label} + {field.description && ( + + ({field.description}) + + )} + + ), + }))} + /> + + + + + + + + + ); +}; + +export default PreTest; \ No newline at end of file