Graphics.vue 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318
  1. <template>
  2. <div class="graphics">
  3. <div class="group-asset">
  4. <t-radio-group
  5. v-model="activeAssets"
  6. @change="assetsChange"
  7. variant="primary-filled"
  8. >
  9. <t-radio-button value="system">系统资源</t-radio-button>
  10. <t-radio-button value="user">我的资源</t-radio-button>
  11. </t-radio-group>
  12. </div>
  13. <div class="groups-panel">
  14. <div class="groups">
  15. <div
  16. v-for="group in groups"
  17. :class="group.name === activedGroup ? 'active' : ''"
  18. @click="groupChange(group.name)"
  19. >
  20. <template v-if="group.type === 'iconfont'">
  21. <i class="l-icon" :class="group.icon"> </i>
  22. {{ group.name }}
  23. </template>
  24. <template v-else>
  25. <t-icon :name="group.icon" />
  26. {{ group.name }}
  27. </template>
  28. </div>
  29. </div>
  30. <div class="list" :class="groupType ? 'two-columns' : ''">
  31. <div class="input-search">
  32. <div class="btn">
  33. <!-- <t-icon name="search" /> -->
  34. <img src="/img/icon_search_gray.svg" />
  35. </div>
  36. <t-input
  37. v-model="search"
  38. @change="onSearch"
  39. @enter="onSearch"
  40. placeholder="搜索"
  41. />
  42. <div class="ml-16">
  43. <t-tooltip content="展开/折叠">
  44. <t-icon
  45. name="menu-fold"
  46. class="hover"
  47. style="font-size: 16px"
  48. @click="onFold"
  49. />
  50. </t-tooltip>
  51. </div>
  52. </div>
  53. <div v-if="loading" class="center mt-16">
  54. <t-loading text="加载中..." size="small" :indicator="false" />
  55. </div>
  56. <template v-else>
  57. <div
  58. v-if="
  59. activedGroup === '组件' ||
  60. activedGroup === '图片' ||
  61. (activeAssets === 'user' &&
  62. (activedGroup === '方案' || activedGroup === '模板'))
  63. "
  64. class="px-16 mt-12 mb-8 ml-4"
  65. >
  66. <a @click="onCreateFolder">+ 新建文件夹</a>
  67. </div>
  68. <t-collapse v-model="activedPanels[activedGroup]">
  69. <t-collapse-panel
  70. :value="item.name"
  71. v-for="item in subGroups"
  72. :key="item.name"
  73. >
  74. <template #header>
  75. <div class="flex middle">
  76. <div v-if="item.edited" @click.stop>
  77. <t-input
  78. v-model="item.label"
  79. style="width: 140px"
  80. @blur="createFoder"
  81. @enter="createFoder"
  82. @keyup="onKeyHeader"
  83. :autofocus="true"
  84. />
  85. </div>
  86. <div v-else>
  87. {{ item.name }}
  88. </div>
  89. </div>
  90. </template>
  91. <template #headerRightContent>
  92. <t-space size="small" @click.stop tabindex="0">
  93. <t-upload
  94. v-if="
  95. item.canEdited &&
  96. (activedGroup === '组件' || activedGroup === '图片')
  97. "
  98. action="/api/image/upload"
  99. :accept="activedGroup === '组件' ? '.svg' : 'image/*'"
  100. :headers="headers"
  101. :data="{
  102. directory: `/大屏/${activedGroup}/${item.name}`,
  103. }"
  104. :auto-upload="true"
  105. :upload-all-files-in-one-request="false"
  106. :before-upload="beforeUpload"
  107. @selectChange="onSelectFiles(item)"
  108. allowUploadDuplicateFile
  109. @success="fileSuccessed"
  110. theme="custom"
  111. >
  112. <t-icon name="image" class="hover" />
  113. </t-upload>
  114. <template v-if="item.canEdited">
  115. <t-icon
  116. v-if="['组件', '方案', '模板'].includes(activedGroup)"
  117. name="add"
  118. class="hover"
  119. @click="onAdd(item)"
  120. />
  121. <t-icon
  122. name="edit"
  123. class="hover"
  124. @click="onEditHeader(item)"
  125. />
  126. <t-popconfirm
  127. content="确认删除该文件夹吗"
  128. placement="left"
  129. @confirm="delFolder(item)"
  130. >
  131. <t-icon name="delete" class="hover" />
  132. </t-popconfirm>
  133. </template>
  134. </t-space>
  135. </template>
  136. <template v-for="elem in item.list">
  137. <div
  138. v-show="elem.visible !== false"
  139. class="graphic"
  140. :draggable="true"
  141. @dragstart="dragStart($event, elem)"
  142. @click.prevent="dragStart($event, elem)"
  143. @dblclick.stop="open(elem)"
  144. @contextmenu="onContextMenu($event, item, elem)"
  145. >
  146. <t-image
  147. v-if="!elem.svg && elem.image"
  148. :src="elem.image"
  149. :lazy="true"
  150. fit="contain"
  151. @load="loadImage(elem)"
  152. />
  153. <div class="svg-box" v-else-if="elem.svg" v-html="elem.svg" />
  154. <svg v-else class="l-icon" aria-hidden="true">
  155. <use :xlink:href="'#' + elem.icon"></use>
  156. </svg>
  157. <p :title="elem.name">{{ elem.name }}</p>
  158. <div class="price" v-if="elem.price > 0">
  159. ¥{{ elem.price }}
  160. </div>
  161. </div>
  162. </template>
  163. <div
  164. v-if="!item.list || !item.list.length"
  165. class="gray"
  166. style="white-space: nowrap; margin-left: 34px"
  167. >
  168. 暂无数据
  169. </div>
  170. </t-collapse-panel>
  171. </t-collapse>
  172. </template>
  173. <div class="div-manage" v-if="['图元', '素材'].includes(activedGroup)">
  174. <t-button
  175. class="w-full"
  176. @click="onManageGraphic"
  177. style="height: 30px"
  178. >
  179. {{ activedGroup }}管理
  180. </t-button>
  181. </div>
  182. </div>
  183. </div>
  184. <div
  185. class="context-menu-box"
  186. ref="contextmenuDom"
  187. v-show="contextmenu.visible"
  188. tabindex="0"
  189. :style="contextmenu.style"
  190. @blur="hideContextmenu"
  191. >
  192. <t-menu
  193. class="context-menu"
  194. @change="onMenu"
  195. expandType="popup"
  196. v-model="contextmenu.activeValue"
  197. >
  198. <t-submenu
  199. value="move"
  200. title="移动到"
  201. v-if="contextmenu.subMenus.length"
  202. :disabled="contextmenu.component['3d']"
  203. >
  204. <t-menu-item
  205. v-for="subMenu in contextmenu.subMenus"
  206. :value="'move:' + subMenu.name"
  207. >
  208. {{ subMenu.name }}
  209. </t-menu-item>
  210. </t-submenu>
  211. <t-menu-item value="edit" :disabled="activedGroup === '图片'">
  212. 编辑
  213. </t-menu-item>
  214. <t-menu-item value="del" :disabled="contextmenu.component['3d']">
  215. 删除
  216. </t-menu-item>
  217. </t-menu>
  218. </div>
  219. <t-dialog
  220. v-if="delDialog.show"
  221. theme="danger"
  222. header="删除"
  223. :visible="true"
  224. @close="delDialog.show = false"
  225. @confirm="delComponent"
  226. >
  227. 确定删除该数据吗?删除后不可恢复!
  228. </t-dialog>
  229. <t-dialog
  230. v-if="chargeDialog.show"
  231. :header="chargeDialog.data.name"
  232. :visible="true"
  233. @close="chargeDialog.show = false"
  234. width="70%"
  235. :top="8"
  236. >
  237. <t-image :src="chargeDialog.data.image" />
  238. <template #footer>
  239. <div class="flex between" style="margin-top: -7px">
  240. <p>付费项目,购买后查看并使用</p>
  241. <div>
  242. <label>金额:</label>
  243. <label class="bland">¥{{ chargeDialog.data.price }}</label>
  244. <t-button class="primary ml-12" @click="onSubmitOrder"
  245. >购买</t-button
  246. >
  247. </div>
  248. </div>
  249. </template>
  250. </t-dialog>
  251. <t-dialog
  252. v-if="wechatPayDialog.show"
  253. v-model:visible="wechatPayDialog.show"
  254. class="pay-dialog"
  255. header="乐吾乐收银台"
  256. :close-on-overlay-click="false"
  257. :top="95"
  258. :width="700"
  259. confirm-btn="支付完成"
  260. :cancel-btn="null"
  261. @close="getPayResult"
  262. >
  263. <WechatPay
  264. :order="wechatPayDialog.order"
  265. :code-url="wechatPayDialog.order.codeUrl"
  266. @success="onChargeSuccess"
  267. />
  268. </t-dialog>
  269. <t-dialog
  270. class="dialog-manage"
  271. :header="activedGroup + '管理'"
  272. :visible="manageDialog.show"
  273. @close="manageDialog.show = false"
  274. @confirm="manageConfirm"
  275. width="90%"
  276. :top="8"
  277. >
  278. <div class="manage-body">
  279. <t-collapse v-model="activedPanels[activedGroup]">
  280. <t-collapse-panel
  281. :value="item.name"
  282. v-for="item in manageDialog.data"
  283. :key="item.name"
  284. >
  285. <template #header>
  286. <div class="flex middle">
  287. {{ item.name }}
  288. </div>
  289. </template>
  290. <template v-for="elem in item.list">
  291. <div
  292. :style="{
  293. background: elem.visible ? '#42516c' : '',
  294. }"
  295. class="graphic manage-list"
  296. >
  297. <t-checkbox v-model:checked="elem.visible"></t-checkbox>
  298. <t-image
  299. :src="elem.image"
  300. :lazy="true"
  301. fit="contain"
  302. @load="loadImage(elem)"
  303. />
  304. </div>
  305. </template>
  306. <div
  307. v-if="!item.list || !item.list.length"
  308. class="gray"
  309. style="white-space: nowrap; margin-left: 34px"
  310. >
  311. 暂无数据
  312. </div>
  313. </t-collapse-panel>
  314. </t-collapse>
  315. </div>
  316. </t-dialog>
  317. </div>
  318. </template>
  319. <script lang="ts" setup>
  320. import { onMounted, onUnmounted, reactive, ref } from 'vue';
  321. import { useRouter } from 'vue-router';
  322. import { MessagePlugin } from 'tdesign-vue-next';
  323. import axios from 'axios';
  324. import { deepClone } from '@meta2d/core';
  325. import { cases, shapes, formComponents, templates } from '@/services/defaults';
  326. import { charts } from '@/services/echarts';
  327. import { getFolders, makeSvg } from '@/services/png';
  328. import {
  329. addCollection,
  330. delImage,
  331. getComponentsList,
  332. getLe5leV,
  333. updateCollection,
  334. getCollectionList,
  335. imageDrive,
  336. } from '@/services/api';
  337. import { convertPen } from '@/services/upgrade';
  338. import { isGif } from '@/services/utils';
  339. import { autoSave, delAttrs, blank, useFolder } from '@/services/common';
  340. import { debounce, throttle } from '@/services/debouce';
  341. import { searchObjectPinyin } from '@/services/pinyin';
  342. import { getCookie } from '@/services/cookie';
  343. import { parseSvg } from '@meta2d/svg';
  344. import WechatPay from './WechatPay.vue';
  345. import { filename } from '@/services/file';
  346. import { useUser } from '@/services/user';
  347. import { iframeCustom } from '@/services/defaults';
  348. import { getLe5le3d } from '@/services/api';
  349. import { useSelection } from '@/services/selections';
  350. import localforage from 'localforage';
  351. const { user } = useUser();
  352. const { setFolder, getFolder } = useFolder();
  353. const router = useRouter();
  354. const { select } = useSelection();
  355. const activedGroup = ref('');
  356. const activeAssets = ref('system');
  357. let groups = reactive([]);
  358. const systemGroups = [
  359. {
  360. icon: 'desktop',
  361. name: '方案',
  362. key: '',
  363. class: 'tow',
  364. },
  365. {
  366. icon: 'root-list',
  367. name: '模板',
  368. key: '',
  369. },
  370. {
  371. icon: 'chart',
  372. name: '图表',
  373. key: 'chart',
  374. },
  375. {
  376. icon: 'image',
  377. name: '素材',
  378. key: '',
  379. },
  380. {
  381. icon: 'control-platform',
  382. name: '图元',
  383. key: '',
  384. },
  385. {
  386. icon: 'relativity',
  387. name: '控件',
  388. key: '',
  389. },
  390. {
  391. icon: 'chart-bubble',
  392. name: '图形',
  393. key: 'shape',
  394. },
  395. // {
  396. // icon: 'app',
  397. // name: '我的',
  398. // key: '',
  399. // },
  400. ];
  401. const userGroups = [
  402. {
  403. icon: 'desktop',
  404. name: '方案',
  405. key: '',
  406. class: 'tow',
  407. },
  408. {
  409. icon: 'root-list',
  410. name: '模板',
  411. key: '',
  412. },
  413. {
  414. icon: 'l-zujian',
  415. name: '组件',
  416. key: 'chart',
  417. type: 'iconfont',
  418. },
  419. {
  420. icon: 'image',
  421. name: '图片',
  422. key: '',
  423. },
  424. {
  425. icon: 'control-platform',
  426. name: '3D',
  427. key: '',
  428. },
  429. ];
  430. groups = systemGroups;
  431. const subGroups = ref<any[]>([]);
  432. const groupType = ref(0);
  433. const activedPanels = reactive<any>({});
  434. const caseCaches = [];
  435. const templateCaches = [];
  436. const materials = [];
  437. const pngs = [];
  438. let dropped = false;
  439. const chargeDialog = reactive<any>({});
  440. const wechatPayDialog = reactive<any>({
  441. show: false,
  442. });
  443. const manageDialog = reactive<any>({
  444. name: '',
  445. data: [],
  446. show: false,
  447. });
  448. const search = ref('');
  449. const loading = ref(false);
  450. const headers = {
  451. Authorization: 'Bearer ' + (localStorage.token || getCookie('token') || ''),
  452. };
  453. const updataData = { directory: '/大屏/默认' };
  454. let lastName = '方案';
  455. let userLastName = '方案';
  456. const assetsChange = (value) => {
  457. if (value === 'system') {
  458. groups = systemGroups;
  459. activedGroup.value = lastName;
  460. } else if (value === 'user') {
  461. groups = userGroups;
  462. activedGroup.value = userLastName;
  463. }
  464. groupChange(activedGroup.value);
  465. };
  466. const groupChange = async (name: string) => {
  467. activedGroup.value = name;
  468. groupType.value = 0;
  469. switch (name) {
  470. case '方案':
  471. if (activeAssets.value === 'system') {
  472. if (!caseCaches.length) {
  473. loading.value = true;
  474. caseCaches.push(...(await getCaseProjects('系统方案')));
  475. for (const group of cases) {
  476. group.list = [];
  477. for (const item of caseCaches) {
  478. if (item.case === group.name) {
  479. group.list.push(item);
  480. }
  481. }
  482. }
  483. loading.value = false;
  484. }
  485. groupType.value = 1;
  486. subGroups.value = cases;
  487. lastName = name;
  488. } else {
  489. // subGroups.value = await getUserProjects('le5leV');
  490. // groupType.value = 1;
  491. // await getPrivateProjects('le5leV');
  492. // userLastName = name;
  493. //用户方案
  494. subGroups.value = await getCollectionImageList('方案', 'v');
  495. groupType.value = 1;
  496. userLastName = name;
  497. }
  498. break;
  499. case '模板':
  500. if (activeAssets.value === 'system') {
  501. if (!templateCaches.length) {
  502. loading.value = true;
  503. templateCaches.push(...(await getCaseProjects('系统模板')));
  504. for (const group of templates) {
  505. group.list = [];
  506. for (const item of templateCaches) {
  507. if (item.case === group.name) {
  508. group.list.push(item);
  509. }
  510. }
  511. }
  512. loading.value = false;
  513. }
  514. groupType.value = 1;
  515. subGroups.value = templates;
  516. lastName = name;
  517. } else {
  518. // subGroups.value = await getUserProjects('le5leV-template');
  519. // groupType.value = 1;
  520. // await getPrivateProjects('le5leV-template');
  521. // userLastName = name;
  522. subGroups.value = await getCollectionImageList('模板', 'le5leV');
  523. groupType.value = 1;
  524. userLastName = name;
  525. }
  526. break;
  527. case '图表':
  528. subGroups.value = charts;
  529. lastName = name;
  530. break;
  531. case '控件':
  532. subGroups.value = formComponents;
  533. lastName = name;
  534. break;
  535. case '素材':
  536. groupType.value = 1;
  537. if (!materials.length) {
  538. loading.value = true;
  539. materials.push(...(await getFolders('v/material')));
  540. loading.value = false;
  541. const _materials: string = await localforage.getItem(
  542. 'le5leV-materials'
  543. );
  544. if (_materials) {
  545. Object.assign(materials, JSON.parse(_materials));
  546. }
  547. }
  548. subGroups.value = materials;
  549. lastName = name;
  550. break;
  551. case '图元':
  552. if (!pngs.length) {
  553. loading.value = true;
  554. pngs.push(...(await getFolders('png')));
  555. pngs.push(...(await getFolders('svg', true)));
  556. loading.value = false;
  557. const _pngs: string = await localforage.getItem('le5leV-pngs');
  558. if (_pngs) {
  559. Object.assign(pngs, JSON.parse(_pngs));
  560. }
  561. }
  562. subGroups.value = pngs;
  563. lastName = name;
  564. break;
  565. case '图形':
  566. subGroups.value = shapes;
  567. lastName = name;
  568. break;
  569. case '组件':
  570. // subGroups.value = await getUserComponents();
  571. // groupType.value = 1;
  572. // await getPrivateGraphics();
  573. // userLastName = name;
  574. subGroups.value = await getCollectionImageList(
  575. '组件',
  576. 'le5leV-components'
  577. );
  578. groupType.value = 1;
  579. userLastName = name;
  580. break;
  581. case '图片':
  582. loading.value = true;
  583. subGroups.value = await getImageList();
  584. loading.value = false;
  585. userLastName = name;
  586. break;
  587. case '3D':
  588. subGroups.value = [
  589. {
  590. name: '3D',
  591. list: [],
  592. },
  593. ];
  594. groupType.value = 1;
  595. await getPrivateGraphics();
  596. userLastName = name;
  597. break;
  598. }
  599. // if (!activedPanels[name]) {
  600. activedPanels[name] = [];
  601. for (const item of subGroups.value) {
  602. activedPanels[name].push(item.name);
  603. }
  604. // }
  605. // searchGraphics();
  606. };
  607. // TODO 获取方案文件
  608. //获取方案文件夹
  609. const getCollectionImageList = async (name?: string, collection?: string) => {
  610. //1. 获取网盘文件夹
  611. const fullpath = `/大屏/${name}`;
  612. let ret: { list: any[] } = await axios.post('/api/directory/list', {
  613. fullpath,
  614. });
  615. if (!ret) {
  616. return [];
  617. }
  618. let list = [];
  619. for (let i of ret.list) {
  620. if (
  621. i.fullpath !== `${fullpath}/默认` &&
  622. i.fullpath.split('/').length === 4
  623. ) {
  624. //不取当前文件夹
  625. list.push(i);
  626. }
  627. }
  628. const data = {
  629. directory: "/大屏/方案/123",
  630. // query: {
  631. // // folder: '',
  632. // tags: name !== '组件' ? name : undefined,
  633. // },
  634. projection: {
  635. image: 1,
  636. id: 1,
  637. name: 1,
  638. tags: 1,
  639. folder: 1,
  640. component: 1,
  641. filename: 1,
  642. },
  643. };
  644. const config = {
  645. params: {
  646. current: 1,
  647. pageSize: 1000,
  648. },
  649. };
  650. //2.请求所有图纸/组件数据
  651. const res: any = await getCollectionList(collection, data, config);
  652. //3.将数据对应到云盘文件夹
  653. const results = [];
  654. const resultsMap = {
  655. 默认: [],
  656. };
  657. for (const item of list) {
  658. let folder = item.fullpath.split('/')[3];
  659. if (!resultsMap[folder]) {
  660. resultsMap[folder] = [];
  661. }
  662. }
  663. for (const item of res.list) {
  664. if (item.folder && resultsMap[item.folder]) {
  665. resultsMap[item.folder].push(item);
  666. } else {
  667. resultsMap['默认'].push(item);
  668. }
  669. }
  670. for (const item of list) {
  671. let folder = item.fullpath.split('/')[3];
  672. results.push({
  673. name: folder,
  674. canEdited: true,
  675. id: item.id,
  676. list: resultsMap[folder],
  677. });
  678. }
  679. results.push({
  680. name: '默认',
  681. list: resultsMap['默认'],
  682. });
  683. return results;
  684. };
  685. const getImageList = async () => {
  686. let ret: { list: any[] } = await axios.post('/api/directory/list', {
  687. fullpath: '/大屏/图片',
  688. });
  689. if (!ret) {
  690. return [];
  691. }
  692. let list = [];
  693. for (let i of ret.list) {
  694. if (i.fullpath.split('/').length === 4) {
  695. //不取当前文件夹
  696. list.push(i);
  697. }
  698. }
  699. return await Promise.all(
  700. list.map(async (item) => {
  701. let secondDir = item.fullpath.split('/');
  702. const _ret: { list: any[]; total: number } = await axios.post(
  703. '/api/file/list',
  704. {
  705. type: '图片',
  706. directory: item.fullpath,
  707. },
  708. {
  709. params: {
  710. current: 1,
  711. pageSize: 100,
  712. },
  713. }
  714. );
  715. let list = _ret.list.map((_item) => {
  716. return {
  717. ..._item,
  718. image: _item.metadata.url || `/file${_item.filename}`,
  719. visible: true,
  720. folder: item.fullpath,
  721. _id: _item._id,
  722. };
  723. });
  724. return {
  725. name: secondDir[3],
  726. _id: item._id,
  727. canEdited: secondDir[3] !== '默认',
  728. list: list,
  729. };
  730. })
  731. );
  732. };
  733. const getCaseProjects = async (name: string, current = 1, pageSize = 1000) => {
  734. const query: any = { tags: name };
  735. const ret: any = await axios.post(
  736. '/api/data/le5leV/list',
  737. {
  738. query,
  739. shared: 'true',
  740. projection: {
  741. id: 1,
  742. _id: 1,
  743. name: 1,
  744. image: 1,
  745. price: 1,
  746. case: 1,
  747. },
  748. sort: { createdAt: 1 },
  749. },
  750. {
  751. params: {
  752. current,
  753. pageSize,
  754. },
  755. }
  756. );
  757. if (!ret) {
  758. return [];
  759. }
  760. for (const item of ret.list) {
  761. if (!item.id) {
  762. item.id = item._id;
  763. }
  764. item.draggable = false;
  765. }
  766. return ret.list;
  767. };
  768. // const getUserProjects = async (name: string, current = 1, pageSize = 1000) => {
  769. // const query: any = { tags: name };
  770. // const ret: any = await axios.post(
  771. // '/api/data/le5leV/list',
  772. // {
  773. // query,
  774. // projection: {
  775. // id: 1,
  776. // _id: 1,
  777. // name: 1,
  778. // image: 1,
  779. // price: 1,
  780. // case: 1,
  781. // folder: 1,
  782. // },
  783. // sort: { createdAt: 1 },
  784. // },
  785. // {
  786. // params: {
  787. // current,
  788. // pageSize,
  789. // },
  790. // }
  791. // );
  792. // if (!ret) {
  793. // return [];
  794. // }
  795. // for (const item of ret.list) {
  796. // if (!item.id) {
  797. // item.id = item._id;
  798. // }
  799. // item.draggable = false;
  800. // }
  801. // return ret.list;
  802. // };
  803. const getPrivateGroups = async () => {
  804. const list = [
  805. {
  806. name: '默认',
  807. list: [],
  808. },
  809. ];
  810. const config = {
  811. params: {
  812. current: 1,
  813. pageSize: 1000,
  814. },
  815. };
  816. let ret: any = await axios.post(
  817. '/api/data/folders/list',
  818. {
  819. projection: {
  820. image: 1,
  821. _id: 1,
  822. name: 1,
  823. list: 1,
  824. },
  825. query: {
  826. type: `le5leV-components`,
  827. },
  828. sort: { createdAt: 1 },
  829. },
  830. config
  831. );
  832. if (!ret) {
  833. ret = { list: [] };
  834. }
  835. for (const item of ret.list) {
  836. item.canEdited = true;
  837. }
  838. list.push(...ret.list);
  839. list.push({
  840. name: '3D',
  841. list: [],
  842. });
  843. return list;
  844. };
  845. const getUserComponents = async () => {
  846. const list = [
  847. {
  848. name: '默认',
  849. list: [],
  850. },
  851. ];
  852. const config = {
  853. params: {
  854. current: 1,
  855. pageSize: 1000,
  856. },
  857. };
  858. let ret: any = await axios.post(
  859. '/api/data/folders/list',
  860. {
  861. projection: {
  862. image: 1,
  863. _id: 1,
  864. name: 1,
  865. list: 1,
  866. },
  867. query: {
  868. type: `le5leV-components`,
  869. },
  870. sort: { createdAt: 1 },
  871. },
  872. config
  873. );
  874. if (!ret) {
  875. ret = { list: [] };
  876. }
  877. for (const item of ret.list) {
  878. item.canEdited = true;
  879. }
  880. list.push(...ret.list);
  881. return list;
  882. };
  883. const getUserProjects = async (type: string) => {
  884. const list = [
  885. {
  886. name: '默认',
  887. list: [],
  888. },
  889. ];
  890. const config = {
  891. params: {
  892. current: 1,
  893. pageSize: 1000,
  894. },
  895. };
  896. let ret: any = await axios.post(
  897. '/api/data/folders/list',
  898. {
  899. projection: {
  900. image: 1,
  901. _id: 1,
  902. name: 1,
  903. list: 1,
  904. },
  905. query: {
  906. type,
  907. },
  908. sort: { createdAt: 1 },
  909. },
  910. config
  911. );
  912. if (!ret) {
  913. ret = { list: [] };
  914. }
  915. for (const item of ret.list) {
  916. item.canEdited = true;
  917. }
  918. list.push(...ret.list);
  919. return list;
  920. };
  921. const getPrivateProjects = async (type: string) => {
  922. for (const item of subGroups.value) {
  923. if (!item.list.length) {
  924. item.loading = true;
  925. //TODO 方案/模板分类
  926. if (item.name === '默认') {
  927. const data = {
  928. query: {
  929. folder: '',
  930. tags: type === 'le5leV-template' ? '模板' : '方案',
  931. },
  932. projection: {
  933. image: 1,
  934. _id: 1,
  935. name: 1,
  936. tags: 1,
  937. },
  938. };
  939. const config = {
  940. params: {
  941. current: 1,
  942. pageSize: 1000,
  943. },
  944. };
  945. const res: any = await getCollectionList('le5leV', data, config);
  946. if (res?.list) {
  947. // res.list.forEach((item) => {
  948. // item.draggable = false;
  949. // })
  950. item.list = res.list;
  951. // if (type === 'le5leV') {
  952. // //过滤模板
  953. // // item.list = res.list.filter((item) => !item.isTemplate);
  954. // }
  955. }
  956. }
  957. item.loading = false;
  958. }
  959. }
  960. };
  961. const dragStart = async (event: DragEvent | MouseEvent, item: any) => {
  962. event.stopPropagation();
  963. if (!item) {
  964. return;
  965. }
  966. meta2d.canvas.addCaches = [];
  967. dropped = false;
  968. let data = null;
  969. const id = item._id || item.id;
  970. let isAsync: boolean;
  971. if (
  972. activeAssets.value === 'user' &&
  973. ['方案', '模板'].includes(activedGroup.value)
  974. ) {
  975. if (!item._id) {
  976. item._id = item.id;
  977. }
  978. item.draggable = false;
  979. data = item.data || item;
  980. } else if (item.draggable === false) {
  981. //方案
  982. data = item.data || item;
  983. } else if (item['3d']) {
  984. const res: any = await getLe5le3d(item._id || item.id, {
  985. image: 1,
  986. _id: 1,
  987. name: 1,
  988. });
  989. data = {
  990. name: 'iframe',
  991. x: 0,
  992. y: 0,
  993. tags: ['meta3d'],
  994. zIndex: 1,
  995. operationalRect: {
  996. x: 0.2,
  997. y: 0.2,
  998. height: 0.8,
  999. width: 0.6,
  1000. },
  1001. props: {
  1002. custom: iframeCustom,
  1003. },
  1004. width: meta2d.store.data.width || meta2d.store.options.width,
  1005. height: meta2d.store.data.height || meta2d.store.options.height,
  1006. externElement: true,
  1007. thumbImg: res.image,
  1008. iframe: 'https://view3d.le5le.com/?id=' + (item._id || item.id),
  1009. };
  1010. } else if (item.component) {
  1011. // 默认
  1012. if (!item.componentDatas && !item.componentData) {
  1013. isAsync = true;
  1014. const ret: any = await axios.post(`/api/data/le5leV-components/get`, {
  1015. id,
  1016. });
  1017. item.componentDatas = ret.componentDatas;
  1018. item.componentData = ret.componentData;
  1019. }
  1020. if (item.componentData) {
  1021. const pens = convertPen([item.componentData]);
  1022. data = deepClone(pens);
  1023. } else if (item.componentDatas) {
  1024. data = deepClone(item.componentDatas);
  1025. }
  1026. } else if (item.data) {
  1027. // 普通图元
  1028. data = item.data;
  1029. } else if (item.image) {
  1030. // 拖拽图片
  1031. let target: any = event.target;
  1032. target.children[0] && (target = target.children[0].children[0]);
  1033. // firefox naturalWidth svg 图片 可能是 0
  1034. const width = target.naturalWidth || target.width;
  1035. const height = target.naturalHeight || target.height;
  1036. const [name, lockedOnCombine] = isGif(item.image)
  1037. ? ['gif', 0]
  1038. : ['image', undefined];
  1039. data = {
  1040. name,
  1041. width,
  1042. height,
  1043. image: item.image,
  1044. imageRatio: true,
  1045. ratio: true,
  1046. lockedOnCombine,
  1047. };
  1048. } else {
  1049. return;
  1050. }
  1051. if (!Array.isArray(data)) {
  1052. data = deepClone([data]);
  1053. }
  1054. !dropped && (meta2d.canvas.addCaches = data);
  1055. if (event instanceof DragEvent) {
  1056. event.dataTransfer.setData(
  1057. 'Meta2d',
  1058. isAsync ? undefined : JSON.stringify(data)
  1059. );
  1060. }
  1061. };
  1062. const dragstart = (event: any) => {
  1063. event.target.style.opacity = 0.5;
  1064. };
  1065. const dragend = (event: any) => {
  1066. event.target.style.opacity = 1;
  1067. };
  1068. const open = async (item: any) => {
  1069. if (!item || item.draggable !== false) {
  1070. return;
  1071. }
  1072. const ret: any = await getLe5leV(item._id || item.id);
  1073. if (!ret) {
  1074. if (item.price > 0) {
  1075. chargeDialog.data = item;
  1076. chargeDialog.show = true;
  1077. }
  1078. return;
  1079. }
  1080. if (activeAssets.value === 'user') {
  1081. router.push({
  1082. path: '/',
  1083. query: {
  1084. r: Date.now() + '',
  1085. id: item._id,
  1086. },
  1087. });
  1088. } else {
  1089. sessionStorage.setItem('opening', '1');
  1090. router.push({
  1091. path: '/',
  1092. query: {
  1093. r: Date.now() + '',
  1094. },
  1095. });
  1096. for (const k of delAttrs) {
  1097. delete ret[k];
  1098. }
  1099. autoSave();
  1100. meta2d.open(ret.data);
  1101. meta2d.fitView();
  1102. }
  1103. select();
  1104. };
  1105. const getPrivateGraphics = async () => {
  1106. for (const item of subGroups.value) {
  1107. if (!item.list.length) {
  1108. item.loading = true;
  1109. if (item.name === '默认') {
  1110. const data = {
  1111. query: { folder: '' },
  1112. projection: {
  1113. image: 1,
  1114. _id: 1,
  1115. name: 1,
  1116. component: 1,
  1117. },
  1118. };
  1119. const config = {
  1120. params: {
  1121. current: 1,
  1122. pageSize: 1000,
  1123. },
  1124. };
  1125. const res: any = await getComponentsList(data, config);
  1126. if (res?.list) {
  1127. item.list = res.list;
  1128. }
  1129. } else if (item.name === '3D') {
  1130. const data = {
  1131. projection: {
  1132. image: 1,
  1133. _id: 1,
  1134. name: 1,
  1135. },
  1136. };
  1137. const config = {
  1138. params: {
  1139. current: 1,
  1140. pageSize: 1000,
  1141. },
  1142. };
  1143. const res: any = await axios.post(
  1144. '/api/data/le5le3d/list',
  1145. data,
  1146. config
  1147. );
  1148. if (res?.list) {
  1149. for (const item of res.list) {
  1150. item['3d'] = true;
  1151. item['draggable'] = true;
  1152. }
  1153. item.list = res.list;
  1154. }
  1155. }
  1156. item.loading = false;
  1157. }
  1158. }
  1159. };
  1160. const editedFolder = ref<any>(undefined);
  1161. const onCreateFolder = () => {
  1162. activedPanels[activedGroup.value].splice(
  1163. 0,
  1164. activedPanels[activedGroup.value].length
  1165. );
  1166. editedFolder.value = {
  1167. _id: '',
  1168. name: '',
  1169. label: '新建文件夹',
  1170. list: [],
  1171. edited: true,
  1172. canEdited: true,
  1173. };
  1174. subGroups.value.splice(subGroups.value.length - 1, 0, editedFolder.value);
  1175. };
  1176. const createFoder = async () => {
  1177. if (!editedFolder.value.label) {
  1178. return;
  1179. }
  1180. if (editedFolder.value.label === editedFolder.value.name) {
  1181. editedFolder.value.edited = false;
  1182. return;
  1183. }
  1184. const found = subGroups.value.findIndex(
  1185. (group: any) => group.name === editedFolder.value.label
  1186. );
  1187. if (found >= 0) {
  1188. MessagePlugin.error('已经存在相同名称文件夹');
  1189. return;
  1190. }
  1191. if (activeAssets.value !== 'user') {
  1192. return;
  1193. }
  1194. if (editedFolder.value.id) {
  1195. const ret: any = await axios.post('/api/directory/update', {
  1196. oldFullpath: `/大屏/${activedGroup.value}/` + editedFolder.value.oldLabel,
  1197. newFullpath: `/大屏/${activedGroup.value}/` + editedFolder.value.label,
  1198. });
  1199. if (ret) {
  1200. editedFolder.value.name = editedFolder.value.label;
  1201. editedFolder.value.edited = false;
  1202. }
  1203. //更新图纸的folder
  1204. if (['组件', '方案', '模板'].includes(activedGroup.value)) {
  1205. const collection =
  1206. activedGroup.value === '组件' ? 'le5leV-components' : 'le5leV';
  1207. editedFolder.value.list?.forEach(async (item) => {
  1208. item.folder = editedFolder.value.label;
  1209. await updateCollection(collection, {
  1210. id: item.id,
  1211. folder: editedFolder.value.label,
  1212. });
  1213. });
  1214. }
  1215. } else {
  1216. const ret: any = await axios.post('/api/directory/add', {
  1217. fullpath: `/大屏/${activedGroup.value}/` + editedFolder.value.label,
  1218. });
  1219. if (ret) {
  1220. editedFolder.value.name = editedFolder.value.label;
  1221. editedFolder.value._id = ret._id || ret.id;
  1222. editedFolder.value.edited = false;
  1223. }
  1224. }
  1225. };
  1226. const _createFoder = async () => {
  1227. if (!editedFolder.value.label) {
  1228. return;
  1229. }
  1230. if (editedFolder.value.label === editedFolder.value.name) {
  1231. editedFolder.value.edited = false;
  1232. return;
  1233. }
  1234. const found = subGroups.value.findIndex(
  1235. (group: any) => group.name === editedFolder.value.label
  1236. );
  1237. if (found >= 0) {
  1238. MessagePlugin.error('已经存在相同名称文件夹');
  1239. return;
  1240. }
  1241. if (activeAssets.value !== 'user') {
  1242. return;
  1243. }
  1244. if (['组件', '方案', '模板'].includes(activedGroup.value)) {
  1245. if (editedFolder.value._id) {
  1246. const ret: any = await axios.post('/api/data/folders/update', {
  1247. _id: editedFolder.value._id,
  1248. name: editedFolder.value.label,
  1249. });
  1250. if (ret) {
  1251. editedFolder.value.name = editedFolder.value.label;
  1252. editedFolder.value.edited = false;
  1253. }
  1254. } else {
  1255. let type =
  1256. activedGroup.value === '组件'
  1257. ? 'le5leV-components'
  1258. : activedGroup.value === '模板'
  1259. ? 'le5leV-template'
  1260. : 'le5leV';
  1261. const ret: any = await axios.post('/api/data/folders/add', {
  1262. name: editedFolder.value.label,
  1263. type,
  1264. list: [],
  1265. });
  1266. if (ret) {
  1267. editedFolder.value.name = editedFolder.value.label;
  1268. editedFolder.value._id = ret._id;
  1269. editedFolder.value.edited = false;
  1270. }
  1271. }
  1272. } else if (activedGroup.value === '图片') {
  1273. if (editedFolder.value._id) {
  1274. const ret: any = await axios.post('/api/directory/update', {
  1275. oldFullpath: '/大屏/' + editedFolder.value.oldLabel,
  1276. newFullpath: '/大屏/' + editedFolder.value.label,
  1277. });
  1278. if (ret) {
  1279. editedFolder.value.name = editedFolder.value.label;
  1280. editedFolder.value.edited = false;
  1281. }
  1282. } else {
  1283. const ret: any = await axios.post('/api/directory/add', {
  1284. fullpath: '/大屏/' + editedFolder.value.label,
  1285. });
  1286. if (ret) {
  1287. editedFolder.value.name = editedFolder.value.label;
  1288. editedFolder.value._id = ret._id || ret.id;
  1289. editedFolder.value.edited = false;
  1290. }
  1291. }
  1292. }
  1293. };
  1294. const onEditHeader = (item: any) => {
  1295. item.label = item.name;
  1296. item.edited = true;
  1297. item.oldLabel = item.name;
  1298. editedFolder.value = item;
  1299. };
  1300. const onAdd = (item: any) => {
  1301. blank();
  1302. if (activedGroup.value === '方案') {
  1303. item.vType = 'le5leV';
  1304. } else if (activedGroup.value === '模板') {
  1305. item.vType = 'le5leV-template';
  1306. } else if (activedGroup.value === '组件') {
  1307. item.vType = 'le5leV-components';
  1308. }
  1309. setFolder(item);
  1310. const query: any = {
  1311. r: Date.now() + '',
  1312. folder: item.name,
  1313. tags: activedGroup.value,
  1314. };
  1315. if (activedGroup.value === '组件') {
  1316. query.c = 1;
  1317. }
  1318. router.push({
  1319. path: '/',
  1320. query,
  1321. });
  1322. // meta2d.open({
  1323. // name: '新建项目',
  1324. // pens: [],
  1325. // enableMock: true,
  1326. // tags: activedGroup.value,
  1327. // folder: item.name,
  1328. // } as any);
  1329. };
  1330. const onKeyHeader = (text: string, event: any) => {
  1331. if (event.e.key === 'Escape') {
  1332. editedFolder.value.edited = false;
  1333. }
  1334. };
  1335. // 默认右键菜单
  1336. const contextmenu = reactive<any>({
  1337. visible: false,
  1338. style: {},
  1339. // 子分类
  1340. group: undefined,
  1341. // 组件图纸
  1342. component: {},
  1343. // 右键二级子菜单
  1344. subMenus: [],
  1345. });
  1346. const contextmenuDom = ref<any>(null);
  1347. const onContextMenu = async (e: MouseEvent, group: any, item: any) => {
  1348. e.preventDefault();
  1349. e.stopPropagation();
  1350. if (activeAssets.value === 'system') {
  1351. return;
  1352. }
  1353. contextmenu.group = group;
  1354. contextmenu.component = item;
  1355. if (document.body.clientHeight - e.clientY < 128) {
  1356. contextmenu.style = {
  1357. left: e.clientX + 'px',
  1358. bottom: '4px',
  1359. };
  1360. } else {
  1361. contextmenu.style = {
  1362. left: e.clientX + 'px',
  1363. top: e.clientY + 'px',
  1364. };
  1365. }
  1366. contextmenu.subMenus = [];
  1367. for (const elem of subGroups.value) {
  1368. if (elem === group || elem.name === '默认' || elem.name === '3D') {
  1369. continue;
  1370. }
  1371. contextmenu.subMenus.push(elem);
  1372. }
  1373. contextmenu.visible = true;
  1374. setTimeout(() => {
  1375. if (contextmenuDom.value) {
  1376. contextmenuDom.value.focus();
  1377. }
  1378. }, 500);
  1379. };
  1380. const delDialog = reactive<any>({
  1381. contextmenuObj: {},
  1382. });
  1383. // TODO 右键菜单移动图纸
  1384. const onMenu = async (val: string) => {
  1385. const id = contextmenu.component._id || contextmenu.component.id;
  1386. setTimeout(() => {
  1387. contextmenu.group = '';
  1388. contextmenu.component = {};
  1389. contextmenu.subMenus = [];
  1390. }, 500);
  1391. switch (val) {
  1392. case 'edit':
  1393. if (
  1394. contextmenu.component.tags &&
  1395. contextmenu.component.tags.includes('svg')
  1396. ) {
  1397. MessagePlugin.warning('解析的svg图元不允许编辑!');
  1398. hideContextmenu();
  1399. contextmenu.activeValue = 0;
  1400. return;
  1401. }
  1402. if (contextmenu.component.component) {
  1403. autoSave();
  1404. router.push({
  1405. path: '/',
  1406. query: {
  1407. id,
  1408. c: 1,
  1409. r: Date.now() + '',
  1410. },
  1411. });
  1412. } else if (contextmenu.component['3d']) {
  1413. let url = 'https://3d.le5le.com/?id=';
  1414. if (window.url3D) {
  1415. url = window.url3D;
  1416. }
  1417. window.open(url + id, '_blank');
  1418. } else {
  1419. if (contextmenu.group._id && contextmenu.group.name !== '默认') {
  1420. setFolder(contextmenu.group);
  1421. }
  1422. autoSave();
  1423. //文件夹
  1424. router.push({
  1425. path: '/',
  1426. query: {
  1427. id,
  1428. r: Date.now() + '',
  1429. },
  1430. });
  1431. }
  1432. hideContextmenu();
  1433. break;
  1434. case 'del':
  1435. delDialog.show = true;
  1436. Object.assign(delDialog.contextmenuObj, contextmenu);
  1437. break;
  1438. default:
  1439. if (val.indexOf('move:')) {
  1440. return;
  1441. }
  1442. val = val.replace('move:', '');
  1443. const group = contextmenu.subMenus.find(
  1444. (element: any) => element.name === val
  1445. );
  1446. if (!group) {
  1447. return;
  1448. }
  1449. // 前端: 添加组件到目标文件夹
  1450. let data: any;
  1451. if (contextmenu.component.component) {
  1452. data = {
  1453. component: true,
  1454. image: contextmenu.component.image,
  1455. name: contextmenu.component.name,
  1456. visible: true,
  1457. _id: contextmenu.component._id || contextmenu.component.id,
  1458. filename: contextmenu.component.filename,
  1459. };
  1460. } else {
  1461. data = {
  1462. image: contextmenu.component.image,
  1463. name: contextmenu.component.name,
  1464. tags: contextmenu.component.tags,
  1465. visible: true,
  1466. _id: contextmenu.component._id || contextmenu.component.id,
  1467. filename: contextmenu.component.filename,
  1468. };
  1469. }
  1470. group.list.push(data);
  1471. // 前端:从源文件夹移出组件
  1472. contextmenu.group.list.forEach((item: any, index: number, arr: any[]) => {
  1473. if (id === item._id || id === item.id) {
  1474. arr.splice(index, 1);
  1475. }
  1476. });
  1477. if (activedGroup.value !== '图片') {
  1478. let collection = contextmenu.component.component
  1479. ? 'v-components'
  1480. : 'v';
  1481. // 更新后端组件信息
  1482. let ret = await updateCollection(collection, {
  1483. id,
  1484. folder: val === '默认' ? '' : val,
  1485. });
  1486. if (!ret) {
  1487. return;
  1488. }
  1489. }
  1490. // // 更新后端源文件夹列表
  1491. // if (contextmenu.group.name !== '默认') {
  1492. // await axios.post('/api/data/folders/update', {
  1493. // _id: contextmenu.group._id || contextmenu.group.id,
  1494. // list: contextmenu.group.list,
  1495. // });
  1496. // }
  1497. // 更新后端目标文件夹列表
  1498. // if (group.name !== '默认') {
  1499. // await axios.post('/api/data/folders/update', {
  1500. // _id: group._id || group.id,
  1501. // list: group.list,
  1502. // });
  1503. // }
  1504. let filename: string =
  1505. contextmenu.component.filename || contextmenu.component.image;
  1506. if (imageDrive && filename.startsWith(imageDrive)) {
  1507. filename = filename.slice(imageDrive.length);
  1508. }
  1509. //更新缩略图位置
  1510. await axios.post('/api/files/update', {
  1511. filenames: [filename],
  1512. 'metadata.directory': `/大屏/${activedGroup.value}/${val || '默认'}`,
  1513. });
  1514. break;
  1515. }
  1516. contextmenu.activeValue = 0;
  1517. };
  1518. const hideContextmenu = () => {
  1519. contextmenu.visible = false;
  1520. };
  1521. const _delComponent = async () => {
  1522. // const id = contextmenu.component._id || contextmenu.component.id;
  1523. if (['组件', '方案', '模板'].includes(activedGroup.value)) {
  1524. let collection = delDialog.contextmenuObj.component.component
  1525. ? 'le5leV-components'
  1526. : 'le5leV';
  1527. const id =
  1528. delDialog.contextmenuObj.component._id ||
  1529. delDialog.contextmenuObj.component.id;
  1530. try {
  1531. await axios.post(`/api/data/${collection}/delete`, {
  1532. id,
  1533. });
  1534. } catch (e) {
  1535. console.error(e);
  1536. return;
  1537. }
  1538. //删除缩略图
  1539. if (delDialog.contextmenuObj.component.image) {
  1540. await delImage(delDialog.contextmenuObj.component.image);
  1541. }
  1542. // 前端:从源文件夹移出组件
  1543. delDialog.contextmenuObj.group.list.forEach(
  1544. (item: any, index: number, arr: any[]) => {
  1545. if (id === item._id || id === item.id) {
  1546. arr.splice(index, 1);
  1547. }
  1548. }
  1549. );
  1550. // 更新后端源文件夹列表
  1551. if (delDialog.contextmenuObj.group.name !== '默认') {
  1552. await axios.post('/api/data/folders/update', {
  1553. _id:
  1554. delDialog.contextmenuObj.group._id ||
  1555. delDialog.contextmenuObj.group.id,
  1556. list: delDialog.contextmenuObj.group.list,
  1557. });
  1558. }
  1559. } else {
  1560. const id =
  1561. delDialog.contextmenuObj.component._id ||
  1562. delDialog.contextmenuObj.component.id;
  1563. delDialog.contextmenuObj.group.list.forEach(
  1564. (item: any, index: number, arr: any[]) => {
  1565. if (id && (id === item._id || id === item.id)) {
  1566. arr.splice(index, 1);
  1567. }
  1568. if (!id) {
  1569. if (item.url === delDialog.contextmenuObj.component.url) {
  1570. arr.splice(index, 1);
  1571. }
  1572. }
  1573. }
  1574. );
  1575. await axios.post(`/api/files/delete`, {
  1576. filenames: [delDialog.contextmenuObj.component.filename],
  1577. physically: false,
  1578. });
  1579. }
  1580. delDialog.show = false;
  1581. delDialog.contextmenuObj = {};
  1582. MessagePlugin.success('删除成功');
  1583. };
  1584. const delComponent = async () => {
  1585. // const id = contextmenu.component._id || contextmenu.component.id;
  1586. if (['组件', '方案', '模板'].includes(activedGroup.value)) {
  1587. let collection = delDialog.contextmenuObj.component.component
  1588. ? 'v-components'
  1589. : 'v';
  1590. const id =
  1591. delDialog.contextmenuObj.component._id ||
  1592. delDialog.contextmenuObj.component.id;
  1593. try {
  1594. await axios.post(`/api/data/${collection}/delete`, {
  1595. id,
  1596. });
  1597. } catch (e) {
  1598. console.error(e);
  1599. return;
  1600. }
  1601. // 前端:从源文件夹移出组件
  1602. delDialog.contextmenuObj.group.list.forEach(
  1603. (item: any, index: number, arr: any[]) => {
  1604. if (id === item._id || id === item.id) {
  1605. arr.splice(index, 1);
  1606. }
  1607. }
  1608. );
  1609. } else {
  1610. const id =
  1611. delDialog.contextmenuObj.component._id ||
  1612. delDialog.contextmenuObj.component.id;
  1613. delDialog.contextmenuObj.group.list.forEach(
  1614. (item: any, index: number, arr: any[]) => {
  1615. if (id && (id === item._id || id === item.id)) {
  1616. arr.splice(index, 1);
  1617. }
  1618. if (!id) {
  1619. if (item.url === delDialog.contextmenuObj.component.url) {
  1620. arr.splice(index, 1);
  1621. }
  1622. }
  1623. }
  1624. );
  1625. }
  1626. let filename: string =
  1627. delDialog.contextmenuObj.component.filename ||
  1628. delDialog.contextmenuObj.component.image;
  1629. if (imageDrive && filename.startsWith(imageDrive)) {
  1630. filename = filename.slice(imageDrive.length);
  1631. }
  1632. await axios.post(`/api/files/delete`, {
  1633. filenames: [filename],
  1634. physically: false,
  1635. });
  1636. delDialog.show = false;
  1637. delDialog.contextmenuObj = {};
  1638. MessagePlugin.success('删除成功');
  1639. };
  1640. const drop = (obj: any) => {
  1641. dropped = true;
  1642. if (obj) {
  1643. Array.isArray(obj) && open(obj[0]);
  1644. } else {
  1645. MessagePlugin.warning('正在请求网络数据中,请稍后重试');
  1646. }
  1647. };
  1648. const onSubmitOrder = async () => {
  1649. const result: any = await axios.post('/api/order/datas/submit', {
  1650. collection: 'le5leV',
  1651. id: chargeDialog.data._id,
  1652. });
  1653. if (result) {
  1654. wechatPayDialog.order = result;
  1655. wechatPayDialog.show = true;
  1656. }
  1657. };
  1658. const getPayResult = async () => {
  1659. const result: { state: number } = await axios.post('/api/order/pay/state', {
  1660. id: wechatPayDialog.order.id || wechatPayDialog.order._id,
  1661. });
  1662. if (result && result.state) {
  1663. return true;
  1664. }
  1665. };
  1666. const onChargeSuccess = () => {
  1667. MessagePlugin.success('支付成功!');
  1668. wechatPayDialog.show = false;
  1669. chargeDialog.show = false;
  1670. };
  1671. const onSearch = () => {
  1672. debounce(searchGraphics, 300);
  1673. };
  1674. const searchGraphics = async () => {
  1675. if (search.value) {
  1676. activedPanels[activedGroup.value].splice(
  1677. 0,
  1678. activedPanels[activedGroup.value].length
  1679. );
  1680. }
  1681. for (const group of subGroups.value) {
  1682. for (const item of group.list) {
  1683. if (search.value) {
  1684. item.visible = searchObjectPinyin(item, 'name', search.value);
  1685. } else {
  1686. item.visible = true;
  1687. }
  1688. }
  1689. if (search.value) {
  1690. activedPanels[activedGroup.value].push(group.name);
  1691. }
  1692. }
  1693. };
  1694. const onFold = () => {
  1695. if (!activedPanels[activedGroup.value]) {
  1696. return;
  1697. }
  1698. if (activedPanels[activedGroup.value].length) {
  1699. activedPanels[activedGroup.value] = [];
  1700. } else {
  1701. activedPanels[activedGroup.value] = [];
  1702. for (const item of subGroups.value) {
  1703. activedPanels[activedGroup.value].push(item.name);
  1704. }
  1705. }
  1706. };
  1707. const loadImage = (elem: any) => {
  1708. if (elem.isSvg) {
  1709. makeSvg(elem);
  1710. if (activedGroup.value === '图元') {
  1711. throttle(renderPngGroup, 100);
  1712. }
  1713. }
  1714. };
  1715. const renderPngGroup = () => {
  1716. subGroups.value = pngs;
  1717. };
  1718. let uploadGroup: any;
  1719. const onSelectFiles = (item: any) => {
  1720. uploadGroup = item;
  1721. };
  1722. const beforeUpload = (file: any) => {
  1723. if (!(user && user.id)) {
  1724. MessagePlugin.warning('请先登录!');
  1725. return false;
  1726. }
  1727. return true;
  1728. };
  1729. /*
  1730. * @description 根据上传的文件处理为meta2d能够识别的内容
  1731. * */
  1732. function processFileObj(fileObj, c) {
  1733. return new Promise((resolve) => {
  1734. if (fileObj.name.endsWith('.svg')) {
  1735. let fileReader = new FileReader();
  1736. fileReader.readAsText(fileObj.raw);
  1737. fileReader.onload = async () => {
  1738. let svgText = fileReader.result;
  1739. c.componentDatas = parseSvg(svgText as string);
  1740. c.component = true;
  1741. c.tags = ['svg'];
  1742. resolve(c);
  1743. };
  1744. } else {
  1745. // 除了svg 其他一律按照图片处理,这样是否会有问题?
  1746. resolve(c);
  1747. }
  1748. });
  1749. }
  1750. const fileSuccessed = async (content: any) => {
  1751. if (activedGroup.value === '组件') {
  1752. const c: any = {
  1753. name: filename(content.file.name),
  1754. image:
  1755. content.response.url ||
  1756. content.response.metadata.url ||
  1757. `/file${content.response.filename}`,
  1758. folder: uploadGroup.name === '默认' ? '' : uploadGroup.name,
  1759. };
  1760. let rst = await processFileObj(
  1761. { raw: content.file.raw, name: content.file.name },
  1762. c
  1763. );
  1764. const ret: any = await addCollection('le5leV-components', rst);
  1765. c._id = ret._id || ret.id;
  1766. // if (ret && uploadGroup.name !== '默认') {
  1767. // if (!uploadGroup.list) {
  1768. // uploadGroup.list = [];
  1769. // }
  1770. // uploadGroup.list.push(c);
  1771. // await axios.post('/api/data/folders/update', {
  1772. // _id: uploadGroup._id || uploadGroup.id,
  1773. // list: uploadGroup.list,
  1774. // });
  1775. // } else {
  1776. // if (!uploadGroup.list) {
  1777. // uploadGroup.list = [];
  1778. // }
  1779. // uploadGroup.list.push(c);
  1780. // }
  1781. uploadGroup.list.push(c);
  1782. } else if (activedGroup.value === '图片') {
  1783. uploadGroup.list.push({
  1784. ...content.response,
  1785. image:
  1786. content.response.url ||
  1787. content.response.metadata.url ||
  1788. `/file${content.response.filename}`,
  1789. visible: true,
  1790. folder: content.response.metadata.directory,
  1791. });
  1792. }
  1793. MessagePlugin.success('上传成功');
  1794. };
  1795. const delFolder = async (item: any) => {
  1796. if (item.list?.length) {
  1797. MessagePlugin.error('文件夹不为空!');
  1798. return;
  1799. }
  1800. const id = item._id || item.id;
  1801. let ret: any;
  1802. if (activeAssets.value !== 'user') {
  1803. return;
  1804. }
  1805. // if (['组件', '方案', '模板'].includes(activedGroup.value)) {
  1806. // ret = await axios.post('/api/data/folders/delete', {
  1807. // id,
  1808. // });
  1809. // } else if (activedGroup.value === '图片') {
  1810. // ret = await axios.post('/api/directory/delete', {
  1811. // fullpaths: [`/大屏/${item.name}`],
  1812. // });
  1813. // }
  1814. ret = await axios.post('/api/directory/delete', {
  1815. fullpaths: [`/大屏/${activedGroup.value}/${item.name}`],
  1816. });
  1817. if (ret) {
  1818. const i = subGroups.value.findIndex(
  1819. (elem: any) => id === elem._id || id === elem.id
  1820. );
  1821. i >= 0 && subGroups.value.splice(i, 1);
  1822. }
  1823. };
  1824. const reloadCurrent = () => {
  1825. groupChange(activedGroup.value);
  1826. };
  1827. const updateAfterSave = (e) => {
  1828. if (activeAssets.value === 'user') {
  1829. if (e === 'le5leV-components' && activedGroup.value === '组件') {
  1830. groupChange('组件');
  1831. } else if (e === 'le5leV-template' && activedGroup.value === '模板') {
  1832. groupChange('模板');
  1833. } else if (e === '' && activedGroup.value === '方案') {
  1834. groupChange('方案');
  1835. }
  1836. }
  1837. };
  1838. const onManageGraphic = () => {
  1839. if (activedGroup.value === '素材') {
  1840. manageDialog.data = deepClone(materials);
  1841. } else if (activedGroup.value === '图元') {
  1842. manageDialog.data = deepClone(pngs);
  1843. }
  1844. manageDialog.show = true;
  1845. };
  1846. const manageConfirm = () => {
  1847. //TODO 后续存放到后端 目前本地存储
  1848. if (activedGroup.value === '素材') {
  1849. localforage.setItem('le5leV-materials', JSON.stringify(manageDialog.data));
  1850. materials.length = 0;
  1851. materials.push(...manageDialog.data);
  1852. } else if (activedGroup.value === '图元') {
  1853. localforage.setItem('le5leV-pngs', JSON.stringify(manageDialog.data));
  1854. pngs.length = 0;
  1855. pngs.push(...manageDialog.data);
  1856. }
  1857. subGroups.value = manageDialog.data;
  1858. manageDialog.show = false;
  1859. };
  1860. onMounted(() => {
  1861. groupChange('方案');
  1862. document.addEventListener('dragstart', dragstart, false);
  1863. document.addEventListener('dragend', dragend, false);
  1864. setTimeout(() => {
  1865. meta2d.on('drop', drop);
  1866. meta2d.on('logout', reloadCurrent);
  1867. meta2d.on('business-save', updateAfterSave);
  1868. }, 2000);
  1869. });
  1870. onUnmounted(() => {
  1871. document.removeEventListener('dragstart', dragstart);
  1872. document.removeEventListener('dragend', dragend);
  1873. meta2d.off('drop', drop);
  1874. meta2d.off('logout', reloadCurrent);
  1875. meta2d.off('business-save', updateAfterSave);
  1876. });
  1877. </script>
  1878. <style lang="postcss" scoped>
  1879. .graphics {
  1880. display: flex;
  1881. flex-direction: column;
  1882. background-color: var(--color-background);
  1883. z-index: 3;
  1884. .group-asset {
  1885. height: 40px;
  1886. justify-content: center;
  1887. align-items: center;
  1888. display: flex;
  1889. .t-radio-group.t-radio-group--filled {
  1890. padding: 0px;
  1891. height: 30px;
  1892. }
  1893. :deep(.t-radio-button__label) {
  1894. margin: auto;
  1895. }
  1896. .t-radio-button {
  1897. width: 120px;
  1898. height: 100%;
  1899. color: #fff;
  1900. &:hover {
  1901. color: var(--color-primary);
  1902. }
  1903. &.t-is-checked {
  1904. background-color: var(--color-primary) !important;
  1905. color: #fff !important;
  1906. }
  1907. }
  1908. }
  1909. .groups-panel {
  1910. display: grid;
  1911. grid-template-columns: 50px 1fr;
  1912. border-top: 1px solid var(--color-background-input);
  1913. flex-grow: 1;
  1914. overflow-y: auto;
  1915. font-size: 12px;
  1916. z-index: 7;
  1917. .groups {
  1918. & > div {
  1919. display: flex;
  1920. flex-direction: column;
  1921. align-items: center;
  1922. padding: 16px 4px;
  1923. line-height: 1;
  1924. cursor: pointer;
  1925. .t-icon {
  1926. font-size: 20px;
  1927. margin-bottom: 8px;
  1928. }
  1929. .l-icon {
  1930. font-size: 24px;
  1931. margin-bottom: 8px;
  1932. }
  1933. &:hover {
  1934. color: var(--color-primary);
  1935. }
  1936. }
  1937. .active {
  1938. background-color: var(--color-background-active);
  1939. color: var(--color-primary);
  1940. border-left: 2px solid var(--color-primary);
  1941. }
  1942. }
  1943. .list {
  1944. overflow-y: auto;
  1945. max-height: calc(100vh - 100px);
  1946. background-color: var(--color-background-active);
  1947. /* padding-top: 8px; */
  1948. .input-search {
  1949. flex-shrink: 0;
  1950. /* height: 40px; */
  1951. position: sticky;
  1952. top: 0px;
  1953. z-index: 10;
  1954. padding: 16px;
  1955. color: #878f9c;
  1956. .btn {
  1957. background: #fff0;
  1958. img {
  1959. background-color: #fff0;
  1960. margin-top: 9px;
  1961. }
  1962. /* .t-icon {
  1963. background: #fff0;
  1964. } */
  1965. }
  1966. }
  1967. .div-manage {
  1968. position: sticky;
  1969. bottom: 2px;
  1970. z-index: 10;
  1971. margin-top: 800px;
  1972. }
  1973. * {
  1974. background-color: var(--color-background-active);
  1975. }
  1976. :deep(.t-collapse) {
  1977. min-height: 100vh;
  1978. border: none;
  1979. }
  1980. :deep(.t-collapse-panel__header) {
  1981. border: none;
  1982. font-size: 12px;
  1983. font-weight: 400;
  1984. padding: 8px 8px 8px 16px;
  1985. & > .t-space {
  1986. display: none;
  1987. }
  1988. & > .t-space:focus {
  1989. display: inline-flex;
  1990. }
  1991. &:hover .t-collapse-panel__icon,
  1992. &:hover > .flex {
  1993. color: var(--color-primary);
  1994. }
  1995. .t-collapse-panel__icon,
  1996. .t-collapse-panel__icon * {
  1997. transition: none;
  1998. }
  1999. .t-icon {
  2000. font-size: 13px;
  2001. }
  2002. }
  2003. :deep(.t-collapse-panel__wrapper:hover) {
  2004. .t-collapse-panel__header > .t-space {
  2005. display: inline-flex;
  2006. }
  2007. }
  2008. :deep(.t-collapse-panel__body) {
  2009. border: none;
  2010. }
  2011. :deep(.t-collapse-panel__content) {
  2012. background-color: var(--color-background-active);
  2013. padding: 4px 4px 20px 4px;
  2014. display: grid;
  2015. grid-template-columns: 1fr 1fr 1fr;
  2016. grid-row-gap: 12px;
  2017. }
  2018. :deep(.t-loading--center) {
  2019. width: 100px;
  2020. .t-loading__text {
  2021. margin-left: 8px;
  2022. height: 24px;
  2023. }
  2024. }
  2025. :deep(.t-image__error) {
  2026. .t-space-item:last-child {
  2027. display: none;
  2028. }
  2029. }
  2030. :deep(.t-image__loading) {
  2031. .t-space-item:last-child {
  2032. display: none;
  2033. }
  2034. }
  2035. .graphic {
  2036. position: relative;
  2037. padding: 10px 0;
  2038. border-radius: 2px;
  2039. border: 1px solid transparent;
  2040. &:hover {
  2041. cursor: pointer;
  2042. border-color: var(--color-primary);
  2043. }
  2044. p {
  2045. margin-top: 10px;
  2046. padding: 0 8px;
  2047. text-align: center;
  2048. font-size: 12px;
  2049. height: 12px;
  2050. line-height: 1;
  2051. overflow: hidden;
  2052. text-overflow: ellipsis;
  2053. display: -webkit-box;
  2054. -webkit-line-clamp: 1;
  2055. word-break: break-all;
  2056. -webkit-box-orient: vertical;
  2057. }
  2058. .t-image__wrapper {
  2059. height: 32px;
  2060. width: 32px;
  2061. margin: auto;
  2062. :deep(.t-image) {
  2063. border-radius: 2px;
  2064. }
  2065. }
  2066. svg {
  2067. color: var(--color);
  2068. height: 32px;
  2069. width: 100%;
  2070. margin: auto;
  2071. }
  2072. .svg-box {
  2073. height: 32px;
  2074. width: 32px;
  2075. margin-left: calc(50% - 16px);
  2076. margin-top: 10px;
  2077. margin-bottom: 10px;
  2078. &:deep(svg) {
  2079. height: 100%;
  2080. width: 100%;
  2081. .cls-1 {
  2082. stroke: var(--color) !important;
  2083. stroke-width: 4px;
  2084. fill: none;
  2085. stroke-dasharray: none;
  2086. opacity: 1;
  2087. }
  2088. }
  2089. }
  2090. .price {
  2091. position: absolute;
  2092. top: 8px;
  2093. right: 8px;
  2094. display: inline-block;
  2095. z-index: 1;
  2096. border-radius: 2px;
  2097. background-color: #ff400060;
  2098. color: var(--color-bland);
  2099. font-size: 10px;
  2100. line-height: 1;
  2101. padding: 3px;
  2102. }
  2103. }
  2104. }
  2105. .two-columns {
  2106. :deep(.t-collapse-panel__content) {
  2107. grid-template-columns: 1fr 1fr;
  2108. }
  2109. .graphic {
  2110. .t-image__wrapper {
  2111. width: 100px;
  2112. height: 56.25px;
  2113. background-color: var(--color-background);
  2114. }
  2115. }
  2116. }
  2117. }
  2118. .context-menu-box {
  2119. position: fixed;
  2120. z-index: 200;
  2121. & > div {
  2122. width: 140px !important;
  2123. position: static;
  2124. }
  2125. :deep(.t-menu) {
  2126. .t-menu__item {
  2127. &.t-is-opened {
  2128. background-color: var(--color-background-popup-hover);
  2129. transition: none !important;
  2130. }
  2131. }
  2132. .t-fake-arrow {
  2133. transform: rotate(-90deg) !important;
  2134. }
  2135. .t-fake-arrow--active {
  2136. transform: rotate(90deg) !important;
  2137. }
  2138. }
  2139. }
  2140. :deep(.dialog-manage) {
  2141. .t-dialog__body {
  2142. height: 500px;
  2143. overflow: scroll;
  2144. }
  2145. .t-collapse {
  2146. border: 0px;
  2147. }
  2148. .t-collapse-panel__header {
  2149. border: 0px;
  2150. }
  2151. .t-collapse-panel__body {
  2152. border: 0px;
  2153. }
  2154. .t-collapse-panel__content {
  2155. display: grid;
  2156. grid-template-columns: 1fr 1fr 1fr 1fr;
  2157. /* grid-template-rows: repeat(100, 100px); */
  2158. grid-auto-rows: minmax(100px, auto);
  2159. grid-row-gap: 20px;
  2160. grid-column-gap: 20px;
  2161. }
  2162. .t-collapse-panel__body {
  2163. background: #fff0;
  2164. }
  2165. .manage-list {
  2166. max-height: 310px;
  2167. border: 1px solid #fff;
  2168. position: relative;
  2169. border: 1px solid #535f79;
  2170. position: relative;
  2171. border-radius: 2px;
  2172. .t-checkbox {
  2173. position: absolute;
  2174. right: 0px;
  2175. top: 10px;
  2176. z-index: 10;
  2177. }
  2178. }
  2179. }
  2180. }
  2181. </style>