422 lines
9.3 KiB
Vue
422 lines
9.3 KiB
Vue
<template>
|
||
<div class="tool-detail-page">
|
||
|
||
<!-- 解析二维码 -->
|
||
<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
|
||
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="false"
|
||
: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 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
|
||
class="form-no-feedback writer-config"
|
||
label-align="left"
|
||
label-placement="top"
|
||
label-width="auto"
|
||
>
|
||
|
||
<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>
|
||
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import {
|
||
NButton, NCard, NColorPicker, NFlex,
|
||
NForm, NFormItem, NInput, NInputNumber,
|
||
NImage, NLi, NOl, NUpload, NUploadDragger,
|
||
} from 'naive-ui';
|
||
|
||
import {
|
||
reactive,
|
||
} from 'vue';
|
||
|
||
import {
|
||
$message,
|
||
} from '@/assets/js/naive-ui';
|
||
|
||
import {
|
||
readQrCodeImage, writeQrCodeImage,
|
||
} from '@/assets/js/qr-code';
|
||
|
||
/** 二维码解析相关数据 */
|
||
const readerData = reactive({
|
||
|
||
/** 图片 DataURL */
|
||
dataURL: '',
|
||
|
||
/** 选择文件列表 */
|
||
fileList: [],
|
||
|
||
/** 解析结果 */
|
||
results: [],
|
||
|
||
});
|
||
|
||
/** 二维码生成相关数据 */
|
||
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 处理解析二维码图片
|
||
* @param {File} file
|
||
*/
|
||
function handleParseQrCode(file) {
|
||
return readQrCodeImage(file).then((result) => {
|
||
|
||
let { error, image, textList } = result;
|
||
|
||
if (error) {
|
||
$message.error(error);
|
||
readerData.dataURL = '';
|
||
readerData.results = [];
|
||
return false;
|
||
} else {
|
||
if (textList.length === 0) {
|
||
$message.warning('未识别到有效的二维码');
|
||
} else {
|
||
$message.success('识别成功');
|
||
}
|
||
readerData.dataURL = image;
|
||
readerData.results = textList;
|
||
return true;
|
||
}
|
||
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @description 处理选择图片
|
||
* @type { import('naive-ui').UploadOnChange }
|
||
*/
|
||
function handleSelectQrImage(options) {
|
||
return handleParseQrCode(options.file.file);
|
||
}
|
||
</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;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
}
|
||
|
||
.reader-result__text-list {
|
||
flex-grow: 1;
|
||
min-width: 256px;
|
||
width: 0;
|
||
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>
|