Header.vue 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181
  1. <template>
  2. <div class="app-header">
  3. <a class="logo" :href="enterprise.home" target="_blank">
  4. <img src="/favicon.ico" />
  5. <span> {{ enterprise.name }}</span>
  6. </a>
  7. <t-dropdown :minColumnWidth="200" :maxHeight="560" :delay2="[10, 150]" overlayClassName="header-dropdown"
  8. trigger1="click">
  9. <a> 文件 </a>
  10. <t-dropdown-menu>
  11. <t-dropdown-item @click="newFile">
  12. <a>新建文件</a>
  13. </t-dropdown-item>
  14. <t-dropdown-item @click="load(true)">
  15. <a>打开文件</a>
  16. </t-dropdown-item>
  17. <t-dropdown-item divider="true" @click="load">
  18. <a>导入文件</a>
  19. </t-dropdown-item>
  20. <t-dropdown-item>
  21. <a @click="save()">保存</a>
  22. </t-dropdown-item>
  23. <t-dropdown-item>
  24. <a @click="save(SaveType.SaveAs, '', undefined, 1)">另保存</a>
  25. </t-dropdown-item>
  26. <t-dropdown-item divider="true">
  27. <a @click="downloadJson">下载JSON文件</a>
  28. </t-dropdown-item>
  29. <t-dropdown-item>
  30. <a @click="downloadZip">
  31. <div class="flex">
  32. 导出为ZIP文件 <span class="flex-grow"></span>
  33. <span><label>VIP</label></span>
  34. </div>
  35. </a>
  36. </t-dropdown-item>
  37. <t-dropdown-item>
  38. <a @click="downloadHtml">
  39. <div class="flex">
  40. 下载离线部署包 <span class="flex-grow"></span>
  41. <span><label>VIP</label></span>
  42. </div>
  43. </a>
  44. </t-dropdown-item>
  45. <t-dropdown-item>
  46. <a @click="downloadVue3">
  47. <div class="flex">
  48. 下载Vue3组件包 <span class="flex-grow"></span>
  49. <span><label>VIP</label></span>
  50. </div>
  51. </a>
  52. </t-dropdown-item>
  53. <t-dropdown-item>
  54. <a @click="downloadVue2">
  55. <div class="flex">
  56. 下载Vue2组件包 <span class="flex-grow"></span>
  57. <span><label>VIP</label></span>
  58. </div>
  59. </a>
  60. </t-dropdown-item>
  61. <t-dropdown-item divider="true">
  62. <a @click="downloadReact">
  63. <div class="flex">
  64. 下载React组件包 <span class="flex-grow"></span>
  65. <span><label>VIP</label></span>
  66. </div>
  67. </a>
  68. </t-dropdown-item>
  69. <t-dropdown-item>
  70. <a @click="downloadPng">下载为PNG</a>
  71. </t-dropdown-item>
  72. <t-dropdown-item divider="true">
  73. <a @click="downloadSvg">下载为SVG</a>
  74. </t-dropdown-item>
  75. <t-dropdown-item>
  76. <a @click="switchTheme('light')">明亮主题</a>
  77. </t-dropdown-item>
  78. <t-dropdown-item>
  79. <a @click="switchTheme('dark')">暗黑主题</a>
  80. </t-dropdown-item>
  81. </t-dropdown-menu>
  82. </t-dropdown>
  83. <t-dropdown :minColumnWidth="180" :maxHeight="500" :delay2="[10, 150]" overlayClassName="header-dropdown">
  84. <a> 编辑 </a>
  85. <t-dropdown-menu>
  86. <t-dropdown-item>
  87. <a @click="onUndo">
  88. <div class="flex">
  89. 撤销 <span class="flex-grow"></span> Ctrl + Z
  90. </div>
  91. </a>
  92. </t-dropdown-item>
  93. <t-dropdown-item divider="true">
  94. <a @click="onRedo">
  95. <div class="flex">
  96. 恢复 <span class="flex-grow"></span> Ctrl + Y
  97. </div>
  98. </a>
  99. </t-dropdown-item>
  100. <t-dropdown-item>
  101. <a @click="onCut">
  102. <div class="flex">
  103. 剪切 <span class="flex-grow"></span> Ctrl + X
  104. </div>
  105. </a>
  106. </t-dropdown-item>
  107. <t-dropdown-item>
  108. <a @click="onCopy">
  109. <div class="flex">
  110. 复制 <span class="flex-grow"></span> Ctrl + C
  111. </div>
  112. </a>
  113. </t-dropdown-item>
  114. <t-dropdown-item divider="true">
  115. <a @click="onPaste">
  116. <div class="flex">
  117. 粘贴 <span class="flex-grow"></span> Ctrl + V
  118. </div>
  119. </a>
  120. </t-dropdown-item>
  121. <t-dropdown-item>
  122. <a @click="onAll">
  123. <div class="flex">
  124. 全选 <span class="flex-grow"></span> Ctrl + A
  125. </div>
  126. </a>
  127. </t-dropdown-item>
  128. <t-dropdown-item>
  129. <a @click="onDelete">
  130. <div class="flex">删除 <span class="flex-grow"></span> DELETE</div>
  131. </a>
  132. </t-dropdown-item>
  133. </t-dropdown-menu>
  134. </t-dropdown>
  135. <t-dropdown :minColumnWidth="180" :maxHeight="500" :delay2="[10, 150]" overlayClassName="header-dropdown">
  136. <a> 工具 </a>
  137. <t-dropdown-menu>
  138. <t-dropdown-item>
  139. <a @click="onScaleWindow">窗口大小</a>
  140. </t-dropdown-item>
  141. <t-dropdown-item>
  142. <a @click="onScaleUp">放大</a>
  143. </t-dropdown-item>
  144. <t-dropdown-item>
  145. <a @click="onScaleDown">缩小</a>
  146. </t-dropdown-item>
  147. <t-dropdown-item divider="true">
  148. <a @click="onScaleFull">100%视图</a>
  149. </t-dropdown-item>
  150. <t-dropdown-item>
  151. <a @click="showMap">
  152. <div class="flex middle">
  153. 鹰眼地图 <span class="flex-grow"></span>
  154. <!-- <t-icon v-show="map" name="check" /> -->
  155. <check-icon v-show="map" />
  156. </div>
  157. </a>
  158. </t-dropdown-item>
  159. <t-dropdown-item divider="true">
  160. <a @click="showMagnifier">
  161. <div class="flex middle">
  162. 放大镜 <span class="flex-grow"></span>
  163. <!-- <t-icon v-show="magnifier" name="check" /> -->
  164. <check-icon v-show="magnifier" />
  165. </div>
  166. </a>
  167. </t-dropdown-item>
  168. <t-dropdown-item>
  169. <a @click="onAutoAnchor">
  170. <div class="flex middle">
  171. 自动锚点 <span class="flex-grow"></span>
  172. <!-- <t-icon v-show="autoAnchor" name="check" /> -->
  173. <check-icon v-show="autoAnchor" />
  174. </div>
  175. </a>
  176. </t-dropdown-item>
  177. <t-dropdown-item divider="true">
  178. <a @click="onDisableAnchor">
  179. <div class="flex middle">
  180. 显示锚点 <span class="flex-grow"></span>
  181. <!-- <t-icon v-show="showAnchor" name="check" /> -->
  182. <check-icon v-show="showAnchor" />
  183. </div>
  184. </a>
  185. </t-dropdown-item>
  186. <t-dropdown-item>
  187. <a @click="onToggleAnchor">
  188. <div class="flex" :style="{
  189. color: showAnchor ? '' : '#4f5b75',
  190. }">
  191. 添加/删除锚点 <span class="flex-grow"></span> A
  192. </div>
  193. </a>
  194. </t-dropdown-item>
  195. </t-dropdown-menu>
  196. </t-dropdown>
  197. <t-dropdown :minColumnWidth="180" :maxHeight="500" :delay2="[10, 150]" overlayClassName="header-dropdown">
  198. <a> 帮助 </a>
  199. <t-dropdown-menu>
  200. <t-dropdown-item v-for="item in enterprise.helps" :divider="item.divider">
  201. <a :href="item.url" target="_blank">{{ item.name }}</a>
  202. </t-dropdown-item>
  203. </t-dropdown-menu>
  204. </t-dropdown>
  205. <div style="width: 148px; flex-shrink: 0"></div>
  206. <input v-model="data.name" @input="onInputName" />
  207. <a :href="enterprise.account" target="_blank">
  208. <!-- <t-icon name="home" /> -->
  209. <home-icon />
  210. 账户中心
  211. </a>
  212. <a :href="enterprise['v']" target="_blank" class="active">
  213. <!-- <t-icon name="desktop" /> -->
  214. <desktop-icon />
  215. 大屏可视化
  216. </a>
  217. <a :href="enterprise['3d']" target="_blank">
  218. <!-- <t-icon name="control-platform" /> -->
  219. <control-platform-icon />
  220. 3D可视化
  221. </a>
  222. <a :href="enterprise['2d']" target="_blank">
  223. <!-- <t-icon name="app" /> -->
  224. <app-icon />
  225. 2D可视化
  226. </a>
  227. <t-dropdown v-if="user.id" :minColumnWidth="200" :maxHeight="500" :delay2="[10, 150]"
  228. overlayClassName="custom-dropdown header">
  229. <a style="margin-left: 32px; margin-right: 12px">
  230. <t-avatar size="small" :image="user.avatarUrl ? user.avatarUrl : baseUrl + 'img/avatar.png'" />
  231. </a>
  232. <t-dropdown-menu>
  233. <t-dropdown-item divider="true">
  234. <a :href="enterprise.account">
  235. {{ user.username }}
  236. <label class="ml-16 vip-label" :style="{
  237. color: user.limit > 1 ? '#ff4000' : '#D5C781',
  238. background: user.limit > 1 ? '#ff400030' : '#D5C78133',
  239. }">VIP</label>
  240. </a>
  241. </t-dropdown-item>
  242. <t-dropdown-item divider="true">
  243. <a :href="`${enterprise.account}/v`" target="_blank"> 我的大屏 </a>
  244. </t-dropdown-item>
  245. <t-dropdown-item>
  246. <a :href="`${enterprise.account}/account/teams`" target="_blank">
  247. 我的团队
  248. </a>
  249. </t-dropdown-item>
  250. <t-dropdown-item>
  251. <a :href="`${enterprise.account}/account/info`" target="_blank">
  252. 账号信息
  253. </a>
  254. </t-dropdown-item>
  255. <t-dropdown-item divider="true">
  256. <a :href="`${enterprise.account}/account/security`" target="_blank">
  257. 安全设置
  258. </a>
  259. </t-dropdown-item>
  260. <t-dropdown-item>
  261. <a @click="logout">退出</a>
  262. </t-dropdown-item>
  263. </t-dropdown-menu>
  264. </t-dropdown>
  265. <div class="flex middle" v-else>
  266. <a class="button primary solid" style="width: 80px" :href="login()">
  267. 登录
  268. </a>
  269. </div>
  270. </div>
  271. <t-dialog v-if="payListDialog.show" v-model:visible="payListDialog.show" class="pay-dialog" header="图纸用到的企业图形库"
  272. :close-on-overlay-click="false" :top="95" :width="700" cancel-btn="跳过" confirm-btn="结算(不支持退款)" @cancel="skipPay"
  273. @close="payListDialog.show = false" @confirm="prePay">
  274. <div class="pay-line">
  275. <div></div>
  276. <div>已购买</div>
  277. <div>价格</div>
  278. </div>
  279. <div class="pay-body">
  280. <t-collapse defaultExpandAll>
  281. <t-collapse-panel v-if="[...prePayList.pngs].length" value="0" :header="data.goods[0].type">
  282. <template #headerRightContent>
  283. <t-space size="small">
  284. <t-checkbox v-model="data.goods[0].checked"></t-checkbox>
  285. </t-space>
  286. </template>
  287. <div v-for="pngPen in prePayList.pngs" class="pay-line">
  288. <div>
  289. <t-image :src="pngPen" :lazy="true" fit="contain" />
  290. {{
  291. pngPen.slice(
  292. pngPen.lastIndexOf('/') + 1,
  293. pngPen.lastIndexOf('.')
  294. )
  295. }}
  296. </div>
  297. <div>
  298. <check-icon v-show="data.purchasedList.some((item) => item.name === pngPen)" style="color: #4480f9ff" />
  299. </div>
  300. <div>
  301. {{
  302. data.purchasedList.some((item) => item.name === pngPen)
  303. ? 0
  304. : data.goods[0].unitPrice
  305. }}
  306. </div>
  307. </div>
  308. <div class="pay-tip">
  309. <p v-if="data.goods[0].checked">
  310. 已选择{{ data.goods[0].unPurchased }}个图元 小计:¥{{
  311. data.goods[0].unPurchased * data.goods[0].unitPrice
  312. }}
  313. </p>
  314. <p v-else>已选择0个图元 小计:¥0</p>
  315. </div>
  316. </t-collapse-panel>
  317. <t-collapse-panel v-if="[...prePayList.iotPens].length" value="1" :header="data.goods[1].type">
  318. <template #headerRightContent>
  319. <t-space size="small">
  320. <t-checkbox v-model="data.goods[1].checked"></t-checkbox>
  321. </t-space>
  322. </template>
  323. <div v-for="iotPen in prePayList.iotPens" class="pay-line">
  324. <div>
  325. <svg class="l-icon" aria-hidden="true">
  326. <use :xlink:href="'#' + iotPensMap[iotPen]?.icon"></use>
  327. </svg>
  328. {{ iotPensMap[iotPen]?.name || iotPen }}
  329. </div>
  330. <div>
  331. <check-icon v-show="data.purchasedList.some((item) => item.name === iotPen)" style="color: #4480f9ff" />
  332. </div>
  333. <div>
  334. {{
  335. data.purchasedList.some((item) => item.name === iotPen)
  336. ? 0
  337. : data.goods[1].unitPrice
  338. }}
  339. </div>
  340. </div>
  341. <div class="pay-tip">
  342. <p v-if="data.goods[1].checked">
  343. 已选择{{ data.goods[1].unPurchased }}个图元 小计:¥{{
  344. data.goods[1].unPurchased * data.goods[1].unitPrice
  345. }}
  346. </p>
  347. <p v-else>已选择0个图元 小计:¥0</p>
  348. </div>
  349. </t-collapse-panel>
  350. <t-collapse-panel v-if="[...prePayList.svgPens].length" value="2" :header="data.goods[2].type">
  351. <template #headerRightContent>
  352. <t-space size="small">
  353. <t-checkbox v-model="data.goods[2].checked"></t-checkbox>
  354. </t-space>
  355. </template>
  356. <template v-if="data.goods[2].count === data.goods[2].total">
  357. <div>
  358. 1套({{
  359. data.goods[2].count
  360. }}个)。【说明】检查到当前项目为老版本格式,需要购买整套SVG线性图元。
  361. </div>
  362. <div>
  363. <check-icon v-show="data.goods[2].unPurchased === 0" style="color: #4480f9ff" />
  364. </div>
  365. </template>
  366. <template v-else>
  367. <div v-for="svgPen in prePayList.svgPens" class="pay-line">
  368. <div>
  369. <t-image class="pay-svg" :src="svgPen" :lazy="true" fit="contain" />
  370. {{
  371. svgPen.slice(
  372. svgPen.lastIndexOf('/') + 1,
  373. svgPen.lastIndexOf('.')
  374. )
  375. }}
  376. </div>
  377. <div>
  378. <check-icon v-show="data.purchasedList.some((item) => item.name === svgPen)
  379. " style="color: #4480f9ff" />
  380. </div>
  381. <div>
  382. {{
  383. data.purchasedList.some((item) => item.name === svgPen)
  384. ? 0
  385. : data.goods[2].unitPrice
  386. }}
  387. </div>
  388. </div>
  389. </template>
  390. <div class="pay-tip">
  391. <p v-if="data.goods[2].checked">
  392. 已选择{{ data.goods[2].unPurchased }}个图元 小计:¥{{
  393. data.goods[2].unPurchased * data.goods[2].unitPrice
  394. }}
  395. </p>
  396. <p v-else>已选择0个图元 小计:¥0</p>
  397. </div>
  398. </t-collapse-panel>
  399. <t-collapse-panel v-if="[...prePayList.jsPens].length" value="3" :header="data.goods[3].type">
  400. <template #headerRightContent>
  401. <t-space size="small">
  402. <t-checkbox v-model="data.goods[3].checked"></t-checkbox>
  403. </t-space>
  404. </template>
  405. <div v-for="jsPen in prePayList.jsPens" class="pay-line">
  406. <div style="margin-left: 16px">{{ jsPen }}</div>
  407. <div>
  408. <check-icon v-show="data.purchasedList.some((item) => item.name === jsPen)" style="color: #4480f9ff" />
  409. </div>
  410. <div>
  411. {{
  412. data.purchasedList.some((item) => item.name === jsPen)
  413. ? 0
  414. : data.goods[3].unitPrice
  415. }}
  416. </div>
  417. </div>
  418. <div class="pay-tip">
  419. <p v-if="data.goods[3].checked">
  420. 已选择{{ data.goods[3].unPurchased }}个图元 小计:¥{{
  421. data.goods[3].unPurchased * data.goods[3].unitPrice
  422. }}
  423. </p>
  424. <p v-else>已选择0个图元 小计:¥0</p>
  425. </div>
  426. </t-collapse-panel>
  427. </t-collapse>
  428. </div>
  429. <div class="flex pay-footer">
  430. <p>企业图形库购买一次,永久使用</p>
  431. <div class="flex pay-price">
  432. 共{{ finalPrice.total }}个图元,合计
  433. <p>¥</p>
  434. <p>{{ finalPrice.price }}</p>
  435. </div>
  436. </div>
  437. </t-dialog>
  438. <t-dialog v-if="wechatPayDialog.show" v-model:visible="wechatPayDialog.show" class="pay-dialog" header="乐吾乐收银台"
  439. :close-on-overlay-click="false" :top="95" :width="700" confirm-btn="支付完成" :cancel-btn="null" @close="finishPay"
  440. @confirm="finishPay">
  441. <WechatPay :order="data.order" :code-url="data.order.codeUrl" @success="onSuccess" />
  442. </t-dialog>
  443. </template>
  444. <script lang="ts" setup>
  445. import {
  446. reactive,
  447. ref,
  448. onBeforeMount,
  449. onUnmounted,
  450. nextTick,
  451. computed,
  452. } from 'vue';
  453. import { useRouter, useRoute } from 'vue-router';
  454. import { useUser } from '@/services/user';
  455. import { MessagePlugin } from 'tdesign-vue-next';
  456. import {
  457. Meta2dBackData,
  458. dealwithFormatbeforeOpen,
  459. gotoAccount,
  460. checkData,
  461. } from '@/services/utils';
  462. import { readFile } from '@/services/file';
  463. import { compareVersion, baseVer, upgrade } from '@/services/upgrade';
  464. import { parseSvg } from '@meta2d/svg';
  465. import { Pen, getGlobalColor, isShowChild } from '@meta2d/core';
  466. import { cdn, upCdn } from '@/services/api';
  467. import JSZip from 'jszip';
  468. import axios from 'axios';
  469. import { switchTheme } from '@/services/theme';
  470. import { noLoginTip } from '@/services/utils';
  471. import {
  472. save,
  473. blank,
  474. newFile,
  475. SaveType,
  476. onScaleFull,
  477. onScaleWindow,
  478. showMagnifier,
  479. showMap,
  480. drawPen,
  481. map,
  482. magnifier,
  483. useDot,
  484. delAttrs,
  485. useAssets,
  486. } from '@/services/common';
  487. import { useEnterprise } from '@/services/enterprise';
  488. import {
  489. CheckIcon,
  490. HomeIcon,
  491. DesktopIcon,
  492. ControlPlatformIcon,
  493. AppIcon,
  494. } from 'tdesign-icons-vue-next';
  495. import {
  496. getDownloadList,
  497. getPayList,
  498. getComponentPurchased,
  499. get2dComponentJs,
  500. getTemPngs,
  501. getGoods
  502. } from '@/services/download';
  503. import { formComponents } from '@/services/defaults';
  504. import WechatPay from './WechatPay.vue';
  505. const { enterprise } = useEnterprise();
  506. const router = useRouter();
  507. const route = useRoute();
  508. const baseUrl = import.meta.env.BASE_URL || '/';
  509. // const { assets, getAssets } = useAssets();
  510. const { user, signout } = useUser();
  511. const { setDot } = useDot();
  512. const data = reactive({
  513. name: '空白文件',
  514. order: {
  515. codeUrl: '',
  516. id: '', //订单id
  517. },
  518. goods: [], //所有商品类型
  519. purchasedList: [], //已经购买的列表
  520. });
  521. onBeforeMount(async () => {
  522. // getAssets();
  523. });
  524. const logout = () => {
  525. signout();
  526. meta2d.emit('logout');
  527. };
  528. const onInputName = () => {
  529. (meta2d.store.data as Meta2dBackData).name = data.name;
  530. setDot();
  531. };
  532. const initMeta2dName = () => {
  533. data.name = (meta2d.store.data as Meta2dBackData).name || '';
  534. };
  535. let downloadList = new Set<any>();
  536. let iframeNum = 0;
  537. let compareNum = 0;
  538. const prePayList = reactive({
  539. pngs: new Set<string>(),
  540. jsPens: new Set<string>(),
  541. iotPens: new Set<string>(),
  542. svgPens: new Set<string>(),
  543. });
  544. let payListNum = 0;
  545. let comparePayListNum = 0;
  546. // let purchasedList = []; //已购买列表
  547. const iotPensMap = {};
  548. let downloadType: Frame = null;
  549. // const payPrice = ref(0);
  550. const getIotPensMap = () => {
  551. formComponents.forEach((item) => {
  552. item.list.forEach((_item) => {
  553. iotPensMap[_item.data.name] = { name: _item.name, icon: _item.icon };
  554. });
  555. });
  556. };
  557. nextTick(() => {
  558. meta2d.on('opened', initMeta2dName);
  559. window.addEventListener('message', dealWithMessage);
  560. getIotPensMap();
  561. });
  562. const dealWithMessage = async (e) => {
  563. if (
  564. typeof e.data !== 'string' ||
  565. !e.data ||
  566. e.data.startsWith('setImmediate')
  567. ) {
  568. return;
  569. }
  570. try {
  571. let data = JSON.parse(e.data);
  572. if (typeof data === 'object') {
  573. if (data.type) {
  574. if (data.name === 'download') {
  575. downloadList = new Set([...downloadList, ...data.data]);
  576. compareNum += 1;
  577. if (compareNum >= iframeNum) {
  578. saveDownload();
  579. }
  580. } else if (data.name === 'payList') {
  581. prePayList.pngs = new Set([...prePayList.pngs, ...data.data.pngs]);
  582. prePayList.jsPens = new Set([
  583. ...prePayList.jsPens,
  584. ...data.data.jsPens,
  585. ]);
  586. prePayList.iotPens = new Set([
  587. ...prePayList.iotPens,
  588. ...data.data.iotPens,
  589. ]);
  590. prePayList.svgPens = new Set([
  591. ...prePayList.svgPens,
  592. ...data.data.svgPens,
  593. ]);
  594. comparePayListNum += 1;
  595. if (comparePayListNum >= payListNum) {
  596. if (
  597. ![...prePayList.pngs].length &&
  598. ![...prePayList.jsPens].length &&
  599. ![...prePayList.iotPens].length &&
  600. ![...prePayList.svgPens].length
  601. ) {
  602. //直接下载
  603. if (downloadType === Frame.html) {
  604. preDownload(meta2d.data());
  605. } else {
  606. preFrameDownload(downloadType);
  607. }
  608. } else {
  609. await showPayListDialog();
  610. }
  611. }
  612. }
  613. } else {
  614. meta2d.emit(data.name);
  615. }
  616. } else {
  617. meta2d.emit(data);
  618. }
  619. } catch (e) {
  620. console.info(e);
  621. }
  622. };
  623. onUnmounted(() => {
  624. meta2d.off('opened', initMeta2dName);
  625. window.removeEventListener('message', dealWithMessage);
  626. });
  627. const login = () => {
  628. return `${enterprise['account']}?cb=${encodeURIComponent(location.href)}`;
  629. };
  630. function load(isNew = false) {
  631. const input = document.createElement('input');
  632. input.type = 'file';
  633. input.onchange = (event) => {
  634. const elem = event.target as HTMLInputElement;
  635. if (elem.files && elem.files[0]) {
  636. // 路由跳转 可能在 openFile 后执行
  637. if (elem.files[0].name.endsWith('.json')) {
  638. blank();
  639. openJson(elem.files[0]);
  640. if (isNew) {
  641. router.push({
  642. path: '/',
  643. query: {
  644. r: Date.now() + '',
  645. },
  646. });
  647. }
  648. } else if (elem.files[0].name.endsWith('.svg')) {
  649. MessagePlugin.info(
  650. '可二次编辑但转换存在损失,若作为图片使用,请使用右侧属性面板的上传图片功能'
  651. );
  652. openSvg(elem.files[0]);
  653. } else if (elem.files[0].name.endsWith('.zip')) {
  654. blank();
  655. router.push({
  656. path: '/',
  657. query: {
  658. r: Date.now() + '',
  659. },
  660. });
  661. setTimeout(() => {
  662. openZip(elem.files[0]);
  663. }, 500);
  664. } else {
  665. MessagePlugin.info('打开文件只支持 json,svg,zip 格式');
  666. }
  667. }
  668. };
  669. input.click();
  670. }
  671. const openJson = async (file: File) => {
  672. const text = await readFile(file);
  673. try {
  674. let data: Meta2dBackData = JSON.parse(text);
  675. if (!data.name) {
  676. data.name = file.name.replace('.json', '');
  677. }
  678. if (!data.version || compareVersion(data.version, baseVer) === -1) {
  679. // 如果版本号不存在或者版本号 version < 1.0.0
  680. data = upgrade(data, baseVer);
  681. }
  682. dealwithFormatbeforeOpen(data);
  683. for (const k of delAttrs) {
  684. delete (data as any)[k];
  685. }
  686. meta2d.open(data);
  687. } catch (e) {
  688. console.error(e);
  689. }
  690. };
  691. const openSvg = async (file: File) => {
  692. const text = await readFile(file);
  693. const pens: Pen[] = parseSvg(text);
  694. meta2d.canvas.addCaches = pens;
  695. MessagePlugin.info('svg转换成功,请点击画布决定放置位置');
  696. };
  697. const openZip = async (file: File) => {
  698. if (!(user && user.id)) {
  699. MessagePlugin.warning(noLoginTip);
  700. return;
  701. }
  702. // if (!user.isVip) {
  703. // gotoAccount();
  704. // return;
  705. // }
  706. if (!user.vip) {
  707. MessagePlugin.info('需要开通普通会员~');
  708. gotoAccount();
  709. return;
  710. }
  711. const { default: JSZip } = await import('jszip');
  712. const zip = new JSZip();
  713. await zip.loadAsync(file);
  714. let dataStr = '';
  715. for (const key in zip.files) {
  716. if (zip.files[key].dir) {
  717. continue;
  718. }
  719. if (key.endsWith('.json')) {
  720. // 认为只有一个 json 文件
  721. // dataStr = await zip.file(key).async('string');
  722. break;
  723. }
  724. }
  725. if (!dataStr) {
  726. return false;
  727. }
  728. for (const key in zip.files) {
  729. if (zip.files[key].dir) {
  730. continue;
  731. }
  732. // let _png = key.indexOf('/png');
  733. // let _img = key.indexOf('/img');
  734. // let _image = key.indexOf('/image');
  735. // let _file = key.indexOf('/file');
  736. let _keyLower = key.toLowerCase();
  737. // if (!key.endsWith('.json') && (_png !== -1 || _img !== -1 || _image !== -1 || _file !== -1)) {
  738. if (
  739. _keyLower.endsWith('.png') ||
  740. _keyLower.endsWith('.svg') ||
  741. _keyLower.endsWith('.gif') ||
  742. _keyLower.endsWith('.jpg') ||
  743. _keyLower.endsWith('.jpeg')
  744. ) {
  745. let filename = key.substr(key.lastIndexOf('/') + 1);
  746. const extPos = filename.lastIndexOf('.');
  747. let ext = '';
  748. if (extPos > 0) {
  749. ext = filename.substr(extPos);
  750. }
  751. filename = filename.substring(0, extPos > 8 ? 8 : extPos);
  752. // 上传文件
  753. const result: any = {};
  754. // await upload(
  755. // // await zip.file(key).async('blob'),
  756. // true,
  757. // filename + ext,
  758. // "/2D/默认"
  759. // );
  760. let _key = key;
  761. // if (_png) {
  762. // _key = key.substring(_png);
  763. // } else if (_image) {
  764. // _key = key.substring(_png);
  765. // } else if (_img) {
  766. // _key = key.substring(_img);
  767. // } else if (_file) {
  768. // _key = key.substring(_file);
  769. // }
  770. if (result) {
  771. if (dataStr.replaceAll) {
  772. //'le5le.meta2d'
  773. dataStr = dataStr.replaceAll(_key.slice(12), result.url);
  774. } else {
  775. while (dataStr.includes(_key)) {
  776. dataStr = dataStr.replace(_key.slice(12), result.url);
  777. // 正则 gm 在特殊情况下报错,例如如下场景
  778. /**
  779. *    
  780. const data = '{"image":"/image/materials/IoT-Chemical(化学)/Air stripper 2(汽提塔2).svg"}';
  781. const key = '/image/materials/IoT-Chemical(化学)/Air stripper 2(汽提塔2).svg';
  782. data.replace(key, '123');
  783. data.replaceAll(key, '123')
  784. data.replace(new RegExp(key, 'gm'), '123');
  785. data.replace(new RegExp(key, 'g'), '123');
  786. */
  787. }
  788. }
  789. }
  790. }
  791. }
  792. try {
  793. let data: Meta2dBackData = JSON.parse(dataStr);
  794. if (data) {
  795. if (!data.name) {
  796. data.name = file.name.replace('.zip', '');
  797. }
  798. if (!data.version || compareVersion(data.version, baseVer) === -1) {
  799. // 如果版本号不存在或者版本号 version < 1.0.0
  800. data = upgrade(data, baseVer);
  801. }
  802. dealwithFormatbeforeOpen(data);
  803. const delAttrs = [
  804. 'userId',
  805. 'shared',
  806. 'team',
  807. 'owner',
  808. 'username',
  809. 'editor',
  810. 'editorId',
  811. 'editorName',
  812. 'createdAt',
  813. 'folder',
  814. 'image',
  815. 'id',
  816. '_id',
  817. 'view',
  818. 'updatedAt',
  819. 'star',
  820. 'recommend',
  821. ];
  822. for (const k of delAttrs) {
  823. delete (data as any)[k];
  824. }
  825. meta2d.open(data);
  826. }
  827. } catch (e) {
  828. return false;
  829. }
  830. };
  831. const downloadJson = () => {
  832. const data: Meta2dBackData = meta2d.data();
  833. if (data._id) delete data._id;
  834. if (data.id) delete data.id;
  835. checkData(data);
  836. import('file-saver').then(({ saveAs }) => {
  837. saveAs(
  838. new Blob(
  839. [JSON.stringify(data).replaceAll(cdn, '').replaceAll(upCdn, '')],
  840. {
  841. type: 'text/plain;charset=utf-8',
  842. }
  843. ),
  844. `${data.name || 'le5le.meta2d'}.json`
  845. );
  846. });
  847. };
  848. const downloadZip = async () => {
  849. if (!(user && user.id)) {
  850. MessagePlugin.warning(noLoginTip);
  851. return;
  852. }
  853. // if (!user.isVip) {
  854. // // gotoAccount();
  855. // return;
  856. // }
  857. if (!user.vip) {
  858. MessagePlugin.info('需要开通普通会员~');
  859. gotoAccount();
  860. return;
  861. }
  862. MessagePlugin.info('正在下载打包中,可能需要几分钟,请耐心等待...');
  863. const [{ default: JSZip }, { saveAs }] = await Promise.all([
  864. import('jszip'),
  865. import('file-saver'),
  866. ]);
  867. const zip: any = new JSZip();
  868. const data: Meta2dBackData = meta2d.data();
  869. let _fileName =
  870. (data.name && data.name.replace(/\//g, '_').replace(/:/g, '_')) ||
  871. 'le5le.meta2d';
  872. const _zip = zip.folder(`${_fileName}`);
  873. if (data._id) delete data._id;
  874. if (data.id) delete data.id;
  875. checkData(data);
  876. _zip.file(
  877. `${_fileName}.json`,
  878. JSON.stringify(data).replaceAll(cdn, '').replaceAll(upCdn, '')
  879. );
  880. await zipBkImg(_zip);
  881. await zipImages(_zip, meta2d.store.data.pens);
  882. const blob = await zip.generateAsync({ type: 'blob' });
  883. saveAs(blob, `${_fileName}.zip`);
  884. };
  885. const zip3D = (name: string) => {
  886. const pen_3d = meta2d.store.data.pens.filter(
  887. (pen) =>
  888. pen.name === 'iframe' &&
  889. (pen.tags.includes('meta3d') || pen.iframe.indexOf('3d') !== -1)
  890. );
  891. if (pen_3d && pen_3d.length) {
  892. //存在3d场景
  893. pen_3d.forEach((pen) => {
  894. //发送消息
  895. let params = queryURLParams(pen.iframe.split('?')[1]);
  896. (
  897. pen.calculative.singleton.div.children[0] as HTMLIFrameElement
  898. ).contentWindow.postMessage(
  899. JSON.stringify({
  900. type: 1,
  901. name,
  902. id: params.id,
  903. }),
  904. '*'
  905. );
  906. });
  907. }
  908. };
  909. const zip2D = (name: string) => {
  910. const pen_2d = meta2d.store.data.pens.filter(
  911. (pen) =>
  912. pen.name === 'iframe' &&
  913. (pen.iframe.indexOf('2d.le5le.com') !== -1 ||
  914. pen.iframe.indexOf('/2d/') !== -1 ||
  915. pen.iframe.indexOf('v.le5le.com') !== -1 ||
  916. pen.iframe.indexOf('/v/') !== -1)
  917. );
  918. if (pen_2d && pen_2d.length) {
  919. //存在3d场景
  920. pen_2d.forEach((pen) => {
  921. //发送消息
  922. // let params = queryURLParams(pen.iframe.split('?')[1]);
  923. (
  924. pen.calculative.singleton.div.children[0] as HTMLIFrameElement
  925. ).contentWindow.postMessage(
  926. JSON.stringify({
  927. name,
  928. type: 1,
  929. }),
  930. '*'
  931. );
  932. });
  933. }
  934. }
  935. function queryURLParams(value?: string) {
  936. let url = value || window.location.href.split('?')[1];
  937. const urlSearchParams = new URLSearchParams(url);
  938. const params = Object.fromEntries(urlSearchParams.entries());
  939. return params;
  940. }
  941. const downloadHtml = async () => {
  942. if (!(user && user.id)) {
  943. MessagePlugin.warning(noLoginTip);
  944. return;
  945. }
  946. if (user.vipDesc !== '超级会员' && user.vipDesc !== '旗舰会员') {
  947. MessagePlugin.info('需要开通超级会员~');
  948. gotoAccount();
  949. return;
  950. }
  951. downloadType = Frame.html;
  952. await preGetPayList(Frame.html);
  953. };
  954. const preGetPayList = async (key: Frame) => {
  955. //图形库需要购买
  956. const meta2dData = meta2d.data();
  957. let list = getPayList(meta2dData);
  958. prePayList.pngs = new Set(list.pngs);
  959. prePayList.jsPens = new Set(list.jsPens);
  960. prePayList.iotPens = new Set(list.iotPens);
  961. prePayList.svgPens = new Set(list.svgPens);
  962. //向iframe发送消息
  963. payListNum = 0;
  964. comparePayListNum = 0;
  965. const pen_pay = meta2dData.pens.filter(
  966. (pen) =>
  967. pen.name === 'iframe' &&
  968. (pen.iframe.indexOf('2d.le5le.com') !== -1 ||
  969. pen.iframe.indexOf('/2d/') !== -1 ||
  970. pen.iframe.indexOf('v.le5le.com') !== -1 ||
  971. pen.iframe.indexOf('/v/') !== -1 ||
  972. pen.iframe.indexOf('/preview') !== -1)
  973. );
  974. if (pen_pay && pen_pay.length) {
  975. //存在3d场景
  976. pen_pay.forEach((pen) => {
  977. //发送消息
  978. (
  979. meta2d.store.pens[pen.id].calculative.singleton.div
  980. .children[0] as HTMLIFrameElement
  981. ).contentWindow.postMessage(
  982. JSON.stringify({
  983. name: 'prePayList',
  984. type: 1,
  985. }),
  986. '*'
  987. );
  988. payListNum += 1;
  989. });
  990. }
  991. if (payListNum === 0) {
  992. //无嵌入页面
  993. if (
  994. !list.pngs.length &&
  995. !list.jsPens.length &&
  996. !list.iotPens.length &&
  997. !list.svgPens.length
  998. ) {
  999. if (key === Frame.html) {
  1000. preDownload(meta2dData);
  1001. } else {
  1002. preFrameDownload(key);
  1003. }
  1004. } else {
  1005. await showPayListDialog();
  1006. }
  1007. }
  1008. }
  1009. const showPayListDialog = async () => {
  1010. data.goods = await getGoods();
  1011. data.purchasedList = await getComponentPurchased(prePayList);
  1012. data.goods.forEach((item, index) => {
  1013. item.checked = true;
  1014. let purchased = data.purchasedList?.filter(
  1015. (_item) => _item.type === item.type
  1016. );
  1017. if (index === 0) {
  1018. item.total = [...prePayList.pngs].length;
  1019. item.unPurchased = item.total - purchased.length;
  1020. } else if (index === 1) {
  1021. item.total = [...prePayList.iotPens].length;
  1022. item.unPurchased = item.total - purchased.length;
  1023. } else if (index === 3) {
  1024. item.total = [...prePayList.jsPens].length;
  1025. item.unPurchased = item.total - purchased.length;
  1026. } else if (index === 2) {
  1027. if (purchased.length === 1 && !purchased[0].name) {
  1028. //说明已经购买全部 //TODO
  1029. item.total = item.count;
  1030. item.unPurchased = 0;
  1031. } else {
  1032. //需要购买全部
  1033. if ([...prePayList.svgPens].includes('*')) {
  1034. item.total = item.count;
  1035. item.unPurchased = item.total;
  1036. } else {
  1037. item.total = [...prePayList.svgPens].length;
  1038. item.unPurchased = item.total - purchased.length;
  1039. }
  1040. }
  1041. }
  1042. });
  1043. let price = 0;
  1044. data.goods.forEach((item, index) => {
  1045. if (item.checked) {
  1046. price += item.unPurchased * item.unitPrice;
  1047. }
  1048. });
  1049. if (price === 0) {
  1050. skipPay();//如果计算价格为0,直接下载
  1051. } else {
  1052. payListDialog.show = true;
  1053. }
  1054. };
  1055. const preDownload = (meta2dData: any) => {
  1056. MessagePlugin.info('正在下载打包中,可能需要几分钟,请耐心等待...');
  1057. iframeNum = 0;
  1058. compareNum = 0;
  1059. meta2dData.userId = user.id;
  1060. const pen_3d = meta2dData.pens.filter(
  1061. (pen) =>
  1062. pen.name === 'iframe' &&
  1063. (pen.iframe.indexOf('3d.le5le.com') !== -1 ||
  1064. pen.iframe.indexOf('/3d/') !== -1)
  1065. );
  1066. if (pen_3d && pen_3d.length) {
  1067. //存在3d场景
  1068. if (pen_3d.length === 1) {
  1069. let params = queryURLParams(pen_3d[0].iframe.split('?')[1]);
  1070. meta2d.store.pens[
  1071. pen_3d[0].id
  1072. ].calculative.singleton.div.children[0].contentWindow.postMessage(
  1073. JSON.stringify({
  1074. name: 'deploy',
  1075. // id: params.id,
  1076. type: 1, //用于区分是系统消息
  1077. path: `3d`,
  1078. }),
  1079. '*'
  1080. );
  1081. pen_3d[0].iframe = '/view?data=3d';
  1082. } else {
  1083. pen_3d.forEach((pen) => {
  1084. //发送消息
  1085. let params = queryURLParams(pen.iframe.split('?')[1]);
  1086. (
  1087. meta2d.store.pens[pen.id].calculative.singleton.div
  1088. .children[0] as HTMLIFrameElement
  1089. ).contentWindow.postMessage(
  1090. JSON.stringify({
  1091. name: 'deploy',
  1092. // id: params.id,
  1093. type: 1,
  1094. path: `3d-${params.id}`,
  1095. }),
  1096. '*'
  1097. );
  1098. pen.iframe = `/view?data=3d-${params.id}`;
  1099. });
  1100. }
  1101. iframeNum += pen_3d.length;
  1102. }
  1103. const pen_2d = meta2dData.pens.filter(
  1104. (pen) =>
  1105. pen.name === 'iframe' &&
  1106. (pen.iframe.indexOf('2d.le5le.com') !== -1 ||
  1107. pen.iframe.indexOf('/2d/') !== -1)
  1108. );
  1109. if (pen_2d && pen_2d.length) {
  1110. //存在3d场景
  1111. if (pen_2d.length === 1) {
  1112. let params = queryURLParams(pen_2d[0].iframe.split('?')[1]);
  1113. meta2d.store.pens[
  1114. pen_2d[0].id
  1115. ].calculative.singleton.div.children[0].contentWindow.postMessage(
  1116. JSON.stringify({
  1117. name: 'downloadHtml',
  1118. id: params.id,
  1119. type: 1,
  1120. path: `2d`,
  1121. }),
  1122. '*'
  1123. );
  1124. pen_2d[0].iframe = '/view?data=2d';
  1125. } else {
  1126. pen_2d.forEach((pen) => {
  1127. //发送消息
  1128. let params = queryURLParams(pen.iframe.split('?')[1]);
  1129. (
  1130. meta2d.store.pens[pen.id].calculative.singleton.div
  1131. .children[0] as HTMLIFrameElement
  1132. ).contentWindow.postMessage(
  1133. JSON.stringify({
  1134. name: 'downloadHtml',
  1135. // id: params.id,
  1136. type: 1,
  1137. path: `2d-${params.id}`,
  1138. }),
  1139. '*'
  1140. );
  1141. pen.iframe = `/view?data=2d-${params.id}`;
  1142. });
  1143. }
  1144. iframeNum += pen_2d.length;
  1145. }
  1146. const pen_v = meta2dData.pens.filter(
  1147. (pen) =>
  1148. pen.name === 'iframe' &&
  1149. (pen.iframe.indexOf('v.le5le.com') !== -1 ||
  1150. pen.iframe.indexOf('/v/') !== -1)
  1151. );
  1152. if (pen_v && pen_v.length) {
  1153. //存在3d场景
  1154. pen_v.forEach((pen) => {
  1155. //发送消息
  1156. let params = queryURLParams(pen.iframe.split('?')[1]);
  1157. (
  1158. meta2d.store.pens[pen.id].calculative.singleton.div
  1159. .children[0] as HTMLIFrameElement
  1160. ).contentWindow.postMessage(
  1161. JSON.stringify({
  1162. name: 'downloadHtml',
  1163. // id: params.id,
  1164. type: 1,
  1165. path: `v-${params.id}`,
  1166. }),
  1167. '*'
  1168. );
  1169. pen.iframe = `/view?data=v-${params.id}`;
  1170. });
  1171. iframeNum += pen_v.length;
  1172. }
  1173. downloadList = getDownloadList(meta2dData);
  1174. if (iframeNum === 0) {
  1175. //如果没有嵌入场景
  1176. saveDownload();
  1177. } else {
  1178. setTimeout(() => {
  1179. if (compareNum < iframeNum) {
  1180. //message阻塞/报错的情况
  1181. saveDownload();
  1182. }
  1183. }, 10000);
  1184. }
  1185. };
  1186. const saveDownload = async () => {
  1187. const list = [...downloadList];
  1188. //控件
  1189. const js = await get2dComponentJs([...prePayList.iotPens]);
  1190. list.push({
  1191. data: js,
  1192. path: '/view/js/2d-components.js',
  1193. });
  1194. ///png图形库
  1195. const pngs = await getTemPngs([...prePayList.pngs]);
  1196. list.forEach((item) => {
  1197. if (pngs[item.url]) {
  1198. item.url = pngs[item.url];
  1199. }
  1200. });
  1201. //js线性图元 由对应页面处理
  1202. //SVG线性图元
  1203. if ([...prePayList.svgPens].length) {
  1204. let purchased = data.purchasedList?.filter(
  1205. (_item) => _item.type === 'SVG线性图元'
  1206. );
  1207. if (purchased.length === 1 && !purchased[0].name) {
  1208. //已经购买全部
  1209. list.forEach((item) => {
  1210. if (item.data && item.path.indexOf('/projects/v') !== 1) {
  1211. //清空所有svgpath
  1212. let meta2dData = JSON.parse(item.data);
  1213. for (let key of Object.keys(meta2dData.paths)) {
  1214. let path = meta2dData.paths[key];
  1215. if (
  1216. path.indexOf('-1.18Zm4-1') !== -1 ||
  1217. path.indexOf('-1.19Zm4-1') !== -1 ||
  1218. path.indexOf('2.85ZM') !== -1 ||
  1219. path.indexOf('-1-2.39.3') !== -1
  1220. ) {
  1221. meta2dData.paths[key] = '';
  1222. }
  1223. }
  1224. item.data = JSON.stringify(meta2dData);
  1225. }
  1226. });
  1227. } else {
  1228. list.forEach((item) => {
  1229. if (item.data && item.path.indexOf('/projects/v') !== -1 && item.path.indexOf('/projects/v/png/') === -1) {
  1230. //2d 图纸数据
  1231. let meta2dData = JSON.parse(item.data);
  1232. meta2dData.pens.forEach((pen) => {
  1233. if (pen.name === 'svgPath' && pen.svgUrl) {
  1234. if ([...prePayList.svgPens].includes(pen.svgUrl)) {
  1235. pen.pathId = null;
  1236. }
  1237. }
  1238. });
  1239. item.data = JSON.stringify(meta2dData);
  1240. }
  1241. });
  1242. }
  1243. }
  1244. //开始下载list
  1245. const [{ default: JSZip }, { saveAs }] = await Promise.all([
  1246. import('jszip'),
  1247. import('file-saver'),
  1248. ]);
  1249. const zip = new JSZip();
  1250. await Promise.all(
  1251. list.map(async (item: any) => {
  1252. if (item.url) {
  1253. //接口请求
  1254. const res: Blob = await axios.get(item.url, {
  1255. responseType: 'blob',
  1256. });
  1257. zip.file(item.path, res, { createFolders: true });
  1258. } else if (item.data) {
  1259. //直接写数据
  1260. zip.file(item.path, item.data, { createFolders: true });
  1261. }
  1262. })
  1263. );
  1264. let _fileName =
  1265. (meta2d.store.data.name &&
  1266. meta2d.store.data.name.replace(/\//g, '_').replace(/:/g, '_')) ||
  1267. 'le5le.meta2d';
  1268. const blob = await zip.generateAsync({ type: 'blob' });
  1269. saveAs(blob, `${_fileName}.zip`);
  1270. };
  1271. const _downloadHtml = async () => {
  1272. if (!(user && user.id)) {
  1273. MessagePlugin.warning(noLoginTip);
  1274. return;
  1275. }
  1276. // if (!user.isVip) {
  1277. // gotoAccount();
  1278. // return;
  1279. // }
  1280. if (user.vipDesc !== '超级会员' && user.vipDesc !== '旗舰会员') {
  1281. MessagePlugin.info('需要开通超级会员~');
  1282. gotoAccount();
  1283. return;
  1284. }
  1285. frameFlag = -1;
  1286. MessagePlugin.info('正在下载打包中,可能需要几分钟,请耐心等待...');
  1287. zip3D('deploy');
  1288. const data: Meta2dBackData = meta2d.data();
  1289. if (data._id) delete data._id;
  1290. if (data.id) delete data.id;
  1291. if (data.image) delete data.image;
  1292. checkData(data);
  1293. const [{ default: JSZip }, { saveAs }] = await Promise.all([
  1294. import('jszip'),
  1295. import('file-saver'),
  1296. ]);
  1297. const zip = new JSZip();
  1298. let _fileName =
  1299. (data.name && data.name.replace(/\//g, '_').replace(/:/g, '_')) ||
  1300. 'le5le.meta2d';
  1301. //处理cdn图片地址
  1302. const _zip: any = zip.folder(`${_fileName}`);
  1303. _zip.file(
  1304. 'data.json',
  1305. JSON.stringify(data).replaceAll(cdn, '').replaceAll(upCdn, '')
  1306. );
  1307. await Promise.all([
  1308. zipBkImg(_zip),
  1309. zipImages(_zip, meta2d.store.data.pens),
  1310. zipFiles(_zip),
  1311. ]);
  1312. const blob = await zip.generateAsync({ type: 'blob' });
  1313. saveAs(blob, `${_fileName}.zip`);
  1314. };
  1315. async function zipBkImg(zip: JSZip) {
  1316. let img = meta2d.store.data.bkImage;
  1317. if (img) {
  1318. if (img.startsWith('/') || img.startsWith(cdn) || img.startsWith(upCdn)) {
  1319. const pngs = await getTemPngs([img.replace(cdn, '').replace(upCdn, '')]);
  1320. await zipImage(zip, img, pngs[img.replace(cdn, '').replace(upCdn, '')]);
  1321. }
  1322. }
  1323. }
  1324. enum Frame {
  1325. vue2,
  1326. vue3,
  1327. react,
  1328. html
  1329. }
  1330. const downloadVue3 = async () => {
  1331. downloadAsFrame(Frame.vue3);
  1332. };
  1333. const downloadVue2 = async () => {
  1334. downloadAsFrame(Frame.vue2);
  1335. };
  1336. const downloadReact = async () => {
  1337. downloadAsFrame(Frame.react);
  1338. };
  1339. let frameFlag = -1;
  1340. async function downloadAsFrame(type: Frame) {
  1341. if (!(user && user.id)) {
  1342. MessagePlugin.warning(noLoginTip);
  1343. return;
  1344. }
  1345. if (user.vipDesc !== '旗舰会员') {
  1346. MessagePlugin.info('需要开通旗舰会员~');
  1347. gotoAccount();
  1348. return;
  1349. }
  1350. downloadType = type;
  1351. await preGetPayList(type);
  1352. }
  1353. const preFrameDownload = async (type: Frame) => {
  1354. frameFlag = type;
  1355. MessagePlugin.info('正在下载打包中,可能需要几分钟,请耐心等待...');
  1356. zip3D(
  1357. type === Frame.vue3 ? 'toVue3' : type === Frame.vue2 ? 'toVue2' : 'toReact'
  1358. );
  1359. zip2D(
  1360. type === Frame.vue3 ? 'downloadVue3' : type === Frame.vue2 ? 'downloadVue2' : 'downloadReact'
  1361. );
  1362. const data: any = meta2d.data();
  1363. if (data._id) delete data._id;
  1364. if (data.id) delete data.id;
  1365. if (data.image) delete data.image;
  1366. data.userId = user.id;
  1367. checkData(data);
  1368. const [{ default: JSZip }, { saveAs }] = await Promise.all([
  1369. import('jszip'),
  1370. import('file-saver'),
  1371. ]);
  1372. const zip = new JSZip();
  1373. let _fileName =
  1374. (data.name && data.name.replace(/\//g, '_').replace(/:/g, '_')) ||
  1375. 'le5le.meta2d';
  1376. //处理付费svg
  1377. if (Object.keys(data.paths).length >= 3) {
  1378. //简单判断有无svg图元
  1379. const res: any = await axios.post('/api/paid/2d/component', {
  1380. type: 'SVG线性图元',
  1381. });
  1382. if (res.list.length === 1 && !res.list[0].name) {
  1383. //已经购买全部
  1384. for (let key of Object.keys(data.paths)) {
  1385. let path = data.paths[key];
  1386. if (
  1387. path.indexOf('-1.18Zm4-1') !== -1 ||
  1388. path.indexOf('-1.19Zm4-1') !== -1 ||
  1389. path.indexOf('2.85ZM') !== -1 ||
  1390. path.indexOf('-1-2.39.3') !== -1
  1391. ) {
  1392. data.paths[key] = '';
  1393. }
  1394. }
  1395. } else {
  1396. //购买部分
  1397. let purchasedList = res.list.map((i) => i.name);
  1398. data.pens.forEach((pen) => {
  1399. if (pen.name === 'svgPath' && pen.svgUrl) {
  1400. if (purchasedList.includes(pen.svgUrl)) {
  1401. pen.pathId = null;
  1402. }
  1403. }
  1404. });
  1405. }
  1406. }
  1407. const _zip: any = zip.folder(`${_fileName}`);
  1408. _zip.file(
  1409. `${type === Frame.vue3
  1410. ? 'meta2d-vue3'
  1411. : type === Frame.vue2
  1412. ? 'meta2d-vue2'
  1413. : 'meta2d-react'
  1414. }/public/json/data.json`,
  1415. JSON.stringify(data).replaceAll(cdn, '').replaceAll(upCdn, '')
  1416. );
  1417. await Promise.all([
  1418. zipJs(_zip),
  1419. zipBkImg(_zip),
  1420. zipImages(_zip, meta2d.store.data.pens),
  1421. type === Frame.vue3
  1422. ? zipVue3Files(_zip)
  1423. : type === Frame.vue2
  1424. ? zipVue2Files(_zip)
  1425. : zipReactFiles(_zip),
  1426. zipIotPens(_zip),
  1427. ]);
  1428. const blob = await zip.generateAsync({ type: 'blob' });
  1429. saveAs(blob, `${_fileName}.zip`);
  1430. frameFlag = -1;
  1431. }
  1432. async function zipIotPens(zip: JSZip) {
  1433. //处理控件
  1434. const js = await get2dComponentJs();
  1435. zip.file(`${frameFlag === Frame.vue3
  1436. ? 'meta2d-vue3'
  1437. : frameFlag === Frame.vue2
  1438. ? 'meta2d-vue2'
  1439. : 'meta2d-react'
  1440. }/public/js/2d-components.js`, js, { createFolders: true });
  1441. const res: Blob = await axios.get('/view/js/r.js', {
  1442. responseType: 'blob',
  1443. });
  1444. zip.file(`${frameFlag === Frame.vue3
  1445. ? 'meta2d-vue3'
  1446. : frameFlag === Frame.vue2
  1447. ? 'meta2d-vue2'
  1448. : 'meta2d-react'
  1449. }/public/js/r.js`, res, { createFolders: true });
  1450. }
  1451. async function zipJs(zip: JSZip) {
  1452. const files = ['/view/js/marked.min.js', '/view/js/lcjs.iife.js'];
  1453. await Promise.all(
  1454. files.map(async (filePath) => {
  1455. const res: Blob = await axios.get(filePath, {
  1456. responseType: 'blob',
  1457. });
  1458. zip.file(
  1459. `${frameFlag === Frame.vue3
  1460. ? 'meta2d-vue3'
  1461. : frameFlag === Frame.vue2
  1462. ? 'meta2d-vue2'
  1463. : 'meta2d-react'
  1464. }/public` + filePath.replace('/view', ''),
  1465. res,
  1466. { createFolders: true }
  1467. );
  1468. })
  1469. );
  1470. }
  1471. async function _zipVue3Files(zip: JSZip) {
  1472. const files = [
  1473. '/view/js/marked.min.js',
  1474. '/view/js/lcjs.iife.js',
  1475. '/view/vue3/Meta2d.vue',
  1476. '/view/index.html',
  1477. '/view/js/meta2d.js',
  1478. '/view/使用说明.md',
  1479. ] as const;
  1480. // 文件同时加载
  1481. await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
  1482. }
  1483. //新
  1484. async function zipVue3Files(zip: JSZip) {
  1485. const files = [
  1486. '/view/meta2d-vue3/src/components/Meta2d.vue',
  1487. '/view/meta2d-vue3/src/App.vue',
  1488. '/view/meta2d-vue3/src/main.js',
  1489. '/view/meta2d-vue3/src/style.css',
  1490. '/view/meta2d-vue3/index.html',
  1491. '/view/meta2d-vue3/package.json',
  1492. '/view/meta2d-vue3/README.md',
  1493. '/view/meta2d-vue3/vite.config.js',
  1494. ] as const;
  1495. // 文件同时加载
  1496. await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
  1497. }
  1498. async function _zipVue2Files(zip: JSZip) {
  1499. const files = [
  1500. '/view/js/marked.min.js',
  1501. '/view/js/lcjs.iife.js',
  1502. '/view/vue2/Meta2d.vue',
  1503. '/view/index.html',
  1504. '/view/js/meta2d.js',
  1505. '/view/使用说明.md',
  1506. ] as const;
  1507. // 文件同时加载
  1508. await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
  1509. }
  1510. async function zipVue2Files(zip: JSZip) {
  1511. const files = [
  1512. '/view/meta2d-vue2/src/components/Meta2d.vue',
  1513. '/view/meta2d-vue2/src/App.vue',
  1514. '/view/meta2d-vue2/src/main.js',
  1515. // '/view/meta2d-vue2/src/style.css',
  1516. '/view/meta2d-vue2/public/index.html',
  1517. '/view/meta2d-vue2/package.json',
  1518. '/view/meta2d-vue2/README.md',
  1519. // '/view/meta2d-vue3/vite.config.js',
  1520. ] as const;
  1521. // 文件同时加载
  1522. await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
  1523. }
  1524. async function _zipReactFiles(zip: JSZip) {
  1525. const files = [
  1526. '/view/js/marked.min.js',
  1527. '/view/js/lcjs.iife.js',
  1528. '/view/react/Meta2d.jsx',
  1529. '/view/react/Meta2d.css',
  1530. '/view/index.html',
  1531. '/view/js/meta2d.js',
  1532. '/view/使用说明.md',
  1533. ] as const;
  1534. // 文件同时加载
  1535. await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
  1536. }
  1537. async function zipReactFiles(zip: JSZip) {
  1538. const files = [
  1539. '/view/meta2d-react/src/index.css',
  1540. '/view/meta2d-react/src/index.js',
  1541. '/view/meta2d-react/src/Meta2d.css',
  1542. '/view/meta2d-react/src/Meta2d.jsx',
  1543. '/view/meta2d-react/package.json',
  1544. '/view/meta2d-react/README.md',
  1545. '/view/meta2d-react/public/index.html',
  1546. ] as const;
  1547. // 文件同时加载
  1548. await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
  1549. }
  1550. async function zipFiles(zip: JSZip) {
  1551. const files = [
  1552. '/view/js/marked.min.js',
  1553. '/view/js/lcjs.iife.js',
  1554. '/view/js/index.js',
  1555. '/view/js/meta2d.js',
  1556. '/view/index.html',
  1557. '/view/index.css',
  1558. '/view/favicon.ico',
  1559. '/view/使用说明.pdf',
  1560. ] as const;
  1561. // 文件同时加载
  1562. await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
  1563. }
  1564. async function zipFile(zip: JSZip, filePath: string) {
  1565. const res: Blob = await axios.get(
  1566. (cdn ? cdn + '/v' : import.meta.env.BASE_URL.slice(0, -1)) + filePath,
  1567. {
  1568. responseType: 'blob',
  1569. }
  1570. );
  1571. zip.file(filePath.replace('/view', ''), res, { createFolders: true });
  1572. }
  1573. /**
  1574. * 图片放到 zip 里
  1575. * @param pens 可以是非具有 calculative 的 pen
  1576. */
  1577. async function zipImages(zip: JSZip, pens: Pen[]) {
  1578. if (!pens) {
  1579. return;
  1580. }
  1581. // 不止 image 上有图片, strokeImage ,backgroundImage 也有图片
  1582. const imageKeys = [
  1583. {
  1584. string: 'image',
  1585. },
  1586. { string: 'strokeImage' },
  1587. { string: 'backgroundImage' },
  1588. ] as const;
  1589. const images: string[] = [];
  1590. for (const pen of pens) {
  1591. for (const i of imageKeys) {
  1592. const image = pen[i.string];
  1593. if (image) {
  1594. // HTMLImageElement 无法精确控制图片格式
  1595. if (
  1596. image.startsWith('/') ||
  1597. image.startsWith(cdn) ||
  1598. image.startsWith(upCdn)
  1599. ) {
  1600. // 只考虑相对路径下的 image ,绝对路径图片无需下载
  1601. if (!images.includes(image)) {
  1602. images.push(image);
  1603. }
  1604. }
  1605. }
  1606. }
  1607. // 无需递归遍历子节点,现在所有的节点都在外层
  1608. }
  1609. //付费pngs
  1610. const pngs = await getTemPngs(images.map(i => i.replace(cdn, '').replace(upCdn, '')));
  1611. await Promise.all(images.map((image) => zipImage(zip, image, pngs[image.replace(cdn, '').replace(upCdn, '')])));
  1612. }
  1613. async function zipImage(zip: JSZip, image: string, temImage?: string) {
  1614. const res: Blob = await axios.get(temImage || image, {
  1615. responseType: 'blob',
  1616. // params: {
  1617. // isZip: true,
  1618. // },
  1619. });
  1620. zip.file(
  1621. (frameFlag === -1
  1622. ? ''
  1623. : `${frameFlag === Frame.vue3
  1624. ? 'meta2d-vue3'
  1625. : frameFlag === Frame.vue2
  1626. ? 'meta2d-vue2'
  1627. : 'meta2d-react'
  1628. }/public`) + (cdn ? image.replace(cdn, '').replace(upCdn, '') : image),
  1629. res,
  1630. {
  1631. createFolders: true,
  1632. }
  1633. );
  1634. }
  1635. const downloadImageTips =
  1636. '无法下载,宽度不合法,画布可能没有画笔/画布大小超出浏览器最大限制';
  1637. const downloadPng = () => {
  1638. if (!meta2d.store.data.pens.length) {
  1639. MessagePlugin.warning(downloadImageTips);
  1640. return;
  1641. }
  1642. try {
  1643. meta2d.downloadPng();
  1644. } catch (e) {
  1645. MessagePlugin.warning(downloadImageTips);
  1646. }
  1647. };
  1648. async function getIconDefs(url: string) {
  1649. let res: any = await axios.get(url);
  1650. let str = res.match(/@font-face([\s\S]*?)\}/)[1];
  1651. str = `@font-face ${str} }`;
  1652. return str;
  1653. }
  1654. const downloadSvg = async () => {
  1655. // await import('@/assets/canvas2svg');
  1656. for (const pen of meta2d.store.data.pens) {
  1657. if (pen.calculative.img) {
  1658. //重新生成绘制图片
  1659. pen.onRenderPenRaw?.(pen);
  1660. }
  1661. }
  1662. if (!C2S) {
  1663. MessagePlugin.error('请先加载乐吾乐官网下的canvas2svg.js');
  1664. return;
  1665. }
  1666. const rect: any = meta2d.getRect();
  1667. if (!isFinite(rect.width)) {
  1668. MessagePlugin.error(downloadImageTips);
  1669. return;
  1670. }
  1671. rect.x -= 10;
  1672. rect.y -= 10;
  1673. const ctx = new C2S(rect.width + 20, rect.height + 20);
  1674. ctx.textBaseline = 'middle';
  1675. ctx.strokeStyle = getGlobalColor(meta2d.store);
  1676. for (const pen of meta2d.store.data.pens) {
  1677. // 不使用 calculative.inView 的原因是,如果 pen 在 view 之外,那么它的 calculative.inView 为 false,但是它的绘制还是需要的
  1678. if (!isShowChild(pen, meta2d.store) || pen.visible == false) {
  1679. continue;
  1680. }
  1681. meta2d.renderPenRaw(ctx, pen, rect);
  1682. }
  1683. let mySerializedSVG = ctx.getSerializedSvg();
  1684. let icon_pens = meta2d.store.data.pens.filter(
  1685. (item) => item.iconFamily && item.icon
  1686. );
  1687. if (icon_pens && icon_pens.length > 0) {
  1688. let iconList = [
  1689. '/icon/国家电网/iconfont.css',
  1690. '/icon/电气工程/iconfont.css',
  1691. '/icon/通用图标/iconfont.css',
  1692. ];
  1693. let defsList: any = await Promise.all(
  1694. iconList.map((item) => getIconDefs(item))
  1695. );
  1696. mySerializedSVG = mySerializedSVG.replace(
  1697. '<defs/>',
  1698. `<defs>
  1699. <style type="text/css">
  1700. ${defsList.join('\n')}
  1701. </style>
  1702. {{bk}}
  1703. </defs>
  1704. {{bkRect}}`
  1705. );
  1706. }
  1707. /* mySerializedSVG = mySerializedSVG.replace(
  1708. '<defs/>',
  1709. `<defs>
  1710. <style type="text/css">
  1711. @font-face {
  1712. font-family: 'ticon';
  1713. src: url('icon/通用图标/iconfont.ttf') format('truetype');
  1714. }
  1715. </style>
  1716. {{bk}}
  1717. </defs>
  1718. {{bkRect}}`
  1719. );
  1720. */
  1721. if (meta2d.store.data.background) {
  1722. mySerializedSVG = mySerializedSVG.replace('{{bk}}', '');
  1723. mySerializedSVG = mySerializedSVG.replace(
  1724. '{{bkRect}}',
  1725. `<rect x="0" y="0" width="100%" height="100%" fill="${meta2d.store.data.background}"></rect>`
  1726. );
  1727. } else {
  1728. mySerializedSVG = mySerializedSVG.replace('{{bk}}', '');
  1729. mySerializedSVG = mySerializedSVG.replace('{{bkRect}}', '');
  1730. }
  1731. mySerializedSVG = mySerializedSVG.replace(/--le5le--/g, '&#x');
  1732. const urlObject: any = (window as any).URL || window;
  1733. const export_blob = new Blob([mySerializedSVG]);
  1734. const url = urlObject.createObjectURL(export_blob);
  1735. const a = document.createElement('a');
  1736. a.setAttribute(
  1737. 'download',
  1738. `${(meta2d.store.data as Meta2dBackData).name || 'le5le.meta2d'}.svg`
  1739. );
  1740. a.setAttribute('href', url);
  1741. const evt = document.createEvent('MouseEvents');
  1742. evt.initEvent('click', true, true);
  1743. a.dispatchEvent(evt);
  1744. };
  1745. const onUndo = () => {
  1746. meta2d.undo();
  1747. };
  1748. const onRedo = () => {
  1749. meta2d.redo();
  1750. };
  1751. const onCut = () => {
  1752. meta2d.cut();
  1753. };
  1754. const onCopy = () => {
  1755. meta2d.copy();
  1756. };
  1757. const onPaste = () => {
  1758. meta2d.paste();
  1759. };
  1760. const onAll = () => {
  1761. meta2d.activeAll();
  1762. };
  1763. const onDelete = () => {
  1764. meta2d.delete();
  1765. };
  1766. const onToggleAnchor = () => {
  1767. //取消连线状态
  1768. // meta2d.store.options.disableAnchor = false;
  1769. if (!meta2d.store.options.disableAnchor) {
  1770. meta2d.canvas.drawingLineName && drawPen();
  1771. meta2d.toggleAnchorMode();
  1772. }
  1773. };
  1774. const onAddAnchorHand = () => {
  1775. meta2d.addAnchorHand();
  1776. };
  1777. const onRemoveAnchorHand = () => {
  1778. meta2d.removeAnchorHand();
  1779. };
  1780. const onToggleAnchorHand = () => {
  1781. meta2d.toggleAnchorHand();
  1782. };
  1783. const onScaleUp = () => {
  1784. const _scale = meta2d.store.data.scale + 0.1;
  1785. meta2d.scale(_scale);
  1786. };
  1787. const onScaleDown = () => {
  1788. const _scale = meta2d.store.data.scale - 0.1;
  1789. meta2d.scale(_scale);
  1790. };
  1791. const autoAnchor = ref(true);
  1792. const onAutoAnchor = () => {
  1793. meta2d.store.options.autoAnchor = !meta2d.store.options.autoAnchor;
  1794. autoAnchor.value = meta2d.store.options.autoAnchor;
  1795. };
  1796. const showAnchor = ref(false);
  1797. const onDisableAnchor = () => {
  1798. meta2d.store.options.disableAnchor = !meta2d.store.options.disableAnchor;
  1799. changeDisableAnchor();
  1800. };
  1801. const changeDisableAnchor = () => {
  1802. const { disableAnchor, autoAnchor } = meta2d.store.options;
  1803. showAnchor.value = !disableAnchor || false;
  1804. if (disableAnchor && autoAnchor) {
  1805. // 禁用瞄点开了,需要关闭自动瞄点
  1806. onAutoAnchor();
  1807. }
  1808. };
  1809. const payListDialog = reactive({
  1810. show: false,
  1811. });
  1812. const prePay = async () => {
  1813. let list = [];
  1814. data.goods[0].checked &&
  1815. prePayList.pngs.forEach((item) => {
  1816. list.push({
  1817. type: '图片图元',
  1818. name: item,
  1819. });
  1820. });
  1821. data.goods[3].checked &&
  1822. prePayList.jsPens.forEach((item) => {
  1823. list.push({
  1824. type: 'JS线性图元',
  1825. name: item,
  1826. });
  1827. });
  1828. data.goods[1].checked &&
  1829. prePayList.iotPens.forEach((item) => {
  1830. list.push({
  1831. type: '控件',
  1832. name: item,
  1833. });
  1834. });
  1835. if (data.goods[2].checked) {
  1836. if ([...prePayList.svgPens].includes('*')) {
  1837. list.push({
  1838. type: 'SVG线性图元',
  1839. });
  1840. } else {
  1841. prePayList.svgPens.forEach((item) => {
  1842. list.push({
  1843. type: 'SVG线性图元',
  1844. name: item,
  1845. });
  1846. });
  1847. }
  1848. }
  1849. const res: any = await axios.post('/api/order/2d/component/submit', {
  1850. list,
  1851. });
  1852. wechatPayDialog.show = true;
  1853. data.order = res;
  1854. };
  1855. const skipPay = () => {
  1856. //跳过支付,直接下载
  1857. if (downloadType === Frame.html) {
  1858. preDownload(meta2d.data());
  1859. } else {
  1860. preFrameDownload(downloadType);
  1861. }
  1862. };
  1863. const finishPay = async () => {
  1864. let id = data.order.id;
  1865. const result: { state: number } = await axios.post('/api/order/pay/state', {
  1866. id,
  1867. });
  1868. if (result && result.state) {
  1869. MessagePlugin.success('支付成功');
  1870. wechatPayDialog.show = false;
  1871. payListDialog.show = false;
  1872. if (downloadType === Frame.html) {
  1873. preDownload(meta2d.data());
  1874. } else {
  1875. preFrameDownload(downloadType);
  1876. }
  1877. } else {
  1878. MessagePlugin.error('支付失败');
  1879. wechatPayDialog.show = false;
  1880. }
  1881. };
  1882. const finalPrice = computed(() => {
  1883. let total = 0;
  1884. let price = 0;
  1885. data.goods.forEach((item, index) => {
  1886. if (item.checked) {
  1887. total += item.unPurchased;
  1888. price += item.unPurchased * item.unitPrice;
  1889. }
  1890. });
  1891. return {
  1892. total,
  1893. price,
  1894. };
  1895. });
  1896. const wechatPayDialog = reactive({
  1897. show: false,
  1898. });
  1899. const emit = defineEmits(['success']);
  1900. const onSuccess = (success: boolean) => {
  1901. emit('success', success);
  1902. };
  1903. </script>
  1904. <style lang="postcss" scoped>
  1905. .app-header {
  1906. display: flex;
  1907. height: 40px;
  1908. background-color: var(--color-background);
  1909. position: relative;
  1910. z-index: 2;
  1911. .logo {
  1912. display: flex;
  1913. padding: 0 16px;
  1914. align-items: center;
  1915. font-size: 14px;
  1916. font-weight: 500;
  1917. img {
  1918. height: 20px;
  1919. margin-right: 6px;
  1920. }
  1921. }
  1922. a {
  1923. display: flex;
  1924. padding: 0 8px;
  1925. margin: 0 8px;
  1926. align-items: center;
  1927. color: var(--color);
  1928. text-decoration: none;
  1929. white-space: nowrap;
  1930. &:hover {
  1931. color: var(--color-primary);
  1932. }
  1933. svg {
  1934. font-size: 15px;
  1935. margin: 2px 4px 0 0;
  1936. }
  1937. &.active {
  1938. background-color: var(--color-primary);
  1939. color: #ffffff;
  1940. }
  1941. }
  1942. input {
  1943. font-size: var(--font-size);
  1944. flex-grow: 1;
  1945. background: none;
  1946. outline: none;
  1947. border: none;
  1948. text-align: center;
  1949. color: var(--color-title);
  1950. }
  1951. }
  1952. .pay-dialog {
  1953. background-color: red;
  1954. }
  1955. .pay-body {
  1956. height: 300px;
  1957. overflow-y: scroll;
  1958. margin-bottom: 30px;
  1959. .t-collapse {
  1960. border: 0px;
  1961. :deep(.t-collapse-panel__wrapper .t-collapse-panel__header) {
  1962. border-bottom: 0px;
  1963. }
  1964. :deep(.t-collapse-panel__wrapper .t-collapse-panel__body) {
  1965. background: #fff0;
  1966. border-bottom: 0px;
  1967. .t-collapse-panel__content {
  1968. padding: 0;
  1969. color: #c1c8d7;
  1970. }
  1971. }
  1972. }
  1973. }
  1974. .pay-line {
  1975. display: flex;
  1976. height: 54px;
  1977. background-color: #afcaff0a;
  1978. margin-top: 1px;
  1979. div {
  1980. width: 30%;
  1981. text-align: center;
  1982. line-height: 54px;
  1983. :deep(.t-image) {
  1984. width: 40px;
  1985. height: 40px;
  1986. margin-top: 7px;
  1987. }
  1988. :deep(.pay-svg) {
  1989. .t-image {
  1990. background: #fff;
  1991. }
  1992. }
  1993. .l-icon {
  1994. width: 40px;
  1995. height: 40px;
  1996. margin-top: 7px;
  1997. width: 30%;
  1998. }
  1999. }
  2000. &>div:first-child {
  2001. width: 40%;
  2002. display: flex;
  2003. }
  2004. }
  2005. .pay-tip {
  2006. height: 54px;
  2007. width: 100%;
  2008. position: relative;
  2009. p {
  2010. position: absolute;
  2011. right: 16px;
  2012. color: var(--color-desc);
  2013. }
  2014. }
  2015. .pay-footer {
  2016. margin-bottom: -42px;
  2017. position: relative;
  2018. .pay-price {
  2019. position: absolute;
  2020. right: 215px;
  2021. /* margin-left: 50px; */
  2022. align-items: flex-end;
  2023. line-height: 10px;
  2024. /* p{
  2025. vertical-align: bottom;
  2026. line-height: normal;
  2027. } */
  2028. p:nth-child(2) {
  2029. font-size: 32px;
  2030. font-weight: Semibold;
  2031. color: #4480f9;
  2032. line-height: 20px;
  2033. }
  2034. p:nth-child(1) {
  2035. font-size: 14px;
  2036. color: #4480f9;
  2037. }
  2038. }
  2039. }
  2040. .pay-title {
  2041. margin: 18px 0px 12px 18px;
  2042. }
  2043. </style>