feat: Enhance mobile UI responsiveness and layout for ChannelsTable and SiderBar
This commit is contained in:
@@ -605,7 +605,7 @@ const ChannelsTable = () => {
|
|||||||
<Button type="primary" onClick={() => setShowColumnSelector(false)}>{t('确定')}</Button>
|
<Button type="primary" onClick={() => setShowColumnSelector(false)}>{t('确定')}</Button>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
style={{ width: 500 }}
|
style={{ width: isMobile() ? '90%' : 500 }}
|
||||||
bodyStyle={{ padding: '24px' }}
|
bodyStyle={{ padding: '24px' }}
|
||||||
>
|
>
|
||||||
<div style={{ marginBottom: 20 }}>
|
<div style={{ marginBottom: 20 }}>
|
||||||
@@ -633,7 +633,11 @@ const ChannelsTable = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={column.key} style={{ width: '50%', marginBottom: 16, paddingRight: 8 }}>
|
<div key={column.key} style={{
|
||||||
|
width: isMobile() ? '100%' : '50%',
|
||||||
|
marginBottom: 16,
|
||||||
|
paddingRight: 8
|
||||||
|
}}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={!!visibleColumns[column.key]}
|
checked={!!visibleColumns[column.key]}
|
||||||
onChange={e => handleColumnVisibilityChange(column.key, e.target.checked)}
|
onChange={e => handleColumnVisibilityChange(column.key, e.target.checked)}
|
||||||
@@ -1253,87 +1257,137 @@ const ChannelsTable = () => {
|
|||||||
<Divider style={{ marginBottom: 15 }} />
|
<Divider style={{ marginBottom: 15 }} />
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: isMobile() ? '' : 'flex',
|
display: 'flex',
|
||||||
|
flexDirection: isMobile() ? 'column' : 'row',
|
||||||
marginTop: isMobile() ? 0 : -45,
|
marginTop: isMobile() ? 0 : -45,
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
pointerEvents: 'none'
|
pointerEvents: 'none'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Space
|
<Space
|
||||||
style={{ pointerEvents: 'auto', marginTop: isMobile() ? 0 : 45 }}
|
style={{
|
||||||
|
pointerEvents: 'auto',
|
||||||
|
marginTop: isMobile() ? 0 : 45,
|
||||||
|
marginBottom: isMobile() ? 16 : 0,
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: isMobile() ? 'wrap' : 'nowrap',
|
||||||
|
gap: '8px'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Typography.Text strong>{t('使用ID排序')}</Typography.Text>
|
<div style={{
|
||||||
<Switch
|
display: 'flex',
|
||||||
checked={idSort}
|
alignItems: 'center',
|
||||||
label={t('使用ID排序')}
|
marginRight: 16,
|
||||||
uncheckedText={t('关')}
|
flexWrap: 'nowrap'
|
||||||
aria-label={t('是否用ID排序')}
|
}}>
|
||||||
onChange={(v) => {
|
<Typography.Text strong style={{ marginRight: 8 }}>{t('使用ID排序')}</Typography.Text>
|
||||||
localStorage.setItem('id-sort', v + '');
|
<Switch
|
||||||
setIdSort(v);
|
checked={idSort}
|
||||||
loadChannels(0, pageSize, v, enableTagMode)
|
label={t('使用ID排序')}
|
||||||
.then()
|
uncheckedText={t('关')}
|
||||||
.catch((reason) => {
|
aria-label={t('是否用ID排序')}
|
||||||
showError(reason);
|
onChange={(v) => {
|
||||||
});
|
localStorage.setItem('id-sort', v + '');
|
||||||
}}
|
setIdSort(v);
|
||||||
></Switch>
|
loadChannels(0, pageSize, v, enableTagMode)
|
||||||
<Button
|
.then()
|
||||||
theme="light"
|
.catch((reason) => {
|
||||||
type="primary"
|
showError(reason);
|
||||||
style={{ marginRight: 8 }}
|
});
|
||||||
onClick={() => {
|
}}
|
||||||
setEditingChannel({
|
></Switch>
|
||||||
id: undefined
|
</div>
|
||||||
});
|
|
||||||
setShowEdit(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('添加渠道')}
|
|
||||||
</Button>
|
|
||||||
<Popconfirm
|
|
||||||
title={t('确定?')}
|
|
||||||
okType={'warning'}
|
|
||||||
onConfirm={testAllChannels}
|
|
||||||
position={isMobile() ? 'top' : 'top'}
|
|
||||||
>
|
|
||||||
<Button theme="light" type="warning" style={{ marginRight: 8 }}>
|
|
||||||
{t('测试所有通道')}
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
<Popconfirm
|
|
||||||
title={t('确定?')}
|
|
||||||
okType={'secondary'}
|
|
||||||
onConfirm={updateAllChannelsBalance}
|
|
||||||
>
|
|
||||||
<Button theme="light" type="secondary" style={{ marginRight: 8 }}>
|
|
||||||
{t('更新所有已启用通道余额')}
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
<Popconfirm
|
|
||||||
title={t('确定是否要删除禁用通道?')}
|
|
||||||
content={t('此修改将不可逆')}
|
|
||||||
okType={'danger'}
|
|
||||||
onConfirm={deleteAllDisabledChannels}
|
|
||||||
>
|
|
||||||
<Button theme="light" type="danger" style={{ marginRight: 8 }}>
|
|
||||||
{t('删除禁用通道')}
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
|
|
||||||
<Button
|
<div style={{
|
||||||
theme="light"
|
display: 'flex',
|
||||||
type="primary"
|
flexWrap: 'wrap',
|
||||||
style={{ marginRight: 8 }}
|
gap: '8px'
|
||||||
onClick={refresh}
|
}}>
|
||||||
>
|
<Button
|
||||||
{t('刷新')}
|
theme="light"
|
||||||
</Button>
|
type="primary"
|
||||||
|
icon={<IconPlus />}
|
||||||
|
onClick={() => {
|
||||||
|
setEditingChannel({
|
||||||
|
id: undefined
|
||||||
|
});
|
||||||
|
setShowEdit(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('添加渠道')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
theme="light"
|
||||||
|
type="primary"
|
||||||
|
icon={<IconRefresh />}
|
||||||
|
onClick={refresh}
|
||||||
|
>
|
||||||
|
{t('刷新')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Dropdown
|
||||||
|
trigger="click"
|
||||||
|
render={
|
||||||
|
<Dropdown.Menu>
|
||||||
|
<Dropdown.Item>
|
||||||
|
<Popconfirm
|
||||||
|
title={t('确定?')}
|
||||||
|
okType={'warning'}
|
||||||
|
onConfirm={testAllChannels}
|
||||||
|
position={isMobile() ? 'top' : 'top'}
|
||||||
|
>
|
||||||
|
<Button theme="light" type="warning" style={{ width: '100%' }}>
|
||||||
|
{t('测试所有通道')}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Dropdown.Item>
|
||||||
|
<Dropdown.Item>
|
||||||
|
<Popconfirm
|
||||||
|
title={t('确定?')}
|
||||||
|
okType={'secondary'}
|
||||||
|
onConfirm={updateAllChannelsBalance}
|
||||||
|
>
|
||||||
|
<Button theme="light" type="secondary" style={{ width: '100%' }}>
|
||||||
|
{t('更新所有已启用通道余额')}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Dropdown.Item>
|
||||||
|
<Dropdown.Item>
|
||||||
|
<Popconfirm
|
||||||
|
title={t('确定是否要删除禁用通道?')}
|
||||||
|
content={t('此修改将不可逆')}
|
||||||
|
okType={'danger'}
|
||||||
|
onConfirm={deleteAllDisabledChannels}
|
||||||
|
>
|
||||||
|
<Button theme="light" type="danger" style={{ width: '100%' }}>
|
||||||
|
{t('删除禁用通道')}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
</Dropdown.Item>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button theme="light" type="tertiary" icon={<IconSetting />}>
|
||||||
|
{t('批量操作')}
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 20 }}>
|
<div style={{
|
||||||
<Space>
|
marginTop: 20,
|
||||||
<Typography.Text strong>{t('开启批量操作')}</Typography.Text>
|
display: 'flex',
|
||||||
|
flexDirection: isMobile() ? 'column' : 'row',
|
||||||
|
alignItems: isMobile() ? 'flex-start' : 'center',
|
||||||
|
gap: isMobile() ? '8px' : '16px'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: isMobile() ? 8 : 0
|
||||||
|
}}>
|
||||||
|
<Typography.Text strong style={{ marginRight: 8 }}>{t('开启批量操作')}</Typography.Text>
|
||||||
<Switch
|
<Switch
|
||||||
label={t('开启批量操作')}
|
label={t('开启批量操作')}
|
||||||
uncheckedText={t('关')}
|
uncheckedText={t('关')}
|
||||||
@@ -1341,20 +1395,25 @@ const ChannelsTable = () => {
|
|||||||
onChange={(v) => {
|
onChange={(v) => {
|
||||||
setEnableBatchDelete(v);
|
setEnableBatchDelete(v);
|
||||||
}}
|
}}
|
||||||
></Switch>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '8px'
|
||||||
|
}}>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={t('确定是否要删除所选通道?')}
|
title={t('确定是否要删除所选通道?')}
|
||||||
content={t('此修改将不可逆')}
|
content={t('此修改将不可逆')}
|
||||||
okType={'danger'}
|
okType={'danger'}
|
||||||
onConfirm={batchDeleteChannels}
|
onConfirm={batchDeleteChannels}
|
||||||
disabled={!enableBatchDelete}
|
disabled={!enableBatchDelete}
|
||||||
position={'top'}
|
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
disabled={!enableBatchDelete}
|
disabled={!enableBatchDelete}
|
||||||
theme="light"
|
theme="light"
|
||||||
type="danger"
|
type="danger"
|
||||||
style={{ marginRight: 8 }}
|
|
||||||
>
|
>
|
||||||
{t('删除所选通道')}
|
{t('删除所选通道')}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -1364,17 +1423,27 @@ const ChannelsTable = () => {
|
|||||||
content={t('进行该操作时,可能导致渠道访问错误,请仅在数据库出现问题时使用')}
|
content={t('进行该操作时,可能导致渠道访问错误,请仅在数据库出现问题时使用')}
|
||||||
okType={'warning'}
|
okType={'warning'}
|
||||||
onConfirm={fixChannelsAbilities}
|
onConfirm={fixChannelsAbilities}
|
||||||
position={'top'}
|
|
||||||
>
|
>
|
||||||
<Button theme="light" type="secondary" style={{ marginRight: 8 }}>
|
<Button theme="light" type="secondary">
|
||||||
{t('修复数据库一致性')}
|
{t('修复数据库一致性')}
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</Space>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 20 }}>
|
|
||||||
<Space>
|
<div style={{
|
||||||
<Typography.Text strong>{t('标签聚合模式')}</Typography.Text>
|
marginTop: 20,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: isMobile() ? 'column' : 'row',
|
||||||
|
alignItems: isMobile() ? 'flex-start' : 'center',
|
||||||
|
gap: isMobile() ? '8px' : '16px'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: isMobile() ? 8 : 0
|
||||||
|
}}>
|
||||||
|
<Typography.Text strong style={{ marginRight: 8 }}>{t('标签聚合模式')}</Typography.Text>
|
||||||
<Switch
|
<Switch
|
||||||
checked={enableTagMode}
|
checked={enableTagMode}
|
||||||
label={t('标签聚合模式')}
|
label={t('标签聚合模式')}
|
||||||
@@ -1385,28 +1454,33 @@ const ChannelsTable = () => {
|
|||||||
loadChannels(0, pageSize, idSort, v);
|
loadChannels(0, pageSize, idSort, v);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '8px'
|
||||||
|
}}>
|
||||||
<Button
|
<Button
|
||||||
disabled={!enableBatchDelete}
|
disabled={!enableBatchDelete}
|
||||||
theme="light"
|
theme="light"
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ marginRight: 8 }}
|
|
||||||
onClick={() => setShowBatchSetTag(true)}
|
onClick={() => setShowBatchSetTag(true)}
|
||||||
>
|
>
|
||||||
{t('批量设置标签')}
|
{t('批量设置标签')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
theme="light"
|
theme="light"
|
||||||
type="tertiary"
|
type="tertiary"
|
||||||
icon={<IconSetting />}
|
icon={<IconSetting />}
|
||||||
onClick={() => setShowColumnSelector(true)}
|
onClick={() => setShowColumnSelector(true)}
|
||||||
style={{ marginRight: 8 }}
|
|
||||||
>
|
>
|
||||||
{t('列设置')}
|
{t('列设置')}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
columns={getVisibleColumns()}
|
columns={getVisibleColumns()}
|
||||||
@@ -1423,6 +1497,7 @@ const ChannelsTable = () => {
|
|||||||
},
|
},
|
||||||
onPageChange: handlePageChange
|
onPageChange: handlePageChange
|
||||||
}}
|
}}
|
||||||
|
expandAllRows={false}
|
||||||
onRow={handleRow}
|
onRow={handleRow}
|
||||||
rowSelection={
|
rowSelection={
|
||||||
enableBatchDelete
|
enableBatchDelete
|
||||||
@@ -1442,6 +1517,7 @@ const ChannelsTable = () => {
|
|||||||
onCancel={() => setShowBatchSetTag(false)}
|
onCancel={() => setShowBatchSetTag(false)}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
centered={true}
|
centered={true}
|
||||||
|
style={{ width: isMobile() ? '90%' : 500 }}
|
||||||
>
|
>
|
||||||
<div style={{ marginBottom: 20 }}>
|
<div style={{ marginBottom: 20 }}>
|
||||||
<Typography.Text>{t('请输入要设置的标签名称')}</Typography.Text>
|
<Typography.Text>{t('请输入要设置的标签名称')}</Typography.Text>
|
||||||
@@ -1450,7 +1526,13 @@ const ChannelsTable = () => {
|
|||||||
placeholder={t('请输入标签名称')}
|
placeholder={t('请输入标签名称')}
|
||||||
value={batchSetTagValue}
|
value={batchSetTagValue}
|
||||||
onChange={(v) => setBatchSetTagValue(v)}
|
onChange={(v) => setBatchSetTagValue(v)}
|
||||||
|
size="large"
|
||||||
/>
|
/>
|
||||||
|
<div style={{ marginTop: 16 }}>
|
||||||
|
<Typography.Text type="secondary">
|
||||||
|
{t('已选择 ${count} 个渠道').replace('${count}', selectedChannels.length)}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{/* 模型测试弹窗 */}
|
{/* 模型测试弹窗 */}
|
||||||
@@ -1464,7 +1546,6 @@ const ChannelsTable = () => {
|
|||||||
footer={null}
|
footer={null}
|
||||||
maskClosable={true}
|
maskClosable={true}
|
||||||
centered={true}
|
centered={true}
|
||||||
width={600}
|
|
||||||
>
|
>
|
||||||
<div style={{ maxHeight: '500px', overflowY: 'auto', padding: '10px' }}>
|
<div style={{ maxHeight: '500px', overflowY: 'auto', padding: '10px' }}>
|
||||||
{currentTestChannel && (
|
{currentTestChannel && (
|
||||||
@@ -1477,8 +1558,9 @@ const ChannelsTable = () => {
|
|||||||
<Input
|
<Input
|
||||||
placeholder={t('搜索模型...')}
|
placeholder={t('搜索模型...')}
|
||||||
value={modelSearchKeyword}
|
value={modelSearchKeyword}
|
||||||
onChange={(value) => setModelSearchKeyword(value)}
|
onChange={(v) => setModelSearchKeyword(v)}
|
||||||
style={{ marginBottom: '16px' }}
|
style={{ marginBottom: '16px' }}
|
||||||
|
prefix={<IconFilter />}
|
||||||
showClear
|
showClear
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState, useContext } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { getFooterHTML, getSystemName } from '../helpers';
|
import { getFooterHTML, getSystemName } from '../helpers';
|
||||||
import { Layout, Tooltip } from '@douyinfe/semi-ui';
|
import { Layout, Tooltip } from '@douyinfe/semi-ui';
|
||||||
|
import { StyleContext } from '../context/Style/index.js';
|
||||||
|
|
||||||
const FooterBar = () => {
|
const FooterBar = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const systemName = getSystemName();
|
const systemName = getSystemName();
|
||||||
const [footer, setFooter] = useState(getFooterHTML());
|
const [footer, setFooter] = useState(getFooterHTML());
|
||||||
|
const [styleState] = useContext(StyleContext);
|
||||||
let remainCheckTimes = 5;
|
let remainCheckTimes = 5;
|
||||||
|
|
||||||
const loadFooter = () => {
|
const loadFooter = () => {
|
||||||
@@ -57,7 +59,10 @@ const FooterBar = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
paddingBottom: styleState?.isMobile ? '112px' : '5px',
|
||||||
|
}}>
|
||||||
{footer ? (
|
{footer ? (
|
||||||
<div
|
<div
|
||||||
className='custom-footer'
|
className='custom-footer'
|
||||||
|
|||||||
@@ -71,7 +71,12 @@ const PageLayout = () => {
|
|||||||
const isSidebarCollapsed = localStorage.getItem('default_collapse_sidebar') === 'true';
|
const isSidebarCollapsed = localStorage.getItem('default_collapse_sidebar') === 'true';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
<Layout style={{
|
||||||
|
height: '100vh',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
<Header style={{
|
<Header style={{
|
||||||
padding: 0,
|
padding: 0,
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
@@ -87,44 +92,49 @@ const PageLayout = () => {
|
|||||||
<Layout style={{
|
<Layout style={{
|
||||||
marginTop: '56px',
|
marginTop: '56px',
|
||||||
height: 'calc(100vh - 56px)',
|
height: 'calc(100vh - 56px)',
|
||||||
overflow: styleState.isMobile ? 'auto' : 'hidden'
|
overflow: 'auto',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
}}>
|
}}>
|
||||||
{styleState.showSider && (
|
{styleState.showSider && (
|
||||||
<Sider style={{
|
<Sider style={{
|
||||||
height: 'calc(100vh - 56px)',
|
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
left: 0,
|
left: 0,
|
||||||
top: '56px',
|
top: '56px',
|
||||||
zIndex: 90,
|
zIndex: 99,
|
||||||
overflowY: 'auto',
|
background: 'var(--semi-color-bg-1)',
|
||||||
overflowX: 'hidden',
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||||||
width: 'auto',
|
|
||||||
background: 'transparent',
|
|
||||||
boxShadow: 'none',
|
|
||||||
border: 'none',
|
border: 'none',
|
||||||
paddingRight: '5px'
|
paddingRight: '0',
|
||||||
|
transition: 'transform 0.3s ease',
|
||||||
|
height: 'calc(100vh - 56px)',
|
||||||
}}>
|
}}>
|
||||||
<SiderBar />
|
<SiderBar />
|
||||||
</Sider>
|
</Sider>
|
||||||
)}
|
)}
|
||||||
<Layout style={{
|
<Layout style={{
|
||||||
marginLeft: styleState.showSider ? (isSidebarCollapsed ? '60px' : '200px') : '0',
|
marginLeft: styleState.isMobile ? '0' : (styleState.showSider ? (isSidebarCollapsed ? '60px' : '200px') : '0'),
|
||||||
transition: 'margin-left 0.3s ease',
|
transition: 'margin-left 0.3s ease',
|
||||||
height: '100%',
|
flex: '1 1 auto',
|
||||||
overflow: 'auto'
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
}}>
|
}}>
|
||||||
<Content
|
<Content
|
||||||
style={{
|
style={{
|
||||||
height: '100%',
|
flex: '1 0 auto',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
WebkitOverflowScrolling: 'touch',
|
WebkitOverflowScrolling: 'touch',
|
||||||
padding: styleState.shouldInnerPadding? '24px': '0',
|
padding: styleState.shouldInnerPadding? '24px': '0',
|
||||||
position: 'relative'
|
position: 'relative',
|
||||||
|
paddingBottom: styleState.isMobile ? '80px' : '0' // 移动端底部额外内边距
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<App />
|
<App />
|
||||||
</Content>
|
</Content>
|
||||||
<Layout.Footer>
|
<Layout.Footer style={{
|
||||||
|
flex: '0 0 auto',
|
||||||
|
width: '100%'
|
||||||
|
}}>
|
||||||
<FooterBar />
|
<FooterBar />
|
||||||
</Layout.Footer>
|
</Layout.Footer>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { setStatusData } from '../helpers/data.js';
|
|||||||
import { stringToColor } from '../helpers/render.js';
|
import { stringToColor } from '../helpers/render.js';
|
||||||
import { useSetTheme, useTheme } from '../context/Theme/index.js';
|
import { useSetTheme, useTheme } from '../context/Theme/index.js';
|
||||||
import { StyleContext } from '../context/Style/index.js';
|
import { StyleContext } from '../context/Style/index.js';
|
||||||
|
import Text from '@douyinfe/semi-ui/lib/es/typography/text';
|
||||||
|
|
||||||
// 自定义侧边栏按钮样式
|
// 自定义侧边栏按钮样式
|
||||||
const navItemStyle = {
|
const navItemStyle = {
|
||||||
@@ -298,16 +299,16 @@ const SiderBar = () => {
|
|||||||
className="custom-sidebar-nav"
|
className="custom-sidebar-nav"
|
||||||
style={{
|
style={{
|
||||||
width: isCollapsed ? '60px' : '200px',
|
width: isCollapsed ? '60px' : '200px',
|
||||||
height: '100%',
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||||||
boxShadow: '0 1px 6px rgba(0, 0, 0, 0.08)',
|
|
||||||
borderRight: '1px solid var(--semi-color-border)',
|
borderRight: '1px solid var(--semi-color-border)',
|
||||||
background: 'var(--semi-color-bg-0)',
|
background: 'var(--semi-color-bg-1)',
|
||||||
borderRadius: '0 8px 8px 0',
|
borderRadius: styleState.isMobile ? '0' : '0 8px 8px 0',
|
||||||
transition: 'all 0.3s ease',
|
transition: 'all 0.3s ease',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
zIndex: 95,
|
zIndex: 95,
|
||||||
|
height: '100%',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
WebkitOverflowScrolling: 'touch' // Improve scrolling on iOS devices
|
WebkitOverflowScrolling: 'touch', // Improve scrolling on iOS devices
|
||||||
}}
|
}}
|
||||||
defaultIsCollapsed={
|
defaultIsCollapsed={
|
||||||
localStorage.getItem('default_collapse_sidebar') === 'true'
|
localStorage.getItem('default_collapse_sidebar') === 'true'
|
||||||
@@ -419,7 +420,7 @@ const SiderBar = () => {
|
|||||||
<Divider style={dividerStyle} />
|
<Divider style={dividerStyle} />
|
||||||
|
|
||||||
{/* Workspace Section */}
|
{/* Workspace Section */}
|
||||||
{!isCollapsed && <div style={groupLabelStyle}>{t('控制台')}</div>}
|
{!isCollapsed && <Text style={groupLabelStyle}>{t('控制台')}</Text>}
|
||||||
{workspaceItems.map((item) => (
|
{workspaceItems.map((item) => (
|
||||||
<Nav.Item
|
<Nav.Item
|
||||||
key={item.itemKey}
|
key={item.itemKey}
|
||||||
@@ -436,7 +437,7 @@ const SiderBar = () => {
|
|||||||
<Divider style={dividerStyle} />
|
<Divider style={dividerStyle} />
|
||||||
|
|
||||||
{/* Admin Section */}
|
{/* Admin Section */}
|
||||||
{!isCollapsed && <div style={groupLabelStyle}>{t('管理员')}</div>}
|
{!isCollapsed && <Text style={groupLabelStyle}>{t('管理员')}</Text>}
|
||||||
{adminItems.map((item) => (
|
{adminItems.map((item) => (
|
||||||
<Nav.Item
|
<Nav.Item
|
||||||
key={item.itemKey}
|
key={item.itemKey}
|
||||||
@@ -453,7 +454,7 @@ const SiderBar = () => {
|
|||||||
<Divider style={dividerStyle} />
|
<Divider style={dividerStyle} />
|
||||||
|
|
||||||
{/* Finance Management Section */}
|
{/* Finance Management Section */}
|
||||||
{!isCollapsed && <div style={groupLabelStyle}>{t('个人中心')}</div>}
|
{!isCollapsed && <Text style={groupLabelStyle}>{t('个人中心')}</Text>}
|
||||||
{financeItems.map((item) => (
|
{financeItems.map((item) => (
|
||||||
<Nav.Item
|
<Nav.Item
|
||||||
key={item.itemKey}
|
key={item.itemKey}
|
||||||
@@ -465,12 +466,10 @@ const SiderBar = () => {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<Nav.Footer
|
<Nav.Footer
|
||||||
collapseButton={true}
|
|
||||||
style={{
|
style={{
|
||||||
borderTop: '1px solid var(--semi-color-border)',
|
paddingBottom: styleState?.isMobile ? '112px' : '0',
|
||||||
padding: '12px 0',
|
|
||||||
marginTop: 'auto'
|
|
||||||
}}
|
}}
|
||||||
|
collapseButton={true}
|
||||||
collapseText={(collapsed)=>
|
collapseText={(collapsed)=>
|
||||||
{
|
{
|
||||||
if(collapsed){
|
if(collapsed){
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ body {
|
|||||||
.semi-navigation-horizontal .semi-navigation-header {
|
.semi-navigation-horizontal .semi-navigation-header {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 确保移动端内容可滚动 */
|
||||||
|
.semi-layout-content {
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 隐藏在移动设备上 */
|
||||||
|
.hide-on-mobile {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.semi-table-tbody > .semi-table-row > .semi-table-row-cell {
|
.semi-table-tbody > .semi-table-row > .semi-table-row-cell {
|
||||||
@@ -162,14 +172,14 @@ code {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.semi-navigation-vertical {
|
/*.semi-navigation-vertical {*/
|
||||||
/*flex: 0 0 auto;*/
|
/* !*flex: 0 0 auto;*!*/
|
||||||
/*display: flex;*/
|
/* !*display: flex;*!*/
|
||||||
/*flex-direction: column;*/
|
/* !*flex-direction: column;*!*/
|
||||||
/*width: 100%;*/
|
/* !*width: 100%;*!*/
|
||||||
height: 100%;
|
/* height: 100%;*/
|
||||||
overflow: hidden;
|
/* overflow: hidden;*/
|
||||||
}
|
/*}*/
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
@@ -184,12 +194,6 @@ code {
|
|||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 600px) {
|
|
||||||
.hide-on-mobile {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 顶部栏样式 */
|
/* 顶部栏样式 */
|
||||||
.topnav {
|
.topnav {
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
@@ -248,8 +252,9 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Custom sidebar shadow */
|
/* Custom sidebar shadow */
|
||||||
.custom-sidebar-nav {
|
/*.custom-sidebar-nav {*/
|
||||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08) !important;
|
/* box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08) !important;*/
|
||||||
-webkit-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08) !important;
|
/* -webkit-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08) !important;*/
|
||||||
-moz-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08) !important;
|
/* -moz-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08) !important;*/
|
||||||
}
|
/* min-height: 100%;*/
|
||||||
|
/*}*/
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:3000',
|
target: 'http://localhost:3000',
|
||||||
|
|||||||
Reference in New Issue
Block a user