style:1.组织管理页面支持组织列表滚动 2.优化一些页面的标题和图标显示
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 275 KiB |
@@ -3,3 +3,17 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 移动端按钮文本优化 */
|
||||
@media (max-width: 576px) {
|
||||
.button-text-mobile {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 小屏幕下按钮文字可以隐藏,只保留图标 */
|
||||
@media (max-width: 480px) {
|
||||
.button-text-mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
GithubOutlined,
|
||||
ReloadOutlined,
|
||||
ClockCircleOutlined,
|
||||
SyncOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
fetchChangelog,
|
||||
@@ -29,6 +30,7 @@ interface ChangelogModalProps {
|
||||
// 提交类型图标和颜色配置
|
||||
const typeConfig: Record<ChangelogEntry['type'], { icon: React.ReactNode; color: string; label: string }> = {
|
||||
feature: { icon: <StarOutlined />, color: 'green', label: '新功能' },
|
||||
update: { icon: <SyncOutlined />, color: 'geekblue', label: '更新' },
|
||||
fix: { icon: <BugOutlined />, color: 'red', label: '修复' },
|
||||
docs: { icon: <FileTextOutlined />, color: 'blue', label: '文档' },
|
||||
style: { icon: <BgColorsOutlined />, color: 'purple', label: '样式' },
|
||||
|
||||
@@ -283,7 +283,10 @@ export default function Careers() {
|
||||
flexWrap: 'wrap',
|
||||
gap: '12px'
|
||||
}}>
|
||||
<Title level={3} style={{ margin: 0 }}>职业管理</Title>
|
||||
<Title level={3} style={{ margin: 0 }}>
|
||||
<TrophyOutlined style={{ marginRight: 8 }} />
|
||||
职业管理
|
||||
</Title>
|
||||
<Space wrap>
|
||||
<Button
|
||||
type="dashed"
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
LeftOutlined,
|
||||
RightOutlined,
|
||||
UnorderedListOutlined,
|
||||
FundOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import api from '../services/api';
|
||||
@@ -189,14 +190,30 @@ const ChapterAnalysis: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
height: '100%',
|
||||
gap: isMobile ? 0 : 16,
|
||||
flexDirection: isMobile ? 'column' : 'row'
|
||||
}}>
|
||||
{/* 左侧章节列表 - 桌面端 */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
{/* 页面标题 - 仅桌面端显示 */}
|
||||
{!isMobile && (
|
||||
<div style={{
|
||||
padding: '16px 0',
|
||||
marginBottom: 16,
|
||||
borderBottom: '1px solid #f0f0f0'
|
||||
}}>
|
||||
<h2 style={{ margin: 0, fontSize: 24 }}>
|
||||
<FundOutlined style={{ marginRight: 8 }} />
|
||||
剧情分析
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
gap: isMobile ? 0 : 16,
|
||||
flexDirection: isMobile ? 'column' : 'row',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{/* 左侧章节列表 - 桌面端 */}
|
||||
{!isMobile && (
|
||||
<Card
|
||||
title="章节列表"
|
||||
style={{ width: 280, height: '100%', overflow: 'hidden' }}
|
||||
@@ -237,9 +254,9 @@ const ChapterAnalysis: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* 移动端章节列表抽屉 */}
|
||||
{/* 移动端章节列表抽屉 */}
|
||||
{isMobile && (
|
||||
<Drawer
|
||||
title="章节列表"
|
||||
@@ -284,10 +301,10 @@ const ChapterAnalysis: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
</Drawer>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* 右侧内容区域 */}
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
{/* 右侧内容区域 */}
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
{!selectedChapter ? (
|
||||
<Card style={{ height: '100%' }}>
|
||||
<Empty description="请从左侧选择一个章节查看" style={{ marginTop: 100 }} />
|
||||
@@ -530,6 +547,7 @@ const ChapterAnalysis: React.FC = () => {
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1491,7 +1491,10 @@ export default function Chapters() {
|
||||
justifyContent: 'space-between',
|
||||
alignItems: isMobile ? 'stretch' : 'center'
|
||||
}}>
|
||||
<h2 style={{ margin: 0, fontSize: isMobile ? 18 : 24 }}>章节管理</h2>
|
||||
<h2 style={{ margin: 0, fontSize: isMobile ? 18 : 24 }}>
|
||||
<BookOutlined style={{ marginRight: 8 }} />
|
||||
章节管理
|
||||
</h2>
|
||||
<Space direction={isMobile ? 'vertical' : 'horizontal'} style={{ width: isMobile ? '100%' : 'auto' }}>
|
||||
{currentProject.outline_mode === 'one-to-many' && (
|
||||
<Button
|
||||
|
||||
@@ -382,7 +382,10 @@ export default function Characters() {
|
||||
justifyContent: 'space-between',
|
||||
alignItems: isMobile ? 'stretch' : 'center'
|
||||
}}>
|
||||
<h2 style={{ margin: 0, fontSize: isMobile ? 18 : 24 }}>角色与组织管理</h2>
|
||||
<h2 style={{ margin: 0, fontSize: isMobile ? 18 : 24 }}>
|
||||
<TeamOutlined style={{ marginRight: 8 }} />
|
||||
角色与组织管理
|
||||
</h2>
|
||||
<Space wrap>
|
||||
<Button
|
||||
type="primary"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Card, Table, Tag, Button, Space, message, Modal, Form, Select, InputNumber, Input, Descriptions } from 'antd';
|
||||
import { PlusOutlined, TeamOutlined, UserOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import { Card, Table, Tag, Button, Space, message, Modal, Form, Select, InputNumber, Input, Descriptions, Drawer } from 'antd';
|
||||
import { PlusOutlined, UserOutlined, EditOutlined, DeleteOutlined, UnorderedListOutlined, BankOutlined } from '@ant-design/icons';
|
||||
import { useStore } from '../store';
|
||||
import { useCharacterSync } from '../store/hooks';
|
||||
import axios from 'axios';
|
||||
@@ -57,6 +57,7 @@ export default function Organizations() {
|
||||
const [editOrgForm] = Form.useForm();
|
||||
const [isMobile, setIsMobile] = useState(window.innerWidth <= 768);
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
const [orgListVisible, setOrgListVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
@@ -82,7 +83,7 @@ export default function Organizations() {
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [projectId, selectedOrg]);
|
||||
}, [projectId]);
|
||||
|
||||
const loadCharacters = useCallback(async () => {
|
||||
try {
|
||||
@@ -300,61 +301,169 @@ export default function Organizations() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
{contextHolder}
|
||||
<Card
|
||||
title={
|
||||
<Space wrap>
|
||||
<TeamOutlined />
|
||||
<span style={{ fontSize: isMobile ? 14 : 16 }}>组织管理</span>
|
||||
{!isMobile && <Tag color="blue">{currentProject?.title}</Tag>}
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
|
||||
{/* 页面标题 - 仅桌面端显示 */}
|
||||
{!isMobile && (
|
||||
<div style={{
|
||||
display: isMobile ? 'flex' : 'grid',
|
||||
flexDirection: isMobile ? 'column' : undefined,
|
||||
gridTemplateColumns: isMobile ? undefined : '300px 1fr',
|
||||
gap: isMobile ? '16px' : '24px',
|
||||
maxHeight: isMobile ? 'calc(100vh - 200px)' : undefined,
|
||||
overflowY: isMobile ? 'auto' : undefined
|
||||
padding: '16px 0',
|
||||
marginBottom: 16,
|
||||
borderBottom: '1px solid #f0f0f0'
|
||||
}}>
|
||||
{/* 左侧:组织列表 */}
|
||||
<div>
|
||||
<Card
|
||||
size="small"
|
||||
title={`组织列表 (${organizations.length})`}
|
||||
loading={loading}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
{organizations.map(org => (
|
||||
<Card
|
||||
key={org.id}
|
||||
size="small"
|
||||
hoverable
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
border: selectedOrg?.id === org.id ? '2px solid var(--color-primary)' : '1px solid var(--color-border-secondary)'
|
||||
}}
|
||||
onClick={() => handleSelectOrganization(org)}
|
||||
>
|
||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||
<strong>{org.name}</strong>
|
||||
<Tag>{org.type}</Tag>
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
成员: {org.member_count} | 势力: {org.power_level}
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
</Card>
|
||||
</div>
|
||||
<h2 style={{ margin: 0, fontSize: 24 }}>
|
||||
<BankOutlined style={{ marginRight: 8 }} />
|
||||
组织管理
|
||||
</h2>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
gap: isMobile ? 0 : 16,
|
||||
flexDirection: isMobile ? 'column' : 'row',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{/* 左侧组织列表 - 桌面端 */}
|
||||
{!isMobile && (
|
||||
<Card
|
||||
title={`组织列表 (${organizations.length})`}
|
||||
style={{ width: 300, height: '100%', overflow: 'hidden' }}
|
||||
bodyStyle={{ padding: 0, height: 'calc(100% - 57px)', overflow: 'auto' }}
|
||||
loading={loading}
|
||||
>
|
||||
{organizations.length === 0 ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px 20px', color: '#999' }}>
|
||||
暂无组织
|
||||
</div>
|
||||
) : (
|
||||
<Space direction="vertical" style={{ width: '100%', padding: '12px' }}>
|
||||
{organizations.map(org => (
|
||||
<Card
|
||||
key={org.id}
|
||||
size="small"
|
||||
hoverable
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
border: selectedOrg?.id === org.id ? '2px solid #1890ff' : '1px solid #d9d9d9',
|
||||
background: selectedOrg?.id === org.id ? '#e6f7ff' : 'transparent'
|
||||
}}
|
||||
onClick={() => handleSelectOrganization(org)}
|
||||
>
|
||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||
<strong style={{ fontSize: 14 }}>{org.name}</strong>
|
||||
<Tag color="blue">{org.type}</Tag>
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
成员: {org.member_count} | 势力: {org.power_level}
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 右侧:组织详情和成员 */}
|
||||
<div style={{ minHeight: isMobile ? 'auto' : undefined }}>
|
||||
{selectedOrg ? (
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
||||
{/* 移动端组织列表抽屉 */}
|
||||
{isMobile && (
|
||||
<Drawer
|
||||
title="组织列表"
|
||||
placement="left"
|
||||
onClose={() => setOrgListVisible(false)}
|
||||
open={orgListVisible}
|
||||
width="85%"
|
||||
styles={{ body: { padding: 0 } }}
|
||||
>
|
||||
{organizations.length === 0 ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px 20px', color: '#999' }}>
|
||||
暂无组织
|
||||
</div>
|
||||
) : (
|
||||
<Space direction="vertical" style={{ width: '100%', padding: '12px' }}>
|
||||
{organizations.map(org => (
|
||||
<Card
|
||||
key={org.id}
|
||||
size="small"
|
||||
hoverable
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
border: selectedOrg?.id === org.id ? '2px solid #1890ff' : '1px solid #d9d9d9',
|
||||
background: selectedOrg?.id === org.id ? '#e6f7ff' : 'transparent'
|
||||
}}
|
||||
onClick={() => {
|
||||
handleSelectOrganization(org);
|
||||
setOrgListVisible(false);
|
||||
}}
|
||||
>
|
||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
||||
<strong style={{ fontSize: 14 }}>{org.name}</strong>
|
||||
<Tag color="blue">{org.type}</Tag>
|
||||
<div style={{ fontSize: '12px', color: '#666' }}>
|
||||
成员: {org.member_count} | 势力: {org.power_level}
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
</Drawer>
|
||||
)}
|
||||
|
||||
{/* 右侧内容区域 */}
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
{!selectedOrg ? (
|
||||
<Card style={{ height: '100%' }}>
|
||||
<div style={{ textAlign: 'center', padding: '100px 20px', color: '#999' }}>
|
||||
{isMobile && organizations.length > 0 && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<UnorderedListOutlined />}
|
||||
onClick={() => setOrgListVisible(true)}
|
||||
style={{ marginBottom: 20 }}
|
||||
>
|
||||
选择组织
|
||||
</Button>
|
||||
)}
|
||||
<div>请选择一个组织查看详情</div>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
<>
|
||||
{/* 工具栏 - 移动端显示项目标题和组织列表按钮 */}
|
||||
{isMobile && (
|
||||
<Card size="small" style={{ marginBottom: 8 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Space>
|
||||
<BankOutlined />
|
||||
<span style={{ fontSize: 14, fontWeight: 600 }}>
|
||||
组织管理
|
||||
</span>
|
||||
<Tag color="blue">{currentProject?.title}</Tag>
|
||||
</Space>
|
||||
<Button
|
||||
icon={<UnorderedListOutlined />}
|
||||
onClick={() => setOrgListVisible(true)}
|
||||
size="small"
|
||||
>
|
||||
列表
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 内容区域 */}
|
||||
<div style={{
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
gap: isMobile ? 0 : 16,
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<Card
|
||||
style={{ flex: 1, overflow: 'auto' }}
|
||||
bodyStyle={{ padding: isMobile ? '12px' : '24px' }}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size={isMobile ? 'middle' : 'large'}>
|
||||
<Card
|
||||
title="组织详情"
|
||||
size="small"
|
||||
@@ -445,17 +554,13 @@ export default function Organizations() {
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</Space>
|
||||
) : (
|
||||
<Card>
|
||||
<div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
|
||||
请从左侧选择一个组织查看详情
|
||||
</div>
|
||||
</Space>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 添加成员模态框 */}
|
||||
<Modal
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Button, List, Modal, Form, Input, message, Empty, Space, Popconfirm, Card, Select, Radio, Tag, InputNumber, Tooltip, Tabs } from 'antd';
|
||||
import { EditOutlined, DeleteOutlined, ThunderboltOutlined, BranchesOutlined, AppstoreAddOutlined, CheckCircleOutlined, ExclamationCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { EditOutlined, DeleteOutlined, ThunderboltOutlined, BranchesOutlined, AppstoreAddOutlined, CheckCircleOutlined, ExclamationCircleOutlined, PlusOutlined, FileTextOutlined } from '@ant-design/icons';
|
||||
import { useStore } from '../store';
|
||||
import { useOutlineSync } from '../store/hooks';
|
||||
import { cardStyles } from '../components/CardStyles';
|
||||
@@ -1904,7 +1904,10 @@ export default function Outline() {
|
||||
alignItems: isMobile ? 'stretch' : 'center'
|
||||
}}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<h2 style={{ margin: 0, fontSize: isMobile ? 18 : 24 }}>故事大纲</h2>
|
||||
<h2 style={{ margin: 0, fontSize: isMobile ? 18 : 24 }}>
|
||||
<FileTextOutlined style={{ marginRight: 8 }} />
|
||||
故事大纲
|
||||
</h2>
|
||||
{currentProject?.outline_mode && (
|
||||
<Tag color={currentProject.outline_mode === 'one-to-one' ? 'blue' : 'green'} style={{ width: 'fit-content' }}>
|
||||
{currentProject.outline_mode === 'one-to-one' ? '传统模式 (1→1)' : '细化模式 (1→N)'}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Card, Table, Tag, Button, Space, message, Modal, Form, Select, Slider, Input, Tabs, AutoComplete } from 'antd';
|
||||
import { PlusOutlined, TeamOutlined, UserOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import { PlusOutlined, ApartmentOutlined, UserOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import { useStore } from '../store';
|
||||
import axios from 'axios';
|
||||
|
||||
@@ -308,7 +308,7 @@ export default function Relationships() {
|
||||
<Card
|
||||
title={
|
||||
<Space wrap>
|
||||
<TeamOutlined />
|
||||
<ApartmentOutlined />
|
||||
<span style={{ fontSize: isMobile ? 14 : 16 }}>关系管理</span>
|
||||
{!isMobile && <Tag color="blue">{currentProject?.title}</Tag>}
|
||||
</Space>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Card, Descriptions, Empty, Typography, Button, Modal, Form, Input, message, Space } from 'antd';
|
||||
import { Card, Descriptions, Empty, Typography, Button, Modal, Form, Input, message, Flex } from 'antd';
|
||||
import { GlobalOutlined, EditOutlined, SyncOutlined } from '@ant-design/icons';
|
||||
import { useState } from 'react';
|
||||
import { useStore } from '../store';
|
||||
@@ -174,39 +174,51 @@ export default function WorldSetting() {
|
||||
backgroundColor: '#fff',
|
||||
padding: '16px 0',
|
||||
marginBottom: 24,
|
||||
borderBottom: '1px solid #f0f0f0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
borderBottom: '1px solid #f0f0f0'
|
||||
}}>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<GlobalOutlined style={{ fontSize: 24, marginRight: 12, color: 'var(--color-primary)' }} />
|
||||
<h2 style={{ margin: 0 }}>世界设定</h2>
|
||||
</div>
|
||||
<Space>
|
||||
<Button
|
||||
icon={<SyncOutlined />}
|
||||
onClick={handleRegenerate}
|
||||
disabled={isRegenerating}
|
||||
>
|
||||
AI重新生成
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => {
|
||||
editForm.setFieldsValue({
|
||||
world_time_period: currentProject.world_time_period || '',
|
||||
world_location: currentProject.world_location || '',
|
||||
world_atmosphere: currentProject.world_atmosphere || '',
|
||||
world_rules: currentProject.world_rules || '',
|
||||
});
|
||||
setIsEditModalVisible(true);
|
||||
}}
|
||||
>
|
||||
编辑世界观
|
||||
</Button>
|
||||
</Space>
|
||||
<Flex
|
||||
justify="space-between"
|
||||
align="flex-start"
|
||||
gap={12}
|
||||
wrap="wrap"
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', minWidth: 'fit-content' }}>
|
||||
<GlobalOutlined style={{ fontSize: 24, marginRight: 12, color: 'var(--color-primary)' }} />
|
||||
<h2 style={{ margin: 0, whiteSpace: 'nowrap' }}>世界设定</h2>
|
||||
</div>
|
||||
<Flex gap={8} wrap="wrap" style={{ flex: '0 1 auto' }}>
|
||||
<Button
|
||||
icon={<SyncOutlined />}
|
||||
onClick={handleRegenerate}
|
||||
disabled={isRegenerating}
|
||||
style={{
|
||||
minWidth: 'fit-content',
|
||||
flex: '1 1 auto'
|
||||
}}
|
||||
>
|
||||
<span className="button-text-mobile">AI重新生成</span>
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => {
|
||||
editForm.setFieldsValue({
|
||||
world_time_period: currentProject.world_time_period || '',
|
||||
world_location: currentProject.world_location || '',
|
||||
world_atmosphere: currentProject.world_atmosphere || '',
|
||||
world_rules: currentProject.world_rules || '',
|
||||
});
|
||||
setIsEditModalVisible(true);
|
||||
}}
|
||||
style={{
|
||||
minWidth: 'fit-content',
|
||||
flex: '1 1 auto'
|
||||
}}
|
||||
>
|
||||
<span className="button-text-mobile">编辑世界观</span>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</div>
|
||||
|
||||
{/* 可滚动内容区域 */}
|
||||
|
||||
@@ -180,7 +180,10 @@ export default function WritingStyles() {
|
||||
justifyContent: 'space-between',
|
||||
alignItems: isMobile ? 'stretch' : 'center'
|
||||
}}>
|
||||
<h2 style={{ margin: 0, fontSize: isMobile ? 18 : 24 }}>写作风格管理</h2>
|
||||
<h2 style={{ margin: 0, fontSize: isMobile ? 18 : 24 }}>
|
||||
<EditOutlined style={{ marginRight: 8 }} />
|
||||
写作风格管理
|
||||
</h2>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
|
||||
@@ -31,7 +31,7 @@ export interface ChangelogEntry {
|
||||
};
|
||||
message: string;
|
||||
commitUrl: string;
|
||||
type: 'feature' | 'fix' | 'docs' | 'style' | 'refactor' | 'perf' | 'test' | 'chore' | 'other';
|
||||
type: 'feature' | 'fix' | 'docs' | 'style' | 'refactor' | 'perf' | 'test' | 'chore' | 'update' | 'other';
|
||||
scope?: string;
|
||||
}
|
||||
|
||||
@@ -39,91 +39,111 @@ const GITHUB_API_BASE = 'https://api.github.com';
|
||||
const REPO_OWNER = 'xiamuceer-j';
|
||||
const REPO_NAME = 'MuMuAINovel';
|
||||
|
||||
/**
|
||||
* 提交类型映射表
|
||||
* 统一不同别名到标准类型
|
||||
*/
|
||||
const TYPE_MAPPING: Record<string, ChangelogEntry['type']> = {
|
||||
// 功能类
|
||||
'feat': 'feature',
|
||||
'feature': 'feature',
|
||||
'update': 'update',
|
||||
|
||||
// 修复类
|
||||
'fix': 'fix',
|
||||
|
||||
// 文档类
|
||||
'docs': 'docs',
|
||||
'doc': 'docs',
|
||||
|
||||
// 样式类
|
||||
'style': 'style',
|
||||
|
||||
// 重构类
|
||||
'refactor': 'refactor',
|
||||
|
||||
// 性能类
|
||||
'perf': 'perf',
|
||||
|
||||
// 测试类
|
||||
'test': 'test',
|
||||
|
||||
// 杂项类
|
||||
'chore': 'chore',
|
||||
};
|
||||
|
||||
/**
|
||||
* 从提交信息中解析类型和作用域
|
||||
* 支持常见的提交信息格式:
|
||||
* - type: message
|
||||
* - type(scope): message
|
||||
* - [type] message
|
||||
*
|
||||
* 匹配优先级(从高到低):
|
||||
* 1. 标准 Conventional Commits 格式: type(scope): message 或 type: message
|
||||
* 2. 方括号格式: [type] message
|
||||
* 3. 简单前缀格式: type: message(支持中文冒号)
|
||||
* 4. 关键词模糊匹配(中英文)
|
||||
*/
|
||||
function parseCommitType(message: string): { type: ChangelogEntry['type']; scope?: string; cleanMessage: string } {
|
||||
const lowerMessage = message.toLowerCase();
|
||||
const lowerMessage = message.toLowerCase().trim();
|
||||
|
||||
// 第一优先级:精确匹配 update: 开头(在正则之前检查)
|
||||
if (lowerMessage.startsWith('update:')) {
|
||||
const cleanMsg = message.replace(/^update:\s*/i, '');
|
||||
return { type: 'feature', cleanMessage: cleanMsg };
|
||||
}
|
||||
|
||||
// 第二优先级:匹配标准 conventional commits 格式 type: message 或 type(scope): message
|
||||
const conventionalMatch = message.match(/^(feat|feature|fix|docs|style|refactor|perf|test|chore)(?:\(([^)]+)\))?\s*:\s*(.+)/i);
|
||||
// 优先级1:标准 Conventional Commits 格式 - type(scope): message 或 type: message
|
||||
// 匹配所有支持的类型
|
||||
const conventionalPattern = new RegExp(
|
||||
`^(${Object.keys(TYPE_MAPPING).join('|')})(?:\\(([^)]+)\\))?\\s*[:\\::]\\s*(.+)`,
|
||||
'i'
|
||||
);
|
||||
const conventionalMatch = message.match(conventionalPattern);
|
||||
if (conventionalMatch) {
|
||||
const typeStr = conventionalMatch[1].toLowerCase();
|
||||
const mappedType = typeStr === 'feature' ? 'feature' : typeStr as ChangelogEntry['type'];
|
||||
const mappedType = TYPE_MAPPING[typeStr] || 'other';
|
||||
return {
|
||||
type: mappedType,
|
||||
scope: conventionalMatch[2],
|
||||
cleanMessage: conventionalMatch[3],
|
||||
cleanMessage: conventionalMatch[3].trim(),
|
||||
};
|
||||
}
|
||||
|
||||
// 第三优先级:匹配 [type] message 格式
|
||||
const bracketMatch = message.match(/^\[(feat|feature|fix|docs|style|refactor|perf|test|chore|update)\]\s*(.+)/i);
|
||||
// 优先级2:方括号格式 - [type] message
|
||||
const bracketPattern = new RegExp(
|
||||
`^\\[(${Object.keys(TYPE_MAPPING).join('|')})\\]\\s*(.+)`,
|
||||
'i'
|
||||
);
|
||||
const bracketMatch = message.match(bracketPattern);
|
||||
if (bracketMatch) {
|
||||
const typeStr = bracketMatch[1].toLowerCase();
|
||||
const mappedType = (typeStr === 'update' || typeStr === 'feature') ? 'feature' : typeStr as ChangelogEntry['type'];
|
||||
const mappedType = TYPE_MAPPING[typeStr] || 'other';
|
||||
return {
|
||||
type: mappedType,
|
||||
cleanMessage: bracketMatch[2],
|
||||
cleanMessage: bracketMatch[2].trim(),
|
||||
};
|
||||
}
|
||||
|
||||
// 第四优先级:通过前缀精确匹配(避免误判)
|
||||
if (lowerMessage.startsWith('fix:')|| lowerMessage.startsWith('fix:')) {
|
||||
const cleanMsg = message.replace(/^fix:\s*/i, '');
|
||||
return { type: 'fix', cleanMessage: cleanMsg };
|
||||
}
|
||||
|
||||
if (lowerMessage.startsWith('perf:')) {
|
||||
const cleanMsg = message.replace(/^perf:\s*/i, '');
|
||||
return { type: 'perf', cleanMessage: cleanMsg };
|
||||
}
|
||||
|
||||
if (lowerMessage.startsWith('docs:')) {
|
||||
const cleanMsg = message.replace(/^docs:\s*/i, '');
|
||||
return { type: 'docs', cleanMessage: cleanMsg };
|
||||
}
|
||||
|
||||
if (lowerMessage.startsWith('feat:') || lowerMessage.startsWith('feature:')) {
|
||||
const cleanMsg = message.replace(/^(feat|feature):\s*/i, '');
|
||||
return { type: 'feature', cleanMessage: cleanMsg };
|
||||
}
|
||||
|
||||
// 第五优先级:关键词模糊匹配(仅当前面都不匹配时)
|
||||
if (lowerMessage.includes('修复') || lowerMessage.includes('fix')) {
|
||||
return { type: 'fix', cleanMessage: message };
|
||||
}
|
||||
|
||||
if (lowerMessage.includes('优化') || lowerMessage.includes('perf')) {
|
||||
return { type: 'perf', cleanMessage: message };
|
||||
}
|
||||
|
||||
if (lowerMessage.includes('文档') || lowerMessage.includes('doc')) {
|
||||
return { type: 'docs', cleanMessage: message };
|
||||
}
|
||||
|
||||
if (lowerMessage.includes('新增') || lowerMessage.includes('添加') || lowerMessage.includes('增加')) {
|
||||
return { type: 'feature', cleanMessage: message };
|
||||
}
|
||||
|
||||
if (lowerMessage.includes('样式') || lowerMessage.includes('style')) {
|
||||
return { type: 'style', cleanMessage: message };
|
||||
}
|
||||
|
||||
if (lowerMessage.includes('重构') || lowerMessage.includes('refactor')) {
|
||||
return { type: 'refactor', cleanMessage: message };
|
||||
// 优先级3:简单前缀格式 - type: message(支持英文和中文冒号)
|
||||
for (const [key, value] of Object.entries(TYPE_MAPPING)) {
|
||||
const prefixPattern = new RegExp(`^${key}\\s*[:\\::]\\s*`, 'i');
|
||||
if (prefixPattern.test(lowerMessage)) {
|
||||
const cleanMsg = message.replace(prefixPattern, '').trim();
|
||||
return { type: value, cleanMessage: cleanMsg };
|
||||
}
|
||||
}
|
||||
|
||||
// 优先级4:关键词模糊匹配(仅当前面都不匹配时)
|
||||
const keywordMap: Array<{ keywords: string[]; type: ChangelogEntry['type'] }> = [
|
||||
{ keywords: ['修复', 'fix'], type: 'fix' },
|
||||
{ keywords: ['优化', 'perf'], type: 'perf' },
|
||||
{ keywords: ['文档', 'document'], type: 'docs' },
|
||||
{ keywords: ['新增', '添加', '增加', 'add'], type: 'feature' },
|
||||
{ keywords: ['更新', 'update'], type: 'update' },
|
||||
{ keywords: ['样式', 'style'], type: 'style' },
|
||||
{ keywords: ['重构', 'refactor'], type: 'refactor' },
|
||||
{ keywords: ['测试', 'test'], type: 'test' },
|
||||
];
|
||||
|
||||
for (const { keywords, type } of keywordMap) {
|
||||
if (keywords.some(keyword => lowerMessage.includes(keyword))) {
|
||||
return { type, cleanMessage: message };
|
||||
}
|
||||
}
|
||||
|
||||
// 默认类型
|
||||
return { type: 'other', cleanMessage: message };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user