Browse Source

新增文件夹

tangning 2 months ago
parent
commit
1bbc8d92bb

+ 4 - 2
package.json

@@ -5,8 +5,8 @@
     "bootstrap": "pnpm install",
     "serve": "npm run dev",
     "dev": "vite",
-    "build": "cross-env NODE_ENV=production vite build --mode production && esno ./build/script/postBuild.ts",
-    "translate": "node translate.js",
+    "build": "node ./scripts/fetch-langs.mjs && cross-env NODE_ENV=production vite build --mode production && esno ./build/script/postBuild.ts",
+    "translate": "node ./scripts/fetch-langs.mjs",
     "build:test": "cross-env vite build --mode test && esno ./build/script/postBuild.ts",
     "build:no-cache": "pnpm clean:cache && npm run build",
     "report": "cross-env REPORT=true npm run build",
@@ -53,6 +53,7 @@
     "lodash-es": "^4.17.21",
     "mockjs": "^1.1.0",
     "moment": "^2.29.1",
+    "node-fetch": "^3.3.2",
     "nprogress": "^0.2.0",
     "path-to-regexp": "^6.2.0",
     "ping.js": "^0.3.0",
@@ -64,6 +65,7 @@
     "showdown": "^1.9.1",
     "sortablejs": "^1.14.0",
     "tinymce": "^5.10.2",
+    "unzipper": "^0.12.3",
     "vditor": "^3.8.10",
     "vue": "^3.2.26",
     "vue-i18n": "^9.1.9",

+ 52 - 0
scripts/fetch-langs.mjs

@@ -0,0 +1,52 @@
+import fetch from 'node-fetch';
+import fs from 'fs';
+import path from 'path';
+import unzipper from 'unzipper';
+
+import { pipeline } from 'node:stream';
+import { promisify } from 'node:util';
+import { createReadStream, createWriteStream } from 'node:fs';
+
+const streamPipeline = promisify(pipeline);
+
+/**
+ * 下载并解压离线包
+ * @param {string} url 下载地址
+ * @param {string} destDir 解压目标目录
+ */
+async function downloadAndExtract(url, destDir) {
+  const zipPath = path.join(destDir, 'temp.zip');
+
+  console.log(`开始下载语言包: ${url}`);
+
+  const res = await fetch(url, {
+    method: 'GET',
+    headers: {
+      //'X-API-Key':'tgpak_gm2f6ntnor2gy4ztnfuw65twoj2wu2tdovzwwztqoe4ts5q'
+    },
+  });
+
+  if (!res.ok) {
+    throw new Error(`下载语言包失败: ${res.status} ${res.statusText}`);
+  }
+
+  // zip文件保存到本地目录
+  await streamPipeline(res.body, createWriteStream(zipPath));
+
+  console.log('语言包下载完成,开始解压...');
+
+  // 解压到指定目录
+  await streamPipeline(createReadStream(zipPath), unzipper.Extract({ path: destDir }));
+
+  console.log('语言包解压完成!');
+
+  // 删除临时压缩包
+  fs.unlinkSync(zipPath);
+}
+
+// 示例调用
+const url =
+  'http://192.168.0.211:9012/v2/projects/export?ak=tgpak_gq2v62bymezha33wmzwg4yztobqwiy3dojxwy33che4wiyi';
+const dest = './src/locales/lang/json';
+
+await downloadAndExtract(url, dest);

+ 41 - 1
src/api/scene/list.ts

@@ -40,11 +40,51 @@ enum Api {
   uploadE57 = 'service/manage_jp/file/uploadE57',
   getUploadUrl = 'service/manage_jp/file/getUploadUrl',
   relevanceE57 = 'service/manage_jp/file/relevanceE57',
+  addOrUpdate = 'service/manage_jp/manageFolder/addOrUpdate',
+  folderDel = 'service/manage_jp/manageFolder/del',
+  folderMoveScene = 'service/manage_jp/manageFolder/moveScene',
+  tree = 'service/manage_jp/manageFolder/tree',
 }
 
 /**
  * @description: Get sample list value
  */
+export const folderDel = (params: moveSceneParam) =>
+  defHttp.post<Result>({
+    url: Api.folderDel,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+export const addOrUpdate = (params: moveSceneParam) =>
+  defHttp.post<Result>({
+    url: Api.addOrUpdate,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+export const folderMoveScene = (params: moveSceneParam) =>
+  defHttp.post<Result>({
+    url: Api.folderMoveScene,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
+export const folderTree = (params) =>
+  defHttp.get<Result>({
+    url: Api.tree,
+    params,
+    headers: {
+      // @ts-ignore
+      ignoreCancelToken: true,
+    },
+  });
 
 export const ListApi = (params: PageParams) =>
   defHttp.post<RentListGetResultModel>({
@@ -285,7 +325,7 @@ export const getUploadUrl = async ({ fileName, file }, onUploadProgress) => {
     });
   };
   console.log('UrlData1');
-  let uploadUrl = UrlData.url.replace('http://', '//')
+  const uploadUrl = UrlData.url.replace('http://', '//');
   await upload(file, uploadUrl, onUploadProgress);
   console.log('UrlData2');
   return UrlData.newFileName;

File diff suppressed because it is too large
+ 777 - 661
src/locales/lang/json/ja.json


File diff suppressed because it is too large
+ 776 - 661
src/locales/lang/json/zh-CN.json


+ 161 - 0
src/views/scenes/folderModal.vue

@@ -0,0 +1,161 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    :title="title"
+    width="500px"
+    :minHeight="100"
+    :centered="true"
+    @ok="handleSubmit"
+    @cancel="handleCancel"
+  >
+    <div class="pt-20px">
+      <BasicForm @register="registerForm" />
+    </div>
+    <template #centerFooter>
+      <!-- <a-button>xxxx</a-button> -->
+    </template>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, reactive, ref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+
+  import { addOrUpdate } from '/@/api/scene/list';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  const { t } = useI18n();
+  const { createMessage } = useMessage();
+
+  export default defineComponent({
+    components: { BasicModal, BasicForm },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['register', 'success', 'cancel'],
+    setup(_, { emit }) {
+      const title = ref('新增文件夹');
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      const selectData = ref({
+        account: '',
+        tableType: NaN,
+        parentId: '',
+      });
+      const schemas: FormSchema[] = [
+        {
+          field: 'id',
+          component: 'Input',
+          label: 'id',
+          show: false,
+        },
+        {
+          field: 'parentId',
+          component: 'Input',
+          label: 'parentId',
+          show: false,
+        },
+        {
+          field: 'type',
+          component: 'Input',
+          label: 'type',
+          show: false,
+        },
+        {
+          field: 'name',
+          label: t('routes.scenes.floderName'),
+          component: 'Input',
+          required: true,
+          componentProps: {
+            maxlength: 10,
+          }
+        },
+      ];
+      const [
+        registerForm,
+        {
+          validate,
+          clearValidate,
+          setFieldsValue,
+          getFieldsValue,
+          resetFields,
+          appendSchemaByField,
+          removeSchemaByField,
+        },
+      ] = useForm({
+        schemas: schemas,
+        labelWidth: 80,
+        baseColProps: { span: 22 },
+        autoSubmitOnEnter: true,
+        showResetButton: false,
+        showSubmitButton: false,
+        submitFunc: async () => {},
+      });
+
+      const checked = ref<boolean>(false);
+      const headerInfo = reactive<Recordable>({});
+      const searchInfo = reactive<Recordable>({});
+
+      function onDataReceive(data: any) {
+        console.log('Data Received', data);
+        setFieldsValue(data);
+        title.value = data.id ? t('routes.scenes.rename') : t('routes.scenes.creatFloder');
+        selectData.value.tableType = Number(data.type);
+        selectData.value.parentId = data.parentId;
+        setFieldsValue({ name: data.sceneName || '' });
+      }
+
+      const handleSelect = async () => {
+        // const keys = getSelectRowKeys();
+        // console.log('key', key);
+      };
+      const handleChange = (val, row) => {
+        console.log('val,row', val, row);
+      };
+      const handleSubmit = async () => {
+        try {
+          let data = await validate();
+          await addOrUpdate({...data, type: selectData.value.tableType, parentId: selectData.value.parentId});
+          createMessage.success(t('common.optSuccess'));
+          resetFields();
+          closeModal();
+          emit('success');
+
+          setTimeout(() => {
+            createMessage.destroy();
+          }, 3000);
+        } catch (error) {
+          console.error('error', error);
+        }
+      };
+      const handleCancel = async () => {
+        selectData.value.account = '';
+        selectData.value.tableType = NaN;
+        selectData.value.num = [];
+        removeSchemaByField('cameraId');
+        appendSchemaByField(schemas[1], 'cameraId');
+        resetFields();
+        emit('cancel');
+      };
+
+      return {
+        register,
+        searchInfo,
+        closeModal,
+        handleSelect,
+        headerInfo,
+        handleChange,
+        t,
+        title,
+        checked,
+        handleSubmit,
+        registerForm,
+        handleCancel,
+        clearValidate,
+      };
+    },
+  });
+</script>

+ 169 - 0
src/views/scenes/folderTreeModal.vue

@@ -0,0 +1,169 @@
+<template>
+  <BasicModal
+    v-bind="$attrs"
+    @register="register"
+    :title="t('routes.scenes.moveScene')"
+    width="600px"
+    :minHeight="400"
+    :centered="true"
+    @ok="handleSubmit"
+    @cancel="handleCancel"
+  >
+    <div class="pt-20px">
+      <div class="ScreahNmae">
+        <a-input
+          class="ScreahInput"
+          v-model:value="selectData.title"
+          :placeholder="t('routes.scenes.moveSceneTitle')"
+        />
+      </div>
+      <div class="treeList">
+        <Tree
+          class="draggable-tree"
+          v-model:selectedKeys="selectedKeys"
+          :tree-data="treeData"
+          show-icon
+          default-expand-all
+          >
+          <template #icon>
+            <!-- <i class="ph:folder-open-fill"></i> -->
+            <img src="/resource/img/file.png" style="padding: 0 6px 0 0" alt="" />
+            <!-- <FolderOpenOutlined /> -->
+          </template>
+          </Tree>
+      </div>
+    </div>
+    <template #centerFooter>
+      <!-- <a-button>xxxx</a-button> -->
+    </template>
+  </BasicModal>
+</template>
+<script lang="ts">
+  import { defineComponent, reactive, ref } from 'vue';
+  import { BasicModal, useModalInner } from '/@/components/Modal';
+  import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
+  import { FolderOpenOutlined } from '@ant-design/icons-vue';
+  import { Tree } from 'ant-design-vue';
+  import { folderMoveScene, folderTree } from '/@/api/scene/list';
+  import { useI18n } from '/@/hooks/web/useI18n';
+  import { useMessage } from '/@/hooks/web/useMessage';
+  import type { TreeProps } from 'ant-design-vue';
+  const { t } = useI18n();
+  const { createMessage } = useMessage();
+
+  export default defineComponent({
+    components: { BasicModal, BasicForm, Tree, FolderOpenOutlined },
+    props: {
+      userData: { type: Object },
+    },
+    emits: ['register', 'success', 'cancel'],
+    setup(_, { emit }) {
+
+      const [register, { closeModal }] = useModalInner((data) => {
+        data && onDataReceive(data);
+      });
+      const selectData = ref({
+        account: '',
+        type: '',
+        title: '',
+        numList: [],
+      });
+      const expandedKeys = ref<string[]>(['0-0', '0-1']);
+      const selectedKeys = ref<string[]>([]);
+      const treeData: TreeProps['treeData'] = ref([]);
+      const checked = ref<boolean>(false);
+      const headerInfo = reactive<Recordable>({});
+      const searchInfo = reactive<Recordable>({});
+
+      async function onDataReceive(data: any) {
+        let tree = await folderTree({ type: data.type });
+        treeData.value = getTreeData(tree);
+        selectData.value.type = Number(data.type);
+        selectData.value.numList = data.numList;
+      }
+
+      const handleSelect = async () => {
+        // const keys = getSelectRowKeys();
+        // console.log('key', key);
+      };
+      const handleChange = (val, row) => {
+        console.log('val,row', val, row);
+      };
+      const handleSubmit = async () => {
+        console.log('selectData', selectedKeys);
+        if(selectedKeys.value && selectedKeys.value.length  == 0) {
+          return createMessage.error(t('routes.scenes.pleaseFloder'));
+        }
+        try {
+          await folderMoveScene({...selectData.value, folederId: selectedKeys.value[0] });
+          createMessage.success(t('common.optSuccess'));
+          // closeModal();
+          emit('success');
+
+          setTimeout(() => {
+            createMessage.destroy();
+          }, 3000);
+        } catch (error) {
+          console.error('error', error);
+        }
+      };
+      const getTreeData = (tree = []) => {
+        return tree.map((item) => {
+          return {
+            ...item,
+            title: item.name,
+            key: item.id,
+            children: getTreeData(item.childrenList),
+          };
+        });
+      };
+      const handleCancel = async () => {
+        selectData.value.account = '';
+        selectData.value.tableType = NaN;
+        selectData.value.numList = [];
+        removeSchemaByField('cameraId');
+        appendSchemaByField(schemas[1], 'cameraId');
+        resetFields();
+        emit('cancel');
+      };
+
+      return {
+        register,
+        searchInfo,
+        closeModal,
+        handleSelect,
+        headerInfo,
+        handleChange,
+        t,
+        checked,
+        selectData,
+        handleSubmit,
+        handleCancel,
+        expandedKeys,
+        selectedKeys,
+        treeData,
+      };
+    },
+  });
+</script>
+<style lang="less">
+  .ScreahNmae {
+    .ScreahInput {
+      height: 40px;
+      line-height: 40px;
+    }
+  }
+  .treeList {
+    min-height: 350px;
+    padding: 5px 20px;
+    // overflow-y: scroll;
+    border: 1px solid rgba(0, 0, 0, 0.15);
+    border-top: none;
+    ::-webkit-scrollbar {
+      display: none; /* Chrome Safari */
+    }
+    .ant-tree .ant-tree-treenode{
+      padding: 5px 0;
+    }
+  }
+</style>

+ 111 - 7
src/views/scenes/list.vue

@@ -18,13 +18,17 @@
         @fetch-success="handleFetchSuccess"
       >
         <template #tableTitle>
-          <div class="fileName">root</div>
+          <div class="fileName" v-if="parentId">
+            <span @click="handleItem({})">Root</span>
+            <span class="fileNameItem" v-for="(item, index) in folderList" :key="index" @click="handleItem(item, index)" :class="{'active': item.id == parentId}">{{` / `}}{{ item.sceneName }}</span>
+          </div>
         </template>
         <template #toolbar>
           <a-button v-if="isPatchAuth" type="primary" @click="handlePatchSelect">
             {{ t('routes.archive.patchArchive') }}</a-button
           >
           <!-- <a-button type="primary" color="warning" @click="() => {}"> 编辑</a-button>-->
+          <a-button type="primary" @click="openfolderModal(true, {type: tableType, parentId})"> {{t('routes.scenes.creatFloder')}}</a-button> 
         </template>
         <template #mapShow="{ record }">
           <Switch
@@ -37,7 +41,7 @@
         </template>
         <template #cover="{ record }">
           <div class="cover">
-            <img src="/resource/img/file.png" style="margin: 0 auto;" alt="" v-if="record.companyName == '哈哈哈哈'" />
+            <img @click="handleFolder(record)" src="/resource/img/file.png" style="margin: 0 auto;" alt="" v-if="record.isFolder" />
             <TableImg v-else
               :size="120"
               :simpleShow="true"
@@ -63,7 +67,28 @@
         </template>
         <template #action="{ record }">
           <TableAction
-            v-if="record.payStatus != -2"
+            v-if="record.isFolder == 1"
+            :actions="[
+              {
+                icon: 'mage:edit-fill',
+                tooltip: t('routes.scenes.rename'),
+                onClick: openFolderEditor.bind(null, record),
+                ifShow: !record.coldStorage,
+              },
+              {
+                color: 'error',
+                icon: 'material-symbols:delete',
+                tooltip: t('routes.scenes.delete'),
+                ifShow: record.isDel,
+                popConfirm: {
+                  title: t('common.delConfirm'),
+                  confirm: handleFolderDelete.bind(null, record),
+                },
+              }
+            ]"
+          />
+          <TableAction
+            v-else-if="record.payStatus != -2"
             :actions="[
               {
                 color: 'error',
@@ -94,6 +119,12 @@
                 onClick: handleCopy.bind(null, record),
               },
               {
+                tooltip: '移动',
+                icon: 'material-symbols:drive-file-move-outline',
+                disabled: record.status != -2,
+                onClick: handleOpenTree.bind(null, record),
+              },
+              {
                 tooltip: t('routes.scenes.creatobj'),
                 icon: 'carbon:chart-3d',
                 ifShow:
@@ -151,6 +182,8 @@
         </template>
       </BasicTable>
       <DownloadModal @register="registerDownloadModal" />
+      <folderModal @register="registerfolderModal" @success="reload" />
+      <folderTreeModal @register="registerTreeModal" @success="reload" />
       <DownloadE57Modal @register="registerDownloadE57Modal" />
       <AssistantModal @register="registerAssistantModal" @success="reload" />
       <MigrateModal
@@ -190,6 +223,7 @@
     updateMapShowApi,
     checkDownloadE57Api,
     downloadSceneDataE57APi,
+    folderDel,
   } from '/@/api/scene/list';
   import { useI18n } from '/@/hooks/web/useI18n';
   import {
@@ -201,6 +235,8 @@
 
   import { useUserStore } from '/@/store/modules/user';
   import DownloadModal from './downloadModal.vue';
+  import folderModal from './folderModal.vue';
+  import folderTreeModal from './folderTreeModal.vue';
   import DownloadE57Modal from './downloadE57Modal.vue';
   import AssistantModal from './assistantModal.vue';
   import { useLocaleStore } from '/@/store/modules/locale';
@@ -219,6 +255,8 @@
       TableImg,
       DownloadModal,
       AssistantModal,
+      folderTreeModal,
+      folderModal,
       PageWrapper,
       MigrateModal,
       Switch,
@@ -229,6 +267,8 @@
     setup() {
       const roleList = userStore.roleList;
       console.log('roleList', roleList);
+      const folderList = ref<any>([]);
+      const parentId = ref<Number | null>(null);
 
       const isPatchAuth = computed(() => {
         return (
@@ -239,6 +279,8 @@
       });
 
       const { createMessage, createConfirm } = useMessage();
+      const [registerfolderModal, { openModal: openfolderModal }] = useModal();
+      const [registerTreeModal, { openModal: openTreeModal }] = useModal();
       const [registerDownloadModal, { openModal: openDownloadModal }] = useModal();
       const [registerDownloadE57Modal, { openModal: openDownloadE57Modal }] = useModal();
       const [registerAssistantModal, { openModal: openAssistantModal }] = useModal();
@@ -346,7 +388,7 @@
           title: t('common.operation'),
           dataIndex: '',
           slots: { customRender: 'action' },
-          width: 300,
+          width: 330,
           ellipsis: false,
           fixed: 'right',
         },
@@ -413,7 +455,7 @@
         useSearchForm: true,
         formConfig: searchForm,
         showTableSetting: true,
-        searchInfo: { type: tableType, lang: isJA.value ? 'ja' : 'zh' },
+        searchInfo: { type: tableType, lang: isJA.value ? 'ja' : 'zh', haveFolder: 1, folderId: parentId },
         tableSetting: { fullScreen: true },
         clickToRowSelect: false,
         showIndexColumn: false,
@@ -516,6 +558,15 @@
           console.log('error', error);
         }
       }
+      async function openFolderEditor(record: Recordable) {
+        openfolderModal(true, {...record, type: tableType})
+      }
+      async function handleFolderDelete(record: Recordable) {
+        console.log('DeleteApi', record);
+        await folderDel({id: record.id});
+        createMessage.success(t('common.optSuccess'));
+        reload();
+      }
       async function handleDelete(record: Recordable) {
         console.log('DeleteApi', record);
         await DeleteApi(record.num);
@@ -556,6 +607,9 @@
         // console.log('record', record);
         openAssistantModal(true, { ...record });
       }
+      function createdFolder(record){
+        openfolderModal(true, record);
+      }
       function handlePatchSelect() {
         const keys = getSelectRowKeys();
         if (keys.length > 0) {
@@ -636,19 +690,47 @@
           });
         }
       }
-
+      function handleFolder(record){
+        parentId.value = record.id;
+        folderList.value.push(record);
+        console.log('folderList', folderList.value, record);
+        reload();
+      }
+      function handleItem(item, index) {
+        console.log('item', item, index);
+        if(!item.id){//为空
+          folderList.value = []
+          parentId.value = null;
+          reload();
+          return
+        }
+        if(parentId.value != item.id ){
+          parentId.value = item.id;
+          folderList.value.splice(index, 1);
+          reload();
+          return
+        }
+      }
+      function handleOpenTree (record) {
+        openTreeModal(true, {type: tableType.value, numList: [record.num]});
+      }
       return {
         reload,
         registerTable,
         createMessage,
         t,
+        folderList,
         handleGenerate,
         openSceneEditor,
+        openFolderEditor,
         handleDownloadScene,
         handleDelete,
         handleLivestream,
         handleUpgrade,
         registerDownloadModal,
+        openfolderModal,
+        registerfolderModal,
+        registerTreeModal,
         changeTable,
         handleCopy,
         tableType,
@@ -668,12 +750,17 @@
         handleDownloadSceneE57,
         registerDownloadE57Modal,
         dayjs,
+        parentId,
+        handleFolder,
+        handleItem,
+        handleOpenTree,
+        handleFolderDelete,
       };
     },
   });
 </script>
 
-<style scoped>
+<style scoped lang="less">
   :deep(.ant-table-tbody > tr:has(.ant-checkbox-disabled) > td) {
     opacity: 0.5;
     user-select: none;
@@ -682,4 +769,21 @@
   :deep(.vben-basic-table-action.left) {
     justify-content: center;
   }
+  .desc-wrap-BasicTable{
+    .fileName{
+      font-weight: 400;
+      font-size: 14px;
+      line-height: 22px;
+      color: #9D9D9D;//#6C6C6C
+      span{
+        cursor: pointer;
+      }
+      .active{
+        color: #000;//#165DFF;
+      }
+      .fileNameItem{
+        margin-right: 8px;
+      }
+    }
+  }
 </style>

File diff suppressed because it is too large
+ 12785 - 0
yarn.lock