feat: 增加了换背景和换装的垫图
This commit is contained in:
6
bun.lock
6
bun.lock
@@ -8,6 +8,7 @@
|
|||||||
"@radix-ui/react-checkbox": "^1.3.2",
|
"@radix-ui/react-checkbox": "^1.3.2",
|
||||||
"@radix-ui/react-dialog": "^1.1.14",
|
"@radix-ui/react-dialog": "^1.1.14",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
|
"@radix-ui/react-popover": "^1.1.14",
|
||||||
"@radix-ui/react-select": "^2.2.5",
|
"@radix-ui/react-select": "^2.2.5",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"lucide-react": "^0.523.0",
|
"lucide-react": "^0.523.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
@@ -196,6 +198,8 @@
|
|||||||
|
|
||||||
"@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="],
|
"@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="],
|
||||||
|
|
||||||
|
"@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw=="],
|
||||||
|
|
||||||
"@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.7", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ=="],
|
"@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.7", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ=="],
|
||||||
|
|
||||||
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="],
|
"@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="],
|
||||||
@@ -444,6 +448,8 @@
|
|||||||
|
|
||||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
|
"dayjs": ["dayjs@1.11.13", "", {}, "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="],
|
||||||
|
|
||||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||||
|
|
||||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@radix-ui/react-checkbox": "^1.3.2",
|
"@radix-ui/react-checkbox": "^1.3.2",
|
||||||
"@radix-ui/react-dialog": "^1.1.14",
|
"@radix-ui/react-dialog": "^1.1.14",
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
|
"@radix-ui/react-popover": "^1.1.14",
|
||||||
"@radix-ui/react-select": "^2.2.5",
|
"@radix-ui/react-select": "^2.2.5",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
|||||||
92
src/components/block/BgPaddingMultiSelect.tsx
Normal file
92
src/components/block/BgPaddingMultiSelect.tsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
export interface BgPaddingMultiValueItem {
|
||||||
|
file: File;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
export type BgPaddingMultiValue = BgPaddingMultiValueItem[];
|
||||||
|
|
||||||
|
export interface BgPaddingMultiSelectProps {
|
||||||
|
value: BgPaddingMultiValue;
|
||||||
|
onChange: (value: BgPaddingMultiValue) => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BgPaddingMultiSelect: React.FC<BgPaddingMultiSelectProps> = ({ value, onChange, className }) => {
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
// 触发文件选择
|
||||||
|
const handleAddClick = () => {
|
||||||
|
fileInputRef.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理多文件选择
|
||||||
|
const handleFilesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const files = Array.from(e.target.files || []);
|
||||||
|
if (files.length > 0) {
|
||||||
|
const newItems = files.map(file => ({
|
||||||
|
file,
|
||||||
|
text: file.name.replace(/\.[^.]+$/, ''),
|
||||||
|
}));
|
||||||
|
onChange([...value, ...newItems]);
|
||||||
|
// 清空 input 以便连续选择同一文件
|
||||||
|
e.target.value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 单项描述变更
|
||||||
|
const handleTextChange = (idx: number, text: string) => {
|
||||||
|
const newArr = value.map((item, i) => (i === idx ? { ...item, text } : item));
|
||||||
|
onChange(newArr);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移除某项
|
||||||
|
const handleRemove = (idx: number) => {
|
||||||
|
const newArr = value.filter((_, i) => i !== idx);
|
||||||
|
onChange(newArr);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('space-y-4 flex flex-col gap-2', className)}>
|
||||||
|
<Button type='button' onClick={handleAddClick} variant='outline'>
|
||||||
|
添加图片
|
||||||
|
</Button>
|
||||||
|
<Input type='file' accept='image/*' multiple ref={fileInputRef} style={{ display: 'none' }} onChange={handleFilesChange} />
|
||||||
|
<div className='space-y-6'>
|
||||||
|
{value.map((item, idx) => (
|
||||||
|
<div key={idx} className='border rounded-md p-3 flex flex-col gap-2 relative bg-muted/30'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<img
|
||||||
|
src={URL.createObjectURL(item.file)}
|
||||||
|
alt={item.text || item.file.name}
|
||||||
|
className='h-12 w-auto rounded-md object-cover border'
|
||||||
|
style={{ maxWidth: 96 }}
|
||||||
|
/>
|
||||||
|
<span className='text-sm text-muted-foreground wrap-anywhere'>{item.file.name}</span>
|
||||||
|
<Button type='button' size='sm' variant='ghost' onClick={() => handleRemove(idx)}>
|
||||||
|
移除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Label htmlFor={`bg-multi-text-${idx}`}>图片描述/备注</Label>
|
||||||
|
<textarea
|
||||||
|
id={`bg-multi-text-${idx}`}
|
||||||
|
className={cn(
|
||||||
|
'w-full min-h-[60px] rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||||
|
'resize-vertical'
|
||||||
|
)}
|
||||||
|
value={item.text}
|
||||||
|
onChange={e => handleTextChange(idx, e.target.value)}
|
||||||
|
placeholder='请输入图片描述或备注...'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BgPaddingMultiSelect;
|
||||||
59
src/components/block/BgPaddingSelect.tsx
Normal file
59
src/components/block/BgPaddingSelect.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import React, { useRef } from 'react';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
export interface BgPaddingValue {
|
||||||
|
file?: File;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BgPaddingSelectProps {
|
||||||
|
value: BgPaddingValue;
|
||||||
|
onChange: (value: BgPaddingValue) => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BgPaddingSelect: React.FC<BgPaddingSelectProps> = ({ value, onChange, className }) => {
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
let newText = value.text;
|
||||||
|
if (!value.text) {
|
||||||
|
// 自动填充文件名(去除后缀)
|
||||||
|
const name = file.name.replace(/\.[^.]+$/, '');
|
||||||
|
newText = name;
|
||||||
|
}
|
||||||
|
onChange({ file, text: newText });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
onChange({ ...value, text: e.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('space-y-4', className)}>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor='bg-file'>选择本地图片</Label>
|
||||||
|
<Input id='bg-file' type='file' accept='image/*' ref={fileInputRef} onChange={handleFileChange} />
|
||||||
|
{value.file && <div className='mt-2 text-sm text-muted-foreground'>已选择:{value.file.name}</div>}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor='bg-text'>图片描述/备注</Label>
|
||||||
|
<textarea
|
||||||
|
id='bg-text'
|
||||||
|
className={cn(
|
||||||
|
'w-full min-h-[80px] rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||||
|
'resize-vertical'
|
||||||
|
)}
|
||||||
|
value={value.text}
|
||||||
|
onChange={handleTextChange}
|
||||||
|
placeholder='请输入图片描述或备注...'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { Button } from '../ui/button';
|
||||||
|
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';
|
||||||
|
import { X } from 'lucide-react';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface CascaderNode {
|
interface CascaderNode {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -26,8 +31,6 @@ const getLeafPaths = (tree: CascaderNode[], prefix: string[] = []): string[][] =
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CascaderMultiSelect: React.FC<CascaderMultiSelectProps> = ({ options, value, onChange }) => {
|
const CascaderMultiSelect: React.FC<CascaderMultiSelectProps> = ({ options, value, onChange }) => {
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
// 判断某路径是否已选
|
// 判断某路径是否已选
|
||||||
const isSelected = (path: string[]) => value.some(v => v.join('/') === path.join('/'));
|
const isSelected = (path: string[]) => value.some(v => v.join('/') === path.join('/'));
|
||||||
|
|
||||||
@@ -47,12 +50,17 @@ const CascaderMultiSelect: React.FC<CascaderMultiSelectProps> = ({ options, valu
|
|||||||
|
|
||||||
// 渲染 tag
|
// 渲染 tag
|
||||||
const renderTags = () => (
|
const renderTags = () => (
|
||||||
<div className='flex flex-wrap gap-1'>
|
<div className={cn(value.length > 0 ? 'mb-2' : '', 'flex flex-wrap gap-2')}>
|
||||||
{value.map(v => (
|
{value.map(v => (
|
||||||
<span key={v.join('/')} className='bg-primary/10 text-primary px-2 py-0.5 rounded text-xs flex items-center gap-1'>
|
<span key={v.join('/')} className='inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary'>
|
||||||
{v.join(' / ')}
|
{v.join(' / ')}
|
||||||
<button type='button' className='ml-1 text-xs hover:text-red-500' onClick={() => handleRemove(v)} style={{ lineHeight: 1 }}>
|
<button
|
||||||
×
|
type='button'
|
||||||
|
className='ml-1 rounded-full p-0.5 hover:bg-red-100 hover:text-red-600 transition-colors'
|
||||||
|
onClick={() => handleRemove(v)}
|
||||||
|
aria-label='移除'
|
||||||
|
>
|
||||||
|
<X className='w-3 h-3' />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
@@ -77,8 +85,8 @@ const CascaderMultiSelect: React.FC<CascaderMultiSelectProps> = ({ options, valu
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<li key={path.join('/')} className='flex items-center gap-1 py-1 pl-2 hover:bg-muted/50 rounded'>
|
<li key={path.join('/')} className='flex items-center gap-1 py-1 pl-2 hover:bg-muted/50 rounded'>
|
||||||
<label className='flex items-center gap-1 cursor-pointer w-full'>
|
<label className='flex items-center gap-2 cursor-pointer w-full text-sm'>
|
||||||
<input type='checkbox' checked={isSelected(path)} onChange={() => handleSelect(path)} className='accent-primary' />
|
<Checkbox checked={isSelected(path)} onCheckedChange={() => handleSelect(path)} className='border-muted-foreground/20' />
|
||||||
<span>{node.title}</span>
|
<span>{node.title}</span>
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
@@ -88,30 +96,19 @@ const CascaderMultiSelect: React.FC<CascaderMultiSelectProps> = ({ options, valu
|
|||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 渲染全部展开的级联下拉
|
|
||||||
const renderCascader = () => (
|
|
||||||
<div
|
|
||||||
className='absolute z-50 bg-white border rounded shadow-lg p-2 mt-1 min-w-[220px] max-h-96 overflow-auto'
|
|
||||||
style={{ boxShadow: '0 4px 24px 0 rgba(0,0,0,0.10)' }}
|
|
||||||
>
|
|
||||||
<style>{`
|
|
||||||
.custom-scrollbar::-webkit-scrollbar { width: 6px; background: transparent; }
|
|
||||||
.custom-scrollbar::-webkit-scrollbar-thumb { background: #e5e7eb; border-radius: 3px; }
|
|
||||||
`}</style>
|
|
||||||
<div className='custom-scrollbar'>{renderTree(options)}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<div className='flex flex-col '>
|
||||||
<div className='flex items-center gap-2 flex-wrap'>
|
{renderTags()}
|
||||||
{renderTags()}
|
<Popover>
|
||||||
<button type='button' className='border px-2 py-1 rounded text-xs bg-white hover:bg-muted' onClick={() => setOpen(v => !v)}>
|
<PopoverTrigger asChild>
|
||||||
选择场景
|
<Button type='button' variant='outline'>
|
||||||
</button>
|
选择场景
|
||||||
</div>
|
</Button>
|
||||||
{open && renderCascader()}
|
</PopoverTrigger>
|
||||||
{open && <div className='fixed inset-0 z-40' onClick={() => setOpen(false)} />}
|
<PopoverContent className='p-2'>
|
||||||
|
<div className='min-w-[220px] max-h-96 overflow-auto'>{renderTree(options)}</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
46
src/components/ui/popover.tsx
Normal file
46
src/components/ui/popover.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Popover({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||||
|
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||||
|
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverContent({
|
||||||
|
className,
|
||||||
|
align = "center",
|
||||||
|
sideOffset = 4,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
data-slot="popover-content"
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverAnchor({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||||
|
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { BgPaddingMultiSelect, type BgPaddingMultiValue } from '@/components/block/BgPaddingMultiSelect';
|
||||||
import CascaderMultiSelect from '@/components/block/CascaderMultiSelect';
|
import CascaderMultiSelect from '@/components/block/CascaderMultiSelect';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardAction, CardContent, CardHeader } from '@/components/ui/card';
|
import { Card, CardAction, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
|
||||||
interface ClothingCardProps {
|
interface ClothingCardProps {
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
@@ -11,9 +14,24 @@ interface ClothingCardProps {
|
|||||||
scenes?: string[][];
|
scenes?: string[][];
|
||||||
onScenesChange?: (scenes: string[][]) => void;
|
onScenesChange?: (scenes: string[][]) => void;
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
|
bgMode: 'custom' | 'scene';
|
||||||
|
onChangeBgMode: (bgMode: 'custom' | 'scene') => void;
|
||||||
|
paddingList?: BgPaddingMultiValue;
|
||||||
|
onPaddingListChange?: (paddingList: BgPaddingMultiValue) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClothingCard: React.FC<ClothingCardProps> = ({ imageUrl, onRemove, scenesOptions = [], scenes = [], onScenesChange, fileName }) => {
|
const ClothingCard: React.FC<ClothingCardProps> = ({
|
||||||
|
imageUrl,
|
||||||
|
onRemove,
|
||||||
|
scenesOptions = [],
|
||||||
|
scenes = [],
|
||||||
|
onScenesChange,
|
||||||
|
fileName,
|
||||||
|
bgMode,
|
||||||
|
onChangeBgMode,
|
||||||
|
paddingList,
|
||||||
|
onPaddingListChange,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Card className='relative md:flex-row gap-6 group hover:shadow-lg transition-shadow'>
|
<Card className='relative md:flex-row gap-6 group hover:shadow-lg transition-shadow'>
|
||||||
<CardHeader className='p-0'>
|
<CardHeader className='p-0'>
|
||||||
@@ -36,11 +54,27 @@ const ClothingCard: React.FC<ClothingCardProps> = ({ imageUrl, onRemove, scenesO
|
|||||||
{fileName && <div className='mt-2 text-xs text-muted-foreground break-all max-w-[200px]'>{fileName}</div>}
|
{fileName && <div className='mt-2 text-xs text-muted-foreground break-all max-w-[200px]'>{fileName}</div>}
|
||||||
</div>
|
</div>
|
||||||
<div className='flex-1 flex flex-col gap-6'>
|
<div className='flex-1 flex flex-col gap-6'>
|
||||||
{/* 场景级联多选 */}
|
{/* 场景级联多选/自定义上传切换 */}
|
||||||
<div className='w-full flex flex-row items-center gap-2'>
|
<div className='w-full flex flex-row items-start gap-4'>
|
||||||
<label className='w-16 text-right flex-shrink-0'>场景</label>
|
<label className='w-16 text-right flex-shrink-0'>场景</label>
|
||||||
<div className='flex-1'>
|
<div className='flex flex-col gap-2 min-h-40 w-full overflow-y-auto'>
|
||||||
<CascaderMultiSelect options={scenesOptions} value={scenes || []} onChange={onScenesChange || (() => {})} />
|
<div className='flex items-center gap-2 h-6'>
|
||||||
|
<Switch
|
||||||
|
id='custom-mode-switch'
|
||||||
|
checked={bgMode === 'custom'}
|
||||||
|
onCheckedChange={() => onChangeBgMode(bgMode === 'custom' ? 'scene' : 'custom')}
|
||||||
|
/>
|
||||||
|
<Label htmlFor='custom-mode-switch'>{bgMode === 'custom' ? '自定义垫图' : '内置场景'}</Label>
|
||||||
|
</div>
|
||||||
|
<div className='w-full flex flex-row items-center gap-2'>
|
||||||
|
<div className='flex-1'>
|
||||||
|
{bgMode === 'scene' ? (
|
||||||
|
<CascaderMultiSelect options={scenesOptions} value={scenes || []} onChange={onScenesChange || (() => {})} />
|
||||||
|
) : (
|
||||||
|
<BgPaddingMultiSelect value={paddingList || []} onChange={onPaddingListChange || (() => {})} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ import React, { useEffect, useRef, useState } from 'react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import ClothingCard from './components/ClothingCard';
|
import ClothingCard from './components/ClothingCard';
|
||||||
import { api, type Body_local_cloud_async_change_bg_api_v2_cloud_batch_change_bg_post } from '@/api';
|
import {
|
||||||
|
api,
|
||||||
|
type Body_async_cloud_change_bg_v3_api_v3_cloud_batch_change_bg_post,
|
||||||
|
type Body_async_cloud_gen_images_v3_api_v3_cloud_batch_edit_images_post,
|
||||||
|
type Body_local_cloud_async_change_bg_api_v2_cloud_batch_change_bg_post,
|
||||||
|
} from '@/api';
|
||||||
|
import { type BgPaddingMultiValue } from '@/components/block/BgPaddingMultiSelect';
|
||||||
|
|
||||||
// 五级联动的 tag schema
|
// 五级联动的 tag schema
|
||||||
const tagSchema = z.object({
|
const tagSchema = z.object({
|
||||||
@@ -20,6 +26,8 @@ const formSchema = z.object({
|
|||||||
|
|
||||||
interface TagForm {
|
interface TagForm {
|
||||||
scenes?: string[][];
|
scenes?: string[][];
|
||||||
|
paddingList?: BgPaddingMultiValue;
|
||||||
|
bgMode?: 'custom' | 'scene';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormValues {
|
interface FormValues {
|
||||||
@@ -93,6 +101,20 @@ const TryOnPage: React.FC = () => {
|
|||||||
form.setValue('tags', newTags);
|
form.setValue('tags', newTags);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 自定义上传
|
||||||
|
const handlePaddingListChange = (idx: number, paddingList: BgPaddingMultiValue) => {
|
||||||
|
const newTags = [...form.getValues('tags')];
|
||||||
|
newTags[idx] = { ...newTags[idx], paddingList };
|
||||||
|
form.setValue('tags', newTags);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换背景模式
|
||||||
|
const handleBgModeChange = (idx: number, bgMode: 'custom' | 'scene') => {
|
||||||
|
const newTags = [...form.getValues('tags')];
|
||||||
|
newTags[idx] = { ...newTags[idx], bgMode };
|
||||||
|
form.setValue('tags', newTags);
|
||||||
|
};
|
||||||
|
|
||||||
// 递归查找 prompt
|
// 递归查找 prompt
|
||||||
function findPromptByPath(tree: any[], path: string[]): string {
|
function findPromptByPath(tree: any[], path: string[]): string {
|
||||||
let node: any = null;
|
let node: any = null;
|
||||||
@@ -114,19 +136,34 @@ const TryOnPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
for (let i = 0; i < values.clothing_images.length; i++) {
|
for (let i = 0; i < values.clothing_images.length; i++) {
|
||||||
const tag = values.tags[i];
|
const tag = values.tags[i];
|
||||||
const scenesArr: string[][] = tag.scenes || [];
|
const bgMode = tag.bgMode || 'scene';
|
||||||
for (const path of scenesArr) {
|
if (bgMode === 'custom') {
|
||||||
const scene = {
|
// 自定义模式
|
||||||
title: path.join('/'),
|
const paddingList = tag.paddingList || [];
|
||||||
prompt: findPromptByPath(scenesOptions, path),
|
for (const padding of paddingList) {
|
||||||
};
|
const formData: Body_async_cloud_change_bg_v3_api_v3_cloud_batch_change_bg_post = {
|
||||||
const formData: Body_local_cloud_async_change_bg_api_v2_cloud_batch_change_bg_post = {
|
clothing_images: [values.clothing_images[i]],
|
||||||
clothing_images: [values.clothing_images[i]],
|
padding_images: [padding.file],
|
||||||
scenes_list: JSON.stringify([{ title: scene.title, prompt: scene.prompt }]),
|
bg_prompts: padding.text,
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line no-await-in-loop
|
const res = await api.ImageGenerateService.asyncCloudChangeBgV3ApiV3CloudBatchChangeBgPost({ formData });
|
||||||
const res = await api.ImageGenerateService.localCloudAsyncChangeBgApiV2CloudBatchChangeBgPost({ formData });
|
results.push(res);
|
||||||
results.push(res);
|
}
|
||||||
|
} else {
|
||||||
|
// 场景模式
|
||||||
|
const scenesArr: string[][] = tag.scenes || [];
|
||||||
|
for (const path of scenesArr) {
|
||||||
|
const scene = {
|
||||||
|
title: path.join('/'),
|
||||||
|
prompt: findPromptByPath(scenesOptions, path),
|
||||||
|
};
|
||||||
|
const formData: Body_local_cloud_async_change_bg_api_v2_cloud_batch_change_bg_post = {
|
||||||
|
clothing_images: [values.clothing_images[i]],
|
||||||
|
scenes_list: JSON.stringify([{ title: scene.title, prompt: scene.prompt }]),
|
||||||
|
};
|
||||||
|
const res = await api.ImageGenerateService.localCloudAsyncChangeBgApiV2CloudBatchChangeBgPost({ formData });
|
||||||
|
results.push(res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setResult(results);
|
setResult(results);
|
||||||
@@ -160,6 +197,10 @@ const TryOnPage: React.FC = () => {
|
|||||||
scenes={tag.scenes || []}
|
scenes={tag.scenes || []}
|
||||||
onScenesChange={scenes => handleScenesChange(idx, scenes)}
|
onScenesChange={scenes => handleScenesChange(idx, scenes)}
|
||||||
fileName={fileName}
|
fileName={fileName}
|
||||||
|
paddingList={tag.paddingList || []}
|
||||||
|
onPaddingListChange={paddingList => handlePaddingListChange(idx, paddingList)}
|
||||||
|
bgMode={tag.bgMode || 'scene'}
|
||||||
|
onChangeBgMode={bgMode => handleBgModeChange(idx, bgMode)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import { BgPaddingMultiSelect, type BgPaddingMultiValue } from '@/components/block/BgPaddingMultiSelect';
|
||||||
import CascaderMultiSelect from '@/components/block/CascaderMultiSelect';
|
import CascaderMultiSelect from '@/components/block/CascaderMultiSelect';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardAction, CardContent, CardHeader } from '@/components/ui/card';
|
import { Card, CardAction, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@@ -33,6 +36,11 @@ interface ClothingCardProps {
|
|||||||
scenes?: string[][];
|
scenes?: string[][];
|
||||||
onScenesChange?: (scenes: string[][]) => void;
|
onScenesChange?: (scenes: string[][]) => void;
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
|
|
||||||
|
bgMode: 'custom' | 'scene';
|
||||||
|
onChangeBgMode: (bgMode: 'custom' | 'scene') => void;
|
||||||
|
paddingList?: BgPaddingMultiValue;
|
||||||
|
onPaddingListChange?: (paddingList: BgPaddingMultiValue) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LABELS = ['性别', '类别', '尺寸', '材质', '颜色'];
|
const LABELS = ['性别', '类别', '尺寸', '材质', '颜色'];
|
||||||
@@ -49,6 +57,10 @@ const ClothingCard: React.FC<ClothingCardProps> = ({
|
|||||||
scenes = [],
|
scenes = [],
|
||||||
onScenesChange,
|
onScenesChange,
|
||||||
fileName,
|
fileName,
|
||||||
|
bgMode,
|
||||||
|
onChangeBgMode,
|
||||||
|
paddingList,
|
||||||
|
onPaddingListChange,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Card className='relative md:flex-row gap-6 group hover:shadow-lg transition-shadow'>
|
<Card className='relative md:flex-row gap-6 group hover:shadow-lg transition-shadow'>
|
||||||
@@ -100,11 +112,29 @@ const ClothingCard: React.FC<ClothingCardProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{/* 场景级联多选 */}
|
<div className='flex-1 flex flex-col gap-6'>
|
||||||
<div className='w-full flex flex-row items-center gap-2'>
|
{/* 场景级联多选/自定义上传切换 */}
|
||||||
<label className='w-16 text-right flex-shrink-0'>场景</label>
|
<div className='w-full flex flex-row items-start gap-4'>
|
||||||
<div className='flex-1'>
|
<label className='w-16 text-right flex-shrink-0'>场景</label>
|
||||||
<CascaderMultiSelect options={scenesOptions} value={scenes || []} onChange={onScenesChange || (() => {})} />
|
<div className='flex flex-col gap-2 min-h-40 w-full overflow-y-auto'>
|
||||||
|
<div className='flex items-center gap-2 h-6'>
|
||||||
|
<Switch
|
||||||
|
id='custom-mode-switch'
|
||||||
|
checked={bgMode === 'custom'}
|
||||||
|
onCheckedChange={() => onChangeBgMode(bgMode === 'custom' ? 'scene' : 'custom')}
|
||||||
|
/>
|
||||||
|
<Label htmlFor='custom-mode-switch'>{bgMode === 'custom' ? '自定义垫图' : '内置场景'}</Label>
|
||||||
|
</div>
|
||||||
|
<div className='w-full flex flex-row items-center gap-2'>
|
||||||
|
<div className='flex-1'>
|
||||||
|
{bgMode === 'scene' ? (
|
||||||
|
<CascaderMultiSelect options={scenesOptions} value={scenes || []} onChange={onScenesChange || (() => {})} />
|
||||||
|
) : (
|
||||||
|
<BgPaddingMultiSelect value={paddingList || []} onChange={onPaddingListChange || (() => {})} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import React, { useEffect, useRef, useState } from 'react';
|
|||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import ClothingCard from './components/ClothingCard';
|
import ClothingCard from './components/ClothingCard';
|
||||||
import { api } from '@/api';
|
import { api, type Body_async_cloud_gen_images_v3_api_v3_cloud_batch_edit_images_post } from '@/api';
|
||||||
|
import { type BgPaddingMultiValue } from '@/components/block/BgPaddingMultiSelect';
|
||||||
|
|
||||||
// 五级联动的 tag schema
|
// 五级联动的 tag schema
|
||||||
const tagSchema = z.object({
|
const tagSchema = z.object({
|
||||||
@@ -30,7 +31,9 @@ interface TagForm {
|
|||||||
size: string;
|
size: string;
|
||||||
material: string;
|
material: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
bgMode?: 'custom' | 'scene';
|
||||||
scenes?: string[][];
|
scenes?: string[][];
|
||||||
|
paddingList?: BgPaddingMultiValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FormValues {
|
interface FormValues {
|
||||||
@@ -123,6 +126,20 @@ const TryOnPage: React.FC = () => {
|
|||||||
form.setValue('tags', newTags);
|
form.setValue('tags', newTags);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 自定义上传
|
||||||
|
const handlePaddingListChange = (idx: number, paddingList: BgPaddingMultiValue) => {
|
||||||
|
const newTags = [...form.getValues('tags')];
|
||||||
|
newTags[idx] = { ...newTags[idx], paddingList };
|
||||||
|
form.setValue('tags', newTags);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换背景模式
|
||||||
|
const handleBgModeChange = (idx: number, bgMode: 'custom' | 'scene') => {
|
||||||
|
const newTags = [...form.getValues('tags')];
|
||||||
|
newTags[idx] = { ...newTags[idx], bgMode };
|
||||||
|
form.setValue('tags', newTags);
|
||||||
|
};
|
||||||
|
|
||||||
// 计算每个卡片的options
|
// 计算每个卡片的options
|
||||||
const getTagOptions = (idx: number) => {
|
const getTagOptions = (idx: number) => {
|
||||||
const tag = tags[idx] || {};
|
const tag = tags[idx] || {};
|
||||||
@@ -163,21 +180,35 @@ const TryOnPage: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
for (let i = 0; i < values.clothing_images.length; i++) {
|
for (let i = 0; i < values.clothing_images.length; i++) {
|
||||||
const tag = values.tags[i];
|
const tag = values.tags[i];
|
||||||
|
const bgMode = tag.bgMode || 'scene';
|
||||||
const scenesArr: string[][] = tag.scenes || [];
|
const scenesArr: string[][] = tag.scenes || [];
|
||||||
for (const path of scenesArr) {
|
if (bgMode === 'custom') {
|
||||||
const scene = {
|
const paddingList = tag.paddingList || [];
|
||||||
title: path.join('/'),
|
for (const padding of paddingList) {
|
||||||
prompt: findPromptByPath(scenesOptions, path),
|
const formData: Body_async_cloud_gen_images_v3_api_v3_cloud_batch_edit_images_post = {
|
||||||
};
|
clothing_images: [values.clothing_images[i]],
|
||||||
const formData: Body_local_async_gen_images_api_v2_local_batch_edit_images_post = {
|
tag_list: JSON.stringify([[tag.sex, tag.category, tag.size, tag.material, tag.color].join('_')]),
|
||||||
clothing_images: [values.clothing_images[i]],
|
padding_clothes: [padding.file],
|
||||||
tag_list: JSON.stringify([[tag.sex, tag.category, tag.size, tag.material, tag.color].join('_')]),
|
bg_prompts: padding.text,
|
||||||
scenes_list: JSON.stringify([{ title: scene.title, prompt: scene.prompt }]),
|
};
|
||||||
mode: 'both',
|
const res = await api.ImageGenerateService.asyncCloudGenImagesV3ApiV3CloudBatchEditImagesPost({ formData });
|
||||||
};
|
results.push(res);
|
||||||
// eslint-disable-next-line no-await-in-loop
|
}
|
||||||
const res = await api.ImageGenerateService.localAsyncGenImagesApiV2LocalBatchEditImagesPost({ formData });
|
} else {
|
||||||
results.push(res);
|
for (const path of scenesArr) {
|
||||||
|
const scene = {
|
||||||
|
title: path.join('/'),
|
||||||
|
prompt: findPromptByPath(scenesOptions, path),
|
||||||
|
};
|
||||||
|
const formData: Body_local_async_gen_images_api_v2_local_batch_edit_images_post = {
|
||||||
|
clothing_images: [values.clothing_images[i]],
|
||||||
|
tag_list: JSON.stringify([[tag.sex, tag.category, tag.size, tag.material, tag.color].join('_')]),
|
||||||
|
scenes_list: JSON.stringify([{ title: scene.title, prompt: scene.prompt }]),
|
||||||
|
mode: 'both',
|
||||||
|
};
|
||||||
|
const res = await api.ImageGenerateService.localAsyncGenImagesApiV2LocalBatchEditImagesPost({ formData });
|
||||||
|
results.push(res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setResult(results);
|
setResult(results);
|
||||||
@@ -216,6 +247,10 @@ const TryOnPage: React.FC = () => {
|
|||||||
scenes={tag.scenes || []}
|
scenes={tag.scenes || []}
|
||||||
onScenesChange={scenes => handleScenesChange(idx, scenes)}
|
onScenesChange={scenes => handleScenesChange(idx, scenes)}
|
||||||
fileName={fileName}
|
fileName={fileName}
|
||||||
|
bgMode={tag.bgMode || 'scene'}
|
||||||
|
onChangeBgMode={bgMode => handleBgModeChange(idx, bgMode)}
|
||||||
|
paddingList={tag.paddingList || []}
|
||||||
|
onPaddingListChange={paddingList => handlePaddingListChange(idx, paddingList)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user