12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469 |
- <template>
- <div class="app-header">
- <a class="logo" :href="enterprise.home" target="_blank">
- <img src="/favicon.ico" />
- <span> {{ enterprise.name }}</span>
- </a>
- <t-dropdown
- :minColumnWidth="200"
- :maxHeight="560"
- :delay2="[10, 150]"
- overlayClassName="header-dropdown"
- trigger1="click"
- >
- <a> 文件 </a>
- <t-dropdown-menu>
- <t-dropdown-item @click="newFile">
- <a>新建文件</a>
- </t-dropdown-item>
- <t-dropdown-item @click="load(true)">
- <a>打开文件</a>
- </t-dropdown-item>
- <t-dropdown-item divider="true" @click="load">
- <a>导入文件</a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="save()">保存</a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="save(SaveType.SaveAs,'',undefined,1)">另保存</a>
- </t-dropdown-item>
- <t-dropdown-item divider="true">
- <a @click="downloadJson">下载JSON文件</a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="downloadZip">
- <div class="flex">
- 导出为ZIP文件 <span class="flex-grow"></span>
- <span><label>VIP</label></span>
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="downloadHtml">
- <div class="flex">
- 下载离线部署包 <span class="flex-grow"></span>
- <span><label>VIP</label></span>
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="downloadVue3">
- <div class="flex">
- 下载Vue3组件包 <span class="flex-grow"></span>
- <span><label>VIP</label></span>
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="downloadVue2">
- <div class="flex">
- 下载Vue2组件包 <span class="flex-grow"></span>
- <span><label>VIP</label></span>
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item divider="true">
- <a @click="downloadReact">
- <div class="flex">
- 下载React组件包 <span class="flex-grow"></span>
- <span><label>VIP</label></span>
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="downloadPng">下载为PNG</a>
- </t-dropdown-item>
- <t-dropdown-item divider="true">
- <a @click="downloadSvg">下载为SVG</a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="switchTheme('light')">明亮主题</a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="switchTheme('dark')">暗黑主题</a>
- </t-dropdown-item>
- </t-dropdown-menu>
- </t-dropdown>
- <t-dropdown
- :minColumnWidth="180"
- :maxHeight="500"
- :delay2="[10, 150]"
- overlayClassName="header-dropdown"
- >
- <a> 编辑 </a>
- <t-dropdown-menu>
- <t-dropdown-item>
- <a @click="onUndo">
- <div class="flex">
- 撤销 <span class="flex-grow"></span> Ctrl + Z
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item divider="true">
- <a @click="onRedo">
- <div class="flex">
- 恢复 <span class="flex-grow"></span> Ctrl + Y
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="onCut">
- <div class="flex">
- 剪切 <span class="flex-grow"></span> Ctrl + X
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="onCopy">
- <div class="flex">
- 复制 <span class="flex-grow"></span> Ctrl + C
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item divider="true">
- <a @click="onPaste">
- <div class="flex">
- 粘贴 <span class="flex-grow"></span> Ctrl + V
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="onAll">
- <div class="flex">
- 全选 <span class="flex-grow"></span> Ctrl + A
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="onDelete">
- <div class="flex">删除 <span class="flex-grow"></span> DELETE</div>
- </a>
- </t-dropdown-item>
- </t-dropdown-menu>
- </t-dropdown>
- <t-dropdown
- :minColumnWidth="180"
- :maxHeight="500"
- :delay2="[10, 150]"
- overlayClassName="header-dropdown"
- >
- <a> 工具 </a>
- <t-dropdown-menu>
- <t-dropdown-item>
- <a @click="onScaleWindow">窗口大小</a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="onScaleUp">放大</a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="onScaleDown">缩小</a>
- </t-dropdown-item>
- <t-dropdown-item divider="true">
- <a @click="onScaleFull">100%视图</a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="showMap">
- <div class="flex middle">
- 鹰眼地图 <span class="flex-grow"></span>
- <!-- <t-icon v-show="map" name="check" /> -->
- <check-icon v-show="map"/>
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item divider="true">
- <a @click="showMagnifier">
- <div class="flex middle">
- 放大镜 <span class="flex-grow"></span>
- <!-- <t-icon v-show="magnifier" name="check" /> -->
- <check-icon v-show="magnifier"/>
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="onAutoAnchor">
- <div class="flex middle">
- 自动锚点 <span class="flex-grow"></span>
- <!-- <t-icon v-show="autoAnchor" name="check" /> -->
- <check-icon v-show="autoAnchor"/>
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item divider="true">
- <a @click="onDisableAnchor">
- <div class="flex middle">
- 显示锚点 <span class="flex-grow"></span>
- <!-- <t-icon v-show="showAnchor" name="check" /> -->
- <check-icon v-show="showAnchor"/>
- </div>
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="onToggleAnchor">
- <div
- class="flex"
- :style="{
- color: showAnchor ? '' : '#4f5b75',
- }"
- >
- 添加/删除锚点 <span class="flex-grow"></span> A
- </div>
- </a>
- </t-dropdown-item>
- </t-dropdown-menu>
- </t-dropdown>
- <t-dropdown
- :minColumnWidth="180"
- :maxHeight="500"
- :delay2="[10, 150]"
- overlayClassName="header-dropdown"
- >
- <a> 帮助 </a>
- <t-dropdown-menu>
- <t-dropdown-item
- v-for="item in enterprise.helps"
- :divider="item.divider"
- >
- <a :href="item.url" target="_blank">{{ item.name }}</a>
- </t-dropdown-item>
- </t-dropdown-menu>
- </t-dropdown>
- <div style="width: 148px; flex-shrink: 0"></div>
- <input v-model="data.name" @input="onInputName" />
- <a :href="enterprise.account" target="_blank">
- <!-- <t-icon name="home" /> -->
- <home-icon/>
- 账户中心
- </a>
- <a :href="enterprise['v']" target="_blank" class="active">
- <!-- <t-icon name="desktop" /> -->
- <desktop-icon/>
- 大屏可视化
- </a>
- <a :href="enterprise['3d']" target="_blank">
- <!-- <t-icon name="control-platform" /> -->
- <control-platform-icon/>
- 3D可视化
- </a>
- <a :href="enterprise['2d']" target="_blank">
- <!-- <t-icon name="app" /> -->
- <app-icon/>
- 2D可视化
- </a>
- <t-dropdown
- v-if="user.id"
- :minColumnWidth="200"
- :maxHeight="500"
- :delay2="[10, 150]"
- overlayClassName="custom-dropdown header"
- >
- <a style="margin-left: 32px; margin-right: 12px">
- <t-avatar
- size="small"
- :image="user.avatarUrl ? user.avatarUrl : baseUrl + 'img/avatar.png'"
- />
- </a>
- <t-dropdown-menu>
- <t-dropdown-item divider="true">
- <a :href="enterprise.account">
- {{ user.username }}
- <label
- class="ml-16 vip-label"
- :style="{
- color: user.limit > 1 ? '#ff4000' : '#D5C781',
- background: user.limit > 1 ? '#ff400030' : '#D5C78133',
- }"
- >VIP</label
- >
- </a>
- </t-dropdown-item>
- <t-dropdown-item divider="true">
- <a :href="`${enterprise.account}/v`" target="_blank"> 我的大屏 </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a :href="`${enterprise.account}/account/teams`" target="_blank">
- 我的团队
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a :href="`${enterprise.account}/account/info`" target="_blank">
- 账号信息
- </a>
- </t-dropdown-item>
- <t-dropdown-item divider="true">
- <a :href="`${enterprise.account}/account/security`" target="_blank">
- 安全设置
- </a>
- </t-dropdown-item>
- <t-dropdown-item>
- <a @click="logout">退出</a>
- </t-dropdown-item>
- </t-dropdown-menu>
- </t-dropdown>
- <div class="flex middle" v-else>
- <a class="button primary solid" style="width: 80px" :href="login()">
- 登录
- </a>
- </div>
- </div>
- </template>
- <script lang="ts" setup>
- import { reactive, ref, onBeforeMount, onUnmounted, nextTick } from 'vue';
- import { useRouter, useRoute } from 'vue-router';
- import { useUser } from '@/services/user';
- import { MessagePlugin } from 'tdesign-vue-next';
- import {
- Meta2dBackData,
- dealwithFormatbeforeOpen,
- gotoAccount,
- checkData,
- } from '@/services/utils';
- import { readFile } from '@/services/file';
- import { compareVersion, baseVer, upgrade } from '@/services/upgrade';
- import { parseSvg } from '@meta2d/svg';
- import { Pen, getGlobalColor, isShowChild } from '@meta2d/core';
- import { cdn, upCdn } from '@/services/api';
- import JSZip from 'jszip';
- import axios from 'axios';
- import { switchTheme } from '@/services/theme';
- import { noLoginTip } from '@/services/utils';
- import {
- save,
- blank,
- newFile,
- SaveType,
- onScaleFull,
- onScaleWindow,
- showMagnifier,
- showMap,
- drawPen,
- map,
- magnifier,
- useDot,
- delAttrs,
- useAssets,
- } from '@/services/common';
- import { useEnterprise } from '@/services/enterprise';
- import { CheckIcon,HomeIcon ,DesktopIcon ,ControlPlatformIcon,AppIcon} from 'tdesign-icons-vue-next';
- import { getDownloadList } from '@/services/download';
- const { enterprise } = useEnterprise();
- const router = useRouter();
- const route = useRoute();
- const baseUrl = import.meta.env.BASE_URL || '/';
- // const { assets, getAssets } = useAssets();
- const { user, signout } = useUser();
- const { setDot } = useDot();
- const data = reactive({
- name: '空白文件',
- });
- onBeforeMount(async () => {
- // getAssets();
- });
- const logout = () => {
- signout();
- meta2d.emit('logout');
- };
- const onInputName = () => {
- (meta2d.store.data as Meta2dBackData).name = data.name;
- setDot();
- };
- const initMeta2dName = () => {
- data.name = (meta2d.store.data as Meta2dBackData).name || '';
- };
- let downloadList = new Set();
- let iframeNum = 0;
- let compareNum = 0;
- nextTick(() => {
- meta2d.on('opened', initMeta2dName);
- window.addEventListener('message', function (e) {
- if (typeof e.data !== 'string'||!e.data||e.data.startsWith('setImmediate')) {
- return;
- }
- try {
- let data = JSON.parse(e.data);
- if (typeof data === 'object') {
- if(data.type && data.name==='download'){
- downloadList = new Set([...downloadList, ...data.data]);
- compareNum+=1;
- if(compareNum>=iframeNum){
- saveDownload();
- }
- }
- meta2d.emit(data.name);
- } else {
- meta2d.emit(data);
- }
- } catch (e) {
- console.info(e);
- }
- });
- });
- onUnmounted(() => {
- meta2d.off('opened', initMeta2dName);
- });
- const login = () => {
- return `${enterprise['account']}?cb=${encodeURIComponent(location.href)}`;
- };
- function load(isNew = false) {
- const input = document.createElement('input');
- input.type = 'file';
- input.onchange = (event) => {
- const elem = event.target as HTMLInputElement;
- if (elem.files && elem.files[0]) {
- blank();
- // 路由跳转 可能在 openFile 后执行
- if (elem.files[0].name.endsWith('.json')) {
- openJson(elem.files[0]);
- if (isNew) {
- router.push({
- path: '/',
- query: {
- r: Date.now() + '',
- },
- });
- }
- } else if (elem.files[0].name.endsWith('.svg')) {
- MessagePlugin.info(
- '可二次编辑但转换存在损失,若作为图片使用,请使用右侧属性面板的上传图片功能'
- );
- openSvg(elem.files[0]);
- } else if (elem.files[0].name.endsWith('.zip')) {
- router.push({
- path: '/',
- query: {
- r: Date.now() + '',
- },
- });
- setTimeout(() => {
- openZip(elem.files[0]);
- }, 500);
- } else {
- MessagePlugin.info('打开文件只支持 json,svg,zip 格式');
- }
- }
- };
- input.click();
- }
- const openJson = async (file: File) => {
- const text = await readFile(file);
- try {
- let data: Meta2dBackData = JSON.parse(text);
- if (!data.name) {
- data.name = file.name.replace('.json', '');
- }
- if (!data.version || compareVersion(data.version, baseVer) === -1) {
- // 如果版本号不存在或者版本号 version < 1.0.0
- data = upgrade(data, baseVer);
- }
- dealwithFormatbeforeOpen(data);
- for (const k of delAttrs) {
- delete (data as any)[k];
- }
- meta2d.open(data);
- } catch (e) {
- console.error(e);
- }
- };
- const openSvg = async (file: File) => {
- const text = await readFile(file);
- const pens: Pen[] = parseSvg(text);
- meta2d.canvas.addCaches = pens;
- MessagePlugin.info('svg转换成功,请点击画布决定放置位置');
- };
- const openZip = async (file: File) => {
- if (!(user && user.id)) {
- MessagePlugin.warning(noLoginTip);
- return;
- }
- // if (!user.isVip) {
- // gotoAccount();
- // return;
- // }
- if (!user.vip) {
- MessagePlugin.info('需要开通普通会员~');
- gotoAccount();
- return;
- }
- const { default: JSZip } = await import('jszip');
- const zip = new JSZip();
- await zip.loadAsync(file);
- let dataStr = '';
- for (const key in zip.files) {
- if (zip.files[key].dir) {
- continue;
- }
- if (key.endsWith('.json')) {
- // 认为只有一个 json 文件
- // dataStr = await zip.file(key).async('string');
- break;
- }
- }
- if (!dataStr) {
- return false;
- }
- for (const key in zip.files) {
- if (zip.files[key].dir) {
- continue;
- }
- // let _png = key.indexOf('/png');
- // let _img = key.indexOf('/img');
- // let _image = key.indexOf('/image');
- // let _file = key.indexOf('/file');
- let _keyLower = key.toLowerCase();
- // if (!key.endsWith('.json') && (_png !== -1 || _img !== -1 || _image !== -1 || _file !== -1)) {
- if (
- _keyLower.endsWith('.png') ||
- _keyLower.endsWith('.svg') ||
- _keyLower.endsWith('.gif') ||
- _keyLower.endsWith('.jpg') ||
- _keyLower.endsWith('.jpeg')
- ) {
- let filename = key.substr(key.lastIndexOf('/') + 1);
- const extPos = filename.lastIndexOf('.');
- let ext = '';
- if (extPos > 0) {
- ext = filename.substr(extPos);
- }
- filename = filename.substring(0, extPos > 8 ? 8 : extPos);
- // 上传文件
- const result: any = {};
- // await upload(
- // // await zip.file(key).async('blob'),
- // true,
- // filename + ext,
- // "/2D/默认"
- // );
- let _key = key;
- // if (_png) {
- // _key = key.substring(_png);
- // } else if (_image) {
- // _key = key.substring(_png);
- // } else if (_img) {
- // _key = key.substring(_img);
- // } else if (_file) {
- // _key = key.substring(_file);
- // }
- if (result) {
- if (dataStr.replaceAll) {
- //'le5le.meta2d'
- dataStr = dataStr.replaceAll(_key.slice(12), result.url);
- } else {
- while (dataStr.includes(_key)) {
- dataStr = dataStr.replace(_key.slice(12), result.url);
- // 正则 gm 在特殊情况下报错,例如如下场景
- /**
- *
- const data = '{"image":"/image/materials/IoT-Chemical(化学)/Air stripper 2(汽提塔2).svg"}';
- const key = '/image/materials/IoT-Chemical(化学)/Air stripper 2(汽提塔2).svg';
- data.replace(key, '123');
- data.replaceAll(key, '123')
- data.replace(new RegExp(key, 'gm'), '123');
- data.replace(new RegExp(key, 'g'), '123');
- */
- }
- }
- }
- }
- }
- try {
- let data: Meta2dBackData = JSON.parse(dataStr);
- if (data) {
- if (!data.name) {
- data.name = file.name.replace('.zip', '');
- }
- if (!data.version || compareVersion(data.version, baseVer) === -1) {
- // 如果版本号不存在或者版本号 version < 1.0.0
- data = upgrade(data, baseVer);
- }
- dealwithFormatbeforeOpen(data);
- const delAttrs = [
- 'userId',
- 'shared',
- 'team',
- 'owner',
- 'username',
- 'editor',
- 'editorId',
- 'editorName',
- 'createdAt',
- 'folder',
- 'image',
- 'id',
- '_id',
- 'view',
- 'updatedAt',
- 'star',
- 'recommend',
- ];
- for (const k of delAttrs) {
- delete (data as any)[k];
- }
- meta2d.open(data);
- }
- } catch (e) {
- return false;
- }
- };
- const downloadJson = () => {
- const data: Meta2dBackData = meta2d.data();
- if (data._id) delete data._id;
- if (data.id) delete data.id;
- checkData(data);
- import('file-saver').then(({ saveAs }) => {
- saveAs(
- new Blob(
- [JSON.stringify(data).replaceAll(cdn, '').replaceAll(upCdn, '')],
- {
- type: 'text/plain;charset=utf-8',
- }
- ),
- `${data.name || 'le5le.meta2d'}.json`
- );
- });
- };
- const downloadZip = async () => {
- if (!(user && user.id)) {
- MessagePlugin.warning(noLoginTip);
- return;
- }
- // if (!user.isVip) {
- // // gotoAccount();
- // return;
- // }
- if (!user.vip) {
- MessagePlugin.info('需要开通普通会员~');
- gotoAccount();
- return;
- }
- MessagePlugin.info('正在下载打包中,可能需要几分钟,请耐心等待...');
- const [{ default: JSZip }, { saveAs }] = await Promise.all([
- import('jszip'),
- import('file-saver'),
- ]);
- const zip: any = new JSZip();
- const data: Meta2dBackData = meta2d.data();
- let _fileName =
- (data.name && data.name.replace(/\//g, '_').replace(/:/g, '_')) ||
- 'le5le.meta2d';
- const _zip = zip.folder(`${_fileName}`);
- if (data._id) delete data._id;
- if (data.id) delete data.id;
- checkData(data);
- _zip.file(
- `${_fileName}.json`,
- JSON.stringify(data).replaceAll(cdn, '').replaceAll(upCdn, '')
- );
- await zipBkImg(_zip);
- await zipImages(_zip, meta2d.store.data.pens);
- const blob = await zip.generateAsync({ type: 'blob' });
- saveAs(blob, `${_fileName}.zip`);
- };
- const zip3D = (name:string) => {
- const pen_3d = meta2d.store.data.pens.filter(
- (pen) =>
- pen.name === 'iframe' &&
- (pen.tags.includes('meta3d') || pen.iframe.indexOf('3d') !== -1)
- );
- if (pen_3d && pen_3d.length) {
- //存在3d场景
- pen_3d.forEach((pen) => {
- //发送消息
- let params = queryURLParams(pen.iframe.split('?')[1]);
- (
- pen.calculative.singleton.div.children[0] as HTMLIFrameElement
- ).contentWindow.postMessage(
- JSON.stringify({
- name,
- id: params.id,
- }),
- '*'
- );
- });
- }
- };
- function queryURLParams(value?: string) {
- let url = value || window.location.href.split('?')[1];
- const urlSearchParams = new URLSearchParams(url);
- const params = Object.fromEntries(urlSearchParams.entries());
- return params;
- }
- const downloadHtml = async () => {
- if (!(user && user.id)) {
- MessagePlugin.warning(noLoginTip);
- return;
- }
- if(user.vipDesc !== '超级会员'&&user.vipDesc !=='旗舰会员'){
- MessagePlugin.info('需要开通超级会员~');
- gotoAccount();
- return
- }
- MessagePlugin.info('正在下载打包中,可能需要几分钟,请耐心等待...');
- compareNum = 0;
- const meta2dData = meta2d.data();
- const pen_3d = meta2dData.pens.filter(
- (pen) =>
- pen.name === 'iframe' &&
- (pen.iframe.indexOf('3d.le5le.com') !== -1)
- );
- if (pen_3d && pen_3d.length) {
- //存在3d场景
- if(pen_3d.length===1){
- let params = queryURLParams(pen_3d[0].iframe.split('?')[1]);
- meta2d.store.pens[pen_3d[0].id].calculative.singleton.div.children[0].contentWindow.postMessage(
- JSON.stringify({
- name:'deploy',
- // id: params.id,
- type:1, //用于区分是系统消息
- path:`3d`
- }),
- '*'
- );
- pen_3d.iframe = '/view?data=3d';
- }else{
- pen_3d.forEach((pen) => {
- //发送消息
- let params = queryURLParams(pen.iframe.split('?')[1]);
- (
- meta2d.store.pens[pen.id].calculative.singleton.div.children[0] as HTMLIFrameElement
- ).contentWindow.postMessage(
- JSON.stringify({
- name:'deploy',
- // id: params.id,
- type:1,
- path:`3d-${params.id}`
- }),
- '*'
- );
- pen.iframe = `/view?data=3d-${params.id}`;
- });
- }
- iframeNum+= pen_3d.length;
- }
- const pen_2d = meta2dData.pens.filter(
- (pen) =>
- pen.name === 'iframe' &&
- (pen.iframe.indexOf('2d.le5le.com') !== -1)
- );
- if (pen_2d && pen_2d.length) {
- //存在3d场景
- if(pen_2d.length===1){
- let params = queryURLParams(pen_2d[0].iframe.split('?')[1]);
- meta2d.store.pens[pen_2d[0].id].calculative.singleton.div.children[0].contentWindow.postMessage(
- JSON.stringify({
- name:'downloadHtml',
- id: params.id,
- type:1,
- path:`2d`
- }),
- '*'
- );
- pen_2d[0].iframe = '/view?data=2d'
- }else{
- pen_2d.forEach((pen) => {
- //发送消息
- let params = queryURLParams(pen.iframe.split('?')[1]);
- (
- meta2d.store.pens[pen.id].calculative.singleton.div.children[0] as HTMLIFrameElement
- ).contentWindow.postMessage(
- JSON.stringify({
- name:'downloadHtml',
- // id: params.id,
- type:1,
- path:`2d-${params.id}`
- }),
- '*'
- );
- });
- pen.iframe = `/view?data=2d-${params.id}`
- }
- iframeNum+= pen_2d.length;
- }
- const pen_v = meta2dData.pens.filter(
- (pen) =>
- pen.name === 'iframe' &&
- (pen.iframe.indexOf('v.le5le.com') !== -1)
- );
- if (pen_v && pen_v.length) {
- //存在3d场景
- pen_v.forEach((pen) => {
- //发送消息
- let params = queryURLParams(pen.iframe.split('?')[1]);
- (
- meta2d.store.pens[pen.id].calculative.singleton.div.children[0] as HTMLIFrameElement
- ).contentWindow.postMessage(
- JSON.stringify({
- name:'downloadHtml',
- // id: params.id,
- type:1,
- path:`v-${params.id}`
- }),
- '*'
- );
- pen.iframe = `/view?data=v-${params.id}`
- });
- iframeNum+= pen_v.length;
- }
- downloadList = getDownloadList(meta2dData);
-
- if(iframeNum===0){
- //如果没有嵌入场景
- saveDownload();
- }else{
- setTimeout(()=>{
- if(compareNum < iframeNum){
- //message阻塞/报错的情况
- saveDownload();
- }
- },10000);
- }
- }
- const saveDownload = async()=>{
- const [{ default: JSZip }, { saveAs }] = await Promise.all([
- import('jszip'),
- import('file-saver'),
- ]);
- const zip = new JSZip();
- await Promise.all([...downloadList].map(async(item)=>{
- if(item.url){
- //接口请求
- const res: Blob = await axios.get(item.url, {
- responseType: 'blob',
- });
- zip.file(item.path, res, { createFolders: true });
- }else if(item.data){
- //直接写数据
- zip.file(
- item.path,
- item.data, { createFolders: true }
- );
- }
- }));
- let _fileName =
- (data.name && data.name.replace(/\//g, '_').replace(/:/g, '_')) ||
- 'le5le.meta2d';
- const blob = await zip.generateAsync({ type: 'blob' });
- saveAs(blob, `${_fileName}.zip`);
- }
- const _downloadHtml = async () => {
- if (!(user && user.id)) {
- MessagePlugin.warning(noLoginTip);
- return;
- }
- // if (!user.isVip) {
- // gotoAccount();
- // return;
- // }
- if(user.vipDesc !== '超级会员'&&user.vipDesc !=='旗舰会员'){
- MessagePlugin.info('需要开通超级会员~');
- gotoAccount();
- return
- }
- frameFlag = -1;
- MessagePlugin.info('正在下载打包中,可能需要几分钟,请耐心等待...');
- zip3D('deploy');
- const data: Meta2dBackData = meta2d.data();
- if (data._id) delete data._id;
- if (data.id) delete data.id;
- if (data.image) delete data.image;
- checkData(data);
- const [{ default: JSZip }, { saveAs }] = await Promise.all([
- import('jszip'),
- import('file-saver'),
- ]);
- const zip = new JSZip();
- let _fileName =
- (data.name && data.name.replace(/\//g, '_').replace(/:/g, '_')) ||
- 'le5le.meta2d';
- //处理cdn图片地址
- const _zip: any = zip.folder(`${_fileName}`);
- _zip.file(
- 'data.json',
- JSON.stringify(data).replaceAll(cdn, '').replaceAll(upCdn, '')
- );
- await Promise.all([
- zipBkImg(_zip),
- zipImages(_zip, meta2d.store.data.pens),
- zipFiles(_zip),
- ]);
- const blob = await zip.generateAsync({ type: 'blob' });
- saveAs(blob, `${_fileName}.zip`);
- };
- async function zipBkImg(zip: JSZip) {
- let img = meta2d.store.data.bkImage;
- if (img) {
- if (img.startsWith('/') || img.startsWith(cdn) || img.startsWith(upCdn)) {
- await zipImage(zip, img);
- }
- }
- }
- enum Frame {
- vue2,
- vue3,
- react,
- }
- const downloadVue3 = async () => {
- downloadAsFrame(Frame.vue3);
- };
- const downloadVue2 = async () => {
- downloadAsFrame(Frame.vue2);
- };
- const downloadReact = async () => {
- downloadAsFrame(Frame.react);
- };
- let frameFlag = -1;
- async function downloadAsFrame(type: Frame) {
- if (!(user && user.id)) {
- MessagePlugin.warning(noLoginTip);
- return;
- }
- // if (!user.isVip) {
- // gotoAccount();
- // return;
- // }
- if(user.vipDesc !=='旗舰会员'){
- MessagePlugin.info('需要开通旗舰会员~');
- gotoAccount();
- return
- }
- frameFlag = type;
- MessagePlugin.info('正在下载打包中,可能需要几分钟,请耐心等待...');
- zip3D(type===Frame.vue3?'toVue3':(type===Frame.vue2?'toVue2':'toReact'));
- const data: Meta2dBackData = meta2d.data();
- if (data._id) delete data._id;
- if (data.id) delete data.id;
- if (data.image) delete data.image;
- checkData(data);
- const [{ default: JSZip }, { saveAs }] = await Promise.all([
- import('jszip'),
- import('file-saver'),
- ]);
- const zip = new JSZip();
- let _fileName =
- (data.name && data.name.replace(/\//g, '_').replace(/:/g, '_')) ||
- 'le5le.meta2d';
- const _zip: any = zip.folder(`${_fileName}`);
- _zip.file(
- `${
- type === Frame.vue3
- ? 'meta2d-vue3'
- : type === Frame.vue2
- ? 'meta2d-vue2'
- : 'meta2d-react'
- }/public/json/data.json`,
- JSON.stringify(data).replaceAll(cdn, '').replaceAll(upCdn, '')
- );
- await Promise.all([
- zipJs(_zip),
- zipBkImg(_zip),
- zipImages(_zip, meta2d.store.data.pens),
- type === Frame.vue3
- ? zipVue3Files(_zip)
- : type === Frame.vue2
- ? zipVue2Files(_zip)
- : zipReactFiles(_zip),
- ]);
- const blob = await zip.generateAsync({ type: 'blob' });
- saveAs(blob, `${_fileName}.zip`);
- frameFlag = -1;
- }
- async function zipJs(zip: JSZip) {
- const files = ['/view/js/marked.min.js', '/view/js/lcjs.iife.js'];
- await Promise.all(
- files.map(async (filePath) => {
- const res: Blob = await axios.get(filePath, {
- responseType: 'blob',
- });
- zip.file(
- `${
- frameFlag === Frame.vue3
- ? 'meta2d-vue3'
- : frameFlag === Frame.vue2
- ? 'meta2d-vue2'
- : 'meta2d-react'
- }/public` + filePath.replace('/view', ''),
- res,
- { createFolders: true }
- );
- })
- );
- }
- async function _zipVue3Files(zip: JSZip) {
- const files = [
- '/view/js/marked.min.js',
- '/view/js/lcjs.iife.js',
- '/view/vue3/Meta2d.vue',
- '/view/index.html',
- '/view/js/meta2d.js',
- '/view/使用说明.md',
- ] as const;
- // 文件同时加载
- await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
- }
- //新
- async function zipVue3Files(zip: JSZip) {
- const files = [
- '/view/meta2d-vue3/src/components/Meta2d.vue',
- '/view/meta2d-vue3/src/App.vue',
- '/view/meta2d-vue3/src/main.js',
- '/view/meta2d-vue3/src/style.css',
- '/view/meta2d-vue3/index.html',
- '/view/meta2d-vue3/package.json',
- '/view/meta2d-vue3/README.md',
- '/view/meta2d-vue3/vite.config.js',
- ] as const;
- // 文件同时加载
- await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
- }
- async function _zipVue2Files(zip: JSZip) {
- const files = [
- '/view/js/marked.min.js',
- '/view/js/lcjs.iife.js',
- '/view/vue2/Meta2d.vue',
- '/view/index.html',
- '/view/js/meta2d.js',
- '/view/使用说明.md',
- ] as const;
- // 文件同时加载
- await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
- }
- async function zipVue2Files(zip: JSZip) {
- const files = [
- '/view/meta2d-vue2/src/components/Meta2d.vue',
- '/view/meta2d-vue2/src/App.vue',
- '/view/meta2d-vue2/src/main.js',
- // '/view/meta2d-vue2/src/style.css',
- '/view/meta2d-vue2/public/index.html',
- '/view/meta2d-vue2/package.json',
- '/view/meta2d-vue2/README.md',
- // '/view/meta2d-vue3/vite.config.js',
- ] as const;
- // 文件同时加载
- await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
- }
- async function _zipReactFiles(zip: JSZip) {
- const files = [
- '/view/js/marked.min.js',
- '/view/js/lcjs.iife.js',
- '/view/react/Meta2d.jsx',
- '/view/react/Meta2d.css',
- '/view/index.html',
- '/view/js/meta2d.js',
- '/view/使用说明.md',
- ] as const;
- // 文件同时加载
- await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
- }
- async function zipReactFiles(zip: JSZip) {
- const files = [
- '/view/meta2d-react/src/index.css',
- '/view/meta2d-react/src/index.js',
- '/view/meta2d-react/src/Meta2d.css',
- '/view/meta2d-react/src/Meta2d.jsx',
- '/view/meta2d-react/package.json',
- '/view/meta2d-react/README.md',
- '/view/meta2d-react/public/index.html',
- ] as const;
- // 文件同时加载
- await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
- }
- async function zipFiles(zip: JSZip) {
- const files = [
- '/view/js/marked.min.js',
- '/view/js/lcjs.iife.js',
- '/view/js/index.js',
- '/view/js/meta2d.js',
- '/view/index.html',
- '/view/index.css',
- '/view/favicon.ico',
- '/view/使用说明.pdf',
- ] as const;
- // 文件同时加载
- await Promise.all(files.map((filePath) => zipFile(zip, filePath)));
- }
- async function zipFile(zip: JSZip, filePath: string) {
- const res: Blob = await axios.get((cdn ? cdn + '/v' : import.meta.env.BASE_URL.slice(0,-1)) + filePath, {
- responseType: 'blob',
- });
- zip.file(filePath.replace('/view', ''), res, { createFolders: true });
- }
- /**
- * 图片放到 zip 里
- * @param pens 可以是非具有 calculative 的 pen
- */
- async function zipImages(zip: JSZip, pens: Pen[]) {
- if (!pens) {
- return;
- }
- // 不止 image 上有图片, strokeImage ,backgroundImage 也有图片
- const imageKeys = [
- {
- string: 'image',
- },
- { string: 'strokeImage' },
- { string: 'backgroundImage' },
- ] as const;
- const images: string[] = [];
- for (const pen of pens) {
- for (const i of imageKeys) {
- const image = pen[i.string];
- if (image) {
- // HTMLImageElement 无法精确控制图片格式
- if (
- image.startsWith('/') ||
- image.startsWith(cdn) ||
- image.startsWith(upCdn)
- ) {
- // 只考虑相对路径下的 image ,绝对路径图片无需下载
- if (!images.includes(image)) {
- images.push(image);
- }
- }
- }
- }
- // 无需递归遍历子节点,现在所有的节点都在外层
- }
- await Promise.all(images.map((image) => zipImage(zip, image)));
- }
- async function zipImage(zip: JSZip, image: string) {
- const res: Blob = await axios.get(image, {
- responseType: 'blob',
- params: {
- isZip: true,
- },
- });
- zip.file(
- (frameFlag === -1
- ? ''
- : `${
- frameFlag === Frame.vue3
- ? 'meta2d-vue3'
- : frameFlag === Frame.vue2
- ? 'meta2d-vue2'
- : 'meta2d-react'
- }/public`) + (cdn ? image.replace(cdn, '').replace(upCdn, '') : image),
- res,
- {
- createFolders: true,
- }
- );
- }
- const downloadImageTips =
- '无法下载,宽度不合法,画布可能没有画笔/画布大小超出浏览器最大限制';
- const downloadPng = () => {
- if (!meta2d.store.data.pens.length) {
- MessagePlugin.warning(downloadImageTips);
- return;
- }
- try {
- meta2d.downloadPng();
- } catch (e) {
- MessagePlugin.warning(downloadImageTips);
- }
- };
- async function getIconDefs(url: string) {
- let res: any = await axios.get(url);
- let str = res.match(/@font-face([\s\S]*?)\}/)[1];
- str = `@font-face ${str} }`;
- return str;
- }
- const downloadSvg = async () => {
- // await import('@/assets/canvas2svg');
- for (const pen of meta2d.store.data.pens) {
- if (pen.calculative.img) {
- //重新生成绘制图片
- pen.onRenderPenRaw?.(pen);
- }
- }
- if (!C2S) {
- MessagePlugin.error('请先加载乐吾乐官网下的canvas2svg.js');
- return;
- }
- const rect: any = meta2d.getRect();
- if (!isFinite(rect.width)) {
- MessagePlugin.error(downloadImageTips);
- return;
- }
- rect.x -= 10;
- rect.y -= 10;
- const ctx = new C2S(rect.width + 20, rect.height + 20);
- ctx.textBaseline = 'middle';
- ctx.strokeStyle = getGlobalColor(meta2d.store);
- for (const pen of meta2d.store.data.pens) {
- // 不使用 calculative.inView 的原因是,如果 pen 在 view 之外,那么它的 calculative.inView 为 false,但是它的绘制还是需要的
- if (!isShowChild(pen, meta2d.store) || pen.visible == false) {
- continue;
- }
- meta2d.renderPenRaw(ctx, pen, rect);
- }
- let mySerializedSVG = ctx.getSerializedSvg();
- let icon_pens = meta2d.store.data.pens.filter(
- (item) => item.iconFamily && item.icon
- );
- if (icon_pens && icon_pens.length > 0) {
- let iconList = [
- '/icon/国家电网/iconfont.css',
- '/icon/电气工程/iconfont.css',
- '/icon/通用图标/iconfont.css',
- ];
- let defsList: any = await Promise.all(
- iconList.map((item) => getIconDefs(item))
- );
- mySerializedSVG = mySerializedSVG.replace(
- '<defs/>',
- `<defs>
- <style type="text/css">
- ${defsList.join('\n')}
- </style>
- {{bk}}
- </defs>
- {{bkRect}}`
- );
- }
- /* mySerializedSVG = mySerializedSVG.replace(
- '<defs/>',
- `<defs>
- <style type="text/css">
- @font-face {
- font-family: 'ticon';
- src: url('icon/通用图标/iconfont.ttf') format('truetype');
- }
- </style>
- {{bk}}
- </defs>
- {{bkRect}}`
- );
- */
- if (meta2d.store.data.background) {
- mySerializedSVG = mySerializedSVG.replace('{{bk}}', '');
- mySerializedSVG = mySerializedSVG.replace(
- '{{bkRect}}',
- `<rect x="0" y="0" width="100%" height="100%" fill="${meta2d.store.data.background}"></rect>`
- );
- } else {
- mySerializedSVG = mySerializedSVG.replace('{{bk}}', '');
- mySerializedSVG = mySerializedSVG.replace('{{bkRect}}', '');
- }
- mySerializedSVG = mySerializedSVG.replace(/--le5le--/g, '&#x');
- const urlObject: any = (window as any).URL || window;
- const export_blob = new Blob([mySerializedSVG]);
- const url = urlObject.createObjectURL(export_blob);
- const a = document.createElement('a');
- a.setAttribute(
- 'download',
- `${(meta2d.store.data as Meta2dBackData).name || 'le5le.meta2d'}.svg`
- );
- a.setAttribute('href', url);
- const evt = document.createEvent('MouseEvents');
- evt.initEvent('click', true, true);
- a.dispatchEvent(evt);
- };
- const onUndo = () => {
- meta2d.undo();
- };
- const onRedo = () => {
- meta2d.redo();
- };
- const onCut = () => {
- meta2d.cut();
- };
- const onCopy = () => {
- meta2d.copy();
- };
- const onPaste = () => {
- meta2d.paste();
- };
- const onAll = () => {
- meta2d.activeAll();
- };
- const onDelete = () => {
- meta2d.delete();
- };
- const onToggleAnchor = () => {
- //取消连线状态
- // meta2d.store.options.disableAnchor = false;
- if (!meta2d.store.options.disableAnchor) {
- meta2d.canvas.drawingLineName && drawPen();
- meta2d.toggleAnchorMode();
- }
- };
- const onAddAnchorHand = () => {
- meta2d.addAnchorHand();
- };
- const onRemoveAnchorHand = () => {
- meta2d.removeAnchorHand();
- };
- const onToggleAnchorHand = () => {
- meta2d.toggleAnchorHand();
- };
- const onScaleUp = () => {
- const _scale = meta2d.store.data.scale + 0.1;
- meta2d.scale(_scale);
- };
- const onScaleDown = () => {
- const _scale = meta2d.store.data.scale - 0.1;
- meta2d.scale(_scale);
- };
- const autoAnchor = ref(true);
- const onAutoAnchor = () => {
- meta2d.store.options.autoAnchor = !meta2d.store.options.autoAnchor;
- autoAnchor.value = meta2d.store.options.autoAnchor;
- };
- const showAnchor = ref(false);
- const onDisableAnchor = () => {
- meta2d.store.options.disableAnchor = !meta2d.store.options.disableAnchor;
- changeDisableAnchor();
- };
- const changeDisableAnchor = () => {
- const { disableAnchor, autoAnchor } = meta2d.store.options;
- showAnchor.value = !disableAnchor || false;
- if (disableAnchor && autoAnchor) {
- // 禁用瞄点开了,需要关闭自动瞄点
- onAutoAnchor();
- }
- };
- </script>
- <style lang="postcss" scoped>
- .app-header {
- display: flex;
- height: 40px;
- background-color: var(--color-background);
- position: relative;
- z-index: 2;
- .logo {
- display: flex;
- padding: 0 16px;
- align-items: center;
- font-size: 14px;
- font-weight: 500;
- img {
- height: 20px;
- margin-right: 6px;
- }
- }
- a {
- display: flex;
- padding: 0 8px;
- margin: 0 8px;
- align-items: center;
- color: var(--color);
- text-decoration: none;
- white-space: nowrap;
- &:hover {
- color: var(--color-primary);
- }
- svg {
- font-size: 15px;
- margin: 2px 4px 0 0;
- }
- &.active {
- background-color: var(--color-primary);
- color: #ffffff;
- }
- }
- input {
- font-size: var(--font-size);
- flex-grow: 1;
- background: none;
- outline: none;
- border: none;
- text-align: center;
- color: var(--color-title);
- }
- }
- </style>
|