新模块功能:蚕茧视频识别
This commit is contained in:
+4
-5
@@ -15,7 +15,6 @@ from routers.RabbitMQ import rqRouter
|
||||
from routers.Report import reportRouter
|
||||
from routers.Service import serviceRouter
|
||||
from routers.Vision import visionRouter
|
||||
from service.Analyze import mq_pull_analysis_async
|
||||
|
||||
|
||||
async def ai_lab():
|
||||
@@ -47,7 +46,7 @@ async def ai_lab():
|
||||
app.include_router(r, prefix="/llm", tags=["llm"])
|
||||
app.include_router(visionRouter, prefix="/cv", tags=["cv"])
|
||||
app.include_router(publicRouter, prefix="/api/public", tags=["api"])
|
||||
config = Config(app=app, host="0.0.0.0", port=13011, log_level="info")
|
||||
config = Config(app=app, host="0.0.0.0", port=13011, log_level="debug")
|
||||
server = Server(config)
|
||||
await server.serve()
|
||||
|
||||
@@ -59,7 +58,7 @@ async def main():
|
||||
task_api = asyncio.create_task(ai_lab())
|
||||
|
||||
# MCP服务-ailab
|
||||
endpoint_url_ai_lab = "wss://ai.ronsunny.cn:8090/aimcp/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D"
|
||||
# endpoint_url_ai_lab = "wss://ai.ronsunny.cn:8090/aimcp/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D"
|
||||
# endpoint_url_ai_lab = "ws://ce_bot_mcp:8004/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D"
|
||||
# task_mcp1 = asyncio.create_task(init_mcp_server(endpoint_url_ai_lab))
|
||||
|
||||
@@ -68,10 +67,10 @@ async def main():
|
||||
task_mcp2 = asyncio.create_task(init_mcp_server(endpoint_url_ql))
|
||||
|
||||
# RabbitMQ服务
|
||||
task_mq = asyncio.create_task(mq_pull_analysis_async())
|
||||
# task_mq = asyncio.create_task(mq_pull_analysis_async())
|
||||
|
||||
# await asyncio.gather(task_api, task_mcp1, task_mcp2, task_mq)
|
||||
await asyncio.gather(task_api, task_mcp2, task_mq)
|
||||
await asyncio.gather(task_api, task_mcp2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -22,11 +22,10 @@ def push_file(bucket_name, object_name, file_bytes, contents, content_type):
|
||||
)
|
||||
|
||||
|
||||
def get_upload_token(bucket_name, object_name, xpires=timedelta(hours=1)):
|
||||
upload_url = minio_client.presigned_put_object(
|
||||
def get_upload_token(user_id, bucket_name, object_name, xpires=timedelta(minutes=15)):
|
||||
return minio_client.presigned_put_object(
|
||||
bucket_name=bucket_name, object_name=object_name, expires=xpires
|
||||
)
|
||||
return {"upload_url": upload_url, "object_name": object_name}
|
||||
|
||||
|
||||
def get_temp_url(bucket_name, object_name):
|
||||
|
||||
@@ -753,3 +753,97 @@ def get_sca_image_list(user_id, name, page=1, page_size=10):
|
||||
)
|
||||
|
||||
return total, result
|
||||
|
||||
|
||||
def get_sca_video_list(name, page=1, page_size=10):
|
||||
"""
|
||||
获取用户已分析视频列表,带分页
|
||||
"""
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
with pg_pool.getConn() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
# 1️⃣ 查询总条数
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT COUNT(*)
|
||||
FROM sca_videos
|
||||
WHERE (%s = '' OR name LIKE '%%' || %s || '%%')
|
||||
""",
|
||||
(name, name),
|
||||
)
|
||||
total = cursor.fetchone()[0]
|
||||
|
||||
# 2️⃣ 查询当前页数据
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT id, name, raw_object_name, ai_object_name, duration, size, video_codec, audio_codec,
|
||||
overall_bit_rate, resolution, sc_analysis_time, sc_analysis_total_count, sc_analysis_max_count,
|
||||
sc_analysis_primary_type, sc_analysis_secondary_type, other_info, created_at
|
||||
FROM sca_videos
|
||||
WHERE (%s = '' OR name LIKE '%%' || %s || '%%')
|
||||
ORDER BY created_at DESC
|
||||
LIMIT %s OFFSET %s
|
||||
""",
|
||||
(name, name, page_size, offset),
|
||||
)
|
||||
|
||||
rows = cursor.fetchall()
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(
|
||||
{
|
||||
"id": row[0],
|
||||
"name": row[1],
|
||||
"raw_video_url": get_temp_url("video-sca", "raw/" + row[2]),
|
||||
"ai_video_url": get_temp_url("video-sca", "ai/" + row[3]),
|
||||
"duration": MyUtils.safe_round(row[4], 2),
|
||||
"size_kb": MyUtils.safe_round(row[5] / 1024, 2),
|
||||
"video_codec": row[6],
|
||||
"audio_codec": row[7],
|
||||
"overall_bit_rate": row[8],
|
||||
"resolution": row[9],
|
||||
"sc_analysis_time": MyUtils.safe_round(row[10], 2),
|
||||
"sc_analysis_total_count": row[11],
|
||||
"sc_analysis_max_count": row[12],
|
||||
"sc_analysis_primary_type": row[13],
|
||||
"sc_analysis_secondary_type": row[14],
|
||||
"other_info": json.loads(row[15]),
|
||||
"created_at": MyUtils.format_datetime(row[16]),
|
||||
}
|
||||
)
|
||||
|
||||
return total, result
|
||||
|
||||
|
||||
def get_sca_video_details(v_id):
|
||||
"""
|
||||
获取指定视频的分析明细列表
|
||||
"""
|
||||
with pg_pool.getConn() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT id, v_id, time_stamp, other_info
|
||||
FROM sca_video_details
|
||||
WHERE v_id = %s
|
||||
ORDER BY time_stamp ASC
|
||||
""",
|
||||
(v_id,),
|
||||
)
|
||||
|
||||
rows = cursor.fetchall()
|
||||
result = []
|
||||
for row in rows:
|
||||
# other_info 从 JSON 字符串解析回字典
|
||||
result.append(
|
||||
{
|
||||
"id": row[0],
|
||||
"v_id": row[1],
|
||||
"time_stamp": row[2],
|
||||
"other_info": row[3],
|
||||
}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
@@ -153,8 +153,41 @@ def getSilkwormCocoonAnalysisTasks(
|
||||
)
|
||||
|
||||
|
||||
@visionRouter.post("/getIVASCUploadToken")
|
||||
def getIVASCUploadToken():
|
||||
# ————————————————————————————————蚕茧视频识别任务————————————————————————————————————————————————
|
||||
|
||||
|
||||
@visionRouter.get("/getIVASCUploadToken")
|
||||
def getIVASCUploadToken(
|
||||
user_id: UUID = Depends(get_user_id_from_token),
|
||||
):
|
||||
# 生成唯一文件名,避免覆盖
|
||||
object_name = f"raw/{uuid.uuid4()}"
|
||||
return get_upload_token("video-sca", object_name)
|
||||
return BaseResponse(data=get_upload_token(user_id, "video-sca", object_name))
|
||||
|
||||
|
||||
@visionRouter.get("/getScVideoList")
|
||||
def getScVideoList(
|
||||
user_id: UUID = Depends(get_user_id_from_token),
|
||||
name: str = "",
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(10, ge=1, le=100),
|
||||
):
|
||||
if not user_id:
|
||||
return {"error": "userId is required"}
|
||||
total, items = pg.get_sca_video_list(name, page=page, page_size=page_size)
|
||||
return BaseResponse(
|
||||
data={
|
||||
"total": total,
|
||||
"items": items,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@visionRouter.get("/getAnalyticsDetailBySCVideoId")
|
||||
def getAnalyticsDetailBySCVideoId(
|
||||
user_id: UUID = Depends(get_user_id_from_token),
|
||||
vId: str = "",
|
||||
):
|
||||
if not user_id:
|
||||
return {"error": "userId is required"}
|
||||
return BaseResponse(data=pg.get_sca_video_details(vId))
|
||||
|
||||
@@ -132,18 +132,6 @@ def process_silkworm_cocoon_image(
|
||||
# YOLO检测
|
||||
img_bytes_out, results_json = YOLOSingleton.detect(img_bytes)
|
||||
|
||||
# results_json = {
|
||||
# "total_objects": "",
|
||||
# "max_confidence": "",
|
||||
# "min_confidence": "",
|
||||
# "avg_confidence": "",
|
||||
# "class_counts": "",
|
||||
# "speed_ms": {
|
||||
# "preprocess": "",
|
||||
# "inference": "",
|
||||
# "postprocess": "",
|
||||
# },
|
||||
# }
|
||||
speed_json = results_json.get("speed_ms")
|
||||
|
||||
file_bytes_out = BytesIO(img_bytes_out)
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"ant-design-vue": "catalog:",
|
||||
"axios": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"handsontable": "^16.0.1",
|
||||
"js-sha256": "^0.11.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './iva';
|
||||
export * from './iva-sc';
|
||||
export * from './license';
|
||||
export * from './sca';
|
||||
export * from './sca2';
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { pyRequestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 获取已分析的视频列表
|
||||
*/
|
||||
export async function refreshSCVideoList(name = '', page = 1, pageSize = 9) {
|
||||
return pyRequestClient.get('/cv/getScVideoList', {
|
||||
params: { name, page, page_size: pageSize },
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取已分析的视频
|
||||
*/
|
||||
export async function refreshSCVideoDetail(vId = '') {
|
||||
return pyRequestClient.get('/cv/getAnalyticsDetailBySCVideoId', {
|
||||
params: { vId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传视频分析任务
|
||||
*/
|
||||
export async function getIVASCUploadToken() {
|
||||
return pyRequestClient.get('/cv/getIVASCUploadToken');
|
||||
}
|
||||
@@ -22,11 +22,12 @@ import {
|
||||
} from '@vben/icons';
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
|
||||
import { Button, Form, Input, message } from 'ant-design-vue';
|
||||
import { Button, Form, message } from 'ant-design-vue';
|
||||
import axios from 'axios';
|
||||
import videojs from 'video.js';
|
||||
|
||||
import * as api from '#/api';
|
||||
import { createImageTaskV2 } from '#/api';
|
||||
import { getIVASCUploadToken } from '#/api';
|
||||
|
||||
import 'video.js/dist/video-js.css';
|
||||
|
||||
@@ -37,11 +38,15 @@ const activeTab = ref<'detail' | 'video'>('detail');
|
||||
const selectedItem = ref<any>(null);
|
||||
const detailList = ref<any[]>([]);
|
||||
const videoEl = ref<HTMLVideoElement | null>(null);
|
||||
const originalVideoEl = ref<HTMLVideoElement | null>(null);
|
||||
const originalPlayer = ref<null | Player>(null);
|
||||
|
||||
const player = ref<null | Player>(null);
|
||||
async function loadList() {
|
||||
error.value = null;
|
||||
const res = await api.refreshVideoList(filterKeyword.value);
|
||||
list.value = res || [];
|
||||
const res = await api.refreshSCVideoList(filterKeyword.value);
|
||||
list.value = res.items || [];
|
||||
total.value = res.total;
|
||||
}
|
||||
function refreshList() {
|
||||
filterKeyword.value = '';
|
||||
@@ -53,15 +58,18 @@ function createTask() {
|
||||
}
|
||||
|
||||
async function selectItem(item: any) {
|
||||
const res = await api.refreshVideoDetail(item.v_id);
|
||||
selectedItem.value = res;
|
||||
const res = await api.refreshSCVideoDetail(item.id);
|
||||
detailList.value = res;
|
||||
selectedItem.value = item;
|
||||
refreshLineChart();
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ key: 'detail', label: '分析详情' },
|
||||
{ key: 'original', label: '原视频' }, // 新增
|
||||
{ key: 'video', label: '分析视频' },
|
||||
];
|
||||
|
||||
let overviewItems: AnalysisOverviewItem[];
|
||||
|
||||
// 监听关键词变化,调用防抖接口
|
||||
@@ -72,45 +80,50 @@ watch(selectedItem, () => {
|
||||
overviewItems = [
|
||||
{
|
||||
icon: SvgDownloadIcon,
|
||||
title: '出现的蚕茧数量',
|
||||
title: '出现的蚕茧总数',
|
||||
totalTitle: '',
|
||||
totalValue: 0,
|
||||
value: selectedItem.value.v_a_count_people,
|
||||
value: selectedItem.value.sc_analysis_total_count,
|
||||
},
|
||||
{
|
||||
icon: SvgCardIcon,
|
||||
title: '出现最多的种类',
|
||||
totalTitle: '',
|
||||
totalValue: 0,
|
||||
value: selectedItem.value.v_a_max_action,
|
||||
value: selectedItem.value.sc_analysis_primary_type,
|
||||
},
|
||||
{
|
||||
icon: SvgCakeIcon,
|
||||
title: '最多同框蚕茧数量',
|
||||
title: '出现次多的种类',
|
||||
totalTitle: '',
|
||||
totalValue: 0,
|
||||
value: selectedItem.value.v_a_total_people,
|
||||
value: selectedItem.value.sc_analysis_secondary_type,
|
||||
},
|
||||
{
|
||||
icon: SvgBellIcon,
|
||||
title: '时间',
|
||||
totalTitle: '最长停留时间',
|
||||
title: '最多同框蚕茧数量',
|
||||
totalTitle: '',
|
||||
totalValue: 0,
|
||||
value: selectedItem.value.v_a_max_stay_time,
|
||||
value: selectedItem.value.sc_analysis_max_count,
|
||||
},
|
||||
];
|
||||
});
|
||||
watch([activeTab, selectedItem], async ([tab]) => {
|
||||
if (tab === 'video' && selectedItem.value?.v_video_play_path) {
|
||||
if (tab === 'video' && selectedItem.value?.ai_video_url) {
|
||||
refreshVideoPlayer();
|
||||
} else if (tab === 'original' && selectedItem.value?.raw_video_url) {
|
||||
refreshOriginalPlayer();
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadList();
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
player.value?.dispose();
|
||||
originalPlayer.value?.dispose();
|
||||
player.value = null;
|
||||
originalPlayer.value = null;
|
||||
});
|
||||
|
||||
// ✅ 切换视频项时销毁并重建
|
||||
@@ -119,7 +132,7 @@ function refreshVideoPlayer() {
|
||||
if (player.value) {
|
||||
player.value.src([
|
||||
{
|
||||
src: selectedItem.value.v_video_play_path,
|
||||
src: selectedItem.value.ai_video_url,
|
||||
type: 'video/mp4',
|
||||
},
|
||||
]);
|
||||
@@ -131,7 +144,7 @@ function refreshVideoPlayer() {
|
||||
preload: 'auto',
|
||||
sources: [
|
||||
{
|
||||
src: selectedItem.value.v_video_play_path,
|
||||
src: selectedItem.value.ai_video_url,
|
||||
type: 'video/mp4',
|
||||
},
|
||||
],
|
||||
@@ -139,6 +152,32 @@ function refreshVideoPlayer() {
|
||||
}
|
||||
});
|
||||
}
|
||||
function refreshOriginalPlayer() {
|
||||
nextTick(() => {
|
||||
if (originalPlayer.value) {
|
||||
originalPlayer.value.src([
|
||||
{
|
||||
src: selectedItem.value.raw_video_url,
|
||||
type: 'video/mp4',
|
||||
},
|
||||
]);
|
||||
} else {
|
||||
if (!originalVideoEl.value) return;
|
||||
originalPlayer.value = videojs(originalVideoEl.value, {
|
||||
controls: true,
|
||||
autoplay: false,
|
||||
preload: 'auto',
|
||||
sources: [
|
||||
{
|
||||
src: selectedItem.value.raw_video_url,
|
||||
type: 'video/mp4',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const showInfoStr = ref<Record<string, number | string>>({});
|
||||
|
||||
const chartRef1 = ref<EchartsUIType>();
|
||||
@@ -150,73 +189,63 @@ const { renderEcharts: renderEcharts2 } = useEcharts(chartRef2);
|
||||
function refreshLineChart() {
|
||||
const data = selectedItem.value;
|
||||
showInfoStr.value = {
|
||||
项目名: data.v_name,
|
||||
文件名: data.v_file_name,
|
||||
文件大小: `${data.v_size} MB`,
|
||||
总时长: `${data.v_duration} 秒`,
|
||||
分辨率: data.v_resolution,
|
||||
视频编码格式: data.v_video_codec,
|
||||
音频编码格式: data.v_audio_codec,
|
||||
总体比特率: data.v_overall_bit_rate,
|
||||
项目名: data.name,
|
||||
文件大小: `${data.size_kb} KB`,
|
||||
总时长: `${data.duration} 秒`,
|
||||
分辨率: data.resolution,
|
||||
视频编码格式: data.video_codec,
|
||||
音频编码格式: data.audio_codec,
|
||||
总体比特率: data.overall_bit_rate,
|
||||
};
|
||||
|
||||
detailList.value = data.v_details_list || [];
|
||||
const temp = detailList.value;
|
||||
// 1. X轴
|
||||
const xAxisData = temp.map((item) => item.time_stamp);
|
||||
|
||||
const detail = selectedItem.value.v_a_details;
|
||||
let yTotalData = Array.isArray(detail.yTotalData)
|
||||
? detail.yTotalData.map((item: any) => [item.first, item.second])
|
||||
: []; // 默认值为空数组
|
||||
let yMaskedData = Array.isArray(detail.yMaskedData)
|
||||
? detail.yMaskedData.map((item: any) => [item.first, item.second])
|
||||
: []; // 默认值为空数组
|
||||
const areaData = Array.isArray(detail.areaData)
|
||||
? detail.areaData.map((actionGroup: any) => {
|
||||
return actionGroup.map((action: any) => {
|
||||
return { xAxis: action.xAxis, itemStyle: action.itemStyle };
|
||||
});
|
||||
})
|
||||
: []; // 默认值为空数组
|
||||
yTotalData = yTotalData.map((item: any) => [
|
||||
new Date(item[0]).getTime(),
|
||||
item[1],
|
||||
]);
|
||||
yMaskedData = yMaskedData.map((item: any) => [
|
||||
new Date(item[0]).getTime(),
|
||||
item[1],
|
||||
]);
|
||||
// 2. 获取类别(other_info 的 key)
|
||||
const categories = Object.keys(temp[0]?.other_info || {});
|
||||
|
||||
renderEcharts1({
|
||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||
series: [
|
||||
{
|
||||
name: '总人数',
|
||||
// 3. 构造 series
|
||||
const series = categories.map((key) => ({
|
||||
name: key,
|
||||
type: 'line',
|
||||
step: 'end',
|
||||
data: yTotalData,
|
||||
markArea: {
|
||||
itemStyle: { color: 'rgba(255, 173, 177, 0.4)' },
|
||||
data: areaData,
|
||||
},
|
||||
},
|
||||
{ name: '口罩佩戴人数', type: 'line', step: 'end', data: yMaskedData },
|
||||
],
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
color: '#019680',
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
trigger: 'axis',
|
||||
},
|
||||
xAxis: { type: 'time' },
|
||||
yAxis: { type: 'value' },
|
||||
});
|
||||
stack: 'Total',
|
||||
data: temp.map((item) => item.other_info[key]),
|
||||
}));
|
||||
|
||||
const option = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: { data: categories },
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: xAxisData,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series,
|
||||
};
|
||||
|
||||
renderEcharts1(option);
|
||||
|
||||
const chartData = data.other_info;
|
||||
|
||||
// 将对象转换为 echarts 的 [{value, name}] 数组
|
||||
const seriesData = Object.entries(chartData).map(([name, value]) => ({
|
||||
name,
|
||||
value,
|
||||
}));
|
||||
|
||||
const maskedRatio = data.v_a_average_masked_ratio * 100;
|
||||
const noMaskedRatio = 100 - maskedRatio;
|
||||
renderEcharts2({
|
||||
legend: { top: '5%', left: 'center' },
|
||||
|
||||
series: [
|
||||
{
|
||||
animationDelay() {
|
||||
@@ -225,17 +254,12 @@ function refreshLineChart() {
|
||||
animationEasing: 'exponentialInOut',
|
||||
animationType: 'scale',
|
||||
avoidLabelOverlap: false,
|
||||
|
||||
// 你原来的颜色保留
|
||||
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
|
||||
data: [
|
||||
{
|
||||
value: maskedRatio,
|
||||
name: '非正茧(%)',
|
||||
},
|
||||
{
|
||||
value: noMaskedRatio,
|
||||
name: '正茧(%)',
|
||||
},
|
||||
],
|
||||
|
||||
data: seriesData, // 关键改动!!!
|
||||
|
||||
emphasis: {
|
||||
label: {
|
||||
fontSize: '12',
|
||||
@@ -255,11 +279,11 @@ function refreshLineChart() {
|
||||
show: false,
|
||||
},
|
||||
padAngle: 5,
|
||||
name: '正茧平均占比',
|
||||
radius: ['40%', '70%'],
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
@@ -270,7 +294,6 @@ function refreshLineChart() {
|
||||
refreshVideoPlayer();
|
||||
}
|
||||
}
|
||||
const projectName = ref('');
|
||||
const fileName = ref('');
|
||||
const selectedFile = ref<File | null>(null);
|
||||
const fileInputRef = ref<HTMLInputElement | null>(null);
|
||||
@@ -293,15 +316,19 @@ const [Modal, modalApi] = useVbenModal({
|
||||
async function uploadFile() {
|
||||
// 先关闭弹窗
|
||||
modalApi.close();
|
||||
const formData = new FormData();
|
||||
formData.append('file', selectedFile.value!);
|
||||
formData.append('projectName', projectName.value);
|
||||
await createImageTaskV2(formData).then(() => {
|
||||
const uploadUrl = await getIVASCUploadToken();
|
||||
// 2. 使用 presigned URL 上传文件
|
||||
const file = selectedFile.value;
|
||||
message.success('正在上传视频');
|
||||
await axios.put(uploadUrl, file, {
|
||||
headers: {
|
||||
'Content-Type': file.type,
|
||||
},
|
||||
});
|
||||
message.success('正在分析,请稍后刷新列表查看');
|
||||
// 清空表单
|
||||
projectName.value = '';
|
||||
fileName.value = '';
|
||||
selectedFile.value = null;
|
||||
});
|
||||
}
|
||||
|
||||
function selectFile() {
|
||||
@@ -334,10 +361,6 @@ function changePage(newPage) {
|
||||
<BaseModal />
|
||||
<Modal>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="任务名称">
|
||||
<Input v-model:value="projectName" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="上传视频*" required>
|
||||
<div
|
||||
@click="selectFile"
|
||||
@@ -380,13 +403,13 @@ function changePage(newPage) {
|
||||
<div class="flex-1 space-y-2 overflow-auto">
|
||||
<div
|
||||
v-for="item in list"
|
||||
:key="item.v_id"
|
||||
:key="item.id"
|
||||
@click="selectItem(item)"
|
||||
class="cursor-pointer rounded border p-3 hover:bg-gray-100"
|
||||
:class="{ 'bg-gray-100': item.v_id === selectedItem?.v_id }"
|
||||
:class="{ 'bg-gray-100': item.id === selectedItem?.id }"
|
||||
>
|
||||
<div class="text-base font-medium">{{ item.v_name }}</div>
|
||||
<div class="text-sm text-gray-400">{{ item.v_a_time }}</div>
|
||||
<div class="text-base font-medium">{{ item.name }}</div>
|
||||
<div class="text-sm text-gray-400">{{ item.created_at }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分页 -->
|
||||
@@ -494,6 +517,17 @@ function changePage(newPage) {
|
||||
controls
|
||||
></video>
|
||||
</div>
|
||||
<div
|
||||
v-show="activeTab === 'original'"
|
||||
class="flex h-full space-x-4"
|
||||
>
|
||||
<video
|
||||
ref="originalVideoEl"
|
||||
class="video-js vjs-default-skin h-full w-full"
|
||||
preload="auto"
|
||||
controls
|
||||
></video>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Generated
+3
@@ -689,6 +689,9 @@ importers:
|
||||
ant-design-vue:
|
||||
specifier: 'catalog:'
|
||||
version: 4.2.6(vue@3.5.16(typescript@5.8.3))
|
||||
axios:
|
||||
specifier: 'catalog:'
|
||||
version: 1.9.0
|
||||
dayjs:
|
||||
specifier: 'catalog:'
|
||||
version: 1.11.13
|
||||
|
||||
Reference in New Issue
Block a user