refactor: 重构微应用集成和组件结构

master
钟良源 1 month ago
parent 570b2664cd
commit 1a1281bcdd

@ -9,10 +9,8 @@ import './style.css'
// 初始化 micro-app // 初始化 micro-app
microApp.start({ microApp.start({
'disable-sandbox': true, // 'disable-sandbox': true,
'disable-scopecss': true, // 'disable-scopecss': true,
// 启用虚拟路由系统,隔离子应用路由
// 这样子应用的 React Router 不会受到基座 hash 路由的影响
}) })
const app = createApp(App) const app = createApp(App)

@ -1,14 +1,14 @@
<template> <template>
<div class="container-canvas" :style="{ padding: config ? '20px' : '', height: config ? '100%' : '' }"> <div class="container-canvas" :style="{ padding: config ? '20px' : '', height: config ? '100%' : '' }">
<micro-app <micro-app
iframe iframe
name="label-app" name="label-app"
:url="baseUrl" v-if="baseUrl"
style="display: block; width: 100%" :url="baseUrl"
:style="{ height: config ? 'fit-content' : '' }" style="display: block; width: 100%;height: 100%"
@mounted="microAppMounted" @mounted="microAppMounted"
router-mode="pure" router-mode="pure"
:disable-scopecss="media_type === '视频' && !config" :disable-scopecss="media_type === '视频' && !config"
/> />
<div v-if="config" class="config-btn"> <div v-if="config" class="config-btn">
<el-button type="default" @click="handleCancel"></el-button> <el-button type="default" @click="handleCancel"></el-button>
@ -18,16 +18,16 @@
<!-- 弹窗标签配置 --> <!-- 弹窗标签配置 -->
<el-dialog v-model="configDialog.show" title="标签配置" width="60vw"> <el-dialog v-model="configDialog.show" title="标签配置" width="60vw">
<!-- 自定义 --> <!-- 自定义 -->
<TagConfiguration v-if="configDialog.method === 'custom'" :id="configDialog.id" :is-use-canvas="true" /> <TagConfiguration v-if="configDialog.method === 'custom'" :id="configDialog.id" :is-use-canvas="true"/>
<!-- 其他 --> <!-- 其他 -->
<div v-else-if="configDialog.method == 'other'"> <div v-else-if="configDialog.method == 'other'">
<micro-app <micro-app
iframe iframe
name="label-app-editor" name="label-app-editor"
:url="configDialog.url" :url="configDialog.url"
style="display: block; width: 100%" style="display: block; width: 100%"
@mounted="microEditorMounted" @mounted="microEditorMounted"
/> />
<div style="text-align: right"> <div style="text-align: right">
<el-button type="primary" @click="labelingToolCreateSubmission"></el-button> <el-button type="primary" @click="labelingToolCreateSubmission"></el-button>
@ -38,18 +38,18 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useMicroApptore } from '@/stores/modules/micro-app'; import {useMicroApptore} from '@/stores/modules/micro-app';
import { useUserStore } from '@/stores/modules/user'; import {useUserStore} from '@/stores/modules/user';
import microAppX from '@micro-zoe/micro-app'; import microAppX from '@micro-zoe/micro-app';
import { useRouter } from 'vue-router'; import {useRouter} from 'vue-router';
import { ElMessage } from 'element-plus'; import {ElMessage} from 'element-plus';
import TagConfiguration from './tag-configuration.vue'; import TagConfiguration from './tag-configuration.vue';
import { tagConfig } from './type'; import {tagConfig} from './type';
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const { taskId, sampleId } = route.params; const {taskId, sampleId} = route.params;
const { media_type } = route.query; const {media_type} = route.query;
const configDialog = ref<tagConfig>({ const configDialog = ref<tagConfig>({
method: '', method: '',
show: false, show: false,
@ -58,7 +58,7 @@ const configDialog = ref<tagConfig>({
}); });
const config = route.hash; const config = route.hash;
const { getAppDetail } = useMicroApptore(); const {getAppDetail} = useMicroApptore();
const userStore = useUserStore(); const userStore = useUserStore();
const postData = reactive({ const postData = reactive({
@ -126,7 +126,7 @@ const handleDataListener = (props: any) => {
ElMessage.warning(props.msg); ElMessage.warning(props.msg);
break; break;
// //
case 'TAG_CONFIG': case 'TAG_CONFIG':
if (props.method == 'custom') { if (props.method == 'custom') {
// //
@ -186,7 +186,7 @@ const handleCancel = () => {
}; };
const handleSubmit = () => { const handleSubmit = () => {
microAppX.setData('label-app', { type: 'SAVE_TASK_EDIT', timestamp: Date.now() }); microAppX.setData('label-app', {type: 'SAVE_TASK_EDIT', timestamp: Date.now()});
}; };
onBeforeUnmount(() => { onBeforeUnmount(() => {
@ -194,7 +194,7 @@ onBeforeUnmount(() => {
}); });
const labelingToolCreateSubmission = () => { const labelingToolCreateSubmission = () => {
microAppX.setData('label-app-editor', { type: 'SAVE_TASK_EDIT', timestamp: Date.now() }); microAppX.setData('label-app-editor', {type: 'SAVE_TASK_EDIT', timestamp: Date.now()});
configDialog.value.show = false; configDialog.value.show = false;
}; };
@ -206,6 +206,8 @@ onUnmounted(() => {
<style lang="less" scoped> <style lang="less" scoped>
.container-canvas { .container-canvas {
width: 100%;
height: 100%;
background-color: #fff; background-color: #fff;
overflow: auto; overflow: auto;

@ -35,7 +35,7 @@
<!-- 标签设置图片视频音频 --> <!-- 标签设置图片视频音频 -->
<div v-if="activeStep === 'labelSettings'" class="steps-micro"> <div v-if="activeStep === 'labelSettings'" class="steps-micro">
<micro-app iframe name="label-app" :url="baseUrl" style="display: block; width: 100%" @mounted="microAppMounted" /> <micro-app iframe name="label-app" :src="baseUrl" style="display: block; width: 100%;height: 100%" @mounted="microAppMounted" />
</div> </div>
<!-- 标签设置文本 --> <!-- 标签设置文本 -->

@ -3,7 +3,9 @@
<div class="header"> <div class="header">
<div class="header-left"> <div class="header-left">
<div class="back-title" @click="goBack"> <div class="back-title" @click="goBack">
<el-icon style="margin-right: 6px" size="18"><ArrowLeft /></el-icon> <el-icon style="margin-right: 6px" size="18">
<ArrowLeft/>
</el-icon>
<span>{{ task.name }}</span> <span>{{ task.name }}</span>
</div> </div>
<div class="filter-buttons"> <div class="filter-buttons">
@ -22,8 +24,10 @@
</div> </div>
</div> </div>
<div class="action-buttons"> <div class="action-buttons">
<el-button v-if="task.media_type !== 'qa'" link @click="openTagConfig" :icon="Setting" style="margin-right: 10px" <el-button v-if="task.media_type !== 'qa'" link @click="openTagConfig" :icon="Setting"
>标签配置</el-button style="margin-right: 10px"
>标签配置
</el-button
> >
<el-button link @click="openExportDialog" :icon="Printer" style="margin-right: 10px">数据导出</el-button> <el-button link @click="openExportDialog" :icon="Printer" style="margin-right: 10px">数据导出</el-button>
<el-button type="primary" @click="startMarking"></el-button> <el-button type="primary" @click="startMarking"></el-button>
@ -33,30 +37,30 @@
<div class="table-container"> <div class="table-container">
<div class="table-content"> <div class="table-content">
<el-table <el-table
:data="sampleList" :data="sampleList"
style="width: 100%" style="width: 100%"
height="100%" height="100%"
:stripe="false" :stripe="false"
v-loading="sampleListLoading" v-loading="sampleListLoading"
@row-click="handleRowClick" @row-click="handleRowClick"
:row-style="{ cursor: 'pointer' }" :row-style="{ cursor: 'pointer' }"
> >
<el-table-column align="center" prop="index" label="序号" width="80" fixed /> <el-table-column align="center" prop="index" label="序号" width="80" fixed/>
<el-table-column <el-table-column
prop="name" prop="name"
label="名称" label="名称"
:width="task.media_type !== 'timeseries' && task.media_type !== 'plaintext' ? '150' : ''" :width="task.media_type !== 'timeseries' && task.media_type !== 'plaintext' ? '150' : ''"
fixed fixed
/> />
<template v-if="task.media_type === 'qa'"> <template v-if="task.media_type === 'qa'">
<el-table-column <el-table-column
v-for="key in qaTableColumn" v-for="key in qaTableColumn"
:key="key" :key="key"
:prop="key" :prop="key"
:label="key" :label="key"
show-overflow-tooltip show-overflow-tooltip
min-width="250px" min-width="250px"
/> />
</template> </template>
@ -64,8 +68,9 @@
<el-table-column label="数据预览"> <el-table-column label="数据预览">
<template #default="scope"> <template #default="scope">
<audio v-if="task.media_type === 'audio'" :src="scope.row.previewUrl" controls></audio> <audio v-if="task.media_type === 'audio'" :src="scope.row.previewUrl" controls></audio>
<video v-else-if="task.media_type === 'video'" :src="scope.row.previewUrl" controls style="height: 80px"></video> <video v-else-if="task.media_type === 'video'" :src="scope.row.previewUrl" controls
<img v-else-if="task.media_type == 'image'" :src="scope.row.previewUrl" style="height: 80px" /> style="height: 80px"></video>
<img v-else-if="task.media_type == 'image'" :src="scope.row.previewUrl" style="height: 80px"/>
</template> </template>
</el-table-column> </el-table-column>
</template> </template>
@ -77,20 +82,21 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-if="task.media_type !== 'qa'" align="center" prop="annotated_count" label="标注数量" width="120" /> <el-table-column v-if="task.media_type !== 'qa'" align="center" prop="annotated_count" label="标注数量"
width="120"/>
</el-table> </el-table>
</div> </div>
<div class="pagination-box"> <div class="pagination-box">
<el-pagination <el-pagination
background background
:current-page="currentPage" :current-page="currentPage"
:page-size="pageSize" :page-size="pageSize"
:page-sizes="[10, 20, 50, 100]" :page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next" layout="total, sizes, prev, pager, next"
:total="total" :total="total"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
@size-change="handleSizeChange" @size-change="handleSizeChange"
/> />
</div> </div>
</div> </div>
@ -99,7 +105,7 @@
<el-form :model="exportForm" :rules="exportRules" ref="exportFormRef" label-width="100px"> <el-form :model="exportForm" :rules="exportRules" ref="exportFormRef" label-width="100px">
<el-form-item label="导出格式" prop="export_type"> <el-form-item label="导出格式" prop="export_type">
<el-select v-model="exportForm.export_type" placeholder="请选择导出格式" style="width: 130px"> <el-select v-model="exportForm.export_type" placeholder="请选择导出格式" style="width: 130px">
<el-option v-for="item in exportTypes" :key="item" :label="item" :value="item" /> <el-option v-for="item in exportTypes" :key="item" :label="item" :value="item"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -112,27 +118,27 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ArrowLeft, Setting, Printer } from '@element-plus/icons-vue'; import {ArrowLeft, Setting, Printer} from '@element-plus/icons-vue';
import { useGet, usePost } from '../../request'; import {useGet, usePost} from '../../request';
import { markingApi, baseApi } from '../../request/api/requestApi'; import {markingApi, baseApi} from '../../request/api/requestApi';
import { parseContent } from '../../utils/parse-first-level'; import {parseContent} from '../../utils/parse-first-level';
import { ElMessage } from 'element-plus'; import {ElMessage} from 'element-plus';
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const id = route.params.id as string; const id = route.params.id as string;
const media_type = route.params.media_type as string; const media_type = route.params.media_type as string;
const { doGet: taskGet } = useGet({ const {doGet: taskGet} = useGet({
url: markingApi.taskApi.get url: markingApi.taskApi.get
}); });
const { doGet: sampleListGet, loading: sampleListLoading } = useGet({ const {doGet: sampleListGet, loading: sampleListLoading} = useGet({
url: markingApi.sample.page(id) url: markingApi.sample.page(id)
}); });
const { doGet: baseConfigGet } = useGet({ const {doGet: baseConfigGet} = useGet({
url: baseApi.config url: baseApi.config
}); });
const { doPost: exportPost, loading: exportPostLoading } = usePost({ const {doPost: exportPost, loading: exportPostLoading} = usePost({
url: markingApi.sample.export url: markingApi.sample.export
}); });
@ -179,7 +185,7 @@ const exportForm = ref({
export_type: 'JSON' export_type: 'JSON'
}); });
const exportRules = ref({ const exportRules = ref({
export_type: [{ required: true, message: '请选择导出类型', trigger: 'blur' }] export_type: [{required: true, message: '请选择导出类型', trigger: 'blur'}]
}); });
const exportFormRef = ref<any>(); const exportFormRef = ref<any>();
@ -202,9 +208,9 @@ const handleExportSubmit = async () => {
const openTagConfig = () => { const openTagConfig = () => {
if (task.value.media_type !== 'plaintext' && task.value.media_type !== 'timeseries') { if (task.value.media_type !== 'plaintext' && task.value.media_type !== 'timeseries') {
router.push({ path: `/data-factory/marking/canvas/${id}`, hash: '#config' }); router.push({path: `/data-factory/marking/canvas/${id}`, hash: '#config'});
} else { } else {
router.push({ path: `/data-factory/marking/plaintextConfiguration/${id}` }); router.push({path: `/data-factory/marking/plaintextConfiguration/${id}`});
} }
}; };
@ -280,11 +286,11 @@ const getSampleList = async (pageNumber: number = 1, pageSize: number = 10, stat
if (res.data.code === 0) { if (res.data.code === 0) {
sampleList.value = res.data.data.data.map((item: any, index: number) => { sampleList.value = res.data.data.data.map((item: any, index: number) => {
const parsedData = const parsedData =
task.value.media_type !== 'timeseries' task.value.media_type !== 'timeseries'
? task.value.media_type === 'qa' ? task.value.media_type === 'qa'
? parseContent(eval(`(${item.data})`).content) ? parseContent(eval(`(${item.data})`).content)
: eval(`(${item.data})`) : eval(`(${item.data})`)
: {}; : {};
return { return {
index: index + 1, index: index + 1,
...item, ...item,
@ -292,9 +298,9 @@ const getSampleList = async (pageNumber: number = 1, pageSize: number = 10, stat
created_time: new Date(item.created_time).toLocaleString(), created_time: new Date(item.created_time).toLocaleString(),
updated_time: new Date(item.updated_time).toLocaleString(), updated_time: new Date(item.updated_time).toLocaleString(),
previewUrl: previewUrl:
task.value.media_type === 'timeseries' || task.value.media_type === 'qa' task.value.media_type === 'timeseries' || task.value.media_type === 'qa'
? '' ? ''
: config.value?.storage_dataset_base_url + parsedData.sample_file_path : config.value?.storage_dataset_base_url + parsedData.sample_file_path
}; };
}); });
if (task.value.media_type === 'qa') { if (task.value.media_type === 'qa') {
@ -374,30 +380,36 @@ const outputSample = async (activeTxt: string) => {
padding: 20px; padding: 20px;
background-color: #ffffff; background-color: #ffffff;
border-radius: 4px; border-radius: 4px;
.header { .header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 20px; margin-bottom: 20px;
.header-left { .header-left {
display: flex; display: flex;
gap: 60px; gap: 60px;
align-items: center; align-items: center;
.back-title { .back-title {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 18px; font-size: 18px;
cursor: pointer; cursor: pointer;
} }
.filter-buttons { .filter-buttons {
display: flex; display: flex;
gap: 15px; gap: 15px;
.btn-item { .btn-item {
padding: 5px 15px; padding: 5px 15px;
color: rgb(78 89 105 / 100%); color: rgb(78 89 105 / 100%);
cursor: pointer; cursor: pointer;
border-radius: 20px; border-radius: 20px;
transition: background-color 0.4s; transition: background-color 0.4s;
&.active { &.active {
color: rgb(22 93 255 / 100%); color: rgb(22 93 255 / 100%);
background: rgb(237 237 237 / 100%); background: rgb(237 237 237 / 100%);
@ -405,20 +417,24 @@ const outputSample = async (activeTxt: string) => {
} }
} }
} }
.action-buttons { .action-buttons {
el-button { el-button {
margin-left: 10px; margin-left: 10px;
} }
} }
} }
.table-container { .table-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
height: calc(100% - 40px); height: calc(100% - 40px);
.table-content { .table-content {
flex: 1; flex: 1;
overflow: auto; overflow: auto;
.text-ellipsis { .text-ellipsis {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
@ -428,12 +444,14 @@ const outputSample = async (activeTxt: string) => {
vertical-align: middle; vertical-align: middle;
} }
} }
.pagination-box { .pagination-box {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
padding: 10px 0; padding: 10px 0;
} }
} }
.dialog-footer { .dialog-footer {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;

@ -1,111 +1,51 @@
<template> <template>
<div class="flow-app-container"> <div class="flow-app-container">
<micro-app <micro-app name="flow-app" :url="baseUrl" style="display: block; width: 100%;height:100%"/>
v-if="baseUrl"
name="flow-app"
:url="baseUrl"
iframe
style="display: block; width: 100%; height: 100%"
@created="onCreated"
@beforemount="onBeforeMount"
@mounted="onMountedHandler"
@error="onError"
/>
<div v-else class="loading-container">
<el-icon class="is-loading"><Loading /></el-icon>
<span>加载中...</span>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Loading } from '@element-plus/icons-vue'; import {useRoute} from 'vue-router';
import { useRoute } from 'vue-router'; import {useMicroApptore} from '@/stores/modules/micro-app';
import { useMicroApptore } from '@/stores/modules/micro-app'; import {ElMessage} from 'element-plus';
import { ElMessage } from 'element-plus';
import microAppX from '@micro-zoe/micro-app'; import microAppX from '@micro-zoe/micro-app';
import { useUserStore } from '@/stores/modules/user'; import {useUserStore} from '@/stores/modules/user';
const userStore = useUserStore(); const userStore = useUserStore();
const { getAppDetail } = useMicroApptore();
const route = useRoute();
const baseUrl = ref('');
const postData = reactive({ const postData = reactive({
userStoreData: userStore.$state userStoreData: userStore.$state
}); });
const {getAppDetail} = useMicroApptore();
// micro-app const route = useRoute();
const onCreated = () => { const baseUrl = ref('');
console.log('[flow-app] created');
};
const onBeforeMount = () => {
console.log('[flow-app] beforemount');
};
const onMountedHandler = () => {
console.log('[flow-app] mounted');
};
const onError = (e: CustomEvent) => {
console.error('[flow-app] error:', e.detail);
ElMessage.error('子应用加载失败');
};
onMounted(() => { onMounted(() => {
const agentId = route.params.id; const commonUrl = `agent/${route.params.id}`;
console.log('[flow-app] agentId:', agentId);
if (import.meta.env.VITE_USER_NODE_ENV === 'production') { if (import.meta.env.VITE_USER_NODE_ENV === 'production') {
const appUrl = getAppDetail('flow-app')?.appUrl; const appUrl = getAppDetail('flow-app')?.appUrl; // 线
if (!appUrl) { if (!appUrl) {
ElMessage.warning('微应用路径获取失败'); ElMessage({
return; message: '微应用路径获取失败'
});
} }
baseUrl.value = appUrl + 'agent/' + agentId; // 线
microAppX.setData('flow-app', { baseUrl.value = appUrl ? appUrl + commonUrl : '';
...postData, microAppX.setData('flow-app', {...postData, server_ip: location.origin, isTemplate: route.query.isTemplate}); //
server_ip: location.origin,
isTemplate: route.query.isTemplate,
agentId
});
} else { } else {
// 访 //
baseUrl.value = '/flowapp/agent/' + agentId; baseUrl.value = 'http://localhost:9222/flowapp/' + commonUrl;
console.log('[flow-app] baseUrl:', baseUrl.value); microAppX.setData('flow-app', {...postData, server_ip: undefined, isTemplate: route.query.isTemplate}); // 使
nextTick(() => {
microAppX.setData('flow-app', {
...postData,
server_ip: undefined,
isTemplate: route.query.isTemplate,
agentId
});
});
} }
}); });
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
.flow-app-container { .flow-app-container {
position: absolute;
top: 0;
left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
} }
.loading-container {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
gap: 8px;
color: #909399;
font-size: 14px;
}
</style> </style>

@ -56,6 +56,7 @@
} }
.process-content { .process-content {
position: relative;
flex: 1; flex: 1;
overflow: auto; overflow: auto;
padding: 16px; padding: 16px;

Loading…
Cancel
Save