Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
A
ai-box
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
青山
ai-box
Commits
b035711f
Commit
b035711f
authored
Nov 07, 2024
by
fisherdaddy
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feature: 新增图片与base64转换器 & FisherAI插件
parent
a1599de3
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
344 additions
and
4 deletions
+344
-4
App.jsx
src/App.jsx
+2
-0
ImageBase64Converter.jsx
src/components/ImageBase64Converter.jsx
+276
-0
i18n.json
src/data/i18n.json
+64
-4
Home.jsx
src/pages/Home.jsx
+2
-0
No files found.
src/App.jsx
View file @
b035711f
...
...
@@ -13,6 +13,7 @@ const About = lazy(() => import('./pages/About'));
const
OpenAITimeline
=
lazy
(()
=>
import
(
'./components/OpenAITimeline'
));
const
PricingCharts
=
lazy
(()
=>
import
(
'./components/PricingCharts'
));
const
HandwriteGen
=
lazy
(()
=>
import
(
'./components/HandwriteGen'
));
const
ImageBase64Converter
=
lazy
(()
=>
import
(
'./components/ImageBase64Converter'
));
function
App
()
{
return
(
...
...
@@ -31,6 +32,7 @@ function App() {
<
Route
path=
"/openai-timeline"
element=
{
<
OpenAITimeline
/>
}
/>
<
Route
path=
"/llm-model-price"
element=
{
<
PricingCharts
/>
}
/>
<
Route
path=
"/handwriting"
element=
{
<
HandwriteGen
/>
}
/>
<
Route
path=
"/image-base64"
element=
{
<
ImageBase64Converter
/>
}
/>
<
Route
path=
"*"
element=
{
<
NotFound
/>
}
/>
</
Routes
>
</
Suspense
>
...
...
src/components/ImageBase64Converter.jsx
0 → 100644
View file @
b035711f
// ImageBase64Converter.jsx
import
React
,
{
useState
,
useCallback
}
from
'react'
;
import
{
Title
,
Wrapper
,
Container
,
InputText
,
Preview
}
from
'../js/SharedStyles'
;
import
styled
from
'styled-components'
;
import
{
useTranslation
}
from
'../js/i18n'
;
import
SEO
from
'../components/SEO'
;
const
ConverterContainer
=
styled
(
Container
)
`
flex-direction: column;
`
;
const
StyledInputText
=
styled
(
InputText
)
`
height: 100px;
margin-bottom: 20px;
@media (min-width: 768px) {
height: 100px;
width: 100%;
}
`
;
const
PreviewWrapper
=
styled
.
div
`
width: 100%;
`
;
const
Label
=
styled
.
label
`
font-weight: 500;
font-size: 14px;
color: #5f6368;
margin-bottom: 8px;
display: block;
letter-spacing: 0.1px;
`
;
const
StyledPreview
=
styled
(
Preview
)
`
background-color: #f8f9fa;
padding: 12px 40px 12px 12px;
border-radius: 8px;
border: 1px solid #dadce0;
font-size: 14px;
color: #202124;
min-height: 24px;
word-break: break-all;
`
;
const
ResultContainer
=
styled
.
div
`
position: relative;
width: 100%;
`
;
const
CopyButton
=
styled
.
button
`
position: absolute;
top: 8px;
right: 8px;
background-color: transparent;
border: none;
cursor: pointer;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: opacity 0.3s, color 0.3s;
&:hover {
opacity: 1;
}
svg {
width: 16px;
height: 16px;
}
&.copied {
color: #34a853;
}
`
;
const
StyledInputFile
=
styled
.
input
`
margin-bottom: 20px;
`
;
const
ImagePreviewContainer
=
styled
.
div
`
position: relative;
display: inline-block;
margin-top: 10px;
`
;
const
ImagePreview
=
styled
.
img
`
max-width: 100%;
height: auto;
border: 1px solid #dadce0;
border-radius: 8px;
display: block;
`
;
const
DownloadButton
=
styled
.
button
`
position: absolute;
top: 8px;
right: 8px;
background-color: #fff;
border: 1px solid #dadce0;
border-radius: 4px;
cursor: pointer;
padding: 6px 8px;
font-size: 12px;
color: #202124;
display: flex;
align-items: center;
opacity: 0.8;
transition: opacity 0.3s;
&:hover {
opacity: 1;
}
svg {
width: 16px;
height: 16px;
margin-right: 4px;
}
`
;
const
ErrorText
=
styled
.
div
`
color: red;
margin-top: 8px;
`
;
function
ImageBase64Converter
()
{
const
{
t
}
=
useTranslation
();
// 图片转 Base64 的状态
const
[
imageFile
,
setImageFile
]
=
useState
(
null
);
const
[
base64String
,
setBase64String
]
=
useState
(
''
);
const
[
isCopied
,
setIsCopied
]
=
useState
(
false
);
// Base64 转图片的状态
const
[
inputBase64
,
setInputBase64
]
=
useState
(
''
);
const
[
imageSrc
,
setImageSrc
]
=
useState
(
''
);
const
[
error
,
setError
]
=
useState
(
''
);
// 处理图片上传
const
handleImageUpload
=
(
e
)
=>
{
const
file
=
e
.
target
.
files
[
0
];
if
(
file
)
{
setImageFile
(
file
);
const
reader
=
new
FileReader
();
reader
.
onloadend
=
()
=>
{
setBase64String
(
reader
.
result
);
};
reader
.
readAsDataURL
(
file
);
}
};
// 复制 Base64 字符串
const
handleCopy
=
useCallback
(()
=>
{
navigator
.
clipboard
.
writeText
(
base64String
).
then
(()
=>
{
setIsCopied
(
true
);
setTimeout
(()
=>
setIsCopied
(
false
),
2000
);
});
},
[
base64String
]);
// 处理 Base64 输入变化
const
handleBase64InputChange
=
(
e
)
=>
{
const
input
=
e
.
target
.
value
.
trim
();
setInputBase64
(
input
);
if
(
input
)
{
let
src
=
input
;
if
(
!
input
.
startsWith
(
'data:image/'
))
{
// 尝试自动推断图片类型
const
match
=
input
.
match
(
/^data:
(
image
\/[
a-zA-Z
]
+
)
;base64,/
);
let
mimeType
=
'image/png'
;
// 默认类型
if
(
match
&&
match
[
1
])
{
mimeType
=
match
[
1
];
}
src
=
`data:
${
mimeType
}
;base64,
${
input
}
`
;
}
setImageSrc
(
src
);
setError
(
''
);
}
else
{
setImageSrc
(
''
);
setError
(
''
);
}
};
// 图片加载错误处理
const
handleImageError
=
()
=>
{
setError
(
t
(
'tools.imageBase64Converter.invalidBase64'
));
setImageSrc
(
''
);
};
// 下载图片
const
handleDownload
=
()
=>
{
const
link
=
document
.
createElement
(
'a'
);
link
.
href
=
imageSrc
;
// 尝试从 Base64 字符串中提取文件类型和扩展名
let
fileName
=
'downloaded_image'
;
const
match
=
imageSrc
.
match
(
/^data:
(
image
\/[
a-zA-Z
]
+
)
;base64,/
);
if
(
match
&&
match
[
1
])
{
const
mime
=
match
[
1
];
const
extension
=
mime
.
split
(
'/'
)[
1
];
fileName
+=
`.
${
extension
}
`
;
}
else
{
fileName
+=
`.png`
;
}
link
.
download
=
fileName
;
document
.
body
.
appendChild
(
link
);
link
.
click
();
document
.
body
.
removeChild
(
link
);
};
return
(
<>
<
SEO
title=
{
t
(
'tools.imageBase64Converter.title'
)
}
description=
{
t
(
'tools.imageBase64Converter.description'
)
}
/>
<
Wrapper
>
<
Title
>
{
t
(
'tools.imageBase64Converter.title'
)
}
</
Title
>
<
ConverterContainer
>
{
/* 图片转 Base64 部分 */
}
<
Label
>
{
t
(
'tools.imageBase64Converter.imageToBase64'
)
}
</
Label
>
<
StyledInputFile
type=
"file"
accept=
"image/*"
onChange=
{
handleImageUpload
}
/>
{
base64String
&&
(
<>
<
Label
>
{
t
(
'tools.imageBase64Converter.base64Result'
)
}
</
Label
>
<
ResultContainer
>
<
StyledPreview
>
{
base64String
}
</
StyledPreview
>
<
CopyButton
onClick=
{
handleCopy
}
className=
{
isCopied
?
'copied'
:
''
}
>
{
isCopied
?
(
<
svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 24 24"
fill=
"currentColor"
>
<
path
d=
"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
/>
</
svg
>
)
:
(
<
svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 24 24"
fill=
"currentColor"
>
<
path
d=
"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1 .9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1 -.9-2 -2-2zm0 16H8V7h11v14z"
/>
</
svg
>
)
}
</
CopyButton
>
</
ResultContainer
>
</>
)
}
{
/* Base64 转图片部分 */
}
<
Label
>
{
t
(
'tools.imageBase64Converter.base64ToImage'
)
}
</
Label
>
<
StyledInputText
value=
{
inputBase64
}
onChange=
{
handleBase64InputChange
}
placeholder=
{
t
(
'tools.imageBase64Converter.base64InputPlaceholder'
)
}
/>
{
error
&&
<
ErrorText
>
{
error
}
</
ErrorText
>
}
{
imageSrc
&&
(
<
div
>
<
Label
>
{
t
(
'tools.imageBase64Converter.imageResult'
)
}
</
Label
>
<
ImagePreviewContainer
>
<
ImagePreview
src=
{
imageSrc
}
alt=
"Base64 to Image"
onError=
{
handleImageError
}
/>
<
DownloadButton
onClick=
{
handleDownload
}
>
<
svg
xmlns=
"http://www.w3.org/2000/svg"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
>
<
path
strokeLinecap=
"round"
strokeLinejoin=
"round"
strokeWidth=
{
2
}
d=
"M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M12 12v9m0 0l-3-3m3 3l3-3"
/>
</
svg
>
{
t
(
'tools.imageBase64Converter.download'
)
}
</
DownloadButton
>
</
ImagePreviewContainer
>
</
div
>
)
}
</
ConverterContainer
>
</
Wrapper
>
</>
);
}
export
default
ImageBase64Converter
;
src/data/i18n.json
View file @
b035711f
...
...
@@ -45,7 +45,22 @@
},
"handwrite"
:
{
"title"
:
"Handwriting Font Generator"
,
"description"
:
"Generates effects similar to writing on paper"
"description"
:
"Generate an effect comparable to writing on paper"
},
"imageBase64Converter"
:
{
"title"
:
"Image and Base64 Converter"
,
"description"
:
"Mutual conversion between images and Base64"
,
"imageToBase64"
:
"Image to Base64"
,
"base64Result"
:
"Base64 Result"
,
"base64ToImage"
:
"Base64 to Image"
,
"base64InputPlaceholder"
:
"Paste Base64 string here"
,
"imageResult"
:
"Image Result"
,
"invalidBase64"
:
"Invalid Base64 string"
,
"download"
:
"Download Image"
},
"fisherai"
:
{
"title"
:
"One-Click Summary Plugin"
,
"description"
:
"The Best Summary Extension for Chrome Browser"
}
},
"notFound"
:
{
...
...
@@ -115,7 +130,22 @@
},
"handwrite"
:
{
"title"
:
"手写字体生成器"
,
"description"
:
"生成和纸上书写一样的效果"
"description"
:
"生成和纸上书写相媲美的效果"
},
"imageBase64Converter"
:
{
"title"
:
"图片与 Base64 转换器"
,
"description"
:
"图片与 Base64 的互相转换"
,
"imageToBase64"
:
"图片转 Base64"
,
"base64Result"
:
"Base64 结果"
,
"base64ToImage"
:
"Base64 转图片"
,
"base64InputPlaceholder"
:
"在此粘贴 Base64 字符串"
,
"imageResult"
:
"图片结果"
,
"invalidBase64"
:
"无效的 Base64 字符串"
,
"download"
:
"下载图片"
},
"fisherai"
:
{
"title"
:
"一键摘要插件"
,
"description"
:
"最好用的 Chrome 浏览器摘要插件"
}
},
"notFound"
:
{
...
...
@@ -185,7 +215,22 @@
},
"handwrite"
:
{
"title"
:
"手書きフォントジェネレーター"
,
"description"
:
"紙に書くような効果を生成します"
"description"
:
"紙に書くのと同等の効果を生成します"
},
"imageBase64Converter"
:
{
"title"
:
"画像とBase64コンバーター"
,
"description"
:
"画像とBase64の相互変換"
,
"imageToBase64"
:
"画像をBase64に変換"
,
"base64Result"
:
"Base64の結果"
,
"base64ToImage"
:
"Base64を画像に変換"
,
"base64InputPlaceholder"
:
"ここにBase64文字列を貼り付け"
,
"imageResult"
:
"画像の結果"
,
"invalidBase64"
:
"無効なBase64文字列"
,
"download"
:
"画像をダウンロード"
},
"fisherai"
:
{
"title"
:
"ワンクリック要約プラグイン"
,
"description"
:
"最高のChromeブラウザ用要約プラグイン"
}
},
"notFound"
:
{
...
...
@@ -255,7 +300,22 @@
},
"handwrite"
:
{
"title"
:
"손글씨 폰트 생성기"
,
"description"
:
"종이에 쓴 것 같은 효과를 생성합니다"
"description"
:
"종이에 쓰는 것과 견줄 수 있는 효과를 생성합니다"
},
"imageBase64Converter"
:
{
"title"
:
"이미지 및 Base64 변환기"
,
"description"
:
"이미지와 Base64의 상호 변환"
,
"imageToBase64"
:
"이미지를 Base64로 변환"
,
"base64Result"
:
"Base64 결과"
,
"base64ToImage"
:
"Base64를 이미지로 변환"
,
"base64InputPlaceholder"
:
"여기에 Base64 문자열을 붙여넣기"
,
"imageResult"
:
"이미지 결과"
,
"invalidBase64"
:
"잘못된 Base64 문자열"
,
"download"
:
"이미지 다운로드"
},
"fisherai"
:
{
"title"
:
"원클릭 요약 플러그인"
,
"description"
:
"가장 유용한 Chrome 브라우저 요약 확장 프로그램"
}
},
"notFound"
:
{
...
...
src/pages/Home.jsx
View file @
b035711f
...
...
@@ -8,9 +8,11 @@ const tools = [
{
id
:
'jsonFormatter'
,
icon
:
'fa-jsonformat'
,
path
:
'/json-formatter'
},
{
id
:
'urlDecode'
,
icon
:
'fa-decode'
,
path
:
'/url-decode'
},
{
id
:
'urlEncode'
,
icon
:
'fa-encode'
,
path
:
'/url-encode'
},
{
id
:
'imageBase64Converter'
,
icon
:
'fa-image-base64'
,
path
:
'/image-base64'
},
{
id
:
'handwrite'
,
icon
:
'fa-handwrite'
,
path
:
'/handwriting'
},
{
id
:
'openAITimeline'
,
icon
:
'fa-openai-timeline'
,
path
:
'/openai-timeline'
},
{
id
:
'modelPrice'
,
icon
:
'fa-model-price'
,
path
:
'/llm-model-price'
},
{
id
:
'fisherai'
,
icon
:
'fa-fisherai'
,
path
:
'https://chromewebstore.google.com/detail/fisherai-your-best-summar/ipfiijaobcenaibdpaacbbpbjefgekbj'
,
external
:
true
}
// 新增外部链接
];
const
Home
=
()
=>
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment