随着社会的不断发现,现实生活中有很多时候会使用到全景现实,比如房地产行业vr看房,汽车行业vr看车之类的,全景可视化真实还原了现场的场景,真正做到沉浸式体验。
现在我们基于vue3.0版本开发出了一款沉浸式的编辑器,只需要使用全景相机在现场拍摄全景场景,然后将场景倒入编辑器,通过拖动图标和导航的方式绑定数据,从而实现沉浸式场景可视化。
部分洁面:
1、自定义动态添加数据绑定图标,实时监控数据运行状态
2、自定义添加文字标记,绑定文字文本,标识场景设备名称
3、自定义添加场景标记,点击可以切换不同场景视角
4、自定义添加地图经纬度标记,查看当前标记位置
5、自定义添加音视频标记,点击查看音视频播放
6、自定义添加网址和富文本标记,点击跳转网址查看富文本内容
部分代码如下:
(function (w) { // isFormat 表示是否格式化时间格式,,默认为格式化function $Date (isFormat = true) { // 格式化日期 前台传值方式 引用类.dateFormat(1402233166999,"yyyy-MM-dd hh:mm:ss")this.dateFormat = function (date, fmt = 'yyyy-MM-dd hh:mm:ss') {let getDate = new Date(date);let o = {'M+': getDate.getMonth() + 1,'d+': getDate.getDate(),'h+': getDate.getHours(),'m+': getDate.getMinutes(),'s+': getDate.getSeconds(),'q+': Math.floor((getDate.getMonth() + 3) / 3),'S': getDate.getMilliseconds()};if (/(y+)/.test(fmt)) {fmt = fmt.replace(RegExp.$1, (getDate.getFullYear() + '').substr(4 - RegExp.$1.length));}for (let k in o) {if (new RegExp('(' + k + ')').test(fmt)) {fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));}}return fmt;};// 当前日期时间this.now = isFormat ? this.dateFormat(new Date()) : new Date();// 当前日期this.date = this.dateFormat(new Date()).split(' ')[0];// 当前时间this.time = this.dateFormat(new Date()).split(' ')[1];// 当前月this.month = new Date().getMonth() + 1;// 当前消失this.hours = new Date().getHours();// 当前月天数this.monthDays = (() => {let nowMonth = new Date().getMonth(); // 当前月let nowYear = new Date().getYear(); // 当前年let monthStartDate = new Date(nowYear, nowMonth, 1);let monthEndDate = new Date(nowYear, nowMonth + 1, 1);let days = (monthEndDate - monthStartDate) / (1000 * 60 * 60 * 24);return days;})();// 本周的开始日期和结束日日期this.endDayOfWeek = (() => {let nowMonth = new Date().getMonth(); // 当前月let nowDay = new Date().getDate(); // 当前日let nowDayOfWeek = new Date().getDay(); // 今天本周的第几天let day = nowDayOfWeek || 7;const start = new Date(new Date().getFullYear(), nowMonth, nowDay - day + 1);const starts = new Date(new Date().getFullYear(), nowMonth, nowDay - day + 1);const end = new Date(new Date().getFullYear(), nowMonth, nowDay + 7 - day);const ends = new Date(new Date().getFullYear(), nowMonth, nowDay + 7 - day);starts.setHours(23);starts.setMinutes(59);starts.setSeconds(59);ends.setHours(23);ends.setMinutes(59);ends.setSeconds(59);const firstDay = isFormat ? this.dateFormat(start) : start;const firstDays = isFormat ? this.dateFormat(starts) : starts;const lastDay = isFormat ? this.dateFormat(end) : end;const lastDays = isFormat ? this.dateFormat(ends) : ends;return {firstDay, firstDays, lastDay, lastDays};})();// 当天开始时间this.todayBegin = (() => {const now = new Date();now.setHours(0);now.setMinutes(0);now.setSeconds(0);return isFormat ? this.dateFormat(now) : now;})();// 当天59时59分59秒this.todayEnd = (() => {const now = new Date();now.setHours(23);now.setMinutes(59);now.setSeconds(59);return isFormat ? this.dateFormat(now) : now;})();// 指定月的最后第一天和最后一天this.getNowTheMothEnd = (M) => {if (typeof M !== 'number') {throw new Error('输入数字');}if (M < 0 || M > 12) {console.error('日期大于1小于12');return false;}const now = new Date();let y = now.getFullYear();let m = M - 1;const firstDay = new Date(y, m, 1);const firstDays = new Date(y, m, 1);firstDays.setHours(23);firstDays.setMinutes(59);firstDays.setSeconds(59);const lastDay = new Date(y, m + 1, 0);const lastDays = new Date(y, m + 1, 0);lastDays.setHours(23);lastDays.setMinutes(59);lastDays.setSeconds(59);return {firstDay: isFormat ? this.dateFormat(firstDay) : firstDay,firstDays: isFormat ? this.dateFormat(firstDays) : firstDays,lastDay: isFormat ? this.dateFormat(lastDay) : lastDay,lastDays: isFormat ? this.dateFormat(lastDays) : lastDays};};// 当月的最后第一天和最后一天this.nowMothEnd = (() => {const now = new Date();let y = now.getFullYear();let m = now.getMonth();const firstDay = new Date(y, m, 1);const firstDays = new Date(y, m, 1);firstDays.setHours(23);firstDays.setMinutes(59);firstDays.setSeconds(59);const lastDay = new Date(y, m + 1, 0);const lastDays = new Date(y, m + 1, 0);lastDays.setHours(23);lastDays.setMinutes(59);lastDays.setSeconds(59);return {firstDay: isFormat ? this.dateFormat(firstDay) : firstDay,firstDays: isFormat ? this.dateFormat(firstDays) : firstDays,lastDay: isFormat ? this.dateFormat(lastDay) : lastDay,lastDays: isFormat ? this.dateFormat(lastDays) : lastDays};})();// 今年的第一天和今年的最后一天this.nowYearsEnd = (() => {const now = new Date();let y = now.getFullYear();let m = now.getMonth();console.log(m);const firstDay = new Date(y, 0, 1);const firstDays = new Date(y, 0, 1);firstDays.setHours(23);firstDays.setMinutes(59);firstDays.setSeconds(59);const lastDay = new Date(y, 12, 0);const lastDays = new Date(y, 12, 0);lastDays.setHours(23);lastDays.setMinutes(59);lastDays.setSeconds(59);return {firstDay: isFormat ? this.dateFormat(firstDay) : firstDay,firstDays: isFormat ? this.dateFormat(firstDays) : firstDays,lastDay: isFormat ? this.dateFormat(lastDay) : lastDay,lastDays: isFormat ? this.dateFormat(lastDays) : lastDays};})();// 指定年的第一天和今年的最后一天this.nowTheYearsEnd = (Y) => {const now = new Date();let y = Y;let m = now.getMonth();console.log(m);const firstDay = new Date(y, 0, 1);const firstDays = new Date(y, 0, 1);firstDays.setHours(23);firstDays.setMinutes(59);firstDays.setSeconds(59);const lastDay = new Date(y, 12, 0);const lastDays = new Date(y, 12, 0);lastDays.setHours(23);lastDays.setMinutes(59);lastDays.setSeconds(59);return {firstDay: isFormat ? this.dateFormat(firstDay) : firstDay,firstDays: isFormat ? this.dateFormat(firstDays) : firstDays,lastDay: isFormat ? this.dateFormat(lastDay) : lastDay,lastDays: isFormat ? this.dateFormat(lastDays) : lastDays};};// 当前时间最近前N天this.getNowBeforeNday = (N) => {const now = new Date().getTime();const before = new Date().getTime() - (24 * 60 * 60 * 1000) * N;return {now: isFormat ? this.dateFormat(new Date(now)) : new Date(now),before: isFormat ? this.dateFormat(new Date(before)) : new Date(before)};};// 当前时间后面N天this.getNowAfterNday = (N) => {const now = new Date().getTime();const after = new Date().getTime() + (24 * 60 * 60 * 1000) * N;return {now: isFormat ? this.dateFormat(new Date(now)) : new Date(now),after: isFormat ? this.dateFormat(new Date(after)) : new Date(after)};};}w.$Date = $Date;
})(window);
const date = window.$Date;
export const $Date = date;
<template><div class="edit-hot_wrapper"><el-dialogv-model="dialogVisible":title="`${hotSpot.id ? '修改' : '新增'}${spotTypeName(form.iconType)}标记`":width="'360px'":top="'10px'":modal="false":close-on-click-modal="false":custom-class="'edit-hot_dialog'":before-close="close"><el-form :model="form" ref="ruleForm" :rules="rules"><el-form-item label="标记名称" required prop="name"><el-input v-model="form.name" placeholder="输入名称" clearable/></el-form-item><!--基础图标绑定--><template v-if="form.iconType == 1"><el-form-item label="绑定数据" required><el-selectv-model="form.devices"placeholder="选择设备"filterablemultiplecollapse-tags><el-optionv-for="v in deviveData.data":label="v.name":value="v.id":key="v.id"></el-option></el-select></el-form-item><el-form-item label="标记图标" required prop="spotTypes"><el-image:class="['icon-types', form.spotTypes == v.id ? 'active' : '']"v-for="v in hotTypes":key="v.id":src="v.url"@click="form.spotTypes = v.id"/></el-form-item></template><!-- 文字图标绑定--><template v-if="form.iconType == 2"><el-form-item label="绑定数据" required prop="devices"><el-selectv-model="form.devices"placeholder="选择设备"filterablemultiplecollapse-tags><el-optionv-for="v in deviveData.data":label="v.name":value="v.id":key="v.id"></el-option></el-select></el-form-item><el-form-item label="文字内容" required prop="text"><el-inputv-model="form.text":rows="4"type="textarea"placeholder="输入文字内容"/></el-form-item></template><!-- 场景图标绑定--><template v-if="form.iconType == 3"><el-form-item label="绑定场景" required prop="sceneId"><el-selectv-model="form.sceneId"placeholder="选择场景"><el-optionv-for="v in scenes":label="v.name":value="v.id":key="v.id"></el-option></el-select></el-form-item></template><!-- 位置图标绑定--><template v-if="form.iconType == 4"><el-form-item label="地图位置" required prop="localtion"><el-input v-model="form.localtion" placeholder="输入经纬度" clearable/></el-form-item><el-form-item label="经纬度查询"><el-button type="success">查询</el-button></el-form-item></template><!-- 位置图标绑定--><template v-if="form.iconType == 5"><el-form-item label="媒体地址" required prop="media"><el-input v-model="form.media.url" placeholder="输入媒体地址" clearable/></el-form-item><el-form-item label="媒体类型" required><el-radio-group v-model="form.media.type" class="ml-4"><el-radio :label="0" size="large">音频</el-radio><el-radio :label="1" size="large">视频</el-radio></el-radio-group></el-form-item></template><!-- 网址图标绑定--><template v-if="form.iconType == 6"><el-form-item label="网站地址" required prop="website"><el-input v-model="form.website" placeholder="输入网址" clearable/></el-form-item></template><!-- 网址图标绑定--><template v-if="form.iconType == 7"><el-form-item label="富文本" required prop="html"><el-input v-model="form.html" :rows="5" type="textarea" placeholder="输入网址" clearable/></el-form-item></template></el-form><template #footer><span class="dialog-footer"><el-button @click="close">取消</el-button><el-button type="primary" @click="save(ruleForm)">保存</el-button></span></template></el-dialog></div>
</template><script setup>
import { res } from '../data/res.js';
// import store from '../../../store/index.js';
import { hotTypes } from '../utils/data.js';
import { defineEmits, defineProps } from 'vue';
import { reactive, ref, computed, onMounted } from 'vue';
import { useStore } from 'vuex';
import { ElMessage } from 'element-plus';
import store from '../vuex/vrViewer.js';
import { ICOM_TYPE } from '../utils/data.js';const ruleForm = ref(null);
const rules = reactive({name: [{required: true,trigger: 'change',message: '标记名称必填'}],devices: [{required: true,trigger: 'change',message: '绑定设备必填'}],spotTypes: [{required: true,trigger: 'change',message: '标记图标类型必填'}],text: [{required: true,trigger: 'change',message: '文本内容必填'}],sceneId: [{required: true,trigger: 'change',message: '绑定场景必填'}],localtion: [{required: true,trigger: 'change',message: '绑定位置必填'}],media: [{required: true,trigger: 'change',message: '媒体地址必填'}],website: [{required: true,trigger: 'change',message: '网站地址必填'}],html: [{required: true,trigger: 'change',message: '富文本内容必填'}],
});
const dialogVisible = ref(true);
const deviveData = ref(res);
const props = defineProps({hotSpot: {}, // 当前标记scenes: {}
});let stores = useStore();
// 当前标记类型id
const iconType = computed(() => {return stores.state.vrViewer.type;
});// 当前标记类型名称
const spotTypeName = () => {const list = Object.values(ICOM_TYPE);return list.find(v => v.type == form.value.iconType).name;
};const types = ref(hotTypes);
const emits = defineEmits(['success','close','save'
]);onMounted(() => {// 编辑回显示if (props.hotSpot.id) {form.value = props.hotSpot.data;// store.commit('vrViewer/setIconType', props.hotSpot.data.iconType);}
});const form = ref({id: '', // 标记idname: '',devices: [],spotTypes: types.value[0].id, // 图标类型spotUrl: types.value[0].url, // 标记图表urliconType: stores.state.vrViewer.type,text: '', // 文字绑定sceneId: '', // 场景绑定localtion: '', // 位置经纬度绑定media: {type: 0, // 0代表音频, 1代表视频url: '' // url播放地址},website: '', // 网址html: '', // html绑定
});const close = () => emits('close');const save = async (formEl) => {// if (!form.value.name) {// // 其他类型报错// ElMessage({// showClose: true,// message: '输入名称',// type: 'error'// });// return;// }// if (!form.value.devices.length) {// // 其他类型报错// ElMessage({// showClose: true,// message: '选择设备',// type: 'error'// });// return;// }console.log(formEl, 'formEl');await formEl.validate((valid, fields) => {if (valid) {emits('save', form.value, props.hotSpot);close();} else {console.log('error submit!', fields)}})
};const success = () => {emits('success');
};
</script>
<style lang="scss">.edit-hot_dialog {.el-dialog__body {padding: 10px 20px;.el-select {width: 245px;}}}
</style>
<style lang="scss" scoped>.icon-types {width: 36px;height: 35px;margin: 2px;border: 1px solid #ccc;user-select: none;&.active {border: 2px solid blue;}}
</style>