Commit 5476806eb72f67274127e32c30bdf24a62e10918

Authored by fengwotao
1 parent fb6eafbb

fix(src/packages/): 小组件三维模型新增设置模型位置,旋转位置,灯光配置,label标点配置(支持多个label)

@@ -47,7 +47,7 @@ const emits = defineEmits(['fileStaticUri']) @@ -47,7 +47,7 @@ const emits = defineEmits(['fileStaticUri'])
47 47
48 const fileList = ref<UploadFileInfo[]>([]) 48 const fileList = ref<UploadFileInfo[]>([])
49 49
50 -const fileSizeMsg = ref(`文件需小于 ${props.fileSizeConst}M;格式为${props.threeSupportFileFormat.join(',')}的文件`) 50 +const fileSizeMsg = ref(`文件需小于 ${props.fileSizeConst}M;格式为${props.threeSupportFileFormat.join(',')}的文件,支持传多个`)
51 51
52 const extname = (filename: string) => { 52 const extname = (filename: string) => {
53 if (!filename || typeof filename != 'string') { 53 if (!filename || typeof filename != 'string') {
@@ -5,8 +5,7 @@ import cloneDeep from 'lodash/cloneDeep' @@ -5,8 +5,7 @@ import cloneDeep from 'lodash/cloneDeep'
5 import { chartInitConfig } from '@/settings/designSetting' 5 import { chartInitConfig } from '@/settings/designSetting'
6 6
7 export const option = { 7 export const option = {
8 - //vue3dLoader支持数组或字符串,暂且绑定字符串,这个插件可以加载多个模型  
9 - dataset: new URL('/src/assets/external/three/test.obj', import.meta.url).href, 8 + dataset: [new URL('/src/assets/external/three/test.obj', import.meta.url).href],//三维数据源
10 backgroundColor: '', //场景背景色 9 backgroundColor: '', //场景背景色
11 backgroundAlpha: 0, //场景透明度 10 backgroundAlpha: 0, //场景透明度
12 enableDamping: false, //是否启用阻尼 11 enableDamping: false, //是否启用阻尼
@@ -19,14 +18,77 @@ export const option = { @@ -19,14 +18,77 @@ export const option = {
19 */ 18 */
20 outputEncoding: 'liner', 19 outputEncoding: 'liner',
21 clearScene: false, //是否清空场景内容 20 clearScene: false, //是否清空场景内容
22 - lights: [], //灯光,暂且没实现 21 + lights: [//灯光为数组,type 为 环境光(AmbientLight) | 方向光(DirectionalLight) | 点光(PointLight) | 半球光(HemisphereLight)
  22 + {
  23 + type: 'AmbientLight',
  24 + label: '环境光(只有颜色)',
  25 + color: 'red',
  26 + position: { x: 100, y: 10, z: 100 }
  27 + },
  28 + {
  29 + type: 'DirectionalLight',
  30 + label: '方向光(可配置颜色,发光位置,光源强度)',
  31 + color: 'green',
  32 + position: { x: 100, y: 10, z: 100 },
  33 + intensity: 0.8
  34 + },
  35 + {
  36 + type: 'PointLight',
  37 + label: '点光(可配置颜色,发光位置,光源强度)',
  38 + color: '#000000',
  39 + position: { x: 200, y: -200, z: 100 },
  40 + intensity: 1
  41 + },
  42 + {
  43 + type: 'HemisphereLight',
  44 + label: '半球光(可配置从天空发出的光线的颜色,从地面发出的光线的颜色,发光位置)',
  45 + position: { x: 200, y: -200, z: 100 },
  46 + skyColor: '#00FF00',
  47 + groundColor: '#000000'
  48 + }
  49 + ],
23 mtlPath: [], //.mtl材质路径,比如搭配obj使用 50 mtlPath: [], //.mtl材质路径,比如搭配obj使用
24 textureImage: [], //jpg/png 材质路径 51 textureImage: [], //jpg/png 材质路径
25 borderConfig: { 52 borderConfig: {
26 color: 'grey', 53 color: 'grey',
27 size: 1, 54 size: 1,
28 show: false 55 show: false
29 - } 56 + },
  57 + position: [//模型位置
  58 + {
  59 + x: 0,
  60 + y: 0,
  61 + z: 0
  62 + }
  63 + ],
  64 + rotation: [//模型旋转
  65 + {
  66 + x: 0,
  67 + y: 0,
  68 + z: 0
  69 + }
  70 + ],
  71 + showFps:false,//是否显示fps
  72 + labels:[ //添加图片/文字标签,暂且支持文字
  73 + {
  74 + image: "",
  75 + text: "",
  76 + textStyle: {
  77 + fontFamily: "Arial",
  78 + fontSize: 18,
  79 + fontWeight: "normal",
  80 + lineHeight: 1,
  81 + color: "#ffffff",
  82 + borderWidth: 8,
  83 + borderRadius: 4,
  84 + borderColor: "rgba(0,0,0,1)",
  85 + backgroundColor: "rgba(0, 0, 0, 1)"
  86 + },
  87 + position: {x:0, y:0, z:0},
  88 + scale:{x:1, y:1, z:0},
  89 + sid: null
  90 + }
  91 + ]
30 } 92 }
31 93
32 export default class Config extends PublicConfigClass implements CreateComponentType { 94 export default class Config extends PublicConfigClass implements CreateComponentType {
@@ -4,42 +4,55 @@ @@ -4,42 +4,55 @@
4 <setting-item name="颜色"> 4 <setting-item name="颜色">
5 <n-color-picker size="small" :show-alpha="false" v-model:value="optionData.borderConfig.color"></n-color-picker> 5 <n-color-picker size="small" :show-alpha="false" v-model:value="optionData.borderConfig.color"></n-color-picker>
6 </setting-item> 6 </setting-item>
7 - <setting-item name="开启"> 7 + <setting-item name="开启边框">
8 <n-switch v-model:value="optionData.borderConfig.show" size="small" /> 8 <n-switch v-model:value="optionData.borderConfig.show" size="small" />
9 </setting-item> 9 </setting-item>
10 <setting-item name="大小"> 10 <setting-item name="大小">
11 <n-input-number :min="0" v-model:value="optionData.borderConfig.size" size="small" /> 11 <n-input-number :min="0" v-model:value="optionData.borderConfig.size" size="small" />
12 </setting-item> 12 </setting-item>
13 </setting-item-box> 13 </setting-item-box>
  14 + <setting-item-box name="上传文件">
  15 + <setting-item>
  16 + <FileUpload
  17 + :max="100"
  18 + :fileList="optionData.dataset"
  19 + :threeSupportFileFormat="threeSupportFileFormat"
  20 + :singleFileType="singleFileTypeNotMtl"
  21 + @fileStaticUri="handleFileStaticUri"
  22 + />
  23 + </setting-item>
  24 + </setting-item-box>
14 <setting-item-box :alone="true"> 25 <setting-item-box :alone="true">
15 <template #name> 26 <template #name>
16 - <n-text>提示</n-text> 27 + <n-text>mtl</n-text>
17 <n-tooltip trigger="hover"> 28 <n-tooltip trigger="hover">
18 <template #trigger> 29 <template #trigger>
19 <n-icon size="21" :depth="3"> 30 <n-icon size="21" :depth="3">
20 <help-outline-icon></help-outline-icon> 31 <help-outline-icon></help-outline-icon>
21 </n-icon> 32 </n-icon>
22 </template> 33 </template>
23 - <span class="help-span">{{ threeFileHelpMessgae }}</span> 34 + <span class="help-span">{{ threeMtlHelpMessgae }}</span>
24 </n-tooltip> 35 </n-tooltip>
25 </template> 36 </template>
26 <FileUpload 37 <FileUpload
27 - :max="1"  
28 - :fileList="[optionData.dataset]"  
29 - :threeSupportFileFormat="threeSupportFileFormat"  
30 - :singleFileType="singleFileTypeNotMtl"  
31 - @fileStaticUri="handleFileStaticUri"  
32 - />  
33 - </setting-item-box>  
34 - <setting-item-box name="mtl材质">  
35 - <FileUpload  
36 :fileList="optionData.mtlPath" 38 :fileList="optionData.mtlPath"
37 :max="100" 39 :max="100"
38 :threeSupportFileFormat="supportFileMtl" 40 :threeSupportFileFormat="supportFileMtl"
39 @fileStaticUri="handleFileMtlStaticUri" 41 @fileStaticUri="handleFileMtlStaticUri"
40 /> 42 />
41 </setting-item-box> 43 </setting-item-box>
42 - <setting-item-box name="材质贴图"> 44 + <setting-item-box :alone="true">
  45 + <template #name>
  46 + <n-text>贴图</n-text>
  47 + <n-tooltip trigger="hover">
  48 + <template #trigger>
  49 + <n-icon size="21" :depth="3">
  50 + <help-outline-icon></help-outline-icon>
  51 + </n-icon>
  52 + </template>
  53 + <span class="help-span">{{ threeTextureHelpMessgae }}</span>
  54 + </n-tooltip>
  55 + </template>
43 <FileUpload 56 <FileUpload
44 :fileList="optionData.textureImage" 57 :fileList="optionData.textureImage"
45 :max="100" 58 :max="100"
@@ -47,6 +60,113 @@ @@ -47,6 +60,113 @@
47 @fileStaticUri="handleFileTextureImageStaticUri" 60 @fileStaticUri="handleFileTextureImageStaticUri"
48 /> 61 />
49 </setting-item-box> 62 </setting-item-box>
  63 + <setting-item-box name="模型位置">
  64 + <setting-item name="模型位置坐标(x,y,z)">
  65 + <template v-for="(item, index) in optionData.position" :key="index">
  66 + <n-input-number v-model:value="item.x" size="small" />
  67 + <n-input-number v-model:value="item.y" size="small" />
  68 + <n-input-number v-model:value="item.z" size="small" />
  69 + </template>
  70 + </setting-item>
  71 + </setting-item-box>
  72 + <setting-item-box name="模型旋转">
  73 + <setting-item name="模型旋转坐标(x,y,z)">
  74 + <template v-for="(item, index) in optionData.rotation" :key="index">
  75 + <n-input-number v-model:value="item.x" size="small" />
  76 + <n-input-number v-model:value="item.y" size="small" />
  77 + <n-input-number v-model:value="item.z" size="small" />
  78 + </template>
  79 + </setting-item>
  80 + </setting-item-box>
  81 + <setting-item-box name="灯光配置">
  82 + <setting-item v-for="(item, index) in optionData.lights" :name="item.label" :key="index">
  83 + <n-color-picker
  84 + v-if="!includeHemisphereLight.includes(item.type)"
  85 + size="small"
  86 + :show-alpha="false"
  87 + v-model:value="item.color"
  88 + ></n-color-picker>
  89 + <n-color-picker
  90 + v-if="includeHemisphereLight.includes(item.type)"
  91 + size="small"
  92 + :show-alpha="false"
  93 + v-model:value="item.skyColor"
  94 + ></n-color-picker>
  95 + <n-color-picker
  96 + v-if="includeHemisphereLight.includes(item.type)"
  97 + size="small"
  98 + :show-alpha="false"
  99 + v-model:value="item.groundColor"
  100 + ></n-color-picker>
  101 + <template v-if="!includeAmbientLight.includes(item.type)">
  102 + <n-input-number v-model:value="item.position.x" size="small" />
  103 + <n-input-number v-model:value="item.position.y" size="small" />
  104 + <n-input-number v-model:value="item.position.z" size="small" />
  105 + </template>
  106 + <n-input-number
  107 + v-if="includeDirectionalLightAndPointLight.includes(item.type)"
  108 + v-model:value="item.intensity"
  109 + size="small"
  110 + />
  111 + </setting-item>
  112 + </setting-item-box>
  113 + <setting-item-box>
  114 + <SettingItem name="启用fps">
  115 + <n-switch v-model:value="optionData.showFps" size="small" />
  116 + </SettingItem>
  117 + </setting-item-box>
  118 + <setting-item-box name="label配置">
  119 + <setting-item v-for="(item, index) in optionData.labels" :key="index">
  120 + <div>
  121 + <span>文字</span>
  122 + <n-input
  123 + type="text"
  124 + placeholder="请输入"
  125 + size="small"
  126 + clearable
  127 + show-count
  128 + v-model:value="item.text"
  129 + ></n-input>
  130 + </div>
  131 + <div>
  132 + <span>位置</span>
  133 + <n-input-number v-model:value="item.position.x" size="small" />
  134 + <n-input-number v-model:value="item.position.y" size="small" />
  135 + <n-input-number v-model:value="item.position.z" size="small" />
  136 + </div>
  137 + <div>
  138 + <span>缩放</span>
  139 + <n-input-number v-model:value="item.scale.x" size="small" />
  140 + <n-input-number v-model:value="item.scale.y" size="small" />
  141 + <n-input-number v-model:value="item.scale.z" size="small" />
  142 + </div>
  143 + <div>
  144 + <span>文字样式</span>
  145 + <span>颜色</span>
  146 + <n-color-picker size="small" :show-alpha="false" v-model:value="item.textStyle.color"></n-color-picker>
  147 + <span>字体</span>
  148 + <n-input-number v-model:value="item.textStyle.fontSize" size="small" />
  149 + <span>行高</span>
  150 + <n-input-number v-model:value="item.textStyle.lineHeight" size="small" />
  151 + <span>背景颜色</span>
  152 + <n-color-picker
  153 + size="small"
  154 + :show-alpha="false"
  155 + v-model:value="item.textStyle.backgroundColor"
  156 + ></n-color-picker>
  157 + <span>边框颜色</span>
  158 + <n-color-picker size="small" :show-alpha="false" v-model:value="item.textStyle.borderColor"></n-color-picker>
  159 + <span>边框大小</span>
  160 + <n-input-number v-model:value="item.textStyle.borderWidth" size="small" />
  161 + <span>边框圆角</span>
  162 + <n-input-number v-model:value="item.textStyle.borderRadius" size="small" />
  163 + </div>
  164 + <div>
  165 + <n-button v-if="optionData.labels.length > 1" @click="optionData.labels.splice(index, 1)"> - </n-button>
  166 + </div>
  167 + </setting-item>
  168 + <n-button v-if="optionData.labels.length < 4" @click="optionData.labels.push(pushItem)"> + </n-button>
  169 + </setting-item-box>
50 <setting-item-box name="属性配置"> 170 <setting-item-box name="属性配置">
51 <setting-item name="场景色(需要这种格式HEX #000000,否则失效)"> 171 <setting-item name="场景色(需要这种格式HEX #000000,否则失效)">
52 <n-color-picker size="small" :show-alpha="false" v-model:value="optionData.backgroundColor"></n-color-picker> 172 <n-color-picker size="small" :show-alpha="false" v-model:value="optionData.backgroundColor"></n-color-picker>
@@ -91,9 +211,36 @@ const props = defineProps({ @@ -91,9 +211,36 @@ const props = defineProps({
91 } 211 }
92 }) 212 })
93 213
  214 +const pushItem = {
  215 + image: '',
  216 + text: '',
  217 + textStyle: {
  218 + fontFamily: 'Arial',
  219 + fontSize: 18,
  220 + fontWeight: 'normal',
  221 + lineHeight: 1,
  222 + color: '#ffffff',
  223 + borderWidth: 8,
  224 + borderRadius: 4,
  225 + borderColor: 'rgba(0,0,0,1)',
  226 + backgroundColor: 'rgba(0, 0, 0, 1)'
  227 + },
  228 + position: { x: 0, y: 0, z: 0 },
  229 + scale: { x: 1, y: 1, z: 0 },
  230 + sid: null
  231 +}
  232 +
  233 +const includeHemisphereLight = ['HemisphereLight']
  234 +
  235 +const includeAmbientLight = ['AmbientLight']
  236 +
  237 +const includeDirectionalLightAndPointLight = ['DirectionalLight', 'PointLight']
  238 +
94 const singleFileTypeNotMtl = ref('') 239 const singleFileTypeNotMtl = ref('')
95 240
96 -const threeFileHelpMessgae = ref('如果格式为obj,则上传顺序是,先上传材质贴图(png或jpg)、mtl材质文件,最后是obj') 241 +const threeMtlHelpMessgae = ref('mtl一般和obj搭配使用')
  242 +
  243 +const threeTextureHelpMessgae = ref('贴图目前支持jpg/png格式')
97 244
98 const threeSupportFileFormat = ['fbx', 'obj', 'gltf', 'stl', 'dae', 'glb', 'ply', 'json'] 245 const threeSupportFileFormat = ['fbx', 'obj', 'gltf', 'stl', 'dae', 'glb', 'ply', 'json']
99 246
@@ -101,17 +248,21 @@ const supportFileMtl = ['mtl'] @@ -101,17 +248,21 @@ const supportFileMtl = ['mtl']
101 248
102 const supportFileTextureImage = ['jpg', 'png'] 249 const supportFileTextureImage = ['jpg', 'png']
103 250
104 -const handleChange = (e: boolean) => {  
105 - if (e) props.optionData.dataset = ''  
106 -}  
107 -  
108 const encodinghList = [ 251 const encodinghList = [
109 { label: 'linear', value: 'linear' }, 252 { label: 'linear', value: 'linear' },
110 { label: 'sRGB ', value: 'sRGB ' } 253 { label: 'sRGB ', value: 'sRGB ' }
111 ] 254 ]
112 255
  256 +const handleChange = (e: boolean) => {
  257 + if (e) props.optionData.dataset = ['']
  258 +}
  259 +
113 const handleFileStaticUri = (value: UploadFileInfo[]) => { 260 const handleFileStaticUri = (value: UploadFileInfo[]) => {
114 - props.optionData.dataset = value[0]?.url as string 261 + props.optionData.dataset = value.map(item => item?.url)?.filter(Boolean) as any
  262 + if (Array.isArray(props.optionData.dataset) && props.optionData.dataset.length === 0) {
  263 + //filePath数组必须有值
  264 + props.optionData.dataset = ['demo.obj']
  265 + }
115 } 266 }
116 267
117 const handleFileMtlStaticUri = (value: UploadFileInfo[]) => { 268 const handleFileMtlStaticUri = (value: UploadFileInfo[]) => {
1 <template> 1 <template>
2 - <div class="go-content-box" :style="{ border: !borderConfig.show ? 'none' : '' }">  
3 - <div v-if="supportWebGL"> 2 + <div class="go-content-box" :style="{ border: !borderConfig.show ? 'none' : ''}">
  3 + <div v-if="useDetectWebGLContext()">
4 <vue3dLoader 4 <vue3dLoader
5 ref="vue3dLoaderRef" 5 ref="vue3dLoaderRef"
6 :webGLRendererOptions="webGLRendererOptions" 6 :webGLRendererOptions="webGLRendererOptions"
@@ -18,7 +18,11 @@ @@ -18,7 +18,11 @@
18 :dampingFactor="dampingFactor" 18 :dampingFactor="dampingFactor"
19 @process="onProcess" 19 @process="onProcess"
20 @load="onLoad" 20 @load="onLoad"
21 - @click="onClick" 21 + :position="position"
  22 + :rotation="rotation"
  23 + :lights="lights"
  24 + :showFps="showFps"
  25 + :labels="labels"
22 /> 26 />
23 <div v-show="show" class="process"> 27 <div v-show="show" class="process">
24 <span> 拼命加载中... </span> 28 <span> 拼命加载中... </span>
@@ -29,10 +33,11 @@ @@ -29,10 +33,11 @@
29 </div> 33 </div>
30 </template> 34 </template>
31 <script setup lang="ts"> 35 <script setup lang="ts">
32 -import { PropType, toRefs, ref, onMounted, nextTick, computed, watch } from 'vue' 36 +import { PropType, toRefs, ref, nextTick, computed, watch } from 'vue'
33 import { CreateComponentType } from '@/packages/index.d' 37 import { CreateComponentType } from '@/packages/index.d'
34 import { vue3dLoader } from 'vue-3d-loader' 38 import { vue3dLoader } from 'vue-3d-loader'
35 import { useDesignStore } from '@/store/modules/designStore/designStore' 39 import { useDesignStore } from '@/store/modules/designStore/designStore'
  40 +import { useDetectWebGLContext } from '@/utils/external/useSupportWebGL'
36 41
37 const designStore = useDesignStore() 42 const designStore = useDesignStore()
38 43
@@ -43,7 +48,6 @@ const props = defineProps({ @@ -43,7 +48,6 @@ const props = defineProps({
43 } 48 }
44 }) 49 })
45 50
46 -// 颜色  
47 const themeColor = computed(() => { 51 const themeColor = computed(() => {
48 return designStore.getAppTheme 52 return designStore.getAppTheme
49 }) 53 })
@@ -74,27 +78,6 @@ const onProcess = (event: any) => { @@ -74,27 +78,6 @@ const onProcess = (event: any) => {
74 process.value = Math.floor((event.loaded / event.total) * 100) 78 process.value = Math.floor((event.loaded / event.total) * 100)
75 } 79 }
76 80
77 -const onClick = (event: any) => {  
78 - console.log(event)  
79 -}  
80 -  
81 -//判断浏览器是否支持WebGL  
82 -const supportWebGL = ref(true)  
83 -const detectWebGLContext = () => {  
84 - let canvas = document.createElement('canvas')  
85 - let gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')  
86 - if (gl && gl instanceof WebGLRenderingContext) {  
87 - supportWebGL.value = true  
88 - } else {  
89 - supportWebGL.value = false  
90 - }  
91 -}  
92 -  
93 -onMounted(() => {  
94 - detectWebGLContext()  
95 - console.log(`实例`, vue3dLoaderRef.value)  
96 -})  
97 -  
98 const { w, h } = toRefs(props.chartConfig.attr) 81 const { w, h } = toRefs(props.chartConfig.attr)
99 82
100 const { 83 const {
@@ -108,12 +91,17 @@ const { @@ -108,12 +91,17 @@ const {
108 outputEncoding, 91 outputEncoding,
109 clearScene, 92 clearScene,
110 dampingFactor, 93 dampingFactor,
111 - borderConfig 94 + borderConfig,
  95 + position,
  96 + rotation,
  97 + lights,
  98 + showFps,
  99 + labels
112 } = toRefs(props.chartConfig.option) as any 100 } = toRefs(props.chartConfig.option) as any
113 101
114 watch(dataset, (newData: string) => { 102 watch(dataset, (newData: string) => {
115 //dateset为空则清除场景 103 //dateset为空则清除场景
116 - if(!newData)clearScene.value=true 104 + if(!newData) clearScene.value=true
117 }) 105 })
118 </script> 106 </script>
119 107
@@ -123,7 +111,6 @@ watch(dataset, (newData: string) => { @@ -123,7 +111,6 @@ watch(dataset, (newData: string) => {
123 border-width: v-bind('borderConfig.size + "px"'); 111 border-width: v-bind('borderConfig.size + "px"');
124 border-style: solid; 112 border-style: solid;
125 border-color: v-bind('borderConfig.color'); 113 border-color: v-bind('borderConfig.color');
126 - // border-color:v-bind('borderConfig.color');  
127 .process { 114 .process {
128 position: absolute; 115 position: absolute;
129 top: 50%; 116 top: 50%;
  1 +/**
  2 + * 判断浏览器是否支持webgl
  3 + */
  4 +
  5 +export const useDetectWebGLContext = () => {
  6 + const canvas = document.createElement('canvas')
  7 + const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
  8 + if (gl && gl instanceof WebGLRenderingContext) {
  9 + return true
  10 + } else {
  11 + return false
  12 + }
  13 +}