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>