From 4e1cc94bb151637f52f808e43e32b2e874260c0d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A3=B7=E5=8F=B6?=
 <14103883+leaf-phos@user.noreply.gitee.com>
Date: Fri, 27 Dec 2024 13:54:58 +0800
Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 package.json                                  |   1 +
 src/components/ImageUpload/index.vue          |  84 ++++-
 .../bst/order/components/OrderFlowList.vue    | 295 +++++++++++++-----
 src/views/bst/order/edit/edit.vue             |   8 +-
 src/views/bst/order/index.vue                 |   4 +-
 .../components/ProcessCardList.vue            |   5 +-
 src/views/index.vue                           |  12 +-
 7 files changed, 330 insertions(+), 79 deletions(-)

diff --git a/package.json b/package.json
index 4941034..d773a3c 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
     "vue": "2.6.12",
     "vue-count-to": "1.0.13",
     "vue-cropper": "0.5.5",
+    "vue-masonry-css": "^1.0.3",
     "vue-meta": "2.4.0",
     "vue-router": "3.4.9",
     "vuedraggable": "2.24.3",
diff --git a/src/components/ImageUpload/index.vue b/src/components/ImageUpload/index.vue
index be6ee68..1caf93e 100644
--- a/src/components/ImageUpload/index.vue
+++ b/src/components/ImageUpload/index.vue
@@ -1,5 +1,9 @@
 <template>
-  <div class="component-upload-image">
+  <div 
+    class="component-upload-image" 
+    @mouseenter="handleMouseEnter"
+    @mouseleave="handleMouseLeave"
+  >
     <el-upload
       multiple
       :action="uploadImgUrl"
@@ -18,6 +22,7 @@
       :class="{hide: this.fileList.length >= this.limit}"
       class="image-upload"
       :style="cssVars"
+      drag
     >
       <i class="el-icon-plus"></i>
     </el-upload>
@@ -46,6 +51,7 @@
 
 <script>
 import { getToken } from '@/utils/auth'
+import axios from 'axios'
 
 export default {
   props: {
@@ -91,7 +97,8 @@ export default {
       headers: {
         Authorization: "Bearer " + getToken(),
       },
-      fileList: []
+      fileList: [],
+      isListening: false // 新增:标记是否正在监听粘贴事件
     };
   },
   watch: {
@@ -133,6 +140,71 @@ export default {
     }
   },
   methods: {
+    // 新增:鼠标进入处理
+    handleMouseEnter() {
+      if (!this.isListening) {
+        document.addEventListener('paste', this.handlePaste);
+        this.isListening = true;
+      }
+    },
+    // 新增:鼠标离开处理
+    handleMouseLeave() {
+      if (this.isListening) {
+        document.removeEventListener('paste', this.handlePaste);
+        this.isListening = false;
+      }
+    },
+    // 粘贴图片上传
+    handlePaste(event) {
+      // 阻止默认行为
+      event.preventDefault();
+      
+      const items = event.clipboardData?.items;
+      
+      if (!items) {
+        this.$modal.msgError('当前浏览器不支持粘贴上传');
+        return;
+      }
+
+      let file = null;
+      for (let i = 0; i < items.length; i++) {
+        if (items[i].type.indexOf('image') !== -1) {
+          file = items[i].getAsFile();
+          break;
+        }
+      }
+
+      if (!file) {
+        this.$modal.msgError('粘贴内容中不包含图片');
+        return;
+      }
+
+      // 检查是否超出数量限制
+      if (this.fileList.length >= this.limit) {
+        this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);
+        return;
+      }
+
+      // 使用 handleBeforeUpload 进行文件验证
+      const valid = this.handleBeforeUpload(file);
+      if (valid !== false) {
+        // 手动上传文件
+        const formData = new FormData();
+        formData.append('file', file);
+        
+        // 发起上传请求
+        axios.post(this.uploadImgUrl, formData, {
+          headers: {
+            ...this.headers,
+            'Content-Type': 'multipart/form-data'
+          }
+        }).then(res => {
+          this.handleUploadSuccess(res.data, file);
+        }).catch(() => {
+          this.handleUploadError();
+        });
+      }
+    },
     // 上传前loading加载
     handleBeforeUpload(file) {
       let isImg = false;
@@ -248,6 +320,14 @@ export default {
   height: var(--height);
 }
 
+::v-deep .el-upload-dragger {
+  width: var(--width);
+  height: var(--height);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
 /* 图片操作按钮容器 */
 ::v-deep .el-upload-list__item-actions {
   display: flex;
diff --git a/src/views/bst/order/components/OrderFlowList.vue b/src/views/bst/order/components/OrderFlowList.vue
index 6406156..fe9cb1a 100644
--- a/src/views/bst/order/components/OrderFlowList.vue
+++ b/src/views/bst/order/components/OrderFlowList.vue
@@ -1,59 +1,53 @@
 <template>
   <div class="order-flow" v-loading="loading">
-    <el-row :gutter="20" v-if="!isEmpty(list)">
-      <el-col :span="6" v-for="order in list" :key="order.id">
+    <div class="waterfall-container" v-if="!isEmpty(list)" ref="container">
+      <div 
+        v-for="(order, index) in list" 
+        :key="order.id" 
+        class="waterfall-item"
+        :style="itemPositions[index]"
+      >
         <el-card class="order-card" shadow="hover">
-          <!-- 主图区域 -->
-          <div class="order-main-image">
-            <image-preview :src="order.picture" :width="200" :height="200" />
-          </div>
+          <div class="order-header">
+            <!-- 主图区域 -->
+            <div class="order-main-image">
+              <image-preview :src="order.picture" :width="100" :height="100" />
+            </div>
 
-          <!-- 订单信息区域 -->
-          <div class="order-info">
-            <div class="info-row">
-              <span class="label">订单号:</span>
-              <span class="value">{{ order.orderNo }}</span>
-            </div>
-            <div class="info-row">
-              <span class="label">客户名称:</span>
-              <span class="value">{{ order.customer }}</span>
-            </div>
-            <div class="info-row">
-              <span class="label">数量:</span>
-              <span class="value">{{ order.num }}</span>
-            </div>
-            <div class="info-row">
-              <span class="label">装量:</span>
-              <span class="value">{{ order.reportNum }}/{{ order.num }}</span>
-            </div>
-            <div class="info-row">
-              <span class="label">特殊要求:</span>
-              <span class="value">{{ order.remark }}</span>
+            <!-- 订单信息区域 -->
+            <div @click="handleOrderClick(order)">
+              <el-descriptions :column="2" size="small">  
+                <el-descriptions-item label="订单" :span="2">{{ order.orderNo | dv}}</el-descriptions-item>
+                <el-descriptions-item label="客户" :span="2">{{ order.customer | dv}}</el-descriptions-item>
+                <el-descriptions-item label="特殊要求" :span="2">{{ order.remark | dv}}</el-descriptions-item>
+                <el-descriptions-item label="数量">{{ order.num | dv}}</el-descriptions-item>
+                <el-descriptions-item label="装量">{{ order.storeNum | dv}}</el-descriptions-item>
+              </el-descriptions>
             </div>
           </div>
+          <div class="progress-container">
+            <el-progress :percentage="order.progress" :color="ProgressColors" :format="ProgressFormat" style="width: 100%;" :stroke-width="10"/>
+          </div>
 
           <!-- 子产品列表 -->
           <div class="sub-products">
-            <div
-              class="sub-product"
-              v-for="(prod, index) in order.orderProdList"
-              :key="index"
-            >
-              <el-checkbox :value="prod.source === 1" disabled
-                >子产品{{ index + 1 }}</el-checkbox
-              >
+            <div class="sub-product" v-for="(prod, index) in order.prodList" :key="index">
+              <image-preview class="prod-image" :src="prod.picture" :width="28" :height="28" />
+              <span class="prod-name">{{ prod.name | dv }}</span>
+              <dict-tag class="prod-type" :value="prod.workType" :options="dict.type.order_prod_work_type" size="mini"/>
               <div class="prod-progress">
                 <el-progress
                   :percentage="prod.progress || 0"
                   :color="ProgressColors"
                   :format="ProgressFormat"
+                  :stroke-width="6"
                 />
               </div>
             </div>
           </div>
         </el-card>
-      </el-col>
-    </el-row>
+      </div>
+    </div>
     <el-empty v-else description="暂无进行中的订单" />
   </div>
 </template>
@@ -66,6 +60,7 @@ import { isEmpty } from '@/utils/index'
 
 export default {
   name: 'OrderFlowList',
+  dicts: ['order_prod_work_type'],
   props: {
   },
   data() {
@@ -73,28 +68,127 @@ export default {
       ProgressColors,
       list: [],
       queryParams: {
-        pageSize: 10,
+        pageSize: 12,
         pageNum: 1,
         status: OrderStatus.RELEASED,
+        needProd: true,
+        needProdProgress: true,
+        orderByColumn: "createTime",
+        isAsc: "desc"
       },
       loading: false,
       total: 0,
+      itemPositions: [],
+      columnWidth: 300,
+      columnCount: 4,
+      columnHeights: [],
+      resizeObserver: null
     }
   },
   created() {
     this.getList()
   },
+  mounted() {
+    this.initWaterfall()
+    this.resizeObserver = new ResizeObserver(this.handleResize)
+    if (this.$refs.container) {
+      this.resizeObserver.observe(this.$refs.container)
+    }
+  },
+  beforeDestroy() {
+    if (this.resizeObserver) {
+      this.resizeObserver.disconnect()
+      this.resizeObserver = null
+    }
+  },
   methods: {
+    handleOrderClick(order) {
+      this.$router.push({ path: `/view/order/${order.id}` })
+    },
     ProgressFormat,
     isEmpty,
     getList() {
-      this.loading = true;
+      this.loading = true
       listOrder(this.queryParams).then(res => {
-        this.list = res.rows;
-        this.total = res.total;
+        this.list = res.rows
+        this.total = res.total
+        this.$nextTick(() => {
+          this.initWaterfall()
+        })
       }).finally(() => {
-        this.loading = false;
+        this.loading = false
       })
+    },
+    initWaterfall() {
+      this.$nextTick(() => {
+        const container = this.$refs.container
+        if (!container) {
+          return
+        }
+        
+        if (this.resizeObserver && !this.isObserving) {
+          this.resizeObserver.observe(container)
+          this.isObserving = true
+        }
+
+        // 根据容器宽度计算列数,最大4列
+        const containerWidth = container.clientWidth
+        const minColumnWidth = 300 // 最小列宽
+        this.columnCount = Math.min(4, Math.max(1, Math.floor(containerWidth / minColumnWidth)))
+        
+        // 计算实际列宽,平均分配容器宽度
+        this.columnWidth = (containerWidth - (this.columnCount + 1) * 8) / this.columnCount // 8px是padding的值
+        
+        // 初始化列高度数组
+        this.columnHeights = new Array(this.columnCount).fill(0)
+        
+        // 计算每个卡片的位置
+        this.layoutItems()
+      })
+    },
+    layoutItems() {
+      const items = this.$refs.container?.getElementsByClassName('waterfall-item')
+      if (!items) return
+
+      this.columnHeights = new Array(this.columnCount).fill(0)
+      this.itemPositions = []
+
+      Array.from(items).forEach((item, index) => {
+        const minHeight = Math.min(...this.columnHeights)
+        const column = this.columnHeights.indexOf(minHeight)
+        
+        // 计算每列的x坐标,考虑padding间距
+        const left = column * this.columnWidth + (column + 1) * 8
+        const top = minHeight
+        
+        this.itemPositions[index] = {
+          transform: `translate3d(${left}px, ${top}px, 0)`,
+          width: `${this.columnWidth}px`
+        }
+        
+        this.columnHeights[column] = minHeight + item.offsetHeight + 8
+      })
+
+      const containerHeight = Math.max(...this.columnHeights)
+      this.$refs.container.style.height = `${containerHeight}px`
+    },
+    handleResize() {
+      if (this.resizeTimer) {
+        clearTimeout(this.resizeTimer)
+      }
+      this.resizeTimer = setTimeout(() => {
+        this.initWaterfall()
+      }, 100)
+    }
+  },
+  watch: {
+    list: {
+      handler() {
+        this.$nextTick(() => {
+          this.initWaterfall()
+        })
+      },
+      deep: true
     }
   }
 }
@@ -102,45 +196,104 @@ export default {
 
 <style lang="scss" scoped>
 .order-flow {
+  .waterfall-container {
+    position: relative;
+    margin: 0 auto;
+    box-sizing: border-box;
+  }
+
+  .waterfall-item {
+    position: absolute;
+    transition: all 0.3s ease;
+    box-sizing: border-box;
+  }
+
   .order-card {
-    margin-bottom: 20px;
+    margin-bottom: 0;
+    z-index: 1; 
+    cursor: pointer;
+  }
 
-    .order-main-image {
-      text-align: center;
-      margin-bottom: 15px;
-    }
+  .order-header {
+    display: flex;
+  }
 
-    .order-info {
-      .info-row {
-        margin-bottom: 8px;
-        display: flex;
+  .progress-container {
+    margin-bottom: 16px;
+  }
 
-        .label {
-          color: #606266;
-          width: 70px;
-        }
+  .order-main-image {
+    text-align: center;
+    margin-right: 16px;
+  }
 
-        .value {
-          flex: 1;
-          color: #303133;
-          overflow: hidden;
-          text-overflow: ellipsis;
-          white-space: nowrap;
-        }
+  .order-info {
+    .info-row {
+      margin-bottom: 8px;
+      display: flex;
+
+      .label {
+        color: #606266;
+        width: 70px;
+      }
+
+      .value {
+        flex: 1;
+        color: #303133;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
       }
     }
+  }
 
-    .sub-products {
-      margin-top: 15px;
+  .sub-products {
 
-      .sub-product {
-        display: flex;
-        align-items: center;
-        margin-bottom: 10px;
+    .sub-title {
+      font-size: 14px;
+      color: #606266;
+      margin-bottom: 12px;
+      font-weight: 500;
+    }
 
-        .prod-progress {
-          flex: 1;
-          margin-left: 10px;
+    .sub-product {
+      display: flex;
+      align-items: center;
+      padding: 4px;
+      margin-bottom: 4px;
+      border-radius: 4px;
+      transition: all 0.3s ease;
+      background: #f8f9fb;
+
+      &:hover {
+        background: #f0f2f5;
+      }
+
+      .prod-image {
+        margin-right: 12px;
+        border-radius: 4px;
+        overflow: hidden;
+      }
+
+      .prod-name {
+        flex: 1;
+        margin-right: 12px;
+        color: #303133;
+        font-size: 12px;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
+
+      .prod-type {
+        margin-right: 12px;
+      }
+
+      .prod-progress {
+        width: 160px;
+        
+        :deep(.el-progress-bar__outer) {
+          background-color: rgba(0,0,0,0.04);
         }
       }
     }
diff --git a/src/views/bst/order/edit/edit.vue b/src/views/bst/order/edit/edit.vue
index 92dbce7..cc81d38 100644
--- a/src/views/bst/order/edit/edit.vue
+++ b/src/views/bst/order/edit/edit.vue
@@ -1,8 +1,9 @@
 <template>
   <div v-loading="loading">
     <edit-header :title="title">
+      <el-button plain @click="cancel" icon="el-icon-close" size="small">取消</el-button>
       <el-button type="primary" plain @click="submitForm(false)" icon="el-icon-check" size="small">保存</el-button>
-      <el-button type="primary" @click="submitForm(true)" icon="el-icon-s-check" size="small" v-if="OrderStatus.canRelease().includes(form.status)">保存并发布</el-button>
+      <el-button type="primary" @click="submitForm(true)" icon="el-icon-s-check" size="small" v-if="form.id == null || OrderStatus.canRelease().includes(form.status)">保存并发布</el-button>
     </edit-header>
 
     <div class="app-container">
@@ -10,7 +11,7 @@
         <div class="edit-title">基础信息</div>
         <el-row>
           <form-col :span="span"  label="主图" prop="picture">
-            <image-upload v-model="form.picture" :limit="1"/>
+            <image-upload v-model="form.picture" :limit="1" />
           </form-col>
           <form-col :span="span"  label="订单编号" prop="orderNo">
             <el-input v-model="form.orderNo" placeholder="请输入订单编号"/>
@@ -160,6 +161,9 @@ export default {
         }
       });
     },
+    cancel() {
+      this.$tab.closeBack();
+    }
   }
 }
 </script>
diff --git a/src/views/bst/order/index.vue b/src/views/bst/order/index.vue
index 32e6f82..9fff0aa 100644
--- a/src/views/bst/order/index.vue
+++ b/src/views/bst/order/index.vue
@@ -308,11 +308,11 @@ export default {
     },
     /** 新增按钮操作 */
     handleAdd() {
-      this.$router.push("/edit/order");
+      this.$tab.closeOpenPage("/edit/order")
     },
     /** 修改按钮操作 */
     handleUpdate(row) {
-      this.$router.push(`/edit/order/${row.id}`)
+      this.$tab.closeOpenPage(`/edit/order/${row.id}`)
     },
     // 完工
     handleFinish(row) {
diff --git a/src/views/bst/prodProcess/components/ProcessCardList.vue b/src/views/bst/prodProcess/components/ProcessCardList.vue
index 456297b..9598278 100644
--- a/src/views/bst/prodProcess/components/ProcessCardList.vue
+++ b/src/views/bst/prodProcess/components/ProcessCardList.vue
@@ -1,5 +1,5 @@
 <template>
-  <div class="process-card-list">
+  <div class="process-card-list" v-if="!isEmpty(data)">
     <el-card
       v-for="(item, index) in data"
       :key="index"
@@ -50,10 +50,12 @@
       </div>
     </el-card>
   </div>
+  <el-empty v-else description="暂无数据"/>
 </template>
 
 <script>
 import { ProgressColors, ProgressFormat } from '@/utils/constants'
+import { isEmpty } from '@/utils/index'
 
 export default {
   name: 'ProcessCardList',
@@ -70,6 +72,7 @@ export default {
   },
   methods: {
     ProgressFormat,
+    isEmpty
   }
 }
 </script>
diff --git a/src/views/index.vue b/src/views/index.vue
index 955052c..4a53869 100644
--- a/src/views/index.vue
+++ b/src/views/index.vue
@@ -3,7 +3,10 @@
 
     <panel-group />
 
-    <div class="order-flow-title">进行中的订单</div>
+    <div class="group-title">
+      <i class="el-icon-s-order"></i>
+      进行中的订单
+    </div>
     <order-flow-list />
     
   </div>
@@ -30,4 +33,11 @@ export default {
 .dashboard-editor-container {
   padding: 32px;
 }
+.group-title {
+  background: linear-gradient(to right, #409EFF, #ffffff 50% );
+  color: #fff;
+  padding: 8px 16px;
+  border-radius: 4px;
+  margin-bottom: 16px;
+}
 </style>