Compare commits
10 Commits
V3.1.3
...
084afc0cef
Author | SHA1 | Date | |
---|---|---|---|
084afc0cef | |||
abb1fed1ef | |||
85a7f66af4 | |||
d616564a55 | |||
ba238a44d9 | |||
55f3e74cbf | |||
173cada6a4 | |||
fb655552b3 | |||
9aa47a6b3b | |||
b338b91e5a |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,5 +1,15 @@
|
||||
# 更新日志
|
||||
|
||||
## [3.1.4] - 2025-02-08
|
||||
|
||||
### Added
|
||||
|
||||
- `工具箱` 添加在新窗口中打开当前工具功能。
|
||||
|
||||
### Fixed
|
||||
|
||||
- `工具箱/JSON 格式化` 优化“输出内容”显示样式,解决内容较多时行号显示不全的问题。
|
||||
|
||||
## [3.1.3] - 2025-02-07
|
||||
|
||||
### Added
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frost-navigation",
|
||||
"description": "Frost Navigation",
|
||||
"version": "3.1.2",
|
||||
"version": "3.1.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
2
public/wasm/README.txt
Normal file
2
public/wasm/README.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
zxing_full.wasm
|
||||
zxing-wasm v2.0.1
|
BIN
public/wasm/zxing_full.wasm
Normal file
BIN
public/wasm/zxing_full.wasm
Normal file
Binary file not shown.
31
src/App.vue
31
src/App.vue
@@ -53,19 +53,36 @@ const themeVars = useThemeVars();
|
||||
*/
|
||||
function handleContextMenu(event) {
|
||||
|
||||
let element = event.target;
|
||||
let elements = event.composedPath();
|
||||
let classValue = '';
|
||||
let classRegExp = /(n-code|n-input|n-input-number|n-ol|n-select)/;
|
||||
|
||||
// 排除按住 Ctrl 键时
|
||||
if (event.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 排除输入框元素
|
||||
if (
|
||||
element instanceof HTMLInputElement &&
|
||||
['password', 'text', 'textarea'].includes(element.type)
|
||||
) {
|
||||
return;
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
|
||||
let element = elements[i];
|
||||
|
||||
// 获取元素 class 信息
|
||||
if (element instanceof HTMLElement) {
|
||||
classValue = element.classList.value;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 排除输入框元素
|
||||
if (element instanceof HTMLInputElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 排除指定元素
|
||||
if (classValue && classRegExp.test(classValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
252
src/assets/js/qr-code.js
Normal file
252
src/assets/js/qr-code.js
Normal file
@@ -0,0 +1,252 @@
|
||||
import {
|
||||
prepareZXingModule,
|
||||
readBarcodes,
|
||||
writeBarcode,
|
||||
} from 'zxing-wasm/full';
|
||||
|
||||
/**
|
||||
* @desc 二维码读取配置选项
|
||||
* @type { import('zxing-wasm').ReaderOptions }
|
||||
*/
|
||||
const readerOptions = {
|
||||
formats: ['QRCode'],
|
||||
maxNumberOfSymbols: 8,
|
||||
textMode: 'Plain',
|
||||
tryDownscale: true,
|
||||
tryHarder: true,
|
||||
tryInvert: true,
|
||||
tryRotate: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* @desc 二维码生成配置选项
|
||||
* @type { import('zxing-wasm').WriterOptions }
|
||||
*/
|
||||
const writerOptions = {
|
||||
ecLevel: '',
|
||||
format: 'QRCode',
|
||||
scale: 1,
|
||||
};
|
||||
|
||||
// 配置 wasm 文件路径
|
||||
prepareZXingModule({
|
||||
overrides: {
|
||||
locateFile: (path, prefix) => {
|
||||
if (path.endsWith('.wasm')) {
|
||||
return `./wasm/${path}`;
|
||||
} else {
|
||||
return (prefix + path);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 转换 Blob 为 DataURL
|
||||
* @param {Blob} blob
|
||||
* @param {Callback} callback
|
||||
*/
|
||||
export function blobToDataURL(blob, callback) {
|
||||
|
||||
/** @typedef {(data: { error: boolean, result: string }) => void} Callback */
|
||||
|
||||
let reader = new FileReader();
|
||||
|
||||
reader.onerror = function () {
|
||||
callback({
|
||||
error: true,
|
||||
result: reader.result,
|
||||
});
|
||||
};
|
||||
|
||||
reader.onload = function () {
|
||||
callback({
|
||||
error: false,
|
||||
result: reader.result,
|
||||
});
|
||||
};
|
||||
|
||||
reader.readAsDataURL(blob);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 将 SVG 字符串渲染到 Canvas
|
||||
* @param {object} options
|
||||
* @param {HTMLCanvasElement} options.canvas
|
||||
* @param {string} options.svgString
|
||||
* @param {number} options.drawLeft
|
||||
* @param {number} options.drawTop
|
||||
* @param {number} options.drawWidth
|
||||
* @param {number} options.drawHeight
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
function renderSvgToCanvas(options) {
|
||||
|
||||
let {
|
||||
canvas, svgString,
|
||||
drawLeft = 0, drawTop = 0,
|
||||
drawWidth = 0, drawHeight = 0,
|
||||
} = options;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
|
||||
let svgBlob = new Blob([svgString], {
|
||||
type: 'image/svg+xml;charset=utf-8',
|
||||
});
|
||||
let svgUrl = URL.createObjectURL(svgBlob);
|
||||
let image = new Image();
|
||||
|
||||
image.onerror = () => {
|
||||
console.error('加载 SVG 失败');
|
||||
URL.revokeObjectURL(svgUrl);
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
image.onload = () => {
|
||||
let ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(image, drawLeft, drawTop, drawWidth, drawHeight);
|
||||
URL.revokeObjectURL(svgUrl);
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
image.src = svgUrl;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 解析二维码图片
|
||||
* @param {Blob} image 图片二进制
|
||||
*/
|
||||
export function readQrCodeImage(image) {
|
||||
|
||||
/**
|
||||
* @desc 返回结果
|
||||
* @type {{ error: string; image: string; textList: string[]; }}
|
||||
*/
|
||||
let returns = {
|
||||
error: '',
|
||||
image: '',
|
||||
textList: [],
|
||||
};
|
||||
|
||||
/** 读取图片,转换为 DataURL */
|
||||
let fileReader = new FileReader();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
|
||||
// 处理读取异常
|
||||
fileReader.onerror = function () {
|
||||
console.error('解析二维码失败:读取图片失败');
|
||||
returns.error = '读取图片失败';
|
||||
resolve('');
|
||||
};
|
||||
|
||||
// 处理读取完成
|
||||
fileReader.onload = function () {
|
||||
resolve(fileReader.result);
|
||||
};
|
||||
|
||||
// 开始读取
|
||||
fileReader.readAsDataURL(image);
|
||||
|
||||
}).then((dataURL) => {
|
||||
if (dataURL) {
|
||||
returns.image = dataURL;
|
||||
return readBarcodes(image, readerOptions);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}).then((resultList) => {
|
||||
|
||||
let textList = returns.textList;
|
||||
|
||||
if (resultList.length === 0) {
|
||||
console.warn('解析二维码失败:未识别到内容');
|
||||
return returns;
|
||||
} else {
|
||||
console.debug('解析二维码成功:', resultList);
|
||||
}
|
||||
|
||||
for (let i = 0; i < resultList.length; i++) {
|
||||
|
||||
let item = resultList[i];
|
||||
|
||||
textList.push(item.text);
|
||||
|
||||
}
|
||||
|
||||
return returns;
|
||||
|
||||
}).catch((error) => {
|
||||
console.error('解析二维码失败:');
|
||||
console.error(error);
|
||||
returns.error = String(error);
|
||||
return returns;
|
||||
});;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 生成二维码图片
|
||||
* @param {object} options
|
||||
* @param {string} options.content
|
||||
* @param {number} options.width
|
||||
* @param {number} options.height
|
||||
* @returns 二维码图片 DataURL
|
||||
*/
|
||||
export function writeQrCodeImage(options = {}) {
|
||||
|
||||
let { content = '', width = 256, height = 256 } = options;
|
||||
|
||||
let canvas = document.createElement('canvas');
|
||||
let ctx = canvas.getContext('2d');
|
||||
|
||||
// 更新画布大小
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
// 设置背景颜色
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
return writeBarcode(content, writerOptions).then((result) => {
|
||||
|
||||
console.debug('生成二维码', result);
|
||||
|
||||
if (result.error) {
|
||||
console.error(`生成二维码失败:${result.error}`);
|
||||
return '';
|
||||
} else {
|
||||
return result.svg;
|
||||
}
|
||||
|
||||
}).then((svgString) => {
|
||||
if (svgString) {
|
||||
return renderSvgToCanvas({
|
||||
canvas: canvas,
|
||||
svgString: svgString,
|
||||
drawLeft: 0,
|
||||
drawTop: 0,
|
||||
drawWidth: width,
|
||||
drawHeight: height,
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}).then((success) => {
|
||||
|
||||
if (success) {
|
||||
return canvas.toDataURL('image/png');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
console.error('生成二维码失败:');
|
||||
console.error(error);
|
||||
return '';
|
||||
});
|
||||
|
||||
}
|
@@ -13,22 +13,22 @@ export const toolList = [
|
||||
enabled: true,
|
||||
items: [
|
||||
{
|
||||
id: 'calc-download-time',
|
||||
component: 'Calculation/CalcDownloadTime',
|
||||
title: '下载用时计算',
|
||||
id: 'calc-ratio',
|
||||
component: 'Calculation/CalcRatio',
|
||||
title: '比例计算',
|
||||
iconClass: 'mdi mdi-calculator-variant-outline',
|
||||
desc: '根据设定的文件大小和下载速度简单计算大约下载完成所需的时间。',
|
||||
desc: '按设定的比例计算给出的数值所对应的数值。',
|
||||
createdAt: '2024-09-08',
|
||||
updatedAt: '2024-09-08',
|
||||
version: '1',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'calc-ratio',
|
||||
component: 'Calculation/CalcRatio',
|
||||
title: '比例计算',
|
||||
id: 'calc-download-time',
|
||||
component: 'Calculation/CalcDownloadTime',
|
||||
title: '下载用时计算',
|
||||
iconClass: 'mdi mdi-calculator-variant-outline',
|
||||
desc: '按设定的比例计算给出的数值所对应的数值。',
|
||||
desc: '根据设定的文件大小和下载速度简单计算大约下载完成所需的时间。',
|
||||
createdAt: '2024-09-08',
|
||||
updatedAt: '2024-09-08',
|
||||
version: '1',
|
||||
@@ -42,15 +42,15 @@ export const toolList = [
|
||||
enabled: true,
|
||||
items: [
|
||||
{
|
||||
id: 'convert-html-entities',
|
||||
component: 'Conversion/ConvertHtmlEntities',
|
||||
title: '转换 HTML 实体',
|
||||
id: 'convert-timestamp',
|
||||
component: 'Conversion/ConvertTimestamp',
|
||||
title: 'Unix 时间戳转换',
|
||||
iconClass: 'mdi mdi-swap-horizontal',
|
||||
desc: '',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
version: '0',
|
||||
enabled: false,
|
||||
desc: '时间戳转时间 / 时间转时间戳',
|
||||
createdAt: '2025-02-05',
|
||||
updatedAt: '2025-02-05',
|
||||
version: '1',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'url-encode-decode',
|
||||
@@ -63,6 +63,17 @@ export const toolList = [
|
||||
version: '0',
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
id: 'qrcode-reader-and-generator',
|
||||
component: 'Conversion/QrcodeReaderAndGenerator',
|
||||
title: '二维码解析和生成',
|
||||
iconClass: 'mdi mdi-qrcode',
|
||||
desc: '解析二维码、生成二维码',
|
||||
createdAt: '2025-02-21',
|
||||
updatedAt: '2025-02-21',
|
||||
version: '1',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'convert-text-structure',
|
||||
component: 'Conversion/ConvertTextStructure',
|
||||
@@ -75,15 +86,15 @@ export const toolList = [
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
id: 'convert-timestamp',
|
||||
component: 'Conversion/ConvertTimestamp',
|
||||
title: 'Unix 时间戳转换',
|
||||
id: 'convert-html-entities',
|
||||
component: 'Conversion/ConvertHtmlEntities',
|
||||
title: '转换 HTML 实体',
|
||||
iconClass: 'mdi mdi-swap-horizontal',
|
||||
desc: '时间戳转时间 / 时间转时间戳',
|
||||
createdAt: '2025-02-05',
|
||||
updatedAt: '2025-02-05',
|
||||
version: '1',
|
||||
enabled: true,
|
||||
desc: '',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
version: '0',
|
||||
enabled: false,
|
||||
},
|
||||
]
|
||||
},
|
||||
@@ -110,8 +121,8 @@ export const toolList = [
|
||||
iconClass: 'mdi mdi-code-json',
|
||||
desc: '格式化 / 美化 JSON 字符串',
|
||||
createdAt: '2025-02-04',
|
||||
updatedAt: '2025-02-04',
|
||||
version: '1',
|
||||
updatedAt: '2025-02-07',
|
||||
version: '2',
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
@@ -162,22 +173,11 @@ export const toolList = [
|
||||
enabled: true,
|
||||
items: [
|
||||
{
|
||||
id: 'calc-minecraft-chunk-location',
|
||||
component: 'Minecraft/CalcChunkLocation',
|
||||
title: 'Minecraft 区块位置计算',
|
||||
iconClass: 'mdi mdi-calculator-variant-outline',
|
||||
desc: '',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
version: '0',
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
id: 'generate-minecraft-dynmap-renderdata',
|
||||
component: 'Minecraft/GenerateDynmapRenderdata',
|
||||
title: '生成 Dynmap renderdata',
|
||||
iconClass: 'mdi mdi-file-outline',
|
||||
desc: '生成用于 Minecraft Dynmap 插件或模组的 renderdata 数据。',
|
||||
id: 'minecraft-uuid-converter',
|
||||
component: 'Minecraft/UuidConverter',
|
||||
title: 'Minecraft UUID 转换',
|
||||
iconClass: 'mdi mdi-identifier',
|
||||
desc: '随机生成或转换 Minecraft 的 UUID。',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
version: '0',
|
||||
@@ -195,11 +195,22 @@ export const toolList = [
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'minecraft-uuid-converter',
|
||||
component: 'Minecraft/UuidConverter',
|
||||
title: 'Minecraft UUID 转换',
|
||||
iconClass: 'mdi mdi-identifier',
|
||||
desc: '随机生成或转换 Minecraft 的 UUID。',
|
||||
id: 'calc-minecraft-chunk-location',
|
||||
component: 'Minecraft/CalcChunkLocation',
|
||||
title: 'Minecraft 区块位置计算',
|
||||
iconClass: 'mdi mdi-calculator-variant-outline',
|
||||
desc: '',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
version: '0',
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
id: 'generate-minecraft-dynmap-renderdata',
|
||||
component: 'Minecraft/GenerateDynmapRenderdata',
|
||||
title: '生成 Dynmap renderdata',
|
||||
iconClass: 'mdi mdi-file-outline',
|
||||
desc: '生成用于 Minecraft Dynmap 插件或模组的 renderdata 数据。',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
version: '0',
|
||||
@@ -230,17 +241,6 @@ export const toolList = [
|
||||
title: '其他',
|
||||
enabled: true,
|
||||
items: [
|
||||
{
|
||||
id: 'genshin-impact-clock',
|
||||
component: 'Other/GenshinImpactClock/GenshinImpactClock',
|
||||
title: '《原神》时钟',
|
||||
iconClass: 'mdi mdi-clock-outline',
|
||||
desc: '在网页上实现的《原神》时钟效果',
|
||||
createdAt: '2024-10-13',
|
||||
updatedAt: '2024-10-13',
|
||||
version: '1',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
id: 'keep-screen-on',
|
||||
component: 'Other/KeepScreenOn',
|
||||
@@ -274,6 +274,17 @@ export const toolList = [
|
||||
version: '0',
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
id: 'genshin-impact-clock',
|
||||
component: 'Other/GenshinImpactClock/GenshinImpactClock',
|
||||
title: '《原神》时钟',
|
||||
iconClass: 'mdi mdi-clock-outline',
|
||||
desc: '在网页上实现的《原神》时钟效果',
|
||||
createdAt: '2024-10-13',
|
||||
updatedAt: '2024-10-13',
|
||||
version: '1',
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@@ -57,7 +57,6 @@
|
||||
label-align="right"
|
||||
label-placement="left"
|
||||
label-width="9em"
|
||||
@contextmenu.stop
|
||||
>
|
||||
|
||||
<n-form-item label="本地时间:">
|
||||
@@ -88,7 +87,6 @@
|
||||
label-align="right"
|
||||
label-placement="left"
|
||||
label-width="9em"
|
||||
@contextmenu.stop
|
||||
>
|
||||
|
||||
<n-form-item label="本地时间:">
|
||||
@@ -120,9 +118,9 @@
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
NButton, NCard, NCode, NFlex,
|
||||
NButton, NCard, NFlex,
|
||||
NForm, NFormItem,
|
||||
NInput, NInputNumber, NP, NSelect, NSwitch,
|
||||
NInput, NP, NSelect, NSwitch,
|
||||
} from 'naive-ui';
|
||||
|
||||
import {
|
||||
|
165
src/views/ToolboxView/Conversion/QrcodeReaderAndGenerator.vue
Normal file
165
src/views/ToolboxView/Conversion/QrcodeReaderAndGenerator.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="tool-detail-page">
|
||||
|
||||
<!-- 解析二维码 -->
|
||||
<n-card size="small" title="解析二维码">
|
||||
<n-form
|
||||
class="form-no-feedback"
|
||||
label-align="left"
|
||||
label-placement="top"
|
||||
label-width="auto"
|
||||
>
|
||||
|
||||
<n-form-item label="选择图片">
|
||||
<n-upload
|
||||
accept="image/jpeg,image/png"
|
||||
:default-upload="false"
|
||||
:file-list="readerData.fileList"
|
||||
:show-file-list="false"
|
||||
@change="handleSelectQrImage"
|
||||
>
|
||||
<n-upload-dragger>
|
||||
<span>点击选择图片 / 拖拽图片到此区域</span>
|
||||
</n-upload-dragger>
|
||||
</n-upload>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="解析结果">
|
||||
<n-flex
|
||||
class="reader-result"
|
||||
:wrap="true"
|
||||
>
|
||||
<div class="reader-result__image-preview">
|
||||
<n-image
|
||||
v-show="readerData.dataURL"
|
||||
object-fit="contain"
|
||||
width="100%"
|
||||
height="100%"
|
||||
:preview-disabled="true"
|
||||
:src="readerData.dataURL"
|
||||
/>
|
||||
</div>
|
||||
<n-flex
|
||||
class="reader-result__text-list"
|
||||
:vertical="true"
|
||||
>
|
||||
<n-ol v-if="readerData.results.length > 0">
|
||||
<n-li
|
||||
v-for="(value, index) in readerData.results"
|
||||
:key="index"
|
||||
>{{ value }}</n-li>
|
||||
</n-ol>
|
||||
<span v-else>请选择二维码图片以进行解析</span>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-form-item>
|
||||
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<!-- 生成二维码 -->
|
||||
<n-card v-if="false" size="small" title="生成二维码">
|
||||
<n-form
|
||||
class="form-no-feedback"
|
||||
label-align="right"
|
||||
label-placement="top"
|
||||
label-width="auto"
|
||||
></n-form>
|
||||
</n-card>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
NCard, NFlex, NForm, NFormItem,
|
||||
NImage, NLi, NOl,
|
||||
NUpload, NUploadDragger,
|
||||
} from 'naive-ui';
|
||||
|
||||
import {
|
||||
reactive,
|
||||
} from 'vue';
|
||||
|
||||
import {
|
||||
$message,
|
||||
} from '@/assets/js/naive-ui';
|
||||
|
||||
import {
|
||||
readQrCodeImage,
|
||||
} from '@/assets/js/qr-code';
|
||||
|
||||
/** 二维码解析相关数据 */
|
||||
const readerData = reactive({
|
||||
|
||||
/** 图片 DataURL */
|
||||
dataURL: '',
|
||||
|
||||
/** 选择文件列表 */
|
||||
fileList: [],
|
||||
|
||||
/** 解析结果 */
|
||||
results: [],
|
||||
|
||||
});
|
||||
|
||||
// /** 二维码生成相关数据 */
|
||||
// const writerData = reactive({
|
||||
// });
|
||||
|
||||
/**
|
||||
* @description 处理选择图片
|
||||
* @type { import('naive-ui').UploadOnChange }
|
||||
*/
|
||||
function handleSelectQrImage(options) {
|
||||
|
||||
let file = options.file.file;
|
||||
|
||||
return readQrCodeImage(file).then((result) => {
|
||||
|
||||
let { error, image, textList } = result;
|
||||
|
||||
if (error) {
|
||||
$message.error(error);
|
||||
readerData.dataURL = '';
|
||||
readerData.results = [];
|
||||
} else {
|
||||
if (textList.length === 0) {
|
||||
$message.warning('未识别到有效的二维码');
|
||||
} else {
|
||||
$message.success('识别成功');
|
||||
}
|
||||
readerData.dataURL = image;
|
||||
readerData.results = textList;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.reader-result {
|
||||
width: 100%;
|
||||
|
||||
.reader-result__image-preview {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
.n-image {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.reader-result__text-list {
|
||||
flex-grow: 1;
|
||||
min-width: 256px;
|
||||
width: 0;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -61,7 +61,6 @@
|
||||
placeholder="请输入 JSON 字符串"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
@contextmenu.stop
|
||||
></n-input>
|
||||
</n-card>
|
||||
|
||||
@@ -72,7 +71,6 @@
|
||||
language="json"
|
||||
:code="data.jsonOutput"
|
||||
:show-line-numbers="true"
|
||||
@contextmenu.stop
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
@@ -225,7 +223,9 @@ function sortObjectKeys(obj) {
|
||||
.json-output {
|
||||
user-select: text;
|
||||
|
||||
:deep(pre) {
|
||||
:deep(.__code__) {
|
||||
flex-grow: 1;
|
||||
width: 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,6 @@
|
||||
label-align="left"
|
||||
label-placement="top"
|
||||
label-width="auto"
|
||||
@contextmenu.stop
|
||||
>
|
||||
|
||||
<n-form-item label="连接地址">
|
||||
|
@@ -8,7 +8,6 @@
|
||||
label-align="left"
|
||||
label-placement="left"
|
||||
label-width="auto"
|
||||
@contextmenu.stop
|
||||
>
|
||||
|
||||
<n-form-item label="目标链接:">
|
||||
@@ -76,10 +75,10 @@ const data = reactive({
|
||||
/** 打开窗口 */
|
||||
function openWindow() {
|
||||
|
||||
var link = data.url || 'https://github.com/Frost-ZX';
|
||||
var width = data.width ?? 400;
|
||||
var height = data.height ?? 300;
|
||||
var features = `height=${height}, width=${width}, toolbar=no, menubar=no, scrollbars=yes, resizable=yes, location=yes, status=yes`;
|
||||
let link = data.url || 'https://github.com/Frost-ZX';
|
||||
let width = data.width ?? 400;
|
||||
let height = data.height ?? 300;
|
||||
let features = `height=${height}, width=${width}, toolbar=no, menubar=no, scrollbars=yes, resizable=yes, location=yes, status=yes`;
|
||||
|
||||
window.open(link, '_blank', features);
|
||||
|
||||
|
@@ -15,6 +15,19 @@
|
||||
<!-- 标题 -->
|
||||
<span>{{ routeTitle }}</span>
|
||||
|
||||
<!-- 占位 -->
|
||||
<div class="placeholder"></div>
|
||||
|
||||
<!-- 新窗口打开 -->
|
||||
<n-button
|
||||
v-show="isToolDetail"
|
||||
class="back-button"
|
||||
:text="true"
|
||||
@click="handleOpenNewWindow"
|
||||
>
|
||||
<span class="mdi mdi-open-in-new"></span>
|
||||
</n-button>
|
||||
|
||||
</div>
|
||||
<div class="app-view-content is-transparent">
|
||||
|
||||
@@ -120,6 +133,18 @@ function handleCloseTool() {
|
||||
});
|
||||
}
|
||||
|
||||
/** 在新窗口中打开当前工具 */
|
||||
function handleOpenNewWindow() {
|
||||
|
||||
let width = window.innerWidth ?? 400;
|
||||
let height = window.innerHeight ?? 300;
|
||||
let url = location.href;
|
||||
let features = `height=${height}, width=${width}, toolbar=no, menubar=no, scrollbars=yes, resizable=yes, location=yes, status=yes`;
|
||||
|
||||
window.open(url, '_blank', features);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 打开工具
|
||||
* @param {ToolboxItem} data
|
||||
@@ -136,6 +161,12 @@ function handleOpenTool(data) {
|
||||
margin-right: 0.5em;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.new-window-button {
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tool-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
Reference in New Issue
Block a user