Commit 1dbf7332 authored by fisherdaddy's avatar fisherdaddy

feature: 增加微信公众号编辑器

parent 4ebe1818
...@@ -30,6 +30,7 @@ const BackgroundRemover = lazy(() => import('./components/BackgroundRemover')); ...@@ -30,6 +30,7 @@ const BackgroundRemover = lazy(() => import('./components/BackgroundRemover'));
const AnthropicTimeline = lazy(() => import('./components/AnthropicTimeline')); const AnthropicTimeline = lazy(() => import('./components/AnthropicTimeline'));
const DrugsList = lazy(() => import('./components/DrugsList')); const DrugsList = lazy(() => import('./components/DrugsList'));
const DeepSeekTimeline = lazy(() => import('./components/DeepSeekTimeline')); const DeepSeekTimeline = lazy(() => import('./components/DeepSeekTimeline'));
const WechatFormatter = lazy(() => import('./components/WechatFormatter'));
function App() { function App() {
return ( return (
...@@ -67,8 +68,8 @@ function App() { ...@@ -67,8 +68,8 @@ function App() {
<Route path="/text-behind-image" element={<TextBehindImage />} /> <Route path="/text-behind-image" element={<TextBehindImage />} />
<Route path="/background-remover" element={<BackgroundRemover />} /> <Route path="/background-remover" element={<BackgroundRemover />} />
<Route path="/deepseek-timeline" element={<DeepSeekTimeline />} /> <Route path="/deepseek-timeline" element={<DeepSeekTimeline />} />
<Route path="/wechat-formatter" element={<WechatFormatter />} />
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />
</Routes> </Routes>
</Suspense> </Suspense>
</main> </main>
......
import React, { useState, useRef } from 'react';
import { marked } from 'marked';
import hljs from 'highlight.js';
import 'highlight.js/styles/github.css';
import styled from 'styled-components';
import SEO from './SEO';
import { useTranslation } from '../js/i18n';
// 配置 marked 使用 highlight.js
marked.setOptions({
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
}
return hljs.highlightAuto(code).value;
},
langPrefix: 'hljs language-',
});
// 容器样式
const Container = styled.div`
min-height: 100vh;
background: linear-gradient(135deg, #f5f7ff 0%, #ffffff 100%);
padding: 4rem 2rem 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 ContentWrapper = styled.div`
display: flex;
gap: 2rem;
max-width: 1400px;
margin: 0 auto;
position: relative;
z-index: 1;
@media (max-width: 768px) {
flex-direction: column;
}
`;
const EditorContainer = styled.div`
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);
`;
const PreviewContainer = styled(EditorContainer)`
overflow: auto;
position: relative;
`;
const Editor = styled.textarea`
width: 100%;
min-height: 400px;
padding: 1rem;
border: none;
background: transparent;
font-family: 'SF Mono', monospace;
font-size: 14px;
line-height: 1.5;
resize: vertical;
color: #1a1a1a;
&:focus {
outline: none;
}
`;
const Preview = styled.div`
font-family: -apple-system, system-ui, sans-serif;
color: #1a1a1a;
line-height: 1.6;
h1, h2, h3, h4, h5, h6 {
margin-top: 1.5em;
margin-bottom: 0.5em;
font-weight: 600;
}
p {
margin: 1em 0;
}
img {
max-width: 100%;
height: auto;
}
table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
th, td {
border: 1px solid rgba(99, 102, 241, 0.2);
padding: 0.5em;
}
`;
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 CopyButton = styled.button`
background: linear-gradient(135deg, #6366F1 0%, #4F46E5 100%);
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: opacity 0.2s;
font-size: 0.9rem;
position: absolute;
top: 1.5rem;
right: 1.5rem;
opacity: ${props => props.visible ? 1 : 0};
pointer-events: ${props => props.visible ? 'auto' : 'none'};
&:hover {
opacity: 0.9;
}
`;
const FormatSelector = styled.div`
display: flex;
gap: 1rem;
margin-bottom: 1rem;
`;
const RadioLabel = styled.label`
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
font-weight: 500;
color: #4b5563;
transition: color 0.2s;
&:hover {
color: #1f2937;
}
input {
accent-color: #6366F1;
}
`;
const WechatFormatter = () => {
const [input, setInput] = useState('');
const [output, setOutput] = useState('');
const [format, setFormat] = useState('markdown'); // 'markdown' 或 'html'
const tempDivRef = useRef(null);
const { t } = useTranslation();
const handleInputChange = (e) => {
setInput(e.target.value);
convertToWechat(e.target.value, format);
};
const handleFormatChange = (e) => {
setFormat(e.target.value);
convertToWechat(input, e.target.value);
};
const convertToWechat = (text, format) => {
let html = '';
if (format === 'markdown') {
html = marked(text);
} else {
html = text;
}
// 添加微信公众号特定的样式
const wechatHtml = `
<div class="wechat-content prose max-w-none">
${html}
</div>
`;
setOutput(wechatHtml);
};
const copyToClipboard = () => {
// 创建一个临时的 div 元素来存放内容
if (!tempDivRef.current) {
tempDivRef.current = document.createElement('div');
document.body.appendChild(tempDivRef.current);
}
// 将 HTML 内容添加到临时 div
tempDivRef.current.innerHTML = output;
tempDivRef.current.style.position = 'absolute';
tempDivRef.current.style.left = '-9999px';
// 创建一个范围来选择内容
const range = document.createRange();
range.selectNode(tempDivRef.current);
// 清除当前选择
window.getSelection().removeAllRanges();
// 选择内容
window.getSelection().addRange(range);
try {
// 执行复制命令
document.execCommand('copy');
alert('已复制到剪贴板!现在可以直接粘贴到微信公众号编辑器中。');
} catch (err) {
console.error('复制失败:', err);
alert('复制失败,请手动复制。');
}
// 清理
window.getSelection().removeAllRanges();
};
return (
<>
<SEO
title="微信公众号排版工具"
description="将 Markdown 或 HTML 转换为微信公众号优质排版"
/>
<Container>
<ContentWrapper>
<EditorContainer>
<TitleLabel>公众号排版编辑器</TitleLabel>
<FormatSelector>
<RadioLabel>
<input
type="radio"
value="markdown"
checked={format === 'markdown'}
onChange={handleFormatChange}
/>
Markdown
</RadioLabel>
<RadioLabel>
<input
type="radio"
value="html"
checked={format === 'html'}
onChange={handleFormatChange}
/>
HTML
</RadioLabel>
</FormatSelector>
<Editor
value={input}
onChange={handleInputChange}
placeholder={`请输入${format === 'markdown' ? 'Markdown' : 'HTML'}内容...`}
/>
</EditorContainer>
<PreviewContainer>
<TitleLabel>预览效果</TitleLabel>
<CopyButton
onClick={copyToClipboard}
visible={output.length > 0}
>
复制内容
</CopyButton>
<Preview
className="preview-content"
dangerouslySetInnerHTML={{ __html: output }}
/>
</PreviewContainer>
</ContentWrapper>
</Container>
</>
);
};
export default WechatFormatter;
\ No newline at end of file
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
"copied": "已复制", "copied": "已复制",
"recommendedTools": "推荐工具", "recommendedTools": "推荐工具",
"dev-tools": { "dev-tools": {
"title": "开发工具", "title": "文本工具",
"description": "集成多种热门高效的开发工具,包括JSON格式化、Base64转图片、在线Diff对比、LaTeX渲染等,帮助开发者快速处理日常编码任务,提升开发效率。" "description": "集成多种热门高效的开发工具,包括JSON格式化、Base64转图片、在线Diff对比、LaTeX渲染等,帮助开发者快速处理日常编码任务,提升开发效率。"
}, },
"image-tools": { "image-tools": {
......
...@@ -241,5 +241,9 @@ ...@@ -241,5 +241,9 @@
"items": "个项目", "items": "个项目",
"allCategories": "所有类别", "allCategories": "所有类别",
"noResults": "没有找到相关结果" "noResults": "没有找到相关结果"
},
"wechatFormatter": {
"title": "微信公众号排版",
"description": "将 Markdown 或 HTML 转换为公众号排版"
} }
} }
...@@ -8,7 +8,7 @@ const tools = [ ...@@ -8,7 +8,7 @@ const tools = [
{ id: 'urlEncodeDecode', icon: '/assets/icon/url-endecode.png', path: '/url-encode-and-decode' }, { id: 'urlEncodeDecode', icon: '/assets/icon/url-endecode.png', path: '/url-encode-and-decode' },
{ id: 'imageBase64Converter', icon: '/assets/icon/image-base64.png', path: '/image-base64' }, { id: 'imageBase64Converter', icon: '/assets/icon/image-base64.png', path: '/image-base64' },
{ id: 'textDiff', icon: '/assets/icon/diff.png', path: '/text-diff' }, { id: 'textDiff', icon: '/assets/icon/diff.png', path: '/text-diff' },
{ id: 'wechatFormatter', icon: '/assets/icon/editor.png', path: '/wechat-formatter' },
]; ];
const DevTools = () => { const DevTools = () => {
......
...@@ -22,6 +22,7 @@ const tools = [ ...@@ -22,6 +22,7 @@ const tools = [
{ id: 'deepSeekTimeline', icon: '/assets/icon/deepseek_small.jpg', path: '/deepseek-timeline' }, { id: 'deepSeekTimeline', icon: '/assets/icon/deepseek_small.jpg', path: '/deepseek-timeline' },
{ id: 'modelPrice', icon: '/assets/icon/model-price.svg', path: '/llm-model-price' }, { id: 'modelPrice', icon: '/assets/icon/model-price.svg', path: '/llm-model-price' },
{ id: 'drugsList', icon: '/assets/icon/drugs.svg', path: '/drugs-list' }, { id: 'drugsList', icon: '/assets/icon/drugs.svg', path: '/drugs-list' },
{ id: 'wechatFormatter', icon: '/assets/icon/editor.png', path: '/wechat-formatter' },
{ id: 'fisherai', icon: '/assets/icon/fisherai.png', path: 'https://chromewebstore.google.com/detail/fisherai-your-best-summar/ipfiijaobcenaibdpaacbbpbjefgekbj', external: true } { id: 'fisherai', icon: '/assets/icon/fisherai.png', path: 'https://chromewebstore.google.com/detail/fisherai-your-best-summar/ipfiijaobcenaibdpaacbbpbjefgekbj', external: true }
]; ];
......
...@@ -15,8 +15,7 @@ export default { ...@@ -15,8 +15,7 @@ export default {
}, },
}, },
plugins: [ plugins: [
// 注意:line-clamp 现在已经内置在 Tailwind CSS v3.3+ 中 require('@tailwindcss/typography'),
// 不需要额外的插件了
], ],
} }
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