前台素材云页面样式统一修改
This commit is contained in:
@@ -0,0 +1,186 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, DatePicker, Input, Space } from 'antd';
|
||||||
|
import {
|
||||||
|
CheckOutlined,
|
||||||
|
ClearOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownloadOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
UploadOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
interface UnifiedFilterBarProps {
|
||||||
|
// 搜索
|
||||||
|
searchValue: string;
|
||||||
|
searchPlaceholder?: string;
|
||||||
|
onSearchChange: (value: string) => void;
|
||||||
|
onSearch: () => void;
|
||||||
|
// 日期
|
||||||
|
showDate?: boolean;
|
||||||
|
dateValue?: Dayjs | null;
|
||||||
|
onDateChange?: (date: Dayjs | null) => void;
|
||||||
|
datePlaceholder?: string;
|
||||||
|
// 批量操作
|
||||||
|
batchCount: number;
|
||||||
|
totalCount: number;
|
||||||
|
onSelectAll: () => void;
|
||||||
|
onClearSelection: () => void;
|
||||||
|
onDelete: () => void;
|
||||||
|
onDownload?: () => void;
|
||||||
|
onPush?: () => void;
|
||||||
|
pushLoading?: boolean;
|
||||||
|
pushText?: string;
|
||||||
|
// 右侧额外操作
|
||||||
|
extraActions?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnBase: React.CSSProperties = {
|
||||||
|
borderRadius: 8,
|
||||||
|
fontWeight: 600,
|
||||||
|
height: 36,
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnSecondary: React.CSSProperties = {
|
||||||
|
...btnBase,
|
||||||
|
background: '#f8f9fc',
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
color: '#334155',
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnDanger: React.CSSProperties = {
|
||||||
|
...btnBase,
|
||||||
|
background: 'linear-gradient(135deg, #ef4444, #dc2626)',
|
||||||
|
border: 'none',
|
||||||
|
color: '#fff',
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnPrimary: React.CSSProperties = {
|
||||||
|
...btnBase,
|
||||||
|
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
||||||
|
border: 'none',
|
||||||
|
color: '#fff',
|
||||||
|
};
|
||||||
|
|
||||||
|
const UnifiedFilterBar: React.FC<UnifiedFilterBarProps> = ({
|
||||||
|
searchValue,
|
||||||
|
searchPlaceholder = '搜索...',
|
||||||
|
onSearchChange,
|
||||||
|
onSearch,
|
||||||
|
showDate = false,
|
||||||
|
dateValue,
|
||||||
|
onDateChange,
|
||||||
|
datePlaceholder = '选择日期',
|
||||||
|
batchCount,
|
||||||
|
totalCount,
|
||||||
|
onSelectAll,
|
||||||
|
onClearSelection,
|
||||||
|
onDelete,
|
||||||
|
onDownload,
|
||||||
|
onPush,
|
||||||
|
pushLoading = false,
|
||||||
|
pushText = '推送至账户',
|
||||||
|
extraActions,
|
||||||
|
}) => {
|
||||||
|
const allSelected = batchCount === totalCount && totalCount > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
marginBottom: 16,
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: 12,
|
||||||
|
background: '#fff',
|
||||||
|
border: '1px solid #f0f0f5',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* 左侧:搜索 + 日期 */}
|
||||||
|
<Space size={8} wrap>
|
||||||
|
<Input.Search
|
||||||
|
placeholder={searchPlaceholder}
|
||||||
|
allowClear
|
||||||
|
value={searchValue}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = e.target.value;
|
||||||
|
onSearchChange(val);
|
||||||
|
if (!val) onSearch();
|
||||||
|
}}
|
||||||
|
onSearch={onSearch}
|
||||||
|
style={{ width: 240, borderRadius: 8 }}
|
||||||
|
enterButton
|
||||||
|
/>
|
||||||
|
{showDate && (
|
||||||
|
<>
|
||||||
|
<DatePicker
|
||||||
|
value={dateValue || undefined}
|
||||||
|
onChange={(date) => onDateChange?.(date)}
|
||||||
|
format="YYYY-MM-DD"
|
||||||
|
placeholder={datePlaceholder}
|
||||||
|
style={{ width: 160, borderRadius: 8 }}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
{/* 右侧:批量操作 + 额外操作 */}
|
||||||
|
<Space size={8} wrap>
|
||||||
|
{/* 全选 / 取消选择 */}
|
||||||
|
<Button
|
||||||
|
icon={allSelected ? <ClearOutlined /> : <CheckOutlined />}
|
||||||
|
onClick={allSelected ? onClearSelection : onSelectAll}
|
||||||
|
style={btnSecondary}
|
||||||
|
>
|
||||||
|
{allSelected ? '取消全选' : '全选'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* 删除 */}
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={onDelete}
|
||||||
|
disabled={batchCount === 0}
|
||||||
|
style={batchCount === 0 ? { ...btnSecondary, opacity: 0.5 } : btnDanger}
|
||||||
|
>
|
||||||
|
删除 {batchCount > 0 && `(${batchCount})`}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* 下载 */}
|
||||||
|
{onDownload && (
|
||||||
|
<Button
|
||||||
|
icon={<DownloadOutlined />}
|
||||||
|
onClick={onDownload}
|
||||||
|
disabled={batchCount === 0}
|
||||||
|
style={batchCount === 0 ? { ...btnSecondary, opacity: 0.5 } : btnPrimary}
|
||||||
|
>
|
||||||
|
下载 {batchCount > 0 && `(${batchCount})`}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 推送 */}
|
||||||
|
{onPush && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<UploadOutlined />}
|
||||||
|
onClick={onPush}
|
||||||
|
loading={pushLoading}
|
||||||
|
disabled={batchCount === 0}
|
||||||
|
style={batchCount === 0 ? { ...btnBase, opacity: 0.5 } : btnPrimary}
|
||||||
|
>
|
||||||
|
{pushLoading ? '推送中...' : `${pushText} ${batchCount > 0 ? `(${batchCount})` : ''}`}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 额外操作按钮 */}
|
||||||
|
{extraActions}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UnifiedFilterBar;
|
||||||
@@ -7,6 +7,9 @@ import dayjs from 'dayjs';
|
|||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
|
// 统一按钮样式
|
||||||
|
const btnBase: React.CSSProperties = { borderRadius: 8, fontWeight: 600 };
|
||||||
|
|
||||||
const toCamelCase = (str: string) => str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
const toCamelCase = (str: string) => str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||||
|
|
||||||
interface ConsumptionRecord {
|
interface ConsumptionRecord {
|
||||||
@@ -164,13 +167,24 @@ const ConsumePage: React.FC = () => {
|
|||||||
<Typography.Text strong style={{ fontSize: 16 }}>消耗记录</Typography.Text>
|
<Typography.Text strong style={{ fontSize: 16 }}>消耗记录</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, marginBottom: 16 }}>
|
<div style={{
|
||||||
<div style={{ display: 'flex', gap: 12 }}>
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
marginBottom: 16,
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: 12,
|
||||||
|
background: '#fff',
|
||||||
|
border: '1px solid #f0f0f5',
|
||||||
|
}}>
|
||||||
|
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||||
<Input
|
<Input
|
||||||
placeholder="广告主ID"
|
placeholder="广告主ID"
|
||||||
value={advertiserId}
|
value={advertiserId}
|
||||||
onChange={(e) => setAdvertiserId(e.target.value)}
|
onChange={(e) => setAdvertiserId(e.target.value)}
|
||||||
style={{ width: 180 }}
|
style={{ width: 180, borderRadius: 8 }}
|
||||||
allowClear
|
allowClear
|
||||||
onPressEnter={() => { setCurrentPage(1); loadData(1, pageSize); }}
|
onPressEnter={() => { setCurrentPage(1); loadData(1, pageSize); }}
|
||||||
/>
|
/>
|
||||||
@@ -183,25 +197,32 @@ const ConsumePage: React.FC = () => {
|
|||||||
setConsumeDateRange(undefined);
|
setConsumeDateRange(undefined);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
style={{ borderRadius: 8 }}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="medium"
|
|
||||||
onClick={handleSearch}
|
onClick={handleSearch}
|
||||||
|
style={{ ...btnBase, borderColor: 'transparent' }}
|
||||||
>
|
>
|
||||||
搜索
|
搜索
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 12 }}>
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
<Button
|
<Button
|
||||||
icon={<SyncOutlined />}
|
icon={<SyncOutlined />}
|
||||||
onClick={handleSync}
|
onClick={handleSync}
|
||||||
loading={syncLoading}
|
loading={syncLoading}
|
||||||
style={{ borderRadius: 8 }}
|
style={btnBase}
|
||||||
>
|
>
|
||||||
拉取消耗
|
拉取消耗
|
||||||
</Button>
|
</Button>
|
||||||
<Button icon={<SettingOutlined />} onClick={() => setShowModal(true)} style={{ borderRadius: 8 }}>自定义表头</Button>
|
<Button
|
||||||
|
icon={<SettingOutlined />}
|
||||||
|
onClick={() => setShowModal(true)}
|
||||||
|
style={{ ...btnBase, background: '#f8f9fc', border: '1px solid #e2e8f0', color: '#334155' }}
|
||||||
|
>
|
||||||
|
自定义表头
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import {
|
|||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
LoadingOutlined,
|
LoadingOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
|
CheckOutlined,
|
||||||
|
ClearOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { PrivatePortraitLibraryPanel } from '../components/privatePortrait';
|
import { PrivatePortraitLibraryPanel } from '../components/privatePortrait';
|
||||||
@@ -909,225 +911,86 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
}, [filterType, filterMedia]);
|
}, [filterType, filterMedia]);
|
||||||
return (
|
return (
|
||||||
<div className="content_box" >
|
<div className="content_box" >
|
||||||
{/* 操作栏:筛选 + 推送按钮 */}
|
{/* 顶部:标签切换 + 批量操作按钮 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 12,
|
gap: 12,
|
||||||
marginBottom: 16,
|
marginBottom: 12,
|
||||||
padding: '12px 16px',
|
|
||||||
borderRadius: 12,
|
|
||||||
background: '#fff',
|
|
||||||
border: '1px solid #f0f0f5',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap' }}>
|
<Tabs
|
||||||
<FilterOutlined style={{ color: '#090a0cff', fontSize: 14 }} />
|
activeKey={filterType}
|
||||||
<Space size={8} wrap={true}>
|
onChange={(key) => {
|
||||||
<Button
|
setFilterType(key as typeof filterType);
|
||||||
type={filterType === 'project' ? 'primary' : 'default'}
|
setIsSelectionMode(false);
|
||||||
onClick={() => {
|
setSelectedItems(new Set());
|
||||||
setFilterType('project');
|
}}
|
||||||
setIsSelectionMode(false);
|
items={[
|
||||||
setSelectedItems(new Set());
|
{ key: 'project', label: <span><FolderOpenOutlined /> 项目记录</span> },
|
||||||
}}
|
{ key: 'creation', label: <span><FileTextOutlined /> 创作记录</span> },
|
||||||
style={{
|
{ key: 'hot_opening_replicate', label: <span><StarOutlined /> 爆款复刻</span> },
|
||||||
borderRadius: 8,
|
{ key: 'shot_replicate', label: <span><PlayCircleOutlined /> 拆镜复刻</span> },
|
||||||
background: filterType === 'project'
|
{ key: 'private_portrait', label: <span><UserOutlined /> 私域素材库</span> },
|
||||||
? 'linear-gradient(135deg, #6366f1, #8b5cf6)'
|
{ key: 'upload_resource', label: <span><UploadOutlined /> 历史素材</span> },
|
||||||
: '#f8f9fc',
|
]}
|
||||||
border: filterType === 'project' ? 'none' : '1px solid #e2e8f0',
|
size="middle"
|
||||||
color: filterType === 'project' ? '#fff' : '#64748b',
|
/>
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
icon={<FolderOpenOutlined />}
|
|
||||||
>
|
|
||||||
项目记录
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type={filterType === 'creation' ? 'primary' : 'default'}
|
|
||||||
onClick={() => {
|
|
||||||
setFilterType('creation');
|
|
||||||
setIsSelectionMode(false);
|
|
||||||
setSelectedItems(new Set());
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
borderRadius: 8,
|
|
||||||
background: filterType === 'creation'
|
|
||||||
? 'linear-gradient(135deg, #6366f1, #8b5cf6)'
|
|
||||||
: '#f8f9fc',
|
|
||||||
border: filterType === 'creation' ? 'none' : '1px solid #e2e8f0',
|
|
||||||
color: filterType === 'creation' ? '#fff' : '#64748b',
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
icon={<FileTextOutlined />}
|
|
||||||
>
|
|
||||||
创作记录
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type={filterType === 'hot_opening_replicate' ? 'primary' : 'default'}
|
|
||||||
onClick={() => {
|
|
||||||
setFilterType('hot_opening_replicate');
|
|
||||||
setIsSelectionMode(false);
|
|
||||||
setSelectedItems(new Set());
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
borderRadius: 8,
|
|
||||||
background: filterType === 'hot_opening_replicate'
|
|
||||||
? 'linear-gradient(135deg, #6366f1, #8b5cf6)'
|
|
||||||
: '#f8f9fc',
|
|
||||||
border: filterType === 'hot_opening_replicate' ? 'none' : '1px solid #e2e8f0',
|
|
||||||
color: filterType === 'hot_opening_replicate' ? '#fff' : '#64748b',
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
icon={<StarOutlined />}
|
|
||||||
>
|
|
||||||
爆款复刻
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type={filterType === 'shot_replicate' ? 'primary' : 'default'}
|
|
||||||
onClick={() => {
|
|
||||||
setFilterType('shot_replicate');
|
|
||||||
setIsSelectionMode(false);
|
|
||||||
setSelectedItems(new Set());
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
borderRadius: 8,
|
|
||||||
background: filterType === 'shot_replicate'
|
|
||||||
? 'linear-gradient(135deg, #6366f1, #8b5cf6)'
|
|
||||||
: '#f8f9fc',
|
|
||||||
border: filterType === 'shot_replicate' ? 'none' : '1px solid #e2e8f0',
|
|
||||||
color: filterType === 'shot_replicate' ? '#fff' : '#64748b',
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
icon={<PlayCircleOutlined />}
|
|
||||||
>
|
|
||||||
拆镜复刻
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type={filterType === 'private_portrait' ? 'primary' : 'default'}
|
|
||||||
onClick={() => {
|
|
||||||
setFilterType('private_portrait');
|
|
||||||
setIsSelectionMode(false);
|
|
||||||
setSelectedItems(new Set());
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
borderRadius: 8,
|
|
||||||
background: filterType === 'private_portrait'
|
|
||||||
? 'linear-gradient(135deg, #6366f1, #8b5cf6)'
|
|
||||||
: '#f8f9fc',
|
|
||||||
border: filterType === 'private_portrait' ? 'none' : '1px solid #e2e8f0',
|
|
||||||
color: filterType === 'private_portrait' ? '#fff' : '#64748b',
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
icon={<UserOutlined />}
|
|
||||||
>
|
|
||||||
私域素材库
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type={filterType === 'upload_resource' ? 'primary' : 'default'}
|
|
||||||
onClick={() => {
|
|
||||||
setFilterType('upload_resource');
|
|
||||||
setIsSelectionMode(false);
|
|
||||||
setSelectedItems(new Set());
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
borderRadius: 8,
|
|
||||||
background: filterType === 'upload_resource'
|
|
||||||
? 'linear-gradient(135deg, #6366f1, #8b5cf6)'
|
|
||||||
: '#f8f9fc',
|
|
||||||
border: filterType === 'upload_resource' ? 'none' : '1px solid #e2e8f0',
|
|
||||||
color: filterType === 'upload_resource' ? '#fff' : '#64748b',
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
icon={<UploadOutlined />}
|
|
||||||
>
|
|
||||||
历史素材
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
{filterType !== 'private_portrait' && filterType !== 'upload_resource' && (
|
{filterType !== 'private_portrait' && filterType !== 'upload_resource' && (
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
{/* 多选模式按钮 */}
|
{isSelectionMode ? (
|
||||||
{isSelectionMode ? (
|
<Space size={8}>
|
||||||
<Space size={8} wrap={true}>
|
<Button
|
||||||
<Button
|
icon={selectedItems.size === recordlist.reduce((sum: number, group: any) => sum + group.items.length, 0) && recordlist.length > 0 ? <ClearOutlined /> : <CheckOutlined />}
|
||||||
onClick={handleSelectAll}
|
onClick={selectedItems.size === recordlist.reduce((sum: number, group: any) => sum + group.items.length, 0) && recordlist.length > 0 ? () => { setSelectedItems(new Set()); } : handleSelectAll}
|
||||||
style={{
|
style={{ borderRadius: 8, fontWeight: 600, background: '#f8f9fc', border: '1px solid #e2e8f0', color: '#334155' }}
|
||||||
borderRadius: 8,
|
>
|
||||||
background: '#f8f9fc',
|
{selectedItems.size === recordlist.reduce((sum: number, group: any) => sum + group.items.length, 0) && recordlist.length > 0 ? '取消全选' : '全选'}
|
||||||
border: '1px solid #e2e8f0',
|
</Button>
|
||||||
color: '#222222ff',
|
<Button
|
||||||
fontWeight: 600,
|
onClick={() => { setIsSelectionMode(false); setSelectedItems(new Set()); }}
|
||||||
}}
|
style={{ borderRadius: 8, fontWeight: 600, background: '#f8f9fc', border: '1px solid #e2e8f0', color: '#64748b' }}
|
||||||
>
|
>
|
||||||
{selectedItems.size === recordlist.reduce((sum: number, group: any) => sum + group.items.length, 0) ? '取消全选' : '全选'}
|
取消选择
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
danger
|
||||||
setIsSelectionMode(false);
|
icon={<DeleteOutlined />}
|
||||||
setSelectedItems(new Set());
|
onClick={handleDeleteSelected}
|
||||||
}}
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #ef4444, #dc2626)', border: 'none', color: '#fff' }}
|
||||||
style={{
|
>
|
||||||
borderRadius: 8,
|
删除 {selectedItems.size > 0 && `(${selectedItems.size})`}
|
||||||
background: '#f8f9fc',
|
</Button>
|
||||||
border: '1px solid #e2e8f0',
|
<Button
|
||||||
color: '#64748b',
|
icon={<DownloadOutlined />}
|
||||||
fontWeight: 600,
|
onClick={handleDownloadSelected}
|
||||||
}}
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1, #8b5cf6)', border: 'none', color: '#fff' }}
|
||||||
>
|
>
|
||||||
取消选择
|
下载 {selectedItems.size > 0 && `(${selectedItems.size})`}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleDeleteSelected()}
|
type="primary"
|
||||||
style={{ borderRadius: 8, background: 'linear-gradient(135deg, #bf0b0a 0%, #ff8165 100%)', fontWeight: 600, padding: '8px 24px', color: '#fff' }}
|
icon={<UploadOutlined />}
|
||||||
>
|
onClick={handleBatchUploadSelected}
|
||||||
删除 ({selectedItems.size})
|
loading={uploading}
|
||||||
</Button>
|
disabled={uploading || selectedItems.size === 0}
|
||||||
<Button
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1, #8b5cf6)', border: 'none', color: '#fff' }}
|
||||||
onClick={() => handleDownloadSelected()}
|
>
|
||||||
style={{
|
{uploading ? '推送中...' : `推送至账户 ${selectedItems.size > 0 ? `(${selectedItems.size})` : ''}`}
|
||||||
borderRadius: 8,
|
</Button>
|
||||||
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
</Space>
|
||||||
color: '#fff',
|
) : (
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
下载 ({selectedItems.size})
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={handleBatchUploadSelected}
|
icon={<UploadOutlined />}
|
||||||
loading={uploading}
|
onClick={() => setIsSelectionMode(true)}
|
||||||
disabled={uploading || selectedItems.size === 0}
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}
|
||||||
style={{
|
|
||||||
borderRadius: 8,
|
|
||||||
background: 'linear-gradient(135deg, #6366f1, #8b5cf6)',
|
|
||||||
color: '#fff',
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{uploading ? '推送中...' : `推送至账户 (${selectedItems.size})`}
|
批量操作
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
)}
|
||||||
) : (
|
</div>
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<UploadOutlined />}
|
|
||||||
onClick={() => setIsSelectionMode(true)}
|
|
||||||
style={{
|
|
||||||
borderRadius: 8,
|
|
||||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
|
||||||
border: 'none',
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
批量操作
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{filterType === 'private_portrait' ? (
|
{filterType === 'private_portrait' ? (
|
||||||
@@ -1136,122 +999,75 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
<UploadResourceHistoryPanel />
|
<UploadResourceHistoryPanel />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Second row filter: 视频 / 图片 */}
|
{/* 统一筛选栏:搜索 + 日期 */}
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 12,
|
gap: 12,
|
||||||
marginBottom: 24,
|
marginBottom: 16,
|
||||||
padding: '12px 16px',
|
padding: '12px 16px',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
border: '1px solid #f0f0f5',
|
border: '1px solid #f0f0f5',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
<Typography.Text style={{ color: '#94a3b8', fontSize: 14 }}>媒体类型:</Typography.Text>
|
<Typography.Text style={{ color: '#94a3b8', fontSize: 14, marginRight: 4 }}>媒体类型:</Typography.Text>
|
||||||
<Space size={8} wrap={true}>
|
<Button
|
||||||
<Button
|
type={filterMedia === 'video' ? 'primary' : 'default'}
|
||||||
type={filterMedia === 'video' ? 'primary' : 'default'}
|
onClick={() => { setFilterMedia('video'); setIsSelectionMode(false); setSelectedItems(new Set()); }}
|
||||||
onClick={() => {
|
style={{ borderRadius: 8, fontWeight: 600, borderColor: filterMedia === 'video' ? 'transparent' : '#e2e8f0', color: filterMedia === 'video' ? '#fff' : '#64748b' }}
|
||||||
setFilterMedia('video');
|
icon={<VideoCameraOutlined />}
|
||||||
setIsSelectionMode(false);
|
>
|
||||||
setSelectedItems(new Set());
|
视频
|
||||||
}}
|
</Button>
|
||||||
style={{
|
<Button
|
||||||
borderRadius: 8,
|
type={filterMedia === 'image' ? 'primary' : 'default'}
|
||||||
background: filterMedia === 'video'
|
onClick={() => { setFilterMedia('image'); setIsSelectionMode(false); setSelectedItems(new Set()); }}
|
||||||
? 'linear-gradient(135deg, #6366f1, #8b5cf6)'
|
style={{ borderRadius: 8, fontWeight: 600, borderColor: filterMedia === 'image' ? 'transparent' : '#e2e8f0', color: filterMedia === 'image' ? '#fff' : '#64748b' }}
|
||||||
: '#f8f9fc',
|
icon={<PictureOutlined />}
|
||||||
border: filterMedia === 'video' ? 'none' : '1px solid #e2e8f0',
|
>
|
||||||
color: filterMedia === 'video' ? '#fff' : '#64748b',
|
图片
|
||||||
fontWeight: 600,
|
</Button>
|
||||||
}}
|
</div>
|
||||||
icon={<VideoCameraOutlined />}
|
<Space size={8} wrap>
|
||||||
>
|
<DatePicker
|
||||||
视频
|
picker="date"
|
||||||
</Button>
|
value={selectedDate ? dayjs(selectedDate) : undefined}
|
||||||
<Button
|
onChange={(date, dateString) => handleDateChange(dateString || '')}
|
||||||
type={filterMedia === 'image' ? 'primary' : 'default'}
|
format="YYYY-MM-DD"
|
||||||
onClick={() => {
|
style={{ width: 160, borderRadius: 8, border: '1px solid #e2e8f0' }}
|
||||||
setFilterMedia('image');
|
placeholder="选择日期"
|
||||||
setIsSelectionMode(false);
|
allowClear
|
||||||
setSelectedItems(new Set());
|
/>
|
||||||
}}
|
<Input.Search
|
||||||
style={{
|
placeholder="搜索提示词"
|
||||||
borderRadius: 8,
|
allowClear
|
||||||
background: filterMedia === 'image'
|
value={searchKeyword}
|
||||||
? 'linear-gradient(135deg, #6366f1, #8b5cf6)'
|
onChange={(e) => {
|
||||||
: '#f8f9fc',
|
const val = e.target.value;
|
||||||
border: filterMedia === 'image' ? 'none' : '1px solid #e2e8f0',
|
setSearchKeyword(val);
|
||||||
color: filterMedia === 'image' ? '#fff' : '#64748b',
|
if (!val) {
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
icon={<PictureOutlined />}
|
|
||||||
>
|
|
||||||
图片
|
|
||||||
</Button>
|
|
||||||
<DatePicker
|
|
||||||
picker="date"
|
|
||||||
value={selectedDate ? dayjs(selectedDate) : undefined}
|
|
||||||
onChange={(date, dateString) => handleDateChange(dateString || '')}
|
|
||||||
format="YYYY-MM-DD"
|
|
||||||
style={{ width: 160, borderRadius: 8, border: '1px solid #e2e8f0' }}
|
|
||||||
placeholder="选择日期"
|
|
||||||
/>
|
|
||||||
{selectedDate && (
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
onClick={() => handleDateChange('')}
|
|
||||||
style={{ color: '#94a3b8', fontSize: 12 }}
|
|
||||||
>
|
|
||||||
清除
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Input.Search
|
|
||||||
placeholder="搜索提示词"
|
|
||||||
allowClear
|
|
||||||
value={searchKeyword}
|
|
||||||
onChange={(e) => {
|
|
||||||
const val = e.target.value;
|
|
||||||
setSearchKeyword(val);
|
|
||||||
if (!val) {
|
|
||||||
setPagebreak(prev => ({ ...prev, page: 1 }));
|
|
||||||
loadRecordList();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onSearch={() => {
|
|
||||||
setPagebreak(prev => ({ ...prev, page: 1 }));
|
setPagebreak(prev => ({ ...prev, page: 1 }));
|
||||||
loadRecordList();
|
loadRecordList();
|
||||||
}}
|
}
|
||||||
style={{ width: 240, borderRadius: 8 }}
|
}}
|
||||||
/>
|
onSearch={() => {
|
||||||
</Space>
|
setPagebreak(prev => ({ ...prev, page: 1 }));
|
||||||
</div>
|
loadRecordList();
|
||||||
<Button
|
}}
|
||||||
icon={<ClockCircleOutlined />}
|
style={{ width: 240, borderRadius: 8 }}
|
||||||
onClick={handleOpenUploadHistory}
|
enterButton
|
||||||
style={{
|
/>
|
||||||
borderRadius: 8,
|
<Button
|
||||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
icon={<ClockCircleOutlined />}
|
||||||
border: 'none',
|
onClick={handleOpenUploadHistory}
|
||||||
color: '#ffffff',
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', color: '#fff' }}
|
||||||
fontWeight: 600,
|
>
|
||||||
boxShadow: '0 4px 15px rgba(102, 126, 234, 0.4)',
|
推送任务历史
|
||||||
transition: 'all 0.3s ease',
|
</Button>
|
||||||
}}
|
</Space>
|
||||||
onMouseEnter={(e) => {
|
|
||||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
|
||||||
e.currentTarget.style.boxShadow = '0 6px 20px rgba(102, 126, 234, 0.6)';
|
|
||||||
}}
|
|
||||||
onMouseLeave={(e) => {
|
|
||||||
e.currentTarget.style.transform = 'translateY(0)';
|
|
||||||
e.currentTarget.style.boxShadow = '0 4px 15px rgba(102, 126, 234, 0.4)';
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
查询推送任务历史
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
{/* Content area */}
|
{/* Content area */}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
@@ -1408,18 +1224,20 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 8,
|
top: 6,
|
||||||
left: 8,
|
left: 6,
|
||||||
width: 20,
|
width: 22,
|
||||||
height: 20,
|
height: 22,
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
backgroundColor: isSelectedItem ? '#10b981' : 'rgba(255,255,255,0.9)',
|
backgroundColor: isSelectedItem ? '#6366f1' : 'rgba(255,255,255,0.92)',
|
||||||
border: isSelectedItem ? '2px solid #10b981' : '2px solid #d1d5db',
|
border: isSelectedItem ? '2px solid #6366f1' : '2px solid #cbd5e1',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
|
boxShadow: isSelectedItem ? '0 2px 8px rgba(99,102,241,0.4)' : '0 1px 3px rgba(0,0,0,0.1)',
|
||||||
|
transition: 'all 0.15s ease',
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -1517,10 +1335,11 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
border: '3px solid #10b981',
|
border: '3px solid #6366f1',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
zIndex: 5,
|
zIndex: 5,
|
||||||
|
boxShadow: 'inset 0 0 0 1px rgba(99,102,241,0.2)',
|
||||||
}} />
|
}} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -383,13 +383,24 @@ const MaterialListPage: React.FC = () => {
|
|||||||
<FolderOpenOutlined style={{ color: '#6366f1', fontSize: 16 }} />
|
<FolderOpenOutlined style={{ color: '#6366f1', fontSize: 16 }} />
|
||||||
<Typography.Text strong style={{ fontSize: 16 }}>素材列表</Typography.Text>
|
<Typography.Text strong style={{ fontSize: 16 }}>素材列表</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'top', marginBottom: 16 }}>
|
<div style={{
|
||||||
<div style={{ display: 'flex', gap: 16, alignItems: 'center', flexWrap: 'wrap' }}>
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 12,
|
||||||
|
marginBottom: 16,
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: 12,
|
||||||
|
background: '#fff',
|
||||||
|
border: '1px solid #f0f0f5',
|
||||||
|
}}>
|
||||||
|
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||||
<Input
|
<Input
|
||||||
placeholder="广告主ID"
|
placeholder="广告主ID"
|
||||||
value={searchParams.advertiser_id}
|
value={searchParams.advertiser_id}
|
||||||
onChange={(e) => setSearchParams(prev => ({ ...prev, advertiser_id: e.target.value }))}
|
onChange={(e) => setSearchParams(prev => ({ ...prev, advertiser_id: e.target.value }))}
|
||||||
style={{ width: 160 }}
|
style={{ width: 150, borderRadius: 8 }}
|
||||||
allowClear
|
allowClear
|
||||||
onPressEnter={() => { setCurrentPage(1); loadMaterialList(1, pageSize); }}
|
onPressEnter={() => { setCurrentPage(1); loadMaterialList(1, pageSize); }}
|
||||||
/>
|
/>
|
||||||
@@ -397,7 +408,7 @@ const MaterialListPage: React.FC = () => {
|
|||||||
placeholder="素材ID"
|
placeholder="素材ID"
|
||||||
value={searchParams.material_id}
|
value={searchParams.material_id}
|
||||||
onChange={(e) => setSearchParams(prev => ({ ...prev, material_id: e.target.value }))}
|
onChange={(e) => setSearchParams(prev => ({ ...prev, material_id: e.target.value }))}
|
||||||
style={{ width: 160 }}
|
style={{ width: 150, borderRadius: 8 }}
|
||||||
allowClear
|
allowClear
|
||||||
onPressEnter={() => { setCurrentPage(1); loadMaterialList(1, pageSize); }}
|
onPressEnter={() => { setCurrentPage(1); loadMaterialList(1, pageSize); }}
|
||||||
/>
|
/>
|
||||||
@@ -405,7 +416,7 @@ const MaterialListPage: React.FC = () => {
|
|||||||
placeholder="上传ID"
|
placeholder="上传ID"
|
||||||
value={searchParams.upload_id}
|
value={searchParams.upload_id}
|
||||||
onChange={(e) => setSearchParams(prev => ({ ...prev, upload_id: e.target.value }))}
|
onChange={(e) => setSearchParams(prev => ({ ...prev, upload_id: e.target.value }))}
|
||||||
style={{ width: 160 }}
|
style={{ width: 150, borderRadius: 8 }}
|
||||||
allowClear
|
allowClear
|
||||||
onPressEnter={() => { setCurrentPage(1); loadMaterialList(1, pageSize); }}
|
onPressEnter={() => { setCurrentPage(1); loadMaterialList(1, pageSize); }}
|
||||||
/>
|
/>
|
||||||
@@ -413,7 +424,7 @@ const MaterialListPage: React.FC = () => {
|
|||||||
placeholder="文件名"
|
placeholder="文件名"
|
||||||
value={searchParams.file_name}
|
value={searchParams.file_name}
|
||||||
onChange={(e) => setSearchParams(prev => ({ ...prev, file_name: e.target.value }))}
|
onChange={(e) => setSearchParams(prev => ({ ...prev, file_name: e.target.value }))}
|
||||||
style={{ width: 140 }}
|
style={{ width: 140, borderRadius: 8 }}
|
||||||
allowClear
|
allowClear
|
||||||
onPressEnter={() => { setCurrentPage(1); loadMaterialList(1, pageSize); }}
|
onPressEnter={() => { setCurrentPage(1); loadMaterialList(1, pageSize); }}
|
||||||
/>
|
/>
|
||||||
@@ -421,7 +432,7 @@ const MaterialListPage: React.FC = () => {
|
|||||||
placeholder="资源类型"
|
placeholder="资源类型"
|
||||||
value={searchParams.resource_type}
|
value={searchParams.resource_type}
|
||||||
onChange={(value) => setSearchParams(prev => ({ ...prev, resource_type: value }))}
|
onChange={(value) => setSearchParams(prev => ({ ...prev, resource_type: value }))}
|
||||||
style={{ width: 120 }}
|
style={{ width: 110, borderRadius: 8 }}
|
||||||
allowClear
|
allowClear
|
||||||
options={[
|
options={[
|
||||||
{ value: 'image', label: '图片' },
|
{ value: 'image', label: '图片' },
|
||||||
@@ -431,21 +442,16 @@ const MaterialListPage: React.FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => { setCurrentPage(1); loadMaterialList(1, pageSize); }}
|
onClick={() => { setCurrentPage(1); loadMaterialList(1, pageSize); }}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, borderColor: 'transparent' }}
|
||||||
>
|
>
|
||||||
搜索
|
搜索
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: 12 }}>
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
<Button
|
<Button
|
||||||
icon={<RobotOutlined />}
|
icon={<RobotOutlined />}
|
||||||
onClick={() => navigate('/generated?filterType=private_portrait&portraitTab=aigc_virtual')}
|
onClick={() => navigate('/generated?filterType=private_portrait&portraitTab=aigc_virtual')}
|
||||||
style={{
|
style={{ borderRadius: 8, fontWeight: 600, borderColor: '#8b5cf6', color: '#7c3aed', background: '#f5f3ff' }}
|
||||||
borderRadius: 12,
|
|
||||||
fontSize: 14,
|
|
||||||
borderColor: '#8b5cf6',
|
|
||||||
color: '#7c3aed',
|
|
||||||
background: '#f5f3ff',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
私域虚拟人像库
|
私域虚拟人像库
|
||||||
</Button>
|
</Button>
|
||||||
@@ -454,16 +460,9 @@ const MaterialListPage: React.FC = () => {
|
|||||||
loading={pushTemplatesLoading}
|
loading={pushTemplatesLoading}
|
||||||
onClick={handleOpenPushModal}
|
onClick={handleOpenPushModal}
|
||||||
disabled={selectedRows.size === 0}
|
disabled={selectedRows.size === 0}
|
||||||
style={{
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none', color: '#fff' }}
|
||||||
borderRadius: 12,
|
|
||||||
fontSize: 14,
|
|
||||||
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
|
||||||
border: 'none',
|
|
||||||
color: '#fff',
|
|
||||||
boxShadow: '0 4px 14px rgba(99,102,241,0.3)',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
推送前测 ({selectedRows.size})
|
推送前测 {selectedRows.size > 0 && `(${selectedRows.size})`}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user