浏览代码

feat:downloadHtml_企业图形库购买

ananzhusen 1 年之前
父节点
当前提交
918710dce7
共有 5 个文件被更改,包括 775 次插入164 次删除
  1. 155 23
      src/services/download.ts
  2. 11 0
      src/services/png.ts
  3. 40 23
      src/views/Preview.vue
  4. 566 118
      src/views/components/Header.vue
  5. 3 0
      vite.config.ts

+ 155 - 23
src/services/download.ts

@@ -1,32 +1,76 @@
 import { cdn, upCdn } from '@/services/api';
-import {
-  checkData,
-} from '@/services/utils';
-
+import { checkData } from '@/services/utils';
+import axios from 'axios';
 export const img_cdn = 'https://assets.le5lecdn.com';
 export const img_upCdn = 'https://drive.le5lecdn.com';
 
-export const getDownloadList = (meta2dData:any, path:string='v') => {
+const components = [
+  'inputDom',
+  'selectDom',
+  'menuDom',
+  'headMenuDom',
+  'sliderVerifyDom',
+  'dropdownDom',
+  'flvPlayerDom',
+  'countdown',
+  'swiperDom',
+  'threeDSence',
+  'rtspPlayerDom',
+  'timeline',
+  'swiperline',
+  'tab',
+  'radio',
+  'checkbox',
+  'calendar',
+  'indicator',
+  'progress',
+  'pagination',
+  'steps',
+  'notification',
+  'list',
+  'tree',
+  'rockerSwitch',
+  'roundSwitch',
+  'breadcrumb',
+  'transferSwitch',
+  'pie3D',
+  'hikVideo',
+  'thermometer',
+  'watermeter',
+  'indicatorLight',
+  'toggleSwitch',
+  'knifeSwitch',
+  'compass',
+  'thermometer1',
+  'airSwitch',
+  'waterTank',
+];
+
+export const getDownloadList = (meta2dData: any, path: string = 'v') => {
   const lists = new Set();
   //TODO 加一个type区分是数据/还是接口
   //背景图片
   // const meta2dData = meta2d.data();
   let img = meta2dData.bkImage;
   if (img) {
-    if (img.startsWith('/') || img.startsWith(img_cdn) || img.startsWith(img_upCdn)) {
+    if (
+      img.startsWith('/') ||
+      img.startsWith(img_cdn) ||
+      img.startsWith(img_upCdn)
+    ) {
       let _img = img.replace(img_cdn, '').replace(img_upCdn, '');
-      if(_img.startsWith('/v/')){
+      if (_img.startsWith('/v/')) {
         _img = _img.slice(2);
       }
       lists.add({
-        url:img,
-        path:`/view/projects/${path}` +_img,
-      })
+        url: img,
+        path: `/view/projects/${path}` + _img,
+      });
     }
   }
 
   //图片图元(image strokeImage backgroundImage)
-  const imageKeys = ['image','strokeImage','backgroundImage'];
+  const imageKeys = ['image', 'strokeImage', 'backgroundImage'];
   const images: string[] = [];
   for (const pen of meta2dData.pens) {
     for (const i of imageKeys) {
@@ -39,22 +83,22 @@ export const getDownloadList = (meta2dData:any, path:string='v') => {
         ) {
           // 只考虑相对路径下的 image ,绝对路径图片无需下载
           let _img = image.replace(img_cdn, '').replace(img_upCdn, '');
-          if(_img.startsWith('/v/')){
+          if (_img.startsWith('/v/')) {
             _img = _img.slice(2);
           }
           if (!images.includes(image)) {
             // let _img = image.replace(cdn, '').replace(upCdn, '');
             lists.add({
-              url:image,
-              path:`/view/projects/${path}`+_img
+              url: image,
+              path: `/view/projects/${path}` + _img,
             });
           }
-          pen[i] = `/view/projects/${path}`+_img
+          pen[i] = `/view/projects/${path}` + _img;
         }
       }
     }
   }
-  
+
   //其他文件
   const files = [
     '/view/assets/index.js',
@@ -72,12 +116,12 @@ export const getDownloadList = (meta2dData:any, path:string='v') => {
     '/view/index.html',
     '/view/favicon.ico',
     '/view/view.conf',
-    '/view/离线部署包使用说明.pdf'
+    '/view/离线部署包使用说明.pdf',
   ];
-  files.forEach((file)=>{
+  files.forEach((file) => {
     lists.add({
-      url: (cdn ? cdn:'')+  file,
-      path:file
+      url: (cdn ? cdn : '') + file,
+      path: file,
     });
   });
 
@@ -88,9 +132,97 @@ export const getDownloadList = (meta2dData:any, path:string='v') => {
   if ((meta2dData as any).image) delete (meta2dData as any).image;
   checkData(meta2dData);
   lists.add({
-    data:JSON.stringify(meta2dData).replaceAll(img_cdn, '').replaceAll(img_upCdn, ''),
-    path:`/view/projects/${path}/data`,
+    data: JSON.stringify(meta2dData)
+      .replaceAll(img_cdn, '')
+      .replaceAll(img_upCdn, ''),
+    path: `/view/projects/${path}/data`,
   });
 
   return lists;
-}
+};
+
+export const getPayList = (meta2dData: any) => {
+  const pngs = new Set<string>();
+  const jsPens = new Set<string>();
+  const iotPens = new Set<string>();
+  for (const pen of meta2dData.pens) {
+    if (pen.image) {
+      if (
+        pen.image.startsWith(`${img_cdn}/png/`) ||
+        pen.image.startsWith('/png/')
+      ) {
+        pngs.add(pen.image.replace(img_cdn, ''));
+      }
+    } else if (pen.subClassName) {
+      jsPens.add(pen.subClassName);
+    } else if (components.includes(pen.name)) {
+      iotPens.add(pen.name);
+    } else if (pen.svgUrl) {
+      //todo SVG
+      if (
+        pen.svgUrl.startsWith(`${img_cdn}/svg/`) ||
+        pen.svgUrl.startsWith('/svg/')
+      ) {
+        pngs.add(pen.svgUrl.replace(img_cdn, ''));
+      }
+    }
+  }
+
+  return {
+    pngs: [...pngs],
+    jsPens: [...jsPens],
+    iotPens: [...iotPens],
+  };
+};
+
+//获取已购买
+export const getComponentPurchased = async (list: any) => {
+  console.log(list);
+  let names = [...list.pngs, ...list.jsPens, ...list.iotPens];
+  const res: any = await axios.post('/api/paid/2d/component', {
+    names,
+  });
+
+  if (res.error) {
+    return {
+      price: 88888888888,
+      list: [],
+    };
+  }
+  let purchasedPngs = res.list.filter((item) => list.pngs.has(item));
+  let purchasedJs = res.list.filter((item) => list.jsPens.has(item));
+  let purchasedIot = res.list.filter((item) => list.iotPens.has(item));
+  // console.log(
+  //   '过滤',
+  //   [...list.pngs].length,
+  //   purchasedPngs.length,
+  //   [...list.jsPens].length,
+  //   purchasedJs.length,
+  //   [...list.iotPens].length,
+  //   purchasedIot.length
+  // );
+  let price =
+    ([...list.pngs].length - purchasedPngs.length) * 50 +
+    ([...list.jsPens].length - purchasedJs.length) * 10 +
+    ([...list.iotPens].length - purchasedIot.length) * 70;
+
+  return {
+    list: res.list,
+    price,
+    // unPurchase
+  };
+};
+
+export const get2dComponentJs = async (names: string[]) => {
+  const res: any = await axios.post('/api/2d-component.js', {
+    names,
+  });
+  return res;
+};
+
+export const getTemPngs = async (names: string[]) => {
+  const res: any = await axios.post('/api/file/presign', {
+    names,
+  });
+  return res;
+};

+ 11 - 0
src/services/png.ts

@@ -52,4 +52,15 @@ export async function makeSvg(elem: any) {
   let _svgDom = svgDom.replaceAll('#333;', '#fff;');
   elem.svg = _svgDom;
   elem.data = parseSvg(_svgDom);
+  //le5le logo
+  let le5le_svgpath = [
+    'M45.78,68.79c-.17.37-.39.79-.65,1.25s-.54.95-.84,1.44-.6,1-.9,1.43-.59.88-.85,1.25L40.2,73c.27-.37.56-.77.87-1.22s.61-.9.89-1.36.55-.91.79-1.36.44-.83.59-1.18Zm4-1.85v4.12a4.1,4.1,0,0,1-.13,1.17,1.74,1.74,0,0,1-.45.73,2.42,2.42,0,0,1-.81.46c-.33.11-.73.23-1.21.34L46,74.05l-.83-2.59.9-.22a2.73,2.73,0,0,0,.72-.27.65.65,0,0,0,.2-.55V67.31l-5.59.74L42,60.31c.62-.08,1.28-.18,2-.29s1.4-.22,2.11-.34l2.14-.39c.71-.13,1.4-.27,2.06-.41s1.29-.27,1.87-.41,1.11-.26,1.57-.39l.55,2.63-2.08.48c-.76.17-1.57.34-2.41.5l-2.6.49-2.58.45-.17,2.55L47,64.84V62.63l2.78-.31v2.15l4.93-.65v2.47Zm2.72-.22c.17.19.39.45.65.78s.54.69.83,1.08.58.79.86,1.2.54.81.77,1.18l-2.36,1.82c-.18-.35-.4-.74-.65-1.15s-.51-.83-.79-1.24L51,69.25c-.27-.36-.51-.66-.73-.91Z',
+    'M71.3,61.9v2.21L56.66,66V63.82l3.27-.43.25-1-2.39.31V60.84l2.84-.37.21-.87-3.4.44V57.85l13.08-1.73v2.2l-6.85.9-.21.88,6-.79-.15,2.85ZM58.11,66.56,69.86,65v5.63L58.11,72.19Zm2.59,3.09,6.44-.84V67.52l-6.44.85ZM62.78,63l3.87-.51.07-.95L63,62.05Z',
+    'M78,64.55c-.17.37-.39.79-.65,1.25s-.54,1-.84,1.44-.6,1-.9,1.43-.59.88-.85,1.25l-2.34-1.13c.27-.36.56-.76.87-1.21s.61-.9.9-1.36.55-.91.79-1.36.43-.83.58-1.18Zm4-1.85v4.12A4.05,4.05,0,0,1,81.84,68a1.56,1.56,0,0,1-.45.73,2.12,2.12,0,0,1-.8.45c-.33.12-.74.23-1.22.35l-1.17.29-.83-2.6.9-.21a2.17,2.17,0,0,0,.72-.28.6.6,0,0,0,.21-.54V63.07l-5.6.74.58-7.74c.62-.08,1.28-.18,2-.29s1.4-.22,2.11-.34l2.14-.39c.72-.13,1.4-.27,2.07-.41s1.28-.27,1.87-.41,1.1-.26,1.56-.39l.55,2.63L84.38,57c-.77.17-1.57.34-2.42.5s-1.71.33-2.59.49l-2.58.45-.18,2.55,2.59-.34V58.38l2.78-.3v2.15l4.93-.65v2.47Zm2.71-.22q.27.29.66.78l.83,1.08c.29.39.58.79.86,1.2s.53.81.76,1.18l-2.36,1.82c-.18-.35-.4-.74-.65-1.16s-.51-.82-.79-1.23-.54-.78-.81-1.14-.51-.66-.72-.91Z',
+  ];
+  elem.data.forEach((pen) => {
+    if (le5le_svgpath.includes(pen.path)) {
+      (pen as any).svgUrl = elem.image;
+    }
+  });
 }

+ 40 - 23
src/views/Preview.vue

@@ -13,7 +13,7 @@ import { useRouter, useRoute } from 'vue-router';
 import { Meta2d, Options, Pen } from '@meta2d/core';
 import { registerBasicDiagram } from '@/services/register';
 import { cdn, getLe5leV } from '@/services/api';
-import { getDownloadList } from '@/services/download';
+import { getDownloadList, getPayList } from '@/services/download';
 const route = useRoute();
 
 const meta2dDom = ref('');
@@ -39,26 +39,30 @@ onMounted(() => {
   window.addEventListener('message', dealWithMessage);
 });
 
-const dealWithMessage = (e)=>{
+const dealWithMessage = (e) => {
   if (typeof e.data !== 'string') {
-      return;
-    }
-    try {
-      let data = JSON.parse(e.data);
-      if (typeof data === 'object') {
-        if(data.type && data.name==='downloadHtml'){
+    return;
+  }
+  try {
+    let data = JSON.parse(e.data);
+    if (typeof data === 'object') {
+      if (data.type) {
+        if (data.name === 'downloadHtml') {
           //处理下载
           doDownload(data.path);
-        }else{
-          meta2d.emit(data.name);
+        } else if (data.name === 'prePayList') {
+          doGetPayList();
         }
       } else {
-        meta2d.emit(data);
+        meta2d.emit(data.name);
       }
-    } catch (e) {
-      console.info(e);
+    } else {
+      meta2d.emit(data);
     }
-}
+  } catch (e) {
+    console.info(e);
+  }
+};
 
 const watcher = watch(
   () => route.query.id,
@@ -98,17 +102,30 @@ const opened = () => {
   });
 };
 
-const doDownload = (path:string) => {
-  const list = getDownloadList(meta2d.data(),path);
+const doDownload = (path: string) => {
+  const list = getDownloadList(meta2d.data(), path);
   window.parent.postMessage(
     JSON.stringify({
-      name:'download',
-      data:[...list],
-      type:1
-    })
-  , 
-  '*');
-}
+      name: 'download',
+      data: [...list],
+      type: 1,
+    }),
+    '*'
+  );
+};
+
+const doGetPayList = () => {
+  const list = getPayList(meta2d.data());
+  window.parent.postMessage(
+    JSON.stringify({
+      name: 'payList',
+      data: list,
+      type: 1,
+    }),
+    '*'
+  );
+};
+
 onUnmounted(() => {
   watcher();
   if (meta2d) {

+ 566 - 118
src/views/components/Header.vue

@@ -26,7 +26,7 @@
           <a @click="save()">保存</a>
         </t-dropdown-item>
         <t-dropdown-item>
-          <a @click="save(SaveType.SaveAs,'',undefined,1)">另保存</a>
+          <a @click="save(SaveType.SaveAs, '', undefined, 1)">另保存</a>
         </t-dropdown-item>
         <t-dropdown-item divider="true">
           <a @click="downloadJson">下载JSON文件</a>
@@ -167,7 +167,7 @@
             <div class="flex middle">
               鹰眼地图 <span class="flex-grow"></span>
               <!-- <t-icon v-show="map" name="check" /> -->
-              <check-icon v-show="map"/>
+              <check-icon v-show="map" />
             </div>
           </a>
         </t-dropdown-item>
@@ -176,7 +176,7 @@
             <div class="flex middle">
               放大镜 <span class="flex-grow"></span>
               <!-- <t-icon v-show="magnifier" name="check" /> -->
-              <check-icon v-show="magnifier"/>
+              <check-icon v-show="magnifier" />
             </div>
           </a>
         </t-dropdown-item>
@@ -185,7 +185,7 @@
             <div class="flex middle">
               自动锚点 <span class="flex-grow"></span>
               <!-- <t-icon v-show="autoAnchor" name="check" /> -->
-              <check-icon v-show="autoAnchor"/>
+              <check-icon v-show="autoAnchor" />
             </div>
           </a>
         </t-dropdown-item>
@@ -194,7 +194,7 @@
             <div class="flex middle">
               显示锚点 <span class="flex-grow"></span>
               <!-- <t-icon v-show="showAnchor" name="check" /> -->
-              <check-icon  v-show="showAnchor"/>
+              <check-icon v-show="showAnchor" />
             </div>
           </a>
         </t-dropdown-item>
@@ -234,22 +234,22 @@
 
     <a :href="enterprise.account" target="_blank">
       <!-- <t-icon name="home" /> -->
-      <home-icon/>
+      <home-icon />
       账户中心
     </a>
     <a :href="enterprise['v']" target="_blank" class="active">
       <!-- <t-icon name="desktop" /> -->
-      <desktop-icon/>
+      <desktop-icon />
       大屏可视化
     </a>
     <a :href="enterprise['3d']" target="_blank">
       <!-- <t-icon name="control-platform" /> -->
-      <control-platform-icon/>
+      <control-platform-icon />
       3D可视化
     </a>
     <a :href="enterprise['2d']" target="_blank">
       <!-- <t-icon name="app" /> -->
-      <app-icon/>
+      <app-icon />
       2D可视化
     </a>
     <t-dropdown
@@ -308,6 +308,147 @@
       </a>
     </div>
   </div>
+  <t-dialog
+    v-if="payListDialog.show"
+    v-model:visible="payListDialog.show"
+    class="pay-dialog"
+    header="图纸用到的企业图形库"
+    :close-on-overlay-click="false"
+    :top="95"
+    :width="700"
+    cancel-btn="跳过"
+    confirm-btn="结算(不支持退款)"
+    @cancel="skipPay"
+    @close="payListDialog.show = false"
+    @confirm="prePay"
+  >
+    <div class="pay-line">
+      <div></div>
+      <div>已购买</div>
+      <div>价格</div>
+    </div>
+    <div class="pay-body">
+      <t-collapse defaultExpandAll>
+        <t-collapse-panel
+          v-if="[...prePayList.pngs].length"
+          value="0"
+          header="图元"
+        >
+          <template #headerRightContent>
+            <t-space size="small">
+              <t-checkbox v-model="ss">全部</t-checkbox>
+              (1000个,5折)
+            </t-space>
+          </template>
+          <div v-for="pngPen in prePayList.pngs" class="pay-line">
+            <div>
+              <t-image :src="pngPen" :lazy="true" fit="contain" />
+              {{
+                pngPen.slice(
+                  pngPen.lastIndexOf('/') + 1,
+                  pngPen.lastIndexOf('.')
+                )
+              }}
+            </div>
+            <div>
+              <check-icon
+                v-show="purchasedList.includes(pngPen)"
+                style="color: #4480f9ff"
+              />
+            </div>
+            <div>
+              {{ purchasedList.includes(pngPen) ? 0 : 50 }}
+            </div>
+          </div>
+          <div class="pay-tip">
+            <p>已选择3个图元 小计:¥200</p>
+          </div>
+        </t-collapse-panel>
+        <t-collapse-panel
+          v-if="[...prePayList.iotPens].length"
+          value="1"
+          header="控件"
+        >
+          <template #headerRightContent>
+            <t-space size="small">
+              <t-checkbox v-model="ss">全部</t-checkbox>
+              (1000个,5折)
+            </t-space>
+          </template>
+          <div v-for="iotPen in prePayList.iotPens" class="pay-line">
+            <div>
+              <svg class="l-icon" aria-hidden="true">
+                <use :xlink:href="'#' + iotPensMap[iotPen].icon"></use>
+              </svg>
+              {{ iotPensMap[iotPen].name }}
+            </div>
+            <div>
+              <check-icon
+                v-show="purchasedList.includes(iotPen)"
+                style="color: #4480f9ff"
+              />
+            </div>
+            <div>{{ purchasedList.includes(iotPen) ? 0 : 70 }}</div>
+          </div>
+          <div class="pay-tip">
+            <p>已选择3个图元 小计:¥200</p>
+          </div>
+        </t-collapse-panel>
+        <t-collapse-panel
+          v-if="[...prePayList.jsPens].length"
+          value="3"
+          header="js图元"
+        >
+          <template #headerRightContent>
+            <t-space size="small">
+              <t-checkbox v-model="ss">全部</t-checkbox>
+              (1000个,5折)
+            </t-space>
+          </template>
+          <div v-for="jsPen in prePayList.jsPens" class="pay-line">
+            <div>{{ jsPen }}</div>
+            <div>
+              <check-icon
+                v-show="purchasedList.includes(jsPen)"
+                style="color: #4480f9ff"
+              />
+            </div>
+            <div>{{ purchasedList.includes(jsPen) ? 0 : 70 }}</div>
+          </div>
+          <div class="pay-tip">
+            <p>已选择3个图元 小计:¥200</p>
+          </div>
+        </t-collapse-panel>
+      </t-collapse>
+    </div>
+    <div class="flex pay-footer">
+      <p>企业图形库购买一次,永久使用</p>
+      <div class="flex pay-price">
+        共100个图元,合计
+        <p>¥</p>
+        <p>{{ payPrice }}</p>
+      </div>
+    </div>
+  </t-dialog>
+  <t-dialog
+    v-if="wechatPayDialog.show"
+    v-model:visible="wechatPayDialog.show"
+    class="pay-dialog"
+    header="乐吾乐收银台"
+    :close-on-overlay-click="false"
+    :top="95"
+    :width="700"
+    confirm-btn="支付完成"
+    :cancel-btn="null"
+    @close="finishPay"
+    @confirm="finishPay"
+  >
+    <WechatPay
+      :order="data.order"
+      :code-url="data.order.codeUrl"
+      @success="onSuccess"
+    />
+  </t-dialog>
 </template>
 
 <script lang="ts" setup>
@@ -347,8 +488,22 @@ import {
   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';
+import {
+  CheckIcon,
+  HomeIcon,
+  DesktopIcon,
+  ControlPlatformIcon,
+  AppIcon,
+} from 'tdesign-icons-vue-next';
+import {
+  getDownloadList,
+  getPayList,
+  getComponentPurchased,
+  get2dComponentJs,
+  getTemPngs,
+} from '@/services/download';
+import { formComponents } from '@/services/defaults';
+import WechatPay from './WechatPay.vue';
 
 const { enterprise } = useEnterprise();
 const router = useRouter();
@@ -362,6 +517,10 @@ const { user, signout } = useUser();
 const { setDot } = useDot();
 const data = reactive({
   name: '空白文件',
+  order: {
+    codeUrl: '',
+    id: '', //订单id
+  },
 });
 
 onBeforeMount(async () => {
@@ -381,36 +540,88 @@ const initMeta2dName = () => {
   data.name = (meta2d.store.data as Meta2dBackData).name || '';
 };
 
-let downloadList = new Set();
+let downloadList = new Set<any>();
 let iframeNum = 0;
 let compareNum = 0;
+const prePayList = reactive({
+  pngs: new Set<string>(),
+  jsPens: new Set<string>(),
+  iotPens: new Set<string>(),
+});
+let payListNum = 0;
+let comparePayListNum = 0;
+let purchasedList = []; //已购买列表
+const iotPensMap = {};
+const payPrice = ref(0);
+
+const getIotPensMap = () => {
+  formComponents.forEach((item) => {
+    item.list.forEach((_item) => {
+      iotPensMap[_item.data.name] = { name: _item.name, icon: _item.icon };
+    });
+  });
+};
 nextTick(() => {
   meta2d.on('opened', initMeta2dName);
   window.addEventListener('message', dealWithMessage);
+  getIotPensMap();
 });
 
-const dealWithMessage = (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'){
+const dealWithMessage = async (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) {
+        if (data.name === 'download') {
           downloadList = new Set([...downloadList, ...data.data]);
-          compareNum+=1;
-          if(compareNum>=iframeNum){
+          compareNum += 1;
+          if (compareNum >= iframeNum) {
             saveDownload();
           }
+        } else if (data.name === 'payList') {
+          console.log('data.data', data);
+          prePayList.pngs = new Set([...prePayList.pngs, ...data.data.pngs]);
+          prePayList.jsPens = new Set([
+            ...prePayList.jsPens,
+            ...data.data.jsPens,
+          ]);
+          prePayList.iotPens = new Set([
+            ...prePayList.iotPens,
+            ...data.data.iotPens,
+          ]);
+
+          comparePayListNum += 1;
+          if (comparePayListNum >= payListNum) {
+            console.log('进入');
+            if (
+              ![...prePayList.pngs].length &&
+              ![...prePayList.jsPens].length &&
+              ![...prePayList.iotPens].length
+            ) {
+              //直接下载
+              preDownload(meta2d.data());
+            } else {
+              await showPayListDialog();
+            }
+          }
         }
-        meta2d.emit(data.name);
       } else {
-        meta2d.emit(data);
+        meta2d.emit(data.name);
       }
-    } catch (e) {
-      console.info(e);
+    } else {
+      meta2d.emit(data);
     }
-}
+  } catch (e) {
+    console.info(e);
+  }
+};
 
 onUnmounted(() => {
   meta2d.off('opened', initMeta2dName);
@@ -427,9 +638,9 @@ function load(isNew = false) {
   input.onchange = (event) => {
     const elem = event.target as HTMLInputElement;
     if (elem.files && elem.files[0]) {
-      blank();
       // 路由跳转 可能在 openFile 后执行
       if (elem.files[0].name.endsWith('.json')) {
+        blank();
         openJson(elem.files[0]);
         if (isNew) {
           router.push({
@@ -445,6 +656,7 @@ function load(isNew = false) {
         );
         openSvg(elem.files[0]);
       } else if (elem.files[0].name.endsWith('.zip')) {
+        blank();
         router.push({
           path: '/',
           query: {
@@ -690,7 +902,7 @@ const downloadZip = async () => {
   saveAs(blob, `${_fileName}.zip`);
 };
 
-const zip3D = (name:string) => {
+const zip3D = (name: string) => {
   const pen_3d = meta2d.store.data.pens.filter(
     (pen) =>
       pen.name === 'iframe' &&
@@ -726,167 +938,262 @@ const downloadHtml = async () => {
     return;
   }
 
-  if(user.vipDesc !== '超级会员'&&user.vipDesc !=='旗舰会员'){
+  if (user.vipDesc !== '超级会员' && user.vipDesc !== '旗舰会员') {
     MessagePlugin.info('需要开通超级会员~');
     gotoAccount();
-    return
+    return;
+  }
+
+  //图形库需要购买
+  const meta2dData = meta2d.data();
+  let list = getPayList(meta2dData);
+  console.log('list', list);
+  prePayList.pngs = new Set(list.pngs);
+  prePayList.jsPens = new Set(list.jsPens);
+  prePayList.iotPens = new Set(list.iotPens);
+
+  //向iframe发送消息
+  payListNum = 0;
+  comparePayListNum = 0;
+  const pen_pay = meta2dData.pens.filter(
+    (pen) =>
+      pen.name === 'iframe' &&
+      (pen.iframe.indexOf('2d.le5le.com') !== -1 ||
+        pen.iframe.indexOf('/2d/') !== -1 ||
+        pen.iframe.indexOf('v.le5le.com') !== -1 ||
+        pen.iframe.indexOf('/v/') !== -1 ||
+        pen.iframe.indexOf('/preview') !== -1)
+  );
+  if (pen_pay && pen_pay.length) {
+    //存在3d场景
+    pen_pay.forEach((pen) => {
+      //发送消息
+      (
+        meta2d.store.pens[pen.id].calculative.singleton.div
+          .children[0] as HTMLIFrameElement
+      ).contentWindow.postMessage(
+        JSON.stringify({
+          name: 'prePayList',
+          type: 1,
+        }),
+        '*'
+      );
+      console.log('发送消息');
+      payListNum += 1;
+    });
+  }
+  if (payListNum === 0) {
+    //无嵌入页面
+    if (!list.pngs.length && !list.jsPens.length && !list.iotPens.length) {
+      preDownload(meta2dData);
+    } else {
+      await showPayListDialog();
+    }
   }
+};
 
+const showPayListDialog = async () => {
+  console.log('prePayList', prePayList);
+  let purchased = await getComponentPurchased(prePayList);
+  console.log(purchased);
+  if (purchased.price) {
+    purchasedList = purchased.list;
+    payPrice.value = purchased.price;
+    payListDialog.show = true;
+  } else {
+    //价格为0,直接下载
+    preDownload(meta2d.data());
+  }
+};
+const preDownload = (meta2dData: any) => {
   MessagePlugin.info('正在下载打包中,可能需要几分钟,请耐心等待...');
+  iframeNum = 0;
   compareNum = 0;
-  const meta2dData = meta2d.data();
 
   const pen_3d = meta2dData.pens.filter(
     (pen) =>
       pen.name === 'iframe' &&
-      (pen.iframe.indexOf('3d.le5le.com') !== -1||pen.iframe.indexOf('/3d/') !== -1)
+      (pen.iframe.indexOf('3d.le5le.com') !== -1 ||
+        pen.iframe.indexOf('/3d/') !== -1)
   );
   if (pen_3d && pen_3d.length) {
     //存在3d场景
-    if(pen_3d.length===1){
+    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`
-          }),
-          '*'
+      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[0].iframe = '/view?data=3d';
-    }else{
+    } else {
       pen_3d.forEach((pen) => {
         //发送消息
         let params = queryURLParams(pen.iframe.split('?')[1]);
         (
-          meta2d.store.pens[pen.id].calculative.singleton.div.children[0] as HTMLIFrameElement
+          meta2d.store.pens[pen.id].calculative.singleton.div
+            .children[0] as HTMLIFrameElement
         ).contentWindow.postMessage(
           JSON.stringify({
-            name:'deploy',
+            name: 'deploy',
             // id: params.id,
-            type:1,
-            path:`3d-${params.id}` 
+            type: 1,
+            path: `3d-${params.id}`,
           }),
           '*'
         );
         pen.iframe = `/view?data=3d-${params.id}`;
       });
     }
-    iframeNum+= pen_3d.length;
+    iframeNum += pen_3d.length;
   }
 
   const pen_2d = meta2dData.pens.filter(
     (pen) =>
       pen.name === 'iframe' &&
-      (pen.iframe.indexOf('2d.le5le.com') !== -1||pen.iframe.indexOf('/2d/') !== -1)
+      (pen.iframe.indexOf('2d.le5le.com') !== -1 ||
+        pen.iframe.indexOf('/2d/') !== -1)
   );
   if (pen_2d && pen_2d.length) {
     //存在3d场景
-    if(pen_2d.length===1){
+    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`
-          }),
-          '*'
+      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[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
+          meta2d.store.pens[pen.id].calculative.singleton.div
+            .children[0] as HTMLIFrameElement
         ).contentWindow.postMessage(
           JSON.stringify({
-            name:'downloadHtml',
+            name: 'downloadHtml',
             // id: params.id,
-            type:1,
-            path:`2d-${params.id}`
+            type: 1,
+            path: `2d-${params.id}`,
           }),
           '*'
         );
-        pen.iframe = `/view?data=2d-${params.id}`
+        pen.iframe = `/view?data=2d-${params.id}`;
       });
     }
-    iframeNum+= pen_2d.length;
+    iframeNum += pen_2d.length;
   }
 
   const pen_v = meta2dData.pens.filter(
     (pen) =>
       pen.name === 'iframe' &&
-      (pen.iframe.indexOf('v.le5le.com') !== -1||pen.iframe.indexOf('/v/') !== -1)
+      (pen.iframe.indexOf('v.le5le.com') !== -1 ||
+        pen.iframe.indexOf('/v/') !== -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}`
+    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;
+    iframeNum += pen_v.length;
   }
 
   downloadList = getDownloadList(meta2dData);
-  
-  if(iframeNum===0){
+
+  if (iframeNum === 0) {
     //如果没有嵌入场景
     saveDownload();
-  }else{
-    setTimeout(()=>{
-      if(compareNum < iframeNum){
+  } else {
+    setTimeout(() => {
+      if (compareNum < iframeNum) {
         //message阻塞/报错的情况
         saveDownload();
       }
-    },10000);
+    }, 10000);
   }
-}
+};
+
+const saveDownload = async () => {
+  const list = [...downloadList];
+  //控件
+  const js = await get2dComponentJs([...prePayList.iotPens]);
+  //  let o=window.dc;
+  //   let e=window.mk;
+  //   let uncodeJs = o(e(user.id), js);
+  //   console.log('uncodeJs',uncodeJs);
+  ///png图形库
+  const pngs = await getTemPngs([...prePayList.pngs]);
+  list.push({
+    data: js,
+    path: '/view/js/2.text',
+  });
+  list.forEach((item) => {
+    if (pngs[item.url]) {
+      item.url = pngs[item.url];
+    }
+  });
+  console.log('down', list);
 
-const saveDownload = async()=>{
+  //iot 2d-component重写
+  //pngs 替换图片请求地址
+  //js ?
+  // return;
   const [{ default: JSZip }, { saveAs }] = await Promise.all([
     import('jszip'),
     import('file-saver'),
   ]);
   const zip = new JSZip();
 
-  await Promise.all([...downloadList].map(async(item:any)=>{
-    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 }
-      );
-    }
-  }));
+  await Promise.all(
+    list.map(async (item: any) => {
+      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 =
-    (meta2d.store.data.name && meta2d.store.data.name.replace(/\//g, '_').replace(/:/g, '_')) ||
+    (meta2d.store.data.name &&
+      meta2d.store.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)) {
@@ -899,10 +1206,10 @@ const _downloadHtml = async () => {
   //   return;
   // }
 
-  if(user.vipDesc !== '超级会员'&&user.vipDesc !=='旗舰会员'){
+  if (user.vipDesc !== '超级会员' && user.vipDesc !== '旗舰会员') {
     MessagePlugin.info('需要开通超级会员~');
     gotoAccount();
-    return
+    return;
   }
   frameFlag = -1;
   MessagePlugin.info('正在下载打包中,可能需要几分钟,请耐心等待...');
@@ -975,14 +1282,16 @@ async function downloadAsFrame(type: Frame) {
   //   gotoAccount();
   //   return;
   // }
-  if(user.vipDesc !=='旗舰会员'){
+  if (user.vipDesc !== '旗舰会员') {
     MessagePlugin.info('需要开通旗舰会员~');
     gotoAccount();
-    return
+    return;
   }
   frameFlag = type;
   MessagePlugin.info('正在下载打包中,可能需要几分钟,请耐心等待...');
-  zip3D(type===Frame.vue3?'toVue3':(type===Frame.vue2?'toVue2':'toReact'));
+  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;
@@ -1124,7 +1433,6 @@ async function zipReactFiles(zip: JSZip) {
     '/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)));
@@ -1146,9 +1454,12 @@ async function zipFiles(zip: JSZip) {
 }
 
 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',
-  });
+  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 });
 }
 
@@ -1414,6 +1725,54 @@ const changeDisableAnchor = () => {
     onAutoAnchor();
   }
 };
+
+const payListDialog = reactive({
+  show: false,
+});
+
+const prePay = async () => {
+  //预支付 //TODO: 这个可以发送已经购买的图元吗
+  const res: any = await axios.post('/api/order/2d/component/submit', {
+    pngs: [...prePayList.pngs],
+    jsPens: [...prePayList.jsPens],
+    iotPens: [...prePayList.iotPens],
+  });
+  console.log('res', res);
+  wechatPayDialog.show = true;
+  data.order = res;
+};
+
+const skipPay = () => {
+  //跳过支付,直接下载
+  preDownload(meta2d.data());
+};
+
+const finishPay = async () => {
+  console.log('进入');
+  let id = data.order.id;
+  const result: { state: number } = await axios.post('/api/order/pay/state', {
+    id,
+  });
+  if (result && result.state) {
+    MessagePlugin.success('支付成功');
+    wechatPayDialog.show = false;
+    payListDialog.show = false;
+    preDownload(meta2d.data());
+  } else {
+    MessagePlugin.error('支付失败');
+    wechatPayDialog.show = false;
+  }
+};
+
+const wechatPayDialog = reactive({
+  show: false,
+});
+
+const emit = defineEmits(['success']);
+
+const onSuccess = (success: boolean) => {
+  emit('success', success);
+};
 </script>
 <style lang="postcss" scoped>
 .app-header {
@@ -1469,4 +1828,93 @@ const changeDisableAnchor = () => {
     color: var(--color-title);
   }
 }
+
+.pay-dialog {
+  background-color: red;
+}
+
+.pay-body {
+  height: 300px;
+  overflow-y: scroll;
+  margin-bottom: 30px;
+  .t-collapse {
+    border: 0px;
+    :deep(.t-collapse-panel__wrapper .t-collapse-panel__header) {
+      border-bottom: 0px;
+    }
+    :deep(.t-collapse-panel__wrapper .t-collapse-panel__body) {
+      background: #fff0;
+      border-bottom: 0px;
+      .t-collapse-panel__content {
+        padding: 0;
+        color: #c1c8d7;
+      }
+    }
+  }
+}
+.pay-line {
+  display: flex;
+  height: 54px;
+  background-color: #afcaff0a;
+  margin-top: 1px;
+  div {
+    width: 30%;
+    text-align: center;
+    line-height: 54px;
+    :deep(.t-image) {
+      width: 40px;
+      height: 40px;
+      margin-top: 7px;
+    }
+    .l-icon {
+      width: 40px;
+      height: 40px;
+      margin-top: 7px;
+      width: 30%;
+    }
+  }
+
+  & > div:first-child {
+    width: 40%;
+    display: flex;
+  }
+}
+.pay-tip {
+  height: 54px;
+  width: 100%;
+  position: relative;
+  p {
+    position: absolute;
+    right: 16px;
+  }
+}
+.pay-footer {
+  margin-bottom: -42px;
+  position: relative;
+  .pay-price {
+    position: absolute;
+    right: 215px;
+    /* margin-left: 50px; */
+    align-items: flex-end;
+    line-height: 10px;
+    /* p{
+    vertical-align: bottom;
+    line-height: normal;
+  } */
+    p:nth-child(2) {
+      font-size: 32px;
+      font-weight: Semibold;
+      color: #4480f9;
+      line-height: 20px;
+    }
+    p:nth-child(1) {
+      font-size: 14px;
+      color: #4480f9;
+    }
+  }
+}
+
+.pay-title {
+  margin: 18px 0px 12px 18px;
+}
 </style>

+ 3 - 0
vite.config.ts

@@ -44,6 +44,9 @@ export default defineConfig({
       '/file': 'http://192.168.110.6:777',
       '/api': 'http://192.168.110.6:777',
       '/v/material': 'http://192.168.110.6:777',
+      '/png': 'http://192.168.110.6:777',
+      '/svg': 'http://192.168.110.6:777',
+
       //java 后端 http://192.168.110.6:8083
       // '/image': 'https://v.le5le.com/',
       // '/file': 'https://v.le5le.com/',