mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-05-12 02:20:28 +08:00
feat: enhance mobile responsiveness across settings and image pages
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
import { useState, useRef, useMemo, useCallback } from 'react';
|
||||
import { Typography, Button, Dropdown, message } from 'antd';
|
||||
import { Typography, Button, Dropdown, message, Row, Col } from 'antd';
|
||||
import { SortAscendingOutlined, UploadOutlined } from '@ant-design/icons';
|
||||
import type { PictureResponse } from '../../api';
|
||||
import ImageUploadDialog from '../../components/upload/ImageUploadDialog';
|
||||
import ImageGrid from '../../components/image/ImageGrid';
|
||||
import useIsMobile from '../../hooks/useIsMobile';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
function AllImages() {
|
||||
const isMobile = useIsMobile();
|
||||
const [images, setImages] = useState<PictureResponse[]>([]);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
@@ -76,60 +78,66 @@ function AllImages() {
|
||||
return (
|
||||
<>
|
||||
<div style={{
|
||||
marginBottom: 50,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: isMobile ? 30 : 50,
|
||||
position: 'relative',
|
||||
zIndex: 1
|
||||
}}>
|
||||
<div>
|
||||
<Title level={2} style={{
|
||||
margin: 0,
|
||||
marginBottom: 10,
|
||||
fontWeight: 600,
|
||||
letterSpacing: '0.5px',
|
||||
fontSize: 32,
|
||||
background: 'linear-gradient(120deg, #000000, #444444)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
}}>所有图片</Title>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<UploadOutlined />}
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
height: 46,
|
||||
padding: '0 24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
fontSize: 15
|
||||
}}
|
||||
onClick={() => setIsUploadDialogVisible(true)}
|
||||
>
|
||||
上传图片
|
||||
</Button>
|
||||
<Dropdown menu={sortMenu} placement="bottomRight">
|
||||
<Button style={{
|
||||
borderRadius: 10,
|
||||
height: 46,
|
||||
border: '1px solid #f0f0f0',
|
||||
padding: '0 24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
fontSize: 15,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.02)',
|
||||
background: '#ffffff'
|
||||
<Row gutter={[16, 16]} align="middle">
|
||||
<Col xs={24} sm={24} md={12} lg={12}>
|
||||
<Title level={2} style={{
|
||||
margin: 0,
|
||||
marginBottom: isMobile ? 5 : 10,
|
||||
fontWeight: 600,
|
||||
letterSpacing: '0.5px',
|
||||
fontSize: isMobile ? 24 : 32,
|
||||
background: 'linear-gradient(120deg, #000000, #444444)',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
textAlign: isMobile ? 'center' : 'left',
|
||||
}}>所有图片</Title>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={12} lg={12}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
gap: isMobile ? 8 : 12,
|
||||
justifyContent: isMobile ? 'center' : 'flex-end'
|
||||
}}>
|
||||
<SortAscendingOutlined />
|
||||
排序方式
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<UploadOutlined />}
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
height: isMobile ? 40 : 46,
|
||||
padding: isMobile ? '0 15px' : '0 24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
fontSize: isMobile ? 14 : 15
|
||||
}}
|
||||
onClick={() => setIsUploadDialogVisible(true)}
|
||||
>
|
||||
上传图片
|
||||
</Button>
|
||||
<Dropdown menu={sortMenu} placement="bottomRight">
|
||||
<Button style={{
|
||||
borderRadius: 10,
|
||||
height: isMobile ? 40 : 46,
|
||||
border: '1px solid #f0f0f0',
|
||||
padding: isMobile ? '0 15px' : '0 24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
fontSize: isMobile ? 14 : 15,
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.02)',
|
||||
background: '#ffffff'
|
||||
}}>
|
||||
<SortAscendingOutlined />
|
||||
{!isMobile && "排序方式"}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<ImageGrid
|
||||
|
||||
@@ -19,12 +19,14 @@ import {
|
||||
import ImageGrid from '../../components/image/ImageGrid';
|
||||
import type { PictureResponse } from '../../api/types';
|
||||
import { getFilteredTags } from '../../api/tagApi';
|
||||
import useIsMobile from '../../hooks/useIsMobile';
|
||||
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
const { Search } = Input;
|
||||
const { Option } = Select;
|
||||
|
||||
const PixHub: React.FC = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const [activeCategory, setActiveCategory] = useState('全部');
|
||||
const [sortBy, setSortBy] = useState('newest');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
@@ -84,28 +86,50 @@ const PixHub: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="image-square">
|
||||
<div className="page-header" style={{ marginBottom: 32 }}>
|
||||
<Row gutter={[24, 24]} align="middle">
|
||||
<div className="page-header" style={{ marginBottom: isMobile ? 20 : 32 }}>
|
||||
<Row gutter={[24, isMobile ? 12 : 24]} align="middle">
|
||||
<Col lg={10} md={12} sm={24} xs={24}>
|
||||
<Title level={2} style={{ marginBottom: 8, fontWeight: 600 }}>
|
||||
<Title level={2} style={{
|
||||
marginBottom: 8,
|
||||
fontWeight: 600,
|
||||
fontSize: isMobile ? 24 : 30,
|
||||
textAlign: isMobile ? 'center' : 'left'
|
||||
}}>
|
||||
图片广场
|
||||
<Text style={{ fontSize: 16, fontWeight: 400, marginLeft: 12, color: '#8c8c8c' }}>
|
||||
{!isMobile && (
|
||||
<Text style={{ fontSize: 16, fontWeight: 400, marginLeft: 12, color: '#8c8c8c' }}>
|
||||
探索世界各地的精彩瞬间
|
||||
</Text>
|
||||
)}
|
||||
</Title>
|
||||
{isMobile && (
|
||||
<Text style={{
|
||||
fontSize: 14,
|
||||
fontWeight: 400,
|
||||
color: '#8c8c8c',
|
||||
display: 'block',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
探索世界各地的精彩瞬间
|
||||
</Text>
|
||||
</Title>
|
||||
<Paragraph style={{ color: '#666666' }}>
|
||||
)}
|
||||
<Paragraph style={{
|
||||
color: '#666666',
|
||||
textAlign: isMobile ? 'center' : 'left',
|
||||
marginBottom: isMobile ? 5 : 'inherit'
|
||||
}}>
|
||||
发现创作者分享的高质量摄影作品,获取灵感,找到你喜爱的风格
|
||||
</Paragraph>
|
||||
</Col>
|
||||
|
||||
<Col lg={14} md={12} sm={24} xs={24}>
|
||||
<Row gutter={[16, 16]} justify="end">
|
||||
<Row gutter={[16, 16]} justify={isMobile ? "center" : "end"}>
|
||||
<Col lg={16} md={16} sm={16} xs={24}>
|
||||
<Search
|
||||
placeholder="搜索图片、标签或创作者"
|
||||
allowClear
|
||||
enterButton={<SearchOutlined />}
|
||||
size="large"
|
||||
size={isMobile ? "middle" : "large"}
|
||||
onSearch={(value) => setSearchQuery(value)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
@@ -113,7 +137,7 @@ const PixHub: React.FC = () => {
|
||||
<Col lg={8} md={8} sm={8} xs={24}>
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
size="large"
|
||||
size={isMobile ? "middle" : "large"}
|
||||
value={sortBy}
|
||||
onChange={(value) => setSortBy(value)}
|
||||
suffixIcon={<FilterOutlined />}
|
||||
@@ -128,18 +152,22 @@ const PixHub: React.FC = () => {
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div className="category-nav" style={{ marginBottom: 28, overflowX: 'auto' }}>
|
||||
<Space size={[12, 20]} wrap style={{ justifyContent: 'center' }}>
|
||||
<div className="category-nav" style={{
|
||||
marginBottom: isMobile ? 20 : 28,
|
||||
overflowX: 'auto',
|
||||
paddingBottom: 8
|
||||
}}>
|
||||
<Space size={[isMobile ? 8 : 12, isMobile ? 10 : 20]} wrap style={{ justifyContent: 'center' }}>
|
||||
{categories.map(category => (
|
||||
<Button
|
||||
key={category}
|
||||
type={activeCategory === category ? "primary" : "default"}
|
||||
shape="round"
|
||||
size="large"
|
||||
size={isMobile ? "middle" : "large"}
|
||||
onClick={() => setActiveCategory(category)}
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
minWidth: 80,
|
||||
minWidth: isMobile ? 70 : 80,
|
||||
boxShadow: activeCategory === category ? '0 4px 12px rgba(0,0,0,0.15)' : 'none',
|
||||
}}
|
||||
icon={category === '全部' ? <ThunderboltOutlined /> : null}
|
||||
@@ -151,12 +179,12 @@ const PixHub: React.FC = () => {
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Divider style={{ margin: '24px 0' }} />
|
||||
<Divider style={{ margin: isMobile ? '16px 0' : '24px 0' }} />
|
||||
|
||||
<div className="results-info" style={{ marginBottom: 24 }}>
|
||||
<div className="results-info" style={{ marginBottom: isMobile ? 16 : 24 }}>
|
||||
<Row justify="space-between" align="middle">
|
||||
<Col>
|
||||
<Text style={{ fontSize: 15 }}>
|
||||
<Text style={{ fontSize: isMobile ? 14 : 15 }}>
|
||||
找到 <strong>{totalCount}</strong> 张图片
|
||||
{activeCategory !== '全部' && <span> · {activeCategory}分类</span>}
|
||||
{searchQuery && <span> · 搜索"{searchQuery}"</span>}
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
// 模拟图片数据
|
||||
export const mockImages = [
|
||||
{
|
||||
id: 1,
|
||||
title: '湖畔日落',
|
||||
url: 'https://images.unsplash.com/photo-1501785888041-af3ef285b470?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '风景',
|
||||
tags: ['风景', '日落', '自然'],
|
||||
likes: 342,
|
||||
views: 1205,
|
||||
favorited: false,
|
||||
liked: true,
|
||||
createdAt: '2023-09-15T10:30:00Z',
|
||||
description: '湖边美丽的日落景色,金色的阳光洒在水面上,形成了壮观的景象。',
|
||||
author: {
|
||||
id: 101,
|
||||
name: '王摄影',
|
||||
avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '都市天际线',
|
||||
url: 'https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '城市',
|
||||
tags: ['城市', '建筑', '天际线'],
|
||||
likes: 278,
|
||||
views: 890,
|
||||
favorited: true,
|
||||
liked: false,
|
||||
createdAt: '2023-10-02T14:45:00Z',
|
||||
description: '现代城市的壮观天际线,高楼大厦在夕阳映衬下显得格外壮观。',
|
||||
author: {
|
||||
id: 102,
|
||||
name: '城市猎影',
|
||||
avatar: 'https://randomuser.me/api/portraits/women/44.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '山谷晨雾',
|
||||
url: 'https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '自然',
|
||||
tags: ['山', '晨雾', '自然'],
|
||||
likes: 432,
|
||||
views: 1560,
|
||||
favorited: false,
|
||||
liked: false,
|
||||
createdAt: '2023-08-05T06:15:00Z',
|
||||
description: '清晨的山谷被薄雾笼罩,阳光穿透云层形成光束,构成一幅绝美的画面。',
|
||||
author: {
|
||||
id: 103,
|
||||
name: '山野行者',
|
||||
avatar: 'https://randomuser.me/api/portraits/men/76.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '意式咖啡',
|
||||
url: 'https://images.unsplash.com/photo-1513558161293-cdaf765ed2fd?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '美食',
|
||||
tags: ['咖啡', '美食', '生活'],
|
||||
likes: 189,
|
||||
views: 745,
|
||||
favorited: true,
|
||||
liked: true,
|
||||
createdAt: '2023-10-12T09:20:00Z',
|
||||
description: '精心制作的意式浓缩咖啡,香气四溢,是一天完美的开始。',
|
||||
author: {
|
||||
id: 104,
|
||||
name: '美食家',
|
||||
avatar: 'https://randomuser.me/api/portraits/women/91.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '森林小径',
|
||||
url: 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '自然',
|
||||
tags: ['森林', '自然', '小径'],
|
||||
likes: 256,
|
||||
views: 920,
|
||||
favorited: false,
|
||||
liked: false,
|
||||
createdAt: '2023-09-28T15:30:00Z',
|
||||
description: '穿过茂密森林的小径,阳光透过树叶形成斑驳的光影效果。',
|
||||
author: {
|
||||
id: 105,
|
||||
name: '自然探索者',
|
||||
avatar: 'https://randomuser.me/api/portraits/men/55.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '古典建筑',
|
||||
url: 'https://images.unsplash.com/photo-1496568816309-51d7c20e3b21?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '建筑',
|
||||
tags: ['建筑', '古典', '艺术'],
|
||||
likes: 312,
|
||||
views: 1105,
|
||||
favorited: true,
|
||||
liked: false,
|
||||
createdAt: '2023-07-19T11:40:00Z',
|
||||
description: '充满历史感的欧洲古典建筑,精美的细节和宏伟的设计令人惊叹。',
|
||||
author: {
|
||||
id: 106,
|
||||
name: '建筑师',
|
||||
avatar: 'https://randomuser.me/api/portraits/women/22.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: '夏日海滩',
|
||||
url: 'https://images.unsplash.com/photo-1520454974749-611b7248ffdb?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '旅行',
|
||||
tags: ['海滩', '旅行', '夏日'],
|
||||
likes: 385,
|
||||
views: 1320,
|
||||
favorited: false,
|
||||
liked: true,
|
||||
createdAt: '2023-08-22T13:50:00Z',
|
||||
description: '阳光明媚的夏日海滩,湛蓝的海水和金色的沙滩构成完美的度假胜地。',
|
||||
author: {
|
||||
id: 107,
|
||||
name: '旅行摄影师',
|
||||
avatar: 'https://randomuser.me/api/portraits/men/29.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
title: '雨后街景',
|
||||
url: 'https://images.unsplash.com/photo-1520116468816-95b69f847357?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '城市',
|
||||
tags: ['城市', '雨天', '生活'],
|
||||
likes: 197,
|
||||
views: 876,
|
||||
favorited: true,
|
||||
liked: true,
|
||||
createdAt: '2023-10-05T18:25:00Z',
|
||||
description: '雨后的城市街道,湿漉漉的路面反射着霓虹灯光,营造出独特的都市氛围。',
|
||||
author: {
|
||||
id: 108,
|
||||
name: '城市观察者',
|
||||
avatar: 'https://randomuser.me/api/portraits/women/67.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
title: '宁静湖泊',
|
||||
url: 'https://images.unsplash.com/photo-1508739773434-c26b3d09e071?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '风景',
|
||||
tags: ['湖泊', '风景', '宁静'],
|
||||
likes: 274,
|
||||
views: 945,
|
||||
favorited: false,
|
||||
liked: false,
|
||||
createdAt: '2023-08-30T07:10:00Z',
|
||||
description: '清晨宁静的湖泊,平静的湖面如同一面镜子,倒映着周围的山景和天空。',
|
||||
author: {
|
||||
id: 109,
|
||||
name: '风景摄影家',
|
||||
avatar: 'https://randomuser.me/api/portraits/men/42.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
title: '人像写真',
|
||||
url: 'https://images.unsplash.com/photo-1531746020798-e6953c6e8e04?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '人像',
|
||||
tags: ['人像', '艺术', '写真'],
|
||||
likes: 364,
|
||||
views: 1230,
|
||||
favorited: true,
|
||||
liked: false,
|
||||
createdAt: '2023-09-18T16:40:00Z',
|
||||
description: '艺术风格的人像摄影,通过光影和构图展现主题的个性与魅力。',
|
||||
author: {
|
||||
id: 110,
|
||||
name: '人像摄影师',
|
||||
avatar: 'https://randomuser.me/api/portraits/women/33.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
title: '繁星夜空',
|
||||
url: 'https://images.unsplash.com/photo-1489549132488-d00b7eee80f1?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '自然',
|
||||
tags: ['夜空', '星星', '自然'],
|
||||
likes: 412,
|
||||
views: 1480,
|
||||
favorited: false,
|
||||
liked: true,
|
||||
createdAt: '2023-07-28T22:15:00Z',
|
||||
description: '壮丽的夜空星河,数以万计的星星点缀着黑色的天幕,令人叹为观止。',
|
||||
author: {
|
||||
id: 111,
|
||||
name: '星空摄影师',
|
||||
avatar: 'https://randomuser.me/api/portraits/men/18.jpg',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
title: '历史建筑',
|
||||
url: 'https://images.unsplash.com/photo-1526378722484-bd91ca387e72?ixlib=rb-1.2.1&auto=format&fit=crop&w=1000&q=80',
|
||||
category: '建筑',
|
||||
tags: ['建筑', '历史', '艺术'],
|
||||
likes: 287,
|
||||
views: 1050,
|
||||
favorited: true,
|
||||
liked: false,
|
||||
createdAt: '2023-08-12T10:05:00Z',
|
||||
description: '充满历史感的古老建筑,每一块砖石都诉说着岁月的故事。',
|
||||
author: {
|
||||
id: 112,
|
||||
name: '历史摄影',
|
||||
avatar: 'https://randomuser.me/api/portraits/women/52.jpg',
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -11,13 +11,15 @@ interface ConfigGroupProps {
|
||||
descriptions: {
|
||||
[key: string]: string;
|
||||
};
|
||||
isMobile?: boolean;
|
||||
}
|
||||
|
||||
const ConfigGroup: React.FC<ConfigGroupProps> = ({
|
||||
groupName,
|
||||
configs,
|
||||
onSave,
|
||||
descriptions
|
||||
descriptions,
|
||||
isMobile = false
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
@@ -48,9 +50,10 @@ const ConfigGroup: React.FC<ConfigGroupProps> = ({
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={configs}
|
||||
size={isMobile ? "middle" : "large"}
|
||||
>
|
||||
{Object.keys(configs).map(key => (
|
||||
<Row key={key} gutter={16} align="middle">
|
||||
<Row key={key} gutter={isMobile ? [8, 8] : [16, 16]} align="middle">
|
||||
<Col xs={24} lg={16}>
|
||||
<Form.Item
|
||||
name={key}
|
||||
@@ -72,12 +75,20 @@ const ConfigGroup: React.FC<ConfigGroupProps> = ({
|
||||
)}
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col xs={24} lg={8} style={{ textAlign: 'right' }}>
|
||||
<Col xs={24} lg={8} style={{
|
||||
textAlign: isMobile ? 'left' : 'right',
|
||||
marginTop: isMobile ? -10 : 0,
|
||||
marginBottom: isMobile ? 10 : 0
|
||||
}}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
onClick={() => handleSaveSingle(key)}
|
||||
style={{ marginBottom: 24 }}
|
||||
style={{
|
||||
marginBottom: isMobile ? 16 : 24,
|
||||
width: isMobile ? '100%' : 'auto'
|
||||
}}
|
||||
size={isMobile ? "middle" : "large"}
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
@@ -89,8 +100,9 @@ const ConfigGroup: React.FC<ConfigGroupProps> = ({
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleSaveAll}
|
||||
style={{ marginTop: 16 }}
|
||||
style={{ marginTop: isMobile ? 8 : 16 }}
|
||||
block
|
||||
size={isMobile ? "middle" : "large"}
|
||||
>
|
||||
保存所有
|
||||
</Button>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Tabs, Layout, Menu } from 'antd';
|
||||
import { Tabs, Layout, Menu, Space } from 'antd';
|
||||
import { useAuth } from '../../api/AuthContext';
|
||||
import { UserRole } from '../../api/types';
|
||||
import { useState, type SetStateAction } from 'react';
|
||||
import SystemConfig from './SystemConfig.tsx';
|
||||
import UserProfile from './UserProfile.tsx';
|
||||
import useIsMobile from '../../hooks/useIsMobile';
|
||||
import {
|
||||
UserOutlined,
|
||||
SettingOutlined,
|
||||
@@ -16,6 +17,7 @@ const { Sider, Content } = Layout;
|
||||
|
||||
function Settings() {
|
||||
const { hasRole } = useAuth();
|
||||
const isMobile = useIsMobile();
|
||||
const [activeMenu, setActiveMenu] = useState('profile');
|
||||
const [activeTab, setActiveTab] = useState('basic');
|
||||
|
||||
@@ -106,7 +108,45 @@ function Settings() {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// 手机版使用Tabs作为顶部导航
|
||||
if (isMobile) {
|
||||
return (
|
||||
<div style={{ padding: 0 }}>
|
||||
<Tabs
|
||||
activeKey={activeMenu}
|
||||
onChange={(key) => handleMenuChange(key)}
|
||||
centered
|
||||
size="large"
|
||||
tabBarStyle={{
|
||||
marginBottom: 16,
|
||||
fontWeight: 500,
|
||||
backgroundColor: '#f5f5f5',
|
||||
padding: '8px 0',
|
||||
borderRadius: '8px'
|
||||
}}
|
||||
>
|
||||
{menuItems.map((item) => (
|
||||
<TabPane
|
||||
tab={
|
||||
<Space size={4}>
|
||||
{item?.icon}
|
||||
<span>{item?.label}</span>
|
||||
</Space>
|
||||
}
|
||||
key={item?.key || ''}
|
||||
>
|
||||
<div style={{ padding: '0 4px' }}>
|
||||
{renderContent()}
|
||||
</div>
|
||||
</TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 桌面版使用侧边栏
|
||||
return (
|
||||
<Layout style={{
|
||||
background: '#fff',
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Tabs, Card, message, Spin, Select } from 'antd';
|
||||
import { CloudOutlined, DatabaseOutlined } from '@ant-design/icons';
|
||||
import { getAllConfigs, setConfig } from '../../api';
|
||||
import ConfigGroup from './ConfigGroup.tsx';
|
||||
import useIsMobile from '../../hooks/useIsMobile';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
const { Option } = Select;
|
||||
@@ -14,6 +15,7 @@ interface ConfigStructure {
|
||||
}
|
||||
|
||||
const SystemConfig: React.FC = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [configs, setConfigs] = useState<ConfigStructure>({});
|
||||
const [activeKey, setActiveKey] = useState('AI');
|
||||
@@ -92,13 +94,27 @@ const SystemConfig: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card title="系统配置" className="system-config-card">
|
||||
<Card
|
||||
title="系统配置"
|
||||
className="system-config-card"
|
||||
bodyStyle={{
|
||||
padding: isMobile ? '12px 8px' : '24px'
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<div style={{ textAlign: 'center', padding: '20px' }}>
|
||||
<Spin tip="加载配置中..." />
|
||||
</div>
|
||||
) : (
|
||||
<Tabs activeKey={activeKey} onChange={setActiveKey}>
|
||||
<Tabs
|
||||
activeKey={activeKey}
|
||||
onChange={setActiveKey}
|
||||
size={isMobile ? "small" : "middle"}
|
||||
tabPosition={isMobile ? "top" : "left"}
|
||||
style={{
|
||||
minHeight: isMobile ? 'auto' : 400
|
||||
}}
|
||||
>
|
||||
<TabPane tab="AI 设置" key="AI">
|
||||
<ConfigGroup
|
||||
groupName="AI"
|
||||
@@ -115,6 +131,7 @@ const SystemConfig: React.FC = () => {
|
||||
Model: 'AI 模型名称',
|
||||
EmbeddingModel: '嵌入向量模型名称'
|
||||
}}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
</TabPane>
|
||||
|
||||
@@ -166,14 +183,28 @@ const SystemConfig: React.FC = () => {
|
||||
</TabPane>
|
||||
|
||||
<TabPane tab="存储设置" key="Storage">
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<span style={{ marginRight: 8, display: 'inline-block', width: 100 }}>默认存储:</span>
|
||||
<div style={{
|
||||
marginBottom: isMobile ? 16 : 20,
|
||||
display: 'flex',
|
||||
flexDirection: isMobile ? 'column' : 'row',
|
||||
alignItems: isMobile ? 'flex-start' : 'center',
|
||||
gap: isMobile ? 8 : 0
|
||||
}}>
|
||||
<span style={{
|
||||
marginRight: isMobile ? 0 : 8,
|
||||
display: 'inline-block',
|
||||
width: isMobile ? 'auto' : 100,
|
||||
marginBottom: isMobile ? 4 : 0
|
||||
}}>
|
||||
默认存储:
|
||||
</span>
|
||||
<Select
|
||||
value={configs.Storage?.DefaultStorage || 'Telegram'}
|
||||
onChange={(value) => {
|
||||
handleSaveConfig('Storage', 'DefaultStorage', value);
|
||||
}}
|
||||
style={{ width: 200 }}
|
||||
style={{ width: isMobile ? '100%' : 200 }}
|
||||
size={isMobile ? "middle" : "large"}
|
||||
>
|
||||
{storageOptions.map(option => (
|
||||
<Option key={option.value} value={option.value}>
|
||||
@@ -186,14 +217,28 @@ const SystemConfig: React.FC = () => {
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<span style={{ marginRight: 8, display: 'inline-block', width: 100 }}>配置存储:</span>
|
||||
<div style={{
|
||||
marginBottom: isMobile ? 16 : 20,
|
||||
display: 'flex',
|
||||
flexDirection: isMobile ? 'column' : 'row',
|
||||
alignItems: isMobile ? 'flex-start' : 'center',
|
||||
gap: isMobile ? 8 : 0
|
||||
}}>
|
||||
<span style={{
|
||||
marginRight: isMobile ? 0 : 8,
|
||||
display: 'inline-block',
|
||||
width: isMobile ? 'auto' : 100,
|
||||
marginBottom: isMobile ? 4 : 0
|
||||
}}>
|
||||
配置存储:
|
||||
</span>
|
||||
<Select
|
||||
value={storageType}
|
||||
onChange={(value) => {
|
||||
setStorageType(value);
|
||||
}}
|
||||
style={{ width: 200 }}
|
||||
style={{ width: isMobile ? '100%' : 200 }}
|
||||
size={isMobile ? "middle" : "large"}
|
||||
>
|
||||
{storageOptions.map(option => (
|
||||
<Option key={option.value} value={option.value}>
|
||||
@@ -218,6 +263,7 @@ const SystemConfig: React.FC = () => {
|
||||
"TelegramStorageBotToken": 'Telegram 机器人令牌',
|
||||
"TelegramStorageChatId": 'Telegram 聊天ID'
|
||||
}}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
)}
|
||||
</TabPane>
|
||||
|
||||
@@ -2,24 +2,44 @@ import React from 'react';
|
||||
import { Card, Form, Input, Button } from 'antd';
|
||||
import { useAuth } from '../../api/AuthContext';
|
||||
import UserAvatar from '../../components/UserAvatar';
|
||||
import useIsMobile from '../../hooks/useIsMobile';
|
||||
|
||||
const UserProfile: React.FC = () => {
|
||||
const { user } = useAuth();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Card title="个人资料" style={{ maxWidth: 600 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: 24 }}>
|
||||
<Card
|
||||
title="个人资料"
|
||||
style={{
|
||||
maxWidth: '100%',
|
||||
margin: isMobile ? '0 auto' : '0 auto',
|
||||
boxShadow: isMobile ? 'none' : '0 1px 3px rgba(0,0,0,0.1)'
|
||||
}}
|
||||
bodyStyle={{
|
||||
padding: isMobile ? '16px 12px' : '24px'
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginBottom: isMobile ? 16 : 24
|
||||
}}>
|
||||
<UserAvatar
|
||||
size={100}
|
||||
size={isMobile ? 80 : 100}
|
||||
email={user?.email}
|
||||
text={user?.userName}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Form layout="vertical" initialValues={{
|
||||
username: user?.userName || '',
|
||||
email: user?.email || '',
|
||||
}}>
|
||||
<Form
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
username: user?.userName || '',
|
||||
email: user?.email || '',
|
||||
}}
|
||||
size={isMobile ? "middle" : "large"}
|
||||
>
|
||||
<Form.Item name="username" label="用户名">
|
||||
<Input placeholder="用户名" />
|
||||
</Form.Item>
|
||||
@@ -37,7 +57,16 @@ const UserProfile: React.FC = () => {
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary">保存更改</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
block={isMobile}
|
||||
size={isMobile ? "middle" : "large"}
|
||||
style={{
|
||||
height: isMobile ? 40 : 'auto'
|
||||
}}
|
||||
>
|
||||
保存更改
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user