Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
A
aitools
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
周成波
aitools
Commits
afd50a86
Commit
afd50a86
authored
Nov 13, 2025
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
样本工具改造v3
parent
6fd406f8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
1753 additions
and
1 deletion
+1753
-1
components.d.ts
components.d.ts
+1
-0
index.ts
src/router/index.ts
+7
-1
common.ts
src/views/sample_handle_v3/compositions/common.ts
+45
-0
customerInfo.ts
src/views/sample_handle_v3/compositions/customerInfo.ts
+101
-0
fileUpload.ts
src/views/sample_handle_v3/compositions/fileUpload.ts
+106
-0
process.ts
src/views/sample_handle_v3/compositions/process.ts
+282
-0
useCropBox.ts
src/views/sample_handle_v3/compositions/useCropBox.ts
+134
-0
useCropBoxV2.ts
src/views/sample_handle_v3/compositions/useCropBoxV2.ts
+262
-0
index.css
src/views/sample_handle_v3/index.css
+149
-0
index.vue
src/views/sample_handle_v3/index.vue
+666
-0
No files found.
components.d.ts
View file @
afd50a86
...
...
@@ -23,6 +23,7 @@ declare module 'vue' {
ElSteps
:
typeof
import
(
'element-plus/es'
)[
'ElSteps'
]
ElSwitch
:
typeof
import
(
'element-plus/es'
)[
'ElSwitch'
]
ElText
:
typeof
import
(
'element-plus/es'
)[
'ElText'
]
ElTree
:
typeof
import
(
'element-plus/es'
)[
'ElTree'
]
ElUpload
:
typeof
import
(
'element-plus/es'
)[
'ElUpload'
]
RouterLink
:
typeof
import
(
'vue-router'
)[
'RouterLink'
]
RouterView
:
typeof
import
(
'vue-router'
)[
'RouterView'
]
...
...
src/router/index.ts
View file @
afd50a86
...
...
@@ -3,6 +3,7 @@ import HomeView from '../views/home/index.vue'
import
SampleHandleView
from
'../views/sample_handle/index.vue'
import
WmNanoBananaView
from
'../views/wm_nano_banana/index.vue'
import
SampleHandleViewV2
from
'../views/sample_handle_v2/index.vue'
import
SampleHandleViewV3
from
'../views/sample_handle_v3/index.vue'
const
router
=
createRouter
({
history
:
createWebHashHistory
(
import
.
meta
.
env
.
BASE_URL
),
...
...
@@ -10,7 +11,7 @@ const router = createRouter({
{
path
:
'/'
,
name
:
'home'
,
component
:
SampleHandleViewV
2
component
:
SampleHandleViewV
3
},
{
path
:
'/report'
,
...
...
@@ -32,6 +33,11 @@ const router = createRouter({
name
:
'sample_handle_v2'
,
component
:
SampleHandleViewV2
},
{
path
:
'/sample_handle_v3'
,
name
:
'sample_handle_v3'
,
component
:
SampleHandleViewV3
},
]
})
...
...
src/views/sample_handle_v3/compositions/common.ts
0 → 100644
View file @
afd50a86
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
)
})
}
src/views/sample_handle_v3/compositions/customerInfo.ts
0 → 100644
View file @
afd50a86
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
,
}
}
src/views/sample_handle_v3/compositions/fileUpload.ts
0 → 100644
View file @
afd50a86
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
,
}
}
src/views/sample_handle_v3/compositions/process.ts
0 → 100644
View file @
afd50a86
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
src/views/sample_handle_v3/compositions/useCropBox.ts
0 → 100644
View file @
afd50a86
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
src/views/sample_handle_v3/compositions/useCropBoxV2.ts
0 → 100644
View file @
afd50a86
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
src/views/sample_handle_v3/index.css
0 → 100644
View file @
afd50a86
.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
src/views/sample_handle_v3/index.vue
0 → 100644
View file @
afd50a86
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment