forked from 77media/video-flow
统一create入口:H5端setting配置项
This commit is contained in:
parent
f69e17193e
commit
066345d568
269
components/pages/create-video/CreateInput/MobileConfigModal.tsx
Normal file
269
components/pages/create-video/CreateInput/MobileConfigModal.tsx
Normal file
@ -0,0 +1,269 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
GlobalOutlined,
|
||||
ClockCircleOutlined,
|
||||
CloseOutlined,
|
||||
DownOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { WandSparkles, RectangleHorizontal, RectangleVertical, Palette } from 'lucide-react';
|
||||
import { Modal, Dropdown, Menu } from 'antd';
|
||||
import { LanguageOptions, VideoDurationOptions } from './config-options';
|
||||
import type { ConfigOptions, LanguageValue, VideoDurationValue } from './config-options';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { fetchSettingByCode } from '@/api/serversetting';
|
||||
|
||||
interface MobileConfigModalProps {
|
||||
/** Modal visibility state */
|
||||
visible: boolean;
|
||||
/** Close handler */
|
||||
onClose: () => void;
|
||||
/** Current configuration options */
|
||||
configOptions: ConfigOptions;
|
||||
/** Handler for configuration changes */
|
||||
onConfigChange: <K extends keyof ConfigOptions>(key: K, value: ConfigOptions[K]) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mobile configuration modal with card-style selections.
|
||||
* Provides a modern, non-traditional form interface for video settings.
|
||||
* @param {boolean} visible - Modal visibility
|
||||
* @param {Function} onClose - Close handler
|
||||
* @param {ConfigOptions} configOptions - Current configuration
|
||||
* @param {Function} onConfigChange - Handler for config changes
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function MobileConfigModal({
|
||||
visible,
|
||||
onClose,
|
||||
configOptions,
|
||||
onConfigChange,
|
||||
}: MobileConfigModalProps) {
|
||||
const [animeOptions, setAnimeOptions] = useState<Array<{ name: string; pcode: string }>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
(async () => {
|
||||
const list = await fetchSettingByCode<Array<{ name: string; pcode: string }>>('comic_config', []);
|
||||
if (!mounted) return;
|
||||
if (Array.isArray(list) && list.length > 0) {
|
||||
setAnimeOptions(list);
|
||||
} else {
|
||||
setAnimeOptions([
|
||||
{ name: 'Korean Comics Long', pcode: 'STANDARD_V1_734684_116483' },
|
||||
]);
|
||||
}
|
||||
})();
|
||||
return () => { mounted = false; };
|
||||
}, []);
|
||||
|
||||
const isPortrait = configOptions.pcode === 'portrait';
|
||||
const currentLanguage = LanguageOptions.find(opt => opt.value === configOptions.language);
|
||||
|
||||
/** Language dropdown menu */
|
||||
const languageMenu = (
|
||||
<Menu
|
||||
className="bg-[#1a1a1a] border border-white/10 max-h-[300px] overflow-y-auto"
|
||||
onClick={({ key }) => onConfigChange('language', key as LanguageValue)}
|
||||
items={LanguageOptions.map((option) => ({
|
||||
key: option.value,
|
||||
label: (
|
||||
<span className={`text-sm ${configOptions.language === option.value ? 'text-cyan-400' : 'text-gray-300'}`}>
|
||||
{option.label}
|
||||
</span>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
/** Anime dropdown menu */
|
||||
const animeMenu = (
|
||||
<Menu
|
||||
className="bg-[#1a1a1a] border border-white/10"
|
||||
onClick={({ key }) => onConfigChange('pcode', key)}
|
||||
items={animeOptions.map((option) => ({
|
||||
key: option.pcode,
|
||||
label: (
|
||||
<span className={`text-sm ${configOptions.pcode === option.pcode ? 'text-cyan-400' : 'text-gray-300'}`}>
|
||||
{option.name}
|
||||
</span>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={visible}
|
||||
onCancel={onClose}
|
||||
footer={null}
|
||||
closeIcon={<CloseOutlined className="text-gray-400 hover:text-cyan-400" />}
|
||||
width="90%"
|
||||
style={{ maxWidth: '420px' }}
|
||||
styles={{
|
||||
mask: { backdropFilter: 'blur(2px)', backgroundColor: 'transparent' },
|
||||
content: {
|
||||
padding: 0,
|
||||
backdropFilter: 'blur(4px)',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||
borderRadius: '20px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div data-alt="mobile-config-modal" className="p-4 space-y-3">
|
||||
{/* Header */}
|
||||
<div data-alt="modal-header" className="pb-2.5 border-b border-white/10">
|
||||
<h2 className="text-base font-semibold text-white">Video Settings</h2>
|
||||
</div>
|
||||
|
||||
{/* Language Row */}
|
||||
<div data-alt="language-row" className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<GlobalOutlined className="text-base text-cyan-400" />
|
||||
<span className="text-sm text-gray-300">Language</span>
|
||||
</div>
|
||||
<Dropdown overlay={languageMenu} trigger={['click']}>
|
||||
<button
|
||||
data-alt="language-selector"
|
||||
className="h-8 px-3 rounded-lg border border-white/20 bg-transparent hover:border-cyan-400/60 transition-all duration-200 flex items-center gap-2 text-gray-300 hover:text-cyan-400"
|
||||
>
|
||||
<span className="text-sm">{currentLanguage?.label}</span>
|
||||
<DownOutlined className="text-xs" />
|
||||
</button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
{/* AutoScript Row */}
|
||||
<div data-alt="autoscript-row" className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<WandSparkles className="w-4 h-4 text-cyan-400" />
|
||||
<span className="text-sm text-gray-300">AI Copilot</span>
|
||||
</div>
|
||||
<button
|
||||
data-alt="autoscript-toggle"
|
||||
disabled={configOptions.videoDuration === '8s'}
|
||||
onClick={() => {
|
||||
if (configOptions.videoDuration !== '8s') {
|
||||
onConfigChange('expansion_mode', !configOptions.expansion_mode);
|
||||
}
|
||||
}}
|
||||
className="flex items-center"
|
||||
>
|
||||
<div className={`w-11 h-6 rounded-full transition-all duration-200 relative ${
|
||||
configOptions.videoDuration === '8s'
|
||||
? 'opacity-40 cursor-not-allowed bg-gray-600'
|
||||
: configOptions.expansion_mode
|
||||
? 'bg-cyan-400'
|
||||
: 'bg-gray-600'
|
||||
}`}>
|
||||
<div className={`absolute top-0.5 w-5 h-5 bg-white rounded-full transition-all duration-200 ${
|
||||
configOptions.expansion_mode ? 'left-5' : 'left-0.5'
|
||||
}`} />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Video Duration Row */}
|
||||
<div data-alt="duration-row" className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<ClockCircleOutlined className="text-base text-cyan-400" />
|
||||
<span className="text-sm text-gray-300">Duration</span>
|
||||
</div>
|
||||
<div className="flex items-center p-0.5 rounded-lg border border-white/20 bg-black/20">
|
||||
{VideoDurationOptions.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
data-alt={`duration-${option.value}`}
|
||||
onClick={() => onConfigChange('videoDuration', option.value as VideoDurationValue)}
|
||||
className={`h-7 px-3 rounded-md transition-all duration-200 text-xs font-medium ${
|
||||
configOptions.videoDuration === option.value
|
||||
? 'bg-cyan-400/20 text-cyan-400'
|
||||
: 'bg-transparent text-gray-400 hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Aspect Ratio Row */}
|
||||
<div data-alt="aspect-ratio-row" className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<RectangleHorizontal className="w-4 h-4 text-cyan-400" />
|
||||
<span className="text-sm text-gray-300">Aspect Ratio</span>
|
||||
</div>
|
||||
<div className="flex items-center p-0.5 rounded-lg border border-white/20 bg-black/20">
|
||||
<button
|
||||
data-alt="aspect-portrait"
|
||||
onClick={() => onConfigChange('aspect_ratio', 'VIDEO_ASPECT_RATIO_PORTRAIT')}
|
||||
className={`h-7 w-9 rounded-md transition-all duration-200 flex items-center justify-center ${
|
||||
configOptions.aspect_ratio === 'VIDEO_ASPECT_RATIO_PORTRAIT'
|
||||
? 'bg-cyan-400/20 text-cyan-400'
|
||||
: 'bg-transparent text-gray-400 hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
<RectangleVertical className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
data-alt="aspect-landscape"
|
||||
onClick={() => onConfigChange('aspect_ratio', 'VIDEO_ASPECT_RATIO_LANDSCAPE')}
|
||||
className={`h-7 w-9 rounded-md transition-all duration-200 flex items-center justify-center ${
|
||||
configOptions.aspect_ratio === 'VIDEO_ASPECT_RATIO_LANDSCAPE'
|
||||
? 'bg-cyan-400/20 text-cyan-400'
|
||||
: 'bg-transparent text-gray-400 hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
<RectangleHorizontal className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Video Style Row */}
|
||||
<div data-alt="style-row" className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Palette className="w-4 h-4 text-cyan-400" />
|
||||
<span className="text-sm text-gray-300">Video Style</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center p-0.5 rounded-lg border border-white/20 bg-black/20">
|
||||
<button
|
||||
data-alt="style-portrait"
|
||||
onClick={() => onConfigChange('pcode', 'portrait')}
|
||||
className={`h-7 px-3 rounded-md transition-all duration-200 text-xs font-medium whitespace-nowrap ${
|
||||
isPortrait
|
||||
? 'bg-cyan-400/20 text-cyan-400'
|
||||
: 'bg-transparent text-gray-400 hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
Portrait
|
||||
</button>
|
||||
<Dropdown overlay={animeMenu} trigger={['click']}>
|
||||
<button
|
||||
data-alt="style-anime"
|
||||
onClick={() => {
|
||||
if (isPortrait && animeOptions.length > 0) {
|
||||
onConfigChange('pcode', animeOptions[0].pcode);
|
||||
}
|
||||
}}
|
||||
className={`h-7 px-3 rounded-md transition-all duration-200 text-xs font-medium max-w-[120px] flex items-center gap-1 ${
|
||||
!isPortrait
|
||||
? 'bg-cyan-400/20 text-cyan-400'
|
||||
: 'bg-transparent text-gray-400 hover:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span className="truncate">
|
||||
{animeOptions.find(opt => opt.pcode === configOptions.pcode)?.name || 'Anime'}
|
||||
</span>
|
||||
<DownOutlined className="text-xs flex-shrink-0" />
|
||||
</button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,9 +9,11 @@ import {
|
||||
CameraOutlined,
|
||||
BulbOutlined,
|
||||
ArrowRightOutlined,
|
||||
SettingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Dropdown, Menu } from 'antd';
|
||||
import { ConfigPanel } from './ConfigPanel';
|
||||
import { MobileConfigModal } from './MobileConfigModal';
|
||||
import { defaultConfig } from './config-options';
|
||||
import type { ConfigOptions } from './config-options';
|
||||
import { useDeviceType } from '@/hooks/useDeviceType';
|
||||
@ -20,6 +22,7 @@ export default function VideoCreationForm() {
|
||||
const [photos, setPhotos] = useState<PhotoItem[]>([]);
|
||||
const [inputText, setInputText] = useState('');
|
||||
const [configOptions, setConfigOptions] = useState<ConfigOptions>(defaultConfig);
|
||||
const [configModalVisible, setConfigModalVisible] = useState(false);
|
||||
|
||||
const { isMobile, isDesktop } = useDeviceType();
|
||||
|
||||
@ -168,13 +171,23 @@ export default function VideoCreationForm() {
|
||||
<span className="text-base font-bold">@</span>
|
||||
</button>
|
||||
|
||||
{/* Configuration Panel */}
|
||||
<ConfigPanel
|
||||
configOptions={configOptions}
|
||||
onConfigChange={handleConfigChange}
|
||||
isMobile={isMobile}
|
||||
isDesktop={isDesktop}
|
||||
/>
|
||||
{/* Configuration - Desktop: Full Panel, Mobile: Setting Icon */}
|
||||
{isDesktop ? (
|
||||
<ConfigPanel
|
||||
configOptions={configOptions}
|
||||
onConfigChange={handleConfigChange}
|
||||
isMobile={isMobile}
|
||||
isDesktop={isDesktop}
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
data-alt="settings-button"
|
||||
className="w-8 h-8 rounded-full border border-white/20 bg-transparent hover:bg-white/5 hover:border-cyan-400/60 transition-all duration-200 flex items-center justify-center text-gray-300 hover:text-cyan-400"
|
||||
onClick={() => setConfigModalVisible(true)}
|
||||
>
|
||||
<SettingOutlined className="text-base" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Side - Create Button */}
|
||||
@ -187,6 +200,14 @@ export default function VideoCreationForm() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Configuration Modal */}
|
||||
<MobileConfigModal
|
||||
visible={configModalVisible}
|
||||
onClose={() => setConfigModalVisible(false)}
|
||||
configOptions={configOptions}
|
||||
onConfigChange={handleConfigChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user