Bladeren bron

建筑楼层管理

zhangyu 4 jaren geleden
bovenliggende
commit
64a4a1d3e5

+ 3 - 0
package.json

@@ -64,7 +64,10 @@
     "sass": "^1.28.0",
     "sass-loader": "^10.0.4",
     "style-resources-loader": "^1.3.3",
+    "spark-md5": "^3.0.0",
+    "stompjs": "^2.3.3",
     "typescript": "^4.0.5",
+    "vue-simple-uploader": "^0.7.4",
     "vue-cli-plugin-element": "^1.0.1",
     "vue-cli-plugin-style-resources-loader": "^0.1.4",
     "vue-template-compiler": "^2.6.12",

+ 9 - 7
src/App.vue

@@ -1,19 +1,23 @@
 <template>
     <div id="app">
         <el-scrollbar>
-            <router-view/>
+            <router-view />
         </el-scrollbar>
+        <global-uploader></global-uploader>
     </div>
 </template>
 
 <script lang="ts">
-import {Component, Vue} from 'vue-property-decorator'
+import { Component, Vue } from "vue-property-decorator";
+import GlobalUploader from "@/components/GlobalUploader/index.vue";
 
 @Component({
-    name: 'App'
+    name: "App",
+    components: {
+        GlobalUploader,
+    },
 })
-export default class extends Vue {
-}
+export default class extends Vue {}
 </script>
 <style lang="scss">
 .el-scrollbar {
@@ -25,8 +29,6 @@ export default class extends Vue {
 
     .el-scrollbar__view {
         height: 100%;
-
     }
 }
-
 </style>

+ 29 - 0
src/api/imageservice.ts

@@ -0,0 +1,29 @@
+/**
+ * 文件服务器接口
+ */
+
+import httputils from "@/api/httputils";
+const baseUrl = '/image-service'
+
+/**
+ * info: 申请分片上传接口(获取上传UploadId)
+ * @params {systemId:string} params 系统的名称
+ * @params {secret:string} params 系统的密码
+ * @params {key:string} params  成功的回上传的文件名(可以包含文件路径)
+ * @params {overwrite:boolean} params 表示是否覆盖已有的文件(如果存在) 注:overwrite有一定风险, 只在注册分片时校验, 合并时候如果key被占用, 会覆盖掉之前文件
+ * 
+ */
+export function getUploadId(params: any): any {
+    return httputils.fetchJson(`${baseUrl}/common/register_multipart_upload`, params, {});
+}
+
+/**
+ * info: 合并分片的接口
+ * @params {systemId:string} params 系统的名称
+ * @params {secret:string} params 系统的密码
+ * @params {uploadId:string} params  需要合并的上传文件的uploadId
+ * 
+ */
+export function mergeMultipart(params: any): any {
+    return httputils.fetchJson(`${baseUrl}/common/merge_multipart`, params, {});
+}

+ 5 - 0
src/api/modelapi.ts

@@ -12,4 +12,9 @@ export function queryModel(postParams: any): any {
 // 查询模型文件夹下的所有模型文件
 export function queryFloorList(postParams: any): any {
     return httputils.postJson(`${baseUrl}/model-floor/query-list`, postParams);
+}
+
+// 准备分片上传模型文件接口
+export function modelFileUpload(postParams: any): any {
+    return httputils.postJson(`${baseUrl}/model-file/upload`, postParams)
 }

+ 553 - 0
src/components/GlobalUploader/index.vue

@@ -0,0 +1,553 @@
+<!--
+ * @Author: zhangyu <taohuzy@163.com>
+ * @Date: 2019-11-28 10:05:28
+ * @Info: 
+ * @LastEditTime : 2020-01-03 15:31:04
+ -->
+<template>
+    <div id="global-uploader">
+        <!-- 上传 -->
+        <uploader
+            ref="uploader"
+            :options="options"
+            :fileStatusText="fileStatusText"
+            :autoStart="false"
+            @file-added="onFileAdded"
+            @file-success="onFileSuccess"
+            @file-progress="onFileProgress"
+            @file-error="onFileError"
+            @file-removed="onFileRemoved"
+            class="uploader-app"
+        >
+            <uploader-unsupport></uploader-unsupport>
+
+            <uploader-btn id="global-uploader-btn" :attrs="attrs" ref="uploadBtn">选择文件</uploader-btn>
+
+            <uploader-list v-show="panelShow">
+                <div class="file-panel" slot-scope="props" :class="{ collapse: collapse }">
+                    <div class="file-title">
+                        <h2>文件列表</h2>
+                        <div class="operate">
+                            <el-button @click="fileListShow" style="color: #999" type="text" :title="collapse ? '折叠' : '展开'">
+                                <i class="iconfont" :class="collapse ? 'icon-bottom' : 'icon-top'"></i>
+                            </el-button>
+                            <el-button @click="close" style="color: #999" type="text" title="关闭">
+                                <i class="iconfont icon-guanbi"></i>
+                            </el-button>
+                        </div>
+                    </div>
+
+                    <ul class="file-list" v-show="collapse">
+                        <el-scrollbar style="height: 100%">
+                            <li v-for="file in props.fileList" :key="file.id">
+                                <uploader-file :class="'file_' + file.id" ref="files" :file="file" :list="true"></uploader-file>
+                            </li>
+                            <div class="no-file" v-if="!props.fileList.length"><i class="iconfont icon-wushuju"></i> 暂无待上传文件</div>
+                        </el-scrollbar>
+                    </ul>
+                </div>
+            </uploader-list>
+        </uploader>
+    </div>
+</template>
+
+<script lang="ts">
+/**
+ *   全局上传插件
+ *   调用方法:Bus.$emit('openUploader', {}) 打开文件选择框,参数为需要传递的额外参数
+ *   监听函数:Bus.$on('fileAdded', fn); 文件选择后的回调
+ *            Bus.$on('fileSuccess', fn); 文件上传成功的回调
+ */
+
+import { Vue, Component, Ref } from "vue-property-decorator";
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+//@ts-ignore
+import SparkMD5 from "spark-md5";
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+//@ts-ignore
+import uploader from "vue-simple-uploader";
+import Bus from "@/utils/bus.ts";
+import { AppModule } from "@/store/modules/app";
+import { getUploadId, mergeMultipart } from "@/api/imageservice";
+
+// import request from "@/api/model/file.js";
+
+@Component({
+    name: "GlobalUploader",
+})
+export default class extends Vue {
+    @Ref("uploader") readonly uploader!: uploader;
+
+    // 中英文按钮映射
+    private fileStatusText: any = {
+        success: "成功",
+        error: "失败",
+        uploading: "上传中",
+        paused: "暂停",
+        waiting: "等待",
+    };
+
+    // 分片上传配置
+    private options: any = {
+        target: "/image-service/common/multipart_upload",
+        chunkSize: 1 * 1024 * 1024,
+        fileParameterName: "file",
+        allowDuplicateUploads: true, //允许重复上传
+        maxChunkRetries: 3,
+        testChunks: false, //是否开启服务器分片校验
+        // 服务器分片校验函数,秒传及断点续传基础
+        // checkChunkUploadedByResponse: function (chunk, message) {
+        //   let objMessage = JSON.parse(message);
+        //   if (objMessage.skipUpload) {
+        //     return true;
+        //   }
+
+        //   return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
+        // },
+        // headers: {
+        // Authorization: Ticket.get() && "Bearer " + Ticket.get().access_token
+        // },
+        query: this.getFileQuery,
+    };
+
+    private attrs: any = {
+        // accept: ACCEPT_CONFIG.getAll()
+    };
+
+    private panelShow = false; // 选择文件后,展示上传panel
+    private collapse = false;
+
+    get uploaderList(): any[] {
+        return AppModule.uploaderList;
+    }
+
+    mounted() {
+        // 刷新或关闭浏览器提示
+        window.addEventListener("beforeunload", (e) => {
+            if (this.uploader.isUploading()) {
+                // 判断是否有文件正在上传
+                e.preventDefault();
+                // 兼容IE8和Firefox之前的版本
+                if (e) {
+                    e.returnValue = "您有文件正在上传,关闭会中断上传,是否继续?";
+                }
+                // Chrome, Safari, Firefox 4+, Opera 12+ , IE 9+
+                return "您有文件正在上传,关闭会中断上传,是否继续?";
+            }
+        });
+
+        Bus.$on("openUploader", async (query: any, file: any) => {
+            if (query.uploadId) {
+                // this.params = query || {};
+                file.uploadId = query.uploadId;
+                file.modelId = query.modelId ? query.modelId : "";
+                this.uploader.addFile(file);
+                this.setUploaderList(
+                    this.uploader.files.map((item: any) => {
+                        item.modelId = item.file.modelId ? item.file.modelId : "";
+                        return item;
+                    })
+                );
+            } else {
+                const res = await getUploadId({
+                    systemId: query.systemId ? query.systemId : "revit",
+                    secret: query.secret ? query.secret : "63afbef6906c342b",
+                    overwrite: query.overwrite ? query.overwrite : false,
+                    key: file.name,
+                });
+                if (res.UploadId) {
+                    // this.params = Object.assign(query, { uploadId: res.UploadId }) || {};
+                    file = Object.assign(file, {
+                        ...query,
+                        uploadId: res.UploadId,
+                    });
+                    this.uploader.addFile(file);
+                    this.setUploaderList(
+                        this.uploader.files.map((item: any) => {
+                            item.modelId = item.file.modelId ? item.file.modelId : "";
+                            return item;
+                        })
+                    );
+                } else {
+                    this.$message.error(`请求分片上传接口失败!`);
+                }
+            }
+
+            // if (this.$refs.uploadBtn) {
+            //   document.getElementById('global-uploader-btn').click();
+            //   // $('#global-uploader-btn').click();
+            // }
+        });
+    }
+
+    destroyed() {
+        Bus.$off("openUploader");
+    }
+
+    private onFileAdded(file: any) {
+        this.panelShow = true;
+        this.collapse = true;
+        this.computeMD5(file);
+
+        Bus.$emit("fileAdded");
+    }
+
+    private onFileProgress(rootFile: any, file: any, chunk: any) {
+        console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`);
+    }
+
+    private onFileSuccess(rootFile: any, file: any, response: any, chunk: any) {
+        console.log(chunk);
+        let res = JSON.parse(response);
+
+        // 服务器自定义的错误(即虽返回200,但是是错误的情况),这种错误是Uploader无法拦截的
+        if (!res.Result || res.Result != "success") {
+            this.$message({ message: res.ResultMsg ? res.ResultMsg : "上传失败!", type: "error" });
+            // 文件状态设为“失败”
+            this.statusSet(file.id, "failed");
+            return;
+        }
+        // 如果服务端返回需要合并
+        console.log("获取分片数", file.chunks.length);
+        if (parseInt(res.TotalCount) >= file.chunks.length) {
+            // 文件状态设为“合并中”
+            this.statusSet(file.id, "merging");
+            if (file.file.modelId) {
+                //模型文件合并专用接口
+                request.mergeModelFile({ ModelId: file.file.modelId, UploadId: file.file.uploadId, Md5: file.file.md5 }, (res) => {
+                    Bus.$emit("modelStatusChange");
+                    // 文件合并成功
+                    Bus.$emit("fileSuccess");
+                    this.statusRemove(file.id);
+                });
+            } else {
+                mergeMultipart(
+                    {
+                        systemId: file.file.systemId ? file.file.systemId : "revit",
+                        secret: file.file.secret ? file.file.secret : "63afbef6906c342b",
+                        uploadId: file.file.uploadId,
+                    },
+                    (res) => {
+                        // 文件合并成功
+                        Bus.$emit("fileSuccess");
+                        this.statusRemove(file.id);
+                    }
+                );
+            }
+
+            // 不需要合并
+        } else {
+            Bus.$emit("fileSuccess");
+            console.log("上传成功");
+        }
+    }
+    onFileError(rootFile, file, response, chunk) {
+        this.$message({
+            message: response,
+            type: "error",
+        });
+    }
+    onFileRemoved(file) {
+        this.uploaderList.splice(
+            this.uploaderList.findIndex((item) => {
+                return item.id == file.id;
+            }, 1)
+        );
+        this.setUploaderList(this.uploaderList);
+        if (file.modelId && !file.isComplete()) {
+            request.deleteModelFileList({ Id: file.modelId, Force: true }, (res) => {
+                console.log("删除错误模型:", file.modelId);
+                Bus.$emit("modelStatusChange");
+            });
+        }
+    }
+
+    /**
+     * 计算md5,实现断点续传及秒传
+     * @param file
+     */
+    computeMD5(file) {
+        let fileReader = new FileReader();
+        let time = new Date().getTime();
+        let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
+        let currentChunk = 0;
+        const chunkSize = 1 * 1024 * 1024;
+        let chunks = Math.floor(file.size / chunkSize);
+        let spark = new SparkMD5.ArrayBuffer();
+
+        // 文件状态设为"计算MD5"
+        this.statusSet(file.id, "md5");
+        file.pause();
+
+        loadNext();
+
+        fileReader.onload = (e) => {
+            spark.append(e.target.result);
+
+            if (currentChunk < chunks) {
+                currentChunk++;
+                loadNext();
+
+                // 实时展示MD5的计算进度
+                this.$nextTick(() => {
+                    document.getElementsByClassName(`myStatus_${file.id}`)[0].innerText =
+                        "校验MD5 " + ((currentChunk / chunks) * 100).toFixed(0) + "%";
+                    // $(`.myStatus_${file.id}`).text('校验MD5 '+ ((currentChunk/chunks)*100).toFixed(0)+'%')
+                });
+            } else {
+                let md5 = spark.end();
+                file.file.md5 = md5;
+                this.computeMD5Success(md5, file);
+                console.log(`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`);
+            }
+        };
+
+        fileReader.onerror = function () {
+            this.error(`文件${file.name}读取出错,请检查该文件`);
+            file.cancel();
+        };
+
+        function loadNext() {
+            let start = currentChunk * chunkSize;
+            let end = start + chunkSize >= file.size ? file.size : start + chunkSize;
+
+            fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
+        }
+    }
+
+    computeMD5Success(md5, file) {
+        // 将自定义参数直接加载uploader实例的opts上
+        console.log(this.uploader);
+        // Object.assign(this.uploader.opts, {
+        //   query: {
+        //     uploadId: file.file.uploadId,
+        //     md5
+        //   }
+        // })
+
+        // file.uniqueIdentifier = this.params.uploadId;
+
+        // file.resume();//继续上传文件
+        // 调用依赖中继续上传的方法达到状态刷新的目的(原生点击继续上传按钮)
+        document.querySelector(`.file_${file.id} .uploader-file-resume`).click();
+        this.statusRemove(file.id);
+    }
+
+    //获取文件参数
+    getFileQuery(file, chunk) {
+        return {
+            uploadId: file.file.uploadId,
+            md5: file.file.md5,
+        };
+    }
+
+    fileListShow() {
+        this.collapse = this.collapse ? false : true;
+
+        // let $list = $('#global-uploader .file-list');
+        // let $list = document.querySelector('#global-uploader .file-list')
+
+        // if ($list.style.display == '' && $list.style.display == 'none') {
+        //   $list.style.display == 'block';//显示
+        //   this.collapse = false;
+        //   // $list.slideUp();//隐藏
+        //   // this.collapse = true;
+        // } else {
+        //   $list.style.display == 'none';//隐藏
+        //   this.collapse = true;
+        //   // $list.slideDown();//显示
+        //   // this.collapse = false;
+        // }
+    }
+    close() {
+        if (this.uploader.isUploading()) {
+            this.$alert("您有文件正在上传,关闭会中断上传,是否继续?", "提示", {
+                confirmButtonText: "确定",
+                callback: (action) => {
+                    console.log(action);
+                    if (action == "confirm") {
+                        this.uploader.cancel();
+                        this.panelShow = false;
+                        this.uploader.files = [];
+                        this.setUploaderList([]);
+                        Bus.$emit("modelStatusChange");
+                    } else {
+                    }
+                },
+            });
+        } else {
+            this.uploader.cancel();
+            this.panelShow = false;
+        }
+    }
+
+    /**
+     * 新增的自定义的状态: 'md5'、'transcoding'、'failed'
+     * @param id
+     * @param status
+     */
+    statusSet(id, status) {
+        let statusMap = {
+            md5: {
+                text: "校验MD5",
+                bgc: "#fff",
+            },
+            merging: {
+                text: "合并中",
+                bgc: "#e2eeff",
+            },
+            transcoding: {
+                text: "转码中",
+                bgc: "#e2eeff",
+            },
+            failed: {
+                text: "上传失败",
+                bgc: "#e2eeff",
+            },
+        };
+
+        this.$nextTick(() => {
+            let node = document.createElement("P");
+            let att = document.createAttribute("class");
+            att.value = `status_style myStatus_${id}`;
+            let text = document.createTextNode(`${statusMap[status].text}`);
+            node.appendChild(text);
+            node.setAttributeNode(att);
+            node.style.backgroundColor = `${statusMap[status].bgc}`;
+            document.querySelector(`.file_${id} .uploader-file-status`).appendChild(node);
+
+            // $(`<p class="myStatus_${id}"></p>`).appendTo(`.file_${id} .uploader-file-status`).css({
+            //   'position': 'absolute',
+            //   'top': '0',
+            //   'left': '0',
+            //   'right': '0',
+            //   'bottom': '0',
+            //   'zIndex': '1',
+            //   'backgroundColor': statusMap[status].bgc
+            // }).text(statusMap[status].text);
+        });
+    }
+    statusRemove(id) {
+        this.$nextTick(() => {
+            let dom = document.getElementsByClassName(`myStatus_${id}`)[0];
+            dom.parentNode.removeChild(dom);
+        });
+    }
+
+    error(msg) {
+        this.$notify({
+            title: "错误",
+            message: msg,
+            type: "error",
+            duration: 2000,
+        });
+    }
+}
+</script>
+
+<style scoped lang="scss">
+#global-uploader {
+    position: fixed;
+    z-index: 101;
+    top: auto;
+    right: 15px;
+    bottom: 0;
+
+    .uploader-app {
+        width: 633px;
+    }
+
+    .file-panel {
+        background-color: #fff;
+        border: 1px solid #e2e2e2;
+        border-radius: 7px 7px 0 0;
+        box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
+
+        .file-title {
+            display: flex;
+            height: 40px;
+            line-height: 40px;
+            padding: 0 15px;
+            border-bottom: 1px solid #ddd;
+            h2 {
+                font-size: 14px;
+                font-style: normal;
+                cursor: text;
+                font-weight: 200;
+                color: #666;
+            }
+
+            .operate {
+                flex: 1;
+                text-align: right;
+            }
+        }
+
+        .file-list {
+            position: relative;
+            height: 350px;
+            overflow-x: hidden;
+            overflow-y: auto;
+            background-color: #fff;
+            ::v-deep .el-scrollbar__wrap {
+                overflow-x: hidden;
+            }
+
+            > li {
+                background-color: #fff;
+            }
+            :v-deep .uploader-file-info i {
+                display: none;
+            }
+            ::v-deep .status_style {
+                position: absolute;
+                top: 0;
+                left: 0;
+                right: 0;
+                bottom: 0;
+                z-index: 1;
+            }
+        }
+
+        // &.collapse {
+        //   .file-title {
+        //     background-color: #E7ECF2;
+        //   }
+        // }
+    }
+
+    .no-file {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+        font-size: 16px;
+        text-align: center;
+    }
+
+    ::v-deep .uploader-file-icon {
+        &:before {
+            content: "" !important;
+        }
+
+        // &[icon=image] {
+        //   background: url(./images/image-icon.png);
+        // }
+        // &[icon=video] {
+        //   background: url(./images/video-icon.png);
+        // }
+        // &[icon=document] {
+        //   background: url(./images/text-icon.png);
+        // }
+    }
+
+    ::v-deep .uploader-file-actions > span {
+        margin-right: 6px;
+    }
+}
+/* 隐藏上传按钮 */
+#global-uploader-btn {
+    position: absolute;
+    clip: rect(0, 0, 0, 0);
+}
+</style>

+ 10 - 7
src/main.ts

@@ -1,6 +1,8 @@
 import Vue from "vue";
 
 import "normalize.css";
+// @ts-ignore
+import uploader from "vue-simple-uploader";
 import ElementUI from "element-ui";
 import SvgIcon from "vue-svgicon";
 
@@ -13,17 +15,18 @@ import router from "@/router/index";
 import "@/icons/components";
 import "@/permission";
 
-Vue.use(ElementUI);
+Vue.use(uploader);
+Vue.use(ElementUI, { size: "small", zIndex: 1000 });
 Vue.use(SvgIcon, {
-  tagName: "svg-icon",
-  defaultWidth: "1em",
-  defaultHeight: "1em"
+    tagName: "svg-icon",
+    defaultWidth: "1em",
+    defaultHeight: "1em"
 });
 
 Vue.config.productionTip = false;
 
 new Vue({
-  router,
-  store,
-  render: h => h(App)
+    router,
+    store,
+    render: h => h(App)
 }).$mount("#app");

+ 13 - 0
src/store/modules/app.ts

@@ -15,6 +15,7 @@ export enum DeviceType {
 
 export interface IAppState {
     device: DeviceType;
+    uploaderList: any[];
     sidebar: {
         opened: boolean;
         withoutAnimation: boolean;
@@ -28,6 +29,8 @@ class App extends VuexModule implements IAppState {
         withoutAnimation: false
     };
 
+    // 当前上传的文件列表
+    public uploaderList: any[] = [];
     public device = DeviceType.Desktop;
 
     @Mutation
@@ -49,6 +52,11 @@ class App extends VuexModule implements IAppState {
     }
 
     @Mutation
+    private SET_UPLOADERLIST(list: any[]) {
+        this.uploaderList = list;
+    }
+
+    @Mutation
     private TOGGLE_DEVICE(device: DeviceType) {
         this.device = device;
     }
@@ -64,6 +72,11 @@ class App extends VuexModule implements IAppState {
     }
 
     @Action
+    public SetUploaderList(list: any[]) {
+        this.SET_UPLOADERLIST(list);
+    }
+
+    @Action
     public ToggleDevice(device: DeviceType) {
         this.TOGGLE_DEVICE(device);
     }

+ 276 - 0
src/views/manage/build/components/AddFloorDialog/index.vue

@@ -0,0 +1,276 @@
+<template>
+    <!-- 新增楼层文件 -->
+    <div id="addFloorDialog">
+        <el-dialog title="新增楼层" :visible.sync="addFloorFileVisible" width="900px" :before-close="handleClose">
+            <el-form ref="addfloorform" :model="form" label-width="120px" style="overflow: hidden">
+                <el-form-item label="模型文件:">
+                    <el-upload
+                        class="upload-demo"
+                        ref="upload"
+                        action="/modelapi/model-file/upload"
+                        :on-preview="handlePreview"
+                        :on-remove="handleRemove"
+                        :file-list="fileList"
+                        :auto-upload="false"
+                        :on-change="onChangeUpLoad"
+                        :limit="1"
+                    >
+                        <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
+                    </el-upload>
+                </el-form-item>
+                <el-form-item label="revit版本:">
+                    <el-select v-model="form.revitVersion" placeholder="请选择">
+                        <el-option v-for="item in revitVersionList" :key="`revit-${item.value}`" :label="item.label" :value="item.value"></el-option>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="模型所属楼层:">
+                    <div class="floorModle">
+                        <el-select v-model="form.floorTypeVal" placeholder="请选择">
+                            <el-option v-for="item in floorType" :key="item.value" :label="item.label" :value="item.value"></el-option>
+                        </el-select>
+                        <!-- 计数 -->
+                        <el-input-number
+                            style="margin-left: 10px"
+                            v-model="form.floorNum"
+                            :min="1"
+                            :disabled="form.floorTypeVal === 'RF'"
+                        ></el-input-number>
+                        <!-- 是否夹层 -->
+                        <el-checkbox style="margin: 0 10px" v-model="form.haveInterlayer">是否夹层</el-checkbox>
+                        <!-- 夹层选择 -->
+                        <el-select v-model="form.interlayerTypeVal" :disabled="!form.haveInterlayer" placeholder="请选择">
+                            <el-option v-for="item in interlayerType" :key="item.value" :label="item.label" :value="item.value"></el-option>
+                        </el-select>
+                    </div>
+                </el-form-item>
+                <el-form-item label="备注信息:">
+                    <el-input type="textarea" v-model="form.desc"></el-input>
+                </el-form-item>
+                <el-form-item style="float: right">
+                    <el-button type="primary" @click="onSubmit">确认</el-button>
+                    <el-button @click="handleClose">取消</el-button>
+                </el-form-item>
+            </el-form>
+            <div style="float: right; margin-top: -10px; color: #aaaaaa">目前只可识别标准字典定义的设备、部件</div>
+        </el-dialog>
+    </div>
+</template>
+<script lang="ts">
+import { Vue, Component, Prop, Emit, Ref, Watch } from "vue-property-decorator";
+import { UserModule } from "@/store/modules/user";
+import { modelFileUpload } from "@/api/modelapi";
+import { ElUpload } from "element-ui/types/upload";
+
+@Component({
+    name: "AddFloorDialog",
+    components: {},
+})
+export default class extends Vue {
+    @Prop() private addFloorFileVisible!: boolean;
+    @Prop() private FolderName!: string;
+    @Prop() private floorList!: any[];
+    @Prop() private FolderId!: string;
+
+    @Ref("upload") readonly upload!: ElUpload;
+
+    /**
+     * 关闭添加楼层模型文件弹窗
+     */
+    @Emit("closeAddFloorDia")
+    handleClose() {
+        return;
+    }
+
+    private get projectId(): string {
+        return UserModule.projectId;
+    }
+
+    private get username(): string {
+        return UserModule.username;
+    }
+
+    private get userId(): string {
+        return UserModule.userId;
+    }
+
+    mounted() {
+        this.fileList = [];
+        this.form.file = null;
+    }
+
+    // 创建楼层信息
+    private form: any = {
+        desc: "", //描述
+        revitVersion: "2017", //revit默认版本
+        floorTypeVal: "F", //楼层类型得值
+        interlayerTypeVal: "M1", //夹层类型得值
+        haveInterlayer: false, //是否有夹层
+        file: null, //上传文件
+        floorNum: 1, //楼层
+    };
+    // 上传楼层列表
+    private fileList = [];
+    // revit版本选项
+    private revitVersionList = [
+        {
+            value: "2016",
+            label: "2016",
+        },
+        {
+            value: "2017",
+            label: "2017",
+        },
+        {
+            value: "2018",
+            label: "2018",
+        },
+    ];
+    // 楼层类型
+    private floorType = [
+        {
+            value: "F",
+            label: "正常层(F)",
+        },
+        {
+            value: "RF",
+            label: "屋顶(RF)",
+        },
+        {
+            value: "B",
+            label: "地下(B)",
+        },
+    ];
+    // 夹层类型
+    interlayerType = [
+        {
+            value: "M1",
+            label: "夹层M1",
+        },
+        {
+            value: "M2",
+            label: "夹层M2",
+        },
+        {
+            value: "M3",
+            label: "夹层M3",
+        },
+    ];
+
+    /**
+     * 提交表单创建楼层并发出上传指令
+     */
+    private async onSubmit() {
+        if (this.form.file == null) {
+            this.$message.error("模型文件不能为空!");
+        } else if (!this.form.revitVersion) {
+            this.$message.error("revit版本不能为空!");
+        } else {
+            let FloorName: string | null = null;
+            // 根据是否有夹层拼接楼层名
+            if (this.form.haveInterlayer) {
+                if (this.form.floorTypeVal === "RF") {
+                    FloorName = this.form.floorTypeVal + this.form.interlayerTypeVal;
+                } else {
+                    FloorName = this.form.floorTypeVal + this.form.floorNum + this.form.interlayerTypeVal;
+                }
+            } else {
+                if (this.form.floorTypeVal === "RF") {
+                    FloorName = this.form.floorTypeVal;
+                } else {
+                    FloorName = this.form.floorTypeVal + this.form.floorNum;
+                }
+            }
+            let FloorObj = this.floorList.find((item) => {
+                return item.FloorName == FloorName;
+            });
+            if (FloorObj && FloorObj.Status !== 0) {
+                this.$message.error("该楼层名称已存在,请勿重复创建!");
+            } else {
+                let data = {
+                    FileName: this.form.file.name,
+                    FloorName: FloorName,
+                    FolderId: this.FolderId,
+                    RevitVersion: this.form.revitVersion,
+                    Note: this.form.desc,
+                    ProjectId: this.projectId,
+                    ReplaceReason: null,
+                    Size: this.form.file.size,
+                    UserName: this.username,
+                    UserId: this.userId,
+                };
+                const res = await modelFileUpload(data);
+                //  创建成功
+                if (res.ModelId && res.UploadId) {
+                    this.$emit("uploadModelFile", {
+                        modelId: res.ModelId,
+                        uploadId: res.UploadId,
+                        file: this.form.file,
+                    });
+                    this.handleClose();
+                } else {
+                    this.$message.info("准备分片上传模型文件接口失败!");
+                }
+            }
+        }
+    }
+
+    /**
+     * 上传到服务器
+     */
+    private submitUpload(FloorModelId: string) {
+        console.log(FloorModelId);
+        debugger;
+        this.upload.submit();
+    }
+
+    /**
+     * 文件列表移除文件时的钩子
+     * @info 删除上传文件
+     */
+    private handleRemove() {
+        this.fileList = [];
+        this.form.file = null;
+    }
+
+    /**
+     * 点击文件列表中已上传的文件时的钩子
+     */
+    private handlePreview(file: File, fileList: FileList) {
+        console.log(file, fileList);
+    }
+
+    /**
+     * 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
+     * @info 获取上传文件
+     */
+    private onChangeUpLoad(file: File, fileList: FileList) {
+        if (fileList.length) {
+            this.form.file = file;
+        }
+    }
+
+    @Watch("addFloorFileVisible")
+    onVisibleChanged(val: boolean) {
+        if (val) {
+            this.handleRemove();
+            this.form = {
+                revitVersion: "2017", //revit默认版本
+                desc: "", //描述
+                floorTypeVal: "F", //楼层类型得值
+                interlayerTypeVal: "M1", //夹层类型得值
+                haveInterlayer: false, //是否有夹层
+                file: null, //上传文件
+                floorNum: 1, //楼层
+            };
+        }
+    }
+}
+</script>
+<style lang="scss">
+#addFloorDialog {
+    .floorModle {
+        display: flex;
+        justify-content: left;
+    }
+}
+</style>

+ 16 - 24
src/views/manage/build/components/Floortable/index.vue

@@ -48,13 +48,13 @@
                             class="el-icon-bank-card"
                             @click="queryModelLog(scope.row)"
                         ></el-button>
-                        <!-- <el-button
+                        <el-button
                             title="下载BIMID模型"
                             type="primary"
                             size="mini"
                             class="el-icon-download"
                             @click="downloadModelBIMID(scope.row)"
-                        ></el-button> -->
+                        ></el-button>
                     </div>
                     <div :class="['upLoad-loading']" v-show="scope.row.Status !== 4">
                         <div class="progress">
@@ -83,14 +83,12 @@
                 </template>
             </el-table-column>
         </el-table>
-        <!-- <version-dialog :dialogVisible.sync="dialogVisible" :modelFile="modelFile" ref="addSpaceDialog"></version-dialog> -->
     </div>
 </template>
 <script lang="ts">
-import { Vue, Component, Prop, Watch, Emit, Ref } from "vue-property-decorator";
+import { Vue, Component, Prop, Emit, Ref } from "vue-property-decorator";
 import { UserModule } from "@/store/modules/user";
 import { ElTable } from "element-ui/types/table";
-// import versionDialog from "@/components/model/file/versionDialog";
 @Component({
     name: "FloorTable",
     components: {},
@@ -99,33 +97,23 @@ export default class extends Vue {
     @Prop({ default: [] }) private tableData!: any[];
     @Prop({ default: "" }) private modelFolderName!: string;
 
-    private maxHeight = 0;
-    private dialogVisible = false;
-    private modelFile = null;
-
     @Ref("filterTable") readonly filterTable!: ElTable;
 
-    get projectId() {
+    private get projectId() {
         return UserModule.projectId;
     }
 
-    get username() {
-        return UserModule.username;
-    }
-
-    // 查看日志
+    /**
+     * 查看日志
+     */
     @Emit("openModelLog")
     private queryModelLog(item: any) {
         return item;
     }
 
-    // 查看版本信息
-    // private handleClickVersion(item: any) {
-    //     this.modelFile = item;
-    //     this.dialogVisible = true;
-    // }
-
-    // 替换模型
+    /**
+     * 替换模型
+     */
     private replaceModel(item: any) {
         if (item.Status !== 4) {
             this.$alert("正在识别模型对象,请稍后再替换。", "替换模型", { confirmButtonText: "确定" });
@@ -134,7 +122,9 @@ export default class extends Vue {
         }
     }
 
-    // 下载模型文件
+    /**
+     * 下载模型文件
+     */
     private downloadModel(item: any) {
         let url = item.Url.match(/(\/image-service\S*)$/g) ? item.Url.match(/(\/image-service\S*)$/g)[0] : "";
         if (url) {
@@ -151,7 +141,9 @@ export default class extends Vue {
         }
     }
 
-    // 下载BIMID模型文件
+    /**
+     * 下载BIMID模型文件
+     */
     private downloadModelBIMID(item: any) {
         let url = item.Url.match(/(\/image-service\S*)$/g) ? item.Url.match(/(\/image-service\S*)$/g)[0] : "";
         if (url) {

+ 196 - 0
src/views/manage/build/components/RepliceModel/index.vue

@@ -0,0 +1,196 @@
+ <!-- 替换模型弹窗 -->
+
+<template>
+    <!-- 新增楼层 -->
+    <div id="replaceModel">
+        <el-dialog title="替换模型" :visible.sync="repliceModelVisible" width="650px" :before-close="handleClose">
+            <el-form ref="form" :model="form" label-width="170px">
+                <el-form-item label="模型文件:">
+                    <el-upload
+                        class="upload-demo"
+                        ref="upload"
+                        action="/modelapi/model-file/upload"
+                        :on-preview="handlePreview"
+                        :on-remove="handleRemove"
+                        :file-list="fileList"
+                        :auto-upload="false"
+                        :on-change="onChangeUpLoad"
+                        :limit="1"
+                    >
+                        <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
+                    </el-upload>
+                </el-form-item>
+                <el-form-item label="revit版本:">
+                    <el-select v-model="form.revitVersion" placeholder="请选择">
+                        <el-option v-for="item in revitVersionList" :key="`revit-${item.value}`" :label="item.label" :value="item.value"></el-option>
+                    </el-select>
+                </el-form-item>
+                <el-form-item label="选择模型文件替换原因:">
+                    <ul>
+                        <li>
+                            <el-radio :disabled="isChioce" v-model="form.ReplaceReason" label="0">之前模型画错要修改</el-radio>
+                        </li>
+                        <li>
+                            <el-radio :disabled="isChioce" v-model="form.ReplaceReason" label="1">因工程改造,更新模型文件</el-radio>
+                        </li>
+                    </ul>
+                </el-form-item>
+                <el-form-item>
+                    <div class="dateTime" v-show="form.ReplaceReason === '1'">
+                        <p>工程改造竣工时间:</p>
+                        <el-date-picker v-model="form.finishTime" type="date" placeholder="选择日期"></el-date-picker>
+                    </div>
+                </el-form-item>
+            </el-form>
+            <div slot="footer" class="dialog-footer">
+                <el-button type="primary" @click="onSubmit">确认</el-button>
+                <el-button @click="handleClose">取消</el-button>
+                <div style="width: 100%; margin-top: 5px; color: #aaaaaa">目前只可识别标准字典定义的设备、部件</div>
+            </div>
+        </el-dialog>
+    </div>
+</template>
+<script lang="ts">
+import { Vue, Component, Prop, Emit, Watch } from "vue-property-decorator";
+import { AppModule } from "@/store/modules/app";
+import { UserModule } from "@/store/modules/user";
+import { modelFileUpload } from "@/api/modelapi";
+
+@Component({
+    name: "RepliceModel",
+    components: {},
+})
+export default class extends Vue {
+    @Prop() private repliceModelVisible!: boolean;
+    @Prop() private replaceModelItem!: any;
+
+    /**
+     * 关闭替换楼层模型文件弹窗
+     */
+    @Emit("closeReplaceModelDia")
+    handleClose() {
+        return;
+    }
+
+    private get projectId(): string {
+        return UserModule.projectId;
+    }
+
+    private get username(): string {
+        return UserModule.username;
+    }
+
+    private get userId(): string {
+        return UserModule.userId;
+    }
+
+    private get uploaderList(): any[] {
+        return AppModule.uploaderList;
+    }
+
+    // 更新楼层信息
+    private form: any = {
+        revitVersion: "2017", //revit默认版本
+        file: null, //上传文件
+        ReplaceReason: "0",
+        finishTime: "",
+    };
+    // revit版本选项
+    private revitVersionList = [
+        {
+            value: "2016",
+            label: "2016",
+        },
+        {
+            value: "2017",
+            label: "2017",
+        },
+        {
+            value: "2018",
+            label: "2018",
+        },
+    ];
+    // 是否可以选择替换原因
+    private isChioce = true;
+    // 上传楼层列表
+    private fileList = [];
+
+    /**
+     * 发送替换模型命令及参数
+     */
+    private async onSubmit() {
+        if (this.form.file == null) {
+            this.$message.error("模型文件不能为空!");
+        } else if (!this.form.revitVersion) {
+            this.$message.error("revit版本不能为空!");
+        } else {
+            let data = {
+                FileName: this.form.file.name,
+                FloorName: this.replaceModelItem.FloorName,
+                FolderId: this.replaceModelItem.FolderId,
+                RevitVersion: this.form.revitVersion,
+                Note: this.replaceModelItem.Note,
+                ProjectId: this.projectId,
+                ReplaceReason: 0,
+                Size: this.form.file.size,
+                UserName: this.username,
+                UserId: this.userId,
+            };
+            const res = await modelFileUpload(data);
+            if (res.UploadId && res.ModelId) {
+                this.$emit("uploadModelFile", {
+                    modelId: res.ModelId,
+                    uploadId: res.UploadId,
+                    file: this.form.file,
+                });
+                this.handleClose();
+            } else {
+                this.$message.info("准备分片上传模型文件接口失败!");
+            }
+        }
+    }
+
+    /**
+     * 文件列表移除文件时的钩子
+     * @info 删除上传文件
+     */
+    private handleRemove() {
+        this.form.file = null;
+        this.fileList = [];
+    }
+
+    /**
+     * 点击文件列表中已上传的文件时的钩子
+     */
+    private handlePreview(file: File, fileList: FileList) {
+        console.log(file, fileList);
+    }
+
+    /**
+     * 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
+     * @info 获取上传文件
+     */
+    private onChangeUpLoad(file: File, fileList: FileList) {
+        if (fileList.length) {
+            this.form.file = file;
+        }
+    }
+
+    @Watch("repliceModelVisible")
+    onVisibleChanged(val: boolean) {
+        if (val) {
+            this.handleRemove();
+        }
+    }
+}
+</script>
+<style lang="scss">
+#replaceModel {
+    .dialog-footer {
+        text-align: right;
+    }
+    ::v-deep .el-dialog__footer {
+        padding: 10px;
+    }
+}
+</style>

+ 84 - 21
src/views/manage/build/index.vue

@@ -7,13 +7,13 @@
                     <div class="top_hand left_top_hand">
                         <div class="folder-box">
                             <el-tooltip class="item" effect="dark" content="新建建筑" placement="top-start">
-                                <el-button icon="el-icon-plus" size="small" @click="addFolder"></el-button>
+                                <el-button icon="el-icon-plus" size="small" @click="addBuild"></el-button>
                             </el-tooltip>
                             <el-tooltip class="item" effect="dark" content="删除建筑" placement="top-start">
-                                <el-button icon="el-icon-minus" size="small" @click="removeFolder"></el-button>
+                                <el-button icon="el-icon-minus" size="small" @click="deleteBuild"></el-button>
                             </el-tooltip>
                             <el-tooltip class="item" effect="dark" content="编辑建筑" placement="top-start">
-                                <el-button @click="editFolder" icon="el-icon-edit-outline" size="small"></el-button>
+                                <el-button @click="updateBuild" icon="el-icon-edit-outline" size="small"></el-button>
                             </el-tooltip>
                         </div>
                     </div>
@@ -45,7 +45,7 @@
             <el-card class="box-card" :body-style="{ padding: '0px', height: '100%' }">
                 <!-- 顶部操作栏 -->
                 <div class="top_hand right_top_hand">
-                    <el-button size="small" @click="addFloorFile">添加楼层模型文件</el-button>
+                    <el-button size="small" @click="addFloorFileVisible = true">添加楼层模型文件</el-button>
                     <el-button size="small" @click="queryFloorFile(currentFolderId)">刷新</el-button>
                 </div>
                 <!-- 列表 -->
@@ -56,7 +56,6 @@
                     :modelFolderName="currentFolderName"
                     @openModelLog="queryModelLog"
                     @replaceModel="repliaceModel"
-                    @percentFinish="queryFloorFile(currentFolderId)"
                 ></floor-table>
             </el-card>
         </div>
@@ -71,21 +70,21 @@
             :logData="logData"
         ></modelLog> -->
         <!-- 替换模型弹窗 -->
-        <!-- <repliceModel
+        <replice-model
             :repliceModelVisible="repliceModelVisible"
             @closeReplaceModelDia="repliceModelVisible = false"
             :replaceModelItem="replaceModelItem"
-            @updataModel="updateModelFile"
-        ></repliceModel> -->
+            @uploadModelFile="uploadModelFile"
+        ></replice-model>
         <!-- 新增楼层 -->
-        <!-- <addFloorDialog
+        <add-floor-dialog
             :addFloorFileVisible="addFloorFileVisible"
             :floorList="tableData"
             :FolderName="currentFolderName"
             :FolderId="currentFolderId"
             @closeAddFloorDia="addFloorFileVisible = false"
-            @finishCreateFloor="uploadModelFIle"
-        ></addFloorDialog> -->
+            @uploadModelFile="uploadModelFile"
+        ></add-floor-dialog>
         <!-- 新增文件夹名称 -->
         <!-- <addFolder
             :addFolderVisible="addFolderVisible"
@@ -116,12 +115,16 @@ import { Vue, Component, Watch } from "vue-property-decorator";
 import { UserModule } from "@/store/modules/user";
 import { queryFloorList, queryModel } from "@/api/modelapi";
 import Bus from "@/utils/bus";
-import FloorTable from "./components/Floortable/index.vue"; //右侧list表
+import FloorTable from "./components/FloorTable/index.vue"; //右侧list表
+import AddFloorDialog from "./components/AddFloorDialog/index.vue"; //新增楼层弹窗
+import RepliceModel from "./components/RepliceModel/index.vue"; //替换模型弹窗
 
 @Component({
     name: "buildFloor",
     components: {
         FloorTable,
+        AddFloorDialog,
+        RepliceModel,
     },
 })
 export default class extends Vue {
@@ -139,7 +142,9 @@ export default class extends Vue {
     private loading = false; // 加载loading
     private tableLoading = false; // 表格加载loading
 
-    get projectId() {
+    private replaceModelItem = null; // 替换文件的item
+
+    private get projectId(): string {
         return UserModule.projectId;
     }
 
@@ -150,8 +155,10 @@ export default class extends Vue {
         });
     }
 
-    // 打开模型文件夹
-    private openFolder(index: number, item: any) {
+    /**
+     * 打开模型文件夹
+     */
+    private openFolder(index: number, item: any): void {
         this.choiceIndex = index + 1;
         this.currentFolderId = item.Id;
         this.currentFolderName = item.Name;
@@ -159,8 +166,31 @@ export default class extends Vue {
         this.queryFloorFile(this.currentFolderId);
     }
 
-    // 查询所有文件夹模型
-    private async queryModel() {
+    /**
+     * 新增建筑
+     */
+    private addBuild(): void {
+        return;
+    }
+
+    /**
+     * 删除建筑
+     */
+    private deleteBuild(): void {
+        return;
+    }
+
+    /**
+     * 维护建筑
+     */
+    private updateBuild(): void {
+        return;
+    }
+
+    /**
+     * 查询所有文件夹模型
+     */
+    private async queryModel(): Promise<void> {
         this.navigationModel = [];
         this.loading = true;
         const res = await queryModel({ Orders: "Name asc" });
@@ -178,6 +208,8 @@ export default class extends Vue {
     }
 
     /**
+     * 查询建筑下的楼层
+     *
      * @info: 状态显示说明
      * 0: 上传中(上传的用户可操作:中止)
      * 1: 等待检查
@@ -185,8 +217,10 @@ export default class extends Vue {
      * 11: 未通过检查
      * 2、20、21、3、31: 正常(所有用户可操作:下载、查看历史)
      * 4: 正常(所有用户可操作:下载、替换、查看历史)
+     *
+     * @param currentFolderId    当前选中的建筑Id
      */
-    private async queryFloorFile(currentFolderId: string) {
+    private async queryFloorFile(currentFolderId: string): Promise<void> {
         if (currentFolderId) {
             this.tableData = [];
             this.tableLoading = true;
@@ -201,13 +235,42 @@ export default class extends Vue {
         }
     }
 
-    private queryModelLog(item: any) {
-        console.log(item);
+    /**
+     * 打开替换文件模型
+     */
+    private repliaceModel(item: any): void {
+        this.replaceModelItem = item;
+        this.repliceModelVisible = true;
     }
 
-    private repliaceModel(item: any) {
+    /**
+     * 上传模型文件
+     */
+    private uploadModelFile(data: any) {
+        Bus.$emit(
+            "openUploader",
+            {
+                uploadId: data.uploadId,
+                modelId: data.modelId,
+                systemId: "revit", //系统名称
+                secret: "63afbef6906c342b", //系统密码
+            },
+            data.file.raw
+        );
+        this.queryFloorFile(this.currentFolderId);
+    }
+
+    /**
+     * 查看模型日志
+     */
+    private queryModelLog(item: any): void {
         console.log(item);
     }
+
+    @Watch("projectId")
+    onProjectIdChanged() {
+        this.queryModel();
+    }
 }
 </script>