Commit afd50a86 authored by Administrator's avatar Administrator

样本工具改造v3

parent 6fd406f8
...@@ -23,6 +23,7 @@ declare module 'vue' { ...@@ -23,6 +23,7 @@ declare module 'vue' {
ElSteps: typeof import('element-plus/es')['ElSteps'] ElSteps: typeof import('element-plus/es')['ElSteps']
ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElText: typeof import('element-plus/es')['ElText'] ElText: typeof import('element-plus/es')['ElText']
ElTree: typeof import('element-plus/es')['ElTree']
ElUpload: typeof import('element-plus/es')['ElUpload'] ElUpload: typeof import('element-plus/es')['ElUpload']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
......
...@@ -3,6 +3,7 @@ import HomeView from '../views/home/index.vue' ...@@ -3,6 +3,7 @@ import HomeView from '../views/home/index.vue'
import SampleHandleView from '../views/sample_handle/index.vue' import SampleHandleView from '../views/sample_handle/index.vue'
import WmNanoBananaView from '../views/wm_nano_banana/index.vue' import WmNanoBananaView from '../views/wm_nano_banana/index.vue'
import SampleHandleViewV2 from '../views/sample_handle_v2/index.vue' import SampleHandleViewV2 from '../views/sample_handle_v2/index.vue'
import SampleHandleViewV3 from '../views/sample_handle_v3/index.vue'
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL), history: createWebHashHistory(import.meta.env.BASE_URL),
...@@ -10,7 +11,7 @@ const router = createRouter({ ...@@ -10,7 +11,7 @@ const router = createRouter({
{ {
path: '/', path: '/',
name: 'home', name: 'home',
component: SampleHandleViewV2 component: SampleHandleViewV3
}, },
{ {
path: '/report', path: '/report',
...@@ -32,6 +33,11 @@ const router = createRouter({ ...@@ -32,6 +33,11 @@ const router = createRouter({
name: 'sample_handle_v2', name: 'sample_handle_v2',
component: SampleHandleViewV2 component: SampleHandleViewV2
}, },
{
path: '/sample_handle_v3',
name: 'sample_handle_v3',
component: SampleHandleViewV3
},
] ]
}) })
......
import aitoolsService from '@/api/service/aitoolsService'
// 检测设备类型
export const detectDeviceType = () => {
const userAgent = navigator.userAgent.toLowerCase()
let source = ''
if (userAgent.match(/mobile/i) || userAgent.match(/android/i) || userAgent.match(/iphone/i) || userAgent.match(/ipad/i)) {
source = 'mobile'
// if (userAgent.match(/iphone/i) || userAgent.match(/ipad/i)) {
// source = 'ios'
// } else if (userAgent.match(/android/i)) {
// source = 'android'
// }
if (userAgent.match(/micromessenger/i)) {
source = 'wechat'
}
} else {
source = 'pc'
}
console.log('设备类型:', source)
return source
}
// 记录用户操作
export const trackUserAction = async (source: string, user_id: string, content: string, action: string) => {
let param: any = {
source: source,
user_id: user_id,
content: content,
action: action,
}
aitoolsService.commonApi('提交用户轨迹数据', 'track', param)
.then((response) => {
if (response == 'ok') {
console.log(action, '上报成功')
} else {
console.log(action, '上报失败')
}
})
.catch((error) => {
console.log(action, '上报失败:', error)
})
}
import aitoolsService from '@/api/service/aitoolsService';
import { trackUserAction } from './common';
import { ElMessage, ElMessageBox } from 'element-plus';
import utils from '@/utils/utils';
import { reactive, ref } from 'vue';
// 客户留资
export const myCustomerInfo = (form: any, title: any) => {
const customer = reactive({
name: '',
mobile: '',
company: '',
note: '',
})
const customerInfoVisible = ref(false)
const onCustomerInfoSubmit = async () => {
if (!customer.name || !customer.mobile) {
ElMessage({
message: '请填写姓名和手机号',
type: 'warning'
})
return
}
if (!utils.isPhone(customer.mobile)) {
ElMessage({
message: '手机号格式不正确',
type: 'warning'
})
return
}
try {
console.log(customer)
let param: any = {
name: customer.name,
mobile: customer.mobile,
company: customer.company,
source: form.source,
note: customer.note,
}
await aitoolsService.commonApi('记录客户信息', 'take_customer_info', param);
// 统计
trackUserAction(form.source, form.user_id, title.value, '客户留资')
customerInfoVisible.value = false
// 防止重复提交
localStorage.setItem("isSubmitCustomerInfo", 'yes');
// 新开一个浏览器窗口,打开一个链接
openNewWindow();
} catch (error) {
ElMessage({
message: String(error),
type: 'error'
})
}
}
const openNewWindow = () => {
if (form.source === 'pc') {
window.open(form.report_url, '_blank');
} else {
if (form.source === 'wechat') {
ElMessageBox.confirm(
'请先<span style="color: red;">「点击确认」</span>查看完整报告,如需保存报告到手机,再至右上角选择<span style="color: red;">「默认浏览器」</span>打开',
'微信提示',
{
confirmButtonText: '确认',
type: 'info',
center: true,
showClose: false,
showCancelButton: false,
dangerouslyUseHTMLString: true,
closeOnClickModal: false,
closeOnPressEscape: false,
}
).then(() => {
window.location.href = form.report_url;
})
} else {
window.location.href = form.report_url;
}
}
}
const onClickDownloadPDF = () => {
const isSubmitCustomerInfo = localStorage.getItem("isSubmitCustomerInfo");
console.log('isSubmitCustomerInfo =', isSubmitCustomerInfo);
if (isSubmitCustomerInfo === 'yes') {
customerInfoVisible.value = false
// 直接打开链接
openNewWindow();
} else {
customerInfoVisible.value = true
}
}
return {
customer,
customerInfoVisible,
onCustomerInfoSubmit,
onClickDownloadPDF,
}
}
import { ref } from 'vue'
import {
ElMessage,
genFileId,
type UploadInstance,
type UploadProps,
type UploadRawFile,
type UploadFile,
} from 'element-plus'
export const myFileUpload = (FileSizeLimitM: number, form: any, loading: any) => {
// ############ 处理上传文件 begin #############
const upload = ref<UploadInstance>()
const actionUrl = ref(
import.meta.env.MODE === 'production'
? '/file'
: import.meta.env.VITE_APP_BASE_API + '/file'
)
const handleBeforeUpload = async (file: any) => {
const isLimit = file.size / 1024 / 1024 <= FileSizeLimitM
if (!isLimit) {
ElMessage.error('上传文件大小不能超过 '+FileSizeLimitM+'MB!')
return false
}
}
const handleUploadSuccess = (val: Wm.UploadResult, file: UploadFile) => {
// console.log(val)
if (val.code == 0) {
form.file_path = val.data[0].path
console.log('form.file_path =', form.file_path)
form.file_path_domain = val.data[0].url
console.log('form.file_path_domain =', form.file_path_domain)
// console.log('file =', file.name, file.size, file.url)
if (file.name && file.size !== undefined) {
form.file_name = file.name
form.file_size = file.size
}
ElMessage({
message: '上传成功',
type: 'success'
})
} else {
ElMessage({
message: '上传失败:' + val.message,
type: 'error'
})
}
loading.value = false
}
const handleUploadExceed: UploadProps['onExceed'] = (files) => {
// 清除已上传的文件
upload.value!.clearFiles()
// 获取超出限制的第一个文件
const file = files[0] as UploadRawFile
// 给文件分配一个新的唯一标识
file.uid = genFileId()
// 手动触发文件上传
upload.value!.handleStart(file)
// 提交上传
upload.value!.submit()
}
const handleUploadError = (error: Error) => {
ElMessage({
message: String(error.message),
type: 'error'
})
loading.value = false
}
const handleUploadProgress = (event: ProgressEvent, file: UploadFile) => {
file.percentage = Math.round(event.loaded / event.total * 100)
console.log('上传进度:', file.percentage)
form.file_uploading_progress = file.percentage
file.url = '/video-icon.png'
// 根据进度控制 loading 状态
if (file.percentage <= 100) {
loading.value = true
}
// if (file.percentage == 100) {
// loading.value = false
// }
}
const handleRemoveFile = () => {
// 清除已上传的文件
// upload.value!.clearFiles()
form.file_path = ""
form.file_path_domain = ""
form.file_name = ""
form.file_size = 0
console.log('文件列表移除File, form.file_path =', form.file_path, ', form.file_path_domain =', form.file_path_domain)
}
// ############ 处理上传文件 end #############
return {
loading,
upload,
actionUrl,
handleBeforeUpload,
handleUploadSuccess,
handleUploadExceed,
handleUploadError,
handleUploadProgress,
handleRemoveFile,
}
}
import aitoolsService from '@/api/service/aitoolsService'
import utils from '@/utils/utils'
import {
ElMessage,
ElLoading,
ElMessageBox
} from 'element-plus'
import type { Action } from 'element-plus'
import { nextTick, ref } from 'vue'
export const onProcessing = (form: any, steps_active: any, process_loading: any, result_loading: any, dialogVisible: any) => {
const fileToClassify = async () => {
if (!form.file_path || form.file_path.length == 0) {
ElMessage({
message: '请先上传文件',
type: 'warning'
})
return
}
// 进入第二页
from_first_to_second();
// 清除结果
form.sample_result = {
root_path: '',
data: [{
dir_name: '',
images: [],
}]
};
// 提交处理请求
try {
let param: any = {
task_id: form.task_id,
file_path: form.file_path,
crop_range: JSON.stringify(form.crop_range),
box_range: JSON.stringify(form.box_range),
output_type: "dir",
classes_select: "",
target_fps: form.target_fps, // 目标帧率
every_x_gen_dir: form.every_x_gen_dir, // 每多少图片生成一个目录
slider_window: form.slider_window_2 ? 2 : 0, // 是否开启滑窗2张图
}
process_loading.value = true;
// 发起请求
aitoolsService.commonApi('提交处理', 'gen_sample_from_video', param)
.then((response) => {
// console.log(form)
console.log(`接口返回:${response}`);
form.sample_result = response;
// 设置默认值
form.classes_select = {}
form.classes_select2 = {}
response.data.forEach((item: { dir_name: string; images: string[] }) => {
form.classes_select[item.dir_name] = '0';
form.classes_select2[item.dir_name] = '';
});
process_loading.value = false;
})
.catch((error) => {
ElMessage({
message: error,
type: 'error'
});
// // 重置task_id
// form.task_id = utils.genDateTimeStr();
// console.log('重置 task_id =', form.task_id);
process_loading.value = false;
})
} catch (error: any) {
ElMessage({
message: error,
type: 'error'
});
process_loading.value = false;
}
}
function isAllDataClassified(): boolean {
let is_ok = false;
if (!form.sample_result?.data || !form.classes_select) {
ElMessage({
message: '数据未正确初始化',
type: 'error'
});
}
form.sample_result.data.every((item: { dir_name: string; images: string[] }) => {
if (!form.classes_select.hasOwnProperty(item.dir_name)) {
ElMessage({
message: `请为${item.dir_name}分类`,
type: 'error'
});
} else {
is_ok = true
}
});
return is_ok;
}
const gen_sample = async (param: any) => {
result_loading.value = true;
// 提交处理请求
try {
const response = await aitoolsService.commonApi('提交处理', 'gen_sample_from_video', param)
console.log(`接口返回:${response}`);
return response.sample_path;
} catch (error: any) {
ElMessage({
message: error,
type: 'error'
});
} finally {
result_loading.value = false;
}
}
const classifyToDownload = async () => {
if (!isAllDataClassified()) {
return
}
// 进入第3页
from_second_to_third();
// 清除结果
form.sample_path = '';
// 生成样本包
let param: any = {
task_id: form.task_id,
file_path: form.file_path,
crop_range: JSON.stringify(form.crop_range),
box_range: JSON.stringify(form.box_range),
output_type: "zip",
classes_select: JSON.stringify(form.classes_select),
target_fps: form.target_fps, // 目标帧率
}
form.sample_path = await gen_sample(param);
}
const from_first_to_second = () => {
steps_active.value = 1
}
const back_to_first = () => {
steps_active.value = 0;
form.task_id = utils.genDateTimeStr();
console.log('返回首页,task_id =', form.task_id);
}
const from_second_to_third = () => {
steps_active.value = 2;
}
const back_to_second = () => {
steps_active.value = 1;
form.classes_select2 = JSON.parse(JSON.stringify(form.classes_select));
}
function downloadFile(url: string, filename?: string) {
const a = document.createElement('a')
a.href = url
if (filename) {
a.download = filename // 指定下载文件名
}
a.target = '_blank'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
}
function deleteOneSample(sample_dir: string) {
ElMessageBox.alert(`确认删除 ${sample_dir} 这行样本吗?`, '删除样本', {
// autofocus: false,
confirmButtonText: '删除',
callback: (action: Action) => {
if (action === 'confirm') {
// 删除 form.sample_result.data
const index = form.sample_result.data.findIndex((item: any) => item.dir_name === sample_dir);
form.sample_result.data.splice(index, 1);
// 删除 form.classes_select[sample_dir]
delete form.classes_select[sample_dir];
// 删除 form.classes_select2[sample_dir]
delete form.classes_select2[sample_dir];
ElMessage({
type: 'success',
message: '删除成功'
})
}
},
})
}
const checkSampleVideo = async (dir_name: string) => {
// 提交处理请求
try {
let param: any = {
task_id: form.task_id,
file_path: form.file_path,
dir_name: dir_name,
}
process_loading.value = true;
// 发起请求
const response = await aitoolsService.commonApi('提交处理', 'gen_clip_from_dirname', param);
console.log(`接口返回:${response}`);
process_loading.value = false;
dialogVisible.value = true;
// 使用 nextTick 等待 DOM 更新完成
await nextTick();
const clipVideo = document.getElementById('clip-video') as HTMLVideoElement;
console.log(clipVideo);
if (clipVideo && typeof response === 'string' && response.startsWith('http')) {
clipVideo.src = response;
} else {
ElMessage({
message: response as string,
type: 'error'
});
}
} catch (error: any) {
ElMessage({
message: error as string,
type: 'error'
});
process_loading.value = false;
}
}
const onCheckedListChange = (newValue: any) => {
// console.log('CheckedList changed:', newValue);
console.log('CheckedList changed:', form.checkedList);
}
function deleteMutiSample() {
ElMessageBox.alert(`确认删除所选的这些样本吗?共${form.checkedList.length}行`, '批量删除样本', {
// autofocus: false,
confirmButtonText: '删除',
callback: (action: Action) => {
if (action === 'confirm') {
form.checkedList.forEach((sample_dir: string) => {
// 删除 form.sample_result.data
const index = form.sample_result.data.findIndex((item: any) => item.dir_name === sample_dir);
form.sample_result.data.splice(index, 1);
// 删除 form.classes_select[sample_dir]
delete form.classes_select[sample_dir];
// 删除 form.classes_select2[sample_dir]
delete form.classes_select2[sample_dir];
})
// 清空已选择列表
form.checkedList = [];
ElMessage({
type: 'success',
message: '删除成功'
})
}
},
})
}
return {
from_first_to_second,
back_to_first,
from_second_to_third,
back_to_second,
fileToClassify,
classifyToDownload,
downloadFile,
process_loading,
result_loading,
deleteOneSample,
checkSampleVideo,
onCheckedListChange,
deleteMutiSample,
gen_sample,
}
}
\ No newline at end of file
import { ref } from 'vue'
import {
ElMessage,
} from 'element-plus'
export function useCropBox(form: any, canvas: any, ctx: any) {
const isDrawing = ref(false)
const startX = ref(0)
const startY = ref(0)
const endX = ref(0)
const endY = ref(0)
const boxes = ref<Array<{ start: number[], end: number[] }>>([])
function generate_canvas() {
const el = document.getElementById('crop-canvas') as HTMLCanvasElement
if (el) {
canvas.value = el
ctx.value = canvas.value.getContext('2d')
// 设置 canvas 尺寸与视频一致
const video = document.getElementById('video-player') as HTMLVideoElement
if (video) {
if (video.videoWidth > 0 && video.videoHeight > 0) {
console.log('video size =', video.videoWidth, video.videoHeight)
canvas.value.width = video.videoWidth
canvas.value.height = video.videoHeight
canvas.value.style.zIndex = '2'
drawBoxes()
}
}
}
}
function startDrawing(e: MouseEvent) {
if (boxes.value.length >= 1) {
while (boxes.value.length > 1) {
boxes.value.pop()
}
return
}
const rect = canvas.value!.getBoundingClientRect()
isDrawing.value = true
startX.value = e.clientX - rect.left
startY.value = e.clientY - rect.top
endX.value = startX.value
endY.value = startY.value
console.log('开始绘制,起点:', startX.value, startY.value)
}
function draw(e: MouseEvent) {
if (!isDrawing.value) return
const rect = canvas.value!.getBoundingClientRect()
endX.value = e.clientX - rect.left
endY.value = e.clientY - rect.top
clearCanvas()
drawBoxes()
drawRect(startX.value, startY.value, endX.value - startX.value, endY.value - startY.value)
}
function endDrawing() {
if (!isDrawing.value) return
isDrawing.value = false
boxes.value.push({
start: [startX.value, startY.value],
end: [endX.value, endY.value]
})
clearCanvas()
drawBoxes()
console.log('结束绘制,终点:', endX.value, endY.value)
console.log('框', boxes.value.length, '个: ', boxes.value)
// if (boxes.value.length === 1) {
// form.crop_range = boxes.value[0]
// }
// if (boxes.value.length === 2) {
// if (boxes.value[1].start[0] < boxes.value[0].start[0]
// || boxes.value[1].start[1] < boxes.value[0].start[1]
// || boxes.value[1].end[0] > boxes.value[0].end[0]
// || boxes.value[1].end[1] > boxes.value[0].end[1]) {
// ElMessage({
// message: '扫描框必须在裁剪框内',
// type: 'warning'
// })
// reset()
// } else {
// form.box_range = boxes.value[1]
// form.is_set = true
// }
// }
if (boxes.value.length === 1) {
form.box_range = boxes.value[0]
form.is_set = true
}
}
function drawRect(x: number, y: number, width: number, height: number) {
const c = ctx.value!
c.strokeStyle = 'green'
c.lineWidth = 2
c.strokeRect(x, y, width, height)
}
function drawBoxes() {
boxes.value.forEach(box => {
drawRect(box.start[0], box.start[1], box.end[0] - box.start[0], box.end[1] - box.start[1])
})
}
function clearCanvas() {
const c = canvas.value!
ctx.value!.clearRect(0, 0, c.width, c.height)
}
function reset() {
if (boxes.value.length === 0) {
return
}
while (boxes.value.length > 0) {
boxes.value.pop()
}
clearCanvas()
form.crop_range = {}
form.box_range = {}
form.is_set = false
console.log('重置绘制数据')
}
return {
startDrawing,
draw,
endDrawing,
clearCanvas,
reset,
generate_canvas
}
}
\ No newline at end of file
import { ref } from 'vue'
import {
ElMessage,
} from 'element-plus'
export function useCropBox(form: any, canvas: any, ctx: any) {
const isDrawing = ref(false)
const startX = ref(0)
const startY = ref(0)
const endX = ref(0)
const endY = ref(0)
const boxes = ref<Array<{ start: number[], end: number[] }>>([])
const draggingBoxIndex = ref<number | null>(null) // 正在拖动的框索引
const resizingBoxIndex = ref<number | null>(null) // 正在缩放的框索引
const resizeHandle = ref<'nw' | 'ne' | 'sw' | 'se' | null>(null) // 缩放角方向
const dragStart = ref({ x: 0, y: 0 }) // 拖动起始点
const boxStart = ref({ x: 0, y: 0, width: 0, height: 0 }) // 框原始位置
function isInsideBox(x: number, y: number, box: any): boolean {
return (
x >= box.start[0] &&
x <= box.end[0] &&
y >= box.start[1] &&
y <= box.end[1]
)
}
function getHandleAt(x: number, y: number, box: any): 'nw' | 'ne' | 'sw' | 'se' | null {
const size = 10
const [x1, y1] = box.start
const [x2, y2] = box.end
if (Math.abs(x - x1) < size && Math.abs(y - y1) < size) return 'nw'
if (Math.abs(x - x2) < size && Math.abs(y - y1) < size) return 'ne'
if (Math.abs(x - x1) < size && Math.abs(y - y2) < size) return 'sw'
if (Math.abs(x - x2) < size && Math.abs(y - y2) < size) return 'se'
return null
}
function onMouseDown(e: MouseEvent) {
const rect = canvas.value!.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
// 检查是否点击到了某个框的角
for (let i = 0; i < boxes.value.length; i++) {
const box = boxes.value[i]
const handle = getHandleAt(x, y, box)
if (handle) {
resizingBoxIndex.value = i
resizeHandle.value = handle
return
}
if (isInsideBox(x, y, box)) {
draggingBoxIndex.value = i
dragStart.value = { x, y }
const [bx1, by1] = box.start
const [bx2, by2] = box.end
boxStart.value = {
x: bx1,
y: by1,
width: bx2 - bx1,
height: by2 - by1
}
return
}
}
}
function onMouseMove(e: MouseEvent) {
const rect = canvas.value!.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
if (resizingBoxIndex.value !== null && resizeHandle.value) {
const i = resizingBoxIndex.value
const box = boxes.value[i]
const [bx1, by1] = box.start
const [bx2, by2] = box.end
switch (resizeHandle.value) {
case 'nw':
boxes.value[i] = { start: [x, y], end: [bx2, by2] }
break
case 'ne':
boxes.value[i] = { start: [bx1, y], end: [x, by2] }
break
case 'sw':
boxes.value[i] = { start: [x, by1], end: [bx2, y] }
break
case 'se':
boxes.value[i] = { start: [bx1, by1], end: [x, y] }
break
}
clearCanvas()
drawBoxes()
if (i === 0) {
form.crop_range = boxes.value[0]
} else {
form.box_range = boxes.value[1]
}
} else if (draggingBoxIndex.value !== null) {
const i = draggingBoxIndex.value
const dx = x - dragStart.value.x
const dy = y - dragStart.value.y
const box = boxes.value[i]
boxes.value[i] = {
start: [box.start[0] + dx, box.start[1] + dy],
end: [box.end[0] + dx, box.end[1] + dy]
}
clearCanvas()
drawBoxes()
if (i === 0) {
form.crop_range = boxes.value[0]
} else {
form.box_range = boxes.value[1]
}
}
}
function onMouseUp() {
draggingBoxIndex.value = null
resizingBoxIndex.value = null
resizeHandle.value = null
}
function generate_canvas() {
const el = document.getElementById('crop-canvas') as HTMLCanvasElement
if (el) {
canvas.value = el
ctx.value = canvas.value.getContext('2d')
// 设置 canvas 尺寸与视频一致
const video = document.getElementById('video-player') as HTMLVideoElement
if (video) {
if (video.videoWidth > 0 && video.videoHeight > 0) {
console.log('video size =', video.videoWidth, video.videoHeight)
canvas.value.width = video.videoWidth
canvas.value.height = video.videoHeight
canvas.value.style.zIndex = '2'
drawBoxes()
}
}
// 绑定事件监听器
canvas.value.addEventListener('mousedown', onMouseDown)
canvas.value.addEventListener('mousemove', onMouseMove)
canvas.value.addEventListener('mouseup', onMouseUp)
canvas.value.addEventListener('mouseleave', onMouseUp)
}
}
function startDrawing(e: MouseEvent) {
if (boxes.value.length >= 2) {
while (boxes.value.length > 2) {
boxes.value.pop()
}
return
}
const rect = canvas.value!.getBoundingClientRect()
isDrawing.value = true
startX.value = e.clientX - rect.left
startY.value = e.clientY - rect.top
endX.value = startX.value
endY.value = startY.value
console.log('开始绘制,起点:', startX.value, startY.value)
}
function draw(e: MouseEvent) {
if (!isDrawing.value) return
const rect = canvas.value!.getBoundingClientRect()
endX.value = e.clientX - rect.left
endY.value = e.clientY - rect.top
clearCanvas()
drawBoxes()
drawRect(startX.value, startY.value, endX.value - startX.value, endY.value - startY.value)
}
function endDrawing() {
if (!isDrawing.value) return
isDrawing.value = false
boxes.value.push({
start: [startX.value, startY.value],
end: [endX.value, endY.value]
})
clearCanvas()
drawBoxes()
console.log('结束绘制,终点:', endX.value, endY.value)
console.log('框', boxes.value.length, '个: ', boxes.value)
if (boxes.value.length === 1) {
form.crop_range = boxes.value[0]
}
if (boxes.value.length === 2) {
if (boxes.value[1].start[0] < boxes.value[0].start[0]
|| boxes.value[1].start[1] < boxes.value[0].start[1]
|| boxes.value[1].end[0] > boxes.value[0].end[0]
|| boxes.value[1].end[1] > boxes.value[0].end[1]) {
ElMessage({
message: '扫描框必须在裁剪框内',
type: 'warning'
})
reset()
} else {
form.box_range = boxes.value[1]
form.is_set = true
}
}
}
function drawRect(x: number, y: number, width: number, height: number) {
const c = ctx.value!
c.strokeStyle = 'green'
c.lineWidth = 2
c.strokeRect(x, y, width, height)
}
function drawBoxes() {
boxes.value.forEach(box => {
drawRect(box.start[0], box.start[1], box.end[0] - box.start[0], box.end[1] - box.start[1])
})
}
function clearCanvas() {
const c = canvas.value!
ctx.value!.clearRect(0, 0, c.width, c.height)
}
function reset() {
if (boxes.value.length === 0) {
return
}
while (boxes.value.length > 0) {
boxes.value.pop()
}
clearCanvas()
form.crop_range = {}
form.box_range = {}
form.is_set = false
console.log('重置绘制数据')
// 移除事件监听器
const el = document.getElementById('crop-canvas') as HTMLCanvasElement
if (el) {
el.removeEventListener('mousedown', onMouseDown)
el.removeEventListener('mousemove', onMouseMove)
el.removeEventListener('mouseup', onMouseUp)
el.removeEventListener('mouseleave', onMouseUp)
}
}
return {
startDrawing,
draw,
endDrawing,
clearCanvas,
reset,
generate_canvas
}
}
\ No newline at end of file
.home-container {
width: 100%;
}
.title {
:is(span) {
font-size: 25px;
font-weight: bold;
color: #181818;
}
text-align: center;
margin: 20px 0 0 0;
}
.subtitle {
:is(span) {
font-size: 13px;
color: #181818;
}
text-align: center;
margin: 0 0 20px 0;
}
.progress {
margin: 10px 50px;
text-align: center;
}
.content-container {
margin: 20px;
display: flex;
flex-direction: row; /* 默认横向排列 */
justify-content: center; /* 水平居中 */
/* box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
border-radius: 8px; /* 可选:添加圆角 */
/* background-color: #fff; /* 可选:添加背景色 */
@media (max-width: 768px) { /* 当屏幕宽度小于768px时变为竖向排列 */
flex-direction: column;
}
}
/* 几个页面的公共样式 */
.upload-section,
.progress-section,
.report-section {
padding: 20px;
/* 标题 */
.section-title {
font-size: 18px;
font-weight: bold;
color: #181818;
}
/* 描述 */
.section-desc {
font-size: 12px;
}
}
/* 上传页 */
.upload-section {
/* 上传区域 */
.upload-div {
margin: 20px 0;
/* border: 2px dashed #dddfe5; */
border-radius: 4px;
}
/* 已上传文件 */
.uploaded-div {
margin: 20px 0;
padding: 20px 10px;
border-radius: 4px;
border: 1px dashed #dddfe5; /* 添加边框样式 */
.uploaded-file-info {
position: relative;
width: auto;
}
}
/* 按钮 */
.button {
display: flex;
flex-direction: column;
align-items: flex-start;
.next {
align-self: flex-end;
}
}
}
/* 处理页 */
.progress-section {
.row {
display: flex;
margin: 10px 10px 0 0;
flex-wrap: wrap; /* 如果图片过多,允许换行 */
.images {
/* width: auto; */
/* display: inline-block; */
margin-right: 10px; /* 可选:为每个图片之间添加间距 */
}
}
/* 按钮 */
.button {
background-color: #f56c6c;;
margin-left: 22px;
padding: 10px;
display: flex;
flex-direction: row; /* 修改为行排列 */
justify-content: space-between; /* 使按钮两端对齐 */
align-items: center; /* 垂直居中对齐 */
position: fixed;
bottom: 20px;
gap: 30px;
}
}
/* 结果页*/
.report-section {
/* 按钮 */
.button {
margin: 20px 0;
display: flex;
flex-direction: row; /* 修改为行排列 */
justify-content: space-between; /* 使按钮两端对齐 */
align-items: center; /* 垂直居中对齐 */
}
}
.folder-viewer {
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
}
.toolbar {
display: flex;
gap: 10px;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
\ No newline at end of file
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment