Header.vue 59 KB

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