1、修改素材云样式
2、修改用户私域人像素材默认上限PRIVATE_PORTRAIT_DEFAULT_ASSET_LIMIT为50
This commit is contained in:
@@ -191,7 +191,7 @@ const AdminUsers: React.FC = () => {
|
|||||||
user_type: userType,
|
user_type: userType,
|
||||||
is_admin: userType === 'admin' ? (values.is_admin ?? false) : false,
|
is_admin: userType === 'admin' ? (values.is_admin ?? false) : false,
|
||||||
frontend_user_kind: values.frontend_user_kind || 'external',
|
frontend_user_kind: values.frontend_user_kind || 'external',
|
||||||
private_portrait_asset_limit: userType === 'frontend' ? Number(values.private_portrait_asset_limit ?? 5) : 0,
|
private_portrait_asset_limit: userType === 'frontend' ? Number(values.private_portrait_asset_limit ?? 50) : 0,
|
||||||
});
|
});
|
||||||
message.success('用户创建成功');
|
message.success('用户创建成功');
|
||||||
setCreateModal(false);
|
setCreateModal(false);
|
||||||
@@ -882,7 +882,7 @@ const AdminUsers: React.FC = () => {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
name="private_portrait_asset_limit"
|
name="private_portrait_asset_limit"
|
||||||
label="私域人像素材总量上限"
|
label="私域人像素材总量上限"
|
||||||
initialValue={5}
|
initialValue={50}
|
||||||
extra="0 表示关闭私域人像素材库;大于 0 表示开启并限制该用户所有私域人像素材总量。"
|
extra="0 表示关闭私域人像素材库;大于 0 表示开启并限制该用户所有私域人像素材总量。"
|
||||||
rules={[{ required: true, message: '请输入私域人像素材总量上限' }]}
|
rules={[{ required: true, message: '请输入私域人像素材总量上限' }]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from enum import Enum
|
|||||||
|
|
||||||
# 用户私域人像素材默认上限。users.private_portrait_asset_limit = 0 表示关闭模块;>0 表示启用并限制总素材量。
|
# 用户私域人像素材默认上限。users.private_portrait_asset_limit = 0 表示关闭模块;>0 表示启用并限制总素材量。
|
||||||
# 统计口径:真人 + 虚拟;图片 + 视频。音频当前业务暂不开放。
|
# 统计口径:真人 + 虚拟;图片 + 视频。音频当前业务暂不开放。
|
||||||
PRIVATE_PORTRAIT_DEFAULT_ASSET_LIMIT = 5
|
PRIVATE_PORTRAIT_DEFAULT_ASSET_LIMIT = 50
|
||||||
|
|
||||||
# 火山 Ark 私域素材 ProjectName:火山侧项目空间固定使用 default,并快照到各业务表 remote_project_name。
|
# 火山 Ark 私域素材 ProjectName:火山侧项目空间固定使用 default,并快照到各业务表 remote_project_name。
|
||||||
# 用户/项目隔离依赖本地 project_id 和火山返回的 Asset Group ID,不再动态拼接 ProjectName。
|
# 用户/项目隔离依赖本地 project_id 和火山返回的 Asset Group ID,不再动态拼接 ProjectName。
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class User(Base, TimestampMixin):
|
|||||||
|
|
||||||
# 私域人像素材总量限制。0 表示关闭模块;>0 表示启用并限制真人/虚拟、图片/视频素材总量。
|
# 私域人像素材总量限制。0 表示关闭模块;>0 表示启用并限制真人/虚拟、图片/视频素材总量。
|
||||||
private_portrait_asset_limit: Mapped[int] = mapped_column(
|
private_portrait_asset_limit: Mapped[int] = mapped_column(
|
||||||
Integer, default=5, server_default="5", nullable=False
|
Integer, default=50, server_default="50", nullable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class AdminUserOut(BaseModel):
|
|||||||
last_login_at: NaiveDatetimeOptional = None
|
last_login_at: NaiveDatetimeOptional = None
|
||||||
allowed_menus: list | None = None
|
allowed_menus: list | None = None
|
||||||
resource_capacity: ResourceCapacityUsageOut | None = None
|
resource_capacity: ResourceCapacityUsageOut | None = None
|
||||||
private_portrait_asset_limit: int = 5
|
private_portrait_asset_limit: int = 50
|
||||||
|
|
||||||
model_config = {"from_attributes": True}
|
model_config = {"from_attributes": True}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class CreateUserRequest(BaseModel):
|
|||||||
is_admin: bool = False
|
is_admin: bool = False
|
||||||
frontend_user_kind: str = Field(default="external", pattern="^(internal|external)$")
|
frontend_user_kind: str = Field(default="external", pattern="^(internal|external)$")
|
||||||
allowed_menus: list | None = None
|
allowed_menus: list | None = None
|
||||||
private_portrait_asset_limit: int = Field(default=5, ge=0, le=9999, description="私域人像素材总量限制,真人/虚拟、图片/视频共用,0 表示关闭")
|
private_portrait_asset_limit: int = Field(default=50, ge=0, le=9999, description="私域人像素材总量限制,真人/虚拟、图片/视频共用,0 表示关闭")
|
||||||
|
|
||||||
|
|
||||||
class UpdateFrontendUserKindRequest(BaseModel):
|
class UpdateFrontendUserKindRequest(BaseModel):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class UserOut(BaseModel):
|
|||||||
allowed_menus: list | None = None
|
allowed_menus: list | None = None
|
||||||
must_set_password: bool = False
|
must_set_password: bool = False
|
||||||
resource_capacity: ResourceCapacityUsageOut | None = None
|
resource_capacity: ResourceCapacityUsageOut | None = None
|
||||||
private_portrait_asset_limit: int = 5
|
private_portrait_asset_limit: int = 50
|
||||||
team_id: str | None = None
|
team_id: str | None = None
|
||||||
team_name: str | None = None
|
team_name: str | None = None
|
||||||
is_team_manager: bool = False
|
is_team_manager: bool = False
|
||||||
|
|||||||
@@ -122,7 +122,14 @@ const RealPersonLibraryPanel: React.FC = () => {
|
|||||||
<Typography.Title level={4} style={{ margin: 0 }}>真人素材库</Typography.Title>
|
<Typography.Title level={4} style={{ margin: 0 }}>真人素材库</Typography.Title>
|
||||||
<Typography.Text type="secondary">创建项目组时先完成真人认证,认证成功后项目组才会正式创建并可上传素材。</Typography.Text>
|
<Typography.Text type="secondary">创建项目组时先完成真人认证,认证成功后项目组才会正式创建并可上传素材。</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>创建项目组</Button>
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => setCreateOpen(true)}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}
|
||||||
|
>
|
||||||
|
创建项目组
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
<Col xs={24} lg={5}>
|
<Col xs={24} lg={5}>
|
||||||
|
|||||||
@@ -392,7 +392,14 @@ const VirtualMaterialPanel: React.FC = () => {
|
|||||||
<Typography.Title level={4} style={{ margin: 0 }}>虚拟素材库</Typography.Title>
|
<Typography.Title level={4} style={{ margin: 0 }}>虚拟素材库</Typography.Title>
|
||||||
<Typography.Text type="secondary">可提前上传虚拟人像素材,后续生成视频时可直接使用。</Typography.Text>
|
<Typography.Text type="secondary">可提前上传虚拟人像素材,后续生成视频时可直接使用。</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>创建项目组</Button>
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => setCreateOpen(true)}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}
|
||||||
|
>
|
||||||
|
创建项目组
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Row gutter={[10, 10]}>
|
<Row gutter={[10, 10]}>
|
||||||
<Col xs={24} lg={5} >
|
<Col xs={24} lg={5} >
|
||||||
@@ -446,11 +453,24 @@ const VirtualMaterialPanel: React.FC = () => {
|
|||||||
title={selectedProject ? selectedProject.name : '素材资产'}
|
title={selectedProject ? selectedProject.name : '素材资产'}
|
||||||
extra={(
|
extra={(
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{/* <Button icon={<ReloadOutlined />} onClick={() => { loadProjects(); loadAssets(assetPage, assetPageSize); }} loading={assetLoading}>刷新</Button> */}
|
<Button
|
||||||
<Button type="primary" icon={<UploadOutlined />} disabled={!selectedProjectId} onClick={() => setUploadOpen(true)}>上传图片/视频</Button>
|
type="primary"
|
||||||
|
icon={<UploadOutlined />}
|
||||||
|
disabled={!selectedProjectId}
|
||||||
|
onClick={() => setUploadOpen(true)}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, borderColor: 'transparent' }}
|
||||||
|
>
|
||||||
|
上传图片/视频
|
||||||
|
</Button>
|
||||||
{selectedProjectId && (
|
{selectedProjectId && (
|
||||||
<Popconfirm title="确认删除当前虚拟人像项目组吗?" onConfirm={handleDeleteProject}>
|
<Popconfirm title="确认删除当前虚拟人像项目组吗?" onConfirm={handleDeleteProject}>
|
||||||
<Button danger icon={<DeleteOutlined />}>删除项目组</Button>
|
<Button
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600 }}
|
||||||
|
>
|
||||||
|
删除项目组
|
||||||
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
@@ -464,14 +484,15 @@ const VirtualMaterialPanel: React.FC = () => {
|
|||||||
value={keyword}
|
value={keyword}
|
||||||
onChange={(e) => setKeyword(e.target.value)}
|
onChange={(e) => setKeyword(e.target.value)}
|
||||||
onSearch={() => loadAssets(1, assetPageSize)}
|
onSearch={() => loadAssets(1, assetPageSize)}
|
||||||
style={{ width: 240 }}
|
style={{ width: 240, borderRadius: 8 }}
|
||||||
|
enterButton
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
allowClear
|
allowClear
|
||||||
placeholder="素材状态"
|
placeholder="素材状态"
|
||||||
value={assetStatus}
|
value={assetStatus}
|
||||||
onChange={(value) => setAssetStatus(value)}
|
onChange={(value) => setAssetStatus(value)}
|
||||||
style={{ width: 150 }}
|
style={{ width: 140, borderRadius: 8 }}
|
||||||
options={[
|
options={[
|
||||||
{ value: 'Processing', label: '处理中' },
|
{ value: 'Processing', label: '处理中' },
|
||||||
{ value: 'Active', label: '可用' },
|
{ value: 'Active', label: '可用' },
|
||||||
@@ -483,13 +504,19 @@ const VirtualMaterialPanel: React.FC = () => {
|
|||||||
placeholder="素材类型"
|
placeholder="素材类型"
|
||||||
value={assetType}
|
value={assetType}
|
||||||
onChange={(value) => setAssetType(value)}
|
onChange={(value) => setAssetType(value)}
|
||||||
style={{ width: 130 }}
|
style={{ width: 120, borderRadius: 8 }}
|
||||||
options={[
|
options={[
|
||||||
{ value: 'Image', label: '图片' },
|
{ value: 'Image', label: '图片' },
|
||||||
{ value: 'Video', label: '视频' },
|
{ value: 'Video', label: '视频' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Button onClick={() => loadAssets(1, assetPageSize)}>筛选</Button>
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => loadAssets(1, assetPageSize)}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, borderColor: 'transparent' }}
|
||||||
|
>
|
||||||
|
筛选
|
||||||
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<Spin spinning={assetLoading}>
|
<Spin spinning={assetLoading}>
|
||||||
|
|||||||
@@ -173,45 +173,93 @@ const UploadResourceHistoryPanel: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'center', marginBottom: 16, flexWrap: 'wrap' }}>
|
{/* 统一筛选栏:媒体类型 + 搜索 + 批量操作 */}
|
||||||
<Space wrap>
|
<div style={{
|
||||||
<Select
|
display: 'flex',
|
||||||
value={resourceType}
|
flexWrap: 'wrap',
|
||||||
style={{ width: 120 }}
|
justifyContent: 'space-between',
|
||||||
options={[
|
alignItems: 'center',
|
||||||
{ label: '全部', value: '' },
|
gap: 12,
|
||||||
{ label: '图片', value: 'image' },
|
marginBottom: 16,
|
||||||
{ label: '视频', value: 'video' },
|
padding: '12px 16px',
|
||||||
{ label: '音频', value: 'audio' },
|
borderRadius: 12,
|
||||||
]}
|
background: '#fff',
|
||||||
onChange={(value) => {
|
border: '1px solid #f0f0f5',
|
||||||
setResourceType(value);
|
}}>
|
||||||
setPage(1);
|
{/* 左侧:媒体类型按钮 */}
|
||||||
}}
|
<Space size={8} wrap>
|
||||||
/>
|
<Typography.Text style={{ color: '#94a3b8', fontSize: 14, marginRight: 4 }}>媒体类型:</Typography.Text>
|
||||||
<Input
|
<Button
|
||||||
|
type={resourceType === '' ? 'primary' : 'default'}
|
||||||
|
onClick={() => { setResourceType(''); setPage(1); }}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, borderColor: resourceType === '' ? 'transparent' : '#e2e8f0', color: resourceType === '' ? '#fff' : '#64748b' }}
|
||||||
|
>
|
||||||
|
全部
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={resourceType === 'image' ? 'primary' : 'default'}
|
||||||
|
onClick={() => { setResourceType('image'); setPage(1); }}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, borderColor: resourceType === 'image' ? 'transparent' : '#e2e8f0', color: resourceType === 'image' ? '#fff' : '#64748b' }}
|
||||||
|
>
|
||||||
|
图片
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={resourceType === 'video' ? 'primary' : 'default'}
|
||||||
|
onClick={() => { setResourceType('video'); setPage(1); }}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, borderColor: resourceType === 'video' ? 'transparent' : '#e2e8f0', color: resourceType === 'video' ? '#fff' : '#64748b' }}
|
||||||
|
>
|
||||||
|
视频
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={resourceType === 'audio' ? 'primary' : 'default'}
|
||||||
|
onClick={() => { setResourceType('audio'); setPage(1); }}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, borderColor: resourceType === 'audio' ? 'transparent' : '#e2e8f0', color: resourceType === 'audio' ? '#fff' : '#64748b' }}
|
||||||
|
>
|
||||||
|
音频
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
{/* 右侧:搜索 + 批量操作 */}
|
||||||
|
<Space size={8} wrap>
|
||||||
|
<Input.Search
|
||||||
allowClear
|
allowClear
|
||||||
value={keyword}
|
value={keyword}
|
||||||
prefix={<SearchOutlined />}
|
|
||||||
placeholder="搜索文件名或URL"
|
placeholder="搜索文件名或URL"
|
||||||
style={{ width: 240 }}
|
|
||||||
onChange={(e) => setKeyword(e.target.value)}
|
onChange={(e) => setKeyword(e.target.value)}
|
||||||
onPressEnter={() => {
|
onSearch={() => { setPage(1); load(); }}
|
||||||
setPage(1);
|
style={{ width: 240, borderRadius: 8 }}
|
||||||
load();
|
enterButton
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{/* <Button icon={<ReloadOutlined />} onClick={load}>刷新</Button> */}
|
|
||||||
</Space>
|
|
||||||
<Space>
|
|
||||||
{batchMode ? (
|
{batchMode ? (
|
||||||
<>
|
<>
|
||||||
<Button onClick={selectAll}>{selectedIds.size && selectedIds.size === allItems.length ? '取消全选' : '全选'}</Button>
|
<Button
|
||||||
<Button danger icon={<DeleteOutlined />} onClick={handleBatchDelete}>批量删除 ({selectedIds.size})</Button>
|
onClick={selectAll}
|
||||||
<Button onClick={() => { setBatchMode(false); setSelectedIds(new Set()); }}>取消批量操作</Button>
|
style={{ borderRadius: 8, fontWeight: 600, background: '#f8f9fc', border: '1px solid #e2e8f0', color: '#334155' }}
|
||||||
|
>
|
||||||
|
{selectedIds.size && selectedIds.size === allItems.length ? '取消全选' : '全选'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
onClick={handleBatchDelete}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #ef4444, #dc2626)', border: 'none', color: '#fff' }}
|
||||||
|
>
|
||||||
|
批量删除 {selectedIds.size > 0 && `(${selectedIds.size})`}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => { setBatchMode(false); setSelectedIds(new Set()); }}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, background: '#f8f9fc', border: '1px solid #e2e8f0', color: '#64748b' }}
|
||||||
|
>
|
||||||
|
取消选择
|
||||||
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Button onClick={() => setBatchMode(true)}>批量操作</Button>
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => setBatchMode(true)}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}
|
||||||
|
>
|
||||||
|
批量操作
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -911,88 +911,25 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
}, [filterType, filterMedia]);
|
}, [filterType, filterMedia]);
|
||||||
return (
|
return (
|
||||||
<div className="content_box" >
|
<div className="content_box" >
|
||||||
{/* 顶部:标签切换 + 批量操作按钮 */}
|
{/* 顶部:标签切换 */}
|
||||||
<div style={{
|
<Tabs
|
||||||
display: 'flex',
|
activeKey={filterType}
|
||||||
flexWrap: 'wrap',
|
onChange={(key) => {
|
||||||
justifyContent: 'space-between',
|
setFilterType(key as typeof filterType);
|
||||||
alignItems: 'center',
|
setIsSelectionMode(false);
|
||||||
gap: 12,
|
setSelectedItems(new Set());
|
||||||
marginBottom: 12,
|
}}
|
||||||
}}>
|
items={[
|
||||||
<Tabs
|
{ key: 'project', label: <span><FolderOpenOutlined /> 项目记录</span> },
|
||||||
activeKey={filterType}
|
{ key: 'creation', label: <span><FileTextOutlined /> 创作记录</span> },
|
||||||
onChange={(key) => {
|
{ key: 'hot_opening_replicate', label: <span><StarOutlined /> 爆款复刻</span> },
|
||||||
setFilterType(key as typeof filterType);
|
{ key: 'shot_replicate', label: <span><PlayCircleOutlined /> 拆镜复刻</span> },
|
||||||
setIsSelectionMode(false);
|
{ key: 'private_portrait', label: <span><UserOutlined /> 私域素材库</span> },
|
||||||
setSelectedItems(new Set());
|
{ key: 'upload_resource', label: <span><UploadOutlined /> 历史素材</span> },
|
||||||
}}
|
]}
|
||||||
items={[
|
size="middle"
|
||||||
{ key: 'project', label: <span><FolderOpenOutlined /> 项目记录</span> },
|
style={{ marginBottom: 12 }}
|
||||||
{ key: 'creation', label: <span><FileTextOutlined /> 创作记录</span> },
|
/>
|
||||||
{ key: 'hot_opening_replicate', label: <span><StarOutlined /> 爆款复刻</span> },
|
|
||||||
{ key: 'shot_replicate', label: <span><PlayCircleOutlined /> 拆镜复刻</span> },
|
|
||||||
{ key: 'private_portrait', label: <span><UserOutlined /> 私域素材库</span> },
|
|
||||||
{ key: 'upload_resource', label: <span><UploadOutlined /> 历史素材</span> },
|
|
||||||
]}
|
|
||||||
size="middle"
|
|
||||||
/>
|
|
||||||
{filterType !== 'private_portrait' && filterType !== 'upload_resource' && (
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
||||||
{isSelectionMode ? (
|
|
||||||
<Space size={8}>
|
|
||||||
<Button
|
|
||||||
icon={selectedItems.size === recordlist.reduce((sum: number, group: any) => sum + group.items.length, 0) && recordlist.length > 0 ? <ClearOutlined /> : <CheckOutlined />}
|
|
||||||
onClick={selectedItems.size === recordlist.reduce((sum: number, group: any) => sum + group.items.length, 0) && recordlist.length > 0 ? () => { setSelectedItems(new Set()); } : handleSelectAll}
|
|
||||||
style={{ borderRadius: 8, fontWeight: 600, background: '#f8f9fc', border: '1px solid #e2e8f0', color: '#334155' }}
|
|
||||||
>
|
|
||||||
{selectedItems.size === recordlist.reduce((sum: number, group: any) => sum + group.items.length, 0) && recordlist.length > 0 ? '取消全选' : '全选'}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => { setIsSelectionMode(false); setSelectedItems(new Set()); }}
|
|
||||||
style={{ borderRadius: 8, fontWeight: 600, background: '#f8f9fc', border: '1px solid #e2e8f0', color: '#64748b' }}
|
|
||||||
>
|
|
||||||
取消选择
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
danger
|
|
||||||
icon={<DeleteOutlined />}
|
|
||||||
onClick={handleDeleteSelected}
|
|
||||||
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #ef4444, #dc2626)', border: 'none', color: '#fff' }}
|
|
||||||
>
|
|
||||||
删除 {selectedItems.size > 0 && `(${selectedItems.size})`}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
icon={<DownloadOutlined />}
|
|
||||||
onClick={handleDownloadSelected}
|
|
||||||
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1, #8b5cf6)', border: 'none', color: '#fff' }}
|
|
||||||
>
|
|
||||||
下载 {selectedItems.size > 0 && `(${selectedItems.size})`}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<UploadOutlined />}
|
|
||||||
onClick={handleBatchUploadSelected}
|
|
||||||
loading={uploading}
|
|
||||||
disabled={uploading || selectedItems.size === 0}
|
|
||||||
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1, #8b5cf6)', border: 'none', color: '#fff' }}
|
|
||||||
>
|
|
||||||
{uploading ? '推送中...' : `推送至账户 ${selectedItems.size > 0 ? `(${selectedItems.size})` : ''}`}
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
) : (
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
icon={<UploadOutlined />}
|
|
||||||
onClick={() => setIsSelectionMode(true)}
|
|
||||||
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}
|
|
||||||
>
|
|
||||||
批量操作
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{filterType === 'private_portrait' ? (
|
{filterType === 'private_portrait' ? (
|
||||||
<PrivatePortraitLibraryPanel />
|
<PrivatePortraitLibraryPanel />
|
||||||
) : filterType === 'upload_resource' ? (
|
) : filterType === 'upload_resource' ? (
|
||||||
@@ -1067,6 +1004,55 @@ const GeneratedRecord: React.FC = () => {
|
|||||||
>
|
>
|
||||||
推送任务历史
|
推送任务历史
|
||||||
</Button>
|
</Button>
|
||||||
|
{/* 批量操作按钮 */}
|
||||||
|
{filterType !== 'private_portrait' && filterType !== 'upload_resource' && (
|
||||||
|
isSelectionMode ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onClick={selectedItems.size === recordlist.reduce((sum: number, group: any) => sum + group.items.length, 0) && recordlist.length > 0 ? () => { setSelectedItems(new Set()); } : handleSelectAll}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, background: '#f8f9fc', border: '1px solid #e2e8f0', color: '#334155' }}
|
||||||
|
>
|
||||||
|
{selectedItems.size === recordlist.reduce((sum: number, group: any) => sum + group.items.length, 0) && recordlist.length > 0 ? '取消全选' : '全选'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => { setIsSelectionMode(false); setSelectedItems(new Set()); }}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, background: '#f8f9fc', border: '1px solid #e2e8f0', color: '#64748b' }}
|
||||||
|
>
|
||||||
|
取消选择
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
onClick={handleDeleteSelected}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #ef4444, #dc2626)', border: 'none', color: '#fff' }}
|
||||||
|
>
|
||||||
|
删除 {selectedItems.size > 0 && `(${selectedItems.size})`}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleDownloadSelected}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1, #8b5cf6)', border: 'none', color: '#fff' }}
|
||||||
|
>
|
||||||
|
下载 {selectedItems.size > 0 && `(${selectedItems.size})`}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleBatchUploadSelected}
|
||||||
|
loading={uploading}
|
||||||
|
disabled={uploading || selectedItems.size === 0}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1, #8b5cf6)', border: 'none', color: '#fff' }}
|
||||||
|
>
|
||||||
|
{uploading ? '推送中...' : `推送至账户 ${selectedItems.size > 0 ? `(${selectedItems.size})` : ''}`}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => setIsSelectionMode(true)}
|
||||||
|
style={{ borderRadius: 8, fontWeight: 600, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', border: 'none' }}
|
||||||
|
>
|
||||||
|
批量操作
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
{/* Content area */}
|
{/* Content area */}
|
||||||
|
|||||||
Reference in New Issue
Block a user