Skip to content

Commit 7cba6bf

Browse files
committed
update icon converter;
1 parent 30d628c commit 7cba6bf

3 files changed

Lines changed: 282 additions & 52 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,4 @@ src/Build/
408408
src/CodeWF.WebAPI/CodeWF.db
409409
src/CodeWF.WebAPI/CodeWF.db-shm
410410
src/CodeWF.WebAPI/CodeWF.db-wal
411+
src/WebSite/wwwroot/UploadIcons/
Lines changed: 213 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,217 @@
1-
@inject IOptions<SiteOption> SiteOptions
1+
@using Microsoft.JSInterop
2+
@inject IOptions<SiteOption> SiteOptions
3+
@inject IJSRuntime JS
24

35
<PageTitle>在线ICO转换器 - @SiteOptions.Value.AppTitle</PageTitle>
46

5-
<script src="@Config.GetStaticFileUrl("_content/CodeWF/css/iconconverter.css")"></script>
6-
7-
<form id="icoGenerator">
8-
<label for="fileInput">图片文件 (格式: png、jpg、jpeg、ico、webp)</label>
9-
<input type="file" id="fileInput" accept=".png,.jpg,.jpeg,.ico,.webp">
10-
<label for="sizeSelect">目标尺寸</label>
11-
<select id="sizeSelect">
12-
<option value="16x16">16x16</option>
13-
<option value="24x24">24x24</option>
14-
<option value="32x32">32x32</option>
15-
<option value="48x48">48x48</option>
16-
<option value="64x64">64x64</option>
17-
<option value="128x128">128x128</option>
18-
<option value="256x256">256x256</option>
19-
</select>
20-
<button type="button" id="mergeButton">合并生成 ico 图标</button>
21-
<button type="button" id="singleButton">单独生成 ico 图标</button>
22-
<a id="downloadLink" href="#" download>下载转换后的文件</a>
23-
</form>
24-
25-
<script src="@Config.GetStaticFileUrl("_content/CodeWF/js/iconconverter.js")"></script>
26-
27-
@code
28-
{
7+
<style>
8+
.converter-container {
9+
max-width: 800px;
10+
margin: 20px auto;
11+
padding: 20px;
12+
}
13+
14+
.input-group {
15+
margin-bottom: 1rem;
16+
}
17+
18+
.size-options {
19+
display: flex;
20+
flex-wrap: wrap;
21+
gap: 1rem;
22+
margin: 1rem 0;
23+
}
24+
25+
.size-option {
26+
display: flex;
27+
align-items: center;
28+
gap: 0.5rem;
29+
}
30+
31+
.button-group {
32+
margin: 1.5rem 0;
33+
display: flex;
34+
gap: 1rem;
35+
}
36+
37+
.btn {
38+
padding: 0.5rem 1rem;
39+
border: none;
40+
border-radius: 4px;
41+
cursor: pointer;
42+
font-size: 1rem;
43+
}
44+
45+
.btn-primary {
46+
background-color: #007bff;
47+
color: white;
48+
}
49+
50+
.btn-secondary {
51+
background-color: #6c757d;
52+
color: white;
53+
}
54+
55+
.btn-success {
56+
background-color: #28a745;
57+
color: white;
58+
}
59+
60+
.memo {
61+
margin-top: 3rem;
62+
padding-top: 1.5rem;
63+
border-top: 1px solid #dee2e6;
64+
}
65+
66+
.code-block {
67+
background-color: #f8f9fa;
68+
padding: 1rem;
69+
border-radius: 4px;
70+
font-family: Consolas, monospace;
71+
margin: 1rem 0;
72+
}
73+
74+
#result {
75+
margin-top: 1rem;
76+
}
77+
78+
.me-2 {
79+
margin-right: 0.5rem;
80+
}
81+
</style>
82+
83+
<div class="converter-container">
84+
<div class="converter-form">
85+
<div class="input-group">
86+
<label for="fileInput">选择源图片文件</label>
87+
<input type="file" id="fileInput" class="form-control" accept=".png,.jpg,.jpeg,.webp" />
88+
<small class="text-muted">支持的格式: PNG、JPG、JPEG、WEBP</small>
89+
</div>
90+
91+
<div class="form-group mt-3">
92+
<label>选择图标尺寸</label>
93+
<div class="size-options">
94+
<label class="checkbox-inline">
95+
<input type="checkbox" value="16" checked> 16x16
96+
</label>
97+
<label class="checkbox-inline">
98+
<input type="checkbox" value="32" checked> 32x32
99+
</label>
100+
<label class="checkbox-inline">
101+
<input type="checkbox" value="48"> 48x48
102+
</label>
103+
<label class="checkbox-inline">
104+
<input type="checkbox" value="64"> 64x64
105+
</label>
106+
<label class="checkbox-inline">
107+
<input type="checkbox" value="128"> 128x128
108+
</label>
109+
<label class="checkbox-inline">
110+
<input type="checkbox" value="256"> 256x256
111+
</label>
112+
</div>
113+
</div>
114+
115+
<div class="form-group mt-3">
116+
<button id="mergeButton" class="btn btn-primary me-2">合并生成ICO</button>
117+
<button id="separateButton" class="btn btn-secondary">分别生成ICO</button>
118+
</div>
119+
120+
<div id="result" style="display:none" class="mt-3">
121+
<a id="downloadLink" class="btn btn-success" target="_blank">下载文件</a>
122+
</div>
123+
</div>
124+
125+
<div class="memo">
126+
<h4>使用说明</h4>
127+
<p>1. ICO图标文件可以包含多个不同尺寸的图像,建议至少包含16x16和32x32两种尺寸。</p>
128+
<p>2. 网站图标引用代码示例:</p>
129+
<div class="code-block">
130+
<code>&lt;link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" /&gt;</code>
131+
</div>
132+
</div>
133+
</div>
134+
135+
@code {
29136
public const string Slug = "ico";
30-
}
137+
138+
protected override async Task OnAfterRenderAsync(bool firstRender)
139+
{
140+
if (firstRender)
141+
{
142+
await JS.InvokeVoidAsync("initIconConverter");
143+
}
144+
}
145+
146+
}
147+
148+
<script>
149+
$(document).ready(function () {
150+
function validateInput() {
151+
const $fileInput = $('#fileInput');
152+
if (!$fileInput[0].files || !$fileInput[0].files.length) {
153+
alert('请选择图片文件');
154+
return false;
155+
}
156+
157+
const sizes = $('input[type="checkbox"]:checked')
158+
.map(function () { return $(this).val(); })
159+
.get();
160+
161+
if (sizes.length === 0) {
162+
alert('请至少选择一个图标尺寸');
163+
return false;
164+
}
165+
166+
return {
167+
file: $fileInput[0].files[0],
168+
sizes: sizes
169+
};
170+
}
171+
172+
async function convertIcon(isMerge) {
173+
const validation = validateInput();
174+
if (!validation) return;
175+
176+
const $button = isMerge ? $('#mergeButton') : $('#separateButton');
177+
const $result = $('#result');
178+
const $downloadLink = $('#downloadLink');
179+
180+
const formData = new FormData();
181+
formData.append('sourceImage', validation.file);
182+
formData.append('sizes', validation.sizes.join(','));
183+
184+
$button.prop('disabled', true)
185+
.text(isMerge ? '合并生成中...' : '分别生成中...');
186+
187+
try {
188+
const response = await $.ajax({
189+
url: `/api/Image/${isMerge ? 'merge' : 'separate'}`,
190+
type: 'POST',
191+
data: formData,
192+
processData: false,
193+
contentType: false
194+
});
195+
196+
if (response.success) {
197+
$downloadLink
198+
.attr('href', response.url)
199+
.text(`下载${isMerge ? 'ICO文件' : 'ZIP压缩包'}`);
200+
$result.fadeIn();
201+
} else {
202+
alert('生成失败:' + response.message);
203+
}
204+
} catch (error) {
205+
console.error('错误:', error);
206+
alert('生成过程出错,请稍后重试');
207+
} finally {
208+
$button.prop('disabled', false)
209+
.text(isMerge ? '合并生成ICO' : '分别生成ICO');
210+
}
211+
}
212+
213+
// 绑定按钮事件
214+
$('#mergeButton').click(() => convertIcon(true));
215+
$('#separateButton').click(() => convertIcon(false));
216+
});
217+
</script>

src/WebSite/Controllers/ImageController.cs

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,90 @@
77
namespace WebSite.Controllers;
88

99
[ApiController]
10-
[Route("[controller]/[action]")]
10+
[Route("api/[controller]")]
1111
public class ImageController : ControllerBase
1212
{
1313
private const string IconFolder = "UploadIcons";
1414

15-
[HttpPost]
15+
[HttpPost("merge")]
1616
[AllowAnonymous]
17-
public async Task<IActionResult> MergeGenerateIconAsync([FromForm] ConvertIconRequest request,
17+
public async Task<IActionResult> MergeGenerateIconAsync([FromForm] IFormFile sourceImage, [FromForm] string sizes,
1818
[FromServices] IWebHostEnvironment env)
1919
{
20-
var fullPath = await SaveFileAsync(request, env);
21-
var icoFullPath = Path.ChangeExtension(fullPath, ".ico");
22-
await ImageHelper.MergeGenerateIcon(fullPath, icoFullPath, request.ConvertSizes);
23-
return Ok(new { Data = icoFullPath, Code = 2001, Msg = "转换成功" });
20+
try
21+
{
22+
// 解析尺寸
23+
var convertSizes = sizes.Split(',').Select(uint.Parse).ToArray();
24+
25+
// 保存上传的文件
26+
var fullPath = await SaveFileAsync(sourceImage, env);
27+
var fileName = $"{Guid.NewGuid():N}.ico";
28+
var icoFullPath = Path.Combine(env.WebRootPath, IconFolder, fileName);
29+
30+
// 确保目录存在
31+
Directory.CreateDirectory(Path.Combine(env.WebRootPath, IconFolder));
32+
33+
// 生成图标
34+
await ImageHelper.MergeGenerateIcon(fullPath, icoFullPath, convertSizes);
35+
36+
// 返回可访问的URL
37+
var iconUrl = $"/{IconFolder}/{fileName}";
38+
return Ok(new { success = true, url = iconUrl });
39+
}
40+
catch (Exception ex)
41+
{
42+
return BadRequest(new { success = false, message = ex.Message });
43+
}
2444
}
2545

26-
[HttpPost]
46+
[HttpPost("separate")]
2747
[AllowAnonymous]
28-
public async Task<IActionResult> SeparateGenerateIcon([FromForm] ConvertIconRequest request,
29-
[FromServices] IWebHostEnvironment env, [FromServices] ISevenZipCompressor sevenZipCompressor)
48+
public async Task<IActionResult> SeparateGenerateIconAsync([FromForm] IFormFile sourceImage,
49+
[FromForm] string sizes, [FromServices] IWebHostEnvironment env,
50+
[FromServices] ISevenZipCompressor sevenZipCompressor)
3051
{
31-
var fullPath = await SaveFileAsync(request, env);
32-
var iconFolderName = Guid.NewGuid().ToString("N");
33-
var iconFolderPath = Path.Combine(env.ContentRootPath, IconFolder, iconFolderName);
34-
var iconZipPath = $"{iconFolderPath}.7z";
35-
await ImageHelper.MergeGenerateIcon(fullPath, iconFolderPath, request.ConvertSizes);
36-
sevenZipCompressor.Zip(iconFolderPath, iconZipPath);
37-
return Ok(new { Data = iconZipPath, Code = 2001, Msg = "转换成功" });
52+
try
53+
{
54+
var convertSizes = sizes.Split(',').Select(uint.Parse).ToArray();
55+
56+
// 创建临时文件夹存放分离的图标
57+
var folderName = $"icons_{Guid.NewGuid():N}";
58+
var iconFolderPath = Path.Combine(env.WebRootPath, IconFolder, folderName);
59+
Directory.CreateDirectory(iconFolderPath);
60+
61+
// 保存上传的文件并生成图标
62+
var sourceFilePath = await SaveFileAsync(sourceImage, env);
63+
await ImageHelper.SeparateGenerateIcon(sourceFilePath, iconFolderPath, convertSizes);
64+
65+
// 创建压缩文件
66+
var zipFileName = $"{folderName}.zip";
67+
var zipFilePath = Path.Combine(env.WebRootPath, IconFolder, zipFileName);
68+
69+
// 压缩文件夹
70+
await Task.Run(() => sevenZipCompressor.Zip(iconFolderPath, zipFilePath));
71+
72+
// 清理临时文件夹
73+
Directory.Delete(iconFolderPath, true);
74+
75+
// 返回zip文件的URL
76+
var zipUrl = $"/{IconFolder}/{zipFileName}";
77+
return Ok(new { success = true, url = zipUrl });
78+
}
79+
catch (Exception ex)
80+
{
81+
return BadRequest(new { success = false, message = ex.Message });
82+
}
3883
}
3984

40-
private async Task<string> SaveFileAsync(ConvertIconRequest request, IWebHostEnvironment env)
85+
private async Task<string> SaveFileAsync(IFormFile file, IWebHostEnvironment env)
4186
{
4287
var saveFileName = Guid.NewGuid().ToString("N");
43-
var saveFolder = Path.Combine(env.ContentRootPath, IconFolder);
44-
if (!Directory.Exists(saveFolder))
45-
{
46-
Directory.CreateDirectory(saveFolder);
47-
}
88+
var saveFolder = Path.Combine(env.WebRootPath, IconFolder);
89+
Directory.CreateDirectory(saveFolder);
90+
4891
var saveFullPath = Path.Combine(saveFolder, saveFileName);
49-
await using FileStream fs = new FileStream(saveFullPath, FileMode.Create);
50-
await request.SourceImage.CopyToAsync(fs);
51-
fs.Flush();
92+
await using var fs = new FileStream(saveFullPath, FileMode.Create);
93+
await file.CopyToAsync(fs);
5294
return saveFullPath;
5395
}
5496
}

0 commit comments

Comments
 (0)