Compare commits
No commits in common. "ecddca00c9bd20d8c2008d82c6f94d1f29afd868" and "838939855ee4e59f64bd8881be415f3f99436f6d" have entirely different histories.
ecddca00c9
...
838939855e
@ -16,7 +16,7 @@ VITE_ENABLE_ANALYZE=true
|
|||||||
VITE_ENABLE_CONFIG_GENERATE=true
|
VITE_ENABLE_CONFIG_GENERATE=true
|
||||||
|
|
||||||
# 当前站点URL
|
# 当前站点URL
|
||||||
VITE_GLOB_ORIGIN=https://tk.astraltwin.cn/
|
VITE_GLOB_ORIGIN=https://editor.astraljs.com/
|
||||||
|
|
||||||
#ws接口
|
#ws接口
|
||||||
VITE_GLOB_SOCKET_URL=wss://tk.astraltwin.cn/socket
|
VITE_GLOB_SOCKET_URL=wss://editor.astraljs.com/socket
|
||||||
@ -3,7 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
|
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
import * as monaco from 'monaco-editor';
|
import * as monaco from 'monaco-editor';
|
||||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
||||||
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
||||||
@ -74,17 +74,6 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.source,
|
|
||||||
value => {
|
|
||||||
if (!editor) return;
|
|
||||||
const currentValue = editor.getValue();
|
|
||||||
if (value !== currentValue) {
|
|
||||||
editor.setValue(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
async function initMonaco() {
|
async function initMonaco() {
|
||||||
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||||||
noSemanticValidation: false,
|
noSemanticValidation: false,
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
import {request} from "@/http/request";
|
|
||||||
|
|
||||||
export interface DataSetGroupPayload {
|
|
||||||
id?: IDataSet.IGroup["id"];
|
|
||||||
name: string;
|
|
||||||
pid: IDataSet.IGroup["pid"];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchDataSetGroupTree() {
|
|
||||||
return request.get<IDataSet.IGroup[]>(`/data-set-group/tree`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchCreateDataSetGroup(data: DataSetGroupPayload) {
|
|
||||||
return request.post(`/data-set-group`, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchUpdateDataSetGroup(data: DataSetGroupPayload) {
|
|
||||||
return request.put(`/data-set-group`, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchDeleteDataSetGroup(id: IDataSet.IGroup["id"]) {
|
|
||||||
return request.delete(`/data-set-group/${id}`, {});
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import {request} from "@/http/request";
|
|
||||||
|
|
||||||
export interface DataSourcePayload {
|
|
||||||
id?: IDataSource.Item["id"];
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
connectionString: string;
|
|
||||||
username?: string;
|
|
||||||
password?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchDataSourceList() {
|
|
||||||
return request.get<IDataSource.Item[]>(`/data-source/list`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchCreateDataSource(data: DataSourcePayload) {
|
|
||||||
return request.post<IDataSource.Item>(`/data-source`, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchUpdateDataSource(data: DataSourcePayload) {
|
|
||||||
return request.put<IDataSource.Item>(`/data-source`, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchDeleteDataSource(id: IDataSource.Item["id"]) {
|
|
||||||
return request.delete(`/data-source/${id}`, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchTestDataSource(data: DataSourcePayload) {
|
|
||||||
return request.post(`/data-source/test`, data);
|
|
||||||
}
|
|
||||||
@ -27,10 +27,9 @@ async function main() {
|
|||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|
||||||
if (import.meta.env.PROD){
|
if (import.meta.env.PROD){
|
||||||
// DisableDevtool({
|
DisableDevtool({
|
||||||
// md5: "32981a13284db7a021131df49e6cd203",
|
clearLog:true
|
||||||
// clearLog: true,
|
});
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,16 +4,20 @@
|
|||||||
<SceneTree />
|
<SceneTree />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<n-tabs default-value="cad" animated type="line" justify-content="space-around" class="!h-60%"
|
<n-tabs default-value="bim" animated type="line" justify-content="space-around" class="!h-60%"
|
||||||
pane-wrapper-class="layout-assets-tab-pane-wrapper">
|
pane-wrapper-class="layout-assets-tab-pane-wrapper">
|
||||||
<n-tab-pane name="cad" tab="CAD" display-directive="show">
|
<n-tab-pane name="cad" tab="CAD" display-directive="show">
|
||||||
<CadLibrary />
|
<CadLibrary />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="bim" tab="BIM" display-directive="show">
|
||||||
|
<BIMLibrary />
|
||||||
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import BIMLibrary from "./assets/BIMLibrary.vue";
|
||||||
import SceneTree from "@/components/tree/SceneTree.vue";
|
import SceneTree from "@/components/tree/SceneTree.vue";
|
||||||
import CadLibrary from "./assets/CadLibrary.vue";
|
import CadLibrary from "./assets/CadLibrary.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
339
packages/editor/src/views/editor/layouts/assets/BIMLibrary.vue
Normal file
339
packages/editor/src/views/editor/layouts/assets/BIMLibrary.vue
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
<template>
|
||||||
|
<div id="bim-library" class="h-full flex flex-col">
|
||||||
|
<n-alert type="info" :show-icon="false" :bordered="false" class="mb-2 mx-2">
|
||||||
|
<n-button quaternary round type="primary" @click="showHistoryModal = true">
|
||||||
|
{{ t("layout.sider.History") }}
|
||||||
|
</n-button>
|
||||||
|
|
||||||
|
<n-button quaternary circle type="primary" @click="showBIMUpload = true">
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<CloudUpload/>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</n-alert>
|
||||||
|
|
||||||
|
<div class="flex-1 overflow-y-auto px-0.3rem">
|
||||||
|
<div class="grid grid-cols-[repeat(auto-fill,minmax(80px,1fr))] gap-2">
|
||||||
|
<n-card size="small" hoverable v-for="item in objectList" :key="item.id" @dblclick="addToScene(item)"
|
||||||
|
draggable="true" @dragstart="dragStart($event, item)" @dragend="dragEnd">
|
||||||
|
<template #cover>
|
||||||
|
<n-spin :show="item.conversionStatus === 0">
|
||||||
|
<template #description>
|
||||||
|
正在轻量化...
|
||||||
|
</template>
|
||||||
|
<img :src="item.thumbnail ? item.thumbnail : '/static/images/placeholder/占位图.png'"
|
||||||
|
:alt="item.fileName" draggable="false">
|
||||||
|
<n-tag :color="{ color: '#F1C3CC', textColor: '#D03050' }" :bordered="false" size="small"
|
||||||
|
class="absolute top-33px w-full" v-if="item.conversionStatus === 2">
|
||||||
|
轻量化失败
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<CloseCircleSharp/>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-tag>
|
||||||
|
</n-spin>
|
||||||
|
</template>
|
||||||
|
<n-tooltip placement="bottom" trigger="hover">
|
||||||
|
<template #trigger> {{ item.fileName }}</template>
|
||||||
|
<span> {{ item.fileName }} </span>
|
||||||
|
</n-tooltip>
|
||||||
|
</n-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center mt-0.5rem">
|
||||||
|
<n-pagination v-bind="paginationReactive"></n-pagination>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<n-modal v-model:show="showHistoryModal" class="!w-60vw" preset="dialog" display-directive="show"
|
||||||
|
:title="t('bim[\'BIM lightweight\']') + t('layout.sider.History')">
|
||||||
|
<n-data-table class="mt-20px" size="small" :loading="tableLoading" :columns="columns"
|
||||||
|
:data="objectList"></n-data-table>
|
||||||
|
<div class="flex justify-end mt-0.5rem">
|
||||||
|
<n-pagination v-bind="paginationReactive"></n-pagination>
|
||||||
|
</div>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- BIM文件上传 -->
|
||||||
|
<BimUploadDialog v-model:show="showBIMUpload" ref="uploadDialogRef"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {ref, reactive, onMounted, h, onBeforeUnmount} from "vue";
|
||||||
|
import type {DataTableColumns} from 'naive-ui';
|
||||||
|
import {NButton, NTag, NIcon} from 'naive-ui'
|
||||||
|
import {CloudUpload, Reload, CheckmarkCircle, CloseOutline, CloseCircleSharp} from '@vicons/ionicons5';
|
||||||
|
import {t} from "@/language";
|
||||||
|
import {useDragStore} from "@/store/modules/drag";
|
||||||
|
import {fetchGetBim2GltfList} from "@/http/api/bim";
|
||||||
|
import {Bim2GltfWsData, WebSocketMessage} from "~/network";
|
||||||
|
import {Loader,Hooks} from "@astral3d/engine";
|
||||||
|
import {filterSize} from "@/utils/common/file";
|
||||||
|
import {onWebSocket, offWebSocket} from "@/hooks/useWebSocket";
|
||||||
|
import {useWebsocketStore} from "@/store/modules/websocket";
|
||||||
|
import {dateTimeFormat} from "@/utils/common/dateTime";
|
||||||
|
import BimUploadDialog from "./bimLibrary/BimUploadDialog.vue";
|
||||||
|
|
||||||
|
const websocketStore = useWebsocketStore();
|
||||||
|
|
||||||
|
let objectList = ref<IBIMData[]>([]);
|
||||||
|
const showHistoryModal = ref(false);
|
||||||
|
const tableLoading = ref(false);
|
||||||
|
const columns: DataTableColumns<IBIMData> = [
|
||||||
|
{
|
||||||
|
title: '文件名',
|
||||||
|
key: 'fileName'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'bim文件体积',
|
||||||
|
key: 'bimFileSize',
|
||||||
|
render(row) {
|
||||||
|
return filterSize(row.bimFileSize);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'gltf文件体积',
|
||||||
|
key: 'gltfFileSize',
|
||||||
|
render(row) {
|
||||||
|
return filterSize(row.gltfFileSize);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '转换时长',
|
||||||
|
key: 'conversionDuration',
|
||||||
|
render(row) {
|
||||||
|
return row.conversionDuration.toFixed(2) + "s";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'conversionStatus',
|
||||||
|
render(row) {
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
bordered: false,
|
||||||
|
type: row.conversionStatus === 0 ? "warning" : row.conversionStatus === 1 ? "success" : "error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => row.conversionStatus === 0 ? "转换中" : row.conversionStatus === 1 ? "成功" : "失败",
|
||||||
|
icon: () => h(
|
||||||
|
NIcon, {
|
||||||
|
component: row.conversionStatus === 0 ? Reload : row.conversionStatus === 1 ? CheckmarkCircle : CloseOutline
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
render(row) {
|
||||||
|
if (row.conversionStatus !== 1) return "";
|
||||||
|
return h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
size: 'small',
|
||||||
|
onClick: () => addToScene(row)
|
||||||
|
},
|
||||||
|
{default: () => t("other.Load")}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
let paginationReactive = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
pageCount: 1,
|
||||||
|
"on-update:page": (page: number) => {
|
||||||
|
paginationReactive.page = page;
|
||||||
|
getBim2GltfList();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const showBIMUpload = ref(false);
|
||||||
|
const uploadDialogRef = ref();
|
||||||
|
|
||||||
|
// 获取BIM转换列表
|
||||||
|
async function getBim2GltfList() {
|
||||||
|
const res = await fetchGetBim2GltfList({
|
||||||
|
offset: (paginationReactive.page - 1) * paginationReactive.pageSize,
|
||||||
|
limit: paginationReactive.pageSize
|
||||||
|
});
|
||||||
|
|
||||||
|
objectList.value = res.data?.items || [];
|
||||||
|
paginationReactive.pageCount = res.data?.pages || 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addToScene(item) {
|
||||||
|
showHistoryModal.value = false;
|
||||||
|
|
||||||
|
let notice = window.$notification.info({
|
||||||
|
title: window.$t("scene['Get the scene data']") + "...",
|
||||||
|
content: window.$t("other.Loading") + "...",
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 从gltfFilePath字段去服务器获取gltf文件
|
||||||
|
fetch(`file/static/${item.gltfFilePath}`)
|
||||||
|
.then(res => res.blob())
|
||||||
|
.then(data => {
|
||||||
|
const file = new File([data as Blob], `${item.fileName}.glb`, {type: 'model/gltf-binary'});
|
||||||
|
|
||||||
|
notice.content = window.$t("scene['Parsing to editor']");
|
||||||
|
|
||||||
|
Loader.loadFiles([file], undefined).finally(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
notice.destroy();
|
||||||
|
Hooks.useDispatchSignal("sceneGraphChanged");
|
||||||
|
}, 800)
|
||||||
|
Hooks.useDispatchSignal("sceneGraphChanged");
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
notice.content = window.$t("scene['Failed to get scene data']");
|
||||||
|
setTimeout(() => {
|
||||||
|
notice.destroy();
|
||||||
|
}, 500)
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始拖拽事件
|
||||||
|
const dragStore = useDragStore();
|
||||||
|
|
||||||
|
function dragStart(_, item) {
|
||||||
|
if (item.conversionStatus !== 1) return;
|
||||||
|
dragStore.setData(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragEnd() {
|
||||||
|
if (dragStore.getActionTarget !== "addToScene") return;
|
||||||
|
|
||||||
|
const data = dragStore.getData
|
||||||
|
if (data.conversionStatus !== 1) return;
|
||||||
|
|
||||||
|
addToScene(data);
|
||||||
|
|
||||||
|
dragStore.setActionTarget("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// websocket监听 bim2gltf 消息
|
||||||
|
function Bim2GltfWsHandle(data: WebSocketMessage<Bim2GltfWsData>) {
|
||||||
|
if (data.type === "bim2gltf") {
|
||||||
|
// 判断订阅者,以确定是自己还是别人轻量化的结果
|
||||||
|
if (data.subscriber === websocketStore.uname) {
|
||||||
|
let wsNotice = uploadDialogRef.value?.getNotice();
|
||||||
|
// 如果是自己的轻量化结果,使用用户登录结果作为uname,则用户可能关闭页面再打开时接收到消息,应继续显示
|
||||||
|
if (!wsNotice) {
|
||||||
|
wsNotice = window.$notification.info({
|
||||||
|
title: t("bim['BIM lightweight']"),
|
||||||
|
content: "",
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.data.conversionStatus === "progress") {
|
||||||
|
wsNotice.content = t("bim['BIM lightweight is in progress']") + "...";
|
||||||
|
} else if (data.data.conversionStatus === "completed") {
|
||||||
|
wsNotice.content = `${t("bim['BIM lightweight completed']")},${t("bim.In")} ${data.data.item.conversionDuration} ${t("bim.seconds")}`;
|
||||||
|
setTimeout(() => {
|
||||||
|
wsNotice?.destroy();
|
||||||
|
window.$dialog.info({
|
||||||
|
title: t("bim['BIM lightweight completed']"),
|
||||||
|
content: t("bim['Whether to load the BIM model into the scene?']"),
|
||||||
|
positiveText: window.$t('other.Load'),
|
||||||
|
negativeText: window.$t('other.Cancel'),
|
||||||
|
onPositiveClick: () => {
|
||||||
|
addToScene(data.data.item);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 800)
|
||||||
|
getBim2GltfList();
|
||||||
|
} else if (data.data.conversionStatus === "failed") {
|
||||||
|
wsNotice.content = t("bim['BIM lightweight failed']");
|
||||||
|
setTimeout(() => {
|
||||||
|
wsNotice?.destroy();
|
||||||
|
}, 1500)
|
||||||
|
getBim2GltfList();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (data.data.conversionStatus !== "completed") return;
|
||||||
|
|
||||||
|
// 别人的轻量化结果
|
||||||
|
const n = window.$notification.info({
|
||||||
|
title: t("bim['BIM lightweight']"),
|
||||||
|
content: t("bim['New lightweight BIM model received, do you want to view it?']"),
|
||||||
|
duration: 5000,
|
||||||
|
closable: true,
|
||||||
|
meta: dateTimeFormat("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
action: () =>
|
||||||
|
h(NButton, {
|
||||||
|
text: true,
|
||||||
|
type: 'primary',
|
||||||
|
onClick: () => {
|
||||||
|
addToScene(data.data.item);
|
||||||
|
n.destroy();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => t("other.Load")
|
||||||
|
}
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getBim2GltfList();
|
||||||
|
|
||||||
|
// 注册websocket消息监听
|
||||||
|
onWebSocket(Bim2GltfWsHandle);
|
||||||
|
})
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
offWebSocket(Bim2GltfWsHandle)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
#bim-library {
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
.n-alert {
|
||||||
|
:deep(.n-alert-body) {
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-card {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
:deep(.n-card-cover) {
|
||||||
|
img {
|
||||||
|
height: 89px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-card__content) {
|
||||||
|
padding: 3px 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,277 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal :show="show" @update:show="(b) => emits('update:show',b)" class="!w-500px" preset="card" :title="t('bim.BIM lightweight')">
|
||||||
|
<n-form label-placement="left" :model="BIMModel" :rules="BIMRules"
|
||||||
|
label-width="100px" label-align="right" ref="formRef"
|
||||||
|
require-mark-placement="right-hanging">
|
||||||
|
<!-- <n-form-item :label="t('bim[\'File name\']')" path="fileName">-->
|
||||||
|
<!-- <n-input v-model:value="BIMModel.fileName"-->
|
||||||
|
<!-- :placeholder="t('bim[\'Please enter the BIM file name\']')"/>-->
|
||||||
|
<!-- </n-form-item>-->
|
||||||
|
<n-form-item :label="t('bim.Thumbnail')">
|
||||||
|
<n-upload :default-upload="false" list-type="image-card" :max="1"
|
||||||
|
@change="thumbnailChange"/>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item :label="t('bim[\'BIM file\']')" path="bimFile">
|
||||||
|
<n-upload ref="uploadBIMRef" :default-upload="false" directory-dnd :max="1"
|
||||||
|
:accept="'.' + NEED_CONVERT_BIM_MODEL.join(',.')" @change="bimChange">
|
||||||
|
<n-upload-dragger>
|
||||||
|
<div>
|
||||||
|
<n-icon size="48" :depth="3">
|
||||||
|
<ArchiveOutline/>
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
|
<n-text style="font-size: 14px">
|
||||||
|
{{
|
||||||
|
t("bim['Click or drag the file to this area.Supported formats are:']") + ` ${NEED_CONVERT_BIM_MODEL.join("、")}`
|
||||||
|
}}
|
||||||
|
</n-text>
|
||||||
|
</n-upload-dragger>
|
||||||
|
</n-upload>
|
||||||
|
</n-form-item>
|
||||||
|
<n-collapse>
|
||||||
|
<template #arrow>
|
||||||
|
<n-icon>
|
||||||
|
<CaretForwardOutline />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<n-collapse-item :title="t('bim[\'Conversion configuration\']')">
|
||||||
|
<!-- 极致轻量化 -->
|
||||||
|
<n-form-item :label="t('bim[\'Extreme lightweight\']')">
|
||||||
|
<n-switch v-model:value="BIMModel.options.Optimize" @change="handleOptimizeChange" />
|
||||||
|
</n-form-item>
|
||||||
|
<!-- 导出属性 -->
|
||||||
|
<n-form-item :label="t('bim[\'Export Property\']')">
|
||||||
|
<n-switch v-model:value="BIMModel.options.ExportProperty" :disabled="exportPropertyDisabled" />
|
||||||
|
</n-form-item>
|
||||||
|
<!-- 转换视图 -->
|
||||||
|
<n-form-item :label="t('bim[\'Conversion view\']')">
|
||||||
|
<n-radio-group v-model:value="BIMModel.options.View" name="View">
|
||||||
|
<n-radio-button value="Default">默认3D视图</n-radio-button>
|
||||||
|
<n-radio-button value="Name">按名称</n-radio-button>
|
||||||
|
</n-radio-group>
|
||||||
|
</n-form-item>
|
||||||
|
<!-- 视图名称 -->
|
||||||
|
<n-form-item :label="t('bim[\'View name\']')" v-if="BIMModel.options.View === 'Name'">
|
||||||
|
<n-input v-model:value="BIMModel.options.ViewName" :placeholder="t('bim[\'Please enter the conversion view name\']')"/>
|
||||||
|
</n-form-item>
|
||||||
|
<!-- 视觉样式 -->
|
||||||
|
<n-form-item :label="t('bim[\'Display style\']')">
|
||||||
|
<n-radio-group v-model:value="BIMModel.options.DisplayStyle" name="DisplayStyle">
|
||||||
|
<n-radio-button value="Colour">{{t("bim.Colour")}}</n-radio-button>
|
||||||
|
<n-radio-button value="Realistic">{{t("bim.Realistic")}}</n-radio-button>
|
||||||
|
<n-radio-button value="ViewDefault">{{t("bim['View default']")}}</n-radio-button>
|
||||||
|
</n-radio-group>
|
||||||
|
</n-form-item>
|
||||||
|
<!-- 坐标参考 -->
|
||||||
|
<n-form-item :label="t('bim[\'Coordinate reference\']')">
|
||||||
|
<n-radio-group v-model:value="BIMModel.options.CoordinateReference" name="CoordinateReference">
|
||||||
|
<n-radio-button value="Origin">{{t("bim.Origin")}}</n-radio-button>
|
||||||
|
<n-radio-button value="ProjectBasePoint">{{t("bim['Project base point']")}}</n-radio-button>
|
||||||
|
<n-radio-button value="MeasuringPoint">{{t("bim['Measuring point']")}}</n-radio-button>
|
||||||
|
</n-radio-group>
|
||||||
|
</n-form-item>
|
||||||
|
</n-collapse-item>
|
||||||
|
</n-collapse>
|
||||||
|
</n-form>
|
||||||
|
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<n-button round type="primary" @click="submit">{{ t("bim['Upload and lightweight']") }}</n-button>
|
||||||
|
</div>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {reactive, ref} from "vue";
|
||||||
|
import {NotificationReactive} from "naive-ui";
|
||||||
|
import {t} from "@/language";
|
||||||
|
import {ArchiveOutline, CaretForwardOutline} from "@vicons/ionicons5";
|
||||||
|
import {fetchUpload} from "@/http/api/sys";
|
||||||
|
import {fetchAddBim2Gltf, fetchUploadRvt} from "@/http/api/bim";
|
||||||
|
import {NEED_CONVERT_BIM_MODEL} from "@/utils/common/constant";
|
||||||
|
|
||||||
|
withDefaults(defineProps<{
|
||||||
|
show:boolean
|
||||||
|
}>(),{
|
||||||
|
show:false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(["update:show"]);
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
const uploadBIMRef = ref();
|
||||||
|
const exportPropertyDisabled = ref(false);
|
||||||
|
|
||||||
|
/* bim文件上传转换 */
|
||||||
|
const BIMModel = reactive<{
|
||||||
|
fileName: string,
|
||||||
|
thumbnail: File | null,
|
||||||
|
bimFile: File | null,
|
||||||
|
options:{
|
||||||
|
// 极致压缩率(会执行实例化网格,破坏模型结构)
|
||||||
|
Optimize:boolean,
|
||||||
|
// 是否导出属性
|
||||||
|
ExportProperty:boolean,
|
||||||
|
// 转换视图 - Default:默认3D视图 | Name:按传入名称查找视图
|
||||||
|
View:string,
|
||||||
|
// 转换视图名称 - View = "Name"时输入有效
|
||||||
|
ViewName:string,
|
||||||
|
// 转换视觉样式 - Colour:着色模式 | Realistic-真实模式 | ViewDefault-视图默认
|
||||||
|
DisplayStyle:string,
|
||||||
|
// 坐标参考 - Origin:默认原点 | ProjectBasePoint:项目基点 | MeasuringPoint:测量点
|
||||||
|
CoordinateReference:string
|
||||||
|
}
|
||||||
|
}>({
|
||||||
|
fileName: "",
|
||||||
|
thumbnail: null,
|
||||||
|
bimFile: null,
|
||||||
|
options:{
|
||||||
|
Optimize:false,
|
||||||
|
ExportProperty:true,
|
||||||
|
View:"Default",
|
||||||
|
ViewName:"",
|
||||||
|
DisplayStyle:"Colour",
|
||||||
|
CoordinateReference:"Origin"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const BIMRules = {
|
||||||
|
bimFile: {
|
||||||
|
required: true,
|
||||||
|
trigger: 'change',
|
||||||
|
validator: (_, value) => {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
// 判断value是否是File类型
|
||||||
|
if (value === '' || !(value instanceof File)) {
|
||||||
|
reject(Error(t("bim['Please upload the BIM file']")))
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预览图变化
|
||||||
|
function thumbnailChange({file}) {
|
||||||
|
if (file.status === "removed") {
|
||||||
|
BIMModel.thumbnail = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BIMModel.thumbnail = file.file as File;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bim文件选择变化
|
||||||
|
function bimChange({file}) {
|
||||||
|
if (file.status === "removed") {
|
||||||
|
BIMModel.bimFile = null;
|
||||||
|
formRef.value?.validate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NEED_CONVERT_BIM_MODEL.includes(file.name.split(".").at(-1).toLowerCase())) {
|
||||||
|
window.$message?.error(t("prompt['This format is not supported, please upload again! Supported formats are:']") + ` ${NEED_CONVERT_BIM_MODEL.join("、")}`);
|
||||||
|
uploadBIMRef.value.clear();
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
BIMModel.bimFile = file.file as File;
|
||||||
|
BIMModel.fileName = BIMModel.bimFile.name;
|
||||||
|
formRef.value?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 极致轻量化选项变化(导出属性选项跟随变化)
|
||||||
|
function handleOptimizeChange() {
|
||||||
|
if(BIMModel.options.Optimize){
|
||||||
|
BIMModel.options.ExportProperty = false;
|
||||||
|
exportPropertyDisabled.value = true;
|
||||||
|
}else{
|
||||||
|
exportPropertyDisabled.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ws 监听使用的notice
|
||||||
|
let wsNotice: null | NotificationReactive = null;
|
||||||
|
const getNotice = () => wsNotice;
|
||||||
|
// 提交
|
||||||
|
function submit(e) {
|
||||||
|
if (import.meta.env.PROD) {
|
||||||
|
window.$message?.error(window.$t("prompt['Disable this function in the demonstration environment!']"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
formRef.value?.validate(async (errors) => {
|
||||||
|
if (!errors) {
|
||||||
|
emits("update:show",false);
|
||||||
|
|
||||||
|
wsNotice = window.$notification.info({
|
||||||
|
title: t("bim['BIM lightweight']"),
|
||||||
|
content: t("prompt.Uploading") + "...",
|
||||||
|
closable: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 1. 上传缩略图
|
||||||
|
let thumbnail = "";
|
||||||
|
if (BIMModel.thumbnail) {
|
||||||
|
const res = await fetchUpload({
|
||||||
|
file: BIMModel.thumbnail,
|
||||||
|
biz: "upload/bim/thumbnail"
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.data === null) {
|
||||||
|
window.$message?.error(t("bim['Failed to upload thumbnail']"));
|
||||||
|
} else {
|
||||||
|
thumbnail = res.data as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 上传bim文件
|
||||||
|
const bimRes = await fetchUploadRvt({
|
||||||
|
file: BIMModel.bimFile,
|
||||||
|
});
|
||||||
|
if (bimRes.data === null) {
|
||||||
|
window.$message?.error(t("bim['Failed to upload BIM file']"));
|
||||||
|
wsNotice.content = `${t("bim['Failed to upload BIM file']")},${t("prompt['Please try again later!']")}`;
|
||||||
|
setTimeout(() => {
|
||||||
|
wsNotice?.destroy();
|
||||||
|
}, 1000)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//3 启动Revit轻量化服务
|
||||||
|
wsNotice.content = t("bim['BIM lightweight is in progress']") + "...";
|
||||||
|
await fetchAddBim2Gltf({
|
||||||
|
bimFilePath: bimRes.data,
|
||||||
|
bimFileSize: (BIMModel.bimFile as File).size,
|
||||||
|
fileName: BIMModel.fileName,
|
||||||
|
fileSourceIp: "",
|
||||||
|
thumbnail,
|
||||||
|
conversionStatus: 0,
|
||||||
|
// 转换配置
|
||||||
|
options:BIMModel.options
|
||||||
|
})
|
||||||
|
|
||||||
|
// reset
|
||||||
|
BIMModel.fileName = "";
|
||||||
|
BIMModel.thumbnail = null;
|
||||||
|
BIMModel.bimFile = null;
|
||||||
|
BIMModel.options = {
|
||||||
|
Optimize:false,
|
||||||
|
ExportProperty:true,
|
||||||
|
View:"Default",
|
||||||
|
ViewName:"",
|
||||||
|
DisplayStyle:"Colour",
|
||||||
|
CoordinateReference:"Origin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({getNotice})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.n-form-item{
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<n-card v-if="visible" class="absolute top-40px right-1 max-w-300px" content-style="padding: 5px 10px;">
|
||||||
|
<n-collapse accordion default-expanded-names="bim">
|
||||||
|
<template #arrow="{ collapsed }">
|
||||||
|
<n-icon v-if="collapsed">
|
||||||
|
<PlanetOutline/>
|
||||||
|
</n-icon>
|
||||||
|
<n-icon v-else>
|
||||||
|
<SunnyOutline/>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<n-collapse-item title=" BIM 构件信息" name="bim">
|
||||||
|
<div class="max-h-360px overflow-y-auto">
|
||||||
|
<n-descriptions v-for="(item, index) in info.parameters" :key="index" label-placement="left"
|
||||||
|
bordered size="small" :column="1">
|
||||||
|
<template #header>
|
||||||
|
<p class="py-2">{{ item.GroupName }}</p>
|
||||||
|
</template>
|
||||||
|
<n-descriptions-item v-for="(it, i) in item.Parameters" :key="i + it.name" :label="it.name">
|
||||||
|
{{ it.value }}
|
||||||
|
</n-descriptions-item>
|
||||||
|
</n-descriptions>
|
||||||
|
</div>
|
||||||
|
</n-collapse-item>
|
||||||
|
</n-collapse>
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {PlanetOutline, SunnyOutline} from "@vicons/ionicons5";
|
||||||
|
import {ref, onBeforeUnmount, onMounted} from "vue";
|
||||||
|
import {Hooks} from "@astral3d/engine";
|
||||||
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const info = ref({
|
||||||
|
parameters: [{GroupName: "", Parameters: [{name: "", value: ""}]}]
|
||||||
|
})
|
||||||
|
|
||||||
|
function objectSelected(object) {
|
||||||
|
if (!object) {
|
||||||
|
visible.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!object.userData.BIM) {
|
||||||
|
if (!object.parent || !object.parent.userData.BIM) {
|
||||||
|
visible.value = false;
|
||||||
|
} else {
|
||||||
|
visible.value = true;
|
||||||
|
info.value = object.parent.userData.BIM;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
visible.value = true;
|
||||||
|
info.value = object.userData.BIM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
Hooks.useAddSignal("objectSelected", objectSelected);
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
Hooks.useRemoveSignal("objectSelected", objectSelected);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@ -6,6 +6,9 @@
|
|||||||
<ViewportInfo/>
|
<ViewportInfo/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- RVT BIM 构件信息悬浮框 -->
|
||||||
|
<BIMProperties/>
|
||||||
|
|
||||||
<!-- IFC BIM 构件信息悬浮框 -->
|
<!-- IFC BIM 构件信息悬浮框 -->
|
||||||
<IFCProperties/>
|
<IFCProperties/>
|
||||||
|
|
||||||
@ -23,6 +26,7 @@ import {usePluginStore} from "@/store/modules/plugin";
|
|||||||
import {installBuiltinPlugin} from "@/plugin";
|
import {installBuiltinPlugin} from "@/plugin";
|
||||||
import { clearBuffer } from "@/utils/wasm/optimize";
|
import { clearBuffer } from "@/utils/wasm/optimize";
|
||||||
import ViewportInfo from "./ViewportInfo.vue";
|
import ViewportInfo from "./ViewportInfo.vue";
|
||||||
|
import BIMProperties from "./BIMProperties.vue";
|
||||||
import IFCProperties from "./IFCProperties.vue";
|
import IFCProperties from "./IFCProperties.vue";
|
||||||
|
|
||||||
const globalStore = useGlobalConfigStore();
|
const globalStore = useGlobalConfigStore();
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
</n-split> -->
|
</n-split> -->
|
||||||
|
|
||||||
<n-flex class="h-full w-full">
|
<n-flex class="h-full w-full">
|
||||||
<DataSetGroup class="w-50 min-w-300px max-w-500px" @select="handleGroupSelect" />
|
<DataSetGroup class="w-50 min-w-300px max-w-500px" />
|
||||||
|
|
||||||
<n-divider vertical class="!h-full" />
|
<n-divider vertical class="!h-full" />
|
||||||
|
|
||||||
@ -34,9 +34,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
|
|
||||||
<n-button type="primary" @click="handleAddDataSet">{{ t('home.Add data set') }}</n-button>
|
<n-button type="primary" @click="showDataSetModal = true">{{ t('home.Add data set') }}</n-button>
|
||||||
</div>
|
</div>
|
||||||
<n-data-table :columns="dataSetColumns" :data="dataSets" :pagination="pagination" :loading="tableLoading" />
|
<n-data-table ref="tableRef" :columns="dataSetColumns" :data="dataSets" :pagination="pagination" />
|
||||||
</div>
|
</div>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|
||||||
@ -45,32 +45,29 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, h, onMounted } from 'vue';
|
import { ref, reactive, h, onMounted, useTemplateRef } from 'vue';
|
||||||
import { NButton, NPopconfirm } from 'naive-ui';
|
import { NButton, NPopconfirm } from 'naive-ui';
|
||||||
|
import type { DataTableInst } from 'naive-ui';
|
||||||
import { Search } from '@vicons/carbon';
|
import { Search } from '@vicons/carbon';
|
||||||
import { t } from "@/language";
|
import { t } from "@/language";
|
||||||
import { fetchDataSetDetail, fetchDataSetPage, fetchDeleteDataSet } from "@/http/api/dataSet";
|
|
||||||
import DataSetModal from "./DataSetModal.vue";
|
import DataSetModal from "./DataSetModal.vue";
|
||||||
import DataSetGroup from "./DataSetGroup.vue";
|
import DataSetGroup from "./DataSetGroup.vue";
|
||||||
|
|
||||||
const dataSets = ref<IDataSet.Item[]>([])
|
const tableRef = useTemplateRef<DataTableInst>("tableRef");
|
||||||
const tableLoading = ref(false);
|
const dataSets = ref<IDataSet.Item[]>([
|
||||||
|
{
|
||||||
|
id: '',
|
||||||
|
groupId:"1",
|
||||||
|
name: '323324234',
|
||||||
|
type: 'SQL'
|
||||||
|
}
|
||||||
|
])
|
||||||
const showDataSetModal = ref(false)
|
const showDataSetModal = ref(false)
|
||||||
const searchName = ref("");
|
|
||||||
const selectedGroupId = ref<IDataSet.IGroup["id"] | null>(null);
|
|
||||||
const defaultDataSet: IDataSet.Item = {
|
|
||||||
id: '',
|
|
||||||
groupId: '',
|
|
||||||
name: '',
|
|
||||||
type: 'API',
|
|
||||||
method: 'GET',
|
|
||||||
api: '',
|
|
||||||
dataSource: '',
|
|
||||||
sql: '',
|
|
||||||
json: ''
|
|
||||||
};
|
|
||||||
const currentDataSet = reactive<IDataSet.Item>({
|
const currentDataSet = reactive<IDataSet.Item>({
|
||||||
...defaultDataSet
|
id: '',
|
||||||
|
groupId:"1",
|
||||||
|
name: '',
|
||||||
|
type: 'SQL'
|
||||||
})
|
})
|
||||||
const dataSetColumns = [
|
const dataSetColumns = [
|
||||||
{
|
{
|
||||||
@ -122,75 +119,23 @@ const dataSetColumns = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
// 分页配置
|
// 分页配置
|
||||||
const pagination = reactive({
|
const pagination = { pageSize: 10 }
|
||||||
page: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
pageCount: 1,
|
|
||||||
"on-update:page": (page: number) => {
|
|
||||||
pagination.page = page;
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function resetCurrentDataSet(data?: Partial<IDataSet.Item>) {
|
function getList() { }
|
||||||
Object.assign(currentDataSet, defaultDataSet, data || {});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getList() {
|
|
||||||
tableLoading.value = true;
|
|
||||||
const res = await fetchDataSetPage({
|
|
||||||
page: pagination.page,
|
|
||||||
pageSize: pagination.pageSize,
|
|
||||||
name: searchName.value || undefined,
|
|
||||||
groupId: selectedGroupId.value ?? undefined
|
|
||||||
});
|
|
||||||
tableLoading.value = false;
|
|
||||||
dataSets.value = res.data?.items || [];
|
|
||||||
pagination.pageCount = res.data?.pages || 1;
|
|
||||||
if (res.data?.current) {
|
|
||||||
pagination.page = res.data.current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSearch(searchText: string) {
|
function handleSearch(searchText: string) {
|
||||||
searchName.value = searchText;
|
tableRef.value?.filter({
|
||||||
pagination.page = 1;
|
name: [searchText]
|
||||||
getList();
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleGroupSelect(group: IDataSet.IGroup | null) {
|
function editDataSet(item) {
|
||||||
selectedGroupId.value = group?.id ?? null;
|
Object.assign(currentDataSet, item)
|
||||||
pagination.page = 1;
|
|
||||||
getList();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleAddDataSet() {
|
|
||||||
resetCurrentDataSet({
|
|
||||||
groupId: selectedGroupId.value ?? ""
|
|
||||||
});
|
|
||||||
showDataSetModal.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function editDataSet(item) {
|
|
||||||
const res = await fetchDataSetDetail(item.id);
|
|
||||||
if (res.error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const detail = (res.data || item) as any;
|
|
||||||
resetCurrentDataSet({
|
|
||||||
...detail,
|
|
||||||
dataSource: detail.dataSourceId || detail.dataSource || ""
|
|
||||||
});
|
|
||||||
showDataSetModal.value = true
|
showDataSetModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteDataSet(item) {
|
function deleteDataSet(item) {
|
||||||
const res = await fetchDeleteDataSet(item.id);
|
dataSets.value = dataSets.value.filter(ds => ds.id !== item.id)
|
||||||
if (res.error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.$message?.success(t("prompt.Success to delete"));
|
|
||||||
getList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@ -34,20 +34,49 @@ import { ref, reactive,onMounted } from "vue";
|
|||||||
import type { DropdownOption, TreeOption, TreeOverrideNodeClickBehaviorReturn } from 'naive-ui';
|
import type { DropdownOption, TreeOption, TreeOverrideNodeClickBehaviorReturn } from 'naive-ui';
|
||||||
import { Search, Add } from '@vicons/carbon';
|
import { Search, Add } from '@vicons/carbon';
|
||||||
import { t } from "@/language";
|
import { t } from "@/language";
|
||||||
import { fetchDataSetGroupTree, fetchDeleteDataSetGroup } from "@/http/api/dataSetGroup";
|
|
||||||
import DataSetGroupModal from "./DataSetGroupModal.vue";
|
import DataSetGroupModal from "./DataSetGroupModal.vue";
|
||||||
|
|
||||||
const emits = defineEmits(["select"]);
|
|
||||||
|
|
||||||
const pattern = ref("");
|
const pattern = ref("");
|
||||||
const data = ref<IDataSet.IGroup[]>([]);
|
const data: IDataSet.IGroup[] = [
|
||||||
const selectedGroup = ref<IDataSet.IGroup | null>(null);
|
{
|
||||||
const activeNode = ref<IDataSet.IGroup | null>(null);
|
name: '0',
|
||||||
|
id: '0',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: '0-0',
|
||||||
|
id: '0-0',
|
||||||
|
children: [
|
||||||
|
{ name: '0-0-0', id: '0-0-0' },
|
||||||
|
{ name: '0-0-1', id: '0-0-1' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '0-1',
|
||||||
|
id: '0-1',
|
||||||
|
children: [
|
||||||
|
{ name: '0-1-0', id: '0-1-0' },
|
||||||
|
{ name: '0-1-1', id: '0-1-1' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '1',
|
||||||
|
id: '1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: '1-0',
|
||||||
|
id: '1-0'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '1-1',
|
||||||
|
id: '1-1'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
async function getTreeData(){
|
function getTreeData(){}
|
||||||
const res = await fetchDataSetGroupTree();
|
|
||||||
data.value = res.data || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 子节点选中,含子节点则展开
|
// 子节点选中,含子节点则展开
|
||||||
function handleOverride({ option }): TreeOverrideNodeClickBehaviorReturn {
|
function handleOverride({ option }): TreeOverrideNodeClickBehaviorReturn {
|
||||||
@ -67,7 +96,7 @@ const currentGroup = reactive<IDataSet.IGroup>({
|
|||||||
|
|
||||||
function handleAddGroup(){
|
function handleAddGroup(){
|
||||||
currentGroup.id = "";
|
currentGroup.id = "";
|
||||||
currentGroup.pid = selectedGroup.value?.id ?? "";
|
currentGroup.pid = "";
|
||||||
currentGroup.name = "";
|
currentGroup.name = "";
|
||||||
|
|
||||||
showGroupModal.value = true;
|
showGroupModal.value = true;
|
||||||
@ -78,7 +107,8 @@ const showDropdown = ref(false);
|
|||||||
const options = ref<DropdownOption[]>([
|
const options = ref<DropdownOption[]>([
|
||||||
{
|
{
|
||||||
label: t("home.Delete"),
|
label: t("home.Delete"),
|
||||||
key: 'delete'
|
key: 'delete',
|
||||||
|
disabled: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("layout.sider.script.Edit"),
|
label: t("layout.sider.script.Edit"),
|
||||||
@ -90,15 +120,9 @@ const y = ref(0)
|
|||||||
|
|
||||||
// 绑定节点属性以实现右键菜单
|
// 绑定节点属性以实现右键菜单
|
||||||
function handleNodeProps({ option }: { option: TreeOption }) {
|
function handleNodeProps({ option }: { option: TreeOption }) {
|
||||||
const group = option as IDataSet.IGroup;
|
|
||||||
return {
|
return {
|
||||||
onClick() {
|
onClick() { },
|
||||||
selectedGroup.value = group;
|
|
||||||
emits("select", group);
|
|
||||||
},
|
|
||||||
onContextmenu(e: MouseEvent): void {
|
onContextmenu(e: MouseEvent): void {
|
||||||
activeNode.value = group;
|
|
||||||
selectedGroup.value = group;
|
|
||||||
showDropdown.value = true;
|
showDropdown.value = true;
|
||||||
x.value = e.clientX;
|
x.value = e.clientX;
|
||||||
y.value = e.clientY;
|
y.value = e.clientY;
|
||||||
@ -108,34 +132,8 @@ function handleNodeProps({ option }: { option: TreeOption }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDropdownSelect(key: string) {
|
function handleDropdownSelect() {
|
||||||
showDropdown.value = false;
|
showDropdown.value = false;
|
||||||
if (!activeNode.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === "edit") {
|
|
||||||
Object.assign(currentGroup, {
|
|
||||||
id: activeNode.value.id,
|
|
||||||
pid: activeNode.value.pid ?? "",
|
|
||||||
name: activeNode.value.name
|
|
||||||
});
|
|
||||||
showGroupModal.value = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === "delete") {
|
|
||||||
const res = await fetchDeleteDataSetGroup(activeNode.value.id);
|
|
||||||
if (res.error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.$message?.success(t("prompt.Success to delete"));
|
|
||||||
if (selectedGroup.value?.id === activeNode.value.id) {
|
|
||||||
selectedGroup.value = null;
|
|
||||||
emits("select", null);
|
|
||||||
}
|
|
||||||
getTreeData();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDropdownClickoutside() {
|
function handleDropdownClickoutside() {
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-modal :show="show" @mask-click="handleClose">
|
<n-modal :show="show" @mask-click="handleClose">
|
||||||
<n-card class="w-120 max-w-600px" :title="t('home.Data set group config')">
|
<n-card class="w-120 max-w-600px" :title="t('home.Data set group config')">
|
||||||
<n-form :model="model" :rules="rules" ref="formRef" label-placement="left" label-width="auto" :disabled="submitLoading">
|
<n-form :model="model" :rules="rules" ref="formRef" label-placement="left" label-width="auto">
|
||||||
<n-form-item :label="t('home.Parent group')">
|
<n-form-item :label="t('home.Parent group')">
|
||||||
<n-cascader v-model:value="model.pid" expand-trigger="hover" :options="treeData"
|
<n-cascader v-model:value="model.pid" expand-trigger="hover" :options="treeData"
|
||||||
check-strategy="all" show-path filterable clearable label-field="name" value-field="id" />
|
check-strategy="child" show-path filterable clearable label-field="name" value-field="id" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item :label="t('home.Group name')" path="name">
|
<n-form-item :label="t('home.Group name')" path="name">
|
||||||
<n-input v-model:value="model.name" />
|
<n-input v-model:value="model.name" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
|
|
||||||
<div class="flex justify-end mt-4">
|
<div class="flex justify-end mt-4">
|
||||||
<n-button :disabled="submitLoading" @click="handleClose" class="mr-2">{{ t("other.Cancel") }}</n-button>
|
<n-button @click="handleClose" class="mr-2">{{ t("other.Cancel") }}</n-button>
|
||||||
<n-button type="primary" :loading="submitLoading" @click="saveDataSet">{{ t("other.Ok") }}</n-button>
|
<n-button type="primary" @click="saveDataSet">{{ t("other.Ok") }}</n-button>
|
||||||
</div>
|
</div>
|
||||||
</n-form>
|
</n-form>
|
||||||
</n-card>
|
</n-card>
|
||||||
@ -20,10 +20,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useTemplateRef, ref } from "vue";
|
import { useTemplateRef,computed } from "vue";
|
||||||
import type { FormInst } from 'naive-ui'
|
import type { FormInst } from 'naive-ui'
|
||||||
import { t } from "@/language";
|
import { t } from "@/language";
|
||||||
import { DataSetGroupPayload, fetchCreateDataSetGroup, fetchUpdateDataSetGroup } from "@/http/api/dataSetGroup";
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
show: boolean,
|
show: boolean,
|
||||||
@ -44,7 +43,6 @@ const formRef = useTemplateRef<FormInst>("formRef");
|
|||||||
const rules = {
|
const rules = {
|
||||||
name: { required: true, message: t("prompt.Please enter a grouping name for the dataset"), trigger: 'blur' }
|
name: { required: true, message: t("prompt.Please enter a grouping name for the dataset"), trigger: 'blur' }
|
||||||
};
|
};
|
||||||
const submitLoading = ref(false);
|
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
emits("update:show", false);
|
emits("update:show", false);
|
||||||
@ -53,21 +51,15 @@ function handleClose() {
|
|||||||
function saveDataSet(e: MouseEvent) {
|
function saveDataSet(e: MouseEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
formRef.value?.validate(async (errors) => {
|
formRef.value?.validate((errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
const payload: DataSetGroupPayload = {
|
if (!props.model.id) {
|
||||||
id: props.model.id || undefined,
|
// 新增
|
||||||
pid: props.model.pid || "",
|
|
||||||
name: props.model.name?.trim() || ""
|
} else {
|
||||||
};
|
// 更新
|
||||||
|
|
||||||
submitLoading.value = true;
|
|
||||||
const res = props.model.id ? await fetchUpdateDataSetGroup(payload) : await fetchCreateDataSetGroup(payload);
|
|
||||||
submitLoading.value = false;
|
|
||||||
if (res.error) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
window.$message?.success(props.model.id ? t("prompt.Success to update") : t("prompt.Saved successfully!"));
|
|
||||||
|
|
||||||
// 刷新父页面表格
|
// 刷新父页面表格
|
||||||
emits("refresh");
|
emits("refresh");
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-modal :show="show" @mask-click="handleClose">
|
<n-modal :show="show" @mask-click="handleClose">
|
||||||
<n-card class="w-200 max-w-1200px" :title="t('home.Data set config')">
|
<n-card class="w-200 max-w-1200px" :title="t('home.Data set config')">
|
||||||
<n-form :model="model" :rules="rules" ref="formRef" label-placement="left" label-width="auto" :disabled="submitLoading">
|
<n-form :model="model" :rules="rules" ref="formRef" label-placement="left" label-width="auto">
|
||||||
<n-grid :cols="24" :x-gap="24">
|
<n-grid :cols="24" :x-gap="24">
|
||||||
<n-form-item-gi :span="12" :label="t('home.Data set name')" path="name">
|
<n-form-item-gi :span="12" :label="t('home.Data set name')" path="name">
|
||||||
<n-input v-model:value="model.name" />
|
<n-input v-model:value="model.name" />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="12" :label="t('home.Data set group')">
|
<n-form-item-gi :span="12" :label="t('home.Data set group')">
|
||||||
<n-cascader v-model:value="model.groupId" expand-trigger="hover" :options="groupOptions"
|
<n-cascader v-model:value="model.groupId" expand-trigger="hover" :options="groupOptions"
|
||||||
check-strategy="all" show-path filterable clearable label-field="name" value-field="id" />
|
check-strategy="child" show-path filterable clearable label-field="name" value-field="id" />
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
<n-form-item-gi :span="12" :label="t('home.Data set type')">
|
<n-form-item-gi :span="12" :label="t('home.Data set type')">
|
||||||
<n-select v-model:value="model.type" :options="setTypes" />
|
<n-select v-model:value="model.type" :options="setTypes" />
|
||||||
@ -51,8 +51,8 @@
|
|||||||
|
|
||||||
<n-gi :span="24">
|
<n-gi :span="24">
|
||||||
<div class="flex justify-end mt-4">
|
<div class="flex justify-end mt-4">
|
||||||
<n-button :disabled="submitLoading" @click="handleClose" class="mr-2">{{ t("other.Cancel") }}</n-button>
|
<n-button @click="handleClose" class="mr-2">{{ t("other.Cancel") }}</n-button>
|
||||||
<n-button type="primary" :loading="submitLoading" @click="saveDataSet">{{ t("other.Ok") }}</n-button>
|
<n-button type="primary" @click="saveDataSet">{{ t("other.Ok") }}</n-button>
|
||||||
</div>
|
</div>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
@ -62,12 +62,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, useTemplateRef } from "vue";
|
import { useTemplateRef } from "vue";
|
||||||
import type { FormInst } from 'naive-ui'
|
import type { FormInst } from 'naive-ui'
|
||||||
import { t } from "@/language";
|
import { t } from "@/language";
|
||||||
import { DataSetPayload, fetchCreateDataSet, fetchUpdateDataSet } from "@/http/api/dataSet";
|
|
||||||
import { fetchDataSetGroupTree } from "@/http/api/dataSetGroup";
|
|
||||||
import { fetchDataSourceList } from "@/http/api/dataSource";
|
|
||||||
import SQLEditor from "@/components/code/SQLEditor.vue";
|
import SQLEditor from "@/components/code/SQLEditor.vue";
|
||||||
import JSONEditor from "@/components/code/JSONEditor.vue";
|
import JSONEditor from "@/components/code/JSONEditor.vue";
|
||||||
|
|
||||||
@ -89,52 +86,13 @@ const formRef = useTemplateRef<FormInst>("formRef");
|
|||||||
const rules = {
|
const rules = {
|
||||||
name: { required: true, message: t("prompt.Please enter a name for the dataset"), trigger: 'blur' }
|
name: { required: true, message: t("prompt.Please enter a name for the dataset"), trigger: 'blur' }
|
||||||
};
|
};
|
||||||
const groupOptions = ref<IDataSet.IGroup[]>([]);
|
const groupOptions = [];
|
||||||
const setTypes = [
|
const setTypes = [
|
||||||
{ label: 'API', value: 'API' },
|
{ label: 'API', value: 'API' },
|
||||||
{ label: 'SQL', value: 'SQL' },
|
{ label: 'SQL', value: 'SQL' },
|
||||||
{ label: 'JSON', value: 'JSON' },
|
{ label: 'JSON', value: 'JSON' },
|
||||||
];
|
];
|
||||||
const dataSourceOptions = ref<{ label: string; value: IDataSource.Item["id"] }[]>([]);
|
const dataSourceOptions = [];
|
||||||
const submitLoading = ref(false);
|
|
||||||
|
|
||||||
watch(() => props.show, (show) => {
|
|
||||||
if (!show) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
loadOptions();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(() => props.model.type, (nextType, oldType) => {
|
|
||||||
if (!nextType || nextType === oldType) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (nextType !== "API") {
|
|
||||||
props.model.method = undefined;
|
|
||||||
props.model.api = "";
|
|
||||||
} else if (!props.model.method) {
|
|
||||||
props.model.method = "GET";
|
|
||||||
}
|
|
||||||
if (nextType !== "SQL") {
|
|
||||||
props.model.dataSource = "";
|
|
||||||
props.model.sql = "";
|
|
||||||
}
|
|
||||||
if (nextType !== "JSON") {
|
|
||||||
props.model.json = "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function loadOptions() {
|
|
||||||
const [groupRes, dataSourceRes] = await Promise.all([
|
|
||||||
fetchDataSetGroupTree(),
|
|
||||||
fetchDataSourceList()
|
|
||||||
]);
|
|
||||||
groupOptions.value = groupRes.data || [];
|
|
||||||
dataSourceOptions.value = (dataSourceRes.data || []).map(item => ({
|
|
||||||
label: item.name,
|
|
||||||
value: item.id
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
emits("update:show", false);
|
emits("update:show", false);
|
||||||
@ -143,32 +101,15 @@ function handleClose() {
|
|||||||
function saveDataSet(e: MouseEvent) {
|
function saveDataSet(e: MouseEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
formRef.value?.validate(async (errors) => {
|
formRef.value?.validate((errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
submitLoading.value = true;
|
if (!props.model.id) {
|
||||||
const payload: DataSetPayload = {
|
// 新增
|
||||||
id: props.model.id || undefined,
|
|
||||||
name: props.model.name?.trim() || "",
|
|
||||||
groupId: props.model.groupId,
|
|
||||||
type: props.model.type
|
|
||||||
};
|
|
||||||
|
|
||||||
if (props.model.type === "API") {
|
} else {
|
||||||
payload.method = props.model.method;
|
// 更新
|
||||||
payload.api = props.model.api?.trim();
|
|
||||||
} else if (props.model.type === "SQL") {
|
|
||||||
payload.dataSourceId = props.model.dataSource;
|
|
||||||
payload.sql = props.model.sql?.trim();
|
|
||||||
} else if (props.model.type === "JSON") {
|
|
||||||
payload.json = props.model.json;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = props.model.id ? await fetchUpdateDataSet(payload) : await fetchCreateDataSet(payload);
|
|
||||||
submitLoading.value = false;
|
|
||||||
if (res.error) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
window.$message?.success(props.model.id ? t("prompt.Success to update") : t("prompt.Saved successfully!"));
|
|
||||||
|
|
||||||
// 刷新父页面表格
|
// 刷新父页面表格
|
||||||
emits("refresh");
|
emits("refresh");
|
||||||
|
|||||||
@ -7,9 +7,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
|
|
||||||
<n-button type="primary" @click="handleAddDataSource">{{ t('home.Add data source') }}</n-button>
|
<n-button type="primary" @click="showDataSourceModal = true">{{ t('home.Add data source') }}</n-button>
|
||||||
</div>
|
</div>
|
||||||
<n-data-table ref="tableRef" :columns="dataSourceColumns" :data="dataSources" :pagination="pagination" :loading="tableLoading" striped />
|
<n-data-table ref="tableRef" :columns="dataSourceColumns" :data="dataSources" :pagination="pagination" striped />
|
||||||
|
|
||||||
<!-- 数据源模态框 -->
|
<!-- 数据源模态框 -->
|
||||||
<DataSourceModal v-model:show="showDataSourceModal" :model="currentDataSource" @refresh="getList" />
|
<DataSourceModal v-model:show="showDataSourceModal" :model="currentDataSource" @refresh="getList" />
|
||||||
@ -21,23 +21,18 @@ import { NButton, NPopconfirm } from 'naive-ui';
|
|||||||
import type { DataTableInst } from 'naive-ui';
|
import type { DataTableInst } from 'naive-ui';
|
||||||
import { Search } from '@vicons/carbon';
|
import { Search } from '@vicons/carbon';
|
||||||
import { t } from "@/language";
|
import { t } from "@/language";
|
||||||
import { fetchDataSourceList, fetchDeleteDataSource } from "@/http/api/dataSource";
|
|
||||||
import DataSourceModal from "./DataSourceModal.vue";
|
import DataSourceModal from "./DataSourceModal.vue";
|
||||||
|
|
||||||
const tableRef = useTemplateRef<DataTableInst>("tableRef");
|
const tableRef = useTemplateRef<DataTableInst>("tableRef");
|
||||||
const dataSources = ref<IDataSource.Item[]>([])
|
const dataSources = ref<IDataSource.Item[]>([])
|
||||||
const showDataSourceModal = ref(false)
|
const showDataSourceModal = ref(false)
|
||||||
const tableLoading = ref(false);
|
const currentDataSource = reactive({
|
||||||
const defaultDataSource = {
|
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
type: 'MySQL',
|
type: 'MySQL',
|
||||||
connectionString: '',
|
connectionString: '',
|
||||||
username: "",
|
username: "",
|
||||||
password: ""
|
password: ""
|
||||||
}
|
|
||||||
const currentDataSource = reactive({
|
|
||||||
...defaultDataSource
|
|
||||||
})
|
})
|
||||||
const dataSourceColumns = [
|
const dataSourceColumns = [
|
||||||
{
|
{
|
||||||
@ -111,21 +106,7 @@ const dataSourceColumns = [
|
|||||||
// 分页配置
|
// 分页配置
|
||||||
const pagination = { pageSize: 10 }
|
const pagination = { pageSize: 10 }
|
||||||
|
|
||||||
async function getList() {
|
function getList() { }
|
||||||
tableLoading.value = true;
|
|
||||||
const res = await fetchDataSourceList();
|
|
||||||
tableLoading.value = false;
|
|
||||||
dataSources.value = res.data || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetCurrentDataSource(data?: Partial<IDataSource.Item>) {
|
|
||||||
Object.assign(currentDataSource, defaultDataSource, data || {});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleAddDataSource() {
|
|
||||||
resetCurrentDataSource();
|
|
||||||
showDataSourceModal.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSearch(searchText: string) {
|
function handleSearch(searchText: string) {
|
||||||
tableRef.value?.filter({
|
tableRef.value?.filter({
|
||||||
@ -134,17 +115,12 @@ function handleSearch(searchText: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function editDataSource(item) {
|
function editDataSource(item) {
|
||||||
resetCurrentDataSource(item)
|
Object.assign(currentDataSource, item)
|
||||||
showDataSourceModal.value = true
|
showDataSourceModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteDataSource(item) {
|
function deleteDataSource(item) {
|
||||||
const res = await fetchDeleteDataSource(item.id);
|
dataSources.value = dataSources.value.filter(ds => ds.id !== item.id)
|
||||||
if (res.error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.$message?.success(t("prompt.Success to delete"));
|
|
||||||
getList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-modal :show="show" @mask-click="handleClose">
|
<n-modal :show="show" @mask-click="handleClose">
|
||||||
<n-card class="w-100 max-w-600px" :title="t('home.Data source config')">
|
<n-card class="w-100 max-w-600px" :title="t('home.Data source config')">
|
||||||
<n-form :model="model" :rules="rules" ref="formRef" label-placement="left" label-width="auto" :disabled="testLinkLoading || submitLoading">
|
<n-form :model="model" :rules="rules" ref="formRef" label-placement="left" label-width="auto">
|
||||||
<n-form-item :label="t('home.Data source name')" path="name">
|
<n-form-item :label="t('home.Data source name')" path="name">
|
||||||
<n-input v-model:value="model.name" :disabled="testLinkLoading" />
|
<n-input v-model:value="model.name" :disabled="testLinkLoading" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@ -22,8 +22,8 @@
|
|||||||
<n-button type="primary" :loading="testLinkLoading" @click="handleTestLink">{{ t("home.Test the connection") }}</n-button>
|
<n-button type="primary" :loading="testLinkLoading" @click="handleTestLink">{{ t("home.Test the connection") }}</n-button>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<n-button :disabled="testLinkLoading || submitLoading" @click="handleClose" class="mr-2">{{ t("other.Cancel") }}</n-button>
|
<n-button @click="handleClose" class="mr-2">{{ t("other.Cancel") }}</n-button>
|
||||||
<n-button type="primary" :loading="submitLoading" @click="saveDataSource">{{ t("other.Ok") }}</n-button>
|
<n-button type="primary" @click="saveDataSource">{{ t("other.Ok") }}</n-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -33,10 +33,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, useTemplateRef } from "vue";
|
import { ref, useTemplateRef } from "vue";
|
||||||
import type { FormInst } from 'naive-ui'
|
import type { FormInst } from 'naive-ui'
|
||||||
import { t } from "@/language";
|
import { t } from "@/language";
|
||||||
import { DataSourcePayload, fetchCreateDataSource, fetchTestDataSource, fetchUpdateDataSource } from "@/http/api/dataSource";
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
show: boolean,
|
show: boolean,
|
||||||
@ -66,8 +65,6 @@ const dbTypes = [
|
|||||||
{ label: 'Oracle', value: 'Oracle' }
|
{ label: 'Oracle', value: 'Oracle' }
|
||||||
]
|
]
|
||||||
const testLinkLoading = ref(false);
|
const testLinkLoading = ref(false);
|
||||||
const submitLoading = ref(false);
|
|
||||||
const isEdit = computed(() => Boolean(props.model.id));
|
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
emits("update:show", false);
|
emits("update:show", false);
|
||||||
@ -75,52 +72,25 @@ function handleClose() {
|
|||||||
|
|
||||||
function handleTestLink(e: MouseEvent) {
|
function handleTestLink(e: MouseEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
formRef.value?.validate(async (errors) => {
|
formRef.value?.validate((errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
testLinkLoading.value = true;
|
testLinkLoading.value = true;
|
||||||
const res = await fetchTestDataSource(buildPayload(false));
|
|
||||||
testLinkLoading.value = false;
|
|
||||||
if (res.error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
window.$message?.success(t("prompt.Operation successful"));
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPayload(includeId: boolean): DataSourcePayload {
|
|
||||||
const payload: DataSourcePayload = {
|
|
||||||
name: props.model.name?.trim() || "",
|
|
||||||
type: props.model.type,
|
|
||||||
connectionString: props.model.connectionString?.trim() || ""
|
|
||||||
};
|
|
||||||
|
|
||||||
if (props.model.username) {
|
|
||||||
payload.username = props.model.username;
|
|
||||||
}
|
|
||||||
if (props.model.password) {
|
|
||||||
payload.password = props.model.password;
|
|
||||||
}
|
|
||||||
if (includeId && props.model.id) {
|
|
||||||
payload.id = props.model.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveDataSource(e: MouseEvent) {
|
function saveDataSource(e: MouseEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
formRef.value?.validate(async (errors) => {
|
formRef.value?.validate((errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
submitLoading.value = true;
|
if (!props.model.id) {
|
||||||
const payload = buildPayload(true);
|
// 新增
|
||||||
const res = isEdit.value ? await fetchUpdateDataSource(payload) : await fetchCreateDataSource(payload);
|
|
||||||
submitLoading.value = false;
|
} else {
|
||||||
if (res.error) {
|
// 更新
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
window.$message?.success(isEdit.value ? t("prompt.Success to update") : t("prompt.Saved successfully!"));
|
|
||||||
|
|
||||||
// 刷新父页面表格
|
// 刷新父页面表格
|
||||||
emits("refresh");
|
emits("refresh");
|
||||||
|
|||||||
@ -19,12 +19,26 @@ const options = [
|
|||||||
key: 'details',
|
key: 'details',
|
||||||
icon: renderIcon(Information)
|
icon: renderIcon(Information)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: () => cpt("home.Rename").value,
|
||||||
|
key: 'rename',
|
||||||
|
icon: renderIcon(Edit)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: () => cpt("home.Delete").value,
|
label: () => cpt("home.Delete").value,
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
icon: renderIcon(Delete)
|
icon: renderIcon(Delete)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: () => cpt("home.Release").value,
|
||||||
|
key: 'release',
|
||||||
|
icon: renderIcon(SendAlt)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: () => cpt("layout.header.Export").value,
|
||||||
|
key: 'export',
|
||||||
|
icon: renderIcon(Export)
|
||||||
|
}
|
||||||
]
|
]
|
||||||
const detail = ref();
|
const detail = ref();
|
||||||
const detailVisible = ref(false);
|
const detailVisible = ref(false);
|
||||||
|
|||||||
@ -158,8 +158,7 @@ export default defineConfig(async ({mode, command}) => {
|
|||||||
// }),
|
// }),
|
||||||
viteStaticCopy({
|
viteStaticCopy({
|
||||||
targets: [
|
targets: [
|
||||||
{ src: 'node_modules/@astral3d/engine/dist/libs/*', dest: 'assets/libs' },
|
{ src: 'node_modules/@astral3d/engine/dist/libs/*', dest: 'assets/libs' }
|
||||||
{ src: 'node_modules/@astral3d/engine/dist/resource/**/*', dest: 'resource' }
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
...plugins
|
...plugins
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import {Timer} from 'three/examples/jsm/misc/Timer.js';
|
|||||||
import {CSS2DRenderer} from "three/examples/jsm/renderers/CSS2DRenderer";
|
import {CSS2DRenderer} from "three/examples/jsm/renderers/CSS2DRenderer";
|
||||||
import {CSS3DRenderer} from "three/examples/jsm/renderers/CSS3DRenderer.js";
|
import {CSS3DRenderer} from "three/examples/jsm/renderers/CSS3DRenderer.js";
|
||||||
import {TransformControls} from "three/examples/jsm/controls/TransformControls.js";
|
import {TransformControls} from "three/examples/jsm/controls/TransformControls.js";
|
||||||
import TWEEN from "three/examples/jsm/libs/tween.module.js";
|
|
||||||
import App from "../app/App";
|
import App from "../app/App";
|
||||||
import {ViewerOptions} from "./ViewerOptions";
|
import {ViewerOptions} from "./ViewerOptions";
|
||||||
import {PluginManager} from "@/core/plugin/plugin";
|
import {PluginManager} from "@/core/plugin/plugin";
|
||||||
@ -1061,9 +1060,6 @@ export default class Viewer extends THREE.EventDispatcher<ViewerEventMap> {
|
|||||||
this.dispatchEvent({type: 'beforeAnimation', delta: timeStamp});
|
this.dispatchEvent({type: 'beforeAnimation', delta: timeStamp});
|
||||||
|
|
||||||
let needRender = App.animationManager.update(timeStamp);
|
let needRender = App.animationManager.update(timeStamp);
|
||||||
if (TWEEN.update()) {
|
|
||||||
needRender = true;
|
|
||||||
}
|
|
||||||
if (needRender) {
|
if (needRender) {
|
||||||
if (App.selected !== null && App.selected.animations.length > 0) {
|
if (App.selected !== null && App.selected.animations.length > 0) {
|
||||||
// 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb)
|
// 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user