Merge pull request #101 from Learnware-LAMDA/feat(frontend)/hetersearch

feat(frontend)/hetersearch
This commit is contained in:
AnnyTerfect 2023-11-07 18:59:28 +08:00 committed by GitHub
commit bc000772c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 416 additions and 284 deletions

View File

@ -78,9 +78,9 @@ watch(
<template>
<v-app>
<app-bar v-model:drawerOpen="drawerOpen" :routes="routes"></app-bar>
<app-bar v-model="drawerOpen" :routes="routes"></app-bar>
<nav-drawer v-model:drawerOpen="drawerOpen" :routes="routes"></nav-drawer>
<nav-drawer v-model="drawerOpen" :routes="routes"></nav-drawer>
<v-main class="bg-gray-100 bg-opacity-50">
<router-view v-slot="{ Component }">

View File

@ -1,4 +1,4 @@
export { downloadLearnware } from "./download.js";
export { fetchex } from "./fetchex.js";
export { default as saveContentToFile } from "./saveContentToFile.js";
export { hex_md5 } from "./encrypt.js";
export { downloadLearnware } from "./download";
export { fetchex } from "./fetchex";
export { default as saveContentToFile } from "@main/utils/saveContentToFile";
export { hex_md5 } from "./encrypt";

View File

@ -342,7 +342,7 @@ async function handleClickExport(): Promise<void> {
});
}
const csvContent = "\ufeff" + table.map((e) => e.join(",")).join("\n");
saveContentToFile(csvContent, "user_list.csv");
saveContentToFile(csvContent, "text/csv;charset=utf-8", "user_list.csv");
}
watch(

View File

@ -1,3 +1,4 @@
export default {
Description: "Description",
Finish: "Finish",
};

View File

@ -10,6 +10,8 @@ export default {
Updated: "Updated",
HeterogeneousSearch: "Heterogeneous Search",
TurnOffHeterogeneousSearch: "Turn off heterogeneous search",
UploadHeterogeneousRequirement: "Upload heterogeneous requirement",
StartHeterogeneousSearch: "Start heterogeneous search",
RecommendedMultipleLearnware: "Recommended multiple learnware",
RecommendedMultipleLearnwareTips:
"The learnwares listed below are highly recommended as they have the highest statistical specification similarity to your tasks. Combining these learnwares can lead to great effectiveness.",

View File

@ -1,3 +1,4 @@
export default {
Description: "描述",
Finish: "完成",
};

View File

@ -10,6 +10,8 @@ export default {
Updated: "更新于",
HeterogeneousSearch: "异构查搜",
TurnOffHeterogeneousSearch: "关闭异构查搜",
UploadHeterogeneousRequirement: "上传异构需求",
StartHeterogeneousSearch: "开始异构查搜",
RecommendedMultipleLearnware: "推荐多个学件",
RecommendedMultipleLearnwareTips:
"以下学件被推荐,因为它们与您的任务具有最高的统计规约相似性。结合这些学件可以带来很好的效果。",

View File

@ -82,9 +82,9 @@ watch(
<template>
<v-app>
<app-bar v-model:drawerOpen="drawerOpen" :routes="routes"></app-bar>
<app-bar v-model="drawerOpen" :routes="routes"></app-bar>
<nav-drawer v-model:drawerOpen="drawerOpen" :routes="routes"></nav-drawer>
<nav-drawer v-model="drawerOpen" :routes="routes"></nav-drawer>
<v-main class="bg-gray-100 bg-opacity-50">
<router-view v-slot="{ Component }">

View File

@ -7,11 +7,11 @@ import learnwareLogo from "/logo.svg?url";
import type { Route } from "@beiming-system/types/route";
export interface Props {
drawerOpen: boolean;
modelValue: boolean;
routes: Route[];
}
const emit = defineEmits(["update:drawerOpen"]);
const emit = defineEmits(["update:modelValue"]);
const router = useRouter();
const props = defineProps<Props>();
@ -54,7 +54,7 @@ const filteredRoutes = computed<Route[]>(
<div class="prepend">
<v-app-bar-nav-icon
v-if="['xs', 'sm'].includes(display.name.value)"
@click="() => emit('update:drawerOpen', !drawerOpen)"
@click="() => emit('update:modelValue', !modelValue)"
></v-app-bar-nav-icon>
<div class="logo" @click="() => router.push('/')">
<img class="logo-img" :src="learnwareLogo" />

View File

@ -5,19 +5,19 @@ import { useDisplay } from "vuetify";
import type { Route } from "@beiming-system/types/route";
export interface Props {
drawerOpen: boolean;
modelValue: boolean;
routes: Route[];
}
const display = useDisplay();
const emit = defineEmits(["update:drawerOpen"]);
const emit = defineEmits(["update:modelValue"]);
const props = defineProps<Props>();
const drawer = computed({
get: () => props.drawerOpen && ["xs", "sm"].includes(display.name.value),
set: (value) => emit("update:drawerOpen", value),
get: () => props.modelValue && ["xs", "sm"].includes(display.name.value),
set: (value) => emit("update:modelValue", value),
});
const store = useStore();

View File

@ -7,11 +7,14 @@ import TaskTypeBtns from "../Specification/SpecTag/TaskType.vue";
import LibraryTypeBtns from "../Specification/SpecTag/LibraryType.vue";
import FileUpload from "../Specification/FileUpload.vue";
import ScenarioListBtns from "../Specification/SpecTag/ScenarioList.vue";
import DescriptionInput from "../Specification/DescriptionInput.vue";
import { saveContentToFile } from "../../utils";
import type { DataType, TaskType, LibraryType, Filter } from "@beiming-system/types/learnware";
export interface Props {
modelValue: Filter;
isAdmin?: boolean;
isHeterogeneous?: boolean;
}
const { t } = useI18n();
@ -20,6 +23,7 @@ const emits = defineEmits(["update:modelValue"]);
withDefaults(defineProps<Props>(), {
isAdmin: false,
isHeterogeneous: false,
});
const route = useRoute();
@ -41,6 +45,27 @@ const scenarioList = ref(tryScenarioList);
const files = ref([]);
const heterDialog = ref(false);
const heterTab = ref<"dataType" | "taskType">("dataType");
const dataTypeDescription = ref({
Dimension: 7,
Description: {
0: "gender",
1: "age",
2: "the description of feature 2",
5: "the description of feature 5",
},
});
const taskTypeDescription = ref({
Dimension: 2,
Description: {
0: "the description of label 0",
1: "the description of label 1",
},
});
const tempDataTypeDescription = ref(dataTypeDescription.value);
const tempTaskTypeDescription = ref(taskTypeDescription.value);
const requirement = computed(() => ({
id: id.value,
name: search.value,
@ -49,8 +74,20 @@ const requirement = computed(() => ({
libraryType: libraryType.value,
scenarioList: scenarioList.value,
files: files.value,
dataTypeDescription: dataTypeDescription.value,
taskTypeDescription: taskTypeDescription.value,
}));
watch(
() => heterDialog.value,
(val) => {
if (!val) {
dataTypeDescription.value = tempDataTypeDescription.value;
taskTypeDescription.value = tempTaskTypeDescription.value;
}
},
);
watch(
() => requirement.value,
() => {
@ -94,17 +131,17 @@ watch(
></v-text-field>
</div>
<data-type-btns v-model:value="dataType" :cols="3" :md="2" :sm="2" :xs="2"></data-type-btns>
<task-type-btns v-model:value="taskType" :cols="2" :md="2" :sm="2" :xs="2"></task-type-btns>
<data-type-btns v-model="dataType" :cols="3" :md="2" :sm="2" :xs="2"></data-type-btns>
<task-type-btns v-model="taskType" :cols="2" :md="2" :sm="2" :xs="2"></task-type-btns>
<library-type-btns
v-model:value="libraryType"
v-model="libraryType"
:cols="2"
:md="2"
:sm="2"
:xs="2"
></library-type-btns>
<scenario-list-btns
v-model:value="scenarioList"
v-model="scenarioList"
:cols="2"
:md="2"
:sm="2"
@ -115,13 +152,103 @@ watch(
</div>
<div class="p-4 pt-0 border-t-1 border-gray-300">
<div ref="anchorRef" class="mt-3 mb-5 w-full text-h6 transition-all truncate">
<template v-if="isHeterogeneous">
<div class="mt-3 mb-5 w-full text-h6 transition-all truncate">
<v-icon class="mr-3" icon="mdi-vector-difference" color="black" size="small"></v-icon>
{{ t("Search.UploadHeterogeneousRequirement") }}
</div>
<v-dialog v-model="heterDialog" width="1024">
<template v-slot:activator="{ props }">
<v-card v-bind="props" flat class="border-gray-500 border-2 rounded-lg bg-transparent">
<v-card-text class="text-center text-base md:text-xl">
{{ t("Search.StartHeterogeneousSearch") }}
</v-card-text>
</v-card>
</template>
<v-card class="p-4 md:p-8 md:pt-4">
<div>
<v-tabs v-model="heterTab" align-tabs="center">
<v-tab value="dataType">{{
t("Submit.SemanticSpecification.DataType.DescriptionInput.Name")
}}</v-tab>
<v-tab value="taskType">{{
t("Submit.SemanticSpecification.TaskType.DescriptionOutput.Name")
}}</v-tab>
</v-tabs>
</div>
<v-window v-model="heterTab">
<v-window-item value="dataType">
<div class="flex justify-between">
<div class="text-h4 font-semibold">
{{ t("Submit.SemanticSpecification.DataType.DescriptionInput.Name") }}
</div>
<v-btn
icon="mdi-download"
variant="flat"
@click="
() =>
saveContentToFile(
JSON.stringify(tempDataTypeDescription, undefined, 2),
'text/json',
'dataTypeDescription.json',
)
"
/>
</div>
<description-input
v-model="tempDataTypeDescription"
:name="t('Submit.SemanticSpecification.DataType.DescriptionInput.Name')"
class="mt-4"
>
</description-input>
</v-window-item>
<v-window-item value="taskType">
<div class="flex justify-between">
<div class="mt-4 text-h4 font-semibold">
{{ t("Submit.SemanticSpecification.TaskType.DescriptionOutput.Name") }}
</div>
<v-btn
icon="mdi-download"
variant="flat"
@click="
() =>
saveContentToFile(
JSON.stringify(tempTaskTypeDescription, undefined, 2),
'text/json',
'dataTypeDescription.json',
)
"
/>
</div>
<description-input
v-model="tempTaskTypeDescription"
:name="t('Submit.SemanticSpecification.TaskType.DescriptionOutput.Name')"
class="mt-4"
>
</description-input>
</v-window-item>
</v-window>
<div class="flex justify-end mt-4">
<v-btn color="primary" rounded variant="flat" @click="heterDialog = false">
{{ t("Public.Finish") }}
</v-btn>
</div>
</v-card>
</v-dialog>
</template>
<div class="mt-3 mb-5 w-full text-h6 transition-all truncate">
<v-icon class="mr-3" icon="mdi-upload" color="black" size="small"></v-icon>
{{ t("Search.UploadStatisticalRequirement") }}
</div>
<file-upload
v-model:files="files"
v-model="files"
:height="28"
:tips="t('Submit.File.DragFileHere', { file: 'json' })"
/>
@ -131,7 +258,7 @@ watch(
<style scoped lang="scss">
.filter {
@apply p-2 w-full md:h-full md:overflow-y-scroll sm:px-5;
@apply p-2 w-full md:h-full md:overflow-y-auto sm:px-5;
* {
@apply mt-2;

View File

@ -1,12 +1,11 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useDisplay } from "vuetify";
import { debounce } from "../../utils";
export interface Props {
name: string;
value: {
modelValue: {
Dimension: number;
Description: Record<number, string>;
};
@ -14,29 +13,19 @@ export interface Props {
const { t, locale } = useI18n();
const display = useDisplay();
const emits = defineEmits(["update:modelValue"]);
const emits = defineEmits(["update:value"]);
const props = defineProps({
name: {
type: String,
required: false,
default: "feature",
},
value: {
type: Object,
required: true,
},
});
const props = defineProps<Props>();
const errorMessages = ref<string>("");
const descriptionJSON = ref(props.value);
const descriptionJSON = ref(props.modelValue);
const descriptionArray = ref<(string | null)[]>(
[...new Array(props.value.Dimension)].map((_, idx) => props.value?.Description[idx] || null),
[...new Array(props.modelValue.Dimension)].map(
(_, idx) => props.modelValue?.Description[idx] || null,
),
);
const descriptionString = ref(JSON.stringify(props.value, null, 2));
const descriptionString = ref(JSON.stringify(props.modelValue, null, 2));
const debouncedSetErrorMessages = debounce<string>((val) => {
errorMessages.value = val;
@ -50,7 +39,7 @@ watch(
(_, idx) => newVal.Description[idx] || null,
);
descriptionString.value = JSON.stringify(newVal, null, 2);
emits("update:value", newVal);
emits("update:modelValue", newVal);
},
);
watch(
@ -101,66 +90,85 @@ watch(
</script>
<template>
<div class="mt-3">
<v-alert type="info" color="primary" closable>
<slot v-if="['sm', 'xs'].includes(display.name.value)" name="msg-small" />
<slot v-else name="msg" />
</v-alert>
<v-container class="mt-3">
<v-row>
<v-col cols="12" md="6" class="flex flex-col max-h-[600px]">
<v-virtual-scroll :items="descriptionArray">
<template #default="{ index: idx }">
<v-hover>
<template #default="{ isHovering, props: hoverProps }">
<div v-bind="hoverProps">
<v-text-field
v-model="descriptionArray[idx]"
:label="`${t('Public.Description')}: ${name} ${idx}`"
class="mb-1"
hide-details
>
<template v-if="isHovering" #append-inner>
<v-icon
class="mr-1"
icon="mdi-plus"
@click="() => descriptionArray.splice(idx, 0, null)"
/>
<v-icon
icon="mdi-delete"
@click="
() => (descriptionArray = descriptionArray.filter((_, i) => i !== idx))
"
/>
</template>
</v-text-field>
</div>
</template>
</v-hover>
<div class="grid grid-cols-1 gap-0 md:grid-cols-2 md:gap-3">
<div class="flex flex-col max-h-[600px]">
<v-virtual-scroll v-if="modelValue.Dimension > 7" :items="descriptionArray" class="flex-1">
<template #default="{ index: idx }">
<v-hover>
<template #default="{ isHovering, props: hoverProps }">
<div v-bind="hoverProps">
<v-text-field
v-model="descriptionArray[idx]"
:label="`${t('Public.Description')}: ${name} ${idx}`"
class="mb-1"
hide-details
>
<template v-if="isHovering" #append-inner>
<v-icon
class="mr-1"
icon="mdi-plus"
@click="() => descriptionArray.splice(idx, 0, null)"
/>
<v-icon
icon="mdi-delete"
@click="
() => (descriptionArray = descriptionArray.filter((_, i) => i !== idx))
"
/>
</template>
</v-text-field>
</div>
</template>
</v-virtual-scroll>
<div>
<v-btn
block
variant="flat"
class="mt-1"
@click="descriptionArray = [...descriptionArray, null]"
>
<v-icon size="large" color="#555">mdi-plus</v-icon>
</v-btn>
</div>
</v-col>
<v-col cols="12" md="6" class="max-h-[600px]">
<div class="h-full overflow-y-scroll">
<v-textarea
v-model="descriptionString"
auto-grow
:label="`${name}${locale != 'zh-cn' ? ' ' : ''}${t('Public.Description')}`"
:error-messages="errorMessages"
/>
</div>
</v-col>
</v-row>
</v-container>
</v-hover>
</template>
</v-virtual-scroll>
<template v-else>
<v-hover v-for="(_description, idx) in descriptionArray" :key="idx">
<template #default="{ isHovering, props: hoverProps }">
<div v-bind="hoverProps">
<v-text-field
v-model="descriptionArray[idx]"
:label="`${t('Public.Description')}: ${name} ${idx}`"
class="mb-1"
hide-details
>
<template v-if="isHovering" #append-inner>
<v-icon
class="mr-1"
icon="mdi-plus"
@click="() => descriptionArray.splice(idx, 0, null)"
/>
<v-icon
icon="mdi-delete"
@click="() => (descriptionArray = descriptionArray.filter((_, i) => i !== idx))"
/>
</template>
</v-text-field>
</div>
</template>
</v-hover>
</template>
<div>
<v-btn
block
variant="flat"
class="mt-1"
@click="descriptionArray = [...descriptionArray, null]"
>
<v-icon size="large" color="#555">mdi-plus</v-icon>
</v-btn>
</div>
</div>
<div class="max-h-[600px]">
<div class="h-full overflow-y-auto">
<v-textarea
v-model="descriptionString"
auto-grow
:label="`${name}${locale != 'zh-cn' ? ' ' : ''}${t('Public.Description')}`"
:error-messages="errorMessages"
/>
</div>
</div>
</div>
</template>

View File

@ -2,16 +2,15 @@
import { ref, computed } from "vue";
export interface Props {
files: File[];
modelValue: File[];
tips: string;
errorMessages?: string;
height?: number;
}
const emit = defineEmits(["update:files"]);
const emit = defineEmits(["update:modelValue"]);
const props = withDefaults(defineProps<Props>(), {
files: () => [],
errorMessages: "",
height: 40,
});
@ -43,9 +42,9 @@ const computeFileSize = (byte: number): string => {
};
const files = computed({
get: () => props.files,
get: () => props.modelValue,
set: (val) => {
emit("update:files", val);
emit("update:modelValue", val);
},
});
</script>

View File

@ -71,37 +71,49 @@ const taskTypeDescription = computed({
<v-alert class="w-full max-w-[900px] mx-auto" closable :text="errorMessages" type="error" />
</v-card-actions>
</v-scroll-y-transition>
<data-type-btns v-model:value="dataType" />
<description-input
v-if="dataType === 'Table'"
v-model:value="dataTypeDescription"
:name="t('Submit.SemanticSpecification.DataType.DescriptionInput.Name')"
>
<template #msg>
{{ t("Submit.SemanticSpecification.DataType.DescriptionInput.FeatureTips") }}
</template>
<template #msg-small>
{{ t("Submit.SemanticSpecification.DataType.DescriptionInput.FeatureTipsSmall") }}
</template>
</description-input>
<task-type-btns v-model:value="taskType" />
<description-input
<data-type-btns v-model="dataType" />
<template v-if="dataType === 'Table'">
<v-alert class="mt-3" type="info" color="primary" closable>
<span class="hidden sm:inline">{{
t("Submit.SemanticSpecification.DataType.DescriptionInput.FeatureTips")
}}</span>
<span class="sm:hidden">{{
t("Submit.SemanticSpecification.DataType.DescriptionInput.FeatureTipsSmall")
}}</span>
</v-alert>
<description-input
v-model="dataTypeDescription"
:name="t('Submit.SemanticSpecification.DataType.DescriptionInput.Name')"
class="mt-3"
/>
</template>
<task-type-btns v-model="taskType" />
<template
v-if="
taskType === 'Classification' ||
taskType === 'Regression' ||
taskType === 'Feature Extraction'
"
v-model:value="taskTypeDescription"
:name="t('Submit.SemanticSpecification.TaskType.DescriptionOutput.Name')"
>
<template #msg>
{{ t("Submit.SemanticSpecification.TaskType.DescriptionOutput.LabelTips") }}
</template>
<template #msg-small>
{{ t("Submit.SemanticSpecification.TaskType.DescriptionOutput.LabelTipsSmall") }}
</template>
</description-input>
<library-type-btns v-model:value="libraryType" />
<scenario-list-btns v-model:value="scenarioList" />
<v-alert class="mt-3" type="info" color="primary" closable>
<span class="hidden sm:inline">{{
t("Submit.SemanticSpecification.TaskType.DescriptionOutput.LabelTips")
}}</span>
<span class="sm:hidden">{{
t("Submit.SemanticSpecification.TaskType.DescriptionOutput.LabelTipsSmall")
}}</span>
</v-alert>
<description-input
v-model="taskTypeDescription"
:name="t('Submit.SemanticSpecification.TaskType.DescriptionOutput.Name')"
class="mt-3"
/>
</template>
<library-type-btns v-model="libraryType" />
<scenario-list-btns v-model="scenarioList" />
</div>
</template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch, computed } from "vue";
import { computed } from "vue";
import GridBtns from "./GridBtns.vue";
import AudioBtn from "../../../assets/images/specification/dataType/audio.svg?component";
import VideoBtn from "../../../assets/images/specification/dataType/video.svg?component";
@ -8,39 +8,33 @@ import ImageBtn from "../../../assets/images/specification/dataType/image.svg?co
import TableBtn from "../../../assets/images/specification/dataType/table.svg?component";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
export interface DataTypeProps {
modelValue: string;
cols?: number;
md?: number;
sm?: number;
xs?: number;
}
const emit = defineEmits(["update:value"]);
const props = defineProps({
value: {
type: String,
required: true,
},
cols: {
type: Number,
default: 5,
},
md: {
type: Number,
default: 4,
},
sm: {
type: Number,
default: 4,
},
xs: {
type: Number,
default: 2,
},
const props = withDefaults(defineProps<DataTypeProps>(), {
cols: 5,
md: 4,
sm: 4,
xs: 2,
});
const value = ref(props.value);
const { t } = useI18n();
watch(
() => value.value,
(newVal) => emit("update:value", newVal),
);
const emit = defineEmits(["update:modelValue"]);
const modelValue = computed({
get() {
return props.modelValue;
},
set(val) {
emit("update:modelValue", val);
},
});
const dataTypeBtns = computed(() => [
{
@ -73,7 +67,7 @@ const dataTypeBtns = computed(() => [
<template>
<grid-btns
v-model:value="value"
v-model="modelValue"
:btns="dataTypeBtns"
:title="t('Submit.SemanticSpecification.DataType.DataType')"
:cols="cols"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { computed } from "vue";
import { useDisplay } from "vuetify";
import IconBtn from "./IconBtn.vue";
import type { FunctionalComponent, SVGAttributes } from "vue";
@ -11,7 +11,7 @@ export interface Btn {
}
export interface Props {
value: string;
modelValue: string;
btns: Btn[];
title: string;
cols?: number;
@ -29,9 +29,16 @@ const props = withDefaults(defineProps<Props>(), {
xs: 3,
});
const emit = defineEmits(["update:value"]);
const emit = defineEmits(["update:modelValue"]);
const value = ref(props.value);
const value = computed({
get() {
return props.modelValue;
},
set(val) {
emit("update:modelValue", val);
},
});
const realCols = computed(() => {
let { cols } = props;
@ -48,13 +55,6 @@ const realCols = computed(() => {
function clickBtn(btn: Btn): void {
value.value = btn.value === value.value ? "" : btn.value;
}
watch(
() => value.value,
(newValue) => {
emit("update:value", newValue);
},
);
</script>
<template>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { computed } from "vue";
import GridBtns from "./GridBtns.vue";
import TensorFlowBtn from "../../../assets/images/specification/libraryType/tensorflow.svg?component";
import PyTorchBtn from "../../../assets/images/specification/libraryType/pytorch.svg?component";
@ -7,40 +7,33 @@ import ScikitLearnBtn from "../../../assets/images/specification/libraryType/sci
import OthersBtn from "../../../assets/images/specification/libraryType/others.svg?component";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
export interface LibraryTypeProps {
modelValue: string;
cols?: number;
md?: number;
sm?: number;
xs?: number;
}
const emit = defineEmits(["update:value"]);
const props = defineProps({
value: {
type: String,
required: true,
},
cols: {
type: Number,
default: 4,
},
md: {
type: Number,
default: 4,
},
sm: {
type: Number,
default: 4,
},
xs: {
type: Number,
default: 2,
},
const props = withDefaults(defineProps<LibraryTypeProps>(), {
cols: 4,
md: 4,
sm: 4,
xs: 2,
});
const value = ref(props.value);
const { t } = useI18n();
watch(
() => value.value,
(newVal) => emit("update:value", newVal),
{ deep: true },
);
const emit = defineEmits(["update:modelValue"]);
const modelValue = computed({
get() {
return props.modelValue;
},
set(val) {
emit("update:modelValue", val);
},
});
const libraryTypeBtns = computed(() => [
{
@ -68,7 +61,7 @@ const libraryTypeBtns = computed(() => [
<template>
<grid-btns
v-model:value="value"
v-model="modelValue"
:btns="libraryTypeBtns"
:title="t('Submit.SemanticSpecification.LibraryType.LibraryType')"
:cols="cols"

View File

@ -5,7 +5,7 @@ import { useI18n } from "vue-i18n";
import type { Scenario, ScenarioList } from "@beiming-system/types/learnware";
export interface Props {
value: ScenarioList;
modelValue: ScenarioList;
cols?: number;
md?: number;
sm?: number;
@ -14,7 +14,7 @@ export interface Props {
const { t } = useI18n();
const emit = defineEmits(["update:value"]);
const emit = defineEmits(["update:modelValue"]);
const display = useDisplay();
@ -116,17 +116,19 @@ const items = computed<
},
]);
const allSelected = computed(() => props.value && props.value.length === items.value.length);
const allSelected = computed(
() => props.modelValue && props.modelValue.length === items.value.length,
);
const selections = computed(() => {
if (props.value) {
return props.value.map((s) => items.value.find((item) => item.value === s));
if (props.modelValue) {
return props.modelValue.map((s) => items.value.find((item) => item.value === s));
}
return [];
});
function click(value: Scenario): void {
if (props.value && props.value.includes(value)) {
if (props.modelValue && props.modelValue.includes(value)) {
deleteSelect(value);
} else {
addSelect(value);
@ -134,21 +136,21 @@ function click(value: Scenario): void {
}
function addSelect(value: Scenario): void {
if (props.value) {
emit("update:value", [...props.value, value]);
if (props.modelValue) {
emit("update:modelValue", [...props.modelValue, value]);
} else {
emit("update:value", [value]);
emit("update:modelValue", [value]);
}
}
function deleteSelect(value: Scenario): void {
if (props.value) {
if (props.modelValue) {
emit(
"update:value",
props.value.filter((s) => s !== value),
"update:modelValue",
props.modelValue.filter((s) => s !== value),
);
} else {
emit("update:value", []);
emit("update:modelValue", []);
}
}
</script>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { computed } from "vue";
import GridBtns from "./GridBtns.vue";
import ClassificationBtn from "../../../assets/images/specification/taskType/classification.svg?component";
import ClusteringBtn from "../../../assets/images/specification/taskType/clustering.svg?component";
@ -11,39 +11,33 @@ import RankingBtn from "../../../assets/images/specification/taskType/ranking.sv
import OthersBtn from "../../../assets/images/specification/taskType/others.svg?component";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
export interface TaskTypeProps {
modelValue: string;
cols?: number;
md?: number;
sm?: number;
xs?: number;
}
const emit = defineEmits(["update:value"]);
const props = defineProps({
value: {
type: String,
required: true,
},
cols: {
type: Number,
default: 5,
},
md: {
type: Number,
default: 4,
},
sm: {
type: Number,
default: 4,
},
xs: {
type: Number,
default: 2,
},
const props = withDefaults(defineProps<TaskTypeProps>(), {
cols: 5,
md: 4,
sm: 4,
xs: 2,
});
const value = ref(props.value);
const { t } = useI18n();
watch(
() => value.value,
(newVal) => emit("update:value", newVal),
);
const emit = defineEmits(["update:modelValue"]);
const modelValue = computed({
get() {
return props.modelValue;
},
set(val) {
emit("update:modelValue", val);
},
});
const taskTypeBtns = computed(() => [
{
@ -91,7 +85,7 @@ const taskTypeBtns = computed(() => [
<template>
<grid-btns
v-model:value="value"
v-model="modelValue"
:btns="taskTypeBtns"
:title="t('Submit.SemanticSpecification.TaskType.TaskType')"
:cols="cols"

View File

@ -44,7 +44,7 @@ function searchLearnware({
scenarioList,
files,
isVerified,
heterogeneousMode,
isHeterogeneous,
page,
limit,
}: {
@ -56,7 +56,7 @@ function searchLearnware({
scenarioList: ScenarioList;
files: Files;
isVerified: boolean;
heterogeneousMode: boolean;
isHeterogeneous: boolean;
page: number;
limit: number;
}): Promise<{
@ -85,7 +85,7 @@ function searchLearnware({
}
fd.append("semantic_specification", JSON.stringify(semanticSpec));
fd.append("statistical_specification", (files.length > 0 && files[0]) || "");
fd.append("heterogeneous_mode", String(heterogeneousMode));
fd.append("is_heterogeneous", String(isHeterogeneous));
fd.append("is_verified", String(isVerified));
fd.append("limit", String(limit));
fd.append("page", String(page));

View File

@ -1,5 +1,6 @@
export { downloadLearnwareSync } from "./download.js";
export { hex_md5 } from "./encrypt.js";
export { verifyLearnware } from "./verifyLearnware.js";
export { promiseReadFile } from "./promiseReadFile.js";
export { debounce, throttle } from "./control.js";
export { downloadLearnwareSync } from "./download";
export { hex_md5 } from "./encrypt";
export { verifyLearnware } from "./verifyLearnware";
export { promiseReadFile } from "./promiseReadFile";
export { debounce, throttle } from "./control";
export { default as saveContentToFile } from "./saveContentToFile";

View File

@ -1,5 +1,5 @@
export default function saveContentToFile(content: BlobPart, fileName: string): void {
const blob = new Blob([content], { type: "text/csv;charset=utf-8" });
export default function saveContentToFile(content: BlobPart, type: string, fileName: string): void {
const blob = new Blob([content], { type });
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
document.body.appendChild(link);

View File

@ -43,7 +43,7 @@ const singleRecommendedLearnwarePage = ref(1);
const singleRecommendedLearnwarePageNum = ref(1);
const singleRecommendedLearnwarePageSize = ref(Math.ceil(display.height.value / 900) * 10);
const singleRecommendedLearnwareItems = ref<LearnwareCardInfo[]>([]);
const heterogeneousMode = ref<boolean>(false);
const isHeterogeneous = ref<boolean>(false);
const rkmeTypeTable = ref<boolean>(false);
const loading = ref(false);
const isVerified = ref(route.query.is_verified ? route.query.is_verified === "true" : true);
@ -82,7 +82,7 @@ function fetchByFilterAndPage(
filters: Filter,
page: number,
isVerified: boolean = false,
heterogeneousMode: boolean = false,
isHeterogeneous: boolean = false,
): void {
showError.value = false;
loading.value = true;
@ -96,7 +96,7 @@ function fetchByFilterAndPage(
scenarioList: filters.scenarioList,
files: filters.files,
isVerified,
heterogeneousMode,
isHeterogeneous,
page,
limit: singleRecommendedLearnwarePageSize.value,
})
@ -233,17 +233,17 @@ watch(
filters.value,
singleRecommendedLearnwarePage.value,
isVerified.value,
heterogeneousMode.value,
isHeterogeneous.value,
],
(newVal) => {
const [newFilters, newPage, newIsVerified, newHeterogeneousMode] = newVal as [
const [newFilters, newPage, newIsVerified, newIsHeterogeneous] = newVal as [
Filter,
number,
boolean,
boolean,
];
fetchByFilterAndPage(newFilters, newPage - 1, newIsVerified, newHeterogeneousMode);
fetchByFilterAndPage(newFilters, newPage - 1, newIsVerified, newIsHeterogeneous);
},
{ deep: true },
);
@ -257,7 +257,7 @@ watch(
.then((res: ArrayBuffer) => new TextDecoder("ascii").decode(res))
.then((res) => JSON.parse(res))
.then((res) => {
if (res.type === "table") {
if (res.type === "RKMETableSpecification") {
rkmeTypeTable.value = true;
}
})
@ -286,7 +286,7 @@ function init(): void {
filters.value,
singleRecommendedLearnwarePage.value - 1,
isVerified.value,
heterogeneousMode.value,
isHeterogeneous.value,
);
}
@ -295,16 +295,16 @@ onMounted(() => init());
<template>
<div class="mx-auto w-full lg:flex">
<v-scroll-y-transition class="fixed w-full z-index-10">
<v-card-actions v-if="showError">
<v-scroll-y-transition>
<div v-if="showError" class="fixed w-full z-10">
<v-alert
class="w-full max-w-[900px] mx-auto"
class="max-w-[900px] mx-auto z-10"
closable
:text="errorMsg"
type="error"
@click:close="showError = false"
/>
</v-card-actions>
</div>
</v-scroll-y-transition>
<div class="w-full lg:max-w-[460px]">
@ -313,14 +313,15 @@ onMounted(() => init());
class="bottom-0 w-full lg:fixed lg:max-w-[460px]"
style="top: var(--v-layout-top)"
:is-admin="isAdmin"
:is-heterogeneous="isHeterogeneous"
>
<template #prepend>
<v-btn
v-if="heterogeneousMode"
v-if="isHeterogeneous"
block
variant="outlined"
color="red"
@click="() => (heterogeneousMode = false)"
@click="() => (isHeterogeneous = false)"
>
{{ t("Search.TurnOffHeterogeneousSearch") }}
</v-btn>
@ -395,13 +396,8 @@ onMounted(() => init());
/>
</v-card>
<div v-if="showHeterogeneousSearchSwitch && !heterogeneousMode" class="text-center">
<v-btn
class="px-8"
variant="outlined"
color="red"
@click="() => (heterogeneousMode = true)"
>
<div v-if="showHeterogeneousSearchSwitch && !isHeterogeneous" class="text-center">
<v-btn class="px-8" variant="outlined" color="red" @click="() => (isHeterogeneous = true)">
{{ t("Search.HeterogeneousSearch") }}
</v-btn>
</div>

View File

@ -462,7 +462,7 @@ onActivated(init);
<v-window-item :value="3">
<div class="p-4 m-auto">
<file-upload
v-model:files="files.value"
v-model="files.value"
:error-messages="files.errorMessages"
:tips="t('Submit.File.DragFileHere', { file: 'zip' })"
class="text-xl"