修复切换<视频智能分析>与<蚕茧仪评分析>Tab页后其他页面无法正常加载的问题
This commit is contained in:
@@ -8,6 +8,12 @@ export namespace AuthApi {
|
|||||||
account?: string;
|
account?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
}
|
}
|
||||||
|
/** 注册 */
|
||||||
|
export interface RegisterParams {
|
||||||
|
account?: string;
|
||||||
|
code?: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Token {
|
export interface Token {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -39,6 +45,22 @@ export async function loginApi(data: AuthApi.LoginParams) {
|
|||||||
return requestClient.post<AuthApi.LoginResult>('/user/login', data);
|
return requestClient.post<AuthApi.LoginResult>('/user/login', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送验证码
|
||||||
|
*/
|
||||||
|
export async function sendCodeApi(str: string) {
|
||||||
|
return requestClient.post<any>('/user/sendCode', { str });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册
|
||||||
|
*/
|
||||||
|
export async function registerApi(data: AuthApi.RegisterParams) {
|
||||||
|
// 密码加密
|
||||||
|
data.password = sha256(data.password?.toString() || '');
|
||||||
|
return requestClient.post<any>('/user/register', data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新accessToken
|
* 刷新accessToken
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -46,5 +46,6 @@ export const overridesPreferences = defineOverridesPreferences({
|
|||||||
},
|
},
|
||||||
tabbar: {
|
tabbar: {
|
||||||
middleClickToClose: true,
|
middleClickToClose: true,
|
||||||
|
keepAlive: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
authority: ['iva'],
|
authority: ['iva'],
|
||||||
icon: 'mdi:video',
|
icon: 'mdi:video',
|
||||||
title: $t('ai.intelligence_video_analysis'),
|
title: $t('ai.intelligence_video_analysis'),
|
||||||
keepAlive: false,
|
keepAlive: true,
|
||||||
},
|
},
|
||||||
component: () => import('#/views/ai/iva/index.vue'),
|
component: () => import('#/views/ai/iva/index.vue'),
|
||||||
},
|
},
|
||||||
@@ -33,6 +33,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
authority: ['sca'],
|
authority: ['sca'],
|
||||||
icon: 'mdi:ice-cream',
|
icon: 'mdi:ice-cream',
|
||||||
title: $t('ai.silkworm_cocoon_analysis'),
|
title: $t('ai.silkworm_cocoon_analysis'),
|
||||||
|
keepAlive: false,
|
||||||
},
|
},
|
||||||
component: () => import('#/views/ai/sca/index.vue'),
|
component: () => import('#/views/ai/sca/index.vue'),
|
||||||
},
|
},
|
||||||
@@ -43,6 +44,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
authority: ['ysa'],
|
authority: ['ysa'],
|
||||||
icon: 'mdi:account-key-outline',
|
icon: 'mdi:account-key-outline',
|
||||||
title: $t('ai.young_silkworm_analysis'),
|
title: $t('ai.young_silkworm_analysis'),
|
||||||
|
keepAlive: false,
|
||||||
},
|
},
|
||||||
component: () => import('#/views/ai/ysa/index.vue'),
|
component: () => import('#/views/ai/ysa/index.vue'),
|
||||||
},
|
},
|
||||||
@@ -53,7 +55,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
meta: {
|
meta: {
|
||||||
icon: 'mdi:wall-fire',
|
icon: 'mdi:wall-fire',
|
||||||
iframeSrc: 'http://171.212.101.199:13010/',
|
iframeSrc: 'http://171.212.101.199:13010/',
|
||||||
keepAlive: true,
|
keepAlive: false,
|
||||||
title: '检索增强生成',
|
title: '检索增强生成',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,25 +2,72 @@
|
|||||||
import type { VbenFormSchema } from '@vben/common-ui';
|
import type { VbenFormSchema } from '@vben/common-ui';
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { computed, h, ref } from 'vue';
|
import { computed, h, ref, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationRegister, z } from '@vben/common-ui';
|
import { AuthenticationRegister, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
import { registerApi, sendCodeApi } from '#/api';
|
||||||
|
import { useAuthStore } from '#/store';
|
||||||
|
|
||||||
defineOptions({ name: 'Register' });
|
defineOptions({ name: 'Register' });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const CODE_LENGTH = 4;
|
||||||
|
|
||||||
|
const registerRef =
|
||||||
|
useTemplateRef<InstanceType<typeof AuthenticationRegister>>('registerRef');
|
||||||
|
const sending = ref(false);
|
||||||
const formSchema = computed((): VbenFormSchema[] => {
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
component: 'VbenInput',
|
component: 'VbenInput',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
placeholder: $t('authentication.usernameTip'),
|
placeholder: $t('authentication.emailTip'),
|
||||||
},
|
},
|
||||||
fieldName: 'username',
|
fieldName: 'account',
|
||||||
label: $t('authentication.username'),
|
label: $t('authentication.email'),
|
||||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
rules: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: $t('authentication.emailTip') })
|
||||||
|
.refine((v) => /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(v), {
|
||||||
|
message: $t('邮箱格式错误'),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'VbenPinInput',
|
||||||
|
componentProps: {
|
||||||
|
codeLength: CODE_LENGTH,
|
||||||
|
createText(countdown: number) {
|
||||||
|
if (sending.value) {
|
||||||
|
return $t('正在发送中');
|
||||||
|
}
|
||||||
|
return countdown > 0
|
||||||
|
? $t('authentication.sendText', [countdown])
|
||||||
|
: $t('authentication.sendCode');
|
||||||
|
},
|
||||||
|
placeholder: $t('authentication.code'),
|
||||||
|
async handleSendCode() {
|
||||||
|
sending.value = true;
|
||||||
|
try {
|
||||||
|
const formApi = registerRef.value?.getFormApi();
|
||||||
|
if (!formApi) throw new Error('formApi is not ready');
|
||||||
|
|
||||||
|
await formApi.validateField('account');
|
||||||
|
const isPhoneReady = await formApi.isFieldValid('account');
|
||||||
|
if (!isPhoneReady) throw new Error('邮箱不可为空');
|
||||||
|
|
||||||
|
const { account } = await formApi.getValues();
|
||||||
|
await sendCodeApi(account);
|
||||||
|
} finally {
|
||||||
|
sending.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fieldName: 'code',
|
||||||
|
label: $t('authentication.code'),
|
||||||
|
rules: z.string().length(CODE_LENGTH, {
|
||||||
|
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'VbenInputPassword',
|
component: 'VbenInputPassword',
|
||||||
@@ -80,15 +127,32 @@ const formSchema = computed((): VbenFormSchema[] => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
async function handleSubmit(value: Recordable<any>) {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
account: value.account,
|
||||||
|
code: value.code,
|
||||||
|
password: value.password,
|
||||||
|
};
|
||||||
|
const { accessToken } = await registerApi(params);
|
||||||
|
|
||||||
function handleSubmit(value: Recordable<any>) {
|
if (accessToken) {
|
||||||
// eslint-disable-next-line no-console
|
// 注册成功后,把 token 交给 authStore 走统一流程
|
||||||
console.log('register submit:', value);
|
const authStore = useAuthStore();
|
||||||
|
authStore.authLogin({ account: value.account, password: value.password });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('注册失败', error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthenticationRegister
|
<AuthenticationRegister
|
||||||
|
ref="registerRef"
|
||||||
:form-schema="formSchema"
|
:form-schema="formSchema"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@submit="handleSubmit"
|
@submit="handleSubmit"
|
||||||
|
|||||||
@@ -345,152 +345,154 @@ function onListItemClick(video: any) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BaseModal />
|
<div class="flex h-full w-full flex-col">
|
||||||
<CreateVideoTaskModal />
|
<BaseModal />
|
||||||
<div class="flex h-full w-full bg-gray-50">
|
<CreateVideoTaskModal />
|
||||||
<!-- 左侧:筛选 + 列表 -->
|
<div class="flex h-full w-full bg-gray-50">
|
||||||
<div class="flex w-64 flex-col border-r bg-white p-4">
|
<!-- 左侧:筛选 + 列表 -->
|
||||||
<!-- 按钮组 -->
|
<div class="flex w-64 flex-col border-r bg-white p-4">
|
||||||
<div class="mb-4 flex justify-between space-x-2">
|
<!-- 按钮组 -->
|
||||||
<Button type="primary" @click="createTask" class="flex-1">
|
<div class="mb-4 flex justify-between space-x-2">
|
||||||
新建任务
|
<Button type="primary" @click="createTask" class="flex-1">
|
||||||
</Button>
|
新建任务
|
||||||
<Button @click="refreshList" class="flex-1"> 刷新列表 </Button>
|
</Button>
|
||||||
</div>
|
<Button @click="refreshList" class="flex-1"> 刷新列表 </Button>
|
||||||
<!-- 筛选框 -->
|
|
||||||
<input
|
|
||||||
v-model="filterKeyword"
|
|
||||||
placeholder="筛选分析任务"
|
|
||||||
class="focus:ring-primary mb-4 rounded-md border px-3 py-2 focus:outline-none focus:ring-2"
|
|
||||||
/>
|
|
||||||
<!-- 列表 -->
|
|
||||||
<div class="flex-1 space-y-2 overflow-auto">
|
|
||||||
<div
|
|
||||||
v-for="item in list"
|
|
||||||
:key="item.v_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 }"
|
|
||||||
>
|
|
||||||
<div class="text-base font-medium">{{ item.v_name }}</div>
|
|
||||||
<div class="text-sm text-gray-400">{{ item.v_a_time }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- 筛选框 -->
|
||||||
</div>
|
<input
|
||||||
|
v-model="filterKeyword"
|
||||||
<!-- 右侧:Tab 内容区 -->
|
placeholder="筛选分析任务"
|
||||||
<div class="flex flex-1 flex-col overflow-hidden p-6">
|
class="focus:ring-primary mb-4 rounded-md border px-3 py-2 focus:outline-none focus:ring-2"
|
||||||
<!-- Tab 标题 -->
|
/>
|
||||||
<div class="mb-4 flex shrink-0 space-x-4 border-b">
|
<!-- 列表 -->
|
||||||
<button
|
<div class="flex-1 space-y-2 overflow-auto">
|
||||||
v-for="tab in tabs"
|
|
||||||
:key="tab.key"
|
|
||||||
@click="activeTab = tab.key as 'detail' | 'video'"
|
|
||||||
class="px-4 py-2"
|
|
||||||
:class="[
|
|
||||||
activeTab === tab.key
|
|
||||||
? 'border-primary text-primary border-b-2'
|
|
||||||
: 'hover:text-primary text-gray-500',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
{{ tab.label }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tab 内容(滚动区域) -->
|
|
||||||
<div class="flex-1 overflow-auto">
|
|
||||||
<div
|
|
||||||
v-if="!selectedItem"
|
|
||||||
class="flex h-full items-center justify-center text-gray-400"
|
|
||||||
>
|
|
||||||
请先选择左侧列表中的分析任务
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<div
|
<div
|
||||||
v-show="activeTab === 'detail'"
|
v-for="item in list"
|
||||||
class="flex h-full flex-col gap-4"
|
:key="item.v_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 }"
|
||||||
>
|
>
|
||||||
<!-- 主内容区域:左右结构 -->
|
<div class="text-base font-medium">{{ item.v_name }}</div>
|
||||||
<div class="flex flex-1 gap-4">
|
<div class="text-sm text-gray-400">{{ item.v_a_time }}</div>
|
||||||
<!-- 左侧 -->
|
</div>
|
||||||
<div class="flex w-72 flex-col gap-4">
|
</div>
|
||||||
<!-- 视频基础信息展示 -->
|
</div>
|
||||||
<div
|
|
||||||
class="w-full rounded border bg-white p-4"
|
<!-- 右侧:Tab 内容区 -->
|
||||||
id="video_base_info"
|
<div class="flex flex-1 flex-col overflow-hidden p-6">
|
||||||
>
|
<!-- Tab 标题 -->
|
||||||
|
<div class="mb-4 flex shrink-0 space-x-4 border-b">
|
||||||
|
<button
|
||||||
|
v-for="tab in tabs"
|
||||||
|
:key="tab.key"
|
||||||
|
@click="activeTab = tab.key as 'detail' | 'video'"
|
||||||
|
class="px-4 py-2"
|
||||||
|
:class="[
|
||||||
|
activeTab === tab.key
|
||||||
|
? 'border-primary text-primary border-b-2'
|
||||||
|
: 'hover:text-primary text-gray-500',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
{{ tab.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab 内容(滚动区域) -->
|
||||||
|
<div class="flex-1 overflow-auto">
|
||||||
|
<div
|
||||||
|
v-if="!selectedItem"
|
||||||
|
class="flex h-full items-center justify-center text-gray-400"
|
||||||
|
>
|
||||||
|
请先选择左侧列表中的分析任务
|
||||||
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-show="activeTab === 'detail'"
|
||||||
|
class="flex h-full flex-col gap-4"
|
||||||
|
>
|
||||||
|
<!-- 主内容区域:左右结构 -->
|
||||||
|
<div class="flex flex-1 gap-4">
|
||||||
|
<!-- 左侧 -->
|
||||||
|
<div class="flex w-72 flex-col gap-4">
|
||||||
|
<!-- 视频基础信息展示 -->
|
||||||
<div
|
<div
|
||||||
v-for="(value, key) in showInfoStr"
|
class="w-full rounded border bg-white p-4"
|
||||||
:key="key"
|
id="video_base_info"
|
||||||
class="mb-2 flex text-sm text-gray-700"
|
|
||||||
>
|
>
|
||||||
<div class="w-32 font-medium text-gray-900">
|
<div
|
||||||
{{ key }}:
|
v-for="(value, key) in showInfoStr"
|
||||||
</div>
|
:key="key"
|
||||||
<div class="flex-1 break-all text-gray-600">
|
class="mb-2 flex text-sm text-gray-700"
|
||||||
{{ value || '—' }}
|
>
|
||||||
|
<div class="w-32 font-medium text-gray-900">
|
||||||
|
{{ key }}:
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 break-all text-gray-600">
|
||||||
|
{{ value || '—' }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 下:空白卡片 -->
|
||||||
|
<div class="h-[300px] flex-1 rounded border bg-white p-4">
|
||||||
|
<EchartsUI ref="chartRef2" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 下:空白卡片 -->
|
<!-- 右侧 -->
|
||||||
<div class="h-[300px] flex-1 rounded border bg-white p-4">
|
<div class="flex flex-1 flex-col gap-4">
|
||||||
<EchartsUI ref="chartRef2" />
|
<!-- 上:四个统计卡片 -->
|
||||||
</div>
|
<AnalysisOverview
|
||||||
</div>
|
:items="overviewItems"
|
||||||
|
class="grid grid-cols-4 gap-4"
|
||||||
<!-- 右侧 -->
|
/>
|
||||||
<div class="flex flex-1 flex-col gap-4">
|
<!-- 下:折线图区域 -->
|
||||||
<!-- 上:四个统计卡片 -->
|
<div class="flex-1 rounded border bg-white p-4">
|
||||||
<AnalysisOverview
|
<EchartsUI ref="chartRef1" />
|
||||||
:items="overviewItems"
|
</div>
|
||||||
class="grid grid-cols-4 gap-4"
|
|
||||||
/>
|
|
||||||
<!-- 下:折线图区域 -->
|
|
||||||
<div class="flex-1 rounded border bg-white p-4">
|
|
||||||
<EchartsUI ref="chartRef1" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-show="activeTab === 'video'" class="flex h-full space-x-4">
|
||||||
<div v-show="activeTab === 'video'" class="flex h-full space-x-4">
|
<!-- 左侧视频区域 -->
|
||||||
<!-- 左侧视频区域 -->
|
<div class="flex-1 overflow-hidden rounded bg-black">
|
||||||
<div class="flex-1 overflow-hidden rounded bg-black">
|
<video
|
||||||
<video
|
ref="videoEl"
|
||||||
ref="videoEl"
|
class="video-js vjs-default-skin h-full w-full"
|
||||||
class="video-js vjs-default-skin h-full w-full"
|
preload="auto"
|
||||||
preload="auto"
|
controls
|
||||||
controls
|
></video>
|
||||||
></video>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 右侧时间点列表 -->
|
<!-- 右侧时间点列表 -->
|
||||||
<div
|
|
||||||
class="flex w-1/4 flex-col overflow-auto rounded-md border bg-white"
|
|
||||||
>
|
|
||||||
<!-- 列表标题 -->
|
|
||||||
<div
|
<div
|
||||||
class="flex justify-between border-b p-3 text-sm font-medium text-gray-700"
|
class="flex w-1/4 flex-col overflow-auto rounded-md border bg-white"
|
||||||
>
|
>
|
||||||
<span>事件</span>
|
<!-- 列表标题 -->
|
||||||
<span>时间点</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 列表内容 -->
|
|
||||||
<div class="flex-1">
|
|
||||||
<div
|
<div
|
||||||
v-for="(video, index) in detailList"
|
class="flex justify-between border-b p-3 text-sm font-medium text-gray-700"
|
||||||
:key="index"
|
|
||||||
@click="onListItemClick(video)"
|
|
||||||
class="flex cursor-pointer justify-between border-b p-3 text-sm hover:bg-gray-100"
|
|
||||||
>
|
>
|
||||||
<span>{{ video.action }}</span>
|
<span>事件</span>
|
||||||
<span>{{ video.time }}</span>
|
<span>时间点</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 列表内容 -->
|
||||||
|
<div class="flex-1">
|
||||||
|
<div
|
||||||
|
v-for="(video, index) in detailList"
|
||||||
|
:key="index"
|
||||||
|
@click="onListItemClick(video)"
|
||||||
|
class="flex cursor-pointer justify-between border-b p-3 text-sm hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
<span>{{ video.action }}</span>
|
||||||
|
<span>{{ video.time }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { EchartsUIType } from '@vben/plugins/echarts';
|
import type { EchartsUIType } from '@vben/plugins/echarts';
|
||||||
|
|
||||||
import { onMounted, ref, watch } from 'vue';
|
import { onActivated, onDeactivated, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||||
@@ -144,112 +144,127 @@ function refreshLineChart() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDeactivated(() => {
|
||||||
|
// 离开路由时清理状态
|
||||||
|
selectedItem.value = null;
|
||||||
|
showInfoStr.value = {};
|
||||||
|
});
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
// 回来的时候重新刷新一次列表
|
||||||
|
loadList();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BaseModal />
|
<div class="flex h-full w-full flex-col">
|
||||||
<CreateYSATaskModal />
|
<BaseModal />
|
||||||
<div class="flex h-full w-full bg-gray-50">
|
<CreateYSATaskModal />
|
||||||
<!-- 左侧:筛选 + 列表 -->
|
<div class="flex h-full w-full bg-gray-50">
|
||||||
<div class="flex w-64 flex-col border-r bg-white p-4">
|
<!-- 左侧:筛选 + 列表 -->
|
||||||
<!-- 按钮组 -->
|
<div class="flex w-64 flex-col border-r bg-white p-4">
|
||||||
<div class="mb-4 flex justify-between space-x-2">
|
<!-- 按钮组 -->
|
||||||
<Button type="primary" @click="createTask" class="flex-1">
|
<div class="mb-4 flex justify-between space-x-2">
|
||||||
新建任务
|
<Button type="primary" @click="createTask" class="flex-1">
|
||||||
</Button>
|
新建任务
|
||||||
<Button @click="refreshList" class="flex-1"> 刷新列表 </Button>
|
</Button>
|
||||||
</div>
|
<Button @click="refreshList" class="flex-1"> 刷新列表 </Button>
|
||||||
<!-- 筛选框 -->
|
</div>
|
||||||
<input
|
<!-- 筛选框 -->
|
||||||
v-model="filterKeyword"
|
<input
|
||||||
placeholder="筛选分析任务"
|
v-model="filterKeyword"
|
||||||
class="focus:ring-primary mb-4 rounded-md border px-3 py-2 focus:outline-none focus:ring-2"
|
placeholder="筛选分析任务"
|
||||||
/>
|
class="focus:ring-primary mb-4 rounded-md border px-3 py-2 focus:outline-none focus:ring-2"
|
||||||
<!-- 列表 -->
|
/>
|
||||||
<div class="flex-1 space-y-2 overflow-auto">
|
<!-- 列表 -->
|
||||||
<div
|
<div class="flex-1 space-y-2 overflow-auto">
|
||||||
v-for="item in list"
|
<div
|
||||||
:key="item.v_id"
|
v-for="item in list"
|
||||||
@click="selectItem(item)"
|
:key="item.v_id"
|
||||||
class="cursor-pointer rounded border p-3 hover:bg-gray-100"
|
@click="selectItem(item)"
|
||||||
:class="{ 'bg-gray-100': item.id === selectedItem?.id }"
|
class="cursor-pointer rounded border p-3 hover:bg-gray-100"
|
||||||
>
|
:class="{ 'bg-gray-100': item.id === selectedItem?.id }"
|
||||||
<div class="text-base font-medium">{{ item.name }}</div>
|
>
|
||||||
<div class="text-sm text-gray-400">{{ item.upload_datetime }}</div>
|
<div class="text-base font-medium">{{ item.name }}</div>
|
||||||
|
<div class="text-sm text-gray-400">{{ item.upload_datetime }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 右侧:Tab 内容区 -->
|
<!-- 右侧:Tab 内容区 -->
|
||||||
<div class="flex flex-1 flex-col overflow-hidden p-6">
|
<div class="flex flex-1 flex-col overflow-hidden p-6">
|
||||||
<div
|
<div
|
||||||
v-if="!selectedItem"
|
v-if="!selectedItem"
|
||||||
class="flex h-full items-center justify-center text-gray-400"
|
class="flex h-full items-center justify-center text-gray-400"
|
||||||
>
|
>
|
||||||
请先选择左侧列表中的分析任务
|
请先选择左侧列表中的分析任务
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="flex h-full flex-col gap-4">
|
<div class="flex h-full flex-col gap-4">
|
||||||
<!-- 主内容区域:左右结构 -->
|
<!-- 主内容区域:左右结构 -->
|
||||||
<div class="flex flex-1 gap-4">
|
<div class="flex flex-1 gap-4">
|
||||||
<!-- 左侧 -->
|
<!-- 左侧 -->
|
||||||
<div class="flex w-72 flex-col gap-4">
|
<div class="flex w-72 flex-col gap-4">
|
||||||
<!-- 视频基础信息展示 -->
|
<!-- 视频基础信息展示 -->
|
||||||
<div
|
|
||||||
class="w-full rounded border bg-white p-4"
|
|
||||||
id="video_base_info"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
v-for="(value, key) in showInfoStr"
|
class="w-full rounded border bg-white p-4"
|
||||||
:key="key"
|
id="video_base_info"
|
||||||
class="mb-2 flex text-sm text-gray-700"
|
|
||||||
>
|
>
|
||||||
<div class="w-32 font-medium text-gray-900">{{ key }}:</div>
|
<div
|
||||||
<div class="flex-1 break-all text-gray-600">
|
v-for="(value, key) in showInfoStr"
|
||||||
{{ value || '—' }}
|
:key="key"
|
||||||
|
class="mb-2 flex text-sm text-gray-700"
|
||||||
|
>
|
||||||
|
<div class="w-32 font-medium text-gray-900">
|
||||||
|
{{ key }}:
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 break-all text-gray-600">
|
||||||
|
{{ value || '—' }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 下:空白卡片 -->
|
<!-- 下:空白卡片 -->
|
||||||
<div class="flex-1 rounded border bg-white p-4">
|
<div class="flex-1 rounded border bg-white p-4">
|
||||||
<EchartsUI ref="chartRef2" />
|
<EchartsUI ref="chartRef2" />
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 右侧 -->
|
|
||||||
<div class="flex flex-1 flex-col gap-4">
|
|
||||||
<!-- 上:左右两个图片显示 -->
|
|
||||||
<div class="flex flex-1 gap-4">
|
|
||||||
<!-- 左图 -->
|
|
||||||
<div
|
|
||||||
class="flex flex-1 items-center justify-center rounded border bg-white p-4"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
:src="selectedItem?.image_pre"
|
|
||||||
alt="左图"
|
|
||||||
class="object-contain"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- 右图 -->
|
|
||||||
<div
|
|
||||||
class="flex flex-1 items-center justify-center rounded border bg-white p-4"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
:src="selectedItem?.image_after"
|
|
||||||
alt="右图"
|
|
||||||
class="object-contain"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 下:柱状图区域 -->
|
|
||||||
<div class="flex-1 rounded border bg-white p-4">
|
<!-- 右侧 -->
|
||||||
<EchartsUI ref="chartRef1" />
|
<div class="flex flex-1 flex-col gap-4">
|
||||||
|
<!-- 上:左右两个图片显示 -->
|
||||||
|
<div class="flex flex-1 gap-4">
|
||||||
|
<!-- 左图 -->
|
||||||
|
<div
|
||||||
|
class="flex flex-1 items-center justify-center rounded border bg-white p-4"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="selectedItem?.image_pre"
|
||||||
|
alt="左图"
|
||||||
|
class="object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 右图 -->
|
||||||
|
<div
|
||||||
|
class="flex flex-1 items-center justify-center rounded border bg-white p-4"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="selectedItem?.image_after"
|
||||||
|
alt="右图"
|
||||||
|
class="object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 下:柱状图区域 -->
|
||||||
|
<div class="flex-1 rounded border bg-white p-4">
|
||||||
|
<EchartsUI ref="chartRef1" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
"sendResetLink": "发送重置链接",
|
"sendResetLink": "发送重置链接",
|
||||||
"sendText": "{0}秒后重新获取",
|
"sendText": "{0}秒后重新获取",
|
||||||
"signUp": "注册",
|
"signUp": "注册",
|
||||||
"signUpSubtitle": "让您的应用程序管理变得简单而有趣",
|
"signUpSubtitle": "让您的人工智能体验变得简单而有趣",
|
||||||
"terms": "条款",
|
"terms": "条款",
|
||||||
"thirdPartyLogin": "其他登录方式",
|
"thirdPartyLogin": "其他登录方式",
|
||||||
"username": "账号",
|
"username": "账号",
|
||||||
|
|||||||
Reference in New Issue
Block a user