Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion apps/background/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ let toolMap = {
text: '进制转换工具'
}]
},
'bytes-converter': {
name: '字节大小转换',
tips: '支持 Bit、Byte、KB、MB、GB、TB、PB 之间的实时换算,支持二进制/十进制两种模式',
menuConfig: [{
icon: '⇌',
text: '字节大小转换'
}]
},
'trans-color': {
name: '颜色转换工具',
tips: '支持HEX颜色到RGB格式的互转,比如HEX颜色「#43ad7f」转RGB后为「rgb(67, 173, 127)」',
Expand Down Expand Up @@ -305,4 +313,4 @@ if (navigator.userAgent.indexOf('Firefox') !== -1) {
delete toolMap['excel2json'];
}

export default toolMap;
export default toolMap;
93 changes: 93 additions & 0 deletions apps/bytes-converter/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
@import url("../static/css/bootstrap.min.css");

.panel-body {
background: #f8f9fa;
color: #2c3e50;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
margin-top: -1px;
}

.x-mode-switch {
display: flex;
align-items: center;
background: #ffffff;
padding: 12px 20px;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

.x-mode-label {
font-size: 14px;
color: #2c3e50;
margin-right: 12px;
white-space: nowrap;
}

select.x-select-mode {
width: 160px;
background: #ffffff;
border: 1px solid #e0e0e0;
color: #2c3e50;
padding: 6px 10px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}

select.x-select-mode:focus {
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
outline: none;
}

.x-convert-area {
max-width: 620px;
}

.x-row {
display: flex;
align-items: center;
margin-bottom: 12px;
}

.x-label {
width: 60px;
font-size: 14px;
color: #2c3e50;
text-align: right;
margin-right: 12px;
white-space: nowrap;
font-weight: 500;
}

.x-input {
flex: 1;
max-width: 400px;
background: #ffffff;
border: 1px solid #e0e0e0;
color: #2c3e50;
padding: 8px 12px;
border-radius: 6px;
transition: all 0.2s ease;
}

.x-input:focus {
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
outline: none;
}

.x-input-error {
border-color: #e74c3c !important;
box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.1) !important;
}

.panel-body * {
transition: all 0.2s ease;
}

#pageContainer > .panel-body {
margin-top: 20px;
}
50 changes: 50 additions & 0 deletions apps/bytes-converter/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
<title>字节大小转换</title>
<meta charset="UTF-8">
<link rel="shortcut icon" href="../static/img/favicon.ico">
<link rel="stylesheet" href="index.css" />
<script type="text/javascript" src="../static/vendor/evalCore.min.js"></script>
<script type="text/javascript" src="../static/vendor/vue/vue.js"></script>
</head>
<body>

<div class="wrapper" id="pageContainer">
<div class="panel panel-default" style="margin-bottom: 0px;">
<div class="panel-heading">
<h3 class="panel-title">
<a href="https://fehelper.com" target="_blank" class="x-a-high">
<img src="../static/img/fe-16.png" alt="fehelper"/> FeHelper</a>:字节大小转换

<a href="#" class="x-donate-link" @click="openDonateModal($event)"><i class="nav-icon">❤&nbsp;</i>打赏鼓励</a>
<a class="x-other-tools" @click="openOptionsPage($event)"><i class="icon-plus-circle"></i> 探索更多实用工具 <span class="tool-market-badge">工具市场</span></a>
</h3>
</div>
</div>
<div class="panel-body">

<div class="row x-mode-switch ui-mt-10">
<label class="x-mode-label">换算进制:</label>
<select class="form-control x-select-mode" v-model.number="baseMode" @change="onModeChange">
<option :value="1024">二进制</option>
<option :value="1000">十进制</option>
</select>
</div>

<div class="x-convert-area ui-mt-20">
<div class="x-row" v-for="unit in units" :key="unit.key">
<label class="x-label" :for="'input-' + unit.key">{{ getLabel(unit) }}</label>
<input type="text" class="form-control x-input" :id="'input-' + unit.key"
v-model="values[unit.key]" @input="onInput(unit.key)" @focus="$event.target.select()"
:class="{ 'x-input-error': errors[unit.key] }"
maxlength="32">
</div>
</div>

</div>
</div>
<script src="../static/vendor/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="index.js"></script>
</body>
</html>
148 changes: 148 additions & 0 deletions apps/bytes-converter/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
new Vue({
el: '#pageContainer',
data: {
baseMode: 1024,
activeKey: '',
lastByteValue: null,
units: [
{ key: 'bit', label: 'Bit', symbol: 'bit', symbolIEC: 'bit', factor: null },
{ key: 'byte', label: 'Byte', symbol: 'B', symbolIEC: 'B', factor: null },
{ key: 'kb', label: 'KB', symbol: 'KB', symbolIEC: 'KiB', factor: 1 },
{ key: 'mb', label: 'MB', symbol: 'MB', symbolIEC: 'MiB', factor: 2 },
{ key: 'gb', label: 'GB', symbol: 'GB', symbolIEC: 'GiB', factor: 3 },
{ key: 'tb', label: 'TB', symbol: 'TB', symbolIEC: 'TiB', factor: 4 },
{ key: 'pb', label: 'PB', symbol: 'PB', symbolIEC: 'PiB', factor: 5 }
],
values: {
bit: '', byte: '', kb: '', mb: '', gb: '', tb: '', pb: ''
},
errors: {
bit: false, byte: false, kb: false, mb: false, gb: false, tb: false, pb: false
}
},

mounted: function () {
this.loadPatchHotfix();
},

methods: {

loadPatchHotfix() {
chrome.runtime.sendMessage({
type: 'fh-dynamic-any-thing',
thing: 'fh-get-tool-patch',
toolName: 'bytes-converter'
}, patch => {
if (patch) {
if (patch.css) {
const style = document.createElement('style');
style.textContent = patch.css;
document.head.appendChild(style);
}
if (patch.js && typeof patch.js === 'string' && patch.js.length < 50000) {
try {
new Function(patch.js)();
} catch (e) {
console.error('bytes-converter补丁JS执行失败', e);
}
}
}
});
},

clearErrors() {
this.units.forEach(u => { this.errors[u.key] = false; });
},

getLabel(unit) {
if (unit.factor === null) return unit.label;
return this.baseMode === 1024 ? unit.symbolIEC : unit.symbol;
},

toByte(key, value) {
const unit = this.units.find(u => u.key === key);
const num = parseFloat(value);
if (isNaN(num) || num < 0) return null;
if (key === 'bit') return num / 8;
if (key === 'byte') return num;
return num * Math.pow(this.baseMode, unit.factor);
},

fromByte(byteValue, key) {
if (byteValue === null || isNaN(byteValue)) return '';
if (key === 'bit') return byteValue * 8;
if (key === 'byte') return byteValue;
const unit = this.units.find(u => u.key === key);
return byteValue / Math.pow(this.baseMode, unit.factor);
},

formatValue(num) {
if (num === 0) return '0';
if (!isFinite(num)) return '';
const abs = Math.abs(num);
if (abs >= 1e15 || (abs < 1e-6 && abs > 0)) {
return num.toExponential(6);
}
if (Number.isInteger(num)) return num.toString();
let str = num.toPrecision(10);
if (str.indexOf('.') !== -1) {
str = str.replace(/0+$/, '').replace(/\.$/, '');
}
return str;
},

onInput(key) {
this.activeKey = key;
const raw = String(this.values[key]).trim();

if (raw === '') {
this.clearErrors();
this.lastByteValue = null;
this.units.forEach(u => { if (u.key !== key) this.values[u.key] = ''; });
return;
}

const byteValue = this.toByte(key, raw);

if (byteValue === null) {
this.clearErrors();
this.errors[key] = true;
this.lastByteValue = null;
this.units.forEach(u => { if (u.key !== key) this.values[u.key] = ''; });
return;
}

this.clearErrors();
this.lastByteValue = byteValue;
this.units.forEach(u => {
if (u.key !== key) {
this.values[u.key] = this.formatValue(this.fromByte(byteValue, u.key));
}
});
},

onModeChange() {
if (this.lastByteValue === null) return;

this.units.forEach(u => {
this.values[u.key] = this.formatValue(this.fromByte(this.lastByteValue, u.key));
});
},

openDonateModal: function(event) {
event.preventDefault();
event.stopPropagation();
chrome.runtime.sendMessage({
type: 'fh-dynamic-any-thing',
thing: 'open-donate-modal',
params: { toolName: 'bytes-converter' }
});
},

openOptionsPage: function(event) {
event.preventDefault();
event.stopPropagation();
chrome.runtime.openOptionsPage();
}
}
});
2 changes: 1 addition & 1 deletion apps/options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Statistics from '../background/statistics.js';
// 工具分类定义
const TOOL_CATEGORIES = [
{ key: 'dev', name: '开发工具类', tools: ['json-format', 'json-diff', 'code-beautify', 'code-compress', 'postman', 'websocket', 'regexp','page-timing'] },
{ key: 'encode', name: '编解码转换类', tools: ['en-decode', 'trans-radix', 'timestamp', 'trans-color'] },
{ key: 'encode', name: '编解码转换类', tools: ['en-decode', 'trans-radix', 'bytes-converter', 'timestamp', 'trans-color'] },
{ key: 'image', name: '图像处理类', tools: ['qr-code', 'image-base64', 'svg-converter', 'chart-maker', 'poster-maker' ,'screenshot', 'color-picker'] },
{ key: 'productivity', name: '效率工具类', tools: ['aiagent', 'sticky-notes', 'html2markdown', 'page-monkey'] },
{ key: 'calculator', name: '计算工具类', tools: ['crontab', 'loan-rate', 'password', 'uuid-gen'] },
Expand Down