Alsmile 1 年之前
父节点
当前提交
359caf88d2
共有 4 个文件被更改,包括 192 次插入228 次删除
  1. 5 0
      src/services/defaults.ts
  2. 1 5
      src/services/png.ts
  3. 5 0
      src/styles/var.css
  4. 181 223
      src/views/components/Graphics.vue

+ 5 - 0
src/services/defaults.ts

@@ -1973,3 +1973,8 @@ export const cases: any[] = [
   { name: '航天航空' },
   { name: '智能家居' },
 ];
+
+export const templates: any[] = [
+  { name: '科技蓝', list: [] },
+  { name: '简约', list: [] },
+];

+ 1 - 5
src/services/png.ts

@@ -1,11 +1,7 @@
 import axios from '@/http';
 import { parseSvg } from '@meta2d/svg';
 import { cdn } from './api';
-
-export function filename(str: string) {
-  const i = str.lastIndexOf('.');
-  return str.substring(0, i);
-}
+import { filename } from './file';
 
 export async function getFolders(name: string, isSvg?: boolean) {
   const path = 'v/' + name;

+ 5 - 0
src/styles/var.css

@@ -74,6 +74,11 @@
   --td-bg-color-specialcomponent: transparent;
   --td-border-level-2-color: var(--color-border-input);
 
+  --td-shadow-inset-top: inset 0 0.5px 0 #555;
+  --td-shadow-inset-right: inset 0.5px 0 0 #555;
+  --td-shadow-inset-bottom: inset 0 -0.5px 0 #555;
+  --td-shadow-inset-left: inset -0.5px 0 0 #555;
+
   --color-background-editor: #181b24;
   --color-border-editor: #1e2430;
 }

+ 181 - 223
src/views/components/Graphics.vue

@@ -45,17 +45,14 @@
           <div v-if="activedGroup === '我的'" class="px-16 mt-12 mb-8 ml-4">
             <a @click="onCreateFolder">+ 新建文件夹</a>
           </div>
-          <t-collapse
-            v-if="groupType < 2"
-            v-model="activedPanels[activedGroup]"
-          >
+          <t-collapse v-model="activedPanels[activedGroup]">
             <t-collapse-panel
               :value="item.name"
               v-for="item in subGroups"
               :key="item.name"
             >
               <template #header>
-                <div class="flex middle mr-8">
+                <div class="flex middle">
                   <div v-if="item.edited" @click.stop>
                     <t-input
                       v-model="item.label"
@@ -63,28 +60,45 @@
                       @blur="createFoder"
                       @enter="createFoder"
                       @keyup="onKeyHeader"
+                      :autofocus="true"
                     />
                   </div>
-                  <div v-else class="ellipsis">
+                  <div v-else>
                     {{ item.name }}
                   </div>
                 </div>
               </template>
-              <template #headerRightContent v-if="item.canEdited">
-                <t-space size="small" @click.stop>
-                  <t-icon
-                    name="edit"
-                    class="hover mr-4"
-                    @click="onEditHeader(item)"
-                  />
-
-                  <t-popconfirm
-                    content="确认删除该文件夹吗"
-                    placement="left"
-                    @confirm=""
+              <template #headerRightContent>
+                <t-space size="small" @click.stop tabindex="0">
+                  <t-upload
+                    v-if="item.canEdited || item.name === '我的组件'"
+                    action="/api/image/upload"
+                    accept="image/*"
+                    :headers="headers"
+                    :data="updataData"
+                    :auto-upload="true"
+                    :upload-all-files-in-one-request="false"
+                    @selectChange="onSelectFiles(item)"
+                    @success="fileSuccessed"
+                    theme="custom"
                   >
-                    <t-icon name="delete" class="hover" />
-                  </t-popconfirm>
+                    <t-icon name="image" class="hover" />
+                  </t-upload>
+
+                  <template v-if="item.canEdited">
+                    <t-icon
+                      name="edit"
+                      class="hover"
+                      @click="onEditHeader(item)"
+                    />
+                    <t-popconfirm
+                      content="确认删除该文件夹吗"
+                      placement="left"
+                      @confirm="delFolder(item)"
+                    >
+                      <t-icon name="delete" class="hover" />
+                    </t-popconfirm>
+                  </template>
                 </t-space>
               </template>
 
@@ -124,41 +138,6 @@
               </div>
             </t-collapse-panel>
           </t-collapse>
-          <div v-else class="t-collapse-panel__content" style="padding: 8px">
-            <template v-for="elem in subGroups">
-              <div
-                class="graphic"
-                :draggable="true"
-                v-show="elem.visible !== false"
-                @dragstart="dragStart($event, elem)"
-                @click.stop="dragStart($event, elem)"
-                @dblclick.stop="open(elem)"
-              >
-                <t-image
-                  v-if="elem.image"
-                  :src="elem.image"
-                  :lazy="true"
-                  fit="contain"
-                />
-                <div class="svg-box" v-else-if="elem.svg" v-html="elem.svg" />
-                <svg v-else class="l-icon" aria-hidden="true">
-                  <use :xlink:href="'#' + elem.icon"></use>
-                </svg>
-                <p :title="elem.name">{{ elem.name }}</p>
-                <div class="price" v-if="elem.price > 0">
-                  ¥{{ elem.price }}
-                </div>
-              </div>
-            </template>
-
-            <div
-              v-if="!subGroups.length"
-              class="gray center"
-              style="white-space: nowrap; margin-left: 32px"
-            >
-              暂无数据
-            </div>
-          </div>
         </template>
       </div>
     </div>
@@ -166,17 +145,17 @@
     <div
       class="context-menu-box"
       ref="contextmenuDom"
-      v-if="contextmenu.visible"
+      v-show="contextmenu.visible"
       tabindex="0"
       :style="contextmenu.style"
-      @blur="contextmenu.visible = false"
+      @blur="hideContextmenu"
     >
       <t-menu class="context-menu" @change="onMenu" expandType="popup">
         <t-submenu
           value="move"
           title="移动到"
           v-if="contextmenu.subMenus.length"
-          :disabled="!contextmenu.component.component"
+          :disabled="contextmenu.component['3d']"
         >
           <t-menu-item
             v-for="subMenu in contextmenu.subMenus"
@@ -185,8 +164,10 @@
             {{ subMenu.name }}
           </t-menu-item>
         </t-submenu>
-        <t-menu-item value="edit"> 编辑 </t-menu-item>
-        <t-menu-item value="del" :disabled="!contextmenu.component.component">
+        <t-menu-item value="edit" :disabled="!contextmenu.component.component">
+          编辑
+        </t-menu-item>
+        <t-menu-item value="del" :disabled="contextmenu.component['3d']">
           删除
         </t-menu-item>
       </t-menu>
@@ -247,23 +228,30 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, reactive, ref, shallowRef } from 'vue';
+import { onMounted, onUnmounted, reactive, ref } from 'vue';
 import { useRouter } from 'vue-router';
 import { MessagePlugin } from 'tdesign-vue-next';
 import axios from 'axios';
+import { deepClone } from '@meta2d/core';
 
-import { cases, shapes, formComponents } from '@/services/defaults';
+import { cases, shapes, formComponents, templates } from '@/services/defaults';
 import { charts } from '@/services/echarts';
 import { getFolders, makeSvg } from '@/services/png';
-import { getComponentsList, getLe5leV, updateCollection } from '@/services/api';
+import {
+  addCollection,
+  getComponentsList,
+  getLe5leV,
+  updateCollection,
+} from '@/services/api';
 import { convertPen } from '@/services/upgrade';
-import { deepClone } from '@meta2d/core';
 import { isGif } from '@/services/utils';
 import { autoSave, delAttrs } from '@/services/common';
-import { debounce } from '@/services/debouce';
+import { debounce, throttle } from '@/services/debouce';
 import { searchObjectPinyin } from '@/services/pinyin';
+import { getCookie } from '@/services/cookie';
 
 import WechatPay from './WechatPay.vue';
+import { filename } from '@/services/file';
 
 const router = useRouter();
 
@@ -312,14 +300,13 @@ const groups = reactive([
     key: '',
   },
 ]);
-const subGroups = shallowRef<any[]>([]);
+const subGroups = ref<any[]>([]);
 const groupType = ref(0);
 const activedPanels = reactive<any>({});
 
 const caseCaches = [];
-const templates = [];
+const templateCaches = [];
 const materials = [];
-// 数量太大,禁用响应式
 const pngs = [];
 
 let dropped = false;
@@ -332,6 +319,11 @@ const search = ref('');
 
 const loading = ref(false);
 
+const headers = {
+  Authorization: 'Bearer ' + (localStorage.token || getCookie('token') || ''),
+};
+const updataData = { directory: '/项目' };
+
 const groupChange = async (name: string) => {
   activedGroup.value = name;
   groupType.value = 0;
@@ -356,10 +348,21 @@ const groupChange = async (name: string) => {
 
       break;
     case '模板':
-      if (!templates.length) {
-        templates.push(...(await getCaseProjects(name)));
+      if (!templateCaches.length) {
+        loading.value = true;
+        templateCaches.push(...(await getCaseProjects(name)));
+        for (const group of templates) {
+          group.list = [];
+          for (const item of templateCaches) {
+            if (item.case === group.name) {
+              group.list.push(item);
+            }
+          }
+        }
+        loading.value = false;
       }
-      groupType.value = 2;
+
+      groupType.value = 1;
       subGroups.value = templates;
       break;
     case '图表':
@@ -369,7 +372,7 @@ const groupChange = async (name: string) => {
       subGroups.value = formComponents;
       break;
     case '素材':
-      groupType.value = 2;
+      groupType.value = 1;
       if (!materials.length) {
         materials.push(...(await getFolders('material')));
       }
@@ -652,6 +655,11 @@ const getPrivateGraphics = async () => {
 
 const editedFolder = ref<any>(undefined);
 const onCreateFolder = () => {
+  activedPanels[activedGroup.value].splice(
+    0,
+    activedPanels[activedGroup.value].length
+  );
+
   editedFolder.value = {
     _id: '',
     name: '',
@@ -721,7 +729,7 @@ const contextmenu = reactive<any>({
   // 子分类
   group: undefined,
   // 组件图纸
-  component: undefined,
+  component: {},
   // 右键二级子菜单
   subMenus: [],
 });
@@ -768,7 +776,11 @@ const delDialog = reactive<any>({});
 
 const onMenu = async (val: string) => {
   const id = contextmenu.component._id || contextmenu.component.id;
-
+  setTimeout(() => {
+    contextmenu.group = '';
+    contextmenu.component = {};
+    contextmenu.subMenus = [];
+  }, 500);
   switch (val) {
     case 'edit':
       if (contextmenu.component.component) {
@@ -840,7 +852,9 @@ const onMenu = async (val: string) => {
       }
       break;
   }
+};
 
+const hideContextmenu = () => {
   contextmenu.visible = false;
 };
 
@@ -909,155 +923,25 @@ const onSearch = () => {
 };
 
 const searchGraphics = async () => {
-  switch (activedGroup.value) {
-    case '场景':
-      subGroups.value = [];
-      if (search.value) {
-        activedPanels[activedGroup.value] = [];
-        for (const item of cases) {
-          activedPanels[activedGroup.value].push(item.name);
-        }
-      }
-      for (const item of caseCaches) {
-        if (search.value) {
-          item.visible = searchObjectPinyin(item, 'name', search.value);
-        } else {
-          item.visible = true;
-        }
-      }
-      setTimeout(() => {
-        subGroups.value = cases;
-      });
-      break;
-    case '模板':
-      subGroups.value = [];
-      for (const item of templates) {
-        if (search.value) {
-          item.visible = searchObjectPinyin(item, 'name', search.value);
-        } else {
-          item.visible = true;
-        }
-      }
-      setTimeout(() => {
-        subGroups.value = templates;
-      });
-      break;
-    case '图表':
-      subGroups.value = [];
-      if (search.value) {
-        activedPanels[activedGroup.value] = [];
-      }
-      for (const group of charts) {
-        if (search.value) {
-          activedPanels[activedGroup.value].push(group.name);
-        }
-        for (const item of group.list) {
-          if (search.value) {
-            // @ts-ignore
-            item.visible = searchObjectPinyin(item, 'name', search.value);
-          } else {
-            // @ts-ignore
-            item.visible = true;
-          }
-        }
-      }
-      setTimeout(() => {
-        subGroups.value = charts;
-      });
-      break;
-    case '控件':
-      subGroups.value = [];
-      if (search.value) {
-        activedPanels[activedGroup.value] = [];
-      }
-      for (const group of formComponents) {
-        if (search.value) {
-          activedPanels[activedGroup.value].push(group.name);
-        }
-        for (const item of group.list) {
-          if (search.value) {
-            // @ts-ignore
-            item.visible = searchObjectPinyin(item, 'name', search.value);
-          } else {
-            // @ts-ignore
-            item.visible = true;
-          }
-        }
-      }
-      setTimeout(() => {
-        subGroups.value = formComponents;
-      });
+  if (search.value) {
+    activedPanels[activedGroup.value].splice(
+      0,
+      activedPanels[activedGroup.value].length
+    );
+  }
 
-      break;
-    case '素材':
-      subGroups.value = [];
-      for (const item of materials) {
-        if (search.value) {
-          item.visible = searchObjectPinyin(item, 'name', search.value);
-        } else {
-          item.visible = true;
-        }
-      }
-      setTimeout(() => {
-        subGroups.value = materials;
-      });
-      break;
-    case '图元':
+  for (const group of subGroups.value) {
+    for (const item of group.list) {
       if (search.value) {
-        activedPanels[activedGroup.value] = [];
-        for (const group of subGroups.value) {
-          activedPanels[activedGroup.value].push(group.name);
-        }
-      }
-      subGroups.value = [];
-      for (const item of pngs) {
-        for (const icon of item.list) {
-          if (search.value) {
-            icon.visible = searchObjectPinyin(icon, 'name', search.value);
-          } else {
-            icon.visible = true;
-          }
-        }
-      }
-      setTimeout(() => {
-        subGroups.value = pngs;
-      });
-      break;
-    case '图形':
-      subGroups.value = [];
-      if (search.value) {
-        activedPanels[activedGroup.value] = [];
-      }
-      for (const group of shapes) {
-        if (search.value) {
-          activedPanels[activedGroup.value].push(group.name);
-        }
-        for (const item of group.list) {
-          if (search.value) {
-            // @ts-ignore
-            item.visible = searchObjectPinyin(item, 'name', search.value);
-          } else {
-            // @ts-ignore
-            item.visible = true;
-          }
-        }
+        item.visible = searchObjectPinyin(item, 'name', search.value);
+      } else {
+        item.visible = true;
       }
-      setTimeout(() => {
-        subGroups.value = shapes;
-      });
+    }
 
-      break;
-    case '我的':
-      for (const item of subGroups.value) {
-        for (const elem of item.list) {
-          if (search.value) {
-            elem.visible = searchObjectPinyin(elem, 'name', search.value);
-          } else {
-            elem.visible = true;
-          }
-        }
-      }
-      break;
+    if (search.value) {
+      activedPanels[activedGroup.value].push(group.name);
+    }
   }
 };
 
@@ -1079,6 +963,58 @@ const onFold = () => {
 const loadImage = (elem: any) => {
   if (elem.isSvg) {
     makeSvg(elem);
+    if (activedGroup.value === '图元') {
+      throttle(renderPngGroup, 100);
+    }
+  }
+};
+
+const renderPngGroup = () => {
+  subGroups.value = pngs;
+};
+
+let uploadGroup: any;
+const onSelectFiles = (item: any) => {
+  uploadGroup = item;
+};
+
+const fileSuccessed = async (content: any) => {
+  const c: any = {
+    name: filename(content.file.name),
+    image: content.response.url,
+    folder: uploadGroup.name === '我的组件' ? '' : uploadGroup.name,
+  };
+
+  const ret: any = await addCollection('le5leV-components', c);
+
+  if (ret && uploadGroup.name !== '我的组件') {
+    c._id = ret._id || ret.id;
+    if (!uploadGroup.list) {
+      uploadGroup.list = [];
+    }
+    uploadGroup.list.push(c);
+    await axios.post('/api/data/folders/update', {
+      _id: uploadGroup._id || uploadGroup.id,
+      list: uploadGroup.list,
+    });
+  }
+};
+
+const delFolder = async (item: any) => {
+  if (item.list?.length) {
+    MessagePlugin.error('文件夹不为空!');
+    return;
+  }
+
+  const id = item._id || item.id;
+  const ret: any = await axios.post('/api/data/folders/delete', {
+    id,
+  });
+  if (ret) {
+    const i = subGroups.value.findIndex(
+      (elem: any) => id === elem._id || id === elem.id
+    );
+    i >= 0 && subGroups.value.splice(i, 1);
   }
 };
 
@@ -1158,17 +1094,38 @@ onUnmounted(() => {
         min-height: 100vh;
         border: none;
       }
+
       :deep(.t-collapse-panel__header) {
         border: none;
         font-size: 12px;
         font-weight: 400;
-        padding: 8px 16px;
-        &:hover {
+        padding: 8px 8px 8px 16px;
+
+        & > .t-space {
+          display: none;
+        }
+
+        & > .t-space:focus {
+          display: inline-flex;
+        }
+
+        &:hover .t-collapse-panel__icon,
+        &:hover > .flex {
           color: var(--color-primary);
         }
 
-        .ellipsis {
-          width: 200px;
+        .t-collapse-panel__icon,
+        .t-collapse-panel__icon * {
+          transition: none;
+        }
+
+        .t-icon {
+          font-size: 13px;
+        }
+      }
+      :deep(.t-collapse-panel__wrapper:hover) {
+        .t-collapse-panel__header > .t-space {
+          display: inline-flex;
         }
       }
 
@@ -1296,6 +1253,7 @@ onUnmounted(() => {
     z-index: 200;
     & > div {
       width: 140px !important;
+      position: static;
     }
 
     :deep(.t-menu) {