mirror of
https://github.com/fofolee/uTools-quickcommand.git
synced 2025-06-07 21:46:12 +08:00
318 lines
7.6 KiB
Vue
318 lines
7.6 KiB
Vue
<template>
|
|
<div class="image-search-editor">
|
|
<div class="row q-col-gutter-sm">
|
|
<!-- 图片预览区域 -->
|
|
<div class="col-12 col-sm-8">
|
|
<div
|
|
class="image-preview q-pa-md"
|
|
:class="{ 'has-image': !!argvs.imagePreview }"
|
|
@click="triggerImageUpload"
|
|
@paste="handlePaste"
|
|
tabindex="0"
|
|
>
|
|
<template v-if="argvs.imagePreview">
|
|
<img :src="argvs.imagePreview" class="preview-image" />
|
|
<q-btn
|
|
round
|
|
flat
|
|
dense
|
|
icon="close"
|
|
class="remove-image"
|
|
@click.stop="clearImage"
|
|
>
|
|
<q-tooltip>移除图片</q-tooltip>
|
|
</q-btn>
|
|
</template>
|
|
<template v-else>
|
|
<div class="upload-placeholder">
|
|
<q-icon name="add_photo_alternate" size="48px" color="grey-6" />
|
|
<div class="text-grey-6 q-mt-sm">点击上传或粘贴图片</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 配置区域 -->
|
|
<div class="col-12 col-sm-4">
|
|
<div class="row">
|
|
<!-- 截图按钮 -->
|
|
<div class="col-12">
|
|
<q-btn
|
|
outline
|
|
color="primary"
|
|
class="full-width"
|
|
@click="getImgByCaptureScreen"
|
|
>
|
|
<q-icon name="crop" class="q-mr-sm" />
|
|
截图查找
|
|
</q-btn>
|
|
</div>
|
|
|
|
<!-- 匹配阈值设置 -->
|
|
<div class="col-12">
|
|
<NumberInput
|
|
v-if="false"
|
|
:model-value="argvs.threshold"
|
|
@update:model-value="updateArgvs('threshold', $event)"
|
|
label="匹配阈值"
|
|
:min="0"
|
|
:max="1"
|
|
:step="0.1"
|
|
class="border-primary"
|
|
:command="{
|
|
icon: 'tune',
|
|
}"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 鼠标动作选择 -->
|
|
<div class="col-12">
|
|
<ButtonGroup
|
|
:is-collapse="false"
|
|
:model-value="argvs.mouseAction"
|
|
@update:model-value="updateArgvs('mouseAction', $event)"
|
|
:options="mouseActionOptions"
|
|
label="找到后"
|
|
class="border-primary"
|
|
>
|
|
</ButtonGroup>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 隐藏的文件上传input -->
|
|
<input
|
|
type="file"
|
|
ref="fileInput"
|
|
accept="image/*"
|
|
style="display: none"
|
|
@change="handleFileUpload"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { defineComponent } from "vue";
|
|
import NumberInput from "components/composer/common/NumberInput.vue";
|
|
import ButtonGroup from "components/composer/common/ButtonGroup.vue";
|
|
|
|
export default defineComponent({
|
|
name: "ImageSearchEditor",
|
|
components: {
|
|
NumberInput,
|
|
ButtonGroup,
|
|
},
|
|
|
|
props: {
|
|
modelValue: {
|
|
type: Object,
|
|
default: () => ({}),
|
|
},
|
|
},
|
|
|
|
emits: ["update:modelValue"],
|
|
|
|
data() {
|
|
return {
|
|
mouseActionOptions: [
|
|
{ label: "不处理", value: "none" },
|
|
{ label: "单击", value: "click" },
|
|
{ label: "双击", value: "dblclick" },
|
|
{ label: "右击", value: "rightclick" },
|
|
],
|
|
defaultArgvs: {
|
|
imagePreview: "",
|
|
threshold: 1,
|
|
mouseAction: "none",
|
|
},
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
argvs() {
|
|
return (
|
|
this.modelValue.argvs || this.parseCodeToArgvs(this.modelValue.code)
|
|
);
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
// 触发文件上传
|
|
triggerImageUpload() {
|
|
this.$refs.fileInput.click();
|
|
},
|
|
|
|
updateArgvs(key, value) {
|
|
const argvs = { ...this.argvs, [key]: value };
|
|
this.updateModelValue(argvs);
|
|
},
|
|
|
|
// 处理文件上传
|
|
async handleFileUpload(event) {
|
|
const file = event.target.files[0];
|
|
if (file) {
|
|
try {
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
this.updateArgvs("imagePreview", e.target.result);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
} catch (error) {
|
|
quickcommand.showMessageBox("读取图片失败", "error");
|
|
}
|
|
}
|
|
},
|
|
|
|
// 处理粘贴事件
|
|
async handlePaste(event) {
|
|
const items = (event.clipboardData || event.originalEvent.clipboardData)
|
|
.items;
|
|
for (const item of items) {
|
|
if (item.type.indexOf("image") === 0) {
|
|
const blob = item.getAsFile();
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
this.updateArgvs("imagePreview", e.target.result);
|
|
};
|
|
reader.readAsDataURL(blob);
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
// 从剪贴板读取
|
|
async getImgByCaptureScreen() {
|
|
const img = await quickcomposer.simulate.captureScreenToClipboard({
|
|
type: "area",
|
|
});
|
|
if (!img) return;
|
|
this.updateArgvs("imagePreview", img);
|
|
},
|
|
|
|
// 清除图片
|
|
clearImage() {
|
|
this.updateArgvs("imagePreview", "");
|
|
},
|
|
|
|
// 更新值
|
|
generateCode(argvs = this.argvs) {
|
|
const imageData = argvs.imagePreview.split(",")[1] || "";
|
|
const config = {
|
|
imageData,
|
|
threshold: argvs.threshold,
|
|
mouseAction: argvs.mouseAction,
|
|
};
|
|
|
|
// 生成代码
|
|
return `${this.modelValue.value}("data:image/png;base64,${config.imageData}", { threshold: ${config.threshold}, mouseAction: "${config.mouseAction}" })`;
|
|
},
|
|
|
|
parseCodeToArgvs(code) {
|
|
const argvs = window.lodashM.cloneDeep(this.defaultArgvs);
|
|
if (!code) return argvs;
|
|
|
|
// 从代码字符串解析配置
|
|
try {
|
|
const imageDataMatch = code.match(/"data:image\/png;base64,([^"]+)"/);
|
|
const thresholdMatch = code.match(/threshold:\s*([\d.]+)/);
|
|
const mouseActionMatch = code.match(/mouseAction:\s*"([^"]+)"/);
|
|
let { imagePreview, threshold, mouseAction } = argvs;
|
|
|
|
if (imageDataMatch) {
|
|
imagePreview = `data:image/png;base64,${imageDataMatch[1]}`;
|
|
}
|
|
if (thresholdMatch) {
|
|
threshold = parseFloat(thresholdMatch[1]);
|
|
}
|
|
if (mouseActionMatch) {
|
|
mouseAction = mouseActionMatch[1];
|
|
}
|
|
return {
|
|
imagePreview,
|
|
threshold,
|
|
mouseAction,
|
|
};
|
|
} catch (e) {
|
|
return argvs;
|
|
}
|
|
},
|
|
updateModelValue(argvs) {
|
|
this.$emit("update:modelValue", {
|
|
...this.modelValue,
|
|
argvs,
|
|
code: this.generateCode(argvs),
|
|
});
|
|
},
|
|
},
|
|
mounted() {
|
|
const argvs = this.modelValue.argvs || this.defaultArgvs;
|
|
if (!this.modelValue.code) {
|
|
this.updateModelValue(argvs);
|
|
}
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.image-search-editor {
|
|
width: 100%;
|
|
}
|
|
|
|
.image-preview {
|
|
border: 2px dashed var(--q-primary);
|
|
border-radius: 8px;
|
|
display: flex;
|
|
max-height: 138px;
|
|
min-height: 138px;
|
|
justify-content: center;
|
|
align-items: center;
|
|
position: relative;
|
|
cursor: pointer;
|
|
outline: none;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.image-preview:hover {
|
|
border-color: var(--q-primary);
|
|
background: rgba(var(--q-primary), 0.03);
|
|
}
|
|
|
|
.image-preview.has-image {
|
|
border-style: solid;
|
|
padding: 4px;
|
|
}
|
|
|
|
.preview-image {
|
|
max-width: 100%;
|
|
max-height: 120px;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.remove-image {
|
|
position: absolute;
|
|
top: 4px;
|
|
right: 4px;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
color: white;
|
|
}
|
|
|
|
.remove-image:hover {
|
|
background: rgba(0, 0, 0, 0.7);
|
|
}
|
|
|
|
.upload-placeholder {
|
|
text-align: center;
|
|
}
|
|
|
|
/* 暗色模式适配 */
|
|
:deep(.dark) .image-preview:hover {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
.border-primary {
|
|
border: 1px solid var(--q-primary);
|
|
border-radius: 4px;
|
|
}
|
|
</style>
|