Alsmile před 2 roky
rodič
revize
97fd5a8187

+ 1 - 1
package.json

@@ -3,7 +3,7 @@
   "private": true,
   "version": "0.0.1",
   "scripts": {
-    "start": "vite --open --port 7000",
+    "start": "vite --host --open --port 7000",
     "prod": "vue-tsc --noEmit && vite build --base=https://assets.le5lecdn.com/v/",
     "build": "vue-tsc --noEmit && vite build --mode base --base=/v/",
     "trial": "vue-tsc --noEmit && vite build --mode trial --base=/v/",

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
public/material/草地.svg


+ 1 - 1
src/services/common.ts

@@ -114,7 +114,7 @@ export const save = async (
   }
   !data.version && (data.version = baseVer);
   if (!data.folder) {
-    data.folder = '大屏';
+    data.folder = '';
   }
   if (type === SaveType.SaveAs) {
     // 另存为一定走 新增 ,由于后端 未控制 userId 等属性,清空一下

+ 268 - 56
src/views/components/Graphics.vue

@@ -18,6 +18,9 @@
         </div>
       </div>
       <div class="list" :class="groupType ? 'two-columns' : ''">
+        <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:value="activedPanel"
@@ -25,10 +28,41 @@
         >
           <t-collapse-panel
             :value="item.name"
-            :header="item.name"
             v-for="item in subGroups"
             :key="item.name"
           >
+            <template #header>
+              <div class="flex middle mr-8" @click.stop>
+                <t-input
+                  v-if="item.edited"
+                  v-model="item.label"
+                  style="width: 140px"
+                  @blur="createFoder"
+                  @enter="createFoder"
+                  @keyup="onKeyHeader"
+                />
+                <div v-else class="ellipsis" style="width: 140px">
+                  {{ 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=""
+                >
+                  <t-icon name="delete" class="hover" />
+                </t-popconfirm>
+              </t-space>
+            </template>
             <div v-if="item.loading">
               <t-loading
                 text="加载中..."
@@ -48,7 +82,12 @@
                 @dblclick.stop="open(elem)"
                 :title="elem.draggable === false ? '双击打开' : '拖拽到画布'"
               >
-                <t-image v-if="elem.image" :src="elem.image" :lazy="true" />
+                <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>
@@ -68,7 +107,39 @@
             </template>
           </t-collapse-panel>
         </t-collapse>
-        <template v-else></template>
+        <div v-else class="t-collapse-panel__content" style="padding: 8px">
+          <div
+            class="graphic"
+            v-for="elem in subGroups"
+            :draggable="elem.draggable !== false"
+            @dragstart="dragStart($event, elem)"
+            @drag="drag($event, elem)"
+            @dragend="dragEnd()"
+            @click.stop="dragStart($event, elem)"
+            @dblclick.stop="open(elem)"
+            :title="elem.draggable === false ? '双击打开' : '拖拽到画布'"
+          >
+            <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>
+          <div
+            v-if="!subGroups.length"
+            class="gray center"
+            style="white-space: nowrap; margin-left: 32px"
+          >
+            暂无数据,待更新
+          </div>
+        </div>
       </div>
     </div>
   </div>
@@ -86,6 +157,7 @@ import { convertPen } from '@/services/upgrade';
 import { deepClone } from '@meta2d/core';
 import { isGif } from '@/services/utils';
 import { autoSave, delAttrs } from '@/services/common';
+import { MessagePlugin } from 'tdesign-vue-next';
 
 const router = useRouter();
 
@@ -108,11 +180,6 @@ const groups = reactive([
     name: '图表',
     key: 'chart',
   },
-  {
-    icon: 'relativity',
-    name: '控件',
-    key: '',
-  },
   {
     icon: 'image',
     name: '素材',
@@ -123,6 +190,11 @@ const groups = reactive([
     name: '图元',
     key: '',
   },
+  {
+    icon: 'relativity',
+    name: '控件',
+    key: '',
+  },
   {
     icon: 'chart-bubble',
     name: '图形',
@@ -165,6 +237,7 @@ const groupChange = async (name: string) => {
     case '模板':
       groupType.value = 2;
       if (!templates.value.length) {
+        templates.value = await getCaseProjects(name);
       }
       subGroups.value = templates.value;
       break;
@@ -177,6 +250,7 @@ const groupChange = async (name: string) => {
     case '素材':
       groupType.value = 2;
       if (!materials.value.length) {
+        materials.value = await getFiles('material/');
       }
       subGroups.value = materials.value;
       break;
@@ -196,18 +270,23 @@ const groupChange = async (name: string) => {
       subGroups.value = shapes;
       break;
     case '我的':
-      subGroups.value = await getPrivateCommponents();
+      subGroups.value = await getPrivateGroups();
       groupType.value = 1;
+      onChangeGroupPanel([subGroups.value[0].name]);
       break;
   }
   activedPanel.value = [subGroups.value[0].name];
 };
 
-const getCaseProjects = async (name: string, group: string) => {
+const getCaseProjects = async (name: string, group?: string) => {
+  const query: any = { tags: name };
+  if (group) {
+    query.case = group;
+  }
   const ret: any = await axios.post(
-    '/api/data/le5leV/list?current=1&pageSize=1000',
+    '/api/data/le5leV/list?current=1&pageSize=100',
     {
-      query: { tags: name, case: group },
+      query,
       shared: 'true',
       projection: { _id: 1, name: 1, image: 1, price: 1 },
     }
@@ -222,40 +301,48 @@ const getCaseProjects = async (name: string, group: string) => {
   return ret.list;
 };
 
-const getPrivateCommponents = async () => {
-  const data = {
-    projection: {
-      image: 1,
-      _id: 1,
-      name: 1,
-      folder: 1,
-      component: 1,
+const getPrivateGroups = async () => {
+  const list = [
+    {
+      name: '我的组件',
+      list: [],
     },
-  };
+  ];
   const config = {
     params: {
       current: 1,
-      pageSize: 100,
+      pageSize: 1000,
     },
   };
-  const res: any = await getComponentsList(data, config);
-  const folderMap: any = {};
-  res.list?.map((item: any) => {
-    if (!folderMap[item.folder]) {
-      folderMap[item.folder] = [];
-    }
+  let ret: any = await axios.post(
+    '/api/data/folders/list',
+    {
+      projection: {
+        image: 1,
+        _id: 1,
+        name: 1,
+        list: 1,
+      },
+      query: {
+        type: `le5leV-components`,
+      },
+    },
+    config
+  );
+  if (!ret) {
+    ret = { list: [] };
+  }
 
-    folderMap[item.folder].push(item);
-  });
-  let list = [];
-  for (let key in folderMap) {
-    list.push({
-      name: key === 'undefined' ? '未分类' : key,
-      show: true,
-      list: folderMap[key],
-    });
+  for (const item of ret.list) {
+    item.canEdited = true;
   }
 
+  list.push(...ret.list);
+  list.push({
+    name: '3D',
+    list: [],
+  });
+
   return list;
 };
 
@@ -327,6 +414,9 @@ const dragend = (event: any) => {
 };
 
 const open = async (item: any) => {
+  if (item.draggable !== false) {
+    return;
+  }
   autoSave();
   router.push({
     path: '/',
@@ -375,18 +465,133 @@ const onChangeGroupPanel = async (val: string[]) => {
               if (item.svg) {
                 item.list = await getIcons(item.folder);
               } else {
-                console.log(item.folder + item.name);
                 item.list = await getFiles(item.folder + item.name);
               }
               item.loading = false;
             }
           }
           break;
+
+        case '我的':
+          for (const item of subGroups.value) {
+            if (!item.list.length) {
+              item.loading = true;
+              if (item.name === '我的组件') {
+                const data = {
+                  query: { folder: '' },
+                  projection: {
+                    image: 1,
+                    _id: 1,
+                    name: 1,
+                    component: 1,
+                  },
+                };
+                const config = {
+                  params: {
+                    current: 1,
+                    pageSize: 1000,
+                  },
+                };
+                const res: any = await getComponentsList(data, config);
+                if (res?.list) {
+                  item.list = res.list;
+                }
+              } else if (item.name === '3D') {
+                const data = {
+                  projection: {
+                    image: 1,
+                    _id: 1,
+                    name: 1,
+                  },
+                };
+                const config = {
+                  params: {
+                    current: 1,
+                    pageSize: 1000,
+                  },
+                };
+                const res: any = await axios.post(
+                  '/api/data/le5le3d/list',
+                  data,
+                  config
+                );
+                if (res?.list) {
+                  item.list = res.list;
+                }
+              }
+              item.loading = false;
+            }
+          }
+          break;
       }
     }
   }
 };
 
+const editedFolder = ref<any>(undefined);
+const onCreateFolder = () => {
+  editedFolder.value = {
+    _id: '',
+    name: '',
+    label: '新建文件夹',
+    list: [],
+    edited: true,
+    canEdited: true,
+  };
+  subGroups.value.splice(subGroups.value.length - 1, 0, editedFolder.value);
+};
+
+const createFoder = async () => {
+  if (!editedFolder.value.label) {
+    return;
+  }
+  if (editedFolder.value.label === editedFolder.value.name) {
+    editedFolder.value.edited = false;
+    return;
+  }
+  const found = subGroups.value.findIndex(
+    (group: any) => group.name === editedFolder.value.label
+  );
+  if (found >= 0) {
+    MessagePlugin.error('已经存在相同名称文件夹');
+    return;
+  }
+
+  if (editedFolder.value._id) {
+    const ret: any = await axios.post('/api/data/folders/update', {
+      _id: editedFolder.value._id,
+      name: editedFolder.value.label,
+    });
+    if (ret) {
+      editedFolder.value.name = editedFolder.value.label;
+      editedFolder.value.edited = false;
+    }
+  } else {
+    const ret: any = await axios.post('/api/data/folders/add', {
+      name: editedFolder.value.label,
+      type: 'le5leV-components',
+      list: [],
+    });
+    if (ret) {
+      editedFolder.value.name = editedFolder.value.label;
+      editedFolder.value._id = ret._id;
+      editedFolder.value.edited = false;
+    }
+  }
+};
+
+const onEditHeader = (item: any) => {
+  item.label = item.name;
+  item.edited = true;
+  editedFolder.value = item;
+};
+
+const onKeyHeader = (text: string, event: any) => {
+  if (event.e.key === 'Escape') {
+    editedFolder.value.edited = false;
+  }
+};
+
 onMounted(() => {
   groupChange('场景');
   document.addEventListener('dragstart', dragstart, false);
@@ -474,7 +679,7 @@ onUnmounted(() => {
         padding: 4px 4px 20px 4px;
         display: grid;
         grid-template-columns: 1fr 1fr 1fr;
-        grid-row-gap: 20px;
+        grid-row-gap: 12px;
       }
 
       :deep(.t-loading--center) {
@@ -484,17 +689,32 @@ onUnmounted(() => {
           height: 24px;
         }
       }
+
+      :deep(.t-image__error) {
+        .t-space-item:last-child {
+          display: none;
+        }
+      }
+
+      :deep(.t-image__loading) {
+        .t-space-item:last-child {
+          display: none;
+        }
+      }
+
       .graphic {
         position: relative;
+        padding: 10px 0;
+        border-radius: 2px;
+        border: 1px solid transparent;
 
         &:hover {
           cursor: pointer;
-          color: var(--color-primary-hover);
-          svg {
-            color: var(--color-primary-hover);
-          }
+          border-color: var(--color-primary);
         }
         p {
+          margin-top: 10px;
+          padding: 0 10px;
           text-align: center;
           font-size: 12px;
           height: 12px;
@@ -509,10 +729,7 @@ onUnmounted(() => {
         .t-image__wrapper {
           height: 32px;
           width: 32px;
-          background: #fff0;
-          margin-left: calc(50% - 16px);
-          margin-top: 10px;
-          margin-bottom: 10px;
+          margin: auto;
           :deep(.t-image) {
             border-radius: 2px;
           }
@@ -522,7 +739,7 @@ onUnmounted(() => {
           color: var(--color);
           height: 32px;
           width: 100%;
-          margin: 4px 0px;
+          margin: auto;
         }
 
         .svg-box {
@@ -558,18 +775,13 @@ onUnmounted(() => {
 
     .two-columns {
       :deep(.t-collapse-panel__content) {
-        padding: 0 8px;
-        grid-template-columns: 116px 116px;
+        grid-template-columns: 1fr 1fr;
       }
       .graphic {
-        p {
-          margin-top: 10px;
-          margin-bottom: 12px;
-        }
         .t-image__wrapper {
-          width: 88px;
+          width: 100px;
           height: 88px;
-          margin-left: 14px;
+          background-color: var(--color-background);
         }
       }
     }

+ 3 - 1
vite.config.ts

@@ -41,7 +41,9 @@ function fileList(): Plugin {
         const url = req.url as string;
 
         if (
-          (url.startsWith('/svg/') || url.startsWith('/png/')) &&
+          (url.startsWith('/svg/') ||
+            url.startsWith('/png/') ||
+            url.startsWith('/material/')) &&
           url.endsWith('/')
         ) {
           const pwd = decodeURI(path.join(__dirname, 'public', url));

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů