575 lines
16 KiB
Vue
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>
|