打卡范围

This commit is contained in:
2025-09-04 17:11:18 +08:00
parent 4dd1f2d6d5
commit a257068054
5 changed files with 481 additions and 16 deletions

View File

@ -227,3 +227,39 @@ export const byProjectIdDetail = (id) => {
method: 'get' method: 'get'
}); });
}; };
// 新增项目打卡范围
export const addAttendanceRange = (data) => {
return request({
url: '/project/projectPunchrange',
method: 'post',
data
});
};
// 删除项目打卡范围
export const delAttendanceRange = (id) => {
return request({
url: '/project/projectPunchrange/' + id,
method: 'delete'
});
};
// 修改项目打卡范围
export const updateAttendanceRange = (data) => {
return request({
url: '/project/projectPunchrange',
method: 'put',
data
});
};
// 查询项目打卡范围列表
export const getAttendanceRangeList = (data) => {
return request({
url: '/project/projectPunchrange/list',
method: 'get',
params: data
});
};

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="centerPage"> <div class="centerPage">
<div class="topPage"> <div class="topPage">
<img src="@/assets/projectLarge/center.png" alt=""> <div id="earth" style="width: 100%;height: 100%;"></div>
</div> </div>
<div class="endPage" :class="{ 'slide-out-down': isHide }"> <div class="endPage" :class="{ 'slide-out-down': isHide }">
<Title title="AI安全巡检"> <Title title="AI安全巡检">
@ -30,7 +30,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup>
import { ref, onMounted, toRefs, getCurrentInstance } from "vue" import { ref, onMounted, toRefs, getCurrentInstance } from "vue"
import Title from './title.vue' import Title from './title.vue'
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue' import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
@ -56,12 +56,12 @@ const inspectionList = ref([{
picture: "", picture: "",
createTime: "" createTime: ""
}]) }])
const swiperContent = ref<HTMLDivElement>() const swiperContent = ref()
const swiperItemWidth = ref(100) const swiperItemWidth = ref(100)
const canLeft = ref(false) const canLeft = ref(false)
const canRight = ref(true) const canRight = ref(true)
const swiperClick = (direction: 'left' | 'right') => { const swiperClick = (direction) => {
if (!swiperContent.value) return if (!swiperContent.value) return
if (direction === 'right') { if (direction === 'right') {
@ -90,18 +90,67 @@ const getInspectionList = async () => {
const { code, data } = res const { code, data } = res
if (code === 200) { if (code === 200) {
data.map(item => { data.map(item => {
item.label = violation_level_type.value.find((i: any) => i.value === item.violationType)?.label item.label = violation_level_type.value.find((i) => i.value === item.violationType)?.label
}) })
inspectionList.value = data inspectionList.value = data
} }
} }
// 创建地球
const createEarth = () => {
window.YJ.on({
ws: true,
// host: getIP(), //资源所在服务器地址
// username: this.loginForm.username, //用户名 可以不登录(不填写用户名),不登录时无法加载服务端的数据
// password: md5pass, //密码 生成方式md5(用户名_密码)
}).then((res) => {
let earth = new YJ.YJEarth("earth");
window.Earth1 = earth;
YJ.Global.openRightClick(window.Earth1);
YJ.Global.openLeftClick(window.Earth1);
let view = {
"position": {
"lng": 102.03643298211526,
"lat": 34.393586474501,
"alt": 11298179.51993155
},
"orientation": {
"heading": 360,
"pitch": -89.94481747201486,
"roll": 0
}
}
loadBaseMap(earth.viewer)
// YJ.Global.flyTo(earth, view);
// YJ.Global.setDefaultView(earth.viewer, view)
})
}
// 加载底图
const loadBaseMap = (viewer) => {
// 创建瓦片提供器
const imageryProvider = new Cesium.UrlTemplateImageryProvider({
url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
// 可选:设置瓦片的格式
fileExtension: 'png',
// 可选:设置瓦片的范围和级别
minimumLevel: 0,
maximumLevel: 18,
// 可选设置瓦片的投影默认为Web Mercator
projection: Cesium.WebMercatorProjection,
// 可选:如果瓦片服务需要跨域请求,设置请求头部
credit: new Cesium.Credit('卫星图数据来源')
});
// 添加图层到视图
const layer = viewer.imageryLayers.addImageryProvider(imageryProvider);
}
onMounted(() => { onMounted(() => {
getInspectionList() getInspectionList()
createEarth()
if (swiperContent.value && swiperContent.value.children.length > 0) { if (swiperContent.value && swiperContent.value.children.length > 0) {
swiperItemWidth.value = swiperContent.value.children[0].clientWidth + 20 swiperItemWidth.value = swiperContent.value.children[0].clientWidth + 20
} }
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -21,8 +21,6 @@ import Header from './components/header.vue';
import leftPage from './components/leftPage.vue'; import leftPage from './components/leftPage.vue';
import centerPage from './components/centerPage.vue'; import centerPage from './components/centerPage.vue';
import rightPage from './components/rightPage.vue'; import rightPage from './components/rightPage.vue';
<<<<<<< HEAD
=======
import { useUserStoreHook } from '@/store/modules/user'; import { useUserStoreHook } from '@/store/modules/user';
const userStore = useUserStoreHook(); const userStore = useUserStoreHook();
@ -44,7 +42,6 @@ const handleChangePage = () => {
isHideOther.value = true; isHideOther.value = true;
} }
} }
>>>>>>> 290fc16c320aeeee8ecb31af446b0ad7d555e954
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -150,7 +150,7 @@
<el-button link type="primary" icon="Edit" @click="handleCheckRules(scope.row)" v-hasPermi="['project:attendanceRule:byProjectId']" <el-button link type="primary" icon="Edit" @click="handleCheckRules(scope.row)" v-hasPermi="['project:attendanceRule:byProjectId']"
>打卡规则 >打卡规则
</el-button> </el-button>
<!-- <el-button link type="primary" icon="Edit" @click="handleScope(scope.row)" v-hasPermi="['project:project:edit']">打卡范围 </el-button> --> <el-button link type="primary" icon="Edit" @click="handleScope(scope.row)" v-hasPermi="['project:project:edit']">打卡范围 </el-button>
<el-button link type="primary" icon="FolderOpened" @click="handleShowUpload(scope.row)" v-hasPermi="['project:project:edit']" <el-button link type="primary" icon="FolderOpened" @click="handleShowUpload(scope.row)" v-hasPermi="['project:project:edit']"
>导入安全协议书 >导入安全协议书
</el-button> </el-button>
@ -367,23 +367,28 @@
</el-dialog> </el-dialog>
<el-dialog draggable title="打卡范围" v-model="ScopeFlag" width="600"> <el-dialog draggable title="打卡范围" v-model="ScopeFlag" width="600">
<div v-for="(item, i) of punchRangeList" :key="i" class="options_item"> <div v-for="(item, i) of punchRangeList" :key="i" class="options_item">
<el-row> <el-row style="margin-bottom: 10px;">
<el-col :span="1"> <el-color-picker v-model="item.punchColor" show-alpha /></el-col> <el-col :span="1"> <el-color-picker v-model="item.punchColor" show-alpha /></el-col>
<el-col :span="12"> <el-input v-model="item.punchName" placeholder="请输入打卡范围名称" class="ml-8" /></el-col> <el-col :span="12"> <el-input v-model="item.punchName" placeholder="请输入打卡范围名称" class="ml-8" /></el-col>
<el-col :span="10" style="text-align: right; margin-top: 5px"> <el-col :span="10" style="text-align: right; margin-top: 5px">
<el-button link type="primary" icon="view">预览</el-button> <el-button v-if="item.id" link type="primary" icon="view" @click="previewPunchRange(item)">预览</el-button>
<el-button link type="primary" icon="plus">添加</el-button> <el-button v-if="item.id" link type="primary" icon="delete" @click="handleScopeDel(item)">移除</el-button>
<el-button link type="primary" icon="delete">移除</el-button> <el-button v-if="!item.id" link type="primary" icon="plus" @click="addPunchRange(item)">添加</el-button>
<el-button v-if="item.id" link type="primary" icon="download" @click="handleScopeEdit(item)">保存</el-button>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button type="primary" @click="scopeSubmit"> 提交</el-button> <el-button type="primary" @click="scopeSubmit">确定</el-button>
<el-button @click="ScopeFlag = false">取消</el-button> <el-button @click="ScopeFlag = false">取消</el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
<CesiumEarthDialog ref="earthDialog" @send-data="handleEarthData"
:position="position"
:data="earthData"
@close="handleEarthClose"></CesiumEarthDialog>
</div> </div>
</template> </template>
@ -398,10 +403,17 @@ import {
updateProject, updateProject,
attendanceRuleAdd, attendanceRuleAdd,
attendanceRuleEdit, attendanceRuleEdit,
byProjectIdDetail byProjectIdDetail,
getAttendanceRangeList,
updateAttendanceRange,
delAttendanceRange
} from '@/api/project/project'; } from '@/api/project/project';
import { ProjectForm, ProjectQuery, ProjectVO, childProjectQuery, locationType } from '@/api/project/project/types'; import { ProjectForm, ProjectQuery, ProjectVO, childProjectQuery, locationType } from '@/api/project/project/types';
import amap from '@/components/amap/index.vue'; import amap from '@/components/amap/index.vue';
import CesiumEarthDialog from "./map.vue"
import { useUserStoreHook } from '@/store/modules/user';
const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_normal_disable, project_category_type, project_type, project_stage } = toRefs<any>( const { sys_normal_disable, project_category_type, project_type, project_stage } = toRefs<any>(
proxy?.useDict('sys_normal_disable', 'project_category_type', 'project_type', 'project_stage') proxy?.useDict('sys_normal_disable', 'project_category_type', 'project_type', 'project_stage')
@ -424,6 +436,14 @@ const projectId = ref<string>('');
const designId = ref<string>(''); const designId = ref<string>('');
const ruleFlag = ref(false); const ruleFlag = ref(false);
const ScopeFlag = ref(false); const ScopeFlag = ref(false);
const earthDialog = ref(null);
const position = ref(''); //预览
let earthData = reactive({
projectId: '',
punchName: '',
punchColor: '#1983ff',
punchRange: '',
}); //传值给地图组件
const punchRangeList = ref<any>([ const punchRangeList = ref<any>([
{ {
punchName: '', punchName: '',
@ -710,10 +730,48 @@ const handleSetChild = async () => {
} }
}; };
const handleScope = (row) => { const handleScope = (row) => {
console.log('row',row);
// 打卡范围 // 打卡范围
ScopeFlag.value = true; ScopeFlag.value = true;
projectId.value = row.id; projectId.value = row.id;
// 获取打卡范围列表
getListScope(row.id);
}; };
// 获取打卡范围列表
const getListScope = (id) => {
punchRangeList.value = [{
punchName: '',
punchColor: '#1983ff'
}];
getAttendanceRangeList({ projectId: id, }).then((res) => {
if (res.code === 200) {
punchRangeList.value.unshift(...res.rows);
}
});
}
// 修改打卡范围
const handleScopeEdit = (row) => {
updateAttendanceRange(row).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess('修改成功');
// ScopeFlag.value = false;
getListScope(projectId.value);
}
});
}
// 删除打卡范围
const handleScopeDel = (row) => {
proxy.$modal.confirm('是否确认删除打卡范围?').then(() => {
delAttendanceRange(row.id).then((res) => {
if (res.code === 200) {
proxy.$modal.msgSuccess('删除成功');
getListScope(projectId.value);
}
});
}).catch(() => {});
}
const scopeSubmit = () => { const scopeSubmit = () => {
// 提交打卡范围 // 提交打卡范围
}; };
@ -761,7 +819,36 @@ const handleExport = () => {
`project_${new Date().getTime()}.xlsx` `project_${new Date().getTime()}.xlsx`
); );
}; };
// 打开绘制范围
const addPunchRange = (item) => {
earthData.punchName = item.punchName
earthData.punchColor = item.punchColor
earthData.projectId = projectId.value
earthData.id = ''
earthData.punchRange = ''
if (earthData.punchName=='') {
proxy.$modal.msgError('请先填写打卡范围名称');
return
}
earthDialog.value.show()
}
// 接受绘制范围
const handleEarthData = (data) => {
ScopeFlag.value = false;
}
// 关闭绘制范围
const handleEarthClose = () => {
earthDialog.value.show = false
}
// 预览范围
const previewPunchRange = (item)=>{
earthData.id = item.id
earthData.projectId = item.projectId
earthData.punchName = item.punchName
earthData.punchColor = item.punchColor
earthData.punchRange = item.punchRange
earthDialog.value.show()
}
onMounted(() => { onMounted(() => {
getList(); getList();
}); });

View File

@ -0,0 +1,296 @@
<template>
<el-dialog v-model="visible" title="地球可视化" :width="dialogWidth" :height="dialogHeight" :before-close="handleClose"
destroy-on-close>
<div v-loading="loading" id="earthMap" class="earth-container"></div>
<template #footer>
<el-button type="warning" @click="redraw">重新绘制</el-button>
<el-button v-if="isDraw" type="success" @click="drawRange">绘制</el-button>
<el-button type="primary" @click="handlesubmit">确定</el-button>
<el-button type="danger" @click="handleClose">关闭</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, onMounted, watch, defineEmits, defineProps } from 'vue';
import { ElMessage } from 'element-plus';
import {
addAttendanceRange,
updateAttendanceRange,
} from '@/api/project/project';
const emit = defineEmits(['send-data', 'close']);
const props = defineProps({
position: {
type: String,
default: ''
},
data: {
type: Object,
default: () => { }
}
});
// 弹窗控制变量
const visible = ref(false);
const dialogWidth = ref('90%');
const dialogHeight = ref('90%');
let earthInstance = null;
let earthContainer = ref(null);
let loading = ref(true);
let positions = ref([]);
const entityObject = ref(null);
const isDraw = ref(true);
// 显示弹窗
const show = () => {
visible.value = true;
console.log(props.data);
if (props.data?.id) {
console.log(231, props.data);
isDraw.value = false;
}
};
// 关闭弹窗
const handleClose = () => {
visible.value = false;
// 清除地球实例
if (earthInstance && earthInstance.destroy) {
earthInstance.destroy();
earthInstance = null;
}
// 清除全局变量引用
if (window.Earth2) {
delete window.Earth2;
}
};
// 监听弹窗显示状态,初始化地球
watch(
() => visible.value,
(newVal) => {
if (newVal && earthContainer.value) {
createEarth();
}
}
);
// 创建地球
const createEarth = () => {
if (!window.YJ) {
ElMessage.error('YJ库未加载请检查依赖');
return;
}
window.YJ.on({
ws: true,
// host: getIP(), // 资源所在服务器地址
// username: this.loginForm.username, // 用户名
// password: md5pass, // 密码
}).then((res) => {
if (!earthContainer.value) return;
loading.value = false;
// 创建地球实例
earthInstance = new YJ.YJEarth(earthContainer.value);
window.Earth2 = earthInstance;
// 开启右键和左键点击事件
YJ.Global.openRightClick(window.Earth2);
YJ.Global.openLeftClick(window.Earth2);
// 设置初始视角
const view = {
position: {
lng: 102.03643298211526,
lat: 34.393586474501,
alt: 11298179.51993155
},
orientation: {
heading: 360,
pitch: -89.94481747201486,
roll: 0
}
};
YJ.Global.CesiumContainer(window.Earth2, {
compass: false, //罗盘
});
// 加载底图
loadBaseMap(earthInstance.viewer);
// 可以取消注释以下代码来设置初始视角
// YJ.Global.flyTo(earthInstance, view);
// YJ.Global.setDefaultView(earthInstance.viewer, view)
if (props.data.punchRange) {
renderRange(JSON.parse(props.data.punchRange));
}
})
.catch((err) => {
console.error('初始化地球失败:', err);
ElMessage.error('初始化地球失败,请稍后重试');
});
};
// 加载底图
const loadBaseMap = (viewer) => {
if (!viewer || !Cesium) {
ElMessage.error('Cesium库未加载请检查依赖');
return;
}
try {
// 创建瓦片提供器
const imageryProvider = new Cesium.UrlTemplateImageryProvider({
url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
fileExtension: 'png',
minimumLevel: 0,
maximumLevel: 18,
projection: Cesium.WebMercatorProjection,
credit: new Cesium.Credit('卫星图数据来源')
});
// 添加图层到视图
viewer.imageryLayers.addImageryProvider(imageryProvider);
} catch (err) {
console.error('加载底图失败:', err);
ElMessage.error('加载底图失败');
}
};
// 处理数据传递
const handlesubmit = () => {
if (!earthInstance || !earthInstance.viewer) {
ElMessage.warning('地球实例未初始化,无法获取数据');
return;
}
// 要传递的对象数据
const dataToSend = {
...props.data,
punchRange: JSON.stringify(positions.value),
};
if (props.data.id) {
updateAttendanceRange1(dataToSend)
} else {
addAttendanceRange1(dataToSend);
}
emit('send-data', dataToSend);
};
// 新增打卡范围接口
const addAttendanceRange1 = (data) => {
addAttendanceRange(data).then((res) => {
if (res.code === 200) {
ElMessage.success('新增打卡范围成功');
handleClose();
} else {
ElMessage.error(res.msg);
}
}).catch((err) => {
console.error('新增打卡范围失败:', err);
ElMessage.error('新增打卡范围失败');
});
};
// 修改打卡范围接口
const updateAttendanceRange1 = (data) => {
updateAttendanceRange(data).then((res) => {
if (res.code === 200) {
ElMessage.success('修改打卡范围成功');
handleClose();
} else {
ElMessage.error(res.msg);
}
}).catch((err) => {
console.error('修改打卡范围失败:', err);
ElMessage.error('修改打卡范围失败');
});
};
// 绘制范围
const drawRange = () => {
if (!earthInstance || !earthInstance.viewer) {
ElMessage.warning('地球实例未初始化,无法绘制');
return;
}
let draw = new YJ.Draw.DrawPolygon(window.Earth2);
draw.start((err, params) => {
if (err) {
console.error('绘制失败:', err);
ElMessage.error('绘制失败');
return;
}
console.log('绘制成功:', params);
positions.value = params;
renderRange(params);
});
}
// 渲染范围
const renderRange = (params) => {
let option = {
id: 'Checkinrange',
info: {
type: "richText",
text: "",
hrefs: "",
},
positions: params,
color: props.data.punchColor || "rgba(255,0,0,0.5)",
line: {
width: 3,
color: "rgba(255,0,0,1)",
},
type: 0,
show: true,
}
// PolygonObject
let entity = new YJ.Obj.PolygonObject(
window.Earth2,
option
);
entity.flyTo();
entityObject.value = entity
}
// 重新绘制
const redraw = () => {
if (entityObject.value) {
entityObject.value.remove();
}
drawRange();
}
// 组件挂载时设置容器ID
onMounted(() => {
if (!earthContainer.value) {
earthContainer.value = 'earthMap'; // 设置与初始化代码中相同的ID
}
});
// 暴露显示方法给父组件
defineExpose({
show
});
</script>
<style scoped>
.earth-container {
width: 100%;
height: 600px;
overflow: hidden;
position: relative;
}
:deep(.el-dialog__body) {
padding: 10px;
height: calc(100% - 100px);
overflow: hidden;
}
:deep(.el-dialog) {
display: flex;
flex-direction: column;
max-height: 90vh;
}
:deep(.el-dialog__content) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
</style>