10 Commits

8 changed files with 593 additions and 141 deletions

View File

@@ -1,5 +1,15 @@
# 更新日志 # 更新日志
## [3.1.5] - 2025-04-06
### Added
- `工具箱` 添加“二维码解析和生成”工具。
### Changed
- `工具箱` 调整工具列表项顺序。
## [3.1.4] - 2025-02-08 ## [3.1.4] - 2025-02-08
### Added ### Added

View File

@@ -1,7 +1,7 @@
{ {
"name": "frost-navigation", "name": "frost-navigation",
"description": "Frost Navigation", "description": "Frost Navigation",
"version": "3.1.4", "version": "3.1.5",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -4,6 +4,15 @@ import {
writeBarcode, writeBarcode,
} from 'zxing-wasm/full'; } from 'zxing-wasm/full';
/** 默认背景颜色 */
const DEFAULT_BGC = 'transparent';
/** 默认前景颜色 */
const DEFAULT_FGC = '#000000';
/** 模块名称 */
const PREFIX = '[qr-code]';
/** /**
* @desc 二维码读取配置选项 * @desc 二维码读取配置选项
* @type { import('zxing-wasm').ReaderOptions } * @type { import('zxing-wasm').ReaderOptions }
@@ -25,7 +34,7 @@ const readerOptions = {
const writerOptions = { const writerOptions = {
ecLevel: '', ecLevel: '',
format: 'QRCode', format: 'QRCode',
scale: 1, scale: 0,
}; };
// 配置 wasm 文件路径 // 配置 wasm 文件路径
@@ -42,31 +51,161 @@ prepareZXingModule({
}); });
/** /**
* @description 转换 Blob 为 DataURL * @description 在图片上绘制矩形,返回 DataURL
* @param {Blob} blob * @param {Blob} blob 图片二进制
* @param {Callback} callback * @param {Rect[]} rects 矩形位置信息列表
*/ */
export function blobToDataURL(blob, callback) { function drawRectsOnImage(blob, rects) {
/** @typedef {(data: { error: boolean, result: string }) => void} Callback */ /** @typedef {{ x: number; y: number; w: number; h: number }} Rect */
let reader = new FileReader(); let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
return renderImageToCanvas(blob, canvas).then((success) => {
if (!success) {
return '';
}
rects.forEach((rect, index) => {
let { x, y, w, h } = rect;
let text = String(index + 1);
// 绘制矩形
ctx.fillStyle = 'rgba(0, 255, 0, 0.25)';
ctx.lineWidth = 4;
ctx.strokeStyle = 'rgba(0, 255, 0, 1)';
ctx.fillRect(x, y, w, h);
ctx.strokeRect(x, y, w, h);
// 绘制文本
ctx.font = `bold ${Math.round(w / 2)}px sans-serif`;
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.lineWidth = Number((w / 100).toFixed(4));
ctx.strokeStyle = 'rgba(0, 0, 0, 1)';
ctx.fillText(text, x, y + h);
ctx.strokeText(text, x, y + h);
reader.onerror = function () {
callback({
error: true,
result: reader.result,
}); });
return canvas.toDataURL('image/png');
});
}
/**
* @description 修改 SVG 内容,获取信息
* @param {object} options
* @param {string} options.background
* @param {string} options.foreground
* @param {string} options.svgString
*/
function modifySvgContent(options) {
let {
background = DEFAULT_BGC,
foreground = DEFAULT_FGC,
svgString,
} = options;
let divElement = document.createElement('div');
let gElement = null;
let pathElement = null;
let rectElement = null;
let svgElement = null;
// 添加 DOM 元素,用于获取位置大小信息
document.body.appendChild(divElement);
divElement.innerHTML = svgString;
gElement = divElement.getElementsByTagName('g')[0] || null;
pathElement = divElement.getElementsByTagName('path')[0] || null;
rectElement = divElement.getElementsByTagName('rect')[0] || null;
svgElement = divElement.getElementsByTagName('svg')[0] || null;
if (!(gElement && pathElement && rectElement && svgElement)) {
return null;
}
// 修改填充颜色
gElement.setAttribute('fill', foreground);
rectElement.setAttribute('fill', background);
// 获取位置大小信息
let rectOfG = gElement.getBoundingClientRect();
let rectOfPath = pathElement.getBoundingClientRect();
let offsetX = Math.round(rectOfPath.left - rectOfG.left);
let offsetY = Math.round(rectOfPath.top - rectOfG.top);
let sizeW = Math.round(rectOfPath.width);
let sizeH = Math.round(rectOfPath.height);
let result = {
offsetX: offsetX,
offsetY: offsetY,
sizeW: sizeW,
sizeH: sizeH,
svgString: divElement.innerHTML,
}; };
reader.onload = function () { // 输出处理结果
callback({ console.debug(PREFIX, '处理 SVG', result);
error: false,
result: reader.result, // 处理完成,移除 DOM 元素
document.body.removeChild(divElement);
return result;
}
/**
* 将图片 Blob 渲染到 Canvas
* @param {Blob} blob
* @param {HTMLCanvasElement} canvas
* @returns {Promise<boolean>}
*/
function renderImageToCanvas(blob, canvas) {
let ctx = canvas.getContext('2d');
if (window.createImageBitmap) {
return createImageBitmap(blob).then((bitmap) => {
canvas.width = bitmap.width;
canvas.height = bitmap.height;
ctx.drawImage(bitmap, 0, 0);
return true;
}).catch((error) => {
console.error('渲染图片失败:');
console.error(error);
return false;
}); });
} else {
return new Promise((resolve) => {
let image = new Image();
let url = URL.createObjectURL(blob);
image.onload = () => {
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
URL.revokeObjectURL(url);
ctx.drawImage(image, 0, 0);
resolve(true);
}; };
reader.readAsDataURL(blob); image.onerror = () => {
console.error(PREFIX, '渲染图片失败:加载图片失败');
URL.revokeObjectURL(url);
resolve(false);
};
image.src = url;
});
}
} }
@@ -75,6 +214,8 @@ export function blobToDataURL(blob, callback) {
* @param {object} options * @param {object} options
* @param {HTMLCanvasElement} options.canvas * @param {HTMLCanvasElement} options.canvas
* @param {string} options.svgString * @param {string} options.svgString
* @param {string} options.background
* @param {string} options.foreground
* @param {number} options.drawLeft * @param {number} options.drawLeft
* @param {number} options.drawTop * @param {number} options.drawTop
* @param {number} options.drawWidth * @param {number} options.drawWidth
@@ -85,27 +226,42 @@ function renderSvgToCanvas(options) {
let { let {
canvas, svgString, canvas, svgString,
background = DEFAULT_BGC, foreground = DEFAULT_FGC,
drawLeft = 0, drawTop = 0, drawLeft = 0, drawTop = 0,
drawWidth = 0, drawHeight = 0, drawWidth = 0, drawHeight = 0,
} = options; } = options;
let svgInfo = modifySvgContent({
background: background,
foreground: foreground,
svgString: svgString,
});
if (!svgInfo) {
return Promise.resolve(false);
}
return new Promise((resolve) => { return new Promise((resolve) => {
let svgBlob = new Blob([svgString], { let ctx = canvas.getContext('2d');
let image = new Image();
let svgBlob = new Blob([svgInfo.svgString], {
type: 'image/svg+xml;charset=utf-8', type: 'image/svg+xml;charset=utf-8',
}); });
let svgUrl = URL.createObjectURL(svgBlob); let svgUrl = URL.createObjectURL(svgBlob);
let image = new Image();
image.onerror = () => { image.onerror = () => {
console.error('加载 SVG 失败'); console.error(PREFIX, '加载 SVG 失败');
URL.revokeObjectURL(svgUrl); URL.revokeObjectURL(svgUrl);
resolve(false); resolve(false);
}; };
image.onload = () => { image.onload = () => {
let ctx = canvas.getContext('2d'); ctx.drawImage(
ctx.drawImage(image, drawLeft, drawTop, drawWidth, drawHeight); image,
svgInfo.offsetX, svgInfo.offsetY, svgInfo.sizeW, svgInfo.sizeH,
drawLeft, drawTop, drawWidth, drawHeight,
);
URL.revokeObjectURL(svgUrl); URL.revokeObjectURL(svgUrl);
resolve(true); resolve(true);
}; };
@@ -117,74 +273,70 @@ function renderSvgToCanvas(options) {
/** /**
* @description 解析二维码图片 * @description 解析二维码图片
* @param {Blob} image 图片二进制 * @param {Blob} blob 图片二进制
*/ */
export function readQrCodeImage(image) { export function readQrCodeImage(blob) {
/** /**
* @desc 返回结果 * @desc 返回结果
* @type {{ error: string; image: string; textList: string[]; }} * @type {{ error: string; image: string; textList: string[]; }}
*/ */
let returns = { let result = {
error: '', error: '',
image: '', image: '',
textList: [], textList: [],
}; };
/** 读取图片,转换为 DataURL */ return readBarcodes(blob, readerOptions).then((codeList) => {
let fileReader = new FileReader();
return new Promise((resolve) => { let rectList = [];
let textList = result.textList;
// 处理读取异常 if (codeList.length === 0) {
fileReader.onerror = function () { console.warn(PREFIX, '解析二维码失败:未识别到内容');
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 { } else {
return null; console.debug(PREFIX, '解析二维码成功:', codeList);
}
}).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++) { for (let i = 0; i < codeList.length; i++) {
let item = resultList[i]; let item = codeList[i];
let position = item.position;
let posX0 = position.topLeft.x;
let posX1 = position.bottomRight.x;
let posY0 = position.topLeft.y;
let posY1 = position.bottomRight.y;
// 记录二维码坐标
rectList.push({
x: posX0,
y: posY0,
w: posX1 - posX0,
h: posY1 - posY0,
});
// 记录二维码文本
textList.push(item.text); textList.push(item.text);
} }
return returns; // 框选二维码区域
return drawRectsOnImage(blob, rectList);
}).then((dataURL) => {
if (dataURL) {
result.image = dataURL;
}
return result;
}).catch((error) => { }).catch((error) => {
console.error('解析二维码失败:'); console.error(PREFIX, '解析二维码失败:');
console.error(error); console.error(error);
returns.error = String(error); result.error = String(error);
return returns; return result;
});; });
} }
@@ -192,13 +344,19 @@ export function readQrCodeImage(image) {
* @description 生成二维码图片 * @description 生成二维码图片
* @param {object} options * @param {object} options
* @param {string} options.content * @param {string} options.content
* @param {string} options.background
* @param {string} options.foreground
* @param {number} options.width * @param {number} options.width
* @param {number} options.height * @param {number} options.height
* @returns 二维码图片 DataURL * @returns 二维码图片 DataURL
*/ */
export function writeQrCodeImage(options = {}) { export function writeQrCodeImage(options = {}) {
let { content = '', width = 256, height = 256 } = options; let {
content = '',
background = DEFAULT_BGC, foreground = DEFAULT_FGC,
width = 256, height = 256,
} = options;
let canvas = document.createElement('canvas'); let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d'); let ctx = canvas.getContext('2d');
@@ -208,15 +366,15 @@ export function writeQrCodeImage(options = {}) {
canvas.height = height; canvas.height = height;
// 设置背景颜色 // 设置背景颜色
ctx.fillStyle = '#FFFFFF'; ctx.fillStyle = background;
ctx.fillRect(0, 0, width, height); ctx.fillRect(0, 0, width, height);
return writeBarcode(content, writerOptions).then((result) => { return writeBarcode(content, writerOptions).then((result) => {
console.debug('生成二维码', result); console.debug(PREFIX, '生成二维码', result);
if (result.error) { if (result.error) {
console.error(`生成二维码失败:${result.error}`); console.error(PREFIX, `生成二维码失败:${result.error}`);
return ''; return '';
} else { } else {
return result.svg; return result.svg;
@@ -227,6 +385,8 @@ export function writeQrCodeImage(options = {}) {
return renderSvgToCanvas({ return renderSvgToCanvas({
canvas: canvas, canvas: canvas,
svgString: svgString, svgString: svgString,
background: background,
foreground: foreground,
drawLeft: 0, drawLeft: 0,
drawTop: 0, drawTop: 0,
drawWidth: width, drawWidth: width,
@@ -244,7 +404,7 @@ export function writeQrCodeImage(options = {}) {
} }
}).catch((error) => { }).catch((error) => {
console.error('生成二维码失败:'); console.error(PREFIX, '生成二维码失败:');
console.error(error); console.error(error);
return ''; return '';
}); });

View File

@@ -41,12 +41,23 @@ export const toolList = [
title: '转换', title: '转换',
enabled: true, enabled: true,
items: [ items: [
{
id: 'base64-encode-decode',
component: 'Conversion/Base64StringEncodeDecode',
title: 'Base64 字符串编码 / 解码',
iconClass: 'mdi mdi-swap-horizontal',
desc: '处理 Base64 编码的字符串。',
createdAt: '',
updatedAt: '',
version: '0',
enabled: false,
},
{ {
id: 'convert-timestamp', id: 'convert-timestamp',
component: 'Conversion/ConvertTimestamp', component: 'Conversion/ConvertTimestamp',
title: 'Unix 时间戳转换', title: 'Unix 时间戳转换',
iconClass: 'mdi mdi-swap-horizontal', iconClass: 'mdi mdi-swap-horizontal',
desc: '时间戳转时间 / 时间转时间戳', desc: '时间戳转时间字符串 / 时间字符串转时间戳',
createdAt: '2025-02-05', createdAt: '2025-02-05',
updatedAt: '2025-02-05', updatedAt: '2025-02-05',
version: '1', version: '1',
@@ -57,7 +68,7 @@ export const toolList = [
component: 'Conversion/UrlEncodeDecode', component: 'Conversion/UrlEncodeDecode',
title: 'URL 编码 / 解码', title: 'URL 编码 / 解码',
iconClass: 'mdi mdi-swap-horizontal', iconClass: 'mdi mdi-swap-horizontal',
desc: '', desc: '处理 URL 编码的字符串。',
createdAt: '', createdAt: '',
updatedAt: '', updatedAt: '',
version: '0', version: '0',
@@ -68,10 +79,10 @@ export const toolList = [
component: 'Conversion/QrcodeReaderAndGenerator', component: 'Conversion/QrcodeReaderAndGenerator',
title: '二维码解析和生成', title: '二维码解析和生成',
iconClass: 'mdi mdi-qrcode', iconClass: 'mdi mdi-qrcode',
desc: '解析二维码生成二维码', desc: '解析二维码 / 生成二维码',
createdAt: '2025-02-21', createdAt: '2025-02-21',
updatedAt: '2025-02-21', updatedAt: '2025-02-23',
version: '1', version: '2',
enabled: true, enabled: true,
}, },
{ {
@@ -143,6 +154,17 @@ export const toolList = [
version: '0', version: '0',
enabled: false, enabled: false,
}, },
{
id: 'frp-config-generator',
component: 'Generator/UuidGenerator',
title: 'UUID 生成器',
iconClass: 'mdi mdi-identifier',
desc: '生成 UUID 列表。',
createdAt: '',
updatedAt: '',
version: '0',
enabled: false,
},
{ {
id: 'generate-urls', id: 'generate-urls',
component: 'Generator/GenerateUrls', component: 'Generator/GenerateUrls',
@@ -246,7 +268,7 @@ export const toolList = [
component: 'Other/KeepScreenOn', component: 'Other/KeepScreenOn',
title: '保持亮屏', title: '保持亮屏',
iconClass: 'mdi mdi-monitor', iconClass: 'mdi mdi-monitor',
desc: '保持屏幕开启,不息屏,不休眠', desc: '保持屏幕开启,不息屏,不休眠',
createdAt: '2024-10-11', createdAt: '2024-10-11',
updatedAt: '2024-10-13', updatedAt: '2024-10-13',
version: '2', version: '2',
@@ -257,7 +279,7 @@ export const toolList = [
component: 'Other/OpenNewWindow', component: 'Other/OpenNewWindow',
title: '新窗口中打开', title: '新窗口中打开',
iconClass: 'mdi mdi-window-maximize', iconClass: 'mdi mdi-window-maximize',
desc: '从新的小窗口中打开指定的链接(仅支持 PC 端浏览器)', desc: '从新的小窗口中打开指定的链接(仅支持 PC 端浏览器)',
createdAt: '2025-02-04', createdAt: '2025-02-04',
updatedAt: '2025-02-04', updatedAt: '2025-02-04',
version: '1', version: '1',
@@ -268,7 +290,7 @@ export const toolList = [
component: 'Other/RunJavaScript', component: 'Other/RunJavaScript',
title: '执行 JavaScript', title: '执行 JavaScript',
iconClass: 'mdi mdi-code-braces', iconClass: 'mdi mdi-code-braces',
desc: '执行简单的 JavaScript 代码片段', desc: '执行简单的 JavaScript 代码片段',
createdAt: '', createdAt: '',
updatedAt: '', updatedAt: '',
version: '0', version: '0',
@@ -279,7 +301,7 @@ export const toolList = [
component: 'Other/GenshinImpactClock/GenshinImpactClock', component: 'Other/GenshinImpactClock/GenshinImpactClock',
title: '《原神》时钟', title: '《原神》时钟',
iconClass: 'mdi mdi-clock-outline', iconClass: 'mdi mdi-clock-outline',
desc: '在网页上实现的《原神》时钟效果', desc: '在网页上实现的《原神》时钟效果',
createdAt: '2024-10-13', createdAt: '2024-10-13',
updatedAt: '2024-10-13', updatedAt: '2024-10-13',
version: '1', version: '1',

View File

@@ -0,0 +1,9 @@
<template>
<div class="tool-detail-page"></div>
</template>
<script setup>
</script>
<style lang="less" scoped>
</style>

View File

@@ -3,6 +3,21 @@
<!-- 解析二维码 --> <!-- 解析二维码 -->
<n-card size="small" title="解析二维码"> <n-card size="small" title="解析二维码">
<template #header-extra>
<n-flex>
<n-button
type="primary"
:text="true"
@click="handlePasteImage"
>粘贴</n-button>
<n-button
type="error"
:disabled="!readerData.dataURL"
:text="true"
@click="handleClearReader"
>清空</n-button>
</n-flex>
</template>
<n-form <n-form
class="form-no-feedback" class="form-no-feedback"
label-align="left" label-align="left"
@@ -35,7 +50,7 @@
object-fit="contain" object-fit="contain"
width="100%" width="100%"
height="100%" height="100%"
:preview-disabled="true" :preview-disabled="false"
:src="readerData.dataURL" :src="readerData.dataURL"
/> />
</div> </div>
@@ -58,13 +73,94 @@
</n-card> </n-card>
<!-- 生成二维码 --> <!-- 生成二维码 -->
<n-card v-if="false" size="small" title="生成二维码"> <n-card size="small" title="生成二维码">
<template #header-extra>
<n-flex>
<n-button
type="primary"
:disabled="!writerData.content"
:text="true"
@click="handleGenerateQrCode"
>生成</n-button>
<n-button
type="primary"
:disabled="!writerData.dataURL"
:text="true"
@click="handleDownloadQrCode"
>下载</n-button>
<n-button
type="error"
:disabled="!writerData.dataURL"
:text="true"
@click="handleClearWriter"
>清空</n-button>
</n-flex>
</template>
<n-form <n-form
class="form-no-feedback" class="form-no-feedback writer-config"
label-align="right" label-align="left"
label-placement="top" label-placement="top"
label-width="auto" label-width="auto"
></n-form> >
<n-form-item label="文本内容">
<n-input
v-model:value="writerData.content"
placeholder="请输入需要生成的二维码文本内容"
type="textarea"
:rows="4"
></n-input>
</n-form-item>
<n-form-item label="分辨率">
<n-flex align="center">
<n-input-number
v-model:value="writerData.resolution"
:min="64"
:max="8192"
:step="1"
></n-input-number>
<span>px</span>
</n-flex>
</n-form-item>
<n-form-item label="颜色">
<n-flex align="center">
<span>前景颜色</span>
<n-color-picker
v-model:value="writerData.colorForeground"
:modes="['hex']"
:show-alpha="true"
:show-preview="true"
:swatches="['#00000000', '#000000FF', '#FFFFFFFF']"
/>
<span>背景颜色</span>
<n-color-picker
v-model:value="writerData.colorBackground"
:modes="['hex']"
:show-alpha="true"
:show-preview="true"
:swatches="['#00000000', '#000000FF', '#FFFFFFFF']"
/>
</n-flex>
</n-form-item>
<n-form-item label="二维码预览">
<div class="writer-preview">
<div class="writer-preview__wrapper">
<n-image
v-show="writerData.dataURL"
object-fit="contain"
width="100%"
height="100%"
:preview-disabled="true"
:src="writerData.dataURL"
/>
</div>
</div>
</n-form-item>
</n-form>
</n-card> </n-card>
</div> </div>
@@ -72,9 +168,9 @@
<script setup> <script setup>
import { import {
NCard, NFlex, NForm, NFormItem, NButton, NCard, NColorPicker, NFlex,
NImage, NLi, NOl, NForm, NFormItem, NInput, NInputNumber,
NUpload, NUploadDragger, NImage, NLi, NOl, NUpload, NUploadDragger,
} from 'naive-ui'; } from 'naive-ui';
import { import {
@@ -86,7 +182,7 @@ import {
} from '@/assets/js/naive-ui'; } from '@/assets/js/naive-ui';
import { import {
readQrCodeImage, readQrCodeImage, writeQrCodeImage,
} from '@/assets/js/qr-code'; } from '@/assets/js/qr-code';
/** 二维码解析相关数据 */ /** 二维码解析相关数据 */
@@ -103,18 +199,142 @@ const readerData = reactive({
}); });
// /** 二维码生成相关数据 */ /** 二维码生成相关数据 */
// const writerData = reactive({ const writerData = reactive({
// });
/** 背景颜色 */
colorBackground: '#FFFFFF',
/** 前景颜色 */
colorForeground: '#000000',
/** 文本内容 */
content: '',
/** 图片 DataURL */
dataURL: '',
/** 分辨率 */
resolution: 256,
});
/** 清空信息 */
function handleClearReader() {
readerData.dataURL = '';
readerData.results = [];
}
/** 清空信息 */
function handleClearWriter() {
writerData.content = '';
writerData.dataURL = '';
writerData.resolution = 256;
}
/** 处理下载二维码 */
function handleDownloadQrCode() {
let url = writerData.dataURL;
let element = document.createElement('a');
if (url) {
element.download = '二维码.png';
element.href = url;
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
}
/** 处理生成二维码 */
function handleGenerateQrCode() {
return writeQrCodeImage({
content: writerData.content,
background: writerData.colorBackground,
foreground: writerData.colorForeground,
width: writerData.resolution,
height: writerData.resolution,
}).then((dataURL) => {
if (dataURL) {
$message.success('生成二维码成功');
} else {
$message.error('生成二维码失败');
}
writerData.dataURL = dataURL;
});
}
/** 从剪贴板中获取图片 */
function handlePasteImage() {
let cb = navigator.clipboard;
if (!cb) {
$message.error('当前浏览器不支持该操作');
return Promise.resolve(false);
}
return cb.read().then((items) => {
console.debug('剪贴板内容', items);
let imageItem = null;
let imageType = '';
for (let i = 0; i < items.length; i++) {
let item = items[i];
let types = item.types;
for (let j = 0; j < types.length; j++) {
let type = types[j];
if (type.startsWith('image/')) {
imageItem = item;
imageType = type;
break;
}
}
if (imageItem) {
break;
}
}
if (imageItem) {
return imageItem.getType(imageType);
} else {
return null;
}
}).then((blob) => {
if (blob) {
return handleParseQrCode(blob);
} else {
$message.warning('未在剪贴板中找到图片');
return false;
}
}).catch((error) => {
console.error('读取剪贴板失败:');
console.error(error);
$message.error('读取剪贴板失败,可能是没有权限。');
return false;
});
}
/** /**
* @description 处理选择图片 * @description 处理解析二维码图片
* @type { import('naive-ui').UploadOnChange } * @param {File} file
*/ */
function handleSelectQrImage(options) { function handleParseQrCode(file) {
let file = options.file.file;
return readQrCodeImage(file).then((result) => { return readQrCodeImage(file).then((result) => {
let { error, image, textList } = result; let { error, image, textList } = result;
@@ -123,6 +343,7 @@ function handleSelectQrImage(options) {
$message.error(error); $message.error(error);
readerData.dataURL = ''; readerData.dataURL = '';
readerData.results = []; readerData.results = [];
return false;
} else { } else {
if (textList.length === 0) { if (textList.length === 0) {
$message.warning('未识别到有效的二维码'); $message.warning('未识别到有效的二维码');
@@ -131,10 +352,18 @@ function handleSelectQrImage(options) {
} }
readerData.dataURL = image; readerData.dataURL = image;
readerData.results = textList; readerData.results = textList;
return true;
} }
}); });
}
/**
* @description 处理选择图片
* @type { import('naive-ui').UploadOnChange }
*/
function handleSelectQrImage(options) {
return handleParseQrCode(options.file.file);
} }
</script> </script>
@@ -152,6 +381,8 @@ function handleSelectQrImage(options) {
.n-image { .n-image {
margin: auto; margin: auto;
width: 100%;
height: 100%;
} }
} }
@@ -162,4 +393,29 @@ function handleSelectQrImage(options) {
user-select: text; user-select: text;
} }
} }
.writer-config {
.n-color-picker {
width: 128px;
}
}
.writer-preview {
width: 100%;
.writer-preview__wrapper {
display: flex;
width: 256px;
height: 256px;
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
.n-image {
margin: auto;
width: 100%;
height: 100%;
image-rendering: pixelated;
}
}
}
</style> </style>

View File

@@ -0,0 +1,9 @@
<template>
<div class="tool-detail-page"></div>
</template>
<script setup>
</script>
<style lang="less" scoped>
</style>

View File

@@ -6,21 +6,19 @@
<n-form <n-form
class="form-no-feedback config-inputs" class="form-no-feedback config-inputs"
label-align="left" label-align="left"
label-placement="left" label-placement="top"
label-width="auto" label-width="auto"
> >
<n-form-item label="目标链接"> <n-form-item label="目标链接">
<n-input <n-input
v-model:value="data.url" v-model:value="data.url"
placeholder="请输入需要打开的 URL需要包含协议部分https://" placeholder="请输入 URL需要包含协议部分https://"
type="text" type="text"
></n-input> ></n-input>
</n-form-item> </n-form-item>
<n-form-item label="窗口大小:"> <n-form-item label="窗口宽度">
<n-flex align="center">
<span>宽度</span>
<n-input-number <n-input-number
v-model:value="data.width" v-model:value="data.width"
:min="0" :min="0"
@@ -28,7 +26,9 @@
:precision="0" :precision="0"
:step="1" :step="1"
></n-input-number> ></n-input-number>
<span>高度</span> </n-form-item>
<n-form-item label="窗口高度">
<n-input-number <n-input-number
v-model:value="data.height" v-model:value="data.height"
:min="0" :min="0"
@@ -36,7 +36,6 @@
:precision="0" :precision="0"
:step="1" :step="1"
></n-input-number> ></n-input-number>
</n-flex>
</n-form-item> </n-form-item>
</n-form> </n-form>
@@ -44,12 +43,10 @@
<!-- 操作 --> <!-- 操作 -->
<n-card size="small" title="操作"> <n-card size="small" title="操作">
<n-flex>
<n-button <n-button
type="primary" type="primary"
@click="openWindow" @click="openWindow"
>打开窗口</n-button> >打开窗口</n-button>
</n-flex>
</n-card> </n-card>
</div> </div>
@@ -57,7 +54,7 @@
<script setup> <script setup>
import { import {
NButton, NCard, NFlex, NButton, NCard,
NForm, NFormItem, NInput, NInputNumber, NForm, NFormItem, NInput, NInputNumber,
} from 'naive-ui'; } from 'naive-ui';
@@ -86,18 +83,7 @@ function openWindow() {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.config-inputs { .config-inputs .n-form-item {
.n-input-number {
flex-grow: 1;
width: 0;
}
.n-flex {
width: 100%;
}
:deep(.n-form-item-blank) {
max-width: 480px; max-width: 480px;
}
} }
</style> </style>