Browse Source

Merge branch 'hqyDev' of http://git.dayaedu.com/huangqiyong/pptList into test-online

黄琪勇 4 weeks ago
parent
commit
01e4a742d7
59 changed files with 1788 additions and 33 deletions
  1. 5 0
      dist/assets/index-6zinoh_3.js
  2. 4 0
      dist/assets/index-BIv8gRAy.js
  3. 4 0
      dist/assets/index-BNXYm0uS.js
  4. 5 0
      dist/assets/index-BRmBk2sE.js
  5. 5 0
      dist/assets/index-BuyCAPrN.js
  6. 1 0
      dist/assets/index-Bwu_lt1j.js
  7. 4 0
      dist/assets/index-CEVZW5va.js
  8. 1 0
      dist/assets/index-CRsF8WVD.js
  9. 1 0
      dist/assets/index-CVsncQBR.js
  10. 1 0
      dist/assets/index-CrU48_Ox.js
  11. 1 0
      dist/assets/index-CyZlGVz7.js
  12. 5 0
      dist/assets/index-D0gDx8o2.js
  13. 4 0
      dist/assets/index-DWzZR4MJ.js
  14. 1 0
      dist/assets/index-Q0AHEMXj.js
  15. 5 0
      dist/assets/index-UAsWcCF9.js
  16. 9 0
      dist/assets/index-vudbuaSr.js
  17. 8 0
      dist/assets/vuedraggable.umd-D-p0Zul4.js
  18. 4 0
      dist/assets/vuedraggable.umd-iAuH7yoq.js
  19. 4 0
      dist/index.html
  20. 3 0
      src/App.vue
  21. 12 0
      src/api/pptOperate.ts
  22. 2 2
      src/components/Modal.vue
  23. 28 2
      src/hooks/useCreateElement.ts
  24. 19 5
      src/queryParams/index.ts
  25. 7 1
      src/store/main.ts
  26. 1 2
      src/store/pptWork.ts
  27. 27 1
      src/types/slides.ts
  28. 3 1
      src/views/Editor/Canvas/EditableElement.vue
  29. 8 4
      src/views/Editor/Canvas/index.vue
  30. 74 6
      src/views/Editor/CanvasTool/index.vue
  31. 7 0
      src/views/Editor/Toolbar/ElementStylePanel/EnjoyStylePanel.vue
  32. 3 1
      src/views/Editor/Toolbar/ElementStylePanel/index.vue
  33. 1 1
      src/views/Editor/Toolbar/index.vue
  34. 3 1
      src/views/Mobile/MobileEditor/MobileEditableElement.vue
  35. 3 1
      src/views/Screen/ScreenElement.vue
  36. 5 1
      src/views/Screen/hooks/useExecPlay.ts
  37. 3 1
      src/views/components/ThumbnailSlide/ThumbnailElement.vue
  38. 10 2
      src/views/components/element/AudioElement/AudioPlayer.vue
  39. 41 0
      src/views/components/element/enjoyElement/BaseEnjoyElement.vue
  40. 51 0
      src/views/components/element/enjoyElement/ScreenEnjoyElement.vue
  41. 102 0
      src/views/components/element/enjoyElement/enjoyElement.vue
  42. 558 0
      src/views/components/element/enjoyElement/enjoyPlayer.vue
  43. BIN
      src/views/components/element/enjoyElement/imgs/del.png
  44. BIN
      src/views/components/element/enjoyElement/imgs/list.png
  45. BIN
      src/views/components/element/enjoyElement/imgs/next.png
  46. BIN
      src/views/components/element/enjoyElement/imgs/pause.png
  47. BIN
      src/views/components/element/enjoyElement/imgs/play.png
  48. BIN
      src/views/components/element/enjoyElement/imgs/pre.png
  49. BIN
      src/views/components/element/enjoyElement/imgs/td.png
  50. BIN
      src/views/components/element/enjoyElement/imgs/td2.png
  51. BIN
      src/views/components/element/enjoyElement/imgs/tip.png
  52. 2 0
      src/views/components/element/enjoyElement/index.ts
  53. BIN
      src/views/components/element/enjoyElement/resourcesList/imgs/audio.png
  54. BIN
      src/views/components/element/enjoyElement/resourcesList/imgs/img.png
  55. BIN
      src/views/components/element/enjoyElement/resourcesList/imgs/jxImg.png
  56. BIN
      src/views/components/element/enjoyElement/resourcesList/imgs/video.png
  57. 2 0
      src/views/components/element/enjoyElement/resourcesList/index.ts
  58. 740 0
      src/views/components/element/enjoyElement/resourcesList/resourcesList.vue
  59. 1 1
      vite.config.ts

+ 5 - 0
dist/assets/index-6zinoh_3.js

@@ -0,0 +1,5 @@
+<<<<<<<< HEAD:dist/assets/index-BNXYm0uS.js
+import{u as c,a9 as l}from"./index-CRsF8WVD.js";import{a5 as i,cw as m,R as t,ae as a,af as p,al as u,ag as f,bG as _}from"./index-BuyCAPrN.js";import"./vuedraggable.umd-iAuH7yoq.js";const S=i({__name:"mobileScreen",setup(d){const s=m(),e=t(!0),n=t(!1),r=c();return s.setScreenMode("mobileScreen"),r.initPPTData().then(()=>{e.value=!1}),(k,o)=>(a(),p("div",{class:"mobileScreen",onClick:o[0]||(o[0]=b=>n.value=!1)},[e.value?f("",!0):(a(),u(l,{key:0}))]))}}),B=_(S,[["__scopeId","data-v-2471f971"]]);export{B as default};
+========
+import{u as c,a9 as l}from"./index-CyZlGVz7.js";import{a5 as i,cw as m,R as t,ae as a,af as p,al as u,ag as f,bG as _}from"./index-vudbuaSr.js";import"./vuedraggable.umd-D-p0Zul4.js";const S=i({__name:"mobileScreen",setup(d){const s=m(),e=t(!0),n=t(!1),r=c();return s.setScreenMode("mobileScreen"),r.initPPTData().then(()=>{e.value=!1}),(k,o)=>(a(),p("div",{class:"mobileScreen",onClick:o[0]||(o[0]=b=>n.value=!1)},[e.value?f("",!0):(a(),u(l,{key:0}))]))}}),B=_(S,[["__scopeId","data-v-2471f971"]]);export{B as default};
+>>>>>>>> 1a387b7762cec8e6bfd2183e7939a6142fff3bf8:dist/assets/index-6zinoh_3.js

+ 4 - 0
dist/assets/index-BIv8gRAy.js

@@ -1 +1,5 @@
+<<<<<<<< HEAD:dist/assets/index-BIv8gRAy.js
 import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-Bwu_lt1j.js";import{a5 as n,ae as i,af as r,ak as o,aq as _,am as d,P as l,bo as p,bH as m,bI as f,bG as u}from"./index-BuyCAPrN.js";const s=a=>(m("data-v-340bbdaa"),a=a(),f(),a),b={class:"login"},h={class:"error"},g=s(()=>o("img",{src:t,class:"img",alt:""},null,-1)),k=s(()=>o("div",{class:"tit"},"登录已过期或服务器错误!",-1)),v=n({__name:"login",setup(a){function e(){window.close()}return(x,B)=>(i(),r("div",b,[o("div",h,[g,k,_(l(c),{class:"backBtn",type:"primary",plain:"",onClick:e},{default:d(()=>[p("关闭页面")]),_:1})])]))}}),E=u(v,[["__scopeId","data-v-340bbdaa"]]);export{E as default};
+========
+import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-Q0AHEMXj.js";import{a5 as n,ae as i,af as r,ak as o,aq as _,am as d,P as l,bo as p,bH as m,bI as f,bG as u}from"./index-vudbuaSr.js";const s=a=>(m("data-v-340bbdaa"),a=a(),f(),a),b={class:"login"},h={class:"error"},g=s(()=>o("img",{src:t,class:"img",alt:""},null,-1)),k=s(()=>o("div",{class:"tit"},"登录已过期或服务器错误!",-1)),v=n({__name:"login",setup(a){function e(){window.close()}return(x,B)=>(i(),r("div",b,[o("div",h,[g,k,_(l(c),{class:"backBtn",type:"primary",plain:"",onClick:e},{default:d(()=>[p("关闭页面")]),_:1})])]))}}),E=u(v,[["__scopeId","data-v-340bbdaa"]]);export{E as default};
+>>>>>>>> 1a387b7762cec8e6bfd2183e7939a6142fff3bf8:dist/assets/index-BRmBk2sE.js

+ 4 - 0
dist/assets/index-BNXYm0uS.js

@@ -1 +1,5 @@
+<<<<<<<< HEAD:dist/assets/index-BNXYm0uS.js
 import{u as c,a9 as l}from"./index-CRsF8WVD.js";import{a5 as i,cw as m,R as t,ae as a,af as p,al as u,ag as f,bG as _}from"./index-BuyCAPrN.js";import"./vuedraggable.umd-iAuH7yoq.js";const S=i({__name:"mobileScreen",setup(d){const s=m(),e=t(!0),n=t(!1),r=c();return s.setScreenMode("mobileScreen"),r.initPPTData().then(()=>{e.value=!1}),(k,o)=>(a(),p("div",{class:"mobileScreen",onClick:o[0]||(o[0]=b=>n.value=!1)},[e.value?f("",!0):(a(),u(l,{key:0}))]))}}),B=_(S,[["__scopeId","data-v-2471f971"]]);export{B as default};
+========
+import{u as c,a9 as l}from"./index-CyZlGVz7.js";import{a5 as i,cw as m,R as t,ae as a,af as p,al as u,ag as f,bG as _}from"./index-vudbuaSr.js";import"./vuedraggable.umd-D-p0Zul4.js";const S=i({__name:"mobileScreen",setup(d){const s=m(),e=t(!0),n=t(!1),r=c();return s.setScreenMode("mobileScreen"),r.initPPTData().then(()=>{e.value=!1}),(k,o)=>(a(),p("div",{class:"mobileScreen",onClick:o[0]||(o[0]=b=>n.value=!1)},[e.value?f("",!0):(a(),u(l,{key:0}))]))}}),B=_(S,[["__scopeId","data-v-2471f971"]]);export{B as default};
+>>>>>>>> 1a387b7762cec8e6bfd2183e7939a6142fff3bf8:dist/assets/index-6zinoh_3.js

+ 5 - 0
dist/assets/index-BRmBk2sE.js

@@ -0,0 +1,5 @@
+<<<<<<<< HEAD:dist/assets/index-BIv8gRAy.js
+import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-Bwu_lt1j.js";import{a5 as n,ae as i,af as r,ak as o,aq as _,am as d,P as l,bo as p,bH as m,bI as f,bG as u}from"./index-BuyCAPrN.js";const s=a=>(m("data-v-340bbdaa"),a=a(),f(),a),b={class:"login"},h={class:"error"},g=s(()=>o("img",{src:t,class:"img",alt:""},null,-1)),k=s(()=>o("div",{class:"tit"},"登录已过期或服务器错误!",-1)),v=n({__name:"login",setup(a){function e(){window.close()}return(x,B)=>(i(),r("div",b,[o("div",h,[g,k,_(l(c),{class:"backBtn",type:"primary",plain:"",onClick:e},{default:d(()=>[p("关闭页面")]),_:1})])]))}}),E=u(v,[["__scopeId","data-v-340bbdaa"]]);export{E as default};
+========
+import{_ as t}from"./404-R0y5dkvZ.js";import{E as c}from"./index-Q0AHEMXj.js";import{a5 as n,ae as i,af as r,ak as o,aq as _,am as d,P as l,bo as p,bH as m,bI as f,bG as u}from"./index-vudbuaSr.js";const s=a=>(m("data-v-340bbdaa"),a=a(),f(),a),b={class:"login"},h={class:"error"},g=s(()=>o("img",{src:t,class:"img",alt:""},null,-1)),k=s(()=>o("div",{class:"tit"},"登录已过期或服务器错误!",-1)),v=n({__name:"login",setup(a){function e(){window.close()}return(x,B)=>(i(),r("div",b,[o("div",h,[g,k,_(l(c),{class:"backBtn",type:"primary",plain:"",onClick:e},{default:d(()=>[p("关闭页面")]),_:1})])]))}}),E=u(v,[["__scopeId","data-v-340bbdaa"]]);export{E as default};
+>>>>>>>> 1a387b7762cec8e6bfd2183e7939a6142fff3bf8:dist/assets/index-BRmBk2sE.js

File diff suppressed because it is too large
+ 5 - 0
dist/assets/index-BuyCAPrN.js


File diff suppressed because it is too large
+ 1 - 0
dist/assets/index-Bwu_lt1j.js


+ 4 - 0
dist/assets/index-CEVZW5va.js

@@ -1 +1,5 @@
+<<<<<<<< HEAD:dist/assets/index-CEVZW5va.js
 import{_ as a}from"./404-R0y5dkvZ.js";import{a5 as o,ek as t,ae as r,af as c,bH as _,bI as d,ak as s,bG as p}from"./index-BuyCAPrN.js";const i=e=>(_("data-v-dda25179"),e=e(),d(),e),n={class:"errorPage"},m=i(()=>s("div",{class:"error"},[s("img",{src:a,class:"img",alt:""}),s("div",{class:"tit"},"页面找不到了~")],-1)),l=[m],f=o({__name:"errorPage",setup(e){return t(),(u,h)=>(r(),c("div",n,l))}}),I=p(f,[["__scopeId","data-v-dda25179"]]);export{I as default};
+========
+import{_ as a}from"./404-R0y5dkvZ.js";import{a5 as o,ek as t,ae as r,af as c,bH as _,bI as d,ak as s,bG as p}from"./index-vudbuaSr.js";const i=e=>(_("data-v-dda25179"),e=e(),d(),e),n={class:"errorPage"},m=i(()=>s("div",{class:"error"},[s("img",{src:a,class:"img",alt:""}),s("div",{class:"tit"},"页面找不到了~")],-1)),l=[m],f=o({__name:"errorPage",setup(e){return t(),(u,h)=>(r(),c("div",n,l))}}),I=p(f,[["__scopeId","data-v-dda25179"]]);export{I as default};
+>>>>>>>> 1a387b7762cec8e6bfd2183e7939a6142fff3bf8:dist/assets/index-D0gDx8o2.js

File diff suppressed because it is too large
+ 1 - 0
dist/assets/index-CRsF8WVD.js


File diff suppressed because it is too large
+ 1 - 0
dist/assets/index-CVsncQBR.js


File diff suppressed because it is too large
+ 1 - 0
dist/assets/index-CrU48_Ox.js


File diff suppressed because it is too large
+ 1 - 0
dist/assets/index-CyZlGVz7.js


+ 5 - 0
dist/assets/index-D0gDx8o2.js

@@ -0,0 +1,5 @@
+<<<<<<<< HEAD:dist/assets/index-CEVZW5va.js
+import{_ as a}from"./404-R0y5dkvZ.js";import{a5 as o,ek as t,ae as r,af as c,bH as _,bI as d,ak as s,bG as p}from"./index-BuyCAPrN.js";const i=e=>(_("data-v-dda25179"),e=e(),d(),e),n={class:"errorPage"},m=i(()=>s("div",{class:"error"},[s("img",{src:a,class:"img",alt:""}),s("div",{class:"tit"},"页面找不到了~")],-1)),l=[m],f=o({__name:"errorPage",setup(e){return t(),(u,h)=>(r(),c("div",n,l))}}),I=p(f,[["__scopeId","data-v-dda25179"]]);export{I as default};
+========
+import{_ as a}from"./404-R0y5dkvZ.js";import{a5 as o,ek as t,ae as r,af as c,bH as _,bI as d,ak as s,bG as p}from"./index-vudbuaSr.js";const i=e=>(_("data-v-dda25179"),e=e(),d(),e),n={class:"errorPage"},m=i(()=>s("div",{class:"error"},[s("img",{src:a,class:"img",alt:""}),s("div",{class:"tit"},"页面找不到了~")],-1)),l=[m],f=o({__name:"errorPage",setup(e){return t(),(u,h)=>(r(),c("div",n,l))}}),I=p(f,[["__scopeId","data-v-dda25179"]]);export{I as default};
+>>>>>>>> 1a387b7762cec8e6bfd2183e7939a6142fff3bf8:dist/assets/index-D0gDx8o2.js

+ 4 - 0
dist/assets/index-DWzZR4MJ.js

@@ -1 +1,5 @@
+<<<<<<<< HEAD:dist/assets/index-DWzZR4MJ.js
 import{u as s,a9 as r}from"./index-CRsF8WVD.js";import{a5 as c,cw as n,R as p,ae as t,af as _,al as u,ag as i,bG as l}from"./index-BuyCAPrN.js";const m={class:"pptScreen"},d=c({__name:"pptScreen",setup(f){const a=n(),e=p(!0),o=s();return a.setScreenMode("pptScreen"),o.initPPTData().then(()=>{e.value=!1}),(S,k)=>(t(),_("div",m,[e.value?i("",!0):(t(),u(r,{key:0}))]))}}),h=l(d,[["__scopeId","data-v-678905e9"]]);export{h as default};
+========
+import{u as s,a9 as r}from"./index-CyZlGVz7.js";import{a5 as c,cw as n,R as p,ae as t,af as _,al as u,ag as i,bG as l}from"./index-vudbuaSr.js";const m={class:"pptScreen"},d=c({__name:"pptScreen",setup(f){const a=n(),e=p(!0),o=s();return a.setScreenMode("pptScreen"),o.initPPTData().then(()=>{e.value=!1}),(S,k)=>(t(),_("div",m,[e.value?i("",!0):(t(),u(r,{key:0}))]))}}),h=l(d,[["__scopeId","data-v-678905e9"]]);export{h as default};
+>>>>>>>> 1a387b7762cec8e6bfd2183e7939a6142fff3bf8:dist/assets/index-UAsWcCF9.js

File diff suppressed because it is too large
+ 1 - 0
dist/assets/index-Q0AHEMXj.js


+ 5 - 0
dist/assets/index-UAsWcCF9.js

@@ -0,0 +1,5 @@
+<<<<<<<< HEAD:dist/assets/index-DWzZR4MJ.js
+import{u as s,a9 as r}from"./index-CRsF8WVD.js";import{a5 as c,cw as n,R as p,ae as t,af as _,al as u,ag as i,bG as l}from"./index-BuyCAPrN.js";const m={class:"pptScreen"},d=c({__name:"pptScreen",setup(f){const a=n(),e=p(!0),o=s();return a.setScreenMode("pptScreen"),o.initPPTData().then(()=>{e.value=!1}),(S,k)=>(t(),_("div",m,[e.value?i("",!0):(t(),u(r,{key:0}))]))}}),h=l(d,[["__scopeId","data-v-678905e9"]]);export{h as default};
+========
+import{u as s,a9 as r}from"./index-CyZlGVz7.js";import{a5 as c,cw as n,R as p,ae as t,af as _,al as u,ag as i,bG as l}from"./index-vudbuaSr.js";const m={class:"pptScreen"},d=c({__name:"pptScreen",setup(f){const a=n(),e=p(!0),o=s();return a.setScreenMode("pptScreen"),o.initPPTData().then(()=>{e.value=!1}),(S,k)=>(t(),_("div",m,[e.value?i("",!0):(t(),u(r,{key:0}))]))}}),h=l(d,[["__scopeId","data-v-678905e9"]]);export{h as default};
+>>>>>>>> 1a387b7762cec8e6bfd2183e7939a6142fff3bf8:dist/assets/index-UAsWcCF9.js

File diff suppressed because it is too large
+ 9 - 0
dist/assets/index-vudbuaSr.js


File diff suppressed because it is too large
+ 8 - 0
dist/assets/vuedraggable.umd-D-p0Zul4.js


+ 4 - 0
dist/assets/vuedraggable.umd-iAuH7yoq.js

@@ -1,4 +1,8 @@
+<<<<<<<< HEAD:dist/assets/vuedraggable.umd-iAuH7yoq.js
 import{cz as Ln,cA as Fn,aG as Tn,cB as On,cC as wn,cD as Mn,cE as Rn,ah as Pn,cF as In,cG as $n,cH as Hn,cI as Nn,aM as zn,aF as jn,cJ as Un,aB as Gn,be as Zn,cK as Wn,cL as Vn,cM as Kn,cN as Xn,cO as Yn,cP as Qn,cQ as kn,aE as Jn,cR as qn,s as _n,bj as t1,al as e1,ag as r1,af as n1,ak as a1,cS as o1,cT as i1,cU as s1,cV as c1,bi as l1,cW as f1,bo as u1,aq as x1,cX as d1,cY as v1,a5 as p1,cZ as h1,c_ as g1,c$ as m1,d0 as E1,d1 as y1,d2 as C1,d3 as A1,d4 as B1,d5 as b1,d6 as S1,d7 as D1,v as L1,d8 as F1,d9 as T1,da as O1,aR as w1,db as M1,dc as R1,dd as P1,de as I1,df as $1,ay as H1,dg as N1,dh as z1,di as j1,dj as U1,A as G1,dk as Z1,dl as W1,bp as V1,dm as K1,dn as X1,dp as Y1,ap as Q1,ac as k1,ai as J1,dq as q1,au as _1,dr as ta,X as ea,y as ra,ds as na,aP as aa,dt as oa,w as ia,du as sa,dv as ca,H as la,dw as fa,bz as ua,aD as xa,ae as da,bI as va,$ as pa,dx as ha,bH as ga,dy as ma,aC as Ea,aO as ya,R as Ca,dz as Aa,bq as Ba,a_ as ba,aj as Sa,aY as Da,aZ as La,an as Fa,dA as Ta,dB as Oa,dC as wa,dD as Ma,dE as Ra,dF as Pa,dG as Ia,Q as $a,dH as Ha,dI as Na,dJ as za,at as ja,dK as Ua,dL as Ga,aT as Za,ad as Wa,aU as Va,dM as Ka,dN as Xa,dO as Ya,P as Qa,a6 as ka,dP as Ja,dQ as qa,dR as _a,dS as t2,a7 as e2,dT as r2,dU as n2,dV as a2,dW as o2,dX as i2,b0 as s2,aA as c2,dY as l2,dZ as f2,E as u2,x as x2,d_ as d2,d$ as v2,e0 as p2,am as h2,e1 as g2,az as m2,a$ as E2,e2 as y2,as as C2,e3 as A2,e4 as i0,bA as nr,e5 as ar}from"./index-BuyCAPrN.js";import{aa as c0}from"./index-CRsF8WVD.js";/**
+========
+import{cz as Ln,cA as Fn,aG as Tn,cB as On,cC as wn,cD as Mn,cE as Rn,ah as Pn,cF as In,cG as $n,cH as Hn,cI as Nn,aM as zn,aF as jn,cJ as Un,aB as Gn,be as Zn,cK as Wn,cL as Vn,cM as Kn,cN as Xn,cO as Yn,cP as Qn,cQ as kn,aE as Jn,cR as qn,s as _n,bj as t1,al as e1,ag as r1,af as n1,ak as a1,cS as o1,cT as i1,cU as s1,cV as c1,bi as l1,cW as f1,bo as u1,aq as x1,cX as d1,cY as v1,a5 as p1,cZ as h1,c_ as g1,c$ as m1,d0 as E1,d1 as y1,d2 as C1,d3 as A1,d4 as B1,d5 as b1,d6 as S1,d7 as D1,v as L1,d8 as F1,d9 as T1,da as O1,aR as w1,db as M1,dc as R1,dd as P1,de as I1,df as $1,ay as H1,dg as N1,dh as z1,di as j1,dj as U1,A as G1,dk as Z1,dl as W1,bp as V1,dm as K1,dn as X1,dp as Y1,ap as Q1,ac as k1,ai as J1,dq as q1,au as _1,dr as ta,X as ea,y as ra,ds as na,aP as aa,dt as oa,w as ia,du as sa,dv as ca,H as la,dw as fa,bz as ua,aD as xa,ae as da,bI as va,$ as pa,dx as ha,bH as ga,dy as ma,aC as Ea,aO as ya,R as Ca,dz as Aa,bq as Ba,a_ as ba,aj as Sa,aY as Da,aZ as La,an as Fa,dA as Ta,dB as Oa,dC as wa,dD as Ma,dE as Ra,dF as Pa,dG as Ia,Q as $a,dH as Ha,dI as Na,dJ as za,at as ja,dK as Ua,dL as Ga,aT as Za,ad as Wa,aU as Va,dM as Ka,dN as Xa,dO as Ya,P as Qa,a6 as ka,dP as Ja,dQ as qa,dR as _a,dS as t2,a7 as e2,dT as r2,dU as n2,dV as a2,dW as o2,dX as i2,b0 as s2,aA as c2,dY as l2,dZ as f2,E as u2,x as x2,d_ as d2,d$ as v2,e0 as p2,am as h2,e1 as g2,az as m2,a$ as E2,e2 as y2,as as C2,e3 as A2,e4 as i0,bA as nr,e5 as ar}from"./index-vudbuaSr.js";import{aa as c0}from"./index-CyZlGVz7.js";/**
+>>>>>>>> 1a387b7762cec8e6bfd2183e7939a6142fff3bf8:dist/assets/vuedraggable.umd-D-p0Zul4.js
 * vue v3.4.34
 * (c) 2018-present Yuxi (Evan) You and Vue contributors
 * @license MIT

+ 4 - 0
dist/index.html

@@ -10,7 +10,11 @@
     <meta name="description" content="ppt编辑器" />
     <meta name="keywords" content="ppt,powerpoint,office powerpoint,在线ppt,幻灯片,演示文稿,ppt在线制作,Vue3,TypeScript" />
     <title>PPT</title>
+<<<<<<< HEAD
     <script type="module" crossorigin src="./assets/index-BuyCAPrN.js"></script>
+=======
+    <script type="module" crossorigin src="./assets/index-vudbuaSr.js"></script>
+>>>>>>> 1a387b7762cec8e6bfd2183e7939a6142fff3bf8
     <link rel="stylesheet" crossorigin href="./assets/index-BrUV76Xx.css">
   </head>
   <body>

+ 3 - 0
src/App.vue

@@ -7,6 +7,9 @@
 <script lang="ts" setup>
 import { ElConfigProvider } from "element-plus"
 import zhCn from "element-plus/es/locale/lang/zh-cn"
+import { initQueryParams } from "@/queryParams"
+
+initQueryParams()
 </script>
 
 <style lang="scss"></style>

+ 12 - 0
src/api/pptOperate.ts

@@ -61,6 +61,18 @@ export const getMusicTagTreeApi = () => {
   })
 }
 
+// 获取标签
+export const getMaterialTagApi = () => {
+  return httpAxios.axioseRquest({
+    method: "post",
+    url: "/edu-app/materialTag/page",
+    data: {
+      page: 1,
+      rows: 9999
+    }
+  })
+}
+
 // 收藏课件 取消收藏
 export const favoriteApi = (data: { favoriteFlag: 0 | 1; materialId: string; type: string }) => {
   return httpAxios.axioseRquest({

+ 2 - 2
src/components/Modal.vue

@@ -79,7 +79,7 @@ const onClickMask = () => {
   left: 0;
   width: 100%;
   height: 100%;
-  z-index: 5000;
+  z-index: 1999;
 }
 
 .modal {
@@ -151,4 +151,4 @@ const onClickMask = () => {
     transform: scale3d(.3, .3, .3);
   }
 }
-</style>
+</style>

+ 28 - 2
src/hooks/useCreateElement.ts

@@ -320,7 +320,6 @@ export default () => {
 
   /**
    * 创建云教练元素
-   * @param url 云教练地址
    */
   const createCloudCoachElement = (sid: string, title: string) => {
     createElement({
@@ -337,6 +336,32 @@ export default () => {
     })
   }
 
+  /**
+   * 音频播放器控件
+   */
+  const createEnjoyElement = (sid: string, title: string, src: string) => {
+    createElement({
+      type: "elf",
+      subtype: "elf-enjoy",
+      sid,
+      title,
+      src,
+      enjoyList: [
+        {
+          id: sid,
+          title,
+          src
+        }
+      ],
+      id: nanoid(10),
+      width: 670,
+      height: 102,
+      rotate: 0,
+      left: 629,
+      top: 900
+    })
+  }
+
   return {
     createImageElement,
     createChartElement,
@@ -347,6 +372,7 @@ export default () => {
     createLatexElement,
     createVideoElement,
     createAudioElement,
-    createCloudCoachElement
+    createCloudCoachElement,
+    createEnjoyElement
   }
 }

+ 19 - 5
src/queryParams/index.ts

@@ -16,12 +16,26 @@ const queryParams = reactive<queryParamsType>({
 })
 
 export function initQueryParams() {
-  const query = router.currentRoute.value.query
-  queryParams.hideFullScreen = !!query.hideFullScreen
-  query.fromType && (queryParams.fromType = query.fromType as any)
-  query.lessonCoursewareKnowledgeId && (queryParams.lessonCoursewareKnowledgeId = query.lessonCoursewareKnowledgeId as any)
-  query.instrumentId && (queryParams.instrumentId = query.instrumentId as any)
+  const paramsFromUrl = getParamsFromUrl()
+  if (paramsFromUrl) {
+    const hideFullScreen = !!paramsFromUrl.get("hideFullScreen")
+    const fromType = paramsFromUrl.get("fromType")
+    const lessonCoursewareKnowledgeId = paramsFromUrl.get("lessonCoursewareKnowledgeId")
+    const instrumentId = paramsFromUrl.get("instrumentId")
+    queryParams.hideFullScreen = hideFullScreen
+    fromType && (queryParams.fromType = fromType as any)
+    lessonCoursewareKnowledgeId && (queryParams.lessonCoursewareKnowledgeId = lessonCoursewareKnowledgeId as any)
+    instrumentId && (queryParams.instrumentId = instrumentId as any)
+  }
   console.log(queryParams, "携带参数")
 }
 
+function getParamsFromUrl() {
+  const fullUrl = window.location.href
+  const queryIndex = fullUrl.indexOf("?")
+  if (queryIndex === -1) return undefined
+  const queryString = fullUrl.slice(queryIndex + 1)
+  const params = new URLSearchParams(queryString)
+  return params
+}
 export default queryParams

+ 7 - 1
src/store/main.ts

@@ -36,6 +36,7 @@ export interface MainState {
   showSelectPanel: boolean
   showSearchPanel: boolean
   showNotesPanel: boolean
+  isPPTWheelPage: boolean
 }
 
 const nanoid = customAlphabet("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
@@ -69,7 +70,8 @@ export const useMainStore = defineStore("main", {
     shapeFormatPainter: null, // 形状格式刷
     showSelectPanel: false, // 打开选择面板
     showSearchPanel: false, // 打开查找替换面板
-    showNotesPanel: false // 打开批注面板
+    showNotesPanel: false, // 打开批注面板
+    isPPTWheelPage: true //控制ppt能不能滚轮翻页,音频播放器列表页使用
   }),
 
   getters: {
@@ -194,6 +196,10 @@ export const useMainStore = defineStore("main", {
 
     setNotesPanelState(show: boolean) {
       this.showNotesPanel = show
+    },
+
+    setIsPPTWheelPageState(isWheel: boolean) {
+      this.isPPTWheelPage = isWheel
     }
   }
 })

+ 1 - 2
src/store/pptWork.ts

@@ -9,14 +9,13 @@ import fileUpload from "@/utils/oss-file-upload"
 import { ElMessage } from "element-plus"
 import { toBlob } from "html-to-image"
 import { useSlidesStore } from "@/store"
-import queryParams, { initQueryParams } from "@/queryParams"
+import queryParams from "@/queryParams"
 import router from "@/router"
 
 type pptWork = { id: string; coverImg: string; jsonUrl: string; isSave: boolean }
 
 const useStore = defineStore("pptWork", {
   state: (): pptWork => {
-    initQueryParams()
     const route = useRoute()
     return {
       id: route.query.id as string,

+ 27 - 1
src/types/slides.ts

@@ -34,7 +34,8 @@ export const enum ElementTypes {
 export const enum ElementSubtypeTypes {
   VIDEO = "elf-video",
   AUDIO = "elf-audio",
-  SING_PLAY = "elf-sing-play"
+  SING_PLAY = "elf-sing-play",
+  ENJOY = "elf-enjoy"
 }
 
 /**
@@ -635,6 +636,30 @@ export interface PPTCloudCoachElement extends PPTBaseElement {
   title: string
 }
 
+/**
+ * 音频播放器控件
+ *
+ * type: elf
+ *
+ * subtype: elf-enjoy
+ *
+ * sid: 音频id
+ *
+ * title:音频名称
+ */
+export interface PPTEnjoyElement extends PPTBaseElement {
+  type: "elf"
+  subtype: "elf-enjoy"
+  sid: string
+  title: string
+  src: string
+  enjoyList: {
+    id: string
+    title: string
+    src: string
+  }[]
+}
+
 export type PPTElement =
   | PPTTextElement
   | PPTImageElement
@@ -646,6 +671,7 @@ export type PPTElement =
   | PPTVideoElement
   | PPTAudioElement
   | PPTCloudCoachElement
+  | PPTEnjoyElement
 
 export type AnimationType = "in" | "out" | "attention"
 export type AnimationTrigger = "click" | "meantime" | "auto"

+ 3 - 1
src/views/Editor/Canvas/EditableElement.vue

@@ -36,6 +36,7 @@ import LatexElement from "@/views/components/element/LatexElement/index.vue"
 import VideoElement from "@/views/components/element/VideoElement/index.vue"
 import AudioElement from "@/views/components/element/AudioElement/index.vue"
 import cloudCoachElement from "@/views/components/element/cloudCoachElement"
+import enjoyElement from "@/views/components/element/enjoyElement"
 
 const props = defineProps<{
   elementInfo: PPTElement
@@ -59,7 +60,8 @@ const currentElementComponent = computed<unknown>(() => {
   const elementSubtypeMap = {
     [ElementSubtypeTypes.AUDIO]: AudioElement,
     [ElementSubtypeTypes.VIDEO]: VideoElement,
-    [ElementSubtypeTypes.SING_PLAY]: cloudCoachElement
+    [ElementSubtypeTypes.SING_PLAY]: cloudCoachElement,
+    [ElementSubtypeTypes.ENJOY]: enjoyElement
   }
   return elementTypeMap[props.elementInfo.type] || elementSubtypeMap[props.elementInfo.subtype] || null
 })

+ 8 - 4
src/views/Editor/Canvas/index.vue

@@ -134,7 +134,8 @@ const {
   creatingElement,
   creatingCustomShape,
   canvasScale,
-  textFormatPainter
+  textFormatPainter,
+  isPPTWheelPage
 } = storeToRefs(mainStore)
 const { currentSlide } = storeToRefs(useSlidesStore())
 const { ctrlKeyState, spaceKeyState } = storeToRefs(useKeyboardStore())
@@ -158,7 +159,8 @@ watchEffect(setLocalElementList)
 const canvasRef = ref<HTMLElement>()
 const { dragViewport, viewportStyles } = useViewportSize(canvasRef)
 
-useDropImageOrText(canvasRef)
+/* 这个功能和 enjoyPlayer 冲突,所以禁用掉 */
+//useDropImageOrText(canvasRef)
 
 const { mouseSelection, mouseSelectionVisible, mouseSelectionQuadrant, updateMouseSelection } = useMouseSelection(elementList, viewportRef)
 
@@ -229,15 +231,17 @@ const throttleScaleCanvas = throttle(scaleCanvas, 100, { leading: true, trailing
 const throttleUpdateSlideIndex = throttle(updateSlideIndex, 300, { leading: true, trailing: false })
 
 const handleMousewheelCanvas = (e: WheelEvent) => {
-  e.preventDefault()
-
   // 按住Ctrl键时:缩放画布
   if (ctrlKeyState.value) {
+    e.preventDefault()
     if (e.deltaY > 0) throttleScaleCanvas("-")
     else if (e.deltaY < 0) throttleScaleCanvas("+")
   }
   // 上下翻页
   else {
+    // 控制能不能翻页
+    if (!isPPTWheelPage.value) return
+    e.preventDefault()
     if (e.deltaY > 0) throttleUpdateSlideIndex(KEYS.DOWN)
     else if (e.deltaY < 0) throttleUpdateSlideIndex(KEYS.UP)
   }

+ 74 - 6
src/views/Editor/CanvasTool/index.vue

@@ -68,11 +68,11 @@
       <div class="handler-item">
         <img class="itemImg" src="./imgs/tylx.png" alt="" />
         <div class="tit">听音练习</div>
-      </div>
-      <div class="handler-item">
+      </div> -->
+      <div class="handler-item" @click="resourcesListVisible = true">
         <img class="itemImg" src="./imgs/zyk.png" alt="" />
         <div class="tit">资源库</div>
-      </div> -->
+      </div>
       <div class="handler-item" @click="drawText()" :class="{ active: creatingElement?.type === 'text' }">
         <img class="itemImg" src="./imgs/wz.png" alt="" />
         <Popover trigger="click" v-model:value="textTypeSelectVisible" :offset="10">
@@ -246,13 +246,34 @@
         "
       />
     </Modal>
+    <Modal
+      :contentStyle="{
+        width: '70%',
+        minWidth: '1200px',
+        height: '86%',
+        boxShadow: '0px 2px 10px 0px rgba(0,0,0,0.08)',
+        borderRadius: '16px',
+        border: '1px solid #DEDEDE',
+        padding: '0'
+      }"
+      v-model:visible="resourcesListVisible"
+    >
+      <resourcesList
+        @update="handleResources"
+        @close="
+          () => {
+            resourcesListVisible = false
+          }
+        "
+      />
+    </Modal>
   </div>
 </template>
 
 <script lang="ts" setup>
 import { ref } from "vue"
 import { storeToRefs } from "pinia"
-import { useMainStore, useSnapshotStore } from "@/store"
+import { useMainStore, useSnapshotStore, useSlidesStore } from "@/store"
 import { getImageDataURL } from "@/utils/image"
 import type { ShapePoolItem } from "@/configs/shapes"
 import type { LinePoolItem } from "@/configs/lines"
@@ -272,9 +293,12 @@ import Popover from "@/components/Popover.vue"
 import PopoverMenuItem from "@/components/PopoverMenuItem.vue"
 import { ElUpload, ElMessage, type UploadRequestOptions } from "element-plus"
 import cloudCoachList from "@/views/components/element/cloudCoachElement/cloudCoachList"
+import resourcesList from "@/views/components/element/enjoyElement/resourcesList"
 import fileUpload from "@/utils/oss-file-upload"
 import usePptWork from "@/store/pptWork"
 
+const useSlidesHook = useSlidesStore()
+
 const usePptWorkHook = usePptWork()
 const mainStore = useMainStore()
 const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
@@ -301,7 +325,8 @@ const {
   createLatexElement,
   createVideoElement,
   createAudioElement,
-  createCloudCoachElement
+  createCloudCoachElement,
+  createEnjoyElement
 } = useCreateElement()
 
 const insertImageElement = (files: FileList) => {
@@ -318,6 +343,7 @@ const latexEditorVisible = ref(false)
 const textTypeSelectVisible = ref(false)
 const shapeMenuVisible = ref(false)
 const cloudCoachVisible = ref(false)
+const resourcesListVisible = ref(false)
 const moreToolsVisible = ref(false)
 
 // 音视频
@@ -345,7 +371,49 @@ function handleCloudCoach(id: string, name: string) {
   createCloudCoachElement(id, name)
   cloudCoachVisible.value = false
 }
-
+// 处理资源创建
+function handleResources(item: Record<string, any>) {
+  if (item.type === "SONG") {
+    // 当前页没有enjoy的时候新增,有的时候添加
+    const enjoyElement = useSlidesHook.currentSlide.elements.find(itemV => {
+      return itemV.type === "elf" && itemV.subtype === "elf-enjoy"
+    })
+    if (enjoyElement) {
+      if (enjoyElement.enjoyList.length >= 10) {
+        ElMessage({
+          showClose: true,
+          message: "单个播放器最多添加10首曲目!",
+          type: "warning"
+        })
+        return
+      }
+      const enjoyData = enjoyElement.enjoyList.find(itemV => {
+        return itemV.id === item.id
+      })
+      if (enjoyData) {
+        ElMessage({
+          showClose: true,
+          message: "请勿重复添加曲目!",
+          type: "warning"
+        })
+      } else {
+        enjoyElement.enjoyList.push({
+          id: item.id,
+          title: item.name,
+          src: item.content
+        })
+      }
+    } else {
+      createEnjoyElement(item.id, item.name, item.content)
+    }
+  } else if (item.type === "IMG") {
+    createImageElement(item.content)
+    resourcesListVisible.value = false
+  } else if (item.type === "VIDEO") {
+    createVideoElement(item.content)
+    resourcesListVisible.value = false
+  }
+}
 // 绘制文字范围
 const drawText = (vertical = false) => {
   mainStore.setCreatingElement({

+ 7 - 0
src/views/Editor/Toolbar/ElementStylePanel/EnjoyStylePanel.vue

@@ -0,0 +1,7 @@
+<template>
+  <div class="EnjoyStylePanel">音频播放器</div>
+</template>
+
+<script setup lang="ts"></script>
+
+<style lang="scss" scoped></style>

+ 3 - 1
src/views/Editor/Toolbar/ElementStylePanel/index.vue

@@ -21,6 +21,7 @@ import VideoStylePanel from "./VideoStylePanel.vue"
 import AudioStylePanel from "./AudioStylePanel.vue"
 import MultiStylePanel from "./MultiStylePanel.vue"
 import CloudCoachStylePanel from "./CloudCoachStylePanel.vue"
+import EnjoyStylePanel from "./EnjoyStylePanel.vue"
 
 const panelMap = {
   [ElementTypes.TEXT]: TextStylePanel,
@@ -35,7 +36,8 @@ const panelMap = {
 const elementSubtypeMap = {
   [ElementSubtypeTypes.AUDIO]: AudioStylePanel,
   [ElementSubtypeTypes.VIDEO]: VideoStylePanel,
-  [ElementSubtypeTypes.SING_PLAY]: CloudCoachStylePanel
+  [ElementSubtypeTypes.SING_PLAY]: CloudCoachStylePanel,
+  [ElementSubtypeTypes.ENJOY]: EnjoyStylePanel
 }
 const { activeElementIdList, activeElementList, handleElement, activeGroupElementId } = storeToRefs(useMainStore())
 

+ 1 - 1
src/views/Editor/Toolbar/index.vue

@@ -39,7 +39,7 @@ const elementTabs = computed<ElementTabs[]>(() => {
       { label: "动画", key: ToolbarStates.EL_ANIMATION }
     ]
   }
-  if (handleElement.value?.type === "elf" && handleElement.value?.subtype === "elf-sing-play") {
+  if (handleElement.value?.type === "elf" && ["elf-sing-play", "elf-enjoy"].includes(handleElement.value?.subtype)) {
     return [
       { label: "位置", key: ToolbarStates.EL_POSITION },
       { label: "动画", key: ToolbarStates.EL_ANIMATION }

+ 3 - 1
src/views/Mobile/MobileEditor/MobileEditableElement.vue

@@ -23,6 +23,7 @@ import LatexElement from "@/views/components/element/LatexElement/index.vue"
 import VideoElement from "@/views/components/element/VideoElement/index.vue"
 import AudioElement from "@/views/components/element/AudioElement/index.vue"
 import cloudCoachElement from "@/views/components/element/cloudCoachElement"
+import enjoyElement from "@/views/components/element/enjoyElement"
 
 const props = defineProps<{
   elementInfo: PPTElement
@@ -44,7 +45,8 @@ const currentElementComponent = computed<unknown>(() => {
   const elementSubtypeMap = {
     [ElementSubtypeTypes.AUDIO]: AudioElement,
     [ElementSubtypeTypes.VIDEO]: VideoElement,
-    [ElementSubtypeTypes.SING_PLAY]: cloudCoachElement
+    [ElementSubtypeTypes.SING_PLAY]: cloudCoachElement,
+    [ElementSubtypeTypes.ENJOY]: enjoyElement
   }
   return elementTypeMap[props.elementInfo.type] || elementSubtypeMap[props.elementInfo.subtype] || null
 })

+ 3 - 1
src/views/Screen/ScreenElement.vue

@@ -33,6 +33,7 @@ import BaseLatexElement from "@/views/components/element/LatexElement/BaseLatexE
 import ScreenVideoElement from "@/views/components/element/VideoElement/ScreenVideoElement.vue"
 import ScreenAudioElement from "@/views/components/element/AudioElement/ScreenAudioElement.vue"
 import ScreenCloudCoachElement from "@/views/components/element/cloudCoachElement/ScreenCloudCoachElement.vue"
+import ScreenEnjoyElement from "@/views/components/element/enjoyElement/ScreenEnjoyElement.vue"
 
 const props = defineProps<{
   elementInfo: PPTElement
@@ -56,7 +57,8 @@ const currentElementComponent = computed<unknown>(() => {
   const elementSubtypeMap = {
     [ElementSubtypeTypes.AUDIO]: ScreenAudioElement,
     [ElementSubtypeTypes.VIDEO]: ScreenVideoElement,
-    [ElementSubtypeTypes.SING_PLAY]: ScreenCloudCoachElement
+    [ElementSubtypeTypes.SING_PLAY]: ScreenCloudCoachElement,
+    [ElementSubtypeTypes.ENJOY]: ScreenEnjoyElement
   }
   return elementTypeMap[props.elementInfo.type] || elementSubtypeMap[props.elementInfo.subtype] || null
 })

+ 5 - 1
src/views/Screen/hooks/useExecPlay.ts

@@ -1,7 +1,7 @@
 import { onMounted, onUnmounted, ref } from "vue"
 import { throttle } from "lodash"
 import { storeToRefs } from "pinia"
-import { useSlidesStore, useScreenStore } from "@/store"
+import { useSlidesStore, useScreenStore, useMainStore } from "@/store"
 import { KEYS } from "@/configs/hotkey"
 import { ANIMATION_CLASS_PREFIX } from "@/configs/animation"
 import message from "@/utils/message"
@@ -9,7 +9,9 @@ import { changePageSlideMes } from "@/messageHooks/pptScreen"
 
 export default () => {
   const slidesStore = useSlidesStore()
+  const mainStore = useMainStore()
   const { slides, slideIndex, formatedAnimations } = storeToRefs(slidesStore)
+  const { isPPTWheelPage } = storeToRefs(mainStore)
 
   // 当前页的元素动画执行到的位置
   const animationIndex = ref(0)
@@ -175,6 +177,8 @@ export default () => {
   // 鼠标滚动翻页
   const mousewheelListener = throttle(
     function (e: WheelEvent) {
+      // 控制能不能翻页
+      if (!isPPTWheelPage.value) return
       if (e.deltaY < 0) execPrev()
       else if (e.deltaY > 0) execNext()
     },

+ 3 - 1
src/views/components/ThumbnailSlide/ThumbnailElement.vue

@@ -24,6 +24,7 @@ import BaseLatexElement from "@/views/components/element/LatexElement/BaseLatexE
 import BaseVideoElement from "@/views/components/element/VideoElement/BaseVideoElement.vue"
 import BaseAudioElement from "@/views/components/element/AudioElement/BaseAudioElement.vue"
 import BaseCloudCoachElement from "@/views/components/element/cloudCoachElement/BaseCloudCoachElement.vue"
+import BaseEnjoyElement from "@/views/components/element/enjoyElement/BaseEnjoyElement.vue"
 
 const props = defineProps<{
   elementInfo: PPTElement
@@ -44,7 +45,8 @@ const currentElementComponent = computed<unknown>(() => {
   const elementSubtypeMap = {
     [ElementSubtypeTypes.AUDIO]: BaseAudioElement,
     [ElementSubtypeTypes.VIDEO]: BaseVideoElement,
-    [ElementSubtypeTypes.SING_PLAY]: BaseCloudCoachElement
+    [ElementSubtypeTypes.SING_PLAY]: BaseCloudCoachElement,
+    [ElementSubtypeTypes.ENJOY]: BaseEnjoyElement
   }
   return elementTypeMap[props.elementInfo.type] || elementSubtypeMap[props.elementInfo.subtype] || null
 })

+ 10 - 2
src/views/components/element/AudioElement/AudioPlayer.vue

@@ -122,6 +122,7 @@ const paused = ref(true)
 const currentTime = ref(0)
 const duration = ref(0)
 const loaded = ref(0)
+const isThumbDown = ref(false)
 
 const playBarTimeVisible = ref(false)
 const playBarTime = ref("00:00")
@@ -186,6 +187,9 @@ const handlePlayed = () => {
 }
 
 const handleEnded = () => {
+  if (isThumbDown.value) {
+    return
+  }
   if (!props.loop) pause()
   else {
     seek(0)
@@ -197,7 +201,7 @@ const handleProgress = () => {
   loaded.value = audioRef.value?.buffered.length ? audioRef.value.buffered.end(audioRef.value.buffered.length - 1) : 0
 }
 
-const handleError = () => message.error("频加载失败")
+const handleError = () => message.error("频加载失败")
 
 const thumbMove = (e: MouseEvent | TouchEvent) => {
   if (!audioRef.value || !playBarWrap.value) return
@@ -222,7 +226,10 @@ const thumbUp = (e: MouseEvent | TouchEvent) => {
 
   audioRef.value.currentTime = time
   currentTime.value = time
-
+  const _time = setTimeout(() => {
+    clearTimeout(_time)
+    isThumbDown.value = false
+  }, 500)
   document.removeEventListener("mousemove", thumbMove)
   document.removeEventListener("touchmove", thumbMove)
   document.removeEventListener("mouseup", thumbUp)
@@ -230,6 +237,7 @@ const thumbUp = (e: MouseEvent | TouchEvent) => {
 }
 
 const handleMousedownPlayBar = () => {
+  isThumbDown.value = true
   document.addEventListener("mousemove", thumbMove)
   document.addEventListener("touchmove", thumbMove)
   document.addEventListener("mouseup", thumbUp)

+ 41 - 0
src/views/components/element/enjoyElement/BaseEnjoyElement.vue

@@ -0,0 +1,41 @@
+<template>
+  <div
+    class="base-element-enjoy"
+    :style="{
+      top: elementInfo.top + 'px',
+      left: elementInfo.left + 'px',
+      width: elementInfo.width + 'px',
+      height: elementInfo.height + 'px'
+    }"
+  >
+    <div class="rotate-wrapper" :style="{ transform: `rotate(${elementInfo.rotate}deg)` }">
+      <div class="element-content">
+        <enjoyPlayer :elementInfo="elementInfo" :isBase="true" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import type { PPTEnjoyElement } from "@/types/slides"
+import enjoyPlayer from "./enjoyPlayer.vue"
+
+defineProps<{
+  elementInfo: PPTEnjoyElement
+}>()
+</script>
+
+<style lang="scss" scoped>
+.base-element-enjoy {
+  position: absolute;
+}
+.rotate-wrapper {
+  width: 100%;
+  height: 100%;
+}
+.element-content {
+  width: 100%;
+  height: 100%;
+  position: relative;
+}
+</style>

+ 51 - 0
src/views/components/element/enjoyElement/ScreenEnjoyElement.vue

@@ -0,0 +1,51 @@
+<template>
+  <div
+    class="base-element-enjoy screen-element-enjoy"
+    :style="{
+      top: elementInfo.top + 'px',
+      left: elementInfo.left + 'px',
+      width: elementInfo.width + 'px',
+      height: elementInfo.height + 'px'
+    }"
+  >
+    <div class="rotate-wrapper" :style="{ transform: `rotate(${elementInfo.rotate}deg)` }">
+      <div class="element-content">
+        <enjoyPlayer v-if="inCurrentSlide" :elementInfo="elementInfo" :scale="scale" :isScreening="true" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { inject, ref, computed } from "vue"
+import { storeToRefs } from "pinia"
+import { useSlidesStore } from "@/store"
+import type { PPTEnjoyElement } from "@/types/slides"
+import enjoyPlayer from "./enjoyPlayer.vue"
+import { injectKeySlideScale, injectKeySlideId } from "@/types/injectKey"
+
+defineProps<{
+  elementInfo: PPTEnjoyElement
+}>()
+
+const { currentSlide } = storeToRefs(useSlidesStore())
+
+const scale = inject(injectKeySlideScale) || ref(1)
+const slideId = inject(injectKeySlideId) || ref("")
+
+const inCurrentSlide = computed(() => currentSlide.value.id === slideId.value)
+</script>
+
+<style lang="scss" scoped>
+.screen-element-enjoy {
+  position: absolute;
+}
+.rotate-wrapper {
+  width: 100%;
+  height: 100%;
+}
+.element-content {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 102 - 0
src/views/components/element/enjoyElement/enjoyElement.vue

@@ -0,0 +1,102 @@
+<template>
+  <div
+    class="editable-element-enjoyElement"
+    :class="{ lock: elementInfo.lock }"
+    :style="{
+      top: elementInfo.top + 'px',
+      left: elementInfo.left + 'px',
+      width: elementInfo.width + 'px',
+      height: elementInfo.height + 'px'
+    }"
+  >
+    <div class="rotate-wrapper" :style="{ transform: `rotate(${elementInfo.rotate}deg)` }">
+      <div
+        class="element-content"
+        v-contextmenu="contextmenus"
+        @mousedown="$event => handleSelectElement($event, false)"
+        @touchstart="$event => handleSelectElement($event, false)"
+      >
+        <enjoyPlayer :elementInfo="elementInfo" :scale="canvasScale" />
+        <div
+          :class="['handler-border', item]"
+          v-for="item in ['t', 'b', 'l', 'r']"
+          :key="item"
+          @mousedown="$event => handleSelectElement($event)"
+          @touchstart="$event => handleSelectElement($event)"
+        ></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import type { PPTEnjoyElement } from "@/types/slides"
+import type { ContextmenuItem } from "@/components/Contextmenu/types"
+import enjoyPlayer from "./enjoyPlayer.vue"
+import { storeToRefs } from "pinia"
+import { useMainStore } from "@/store"
+
+const props = defineProps<{
+  elementInfo: PPTEnjoyElement
+  selectElement: (e: MouseEvent | TouchEvent, element: PPTEnjoyElement, canMove?: boolean) => void
+  contextmenus: () => ContextmenuItem[] | null
+}>()
+
+const mainStore = useMainStore()
+const { canvasScale } = storeToRefs(mainStore)
+
+const handleSelectElement = (e: MouseEvent | TouchEvent, canMove = true) => {
+  if (props.elementInfo.lock) return
+  e.stopPropagation()
+
+  props.selectElement(e, props.elementInfo, canMove)
+}
+</script>
+
+<style lang="scss" scoped>
+.editable-element-enjoyElement {
+  position: absolute;
+
+  &.lock .handler-border {
+    cursor: default;
+  }
+}
+.rotate-wrapper {
+  width: 100%;
+  height: 100%;
+}
+.element-content {
+  width: 100%;
+  height: 100%;
+  position: relative;
+}
+.handler-border {
+  position: absolute;
+  cursor: move;
+
+  &.t {
+    width: 100%;
+    height: 20px;
+    top: 0;
+    left: 0;
+  }
+  &.b {
+    width: 100%;
+    height: 5px;
+    bottom: 0;
+    left: 0;
+  }
+  &.l {
+    width: 10px;
+    height: 100%;
+    left: 0;
+    top: 0;
+  }
+  &.r {
+    width: 10px;
+    height: 100%;
+    right: 0;
+    top: 0;
+  }
+}
+</style>

+ 558 - 0
src/views/components/element/enjoyElement/enjoyPlayer.vue

@@ -0,0 +1,558 @@
+<template>
+  <div class="enjoyPlayer">
+    <div v-if="!isBase" v-show="isShowEnjoyPlayerList" class="enjoyPlayerList">
+      <div class="titNameCon">
+        <div class="titName">
+          <div class="tit">{{ `音频列表 (${elementInfo.enjoyList?.length || 0})` }}</div>
+        </div>
+      </div>
+      <div class="enjoyPlayerListCon">
+        <draggable
+          :handle="'.drag-handle_enjoyPlayer'"
+          v-model="elementInfo.enjoyList"
+          itemKey="id"
+          :animation="200"
+          :scroll="true"
+          :scrollSensitivity="50"
+          @end="handleDragEnd"
+        >
+          <template #item="{ element }">
+            <div class="playerItem" :class="{ active: element.id === elementInfo.sid }" @click="handlePlayMusic(element)">
+              <div class="itemLeft">
+                {{ element.title }}
+              </div>
+              <div class="itemRight">
+                <template v-if="!isScreening">
+                  <div class="itemListBtn drag-handle_enjoyPlayer" @click.stop></div>
+                  <div class="itemCloseBtn" @click.stop="handleEnjoyDel(element.id)"></div>
+                </template>
+              </div>
+            </div>
+          </template>
+        </draggable>
+      </div>
+    </div>
+    <audio
+      v-if="!isBase"
+      ref="audioRef"
+      :src="elementInfo.src"
+      @durationchange="handleDurationchange()"
+      @timeupdate="handleTimeupdate()"
+      @play="handlePlayed()"
+      @pause="handlePaused()"
+      @ended="handleEnded()"
+      @progress="handleProgress()"
+      @error="handleError()"
+    ></audio>
+    <div class="playerCon">
+      <img class="tipImg" src="./imgs/tip.png" alt="" />
+      <div class="operateBtn" :class="{ paused: paused }" @click="toggle"></div>
+      <div class="operateMidCon">
+        <div class="titleCon">
+          <div class="title">{{ elementInfo.title }}</div>
+          <div class="timesCon">
+            {{ `${ptime}/${dtime}` }}
+          </div>
+        </div>
+        <div class="bar-wrap" ref="playBarWrap" @mousedown="handleMousedownPlayBar()" @touchstart="handleMousedownPlayBar()">
+          <div class="bar">
+            <div class="loaded" :style="{ width: loadedBarWidth }"></div>
+            <div class="played" :style="{ width: playedBarWidth }">
+              <div class="thumb"></div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="operateRightBtn">
+        <div class="preBtn" @click="handleChangeMusic('pre')"></div>
+        <div class="nextBtn" @click="handleChangeMusic('next')"></div>
+        <div class="listBtn" @click="hanleEnjoyList"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, ref, onUnmounted } from "vue"
+import message from "@/utils/message"
+import type { PPTEnjoyElement } from "@/types/slides"
+import { useSlidesStore, useMainStore } from "@/store"
+import draggable from "vuedraggable"
+
+const slidesStore = useSlidesStore()
+const mainStore = useMainStore()
+const props = withDefaults(
+  defineProps<{
+    scale?: number
+    elementInfo: PPTEnjoyElement
+    isScreening?: boolean
+    isBase?: boolean
+  }>(),
+  {
+    scale: 1,
+    isScreening: false,
+    isBase: false
+  }
+)
+
+const isShowEnjoyPlayerList = ref(false)
+
+function hanleEnjoyList() {
+  if (props.isBase) {
+    return
+  }
+  isShowEnjoyPlayerList.value = !isShowEnjoyPlayerList.value
+  mainStore.setIsPPTWheelPageState(!isShowEnjoyPlayerList.value)
+}
+// 卸载之后让ppt能滚动
+onUnmounted(() => {
+  mainStore.setIsPPTWheelPageState(true)
+})
+function handleDragEnd() {
+  slidesStore.updateElement({
+    id: props.elementInfo.id,
+    props: {
+      enjoyList: props.elementInfo.enjoyList
+    }
+  })
+}
+
+function handleEnjoyDel(id: string) {
+  if (id === props.elementInfo.sid) {
+    //删除组件
+    if (props.elementInfo.enjoyList.length === 1) {
+      slidesStore.deleteElement(props.elementInfo.id)
+    } else {
+      const index = props.elementInfo.enjoyList.findIndex(item => {
+        return item.id === id
+      })
+      // eslint-disable-next-line vue/no-mutating-props
+      props.elementInfo.enjoyList.splice(index, 1)
+      const enjoyList = props.elementInfo.enjoyList
+      slidesStore.updateElement({
+        id: props.elementInfo.id,
+        props: {
+          sid: enjoyList[0].id,
+          title: enjoyList[0].title,
+          src: enjoyList[0].src,
+          enjoyList: enjoyList
+        }
+      })
+      // 关闭播放按钮
+      paused.value = true
+    }
+  } else {
+    const index = props.elementInfo.enjoyList.findIndex(item => {
+      return item.id === id
+    })
+    // eslint-disable-next-line vue/no-mutating-props
+    props.elementInfo.enjoyList.splice(index, 1)
+    slidesStore.updateElement({
+      id: props.elementInfo.id,
+      props: {
+        enjoyList: props.elementInfo.enjoyList
+      }
+    })
+  }
+}
+
+function handleChangeMusic(type: "pre" | "next") {
+  if (props.elementInfo.enjoyList.length === 1) {
+    seek(0)
+  } else {
+    let index = props.elementInfo.enjoyList.findIndex(item => {
+      return item.id === props.elementInfo.sid
+    })
+    index += type === "next" ? 1 : -1
+    if (index > props.elementInfo.enjoyList.length - 1) {
+      index = 0
+    } else if (index < 0) {
+      index = props.elementInfo.enjoyList.length - 1
+    }
+    const enjoyData = props.elementInfo.enjoyList[index]
+    slidesStore.updateElement({
+      id: props.elementInfo.id,
+      props: {
+        sid: enjoyData.id,
+        title: enjoyData.title,
+        src: enjoyData.src
+      }
+    })
+  }
+}
+
+function handlePlayMusic(item: Record<string, any>) {
+  if (item.id === props.elementInfo.sid) {
+    toggle()
+  } else {
+    slidesStore.updateElement({
+      id: props.elementInfo.id,
+      props: {
+        sid: item.id,
+        title: item.title,
+        src: item.src
+      }
+    })
+  }
+}
+
+const secondToTime = (second = 0) => {
+  if (second === 0 || isNaN(second)) return "00:00"
+
+  const add0 = (num: number) => (num < 10 ? "0" + num : "" + num)
+  const hour = Math.floor(second / 3600)
+  const min = Math.floor((second - hour * 3600) / 60)
+  const sec = Math.floor(second - hour * 3600 - min * 60)
+  return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(":")
+}
+
+const getBoundingClientRectViewLeft = (element: HTMLElement) => {
+  return element.getBoundingClientRect().left
+}
+
+const audioRef = ref<HTMLAudioElement>()
+const playBarWrap = ref<HTMLElement>()
+
+const paused = ref(true)
+const currentTime = ref(0)
+const duration = ref(0)
+const loaded = ref(0)
+
+const isThumbDown = ref(false)
+
+const ptime = computed(() => secondToTime(currentTime.value))
+const dtime = computed(() => secondToTime(duration.value))
+const playedBarWidth = computed(() => (currentTime.value / duration.value) * 100 + "%")
+const loadedBarWidth = computed(() => (loaded.value / duration.value) * 100 + "%")
+
+const seek = (time: number) => {
+  if (!audioRef.value) return
+
+  time = Math.max(time, 0)
+  time = Math.min(time, duration.value)
+
+  audioRef.value.currentTime = time
+  currentTime.value = time
+}
+
+const play = () => {
+  if (!audioRef.value) return
+
+  paused.value = false
+  audioRef.value.play()
+}
+
+const pause = () => {
+  if (!audioRef.value) return
+
+  paused.value = true
+  audioRef.value.pause()
+}
+
+const toggle = () => {
+  if (paused.value) play()
+  else pause()
+}
+
+const handleDurationchange = () => {
+  duration.value = audioRef.value?.duration || 0
+}
+
+const handleTimeupdate = () => {
+  currentTime.value = audioRef.value?.currentTime || 0
+}
+
+const handlePlayed = () => {
+  paused.value = false
+}
+
+const handlePaused = () => {
+  paused.value = true
+}
+const handleEnded = () => {
+  return
+  if (isThumbDown.value) {
+    return
+  }
+  seek(0)
+  play()
+}
+
+const handleProgress = () => {
+  loaded.value = audioRef.value?.buffered.length ? audioRef.value.buffered.end(audioRef.value.buffered.length - 1) : 0
+  if (!paused.value) {
+    play()
+  }
+}
+
+const handleError = () => message.error("音频加载失败")
+
+const thumbMove = (e: MouseEvent | TouchEvent) => {
+  if (!audioRef.value || !playBarWrap.value) return
+  const clientX = "clientX" in e ? e.clientX : e.changedTouches[0].clientX
+  let percentage = (clientX - getBoundingClientRectViewLeft(playBarWrap.value)) / playBarWrap.value.clientWidth / props.scale
+  percentage = Math.max(percentage, 0)
+  percentage = Math.min(percentage, 1)
+  const time = percentage * duration.value
+
+  audioRef.value.currentTime = time
+  currentTime.value = time
+}
+
+const thumbUp = (e: MouseEvent | TouchEvent) => {
+  if (!audioRef.value || !playBarWrap.value) return
+
+  const clientX = "clientX" in e ? e.clientX : e.changedTouches[0].clientX
+  let percentage = (clientX - getBoundingClientRectViewLeft(playBarWrap.value)) / playBarWrap.value.clientWidth / props.scale
+  percentage = Math.max(percentage, 0)
+  percentage = Math.min(percentage, 1)
+  const time = percentage * duration.value
+
+  audioRef.value.currentTime = time
+  currentTime.value = time
+  const _time = setTimeout(() => {
+    clearTimeout(_time)
+    isThumbDown.value = false
+  }, 500)
+  document.removeEventListener("mousemove", thumbMove)
+  document.removeEventListener("touchmove", thumbMove)
+  document.removeEventListener("mouseup", thumbUp)
+  document.removeEventListener("touchend", thumbUp)
+}
+
+const handleMousedownPlayBar = () => {
+  isThumbDown.value = true
+  document.addEventListener("mousemove", thumbMove)
+  document.addEventListener("touchmove", thumbMove)
+  document.addEventListener("mouseup", thumbUp)
+  document.addEventListener("touchend", thumbUp)
+}
+</script>
+
+<style lang="scss" scoped>
+.enjoyPlayer {
+  transform-origin: left top;
+  .playerCon {
+    background: linear-gradient(180deg, #ffffff 0%, #dfdfdf 100%);
+    box-shadow:
+      0px 3px 16px 0px rgba(0, 0, 0, 0.08),
+      inset 0px -6px 6px 0px rgba(0, 0, 0, 0.08);
+    border-radius: 112px;
+    outline: 2px solid rgba(0, 0, 0, 0.03);
+    padding: 16px 21px;
+    width: 670px;
+    display: flex;
+    align-items: center;
+    position: relative;
+    z-index: 1;
+    .tipImg {
+      position: absolute;
+      width: 44px;
+      height: 12px;
+      right: 47px;
+      top: 7px;
+    }
+    .operateBtn {
+      flex-shrink: 0;
+      background: url("./imgs/pause.png") no-repeat;
+      background-size: 100% 100%;
+      width: 70px;
+      height: 70px;
+      cursor: pointer;
+      &.paused {
+        background: url("./imgs/play.png") no-repeat;
+        background-size: 100% 100%;
+      }
+    }
+    .operateMidCon {
+      margin: 0 21px;
+      flex-grow: 1;
+      .titleCon {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        .title {
+          width: 200px;
+          font-weight: 600;
+          font-size: 22px;
+          color: #131415;
+          line-height: 30px;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+        .timesCon {
+          flex-shrink: 0;
+          font-weight: 400;
+          font-size: 18px;
+          color: #999999;
+          line-height: 25px;
+        }
+      }
+      .bar-wrap {
+        position: relative;
+        cursor: pointer;
+        width: 100%;
+        margin-top: 12px;
+        .bar {
+          position: relative;
+          height: 10px;
+          width: 100%;
+          background: #dcdcdc;
+          box-shadow: inset 0px 2px 3px 0px #a9a9a9;
+          border-radius: 5px;
+          outline: 2px solid rgba(255, 255, 255, 0.5);
+          .loaded {
+            position: absolute;
+            left: 0;
+            top: 0;
+            bottom: 0;
+            background: rgba(255, 255, 255, 0.4);
+            height: 10px;
+            transition: all 0.5s ease;
+            will-change: width;
+            border-radius: 6px;
+          }
+          .played {
+            position: absolute;
+            left: 0;
+            top: 0;
+            bottom: 0;
+            height: 10px;
+            will-change: width;
+            background: linear-gradient(270deg, #97d1fd 0%, #0d93ff 100%);
+            border-radius: 6px;
+
+            .thumb {
+              position: absolute;
+              top: -12px;
+              right: -26px;
+              cursor: pointer;
+              width: 52px;
+              height: 38px;
+              background: url("./imgs/td.png") no-repeat;
+              background-size: 100% 100%;
+            }
+          }
+        }
+      }
+    }
+    .operateRightBtn {
+      flex-shrink: 0;
+      display: flex;
+      align-items: center;
+      .preBtn {
+        background: url("./imgs/pre.png") no-repeat;
+        background-size: 100% 100%;
+        width: 56px;
+        height: 56px;
+        margin-right: 14px;
+        cursor: pointer;
+      }
+      .nextBtn {
+        background: url("./imgs/next.png") no-repeat;
+        background-size: 100% 100%;
+        width: 56px;
+        height: 56px;
+        margin-right: 14px;
+        cursor: pointer;
+      }
+      .listBtn {
+        background: url("./imgs/list.png") no-repeat;
+        background-size: 100% 100%;
+        width: 56px;
+        height: 56px;
+        cursor: pointer;
+      }
+    }
+  }
+  .enjoyPlayerList {
+    width: 100%;
+    height: 520px;
+    position: absolute;
+    left: 0;
+    bottom: 51px;
+    box-shadow: 0px 3px 22px 0px rgba(0, 0, 0, 0.12);
+    border-radius: 20px 20px 0px 0px;
+    background-color: rgba(255, 255, 255, 0.9);
+    padding: 20px 0 61px 10px;
+    .titNameCon {
+      padding-left: 10px;
+      display: flex;
+      align-items: center;
+      height: 38px;
+      .titName {
+        font-weight: 600;
+        font-size: 22px;
+        color: #131415;
+        line-height: 30px;
+        position: relative;
+        &::after {
+          position: absolute;
+          left: 0;
+          bottom: 0;
+          content: "";
+          width: 100%;
+          height: 12px;
+          background: linear-gradient(90deg, #77bbff 0%, rgba(163, 231, 255, 0.22) 100%);
+        }
+        .tit {
+          position: relative;
+          z-index: 1;
+        }
+      }
+    }
+    .enjoyPlayerListCon {
+      margin-top: 20px;
+      overflow-y: auto;
+      height: calc(100% - 58px);
+      padding-right: 10px;
+      .playerItem {
+        padding: 18px 5px 18px 10px;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        &.active {
+          background: rgba(85, 176, 255, 0.15);
+          border-radius: 10px;
+          .itemLeft {
+            color: #198cfe;
+            font-weight: 600;
+          }
+        }
+        .itemLeft {
+          font-weight: 400;
+          font-size: 20px;
+          color: #131415;
+          line-height: 28px;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+        .itemRight {
+          margin-left: 10px;
+          flex-shrink: 0;
+          display: flex;
+          .itemListBtn {
+            width: 34px;
+            height: 34px;
+            margin-right: 10px;
+            background: url("./imgs/td2.png") no-repeat;
+            background-size: 24px 24px;
+            background-position: center;
+            cursor: grab;
+          }
+          .itemCloseBtn {
+            width: 34px;
+            height: 34px;
+            background: url("./imgs/del.png") no-repeat;
+            background-size: 24px 24px;
+            background-position: center;
+            cursor: pointer;
+          }
+        }
+      }
+    }
+  }
+}
+</style>

BIN
src/views/components/element/enjoyElement/imgs/del.png


BIN
src/views/components/element/enjoyElement/imgs/list.png


BIN
src/views/components/element/enjoyElement/imgs/next.png


BIN
src/views/components/element/enjoyElement/imgs/pause.png


BIN
src/views/components/element/enjoyElement/imgs/play.png


BIN
src/views/components/element/enjoyElement/imgs/pre.png


BIN
src/views/components/element/enjoyElement/imgs/td.png


BIN
src/views/components/element/enjoyElement/imgs/td2.png


BIN
src/views/components/element/enjoyElement/imgs/tip.png


+ 2 - 0
src/views/components/element/enjoyElement/index.ts

@@ -0,0 +1,2 @@
+import enjoyElement from "./enjoyElement.vue"
+export default enjoyElement

BIN
src/views/components/element/enjoyElement/resourcesList/imgs/audio.png


BIN
src/views/components/element/enjoyElement/resourcesList/imgs/img.png


BIN
src/views/components/element/enjoyElement/resourcesList/imgs/jxImg.png


BIN
src/views/components/element/enjoyElement/resourcesList/imgs/video.png


+ 2 - 0
src/views/components/element/enjoyElement/resourcesList/index.ts

@@ -0,0 +1,2 @@
+import resourcesList from "./resourcesList.vue"
+export default resourcesList

+ 740 - 0
src/views/components/element/enjoyElement/resourcesList/resourcesList.vue

@@ -0,0 +1,740 @@
+<template>
+  <div class="resourcesList">
+    <div class="headCon">
+      <div class="headLeft">
+        <img class="tipImg" src="@/views/Editor/CanvasTool/imgs/zyk.png" alt="" />
+        <div class="title">资源库</div>
+      </div>
+      <div class="headright">
+        <img @click="emits('close')" class="closeBtn" src="../../cloudCoachElement/cloudCoachList/imgs/close.png" alt="" />
+      </div>
+    </div>
+    <div class="content">
+      <div class="tabTools">
+        <div class="tabCon">
+          <div
+            class="tab"
+            @click="handleTabChange(item.value)"
+            :class="{ active: item.value === queryData.sourceType }"
+            v-for="item in tabData"
+            :key="item.value"
+          >
+            {{ item.label }}
+          </div>
+        </div>
+        <div></div>
+      </div>
+      <div class="typeTools">
+        <div class="typeTabCon">
+          <div
+            v-for="item in resourcesTypeOption"
+            :key="item.value"
+            @click="handleTypeChange(item.value)"
+            :class="['queryTip', queryData.type === item.value && 'active']"
+          >
+            {{ item.text }}
+          </div>
+        </div>
+        <div class="query">
+          <Input :placeholder="'请输入搜索关键词'" v-model:value="queryData.name" @enter="handleQuery">
+            <template #prefix>
+              <img class="img" src="../../cloudCoachElement/cloudCoachList/imgs/query.png" alt="" />
+            </template>
+            <template #suffix>
+              <div class="queryBtn" @click="handleQuery">搜索</div>
+            </template>
+          </Input>
+        </div>
+      </div>
+      <div class="musicListCon">
+        <div class="queryFrom" :class="{ isExpandAct: !isExpand }">
+          <div class="queryFromList">
+            <div class="tit">乐器:</div>
+            <div class="queryFromCon">
+              <template v-for="item in subjectList">
+                <div
+                  :class="['queryTip', queryData.subject.id === item.instruments[0].id && 'active']"
+                  @click="handleSubjectChange(item.instruments[0])"
+                  v-if="item.instruments.length === 1"
+                  :key="item.id"
+                >
+                  {{ item.instruments[0].name }}
+                </div>
+                <Popover v-model:value="item.isExpand" trigger="mouseenter" v-else :offset="-4" :key="item.id + '_'">
+                  <template #content>
+                    <PopoverMenuItem
+                      @click="
+                        () => {
+                          item.isExpand = false
+                          handleSubjectChange(row)
+                        }
+                      "
+                      v-for="row in item.instruments"
+                      :key="row.id"
+                      :active="row.id === queryData.subject.id"
+                      >{{ row.name }}</PopoverMenuItem
+                    >
+                  </template>
+                  <div class="queryTip" :class="{ hoverActive: isActiveSubjectPop(item) }">
+                    <div>{{ isActiveSubjectPop(item) ? queryData.subject.name : item.name }}</div>
+                    <img src="../../cloudCoachElement/cloudCoachList/imgs/jt.png" alt="" />
+                  </div>
+                </Popover>
+              </template>
+            </div>
+          </div>
+          <div class="queryFromList">
+            <div class="tit">标签:</div>
+            <div class="queryFromCon">
+              <div
+                v-for="item in materialTagList"
+                :key="item.id"
+                @click="handleMaterialTagChange(item.id)"
+                :class="['queryTip', queryData.materialTagId === item.id && 'active']"
+              >
+                {{ item.name }}
+              </div>
+            </div>
+          </div>
+        </div>
+        <!-- <div v-show="queryData.sourceType === 2" @click="isExpand = !isExpand" class="isExpand" :class="{ active: isExpand }">
+          <div>{{ isExpand ? "收起" : "展开" }}</div>
+          <img src="../../cloudCoachElement/cloudCoachList/imgs/jiao.png" alt="" />
+        </div> -->
+        <div class="musicListConBox" v-loading="loading">
+          <div class="musicList" :class="{ empty: !musicList.length && !loading }">
+            <div class="musicListBox" v-if="musicList.length && !loading">
+              <div class="musicCon" v-for="item in musicList" :key="item.id">
+                <div class="coverImgCon">
+                  <img v-if="item.sourceFrom === 'PLATFORM'" class="jxImg" src="./imgs/jxImg.png" alt="" />
+                  <div class="addBtn" @click="handleAddResources(item)">添加</div>
+                  <img class="coverImg" :src="item.coverImg" />
+                </div>
+                <div class="musicDetails">
+                  <div class="musLeft">
+                    <img class="labelImg" :src="item.type === 'IMG' ? imgImg : item.type === 'SONG' ? audioImg : videoImg" />
+                    <div class="musicTitCon">
+                      <EllipsisScroll class="musicTit" :title="item.highName || ''" />
+                    </div>
+                  </div>
+                  <div class="musRight">
+                    <img
+                      v-if="queryParams.fromType !== 'PLATFORM'"
+                      class="sc"
+                      @click="handleFavorite(item)"
+                      :src="item.favoriteFlag ? scActImg : scImg"
+                      alt=""
+                    />
+                  </div>
+                </div>
+              </div>
+            </div>
+            <Empty v-if="!musicList.length && !loading" />
+          </div>
+          <div class="pagination" v-show="musicList.length">
+            <el-pagination layout="prev, pager, next" :default-page-size="21" @current-change="handleCurrentChange" :total="queryData.total" />
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ElLoading, ElPagination } from "element-plus"
+import Input from "@/components/Input.vue"
+import Popover from "@/components/Popover.vue"
+import PopoverMenuItem from "@/components/PopoverMenuItem.vue"
+import Empty from "@/components/Empty"
+import EllipsisScroll from "@/components/ellipsisScroll"
+import { reactive, ref } from "vue"
+import { getMaterialQueryPage, getSubjectListApi, favoriteApi, getMaterialTagApi } from "@/api/pptOperate"
+import { httpAjax } from "@/plugins/httpAjax"
+import queryParams from "@/queryParams"
+import scActImg from "../../cloudCoachElement/cloudCoachList/imgs/scAct.png"
+import scImg from "../../cloudCoachElement/cloudCoachList/imgs/sc.png"
+import audioImg from "./imgs/audio.png"
+import imgImg from "./imgs/img.png"
+import videoImg from "./imgs/video.png"
+import { CODE_ERR_CANCELED } from "@/libs/auth"
+
+const emits = defineEmits<{
+  (event: "update", item: Record<string, any>): void
+  (event: "close"): void
+}>()
+
+function handleAddResources(item: Record<string, any>) {
+  emits("update", item)
+}
+
+const tabData =
+  queryParams.fromType === "PLATFORM"
+    ? [
+        {
+          label: "共享资源",
+          value: 2
+        }
+      ]
+    : [
+        {
+          label: "相关资源",
+          value: 5
+        },
+        {
+          label: "共享资源",
+          value: 2
+        },
+        {
+          label: "我的资源",
+          value: 3
+        },
+        {
+          label: "我的收藏",
+          value: 4
+        }
+      ]
+// 资源类型
+const resourcesTypeOption = [
+  { text: "图片", value: "IMG" },
+  { text: "音频", value: "SONG" },
+  { text: "视频", value: "VIDEO" }
+]
+// 标签
+const materialTagList = ref<any[]>([])
+// 乐器
+const subjectList = ref<any[]>([])
+
+const queryData = reactive({
+  page: 1,
+  rows: 21,
+  total: 0,
+  type: "SONG",
+  materialTagId: "",
+  sourceType: tabData[0].value,
+  name: "",
+  subject: {
+    id: "",
+    name: ""
+  }
+})
+
+const musicList = ref<any[]>([])
+const loading = ref(true)
+const vLoading = ElLoading.directive
+const isExpand = ref(true)
+
+getQueryList()
+function getQueryList() {
+  Promise.all([httpAjax(getSubjectListApi), httpAjax(getMaterialTagApi)]).then(res => {
+    const [subjectListRes, materialTagRes] = res
+    if (subjectListRes.code === 200) {
+      subjectList.value = subjectListRes.data.map((item: any) => {
+        return item.instruments.length > 1 ? Object.assign(item, { isExpand: ref(false) }) : item
+      })
+      // 赋默认值
+      handleSubjectDefault()
+    }
+    if (materialTagRes.code === 200) {
+      materialTagList.value = [{ id: "", name: "全部" }, ...(materialTagRes.data?.rows || [])]
+    }
+    handleQuery()
+  })
+}
+function handleSubjectDefault() {
+  if (subjectList.value.length > 0) {
+    const instruments = subjectList.value.reduce((arr, item) => {
+      arr.push(...item.instruments)
+      return arr
+    }, [])
+    const instrumentId = queryParams.instrumentId
+    // 有id 就用id,没有就默认第一个
+    const instrumentObj = instrumentId
+      ? instruments.find((i: any) => {
+          return i.id === instrumentId
+        })
+      : instruments[0]
+    if (instrumentObj) {
+      queryData.subject.id = instrumentObj.id
+      queryData.subject.name = instrumentObj.name
+    }
+  }
+}
+function clearQueryData() {
+  queryData.page = 1
+  queryData.rows = 21
+  queryData.total = 0
+  queryData.sourceType = 5
+  queryData.name = ""
+  queryData.type = "SONG"
+  queryData.materialTagId = ""
+  queryData.subject = {
+    id: "",
+    name: ""
+  }
+  handleSubjectDefault()
+}
+function handleTabChange(sourceType: number) {
+  clearQueryData()
+  isExpand.value = true
+  queryData.sourceType = sourceType
+  handleQuery()
+}
+function handleSubjectChange(item: any) {
+  queryData.subject.id = item.id
+  queryData.subject.name = item.name
+  handleQuery()
+}
+function handleMaterialTagChange(id: string) {
+  queryData.materialTagId = id
+  handleQuery()
+}
+function handleTypeChange(type: string) {
+  queryData.type = type
+  handleQuery()
+}
+function isActiveSubjectPop(item: any) {
+  return item.instruments.some((i: any) => {
+    return i.id === queryData.subject.id
+  })
+}
+
+function handleCurrentChange(e: number) {
+  queryData.page = e
+  handleGetQuery()
+}
+function handleQuery() {
+  queryData.page = 1
+  queryData.rows = 21
+  handleGetQuery()
+}
+
+let controller: AbortController
+function handleGetQuery() {
+  loading.value = true
+  let { sourceType, subject, type, materialTagId, name, page, rows } = queryData
+  let musicalInstrumentId = subject.id
+  const params = {
+    name,
+    type,
+    sourceType,
+    musicalInstrumentId,
+    enableFlag: true,
+    page,
+    rows,
+    materialTagId,
+    lessonCoursewareKnowledgeId: [2, 5].includes(sourceType) ? queryParams.lessonCoursewareKnowledgeId : "" // 相关资源和共享资源需要这个
+  }
+  if (controller) {
+    controller.abort()
+  }
+  controller = new AbortController()
+  httpAjax(getMaterialQueryPage, params, controller).then(res => {
+    // 自己关闭的时候不取消加载
+    if (res.code === CODE_ERR_CANCELED) {
+      return
+    }
+    if (res.code === 200) {
+      musicList.value = res.data.rows.map((item: any) => {
+        item.highName = highlightedText(item.name, queryData.name)
+        return item
+      })
+      queryData.total = res.data.total
+    }
+    loading.value = false
+  })
+}
+
+function handleFavorite(item: any) {
+  httpAjax(favoriteApi, {
+    favoriteFlag: item.favoriteFlag ? 0 : 1,
+    materialId: item.id,
+    type: item.type
+  }).then(res => {
+    if (res.code === 200) {
+      item.favoriteFlag = !item.favoriteFlag
+    }
+  })
+}
+const highlightedText = (text: string, query: string) => {
+  if (!text) {
+    return ""
+  }
+  if (!query) {
+    return text
+  }
+  const regex = new RegExp(`(${query})`, "gi")
+  return text.replace(regex, '<span class="highlighted">$1</span>')
+}
+</script>
+
+<style lang="scss" scoped>
+.resourcesList {
+  width: 100%;
+  height: 100%;
+  .headCon {
+    width: 100%;
+    height: 64px;
+    border-bottom: 1px solid #eaeaea;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .headLeft {
+      margin-left: 30px;
+      display: flex;
+      align-items: center;
+      .tipImg {
+        width: 24px;
+        height: 24px;
+      }
+      .title {
+        font-weight: 600;
+        font-size: 18px;
+        color: #131415;
+        margin-left: 8px;
+      }
+    }
+    .headright {
+      margin-right: 30px;
+      display: flex;
+      align-items: center;
+      .closeBtn {
+        width: 24px;
+        height: 24px;
+        cursor: pointer;
+        &:hover {
+          opacity: 0.8;
+        }
+      }
+    }
+  }
+  .content {
+    width: 100%;
+    height: calc(100% - 64px);
+    display: flex;
+    flex-direction: column;
+    .tabTools {
+      flex-shrink: 0;
+      width: 100%;
+      padding: 24px 30px;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .tabCon {
+        display: flex;
+        .tab {
+          margin-right: 32px;
+          font-weight: 400;
+          font-size: 16px;
+          color: #8b8d98;
+          line-height: 22px;
+          cursor: pointer;
+          &:hover {
+            opacity: 0.8;
+          }
+          &:last-child {
+            margin-right: 0;
+          }
+          &.active {
+            font-weight: 600;
+            color: #131415;
+            position: relative;
+            &::after {
+              content: "";
+              position: absolute;
+              width: 100%;
+              height: 10px;
+              background: linear-gradient(90deg, #77bbff 0%, rgba(163, 231, 255, 0.22) 100%);
+              bottom: 0;
+              left: 0;
+              z-index: -1;
+            }
+          }
+        }
+      }
+    }
+    .typeTools {
+      flex-shrink: 0;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin: 0 30px;
+      padding-bottom: 16px;
+      border-bottom: 1px solid #eaeaea;
+      .typeTabCon {
+        display: flex;
+        align-items: center;
+        .queryTip {
+          margin-right: 16px;
+          font-weight: 400;
+          font-size: 14px;
+          color: rgba(0, 0, 0, 0.6);
+          line-height: 20px;
+          padding: 6px 20px;
+          background: #f5f6fa;
+          border-radius: 16px;
+          cursor: pointer;
+          &.active,
+          &:hover {
+            background: #d2ecff;
+            color: rgba(0, 0, 0, 1);
+          }
+        }
+      }
+      .query {
+        width: 400px;
+        height: 36px;
+        &::v-deep(.input) {
+          align-items: center;
+          padding: 0 3px 0 12px;
+          border-radius: 18px;
+          height: 100%;
+          &:not(.disabled):hover,
+          &.focused {
+            .img {
+              opacity: 1;
+            }
+            .queryBtn {
+              opacity: 1;
+            }
+          }
+          input {
+            font-size: 14px;
+          }
+          .img {
+            width: 16px;
+            height: 16px;
+            opacity: 0.4;
+          }
+          .queryBtn {
+            width: 60px;
+            height: 30px;
+            background: #198cfe;
+            border-radius: 16px;
+            font-weight: 500;
+            font-size: 14px;
+            color: #ffffff;
+            line-height: 30px;
+            text-align: center;
+            opacity: 0.4;
+            cursor: pointer;
+            &:hover {
+              opacity: 0.8 !important;
+            }
+          }
+        }
+      }
+    }
+    .musicListCon {
+      padding-top: 16px;
+      width: 100%;
+      flex-grow: 1;
+      overflow: hidden;
+      display: flex;
+      flex-direction: column;
+      .queryFrom {
+        &.isExpandAct {
+          height: 42px;
+          overflow: hidden;
+        }
+        flex-shrink: 0;
+        padding: 0 30px;
+        .queryFromList {
+          display: flex;
+          margin-bottom: 4px;
+          .tit {
+            flex-shrink: 0;
+            font-weight: 500;
+            font-size: 14px;
+            color: #131415;
+            line-height: 32px;
+            margin-right: 16px;
+          }
+          .queryFromCon {
+            display: flex;
+            flex-wrap: wrap;
+            .queryTip {
+              margin: 0 16px 12px 0;
+              font-weight: 400;
+              font-size: 14px;
+              color: rgba(0, 0, 0, 0.6);
+              line-height: 20px;
+              padding: 6px 16px;
+              background: #f5f6fa;
+              border-radius: 6px;
+              cursor: pointer;
+              display: flex;
+              align-items: center;
+              & > img {
+                width: 7px;
+                height: 4px;
+                margin-left: 6px;
+              }
+              &.active,
+              &:hover {
+                background: #d2ecff;
+                color: rgba(0, 0, 0, 1);
+                > img {
+                  transform: rotate(180deg);
+                }
+              }
+              &.hoverActive {
+                background: #d2ecff;
+                color: rgba(0, 0, 0, 1);
+              }
+            }
+          }
+        }
+      }
+      .isExpand {
+        flex-shrink: 0;
+        margin-bottom: 12px;
+        cursor: pointer;
+        display: flex;
+        justify-content: center;
+        font-weight: 400;
+        font-size: 14px;
+        color: #198cfe;
+        line-height: 20px;
+        align-items: center;
+        &:hover {
+          opacity: 0.8;
+        }
+        &.active > img {
+          transform: rotate(0deg);
+        }
+        & > img {
+          transform: rotate(180deg);
+          margin-left: 4px;
+          width: 10px;
+          height: 10px;
+        }
+      }
+      .musicListConBox {
+        flex-grow: 1;
+        overflow: hidden;
+        .musicList {
+          padding: 4px 0;
+          height: calc(100% - 60px);
+          overflow: auto;
+          &.empty {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+          }
+          .musicListBox {
+            width: calc(100% + 24px);
+            margin-left: -24px;
+            display: flex;
+            flex-wrap: wrap;
+            padding: 0 30px;
+            .musicCon {
+              margin-bottom: 24px;
+              width: calc(25% - 24px);
+              margin-left: 24px;
+              border-radius: 12px;
+              background: linear-gradient(0, #dbf1ff 0%, #e7f9ff 100%);
+              outline: 1.5px solid #d3eef1;
+              height: 213px;
+              display: flex;
+              flex-direction: column;
+              overflow: hidden;
+              &:nth-last-child(-n + 3) {
+                margin-bottom: 0;
+              }
+              &:hover {
+                outline: 2px solid #198cfe;
+              }
+              .coverImgCon {
+                flex-shrink: 0;
+                width: 100%;
+                height: 170px;
+                position: relative;
+                .coverImg {
+                  width: 100%;
+                  height: 100%;
+                  object-fit: cover;
+                  object-position: top;
+                }
+                .jxImg {
+                  position: absolute;
+                  left: 0;
+                  top: 0;
+                  width: 50px;
+                  height: 27px;
+                }
+                .addBtn {
+                  position: absolute;
+                  top: 8px;
+                  right: 12px;
+                  width: 54px;
+                  height: 26px;
+                  background: #198cfe;
+                  border-radius: 4px;
+                  font-weight: 600;
+                  font-size: 13px;
+                  color: #ffffff;
+                  line-height: 26px;
+                  text-align: center;
+                  cursor: pointer;
+                  &:hover {
+                    opacity: 0.8;
+                  }
+                }
+              }
+              .musicDetails {
+                flex-grow: 1;
+                padding: 0 12px;
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+                .musLeft {
+                  display: flex;
+                  align-items: center;
+                  margin-right: 14px;
+                  overflow: hidden;
+                  .labelImg {
+                    flex-shrink: 0;
+                    width: 16px;
+                    height: 16px;
+                  }
+                  .musicTitCon {
+                    margin-left: 6px;
+                    overflow: hidden;
+                    .musicTit {
+                      font-weight: 600;
+                      font-size: 14px;
+                      color: #131415;
+                      line-height: 20px;
+                      &::v-deep(.highlighted) {
+                        color: $themeColor;
+                      }
+                    }
+                  }
+                }
+                .musRight {
+                  flex-shrink: 0;
+                  .sc {
+                    width: 26px;
+                    height: 26px;
+                    cursor: pointer;
+                    &:hover {
+                      opacity: 0.8;
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+        .pagination {
+          padding: 0 30px;
+          display: flex;
+          justify-content: flex-end;
+          align-items: center;
+          height: 60px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 1 - 1
vite.config.ts

@@ -28,7 +28,7 @@ export default defineConfig({
     proxy: {
       // 正则表达式写法
       "^/pptApi/.*": {
-        target: "https://test.kt.colexiu.com",
+        target: "https://dev.kt.colexiu.com",
         changeOrigin: true,
         rewrite: path => path.replace(/^\/pptApi/, "")
       }

Some files were not shown because too many files changed in this diff