@shijiu/jsview-vue 2.0.1021 → 2.0.1073

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/package.json +4 -3
  2. package/utils/JsViewEngineWidget/CheckType.js +82 -0
  3. package/utils/JsViewEngineWidget/MetroWidget/AnimationManager.ts +72 -0
  4. package/utils/JsViewEngineWidget/MetroWidget/Const.ts +24 -0
  5. package/utils/JsViewEngineWidget/MetroWidget/ListWidget.vue +295 -0
  6. package/utils/JsViewEngineWidget/MetroWidget/MetroWidget.vue +110 -1651
  7. package/utils/JsViewEngineWidget/MetroWidget/MetroWidgetSetup.js +1867 -0
  8. package/utils/JsViewEngineWidget/MetroWidget/PageUpdater.ts +111 -0
  9. package/utils/JsViewEngineWidget/MetroWidget/RenderItem.ts +153 -0
  10. package/utils/JsViewEngineWidget/MetroWidget/VisibleInfo.ts +43 -0
  11. package/utils/JsViewEngineWidget/MetroWidget/WidgetRectInfo.ts +49 -0
  12. package/utils/JsViewEngineWidget/TemplateParser/CommonMetroTemplate.ts +1424 -0
  13. package/utils/JsViewEngineWidget/TemplateParser/Fence.ts +135 -0
  14. package/utils/JsViewEngineWidget/TemplateParser/ListMetroTemplate.ts +177 -0
  15. package/utils/JsViewEngineWidget/TemplateParser/MetroTemplate.ts +334 -0
  16. package/utils/JsViewEngineWidget/TemplateParser/TemplateItemAdder.ts +147 -0
  17. package/utils/JsViewEngineWidget/TemplateParser/index.ts +4 -0
  18. package/utils/JsViewEngineWidget/{WidgetCommon.js → WidgetCommon.ts} +64 -71
  19. package/utils/JsViewEngineWidget/index.js +2 -1
  20. package/utils/JsViewPlugin/JsvAudio/AudioProxy.js +26 -1
  21. package/utils/JsViewPlugin/JsvAudio/JsvAudio.vue +120 -133
  22. package/utils/JsViewPlugin/JsvAudio/JsvAudioBrowser.vue +11 -7
  23. package/utils/JsViewPlugin/JsvPlayer/GetVersion.js +1 -1
  24. package/utils/JsViewPlugin/JsvPlayer/JsvPlayerBrowser.vue +379 -41
  25. package/utils/JsViewPlugin/JsvPlayer/version.mjs +5 -5
  26. package/utils/JsViewVueTools/JsvHashHistory.js +2 -1
  27. package/utils/JsViewVueWidget/JsvRadarChart.vue +220 -0
  28. package/utils/JsViewVueWidget/JsvSystemAudio.vue +76 -44
  29. package/utils/JsViewVueWidget/index.js +1 -0
  30. package/utils/JsViewEngineWidget/MetroWidget/Const.js +0 -11
  31. package/utils/JsViewEngineWidget/MetroWidget/PageUpdater.js +0 -136
  32. package/utils/JsViewEngineWidget/MetroWidget/ToolFunctions.js +0 -18
  33. package/utils/JsViewEngineWidget/TemplateParser.js +0 -2004
@@ -1,53 +1,391 @@
1
1
  <script>
2
- import playerExMethods from "./JsvMediaBrowserInterface.js"
2
+ import { Forge } from "@shijiu/jsview/dom/jsv-forge-define";
3
+ import { shallowRef } from "vue"
4
+
5
+ let logDebug = console.log;
3
6
 
4
7
  export default {
5
- props: {
6
- onRef: { type: Function, default: () => {} },
7
- playerKey: { type: String, default: "JsvPlayer"}, // TODO: 尝试通过teleport合并多个video元素
8
- playerType: {type: Number, default: 1}, // PC 模拟时,不对接本功能
9
- background: {type: Boolean, default: true}, // PC 模拟时,不对接本功能
10
- },
11
- setup() {
12
- return {
13
- VideoRef: null
14
- };
15
- },
16
- methods: {
17
- onRefProxy(video_ref) {
18
- this.VideoRef = video_ref;
19
-
20
- // 不在此直接回调 onRef,因为onRefProxy可能会因为 .src的设置导致重新触发一次调用(vue本身问题)
21
- // 参照vue-core处理的 componentUpdateFn处理, onMounted 一次, src设置后会再次 onPatch 一次
22
- }
23
- },
24
- mounted() {
25
- if (this.VideoRef && this.onRef) {
26
- let video_ref = this.VideoRef;
27
- this.VideoRef = null;
28
-
29
- // 补充插件扩展出来的接口
30
- for (let method_name in playerExMethods) {
31
- video_ref[method_name] = playerExMethods[method_name].bind(video_ref);
8
+ props: {
9
+ /**
10
+ * 回调函数,播放器对象通知接口
11
+ * @param {Object} video对象,可以通过此video对象调用video相关属性和方法,具体属性和方法定义见JsvMedia.js文件里相关说明。
12
+ */
13
+ onRef: { type: Function, default: () => {} },
14
+ /**
15
+ * 属性,Boolean类型,true表示自动播放,默认false。
16
+ */
17
+ autoplay: { type: Boolean, default: false },
18
+ /**
19
+ * 属性,String类型,播放器实例索引,同样的key使用同一个播放器实例。
20
+ */
21
+ playerKey: { type: String, default: null},
22
+ /**
23
+ * 属性,String类型,播放器窗口模式,默认resizable模式。
24
+ * full:观影模式,全屏方式,不建议修改w/h/l/t,视频清晰度/流畅度最佳;
25
+ * resizable:可变窗口模式,可以随意修改w/h/l/t,支持动画效果,视频清晰度/流畅度表现可能不如full模式;
26
+ * pip:画中画模式,只在支持多路硬解的情况下使用,用户画中画的小窗播放,对清晰度/流畅度要求较差,可能会有反交错问题。
27
+ */
28
+ windowMode: {type: String, default: "resizable"},
29
+ /**
30
+ * 属性,int类型,底层使用的播放器类型。1:系统播放器;2:jsv播放器。默认2。
31
+ */
32
+ playerType: {type: Number, default: 2},
33
+ /**
34
+ * 属性,int类型,播放器解码方式。0:根据硬件能力自动匹配;1:硬解码;2:软解码。默认0(根据芯片能力自动匹配)。
35
+ */
36
+ decodeType: {type: Number, default: 0},
37
+ /**
38
+ * 属性,Boolean类型,层级关系,true表示在界面的下面,false表示在界面上面,默认true。
39
+ */
40
+ background: {type: Boolean, default: true},
41
+ /**
42
+ * 属性,Boolean类型,true表示静音,默认false。
43
+ */
44
+ muted: {type: Boolean, default: false},
45
+ /**
46
+ * 属性,Boolean类型,true表示循环播放,默认false。
47
+ */
48
+ loop:{type: Boolean, default: false},
49
+ /**
50
+ * 属性,String类型,播放地址。
51
+ */
52
+ src: {type: String, default: ""},
53
+ /**
54
+ * 属性,Double类型,起播时间,0-duration。
55
+ */
56
+ currentTime: { type: Number, default: 0},
57
+ /**
58
+ * 属性,Boolean类型,是否保留最后一帧,true保留,false不保留。
59
+ */
60
+ keepLastFrame: {type: Boolean, default: true},
61
+ /**
62
+ * 属性,String类型,视频显示比例,origin原始比例显示,full全屏显示,x:y按照指定比例显示。
63
+ */
64
+ videoAspectRatio: {type: String, default: "origin"},
65
+ /**
66
+ * 属性,JSON Object类型,色度抠像参数,包含color,colorDistance,edgeDistance,greenOffset四个参数,
67
+ * 参考格式:{color: 0x30FF30, colorDistance: 0.15, edgeDistance: 0.4, greenOffset: 0.05}
68
+ * color,色值,int类型,为抠图色值范围的中心点,格式为0xRRGGBB。
69
+ * colorDistance,float类型,色域范围,范围0-1.0。
70
+ * edgeDistance,float类型,alpha透明度距离,范围colorDistance-1.0,从colorDistance到edgeDistance的alpha值从1.0-0线性递减。
71
+ * greenOffset,float类型,红蓝差值,范围0-1.0。
72
+ * 目前只支持扣绿。
73
+ */
74
+ chromaKey: {type: Object, default: null},
75
+ /**
76
+ * 回调函数,播放结束时通过此回调接口通知。
77
+ */
78
+ onEnded: {type: Function, default: ()=>{return{}}},
79
+ /**
80
+ * 回调函数,播放错误时通过此接口通知。
81
+ * @param {int} 错误类型,当前定义了四种错误。1是异常中断;2是网络错误;3是解码错误;4是格式不支持。
82
+ */
83
+ onError: {type: Function, default: ()=>{return{}}},
84
+ // onAbort: {type: Function, default: ()=>{return{}}},
85
+ /**
86
+ * 回调函数,当正常播放时,每0.5秒上报一次,回调接口里会带上当前时间,也可以使用video对象的currentTime属性去获取当前时间。
87
+ * @param {Long} 当前播放时间,单位秒。
88
+ */
89
+ onTimeUpdate: {type: Function, default: ()=>{return{}}},
90
+ /**
91
+ * 回调函数,开始加载,设置完播放地址后,上报此事件。
92
+ */
93
+ onLoadStart: {type: Function, default: ()=>{return{}}},
94
+ /**
95
+ * 回调函数,开始正常播放,同一个播放地址仅触发一次。
96
+ */
97
+ onCanPlayThrough: {type: Function, default: ()=>{return{}}},
98
+ /**
99
+ * 回调函数,正常下载,duration不变的情况下只触发一次。
100
+ */
101
+ onProgress: {type: Function, default: ()=>{return{}}},
102
+ /**
103
+ * 回调函数,视频准备好后触发。
104
+ */
105
+ onLoadedMetaData: {type: Function, default: ()=>{return{}}},
106
+ /**
107
+ * 回调函数,视频准备好后触发,这个时候可以正常seek。
108
+ */
109
+ onLoad: {type: Function, default: ()=>{return{}}},
110
+ /**
111
+ * 回调函数,视频准备好后触发,这个时候可以获取duration。
112
+ */
113
+ onDurationChange: {type: Function, default: ()=>{return{}}},
114
+ /**
115
+ * 回调函数,视频准备好后,seek(修改currentTime)会出发此事件。
116
+ */
117
+ onSeeking: {type: Function, default: ()=>{return{}}},
118
+ /**
119
+ * 回调函数,seek完成后触发此事件。
120
+ */
121
+ onSeeked: {type: Function, default: ()=>{return{}}},
122
+ /**
123
+ * 回调函数,缓冲时触发。
124
+ */
125
+ onStalled: {type: Function, default: ()=>{return{}}},
126
+ /**
127
+ * 回调函数,正常播放后触发。
128
+ */
129
+ onPlaying: {type: Function, default: ()=>{return{}}},
130
+ /**
131
+ * 回调函数,正常显示后触发此事件。
132
+ */
133
+ onCanPlay: {type: Function, default: ()=>{return{}}},
134
+ /**
135
+ * 回调函数,音频失去焦点后触发此事件,可能会导致pause(点播)或者离开频道(直播)。
136
+ */
137
+ onAudioFocusLoss: {type: Function, default: ()=>{return{}}},
138
+ /**
139
+ * 回调函数,音频获取焦点后触发此事件,可能会触发resume(点播)或者加入频道(直播)。
140
+ */
141
+ onAudioFocusGain: {type: Function, default: ()=>{return{}}},
142
+ /**
143
+ * 回调函数,直播进入时移状态时,触发此事件。
144
+ */
145
+ onTimeShift: {type: Function, default: ()=>{return{}}},
146
+ /**
147
+ * 回调函数,直播进入时移状态时,成功播放触发此事件。
148
+ */
149
+ onTimeShifted: {type: Function, default: ()=>{return{}}},
150
+ /**
151
+ * 回调函数,时移回到直播状态,触发此事件。
152
+ */
153
+ onBackLive: {type: Function, default: ()=>{return{}}},
154
+ /**
155
+ * 回调函数,时移回到直播状态时,成功播放触发此事件。
156
+ */
157
+ onBackLived: {type: Function, default: ()=>{return{}}},
158
+ /**
159
+ * 属性,Object类型,设置窗口属性,同其他组件的style设置。
160
+ */
161
+ style: {type: Object, default: ()=>{return{}}},
162
+ /**
163
+ * 属性,Boolean类型,活跃状态,当多个JsvPlayer组件指向一个video对象时,只能有一个处于活跃状态。
164
+ * 视频显示在活跃JsvPlayer设置的显示区域内,事件通知也只会送给活跃的JsvPlayer组件注册的回调函数。
165
+ */
166
+ active:{type: Boolean, default: true},
167
+ },
168
+ components: {
169
+
170
+ },
171
+ watch: {
172
+ active(newValue) {
173
+ console.log("LudlDebug active newValue: "+newValue);
174
+ if(this.video){
175
+ if(newValue){
176
+ this.registerEvent();
177
+ this.video.setHoleID(this.holeId);
178
+ this.holeStyle = {
179
+ left: 0, top: 0,
180
+ width: this.style.width, height: this.style.height
181
+ };
182
+ }else{
183
+ this.holeStyle = {
184
+ left: 0, top: 0,
185
+ width: 0, height: 0
186
+ };
187
+ }
188
+ }
189
+ },
190
+ style(newValue){
191
+ logDebug("holeStyle newValue: left="+newValue.left+", top="+newValue.top+", width="+newValue.width+", height="+newValue.height);
192
+ if(this.active){
193
+ this.holeStyle = {
194
+ left: 0, top: 0,
195
+ width:newValue.width, height:newValue.height
196
+ };
197
+ }
198
+ },
199
+ keepLastFrame(newValue){
200
+ logDebug("keepLastFrame newValue: "+newValue);
201
+ if(this.video && this.active){
202
+ this.video.keepLastFrame = this.keepLastFrame;
203
+ }
204
+ }
205
+ },
206
+
207
+ setup(){
208
+ return {
209
+ video: null,
210
+ holeId: "",
211
+ jsvMainView: shallowRef(null),
212
+ innerViewId: shallowRef(-1),
213
+ };
214
+ },
215
+
216
+ data() {
217
+ return {
218
+ holeStyle: {},
219
+ };
220
+ },
221
+ created() {
222
+ },
223
+ mounted() {
224
+ logDebug("JsvPlayer:",this.style)
225
+
226
+ let key = "Jsv_" + Math.floor(Math.random() * 10000);
227
+ if(this.playerKey){
228
+ key = this.playerKey;
229
+ }
230
+ logDebug("player key:"+key)
231
+
232
+ let player_type = 1;
233
+ if(this.playerType)
234
+ player_type = this.playerType;
235
+
236
+ let background = true;
237
+ if(!this.background)
238
+ background = this.background;
239
+
240
+ logDebug("JsvPlayer background:"+background)
241
+
242
+ const designMap = window.Forge.DesignMap();
243
+ logDebug("JsvPlayer:", this.holeId)
244
+
245
+ // this.video = findMediaObjectByKey(key);
246
+ // TODO: 后续支持共享MediaId ?
247
+ let first_create = true;
248
+
249
+ if(!this.video){
250
+ // 创建PC版本的video标签
251
+ this.video = window.originDocument.createElement("video");
252
+ this._extendsApi(this.video);
253
+ }else{
254
+ this.video.setRef();
255
+ first_create = false;
256
+ }
257
+
258
+ if(this.video != null){
259
+ if(this.active){
260
+ this.registerEvent();
261
+
262
+ //if(first_create){
263
+ if(this.src && this.src !== "")
264
+ this.video.src = this.src;
265
+
266
+ if(this.currentTime !== 0)
267
+ this.video.currentTime = this.currentTime;
268
+
269
+ if(this.autoplay){
270
+ this.video.autoplay = this.autoplay;
271
+ }
272
+
273
+ if(this.muted){
274
+ this.video.muted = this.muted;
275
+ }
276
+
277
+ if(this.loop){
278
+ this.video.loop = this.loop;
279
+ }
280
+
281
+ if(!this.keepLastFrame){
282
+ this.video.keepLastFrame = this.keepLastFrame;
283
+ }
284
+
285
+ if(this.videoAspectRatio !== "origin"){
286
+ this.video.videoAspectRatio = this.videoAspectRatio;
287
+ }
288
+
289
+ if(this.chromaKey && this.chromaKey !== ""){
290
+ this.video.setChromaKey(this.chromaKey)
291
+ }
292
+ //}else{
293
+ if(!first_create){
294
+ this.video.setHoleID(this.holeId);
295
+ this.holeStyle = {
296
+ left: 0, top: 0,
297
+ width: this.style.width, height: this.style.height
298
+ };
299
+ }
300
+ }
301
+
302
+ // 创建挂载的VideoView
303
+ this.jsvMainView = new Forge.VideoView(this.video, null);
304
+ this.innerViewId = Forge.sViewStore.add(
305
+ new Forge.ViewInfo(this.jsvMainView)
306
+ );
307
+
308
+ this.onRef?.(this.video)
309
+ }
310
+ },
311
+
312
+ unmounted(){
313
+ if(this.video != null) {
314
+ // 清理View管理缓存
315
+ if (this.innerViewId !== -1) {
316
+ Forge.sViewStore.remove(this.innerViewId);
317
+ this.innerViewId = -1;
318
+ this.jsvMainView = null;
32
319
  }
320
+ this.onRef?.(null)
321
+ }
322
+ },
33
323
 
34
- // 将修改好的video回调返回给使用者
35
- this.onRef(video_ref);
36
- }
37
- },
38
- beforeUnmount() {
39
- if (this.onRef) {
40
- // video_ref为null,代表video标签卸载处理
41
- this.onRef(null);
42
- }
43
- }
44
- };
324
+ methods: {
325
+ getHoleId(id){
326
+ logDebug("getHoleId:", id)
327
+ this.holeId = id;
328
+ },
329
+ registerEvent(){
330
+ if(this.video && this.active){
331
+ this.video.addEventListener("end", this.onEnded);
332
+ this.video.addEventListener("error", this.onError);
333
+ //this.video.addEventListener("abort", this.onAbort);
334
+ this.video.addEventListener("timeupdate", this.onTimeUpdate);
335
+ this.video.addEventListener("loadstart", () => {
336
+ logDebug("JsvPlayer received loadstart event.");
337
+ this.holeStyle = {
338
+ left: 0, top: 0,
339
+ width:this.style.width, height:this.style.height
340
+ };
341
+ this.onLoadStart();
342
+ });
343
+ this.video.addEventListener("canplaythrough", this.onCanPlayThrough);
344
+ this.video.addEventListener("progress", this.onProgress);
345
+ this.video.addEventListener("loadedmetadata", this.onLoadedMetaData);
346
+ this.video.addEventListener("load", this.onLoad);
347
+ this.video.addEventListener("durationchange", this.onDurationChange);
348
+ this.video.addEventListener("seeking", this.onSeeking);
349
+ this.video.addEventListener("seeked", this.onSeeked);
350
+ this.video.addEventListener("stalled", this.onStalled);
351
+ this.video.addEventListener("playing", this.onPlaying);
352
+ this.video.addEventListener("canplay", this.onCanPlay);
353
+ this.video.addEventListener("audiofocusloss", this.onAudioFocusLoss);
354
+ this.video.addEventListener("audiofocusgain", this.onAudioFocusGain);
355
+ this.video.addEventListener("timeshift", this.onTimeShift);
356
+ this.video.addEventListener("timeshifted", this.onTimeShifted);
357
+ this.video.addEventListener("backlive", this.onBackLive);
358
+ this.video.addEventListener("backlived", this.onBackLived);
359
+ }
360
+ },
361
+ _extendsApi(videoRef) {
362
+ videoRef.setSrc = (newVideoUrl) => {
363
+ videoRef.src = newVideoUrl;
364
+ };
365
+ videoRef.setRef = () => {
366
+ console.log("Simulate setRef");
367
+ };
368
+ }
369
+ },
370
+ }
45
371
 
372
+ //export default JsvPlayer;
46
373
  </script>
47
374
 
48
375
  <template>
49
- <video :ref="onRefProxy" v-bind="$attrs"/>
376
+ <div :style="holeStyle" backgroundColor="rgba(0,0,0,1)">
377
+ <div
378
+ :style="{
379
+ left: holeStyle.left,
380
+ top: holeStyle.top,
381
+ width: holeStyle.width,
382
+ height: holeStyle.height,
383
+ }"
384
+ :data-jsv-vw-innerview="innerViewId"
385
+ />
386
+ </div>
50
387
  </template>
51
388
 
52
389
  <style scoped>
53
- </style>
390
+
391
+ </style>
@@ -1,17 +1,17 @@
1
1
  let PluginInfo={
2
- // downloadUrl:"http://192.168.0.85:8080/plugin/JsvPlayer-199.zip", //插件下载地址
2
+ // downloadUrl:"http://192.168.0.85:8080/plugin/JsvPlayer-203.zip", //插件下载地址
3
3
  packageName:"com.qcode.jsvplayer",
4
4
  name:"播放器插件",
5
- version:"2.0.2", //插件需要的版本号
6
- versionCodeMin:202,
7
- versionCodeMax:202,
5
+ version:"2.0.4", //插件需要的版本号
6
+ versionCodeMin:204,
7
+ versionCodeMax:204,
8
8
  bridgeName:"jsvPlayerBridge", //插件bridge注册到jsview的名称
9
9
  className:"com.qcode.jsvplayer.JsvPlayer", //插件初始化类名称
10
10
  initMethod:"createInstance", //插件初始化方法
11
11
  listener:"top.JsvPlayerPluginLoadResult", //插件加载结果回调
12
12
  listener2: "top.JsvPlayerPluginStatus",
13
13
  // debug:true,
14
- md5:"6de8d620492bf0143bbccc62636a6271"
14
+ md5:"7aa897a8ff6f6a09d2522cf93c3a083e"
15
15
  };
16
16
 
17
17
  // 导出字段要含有 pluginVersionInfo 信息,作为npm时版本参考
@@ -50,7 +50,8 @@ class JsvHashHistory {
50
50
 
51
51
  #createMockHashHistory(base) {
52
52
  // 抄 createWebHashHistory 作业
53
- base = location.host ? base || location.pathname + location.search : '';
53
+ // base = location.host ? base || location.pathname + location.search : '';
54
+ base = (location.host || location.protocol === 'file:') ? base || location.pathname + location.search : '';
54
55
  if (!base.includes('#'))
55
56
  base += '#';
56
57
  if (!base.endsWith('#/') && !base.endsWith('#')) {
@@ -0,0 +1,220 @@
1
+ <!--
2
+ * 【模块 export 内容】
3
+ * JsvRadarChart:Vue高阶组件,多维属性图展示控件,又称雷达图,该控件的原图要求为正方形,按对角线划分,一边为透明,一边为带边框的正三角形,
4
+ * 带上等分线。
5
+ * props说明:
6
+ * style {Object} (必填) 同div的Style,通过top/left控制圆心坐标
7
+ * backgroundUrl {String} (必填) 背景图片的加载地址
8
+ * foregroundUrl {String} (必填) 展示图片的加载地址
9
+ * data {Array} (必填) 响应式数组对象,有title和value两个属性 例: const data=ref([{title:'智力',value:80}])
10
+ * 注:value为0-100的整数
11
+ * fontSize {Number} (必填) 要展示的字体大小
12
+ * fontColor {String} 字体颜色,默认黑色。
13
+ * radius {Number} (必填) 半径宽度
14
+ * extendOffset {Number} (必填) 延展半径宽度(width),用于展示title
15
+ * 补充说明:
16
+ * 该控件以左上角为顶点旋转渲染,所以图片中带边框的正三角形的三个顶点应该为:左上,右上,左下;请调整图的朝向。
17
+ -->
18
+ <script setup>
19
+ import { shallowRef, onMounted, onBeforeUnmount, watch, ref, onBeforeUpdate } from "vue"
20
+ import { getTextWidth } from "jsview"
21
+ const props = defineProps({
22
+ style: {
23
+ type: Object,
24
+ default: () => {
25
+ return {};
26
+ },
27
+ },
28
+ backgroundUrl: { type: String, required: true },
29
+ foregroundUrl: { type: String, required: true },
30
+ data: { type: Array, required: true, default: () => ref([{ title: '', value: 10 }]) },
31
+ fontSize: { type: Number, required: true, default: 16 },
32
+ fontColor: { type: String, default: '#000000' },
33
+ radius: { type: Number, required: true },
34
+ extendOffset: { type: Number, required: true }
35
+ });
36
+ //检测数组里面的值是否都在0-100
37
+ const check=props.data.every((item,index)=>{
38
+ if(item.value>0&&item.value<=100){
39
+ return true
40
+ }else{
41
+ return false
42
+ }
43
+ })
44
+
45
+ //让props传过来值具有响应式
46
+ const data = shallowRef(props.data)
47
+ //监听
48
+ watch(props.data, (n, o) => {
49
+ props.data.value = n
50
+ })
51
+ //定时器
52
+ const timer = { id: -1 }
53
+ //数组为空数组的时不展示
54
+ const isReady = () => {
55
+ if (!props.data.value) {
56
+ return true;
57
+ }else if(!check){
58
+ console.warn('请检查数组内每一项value的值,范围0-100')
59
+ return false
60
+ }
61
+ return props.style && props.radius !== 0;
62
+ };
63
+ //初始化数组
64
+ let showData = shallowRef(null)
65
+ showData.value = data.value.map(item => {
66
+ return {
67
+ ...item,
68
+ height: 10,
69
+ value: 10
70
+ }
71
+ })
72
+ //动画
73
+ const changeTransition = () => {
74
+ let transition = 'width 1s ,height 1s linear'
75
+ return transition
76
+ }
77
+
78
+ //找出title最多的一项算长度
79
+ const longestTitleObj = data.value.reduce((accumulator, currentObj) => {
80
+ return accumulator.title.length > currentObj.title.length ? accumulator : currentObj;
81
+ }, data.value[0]);
82
+ //算长度
83
+ let textWidth = shallowRef(null)
84
+ textWidth.value = getTextWidth(longestTitleObj.title, { fontSize: props.fontSize })
85
+
86
+ //确定各个顶点坐标位置
87
+ const coordinate = props.data.map((item, index) => {
88
+ item.top = (0 + (props.radius + props.extendOffset) * Math.cos(index * (360 / props.data.length / 180 * Math.PI)))
89
+ item.left = (0 + (props.radius + props.extendOffset) * Math.sin(index * (360 / props.data.length / 180 * Math.PI)))
90
+ return {
91
+ ...item
92
+ }
93
+ })
94
+ //反转定点坐标
95
+ const cos = Math.cos(Math.PI);
96
+ const sin = Math.sin(Math.PI);
97
+ const reverse = coordinate.map((item, index) => {
98
+ const cx = 0;
99
+ const cy = 0;
100
+ const x = item.left;
101
+ const y = item.top
102
+ const newX = cos * (x - cx) - sin * (y - cy) + cx; // 计算 x 坐标的新值
103
+ const newY = sin * (x - cx) + cos * (y - cy) + cy; // 计算 y 坐标的新值
104
+ return {
105
+ ...item,
106
+ top: newY,
107
+ left: newX,
108
+ }
109
+ })
110
+
111
+ //每一项往下赋值
112
+ const temp = reverse[reverse.length - 1].title;
113
+ for (let i = reverse.length - 1; i >= 1; i--) {
114
+ reverse[i].title = reverse[i - 1].title;
115
+ }
116
+ reverse[0].title = temp;
117
+
118
+ //最后处理 定义文字方向和修改
119
+ const final = reverse.map((item, index) => {
120
+ const temp = reverse[reverse.length - 1].value
121
+ index == 0 ? item.value = temp : item.value = reverse[reverse.length - 1].value
122
+ if (index == 0 || index == props.data.length / 2) {
123
+ item.textAlign = 'center',
124
+ item.left -= textWidth.value / 2
125
+ if (index == props.data.length / 2) {
126
+ item.top -= props.fontSize
127
+ }
128
+ } else if (item.left > 0 && item.top > 0) {
129
+ item.textAlign = 'start'
130
+ } else if (item.left < 0) {
131
+ item.textAlign = 'end'
132
+ item.left -= textWidth.value
133
+ }
134
+ return {
135
+ ...item,
136
+ textAlign: item.textAlign
137
+ }
138
+ })
139
+
140
+ onMounted(() => {
141
+ if (props.data.value.length == 0) {
142
+ console.error("data长度不要为空")
143
+ } else {
144
+ //这里赋值是为了让其有变化。
145
+ data.value = showData.value
146
+ timer.id = setTimeout(() => {
147
+ if (props.data.value) {
148
+ //赋值height
149
+ data.value = props.data.value.map((item, index) => {
150
+ const prevIndex = index === 0 ? props.data.length - 1 : index - 1
151
+ const prevValue = props.data.value[prevIndex].value
152
+ return {
153
+ ...item,
154
+ height: prevValue
155
+ }
156
+ })
157
+ }
158
+ }, 500)
159
+ }
160
+
161
+ })
162
+
163
+ onBeforeUpdate(() => {
164
+ timer.id = setTimeout(() => {
165
+ if (props.data.value) {
166
+ //赋值height
167
+ data.value = props.data.value.map((item, index) => {
168
+ const prevIndex = index === 0 ? props.data.length - 1 : index - 1
169
+ const prevValue = props.data.value[prevIndex].value
170
+ return {
171
+ ...item,
172
+ height: prevValue
173
+ }
174
+ })
175
+ }
176
+ }, 500)
177
+ })
178
+
179
+ onBeforeUnmount(() => {
180
+ if (timer.id > 0) {
181
+ clearTimeout(timer.id)
182
+ timer.id = -1;
183
+ }
184
+
185
+ })
186
+ </script>
187
+ <template>
188
+ <div :style="{ top: props.style.top, left: props.style.left }" v-if="isReady()">
189
+ <!-- 偏转为了对上文字 -->
190
+ <div :style="{ transform: `rotate3d(0,0,1,180deg)` }">
191
+ <!-- 背景图 -->
192
+ <div v-for="item, index in data" :key="`${item}square`" :style="{
193
+ width: props.radius,
194
+ height: props.radius / Math.cos(Math.PI * (90 - (360 / props.data.length)) / 180),
195
+ transform: `rotate3d(0,0,1,${-index * (360 / props.data.length)}deg) skew(0,${(90 - (360 / props.data.length))}deg)`,
196
+ transformOrigin: `left top`,
197
+ backgroundImage: props.backgroundUrl
198
+ }">
199
+ <!-- 展示图 -->
200
+ <div :style="{
201
+ width: Math.ceil(props.radius * item.value / 100), height: Math.ceil((props.radius / Math.cos(Math.PI * (90 - (360 / props.data.length)) / 180)) * item.height / 100), backgroundImage: props.foregroundUrl,
202
+ transition: changeTransition()
203
+ }" />
204
+ </div>
205
+ </div>
206
+ <!-- 展示title -->
207
+ <div v-for="(item, index) in final" :key="`${item}text`" :style="{
208
+ left: item.left,
209
+ top: item.top,
210
+ width: textWidth,
211
+ height: props.fontSize + 10,
212
+ color: props.fontColor,
213
+ fontSize: props.fontSize,
214
+ textAlign: item.textAlign,
215
+ }">
216
+ {{ item.title }}
217
+
218
+ </div>
219
+ </div>
220
+ </template>