Commit c756def8 authored by fisherdaddy's avatar fisherdaddy

chore: 优化手写字体生成器和名言卡片生成器的样式

parent 581157d2
...@@ -145,178 +145,176 @@ function HandwritingGenerator() { ...@@ -145,178 +145,176 @@ function HandwritingGenerator() {
const backgroundOffset = -(lineSpacing * fontSize - fontSize); const backgroundOffset = -(lineSpacing * fontSize - fontSize);
return ( return (
<Layout style={{ minHeight: '100vh' }}> <div className="handwrite-container">
<Sider width={300} className="site-layout-background"> <Layout>
<Menu mode="inline" defaultSelectedKeys={['1']}> <Sider width={300} className="site-layout-background">
<Menu.Item key="1">手写字体生成器</Menu.Item> <div className="settings-section">
</Menu> <h2 className="title-label">手写字体生成器</h2>
<div className="settings-section"> <div className="form-group">
<div className="form-group"> <label>手写字体</label>
<label>手写字体</label> <Select value={font} onChange={setFont} style={{ width: '100%' }}>
<Select value={font} onChange={setFont} style={{ width: '100%' }}> <Option value="'XINYE'">新叶念体</Option>
<Option value="'XINYE'">新叶念体</Option> <Option value="'cicada'">CC 字体</Option>
<Option value="'cicada'">CC 字体</Option> <Option value="'xiongdi'">兄弟字体</Option>
<Option value="'xiongdi'">兄弟字体</Option> <Option value="'qishan-zhong'">Zhong Qi Shan 体</Option>
<Option value="'qishan-zhong'">Zhong Qi Shan 体</Option> </Select>
</Select> </div>
</div>
<div className="form-group"> <div className="form-group">
<label>纸张类型</label> <label>纸张类型</label>
<Select value={paperType} onChange={setPaperType} style={{ width: '100%' }}> <Select value={paperType} onChange={setPaperType} style={{ width: '100%' }}>
<Option value="No Paper">无纸张</Option> <Option value="No Paper">无纸张</Option>
<Option value="Lined Paper">横线纸</Option> <Option value="Lined Paper">横线纸</Option>
</Select> </Select>
</div> </div>
{/* 新增纸张背景选项 */} {/* 新增纸张背景选项 */}
<div className="form-group"> <div className="form-group">
<label>纸张背景</label> <label>纸张背景</label>
<Select value={paperBackground} onChange={setPaperBackground} style={{ width: '100%' }}> <Select value={paperBackground} onChange={setPaperBackground} style={{ width: '100%' }}>
<Option value="None">无背景</Option> <Option value="None">无背景</Option>
<Option value="Style1">样式1</Option> <Option value="Style1">样式1</Option>
<Option value="Style2">样式2</Option> <Option value="Style2">样式2</Option>
<Option value="Style3">样式3</Option> <Option value="Style3">样式3</Option>
</Select> </Select>
</div> </div>
<div className="form-group"> <div className="form-group">
<Checkbox checked={borderEnabled} onChange={() => setBorderEnabled(!borderEnabled)}> <Checkbox checked={borderEnabled} onChange={() => setBorderEnabled(!borderEnabled)}>
边框 边框
</Checkbox> </Checkbox>
</div> </div>
<div className="form-group">
<label>边距设置 (px)</label>
<Row gutter={8}>
<Col span={8}>
<Input
type="number"
value={topMargin}
onChange={(e) => setTopMargin(e.target.value)}
placeholder="上"
/>
</Col>
<Col span={8}>
<Input
type="number"
value={leftMargin}
onChange={(e) => setLeftMargin(e.target.value)}
placeholder="左"
/>
</Col>
<Col span={8}>
<Input
type="number"
value={rightMargin}
onChange={(e) => setRightMargin(e.target.value)}
placeholder="右"
/>
</Col>
</Row>
</div>
<div className="form-group"> <div className="form-group">
<label>字体大小 (px)</label> <label>边距设置 (px)</label>
<Slider <Row gutter={8}>
min={12} <Col span={8}>
max={72} <Input
value={fontSize} type="number"
onChange={setFontSize} value={topMargin}
/> onChange={(e) => setTopMargin(e.target.value)}
</div> placeholder="上"
/>
</Col>
<Col span={8}>
<Input
type="number"
value={leftMargin}
onChange={(e) => setLeftMargin(e.target.value)}
placeholder="左"
/>
</Col>
<Col span={8}>
<Input
type="number"
value={rightMargin}
onChange={(e) => setRightMargin(e.target.value)}
placeholder="右"
/>
</Col>
</Row>
</div>
<div className="form-group"> <div className="form-group">
<label>字体颜色</label> <label>字体大小 (px)</label>
<Input <Slider
type="color" min={12}
value={fontColor} max={72}
onChange={(e) => setFontColor(e.target.value)} value={fontSize}
style={{ width: '100%' }} onChange={setFontSize}
/> />
</div> </div>
<div className="form-group"> <div className="form-group">
<label>文字对齐</label> <label>字体颜色</label>
<Select value={textAlign} onChange={setTextAlign} style={{ width: '100%' }}> <Input
<Option value="left">居左</Option> type="color"
<Option value="center">居中</Option> value={fontColor}
<Option value="right">居右</Option> onChange={(e) => setFontColor(e.target.value)}
</Select> style={{ width: '100%' }}
</div> />
</div>
<div className="form-group"> <div className="form-group">
<label>行间距</label> <label>文字对齐</label>
<Slider <Select value={textAlign} onChange={setTextAlign} style={{ width: '100%' }}>
min={1} <Option value="left">居左</Option>
max={3} <Option value="center">居中</Option>
step={0.1} <Option value="right">居右</Option>
value={lineSpacing} </Select>
onChange={setLineSpacing} </div>
/>
</div>
<div className="form-group"> <div className="form-group">
<label>字符间距 (px)</label> <label>行间距</label>
<Slider <Slider
min={0} min={1}
max={10} max={3}
value={charSpacing} step={0.1}
onChange={setCharSpacing} value={lineSpacing}
/> onChange={setLineSpacing}
</div> />
</div>
<Button type="primary" onClick={handleGenerate} style={{ width: '100%' }}> <div className="form-group">
生成图片 <label>字符间距 (px)</label>
</Button> <Slider
</div> min={0}
</Sider> max={10}
<Layout> value={charSpacing}
<Content style={{ margin: '16px' }}> onChange={setCharSpacing}
<Row gutter={16}>
<Col xs={24} lg={12}>
<Title level={4}>输入文本</Title>
<TextArea
rows={15}
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="请输入您的文本..."
/> />
</Col> </div>
<Col xs={24} lg={12}>
<Title level={4}>预览</Title>
<div
className="preview-area"
style={{
fontFamily: font,
fontSize: `${fontSize}px`,
color: fontColor,
textAlign: textAlign,
marginTop: `${topMargin}px`,
marginLeft: `${leftMargin}px`,
marginRight: `${rightMargin}px`,
lineHeight: `${lineSpacing * fontSize}px`,
letterSpacing: `${charSpacing}px`,
border: borderEnabled ? '1px solid #000' : 'none',
backgroundImage: getPaperBackground(),
backgroundSize: getBackgroundSize(),
backgroundRepeat: getBackgroundRepeat(),
padding: '20px',
minHeight: '400px',
boxSizing: 'border-box',
backgroundPosition: `left ${backgroundOffset}px`,
}}
>
{text.split('\n').map((line, index) => (
<p key={index} style={{ margin: 0 }}>{line}</p>
))}
</div>
</Col> <Button type="primary" onClick={handleGenerate} style={{ width: '100%' }}>
</Row> 生成图片
</Content> </Button>
</div>
</Sider>
<Layout>
<Content style={{ padding: '1rem' }}>
<Row gutter={24}>
<Col xs={24} lg={12}>
<div className="section-title">输入文本</div>
<TextArea
rows={15}
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="请输入您的文本..."
style={{ borderRadius: '16px', padding: '1rem' }}
/>
</Col>
<Col xs={24} lg={12}>
<div className="section-title">预览</div>
<div
className="preview-area"
style={{
fontFamily: font,
fontSize: `${fontSize}px`,
color: fontColor,
textAlign: textAlign,
paddingTop: `${topMargin}px`,
paddingLeft: `${leftMargin}px`,
paddingRight: `${rightMargin}px`,
lineHeight: `${lineSpacing * fontSize}px`,
letterSpacing: `${charSpacing}px`,
border: borderEnabled ? '1px solid #000' : 'none',
backgroundImage: getPaperBackground(),
backgroundSize: getBackgroundSize(),
backgroundRepeat: getBackgroundRepeat(),
backgroundPosition: `left ${backgroundOffset}px`,
minHeight: '400px',
}}
>
{text.split('\n').map((line, index) => (
<p key={index} style={{ margin: 0 }}>{line}</p>
))}
</div>
</Col>
</Row>
</Content>
</Layout>
</Layout> </Layout>
</Layout> </div>
); );
} }
......
...@@ -28,42 +28,88 @@ const backgroundOptions = [ ...@@ -28,42 +28,88 @@ const backgroundOptions = [
]; ];
const Container = styled.div` const Container = styled.div`
display: flex;
flex-direction: row;
gap: 20px;
padding: 20px;
justify-content: center;
background-color: #f5f5f5;
min-height: 100vh; min-height: 100vh;
background: linear-gradient(135deg, #f5f7ff 0%, #ffffff 100%);
padding: 2rem;
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
linear-gradient(90deg, rgba(99, 102, 241, 0.05) 1px, transparent 1px),
linear-gradient(rgba(99, 102, 241, 0.05) 1px, transparent 1px);
background-size: 20px 20px;
pointer-events: none;
}
`; `;
const TitleLabel = styled.label` const ContentWrapper = styled.div`
font-size: 16px; // 增大字体大小 display: flex;
color: #1677FF; // 设置字体颜色为深色 gap: 2rem;
`; max-width: 1400px;
margin: 0 auto;
position: relative;
z-index: 1;
@media (max-width: 768px) {
flex-direction: column;
}
`;
const InputContainer = styled.div` const InputContainer = styled.div`
flex: 1; flex: 1;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 1.5rem;
box-shadow: 0 8px 32px rgba(99, 102, 241, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 15px; gap: 1rem;
`;
const TitleLabel = styled.h2`
font-size: 1.8rem;
margin-bottom: 1.5rem;
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
letter-spacing: -0.02em;
`; `;
const PreviewContainer = styled.div` const PreviewContainer = styled.div`
flex: 1; flex: 1;
background-color: ${(props) => props.$bgColor || '#333333'}; // 使用短暂属性 background: ${(props) => props.$bgColor || '#333333'};
padding: 20px; padding: 2rem;
border-radius: 12px; border-radius: 16px;
border: 1px solid #333; box-shadow: 0 8px 32px rgba(99, 102, 241, 0.1);
color: ${(props) => props.$fontColor || '#b8b83b'}; // 使用短暂属性 border: 1px solid rgba(255, 255, 255, 0.2);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
position: relative; min-height: 200px;
width: 100%; height: fit-content;
height: 100%; align-self: flex-start;
transition: all 0.3s ease;
margin-top: 1rem;
&:hover {
transform: translateY(-5px);
box-shadow: 0 12px 24px rgba(99, 102, 241, 0.15);
}
@media (max-width: 768px) {
margin-top: 2rem;
width: 100%;
}
`; `;
const QuoteText = styled.div` const QuoteText = styled.div`
...@@ -98,19 +144,31 @@ const InputText = styled.textarea` ...@@ -98,19 +144,31 @@ const InputText = styled.textarea`
const InputField = styled.input` const InputField = styled.input`
width: 100%; width: 100%;
padding: 10px; padding: 0.8rem;
border-radius: 8px; border-radius: 8px;
border: 1px solid #dadce0; border: 1px solid rgba(99, 102, 241, 0.2);
font-size: 16px; font-size: 1rem;
transition: all 0.3s ease;
&:hover, &:focus {
border-color: rgba(99, 102, 241, 0.4);
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1);
}
`; `;
const Select = styled.select` const Select = styled.select`
width: 100%; width: 100%;
padding: 10px; padding: 0.8rem;
border-radius: 8px; border-radius: 8px;
border: 1px solid #dadce0; border: 1px solid rgba(99, 102, 241, 0.2);
font-size: 16px; font-size: 1rem;
background-color: #fff; transition: all 0.3s ease;
background: white;
cursor: pointer;
&:hover {
border-color: rgba(99, 102, 241, 0.4);
}
`; `;
const ColorInput = styled.input` const ColorInput = styled.input`
...@@ -121,22 +179,26 @@ const ColorInput = styled.input` ...@@ -121,22 +179,26 @@ const ColorInput = styled.input`
`; `;
const Label = styled.label` const Label = styled.label`
font-size: 14px; font-weight: 600;
margin-bottom: 5px; color: #1a1a1a;
margin-bottom: 0.5rem;
display: block;
`; `;
const DownloadButton = styled.button` const DownloadButton = styled.button`
padding: 10px 20px; background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
background-color: #1677ff;
color: white; color: white;
border: none; border: none;
padding: 1rem;
border-radius: 8px; border-radius: 8px;
font-weight: 600;
cursor: pointer; cursor: pointer;
font-size: 16px; transition: all 0.3s ease;
margin-top: 20px; margin-top: 1rem;
&:hover { &:hover {
background-color: #2f86ff; transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2);
} }
`; `;
...@@ -179,110 +241,112 @@ function QuoteCard() { ...@@ -179,110 +241,112 @@ function QuoteCard() {
return ( return (
<Container> <Container>
{/* 左侧输入区域 */} <ContentWrapper>
<InputContainer> {/* 左侧输入区域 */}
<TitleLabel>名言卡片生成器</TitleLabel> <InputContainer>
<InputText <TitleLabel>名言卡片生成器</TitleLabel>
placeholder="请输入中文名人名言" <InputText
value={chineseText} placeholder="请输入中文名人名言"
onChange={(e) => setChineseText(e.target.value)} value={chineseText}
/> onChange={(e) => setChineseText(e.target.value)}
<InputText />
placeholder="请输入英文翻译" <InputText
value={englishText} placeholder="请输入英文翻译"
onChange={(e) => setEnglishText(e.target.value)} value={englishText}
/> onChange={(e) => setEnglishText(e.target.value)}
<InputField />
placeholder="请输入作者姓名" <InputField
value={author} placeholder="请输入作者姓名"
onChange={(e) => setAuthor(e.target.value)} value={author}
/> onChange={(e) => setAuthor(e.target.value)}
/>
{/* 字体选择 */}
<Label>选择中文字体:</Label> {/* 字体选择 */}
<Select value={chineseFont} onChange={(e) => setChineseFont(e.target.value)}> <Label>选择中文字体:</Label>
{chineseFonts.map((font) => ( <Select value={chineseFont} onChange={(e) => setChineseFont(e.target.value)}>
<option key={font.value} value={font.value}> {chineseFonts.map((font) => (
{font.name} <option key={font.value} value={font.value}>
</option> {font.name}
))} </option>
</Select> ))}
</Select>
<Label>选择英文字体:</Label>
<Select value={englishFont} onChange={(e) => setEnglishFont(e.target.value)}> <Label>选择英文字体:</Label>
{englishFonts.map((font) => ( <Select value={englishFont} onChange={(e) => setEnglishFont(e.target.value)}>
<option key={font.value} value={font.value}> {englishFonts.map((font) => (
{font.name} <option key={font.value} value={font.value}>
</option> {font.name}
))} </option>
</Select> ))}
</Select>
{/* 字体颜色选择 */}
<Label>选择字体颜色:</Label> {/* 字体颜色选择 */}
<ColorInput <Label>选择字体颜色:</Label>
type="color" <ColorInput
value={fontColor} type="color"
onChange={(e) => setFontColor(e.target.value)} value={fontColor}
/> onChange={(e) => setFontColor(e.target.value)}
/>
{/* 作者颜色选择 */}
<Label>选择作者颜色:</Label> {/* 作者颜色选择 */}
<ColorInput <Label>选择作者颜色:</Label>
type="color" <ColorInput
value={authorColor} type="color"
onChange={(e) => setAuthorColor(e.target.value)} value={authorColor}
/> onChange={(e) => setAuthorColor(e.target.value)}
/>
{/* 背景色选择 */}
<Label>选择背景颜色:</Label> {/* 背景色选择 */}
<Select <Label>选择背景颜色:</Label>
value={ <Select
backgroundOptions.some((option) => option.value === bgColor) value={
? bgColor backgroundOptions.some((option) => option.value === bgColor)
: 'custom' ? bgColor
} : 'custom'
onChange={handleBackgroundChange} }
onChange={handleBackgroundChange}
>
{backgroundOptions.map((option) => (
<option key={option.name} value={option.value}>
{option.name}
</option>
))}
</Select>
{bgColor === 'custom' && (
<>
<Label>选择自定义背景颜色:</Label>
<ColorInput
type="color"
value={customBgColor}
onChange={(e) => setBgColor(e.target.value)}
title="选择自定义背景颜色"
/>
</>
)}
{/* 下载按钮 */}
<DownloadButton onClick={handleDownload}>
下载图片
</DownloadButton>
</InputContainer>
{/* 右侧预览区域 */}
<PreviewContainer
$bgColor={bgColor}
$fontColor={fontColor}
ref={previewRef}
> >
{backgroundOptions.map((option) => ( <QuoteText $color={fontColor} $fontFamily={chineseFont}>
<option key={option.name} value={option.value}> {chineseText || '请输入中文名人名言'}
{option.name} </QuoteText>
</option> <QuoteText $color={fontColor} $fontFamily={englishFont}>
))} {englishText || 'Enter the English translation here.'}
</Select> </QuoteText>
{bgColor === 'custom' && ( <QuoteAuthor $color={authorColor} $fontFamily={englishFont}>
<> —— {author || '作者姓名'}
<Label>选择自定义背景颜色:</Label> </QuoteAuthor>
<ColorInput </PreviewContainer>
type="color" </ContentWrapper>
value={customBgColor}
onChange={(e) => setBgColor(e.target.value)}
title="选择自定义背景颜色"
/>
</>
)}
{/* 下载按钮 */}
<DownloadButton onClick={handleDownload}>
下载图片
</DownloadButton>
</InputContainer>
{/* 右侧预览区域 */}
<PreviewContainer
$bgColor={bgColor}
$fontColor={fontColor}
ref={previewRef}
>
<QuoteText $color={fontColor} $fontFamily={chineseFont}>
{chineseText || '请输入中文名人名言'}
</QuoteText>
<QuoteText $color={fontColor} $fontFamily={englishFont}>
{englishText || 'Enter the English translation here.'}
</QuoteText>
<QuoteAuthor $color={authorColor} $fontFamily={englishFont}>
—— {author || '作者姓名'}
</QuoteAuthor>
</PreviewContainer>
</Container> </Container>
); );
} }
......
...@@ -58,3 +58,92 @@ body, html, #root { ...@@ -58,3 +58,92 @@ body, html, #root {
.ant-btn { .ant-btn {
margin-top: 16px; margin-top: 16px;
} }
.handwrite-container {
min-height: 100vh;
background: linear-gradient(135deg, #f5f7ff 0%, #ffffff 100%);
}
.settings-panel {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 1.5rem;
box-shadow: 0 8px 32px rgba(99, 102, 241, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
margin: 1rem;
}
.preview-area {
background: rgba(255, 255, 255, 0.9);
border-radius: 16px;
box-shadow: 0 8px 32px rgba(99, 102, 241, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
overflow: auto;
padding: 2rem;
}
.preview-area:hover {
box-shadow: 0 12px 24px rgba(99, 102, 241, 0.15);
border-color: rgba(99, 102, 241, 0.3);
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
font-weight: 600;
color: #1a1a1a;
margin-bottom: 0.5rem;
display: block;
}
.ant-select-selector,
.ant-input,
.ant-input-number,
.ant-slider {
border-radius: 8px !important;
border: 1px solid rgba(99, 102, 241, 0.2) !important;
}
.ant-select-selector:hover,
.ant-input:hover,
.ant-input-number:hover {
border-color: rgba(99, 102, 241, 0.4) !important;
}
.ant-btn-primary {
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%) !important;
border: none !important;
border-radius: 8px !important;
height: 40px !important;
font-weight: 600 !important;
transition: all 0.3s ease !important;
}
.ant-btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2);
}
.section-title {
font-size: 1.4rem;
font-weight: 600;
margin-bottom: 1rem;
background: linear-gradient(135deg, #1a1a1a 0%, #333333 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: -0.01em;
}
.title-label {
font-size: 1.8rem;
margin-bottom: 1.5rem;
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
letter-spacing: -0.02em;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment