deep-engine-demo/packages/demo/src/panels/FracturingPanel.vue
2026-04-19 18:46:28 +08:00

575 lines
16 KiB
Vue

<template>
<n-collapse-item name="fracturing" title="压裂注采">
<n-tabs animated type="segment">
<!-- 注入井 -->
<n-tab-pane name="injection" tab="注入井">
<n-form label-placement="left" label-width="120" size="small">
<n-form-item label="注入流量(m³/s)">
<n-input-number
v-model:value="injectionParams.flowRate"
:max="10"
:min="0"
:step="0.1"
style="width: 100%"
/>
</n-form-item>
<n-form-item label="注入速率(m/s)">
<n-input-number
v-model:value="injectionParams.injectionRate"
:max="50"
:min="0"
:step="0.5"
style="width: 100%"
/>
</n-form-item>
<n-form-item label="注入压力(MPa)">
<n-input-number
v-model:value="injectionParams.pressure"
:max="100"
:min="0"
:step="1"
style="width: 100%"
/>
</n-form-item>
<n-form-item label="流体温度(°C)">
<n-input-number
v-model:value="injectionParams.temperature"
:max="200"
:min="0"
:step="1"
style="width: 100%"
/>
</n-form-item>
</n-form>
<n-space style="width: 100%" vertical>
<n-button
:disabled="isSelectingInjectionWell"
block
type="primary"
@click="startSelectingInjectionWell">
{{ isSelectingInjectionWell ? '请点击场景选择注入井...' : '选择注入井' }}
</n-button>
<n-button
:disabled="injectionWells.length === 0"
block
type="warning"
@click="startInjection">
开始注入 ({{ injectionWells.length }})
</n-button>
<n-button
:disabled="injectionWells.length === 0"
block
type="error"
@click="clearInjectionWells">
清除注入井
</n-button>
</n-space>
</n-tab-pane>
<!-- 采出井 -->
<n-tab-pane name="production" tab="采出井">
<n-form label-placement="left" label-width="120" size="small">
<n-form-item label="采出流量(m³/h)">
<n-input-number
v-model:value="productionParams.flowRate"
:max="1000"
:min="0"
:step="10"
style="width: 100%"
/>
</n-form-item>
<n-form-item label="采出速率(m/s)">
<n-input-number
v-model:value="productionParams.productionRate"
:max="10"
:min="0"
:step="0.1"
style="width: 100%"
/>
</n-form-item>
<n-form-item label="采出压力(MPa)">
<n-input-number
v-model:value="productionParams.pressure"
:max="50"
:min="0"
:step="0.5"
style="width: 100%"
/>
</n-form-item>
<n-form-item label="流体温度(°C)">
<n-input-number
v-model:value="productionParams.temperature"
:max="150"
:min="0"
:step="1"
style="width: 100%"
/>
</n-form-item>
</n-form>
<n-space style="width: 100%" vertical>
<n-button
:disabled="isSelectingProductionWell"
block
type="primary"
@click="startSelectingProductionWell">
{{ isSelectingProductionWell ? '请点击场景选择采出井...' : '选择采出井' }}
</n-button>
<n-button
:disabled="productionWells.length === 0"
block
type="success"
@click="startProduction">
开始采出 ({{ productionWells.length }})
</n-button>
<n-button
:disabled="productionWells.length === 0"
block
type="error"
@click="clearProductionWells">
清除采出井
</n-button>
</n-space>
</n-tab-pane>
</n-tabs>
</n-collapse-item>
</template>
<script lang="ts" setup>
import {onMounted, ref} from 'vue';
import {NButton, NCollapseItem, NForm, NFormItem, NInputNumber, NSpace, NTabPane, NTabs} from 'naive-ui';
import {useBus} from '@/hooks';
import {BusEvents} from '@/hooks/Bus.ts';
import {EventManagerEvents, Viewer} from '@deep/engine';
import {InjectionHtmlPanel, ProductionHtmlPanel} from '../../htmlPanel';
import * as THREE from 'three/webgpu';
const bus = useBus();
let viewer: Viewer | null = null;
let injectionWells: any[] = [];
let productionWells: any[] = [];
let wellCounter = 0;
const isSelectingInjectionWell = ref(false);
const isSelectingProductionWell = ref(false);
const injectionModals = new Map<any, InjectionHtmlPanel>();
const productionModals = new Map<any, ProductionHtmlPanel>();
// 注入参数
const injectionParams = ref({
flowRate: 2.5,
injectionRate: 10,
pressure: 30,
temperature: 80,
});
// 采出参数
const productionParams = ref({
flowRate: 500,
productionRate: 5,
pressure: 15,
temperature: 60,
});
onMounted(() => {
bus.once(BusEvents.VIEWER_INITIALIZED).then(() => {
viewer = bus.getViewer();
setupClickListener();
});
});
/**
* 设置点击事件监听
*/
const setupClickListener = () => {
if (!viewer) return;
viewer.events.on(EventManagerEvents.RAYCAST_PICK, ({data}) => {
if (isSelectingInjectionWell.value || isSelectingProductionWell.value || !viewer) return;
const {intersects} = data;
if (!intersects || intersects.length === 0) return;
for (const intersection of intersects) {
const clickedObject = intersection.object;
// 检查是否点击了注入井
const injectionWell = injectionWells.find(w => w.mesh === clickedObject);
if (injectionWell) {
let modal = injectionModals.get(injectionWell);
if (!modal) {
modal = createInjectionModalForWell(injectionWell);
}
updateInjectionModalInfo(injectionWell, modal);
if (modal && intersection.point) {
const modalObject = modal.getCssObject();
modalObject.position.copy(intersection.point);
modalObject.position.y += 0.5;
}
break;
}
// 检查是否点击了采出井
const productionWell = productionWells.find(w => w.mesh === clickedObject);
if (productionWell) {
let modal = productionModals.get(productionWell);
if (!modal) {
modal = createProductionModalForWell(productionWell);
}
updateProductionModalInfo(productionWell, modal);
if (modal && intersection.point) {
const modalObject = modal.getCssObject();
modalObject.position.copy(intersection.point);
modalObject.position.y += 0.5;
}
break;
}
}
});
};
/**
* 为注入井创建信息弹窗
*/
const createInjectionModalForWell = (well: any): InjectionHtmlPanel => {
if (!viewer) throw new Error('Viewer not initialized');
const modal = new InjectionHtmlPanel('css2d');
modal.onDelete(() => {
modal.hide();
const modalObject = modal.getCssObject();
if (modalObject.parent) {
modalObject.parent.remove(modalObject);
}
injectionModals.delete(well);
});
const wellPosition = well.mesh.position;
const modalObject = modal.getCssObject();
modalObject.position.set(wellPosition.x, wellPosition.y + 1, wellPosition.z);
viewer.scene.add(modalObject);
modal.show();
injectionModals.set(well, modal);
return modal;
};
/**
* 为采出井创建信息弹窗
*/
const createProductionModalForWell = (well: any): ProductionHtmlPanel => {
if (!viewer) throw new Error('Viewer not initialized');
const modal = new ProductionHtmlPanel('css2d');
modal.onDelete(() => {
modal.hide();
const modalObject = modal.getCssObject();
if (modalObject.parent) {
modalObject.parent.remove(modalObject);
}
productionModals.delete(well);
});
const wellPosition = well.mesh.position;
const modalObject = modal.getCssObject();
modalObject.position.set(wellPosition.x, wellPosition.y + 1, wellPosition.z);
viewer.scene.add(modalObject);
modal.show();
productionModals.set(well, modal);
return modal;
};
/**
* 更新注入井信息弹窗内容
*/
const updateInjectionModalInfo = (well: any, modal: InjectionHtmlPanel) => {
modal.updateData({
wellName: well.name,
flowRate: well.flowRate || injectionParams.value.flowRate,
injectionRate: well.injectionRate || injectionParams.value.injectionRate,
pressure: well.pressure || injectionParams.value.pressure,
volume: well.volume || 0,
temperature: well.temperature || injectionParams.value.temperature,
status: well.isInjecting ? 'injecting' : 'stopped'
});
};
/**
* 更新采出井信息弹窗内容
*/
const updateProductionModalInfo = (well: any, modal: ProductionHtmlPanel) => {
modal.updateData({
wellName: well.name,
flowRate: well.flowRate || productionParams.value.flowRate,
productionRate: well.productionRate || productionParams.value.productionRate,
pressure: well.pressure || productionParams.value.pressure,
volume: well.volume || 0,
temperature: well.temperature || productionParams.value.temperature,
status: well.isProducing ? 'producing' : 'stopped'
});
};
/**
* 开始选择注入井
*/
const startSelectingInjectionWell = () => {
if (!viewer) return;
isSelectingInjectionWell.value = true;
const canvas = viewer.renderer!.domElement;
canvas.addEventListener('click', onInjectionWellClick, {once: true});
canvas.style.cursor = 'crosshair';
};
/**
* 开始选择采出井
*/
const startSelectingProductionWell = () => {
if (!viewer) return;
isSelectingProductionWell.value = true;
const canvas = viewer.renderer!.domElement;
canvas.addEventListener('click', onProductionWellClick, {once: true});
canvas.style.cursor = 'crosshair';
};
/**
* 处理注入井点击事件
*/
const onInjectionWellClick = (event: MouseEvent) => {
if (!viewer || !isSelectingInjectionWell.value) return;
const canvas = viewer.renderer!.domElement;
const rect = canvas.getBoundingClientRect();
const mouse = new THREE.Vector2();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, viewer.camera);
const intersects = raycaster.intersectObjects(viewer.scene.children, true);
if (intersects.length > 0) {
const intersection = intersects[0];
if (intersection) {
createInjectionWell(intersection.point);
}
}
canvas.style.cursor = 'default';
isSelectingInjectionWell.value = false;
};
/**
* 处理采出井点击事件
*/
const onProductionWellClick = (event: MouseEvent) => {
if (!viewer || !isSelectingProductionWell.value) return;
const canvas = viewer.renderer!.domElement;
const rect = canvas.getBoundingClientRect();
const mouse = new THREE.Vector2();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, viewer.camera);
const intersects = raycaster.intersectObjects(viewer.scene.children, true);
if (intersects.length > 0) {
const intersection = intersects[0];
if (intersection) {
createProductionWell(intersection.point);
}
}
canvas.style.cursor = 'default';
isSelectingProductionWell.value = false;
};
/**
* 创建注入井
*/
const createInjectionWell = (position: THREE.Vector3) => {
if (!viewer) return;
const geometry = new THREE.CylinderGeometry(0.1, 0.1, 1, 16);
const material = new THREE.MeshBasicMaterial({color: 0xff6b6b});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(position);
mesh.position.y += 0.5;
wellCounter++;
const wellName = `注入井_${wellCounter}`;
mesh.name = wellName;
viewer.scene.add(mesh);
const well = {
mesh,
name: wellName,
flowRate: injectionParams.value.flowRate,
injectionRate: injectionParams.value.injectionRate,
pressure: injectionParams.value.pressure,
volume: 0,
temperature: injectionParams.value.temperature,
isInjecting: false
};
injectionWells.push(well);
bus.triggerSceneTreeUpdate()
};
/**
* 创建采出井
*/
const createProductionWell = (position: THREE.Vector3) => {
if (!viewer) return;
const geometry = new THREE.CylinderGeometry(0.1, 0.1, 1, 16);
const material = new THREE.MeshBasicMaterial({color: 0x4ecdc4});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(position);
mesh.position.y += 0.5;
wellCounter++;
const wellName = `采出井_${wellCounter}`;
mesh.name = wellName;
viewer.scene.add(mesh);
const well = {
mesh,
name: wellName,
flowRate: productionParams.value.flowRate,
productionRate: productionParams.value.productionRate,
pressure: productionParams.value.pressure,
volume: 0,
temperature: productionParams.value.temperature,
isProducing: false
};
productionWells.push(well);
bus.triggerSceneTreeUpdate()
};
/**
* 开始注入
*/
const startInjection = () => {
injectionWells.forEach(well => {
well.isInjecting = true;
const modal = injectionModals.get(well);
if (modal) {
updateInjectionModalInfo(well, modal);
}
// 模拟注入体积增加
const interval = setInterval(() => {
if (!well.isInjecting) {
clearInterval(interval);
return;
}
well.volume += well.flowRate * 0.1;
const modal = injectionModals.get(well);
if (modal) {
updateInjectionModalInfo(well, modal);
}
}, 100);
});
};
/**
* 开始采出
*/
const startProduction = () => {
productionWells.forEach(well => {
well.isProducing = true;
const modal = productionModals.get(well);
if (modal) {
updateProductionModalInfo(well, modal);
}
// 模拟采出体积增加
const interval = setInterval(() => {
if (!well.isProducing) {
clearInterval(interval);
return;
}
well.volume += well.flowRate * 0.1 / 3600; // 转换为秒
const modal = productionModals.get(well);
if (modal) {
updateProductionModalInfo(well, modal);
}
}, 100);
});
};
/**
* 清除注入井
*/
const clearInjectionWells = () => {
if (injectionWells.length > 0) {
injectionWells.forEach(well => {
const modal = injectionModals.get(well);
if (modal) {
modal.hide();
const modalObject = modal.getCssObject();
if (modalObject.parent) {
modalObject.parent.remove(modalObject);
}
}
if (viewer) {
viewer.scene.remove(well.mesh);
}
});
injectionWells = [];
injectionModals.clear();
bus.triggerSceneTreeUpdate()
}
};
/**
* 清除采出井
*/
const clearProductionWells = () => {
if (productionWells.length > 0) {
productionWells.forEach(well => {
const modal = productionModals.get(well);
if (modal) {
modal.hide();
const modalObject = modal.getCssObject();
if (modalObject.parent) {
modalObject.parent.remove(modalObject);
}
}
if (viewer) {
viewer.scene.remove(well.mesh);
}
});
productionWells = [];
productionModals.clear();
bus.triggerSceneTreeUpdate()
}
};
</script>
<style scoped>
</style>