mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-02-15 20:34:41 +08:00
feat: videocard mobile action optimize
This commit is contained in:
@@ -38,6 +38,29 @@ export const UserMenu: React.FC = () => {
|
||||
const [storageType, setStorageType] = useState<string>('localstorage');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
// Body 滚动锁定 - 使用 overflow 方式避免布局问题
|
||||
useEffect(() => {
|
||||
if (isSettingsOpen || isChangePasswordOpen) {
|
||||
const body = document.body;
|
||||
const html = document.documentElement;
|
||||
|
||||
// 保存原始样式
|
||||
const originalBodyOverflow = body.style.overflow;
|
||||
const originalHtmlOverflow = html.style.overflow;
|
||||
|
||||
// 只设置 overflow 来阻止滚动
|
||||
body.style.overflow = 'hidden';
|
||||
html.style.overflow = 'hidden';
|
||||
|
||||
return () => {
|
||||
|
||||
// 恢复所有原始样式
|
||||
body.style.overflow = originalBodyOverflow;
|
||||
html.style.overflow = originalHtmlOverflow;
|
||||
};
|
||||
}
|
||||
}, [isSettingsOpen, isChangePasswordOpen]);
|
||||
|
||||
// 设置相关状态
|
||||
const [defaultAggregateSearch, setDefaultAggregateSearch] = useState(true);
|
||||
const [doubanProxyUrl, setDoubanProxyUrl] = useState('');
|
||||
@@ -566,324 +589,347 @@ export const UserMenu: React.FC = () => {
|
||||
<div
|
||||
className='fixed inset-0 bg-black/50 backdrop-blur-sm z-[1000]'
|
||||
onClick={handleCloseSettings}
|
||||
onTouchMove={(e) => {
|
||||
// 只阻止滚动,允许其他触摸事件
|
||||
e.preventDefault();
|
||||
}}
|
||||
onWheel={(e) => {
|
||||
// 阻止滚轮滚动
|
||||
e.preventDefault();
|
||||
}}
|
||||
style={{
|
||||
touchAction: 'none',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 设置面板 */}
|
||||
<div className='fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-xl max-h-[90vh] bg-white dark:bg-gray-900 rounded-xl shadow-xl z-[1001] p-6 overflow-y-auto'>
|
||||
{/* 标题栏 */}
|
||||
<div className='flex items-center justify-between mb-6'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<h3 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
||||
本地设置
|
||||
</h3>
|
||||
<div
|
||||
className='fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-xl max-h-[90vh] bg-white dark:bg-gray-900 rounded-xl shadow-xl z-[1001] flex flex-col'
|
||||
>
|
||||
{/* 内容容器 - 独立的滚动区域 */}
|
||||
<div
|
||||
className='flex-1 p-6 overflow-y-auto'
|
||||
data-panel-content
|
||||
style={{
|
||||
touchAction: 'pan-y', // 只允许垂直滚动
|
||||
overscrollBehavior: 'contain', // 防止滚动冒泡
|
||||
}}
|
||||
>
|
||||
{/* 标题栏 */}
|
||||
<div className='flex items-center justify-between mb-6'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<h3 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
||||
本地设置
|
||||
</h3>
|
||||
<button
|
||||
onClick={handleResetSettings}
|
||||
className='px-2 py-1 text-xs text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 border border-red-200 hover:border-red-300 dark:border-red-800 dark:hover:border-red-700 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors'
|
||||
title='重置为默认设置'
|
||||
>
|
||||
恢复默认
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleResetSettings}
|
||||
className='px-2 py-1 text-xs text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 border border-red-200 hover:border-red-300 dark:border-red-800 dark:hover:border-red-700 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors'
|
||||
title='重置为默认设置'
|
||||
onClick={handleCloseSettings}
|
||||
className='w-8 h-8 p-1 rounded-full flex items-center justify-center text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors'
|
||||
aria-label='Close'
|
||||
>
|
||||
恢复默认
|
||||
<X className='w-full h-full' />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleCloseSettings}
|
||||
className='w-8 h-8 p-1 rounded-full flex items-center justify-center text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors'
|
||||
aria-label='Close'
|
||||
>
|
||||
<X className='w-full h-full' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 设置项 */}
|
||||
<div className='space-y-6'>
|
||||
{/* 豆瓣数据源选择 */}
|
||||
<div className='space-y-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
豆瓣数据代理
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
选择获取豆瓣数据的方式
|
||||
</p>
|
||||
</div>
|
||||
<div className='relative' data-dropdown='douban-datasource'>
|
||||
{/* 自定义下拉选择框 */}
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => setIsDoubanDropdownOpen(!isDoubanDropdownOpen)}
|
||||
className='w-full px-3 py-2.5 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all duration-200 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 shadow-sm hover:border-gray-400 dark:hover:border-gray-500 text-left'
|
||||
>
|
||||
{
|
||||
doubanDataSourceOptions.find(
|
||||
(option) => option.value === doubanDataSource
|
||||
)?.label
|
||||
}
|
||||
</button>
|
||||
|
||||
{/* 下拉箭头 */}
|
||||
<div className='absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none'>
|
||||
<ChevronDown
|
||||
className={`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform duration-200 ${isDoubanDropdownOpen ? 'rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 下拉选项列表 */}
|
||||
{isDoubanDropdownOpen && (
|
||||
<div className='absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-lg max-h-60 overflow-auto'>
|
||||
{doubanDataSourceOptions.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type='button'
|
||||
onClick={() => {
|
||||
handleDoubanDataSourceChange(option.value);
|
||||
setIsDoubanDropdownOpen(false);
|
||||
}}
|
||||
className={`w-full px-3 py-2.5 text-left text-sm transition-colors duration-150 flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-700 ${doubanDataSource === option.value
|
||||
? 'bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400'
|
||||
: 'text-gray-900 dark:text-gray-100'
|
||||
}`}
|
||||
>
|
||||
<span className='truncate'>{option.label}</span>
|
||||
{doubanDataSource === option.value && (
|
||||
<Check className='w-4 h-4 text-green-600 dark:text-green-400 flex-shrink-0 ml-2' />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 感谢信息 */}
|
||||
{getThanksInfo(doubanDataSource) && (
|
||||
<div className='mt-3'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() =>
|
||||
window.open(getThanksInfo(doubanDataSource)!.url, '_blank')
|
||||
}
|
||||
className='flex items-center justify-center gap-1.5 w-full px-3 text-xs text-gray-500 dark:text-gray-400 cursor-pointer'
|
||||
>
|
||||
<span className='font-medium'>
|
||||
{getThanksInfo(doubanDataSource)!.text}
|
||||
</span>
|
||||
<ExternalLink className='w-3.5 opacity-70' />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 豆瓣代理地址设置 - 仅在选择自定义代理时显示 */}
|
||||
{doubanDataSource === 'custom' && (
|
||||
{/* 设置项 */}
|
||||
<div className='space-y-6'>
|
||||
{/* 豆瓣数据源选择 */}
|
||||
<div className='space-y-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
豆瓣代理地址
|
||||
豆瓣数据代理
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
自定义代理服务器地址
|
||||
选择获取豆瓣数据的方式
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type='text'
|
||||
className='w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all duration-200 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 shadow-sm hover:border-gray-400 dark:hover:border-gray-500'
|
||||
placeholder='例如: https://proxy.example.com/fetch?url='
|
||||
value={doubanProxyUrl}
|
||||
onChange={(e) => handleDoubanProxyUrlChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className='relative' data-dropdown='douban-datasource'>
|
||||
{/* 自定义下拉选择框 */}
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => setIsDoubanDropdownOpen(!isDoubanDropdownOpen)}
|
||||
className='w-full px-3 py-2.5 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all duration-200 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 shadow-sm hover:border-gray-400 dark:hover:border-gray-500 text-left'
|
||||
>
|
||||
{
|
||||
doubanDataSourceOptions.find(
|
||||
(option) => option.value === doubanDataSource
|
||||
)?.label
|
||||
}
|
||||
</button>
|
||||
|
||||
{/* 分割线 */}
|
||||
<div className='border-t border-gray-200 dark:border-gray-700'></div>
|
||||
{/* 下拉箭头 */}
|
||||
<div className='absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none'>
|
||||
<ChevronDown
|
||||
className={`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform duration-200 ${isDoubanDropdownOpen ? 'rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 豆瓣图片代理设置 */}
|
||||
<div className='space-y-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
豆瓣图片代理
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
选择获取豆瓣图片的方式
|
||||
</p>
|
||||
</div>
|
||||
<div className='relative' data-dropdown='douban-image-proxy'>
|
||||
{/* 自定义下拉选择框 */}
|
||||
<button
|
||||
type='button'
|
||||
onClick={() =>
|
||||
setIsDoubanImageProxyDropdownOpen(
|
||||
!isDoubanImageProxyDropdownOpen
|
||||
)
|
||||
}
|
||||
className='w-full px-3 py-2.5 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all duration-200 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 shadow-sm hover:border-gray-400 dark:hover:border-gray-500 text-left'
|
||||
>
|
||||
{
|
||||
doubanImageProxyTypeOptions.find(
|
||||
(option) => option.value === doubanImageProxyType
|
||||
)?.label
|
||||
}
|
||||
</button>
|
||||
|
||||
{/* 下拉箭头 */}
|
||||
<div className='absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none'>
|
||||
<ChevronDown
|
||||
className={`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform duration-200 ${isDoubanDropdownOpen ? 'rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
{/* 下拉选项列表 */}
|
||||
{isDoubanDropdownOpen && (
|
||||
<div className='absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-lg max-h-60 overflow-auto'>
|
||||
{doubanDataSourceOptions.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type='button'
|
||||
onClick={() => {
|
||||
handleDoubanDataSourceChange(option.value);
|
||||
setIsDoubanDropdownOpen(false);
|
||||
}}
|
||||
className={`w-full px-3 py-2.5 text-left text-sm transition-colors duration-150 flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-700 ${doubanDataSource === option.value
|
||||
? 'bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400'
|
||||
: 'text-gray-900 dark:text-gray-100'
|
||||
}`}
|
||||
>
|
||||
<span className='truncate'>{option.label}</span>
|
||||
{doubanDataSource === option.value && (
|
||||
<Check className='w-4 h-4 text-green-600 dark:text-green-400 flex-shrink-0 ml-2' />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 下拉选项列表 */}
|
||||
{isDoubanImageProxyDropdownOpen && (
|
||||
<div className='absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-lg max-h-60 overflow-auto'>
|
||||
{doubanImageProxyTypeOptions.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type='button'
|
||||
onClick={() => {
|
||||
handleDoubanImageProxyTypeChange(option.value);
|
||||
setIsDoubanImageProxyDropdownOpen(false);
|
||||
}}
|
||||
className={`w-full px-3 py-2.5 text-left text-sm transition-colors duration-150 flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-700 ${doubanImageProxyType === option.value
|
||||
? 'bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400'
|
||||
: 'text-gray-900 dark:text-gray-100'
|
||||
}`}
|
||||
>
|
||||
<span className='truncate'>{option.label}</span>
|
||||
{doubanImageProxyType === option.value && (
|
||||
<Check className='w-4 h-4 text-green-600 dark:text-green-400 flex-shrink-0 ml-2' />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
{/* 感谢信息 */}
|
||||
{getThanksInfo(doubanDataSource) && (
|
||||
<div className='mt-3'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() =>
|
||||
window.open(getThanksInfo(doubanDataSource)!.url, '_blank')
|
||||
}
|
||||
className='flex items-center justify-center gap-1.5 w-full px-3 text-xs text-gray-500 dark:text-gray-400 cursor-pointer'
|
||||
>
|
||||
<span className='font-medium'>
|
||||
{getThanksInfo(doubanDataSource)!.text}
|
||||
</span>
|
||||
<ExternalLink className='w-3.5 opacity-70' />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 感谢信息 */}
|
||||
{getThanksInfo(doubanImageProxyType) && (
|
||||
<div className='mt-3'>
|
||||
{/* 豆瓣代理地址设置 - 仅在选择自定义代理时显示 */}
|
||||
{doubanDataSource === 'custom' && (
|
||||
<div className='space-y-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
豆瓣代理地址
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
自定义代理服务器地址
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type='text'
|
||||
className='w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all duration-200 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 shadow-sm hover:border-gray-400 dark:hover:border-gray-500'
|
||||
placeholder='例如: https://proxy.example.com/fetch?url='
|
||||
value={doubanProxyUrl}
|
||||
onChange={(e) => handleDoubanProxyUrlChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 分割线 */}
|
||||
<div className='border-t border-gray-200 dark:border-gray-700'></div>
|
||||
|
||||
{/* 豆瓣图片代理设置 */}
|
||||
<div className='space-y-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
豆瓣图片代理
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
选择获取豆瓣图片的方式
|
||||
</p>
|
||||
</div>
|
||||
<div className='relative' data-dropdown='douban-image-proxy'>
|
||||
{/* 自定义下拉选择框 */}
|
||||
<button
|
||||
type='button'
|
||||
onClick={() =>
|
||||
window.open(
|
||||
getThanksInfo(doubanImageProxyType)!.url,
|
||||
'_blank'
|
||||
setIsDoubanImageProxyDropdownOpen(
|
||||
!isDoubanImageProxyDropdownOpen
|
||||
)
|
||||
}
|
||||
className='flex items-center justify-center gap-1.5 w-full px-3 text-xs text-gray-500 dark:text-gray-400 cursor-pointer'
|
||||
className='w-full px-3 py-2.5 pr-10 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all duration-200 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 shadow-sm hover:border-gray-400 dark:hover:border-gray-500 text-left'
|
||||
>
|
||||
<span className='font-medium'>
|
||||
{getThanksInfo(doubanImageProxyType)!.text}
|
||||
</span>
|
||||
<ExternalLink className='w-3.5 opacity-70' />
|
||||
{
|
||||
doubanImageProxyTypeOptions.find(
|
||||
(option) => option.value === doubanImageProxyType
|
||||
)?.label
|
||||
}
|
||||
</button>
|
||||
|
||||
{/* 下拉箭头 */}
|
||||
<div className='absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none'>
|
||||
<ChevronDown
|
||||
className={`w-4 h-4 text-gray-400 dark:text-gray-500 transition-transform duration-200 ${isDoubanDropdownOpen ? 'rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 下拉选项列表 */}
|
||||
{isDoubanImageProxyDropdownOpen && (
|
||||
<div className='absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg shadow-lg max-h-60 overflow-auto'>
|
||||
{doubanImageProxyTypeOptions.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type='button'
|
||||
onClick={() => {
|
||||
handleDoubanImageProxyTypeChange(option.value);
|
||||
setIsDoubanImageProxyDropdownOpen(false);
|
||||
}}
|
||||
className={`w-full px-3 py-2.5 text-left text-sm transition-colors duration-150 flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-700 ${doubanImageProxyType === option.value
|
||||
? 'bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400'
|
||||
: 'text-gray-900 dark:text-gray-100'
|
||||
}`}
|
||||
>
|
||||
<span className='truncate'>{option.label}</span>
|
||||
{doubanImageProxyType === option.value && (
|
||||
<Check className='w-4 h-4 text-green-600 dark:text-green-400 flex-shrink-0 ml-2' />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 感谢信息 */}
|
||||
{getThanksInfo(doubanImageProxyType) && (
|
||||
<div className='mt-3'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() =>
|
||||
window.open(
|
||||
getThanksInfo(doubanImageProxyType)!.url,
|
||||
'_blank'
|
||||
)
|
||||
}
|
||||
className='flex items-center justify-center gap-1.5 w-full px-3 text-xs text-gray-500 dark:text-gray-400 cursor-pointer'
|
||||
>
|
||||
<span className='font-medium'>
|
||||
{getThanksInfo(doubanImageProxyType)!.text}
|
||||
</span>
|
||||
<ExternalLink className='w-3.5 opacity-70' />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 豆瓣图片代理地址设置 - 仅在选择自定义代理时显示 */}
|
||||
{doubanImageProxyType === 'custom' && (
|
||||
<div className='space-y-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
豆瓣图片代理地址
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
自定义图片代理服务器地址
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type='text'
|
||||
className='w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all duration-200 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 shadow-sm hover:border-gray-400 dark:hover:border-gray-500'
|
||||
placeholder='例如: https://proxy.example.com/fetch?url='
|
||||
value={doubanImageProxyUrl}
|
||||
onChange={(e) =>
|
||||
handleDoubanImageProxyUrlChange(e.target.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 豆瓣图片代理地址设置 - 仅在选择自定义代理时显示 */}
|
||||
{doubanImageProxyType === 'custom' && (
|
||||
<div className='space-y-3'>
|
||||
{/* 分割线 */}
|
||||
<div className='border-t border-gray-200 dark:border-gray-700'></div>
|
||||
|
||||
{/* 默认聚合搜索结果 */}
|
||||
<div className='flex items-center justify-between'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
豆瓣图片代理地址
|
||||
默认聚合搜索结果
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
自定义图片代理服务器地址
|
||||
搜索时默认按标题和年份聚合显示结果
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
type='text'
|
||||
className='w-full px-3 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all duration-200 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 shadow-sm hover:border-gray-400 dark:hover:border-gray-500'
|
||||
placeholder='例如: https://proxy.example.com/fetch?url='
|
||||
value={doubanImageProxyUrl}
|
||||
onChange={(e) =>
|
||||
handleDoubanImageProxyUrlChange(e.target.value)
|
||||
}
|
||||
/>
|
||||
<label className='flex items-center cursor-pointer'>
|
||||
<div className='relative'>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='sr-only peer'
|
||||
checked={defaultAggregateSearch}
|
||||
onChange={(e) => handleAggregateToggle(e.target.checked)}
|
||||
/>
|
||||
<div className='w-11 h-6 bg-gray-300 rounded-full peer-checked:bg-green-500 transition-colors dark:bg-gray-600'></div>
|
||||
<div className='absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full transition-transform peer-checked:translate-x-5'></div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 分割线 */}
|
||||
<div className='border-t border-gray-200 dark:border-gray-700'></div>
|
||||
|
||||
{/* 默认聚合搜索结果 */}
|
||||
<div className='flex items-center justify-between'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
默认聚合搜索结果
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
搜索时默认按标题和年份聚合显示结果
|
||||
</p>
|
||||
</div>
|
||||
<label className='flex items-center cursor-pointer'>
|
||||
<div className='relative'>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='sr-only peer'
|
||||
checked={defaultAggregateSearch}
|
||||
onChange={(e) => handleAggregateToggle(e.target.checked)}
|
||||
/>
|
||||
<div className='w-11 h-6 bg-gray-300 rounded-full peer-checked:bg-green-500 transition-colors dark:bg-gray-600'></div>
|
||||
<div className='absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full transition-transform peer-checked:translate-x-5'></div>
|
||||
{/* 优选和测速 */}
|
||||
<div className='flex items-center justify-between'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
优选和测速
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
如出现播放器劫持问题可关闭
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
<label className='flex items-center cursor-pointer'>
|
||||
<div className='relative'>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='sr-only peer'
|
||||
checked={enableOptimization}
|
||||
onChange={(e) => handleOptimizationToggle(e.target.checked)}
|
||||
/>
|
||||
<div className='w-11 h-6 bg-gray-300 rounded-full peer-checked:bg-green-500 transition-colors dark:bg-gray-600'></div>
|
||||
<div className='absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full transition-transform peer-checked:translate-x-5'></div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* 流式搜索 */}
|
||||
<div className='flex items-center justify-between'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
流式搜索输出
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
启用搜索结果实时流式输出,关闭后使用传统一次性搜索
|
||||
</p>
|
||||
</div>
|
||||
<label className='flex items-center cursor-pointer'>
|
||||
<div className='relative'>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='sr-only peer'
|
||||
checked={fluidSearch}
|
||||
onChange={(e) => handleFluidSearchToggle(e.target.checked)}
|
||||
/>
|
||||
<div className='w-11 h-6 bg-gray-300 rounded-full peer-checked:bg-green-500 transition-colors dark:bg-gray-600'></div>
|
||||
<div className='absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full transition-transform peer-checked:translate-x-5'></div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 优选和测速 */}
|
||||
<div className='flex items-center justify-between'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
优选和测速
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
如出现播放器劫持问题可关闭
|
||||
</p>
|
||||
</div>
|
||||
<label className='flex items-center cursor-pointer'>
|
||||
<div className='relative'>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='sr-only peer'
|
||||
checked={enableOptimization}
|
||||
onChange={(e) => handleOptimizationToggle(e.target.checked)}
|
||||
/>
|
||||
<div className='w-11 h-6 bg-gray-300 rounded-full peer-checked:bg-green-500 transition-colors dark:bg-gray-600'></div>
|
||||
<div className='absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full transition-transform peer-checked:translate-x-5'></div>
|
||||
</div>
|
||||
</label>
|
||||
{/* 底部说明 */}
|
||||
<div className='mt-6 pt-4 border-t border-gray-200 dark:border-gray-700'>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 text-center'>
|
||||
这些设置保存在本地浏览器中
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 流式搜索 */}
|
||||
<div className='flex items-center justify-between'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300'>
|
||||
流式搜索输出
|
||||
</h4>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
|
||||
启用搜索结果实时流式输出,关闭后使用传统一次性搜索
|
||||
</p>
|
||||
</div>
|
||||
<label className='flex items-center cursor-pointer'>
|
||||
<div className='relative'>
|
||||
<input
|
||||
type='checkbox'
|
||||
className='sr-only peer'
|
||||
checked={fluidSearch}
|
||||
onChange={(e) => handleFluidSearchToggle(e.target.checked)}
|
||||
/>
|
||||
<div className='w-11 h-6 bg-gray-300 rounded-full peer-checked:bg-green-500 transition-colors dark:bg-gray-600'></div>
|
||||
<div className='absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full transition-transform peer-checked:translate-x-5'></div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 底部说明 */}
|
||||
<div className='mt-6 pt-4 border-t border-gray-200 dark:border-gray-700'>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 text-center'>
|
||||
这些设置保存在本地浏览器中
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -896,87 +942,113 @@ export const UserMenu: React.FC = () => {
|
||||
<div
|
||||
className='fixed inset-0 bg-black/50 backdrop-blur-sm z-[1000]'
|
||||
onClick={handleCloseChangePassword}
|
||||
onTouchMove={(e) => {
|
||||
// 只阻止滚动,允许其他触摸事件
|
||||
e.preventDefault();
|
||||
}}
|
||||
onWheel={(e) => {
|
||||
// 阻止滚轮滚动
|
||||
e.preventDefault();
|
||||
}}
|
||||
style={{
|
||||
touchAction: 'none',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 修改密码面板 */}
|
||||
<div className='fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-md bg-white dark:bg-gray-900 rounded-xl shadow-xl z-[1001] p-6'>
|
||||
{/* 标题栏 */}
|
||||
<div className='flex items-center justify-between mb-6'>
|
||||
<h3 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
||||
修改密码
|
||||
</h3>
|
||||
<button
|
||||
onClick={handleCloseChangePassword}
|
||||
className='w-8 h-8 p-1 rounded-full flex items-center justify-center text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors'
|
||||
aria-label='Close'
|
||||
>
|
||||
<X className='w-full h-full' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 表单 */}
|
||||
<div className='space-y-4'>
|
||||
{/* 新密码输入 */}
|
||||
<div>
|
||||
<label className='block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
|
||||
新密码
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent transition-colors bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400'
|
||||
placeholder='请输入新密码'
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
disabled={passwordLoading}
|
||||
/>
|
||||
<div
|
||||
className='fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-md bg-white dark:bg-gray-900 rounded-xl shadow-xl z-[1001] overflow-hidden'
|
||||
>
|
||||
{/* 内容容器 - 独立的滚动区域 */}
|
||||
<div
|
||||
className='h-full p-6'
|
||||
data-panel-content
|
||||
onTouchMove={(e) => {
|
||||
// 阻止事件冒泡到遮罩层,但允许内部滚动
|
||||
e.stopPropagation();
|
||||
}}
|
||||
style={{
|
||||
touchAction: 'auto', // 允许所有触摸操作
|
||||
}}
|
||||
>
|
||||
{/* 标题栏 */}
|
||||
<div className='flex items-center justify-between mb-6'>
|
||||
<h3 className='text-xl font-bold text-gray-800 dark:text-gray-200'>
|
||||
修改密码
|
||||
</h3>
|
||||
<button
|
||||
onClick={handleCloseChangePassword}
|
||||
className='w-8 h-8 p-1 rounded-full flex items-center justify-center text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors'
|
||||
aria-label='Close'
|
||||
>
|
||||
<X className='w-full h-full' />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 确认密码输入 */}
|
||||
<div>
|
||||
<label className='block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
|
||||
确认密码
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent transition-colors bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400'
|
||||
placeholder='请再次输入新密码'
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
disabled={passwordLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 错误信息 */}
|
||||
{passwordError && (
|
||||
<div className='text-red-500 text-sm bg-red-50 dark:bg-red-900/20 p-3 rounded-md border border-red-200 dark:border-red-800'>
|
||||
{passwordError}
|
||||
{/* 表单 */}
|
||||
<div className='space-y-4'>
|
||||
{/* 新密码输入 */}
|
||||
<div>
|
||||
<label className='block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
|
||||
新密码
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent transition-colors bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400'
|
||||
placeholder='请输入新密码'
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
disabled={passwordLoading}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className='flex gap-3 mt-6 pt-4 border-t border-gray-200 dark:border-gray-700'>
|
||||
<button
|
||||
onClick={handleCloseChangePassword}
|
||||
className='flex-1 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md transition-colors'
|
||||
disabled={passwordLoading}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmitChangePassword}
|
||||
className='flex-1 px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 dark:bg-green-700 dark:hover:bg-green-600 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
disabled={passwordLoading || !newPassword || !confirmPassword}
|
||||
>
|
||||
{passwordLoading ? '修改中...' : '确认修改'}
|
||||
</button>
|
||||
</div>
|
||||
{/* 确认密码输入 */}
|
||||
<div>
|
||||
<label className='block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
|
||||
确认密码
|
||||
</label>
|
||||
<input
|
||||
type='password'
|
||||
className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent transition-colors bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400'
|
||||
placeholder='请再次输入新密码'
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
disabled={passwordLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 底部说明 */}
|
||||
<div className='mt-4 pt-4 border-t border-gray-200 dark:border-gray-700'>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 text-center'>
|
||||
修改密码后需要重新登录
|
||||
</p>
|
||||
{/* 错误信息 */}
|
||||
{passwordError && (
|
||||
<div className='text-red-500 text-sm bg-red-50 dark:bg-red-900/20 p-3 rounded-md border border-red-200 dark:border-red-800'>
|
||||
{passwordError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className='flex gap-3 mt-6 pt-4 border-t border-gray-200 dark:border-gray-700'>
|
||||
<button
|
||||
onClick={handleCloseChangePassword}
|
||||
className='flex-1 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md transition-colors'
|
||||
disabled={passwordLoading}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmitChangePassword}
|
||||
className='flex-1 px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 dark:bg-green-700 dark:hover:bg-green-600 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed'
|
||||
disabled={passwordLoading || !newPassword || !confirmPassword}
|
||||
>
|
||||
{passwordLoading ? '修改中...' : '确认修改'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 底部说明 */}
|
||||
<div className='mt-4 pt-4 border-t border-gray-200 dark:border-gray-700'>
|
||||
<p className='text-xs text-gray-500 dark:text-gray-400 text-center'>
|
||||
修改密码后需要重新登录
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user