Commit 5476806eb72f67274127e32c30bdf24a62e10918

Authored by fengwotao
1 parent fb6eafbb

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

... ... @@ -47,7 +47,7 @@ const emits = defineEmits(['fileStaticUri'])
47 47
48 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 52 const extname = (filename: string) => {
53 53 if (!filename || typeof filename != 'string') {
... ...
... ... @@ -5,8 +5,7 @@ import cloneDeep from 'lodash/cloneDeep'
5 5 import { chartInitConfig } from '@/settings/designSetting'
6 6
7 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 9 backgroundColor: '', //场景背景色
11 10 backgroundAlpha: 0, //场景透明度
12 11 enableDamping: false, //是否启用阻尼
... ... @@ -19,14 +18,77 @@ export const option = {
19 18 */
20 19 outputEncoding: 'liner',
21 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 50 mtlPath: [], //.mtl材质路径,比如搭配obj使用
24 51 textureImage: [], //jpg/png 材质路径
25 52 borderConfig: {
26 53 color: 'grey',
27 54 size: 1,
28 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 94 export default class Config extends PublicConfigClass implements CreateComponentType {
... ...
... ... @@ -4,42 +4,55 @@
4 4 <setting-item name="颜色">
5 5 <n-color-picker size="small" :show-alpha="false" v-model:value="optionData.borderConfig.color"></n-color-picker>
6 6 </setting-item>
7   - <setting-item name="开启">
  7 + <setting-item name="开启边框">
8 8 <n-switch v-model:value="optionData.borderConfig.show" size="small" />
9 9 </setting-item>
10 10 <setting-item name="大小">
11 11 <n-input-number :min="0" v-model:value="optionData.borderConfig.size" size="small" />
12 12 </setting-item>
13 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 25 <setting-item-box :alone="true">
15 26 <template #name>
16   - <n-text>提示</n-text>
  27 + <n-text>mtl</n-text>
17 28 <n-tooltip trigger="hover">
18 29 <template #trigger>
19 30 <n-icon size="21" :depth="3">
20 31 <help-outline-icon></help-outline-icon>
21 32 </n-icon>
22 33 </template>
23   - <span class="help-span">{{ threeFileHelpMessgae }}</span>
  34 + <span class="help-span">{{ threeMtlHelpMessgae }}</span>
24 35 </n-tooltip>
25 36 </template>
26 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 38 :fileList="optionData.mtlPath"
37 39 :max="100"
38 40 :threeSupportFileFormat="supportFileMtl"
39 41 @fileStaticUri="handleFileMtlStaticUri"
40 42 />
41 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 56 <FileUpload
44 57 :fileList="optionData.textureImage"
45 58 :max="100"
... ... @@ -47,6 +60,113 @@
47 60 @fileStaticUri="handleFileTextureImageStaticUri"
48 61 />
49 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 170 <setting-item-box name="属性配置">
51 171 <setting-item name="场景色(需要这种格式HEX #000000,否则失效)">
52 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 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 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 245 const threeSupportFileFormat = ['fbx', 'obj', 'gltf', 'stl', 'dae', 'glb', 'ply', 'json']
99 246
... ... @@ -101,17 +248,21 @@ const supportFileMtl = ['mtl']
101 248
102 249 const supportFileTextureImage = ['jpg', 'png']
103 250
104   -const handleChange = (e: boolean) => {
105   - if (e) props.optionData.dataset = ''
106   -}
107   -
108 251 const encodinghList = [
109 252 { label: 'linear', value: 'linear' },
110 253 { label: 'sRGB ', value: 'sRGB ' }
111 254 ]
112 255
  256 +const handleChange = (e: boolean) => {
  257 + if (e) props.optionData.dataset = ['']
  258 +}
  259 +
113 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 268 const handleFileMtlStaticUri = (value: UploadFileInfo[]) => {
... ...
1 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 4 <vue3dLoader
5 5 ref="vue3dLoaderRef"
6 6 :webGLRendererOptions="webGLRendererOptions"
... ... @@ -18,7 +18,11 @@
18 18 :dampingFactor="dampingFactor"
19 19 @process="onProcess"
20 20 @load="onLoad"
21   - @click="onClick"
  21 + :position="position"
  22 + :rotation="rotation"
  23 + :lights="lights"
  24 + :showFps="showFps"
  25 + :labels="labels"
22 26 />
23 27 <div v-show="show" class="process">
24 28 <span> 拼命加载中... </span>
... ... @@ -29,10 +33,11 @@
29 33 </div>
30 34 </template>
31 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 37 import { CreateComponentType } from '@/packages/index.d'
34 38 import { vue3dLoader } from 'vue-3d-loader'
35 39 import { useDesignStore } from '@/store/modules/designStore/designStore'
  40 +import { useDetectWebGLContext } from '@/utils/external/useSupportWebGL'
36 41
37 42 const designStore = useDesignStore()
38 43
... ... @@ -43,7 +48,6 @@ const props = defineProps({
43 48 }
44 49 })
45 50
46   -// 颜色
47 51 const themeColor = computed(() => {
48 52 return designStore.getAppTheme
49 53 })
... ... @@ -74,27 +78,6 @@ const onProcess = (event: any) => {
74 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 81 const { w, h } = toRefs(props.chartConfig.attr)
99 82
100 83 const {
... ... @@ -108,12 +91,17 @@ const {
108 91 outputEncoding,
109 92 clearScene,
110 93 dampingFactor,
111   - borderConfig
  94 + borderConfig,
  95 + position,
  96 + rotation,
  97 + lights,
  98 + showFps,
  99 + labels
112 100 } = toRefs(props.chartConfig.option) as any
113 101
114 102 watch(dataset, (newData: string) => {
115 103 //dateset为空则清除场景
116   - if(!newData)clearScene.value=true
  104 + if(!newData) clearScene.value=true
117 105 })
118 106 </script>
119 107
... ... @@ -123,7 +111,6 @@ watch(dataset, (newData: string) => {
123 111 border-width: v-bind('borderConfig.size + "px"');
124 112 border-style: solid;
125 113 border-color: v-bind('borderConfig.color');
126   - // border-color:v-bind('borderConfig.color');
127 114 .process {
128 115 position: absolute;
129 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 +}
... ...