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