shish 12 часов назад
Родитель
Сommit
997a0b3768
2 измененных файлов с 348 добавлено и 221 удалено
  1. 347 219
      hdApp/src/admin/goods/categorySort.vue
  2. 1 2
      hdApp/src/admin/home/apply.vue

+ 347 - 219
hdApp/src/admin/goods/categorySort.vue

@@ -1,10 +1,40 @@
+<!--
+  商品排序入口页
+  用途:花束分类排序 + 花材分类排序合页;顶部 Tab 对齐商品列表 manage.vue
+  谁用:应用页「排序」、mall-home-panel「排序」
+-->
 <template>
   <view class="app-content">
-    <view class="list-wrap">
+    <!-- 花束 / 花材切换,样式对齐商品列表 -->
+    <view class="modern-tabs-container">
+      <view class="modern-tabs">
+        <view
+          class="modern-tab"
+          :class="{ active: activeType === 'goods' }"
+          @click="switchType('goods')"
+        >
+          <view class="tab-content">
+            <text class="tab-text">花束</text>
+          </view>
+        </view>
+        <view
+          class="modern-tab"
+          :class="{ active: activeType === 'item' }"
+          @click="switchType('item')"
+        >
+          <view class="tab-content">
+            <text class="tab-text">花材</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 花束:按分类进入商品排序 -->
+    <view v-if="activeType === 'goods'" class="list-wrap">
       <block v-if="!$util.isEmpty(list.data)">
         <block v-for="(item, index) in list.data" :key="item.id">
           <view
-            class="list"
+            class="list goods-list-row"
             :class="{ dragging: draggedIndex === index }"
             :style="{
               transform: transformStyle(index),
@@ -17,35 +47,10 @@
               <image :src="item.img" mode="aspectFit"></image>
             </view>
             <view class="list-det">
-              <view class="app-size-28 app-color-0" style="font-size: 30rpx; font-weight: bold">{{ item.categoryName }}</view>
+              <view class="app-size-28 app-color-0 category-name">{{ item.categoryName }}</view>
             </view>
-
-            <!--<text v-if="index > 0" style="right:335rpx;" class="move-btn" @click.stop="topFn(item.id)">↑</text>
-						<text v-if="index > 0" style="right:350rpx;line-height: 10rpx;" class="move-btn-line">-</text>
-
-						<text v-if="index < list.data.length - 1" style="right:282rpx;" class="move-btn" @click.stop="bottomFn(item.id)">↓</text>
-						<text v-if="index < list.data.length - 1" style="right:282rpx;line-height: 68rpx;" class="move-btn-line">-</text>
-
-						<text style="right:200rpx;" class="move-btn" @click.stop="upFn(item.id)">↑</text>
-						<text style="right:125rpx;" class="move-btn" @click.stop="downFn(item.id)">↓</text>
-						-->
-
-            <!-- 这个页面不显示,影响用户使用,这个做到分类页去
-            <view
-              style="position: absolute; top: 50rpx; right: 188rpx; cursor: move"
-              class="drag-handle"
-              :data-index="index"
-              :data-id="item.id"
-              @touchstart="dragStart"
-              @touchmove="dragMove"
-              @touchend="dragEnd"
-            >
-              <text style="color: #333333; font-size: 45rpx">≡</text>
-            </view>
-            -->
-
             <view class="confirm-btn">
-              <button class="admin-button-com middle blue" style="position:absolute;top:50rpx;right: 10rpx">开始排序</button>
+              <button class="admin-button-com middle blue sort-enter-btn">开始排序</button>
             </view>
           </view>
         </block>
@@ -54,267 +59,412 @@
         <app-wrapper-empty title="暂无数据" :is-empty="$util.isEmpty(list.data)" />
       </block>
     </view>
+
+    <!-- 花材:按分类拖拽/数字排序(原 itemClass/sort) -->
+    <view v-else class="list-wrap item-list-wrap">
+      <block v-if="!$util.isEmpty(itemList.data)">
+        <view class="list item-list-row" v-for="(item, index) in itemList.data" :key="index">
+          <view class="list-index">{{ item.id }}</view>
+          <view class="list-item">
+            <view class="item-class-name">{{ item.name || '' }}</view>
+          </view>
+          <button
+            class="admin-button-com blue middle"
+            @click.stop="pageTo('/admin/itemClass/pull?classId=' + item.id)"
+          >拖拽排序</button>
+          <button
+            class="admin-button-com blue middle"
+            @click.stop="pageTo('/admin/itemClass/num?classId=' + item.id)"
+          >数字排序</button>
+        </view>
+      </block>
+      <block v-else>
+        <app-wrapper-empty title="暂无数据" :is-empty="$util.isEmpty(itemList.data)" />
+      </block>
+    </view>
   </view>
 </template>
 <script>
-import ModalModule from '@/components/plugin/modal';
-import AppUploader from '@/components/app-uploader';
-import OperateModule from '@/admin/home/components/module/operate';
-import AppWrapperEmpty from '@/components/app-wrapper-empty';
-import { list } from '@/mixins';
-import { categoryListInfo } from '@/api/goods';
-import { sort } from '@/api/category';
+import AppWrapperEmpty from '@/components/app-wrapper-empty'
+import { list } from '@/mixins'
+import { categoryListInfo } from '@/api/goods'
+import { sort } from '@/api/category'
+import { getList } from '@/api/item-class'
+
 export default {
-  name: 'category',
+  name: 'categorySort',
   components: {
-    ModalModule,
-    AppUploader,
-    OperateModule,
     AppWrapperEmpty
   },
   mixins: [list],
   data() {
     return {
+      /** goods=花束分类排序,item=花材分类排序 */
+      activeType: 'goods',
+      /** 花材分类是否已拉过,避免重复请求 */
+      itemLoaded: false,
+      itemList: {
+        data: [],
+        page: 1,
+        pageSize: 20,
+        loading: true,
+        finished: false
+      },
       draggedIndex: -1,
       draggedItem: null,
       dragOffsetY: 0,
       isDragging: false,
-      itemHeight: 160, // 列表项高度,单位rpx
+      itemHeight: 160,
       startY: 0
-    };
+    }
+  },
+  onLoad(options) {
+    // 支持 ?type=item 深链直接进花材,对齐商品列表
+    if (options && options.type === 'item') {
+      this.activeType = 'item'
+    }
   },
   onPullDownRefresh() {
-    this.resetList();
-    this.getCategoryList().then(() => {
-      uni.stopPullDownRefresh();
-    });
+    if (this.activeType === 'goods') {
+      this.resetList()
+      this.getCategoryList().then(() => {
+        uni.stopPullDownRefresh()
+      })
+      return
+    }
+    this.resetItemList()
+    this.getItemClassList().then(() => {
+      uni.stopPullDownRefresh()
+    })
   },
   onReachBottom() {
-    if (!this.list.finished) {
-      this.getCategoryList().then(() => {
-        uni.stopPullDownRefresh();
-      });
+    if (this.activeType === 'goods') {
+      if (!this.list.finished) {
+        this.getCategoryList().then(() => {
+          uni.stopPullDownRefresh()
+        })
+      } else {
+        uni.stopPullDownRefresh()
+      }
+      return
+    }
+    if (!this.itemList.finished) {
+      this.getItemClassList().then(() => {
+        uni.stopPullDownRefresh()
+      })
     } else {
-      uni.stopPullDownRefresh();
+      uni.stopPullDownRefresh()
     }
   },
   methods: {
-    // 跳转到商品排序页面
+    /**
+     * 切换花束 / 花材;花材首次进入时懒加载分类列表
+     * @param {string} type goods | item
+     */
+    switchType(type) {
+      if (type === this.activeType) return
+      this.activeType = type
+      if (type === 'item' && !this.itemLoaded) {
+        this.getItemClassList()
+      }
+    },
+    /** 进入某花束分类下的商品排序页 */
     navigateToGoodsSort(item) {
-      uni.navigateTo({ url: '/admin/goods/goodsSort?id=' + item.id+"&name="+item.categoryName })
+      uni.navigateTo({
+        url: '/admin/goods/goodsSort?id=' + item.id + '&name=' + item.categoryName
+      })
     },
     async init() {
-      this.getCategoryList();
+      if (this.activeType === 'item') {
+        this.getItemClassList()
+        return
+      }
+      this.getCategoryList()
     },
     getCategoryList() {
       return categoryListInfo().then((res) => {
-        let data = { code: res.code, msg: res.msg, data: { list: res.data } };
-        this.completes(data);
-      });
+        const data = { code: res.code, msg: res.msg, data: { list: res.data } }
+        this.completes(data)
+      })
+    },
+    /** 重置花材分类分页状态 */
+    resetItemList() {
+      this.itemList = {
+        data: [],
+        page: 1,
+        pageSize: 20,
+        loading: true,
+        finished: false
+      }
+      this.itemLoaded = false
+    },
+    /**
+     * 拉取花材分类列表(原 itemClass/sort)
+     * 副作用:写入 itemList,标记 itemLoaded
+     */
+    getItemClassList() {
+      return getList({ page: this.itemList.page }).then((res) => {
+        this.itemList.loading = false
+        this.itemLoaded = true
+        if (res.code !== 1) {
+          this.itemList.finished = true
+          return
+        }
+        const listArr = res.data.list || res.data.data || []
+        const arr = Array.isArray(listArr) ? listArr : []
+        this.itemList.data =
+          this.itemList.page === 1 ? arr : this.itemList.data.concat(arr)
+        if (!res.data.moreData) {
+          this.itemList.finished = true
+        } else {
+          this.itemList.page++
+        }
+      })
     },
-    // 拖拽相关方法
     transformStyle(index) {
       if (index === this.draggedIndex) {
-        return `translateY(${this.dragOffsetY}rpx)`;
+        return `translateY(${this.dragOffsetY}rpx)`
       }
-      return 'translateY(0rpx)';
+      return 'translateY(0rpx)'
     },
-
     dragStart(e) {
-      const index = parseInt(e.currentTarget.dataset.index);
-      this.draggedIndex = index;
-      this.draggedItem = this.list.data[index];
-      this.startY = e.touches[0].clientY;
-      this.isDragging = true;
+      const index = parseInt(e.currentTarget.dataset.index)
+      this.draggedIndex = index
+      this.draggedItem = this.list.data[index]
+      this.startY = e.touches[0].clientY
+      this.isDragging = true
     },
-
     dragMove(e) {
-      if (!this.isDragging || this.draggedIndex === -1) return;
-
-      const currentY = e.touches[0].clientY;
-      const deltaY = currentY - this.startY;
-
-      const systemInfo = uni.getSystemInfoSync();
-      const pixelRatio = 750 / systemInfo.windowWidth;
-      // 根据设备像素比例转换为rpx单位
-      const deltaYrpx = deltaY * pixelRatio;
-
-      this.dragOffsetY = deltaYrpx;
-
-      // 计算目标位置
-      const itemHeightrpx = this.itemHeight * pixelRatio;
-
-      // 使用更精确的计算方法确定移动的项目数
-      const moveDistance = Math.abs(deltaYrpx);
-      const moveDirection = deltaYrpx > 0 ? 1 : -1;
-      const moveItems = Math.floor(moveDistance / (itemHeightrpx * 0.5)) * moveDirection;
-
-      // 确保目标索引在有效范围内
-      const targetIndex = Math.max(0, Math.min(this.list.data.length - 1, this.draggedIndex + moveItems));
-
+      if (!this.isDragging || this.draggedIndex === -1) return
+      const currentY = e.touches[0].clientY
+      const deltaY = currentY - this.startY
+      const systemInfo = uni.getSystemInfoSync()
+      const pixelRatio = 750 / systemInfo.windowWidth
+      const deltaYrpx = deltaY * pixelRatio
+      this.dragOffsetY = deltaYrpx
+      const itemHeightrpx = this.itemHeight * pixelRatio
+      const moveDistance = Math.abs(deltaYrpx)
+      const moveDirection = deltaYrpx > 0 ? 1 : -1
+      const moveItems = Math.floor(moveDistance / (itemHeightrpx * 0.5)) * moveDirection
+      const targetIndex = Math.max(
+        0,
+        Math.min(this.list.data.length - 1, this.draggedIndex + moveItems)
+      )
       if (targetIndex !== this.draggedIndex) {
-        // 交换元素位置
-        const newData = [...this.list.data];
-        const draggedItem = newData[this.draggedIndex];
-        newData.splice(this.draggedIndex, 1);
-        newData.splice(targetIndex, 0, draggedItem);
-
-        // 更新数据和拖拽状态
-        this.list.data = newData;
-        this.draggedIndex = targetIndex;
-        this.startY = currentY;
-        this.dragOffsetY = 0;
+        const newData = [...this.list.data]
+        const draggedItem = newData[this.draggedIndex]
+        newData.splice(this.draggedIndex, 1)
+        newData.splice(targetIndex, 0, draggedItem)
+        this.list.data = newData
+        this.draggedIndex = targetIndex
+        this.startY = currentY
+        this.dragOffsetY = 0
       }
     },
-
     dragEnd() {
-      if (!this.isDragging) return;
-
-      // 重置拖拽状态
-      this.draggedIndex = -1;
-      this.draggedItem = null;
-      this.dragOffsetY = 0;
-      this.isDragging = false;
-      this.startY = 0;
-
-      // 更新排序值并发送到后端
-      this.updateSortOrder();
+      if (!this.isDragging) return
+      this.draggedIndex = -1
+      this.draggedItem = null
+      this.dragOffsetY = 0
+      this.isDragging = false
+      this.startY = 0
+      this.updateSortOrder()
     },
-
     updateSortOrder() {
-      // 更新每个项目的inTurn值,从大到小排序
       const sortData = this.list.data.map((item, index) => ({
         id: item.id,
-        inTurn: this.list.data.length - index // 反向索引,使第一个项目有最大值
-        // 如果是分页加载的,请用 data.total 替换掉 this.list.data.length
-      }));
-
-      // 调用API更新排序
+        inTurn: this.list.data.length - index
+      }))
       sort({ items: sortData })
         .then((res) => {
           if (res.code === 1) {
-            this.$msg('已更新');
+            this.$msg('已更新')
           } else {
-            this.$msg('排序更新失败');
+            this.$msg('排序更新失败')
           }
         })
         .catch((err) => {
-          console.error('更新排序失败:', err);
-          this.$msg('排序更新失败');
-        });
+          console.error('更新排序失败:', err)
+          this.$msg('排序更新失败')
+        })
     },
-
-    // 上移
     upFn(id) {
-      const index = this.list.data.findIndex((item) => item.id === id);
+      const index = this.list.data.findIndex((item) => item.id === id)
       if (index > 0) {
-        const newData = [...this.list.data];
-        const temp = newData[index];
-        newData[index] = newData[index - 1];
-        newData[index - 1] = temp;
-        this.list.data = newData;
-        this.updateSortOrder();
+        const newData = [...this.list.data]
+        const temp = newData[index]
+        newData[index] = newData[index - 1]
+        newData[index - 1] = temp
+        this.list.data = newData
+        this.updateSortOrder()
       }
     },
-
-    // 下移
     downFn(id) {
-      const index = this.list.data.findIndex((item) => item.id === id);
+      const index = this.list.data.findIndex((item) => item.id === id)
       if (index < this.list.data.length - 1) {
-        const newData = [...this.list.data];
-        const temp = newData[index];
-        newData[index] = newData[index + 1];
-        newData[index + 1] = temp;
-        this.list.data = newData;
-        this.updateSortOrder();
+        const newData = [...this.list.data]
+        const temp = newData[index]
+        newData[index] = newData[index + 1]
+        newData[index + 1] = temp
+        this.list.data = newData
+        this.updateSortOrder()
       }
     },
-
-    // 置顶
     topFn(id) {
-      const index = this.list.data.findIndex((item) => item.id === id);
+      const index = this.list.data.findIndex((item) => item.id === id)
       if (index > 0) {
-        const newData = [...this.list.data];
-        const targetItem = newData[index];
-
-        // 将目标项移到第一位
-        newData.splice(index, 1);
-        newData.unshift(targetItem);
-
-        this.list.data = newData;
-        this.updateSortOrder();
+        const newData = [...this.list.data]
+        const targetItem = newData[index]
+        newData.splice(index, 1)
+        newData.unshift(targetItem)
+        this.list.data = newData
+        this.updateSortOrder()
       }
     },
-
-    // 置底
     bottomFn(id) {
-      const index = this.list.data.findIndex((item) => item.id === id);
+      const index = this.list.data.findIndex((item) => item.id === id)
       if (index < this.list.data.length - 1) {
-        const newData = [...this.list.data];
-        const targetItem = newData[index];
-
-        // 将目标项移到最后一位
-        newData.splice(index, 1);
-        newData.push(targetItem);
-
-        this.list.data = newData;
-        this.updateSortOrder();
+        const newData = [...this.list.data]
+        const targetItem = newData[index]
+        newData.splice(index, 1)
+        newData.push(targetItem)
+        this.list.data = newData
+        this.updateSortOrder()
       }
     }
   }
-};
+}
 </script>
 <style lang="scss" scoped>
 .app-content {
   min-height: calc(100vh - 100rpx);
   padding-bottom: 100rpx;
 }
+
+/* 顶部花束/花材切换:对齐 admin/goods/manage.vue */
+.modern-tabs-container {
+  padding: 0;
+  background-color: #f8f9fa;
+  width: 100%;
+  flex-shrink: 0;
+}
+
+.modern-tabs {
+  display: flex;
+  background-color: #ffffff;
+  padding: 6upx;
+  box-shadow: 0 2upx 12upx rgba(0, 0, 0, 0.06);
+  border: 2upx solid #f0f2f5;
+  position: relative;
+  z-index: 5;
+  width: 100%;
+}
+
+.modern-tab {
+  flex: 1;
+  text-align: center;
+  padding: 30upx 0;
+  position: relative;
+  transition: all 0.35s cubic-bezier(0.25, 1, 0.5, 1);
+  border-radius: 12upx;
+  margin: 0 2upx;
+  background-color: #e2dfdf;
+  border: 2upx solid #f2f4f7;
+  box-shadow: none;
+}
+
+.modern-tab .tab-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.modern-tab .tab-text {
+  font-size: 30upx;
+  color: #666;
+  font-weight: 700;
+}
+
+.modern-tab.active {
+  background: #09c567;
+}
+
+.modern-tab.active .tab-text {
+  color: #ffffff;
+  font-weight: 700;
+  font-size: 30upx;
+}
+
 .list-wrap {
   position: relative;
   background-color: #fff;
+
   .list {
-    height: 160rpx;
     @include disFlex(center, flex-start);
-    padding: 30rpx 0;
     margin: 0 30rpx;
     color: $fontColor3;
     border-bottom: 1px solid $borderColor;
     position: relative;
-    .list-img {
+  }
+
+  .goods-list-row {
+    height: 160rpx;
+    padding: 30rpx 0;
+  }
+
+  .item-list-row {
+    height: 130upx;
+  }
+
+  .list-img {
+    width: 130rpx;
+    height: 130rpx;
+    image {
       width: 130rpx;
       height: 130rpx;
-      image {
-        width: 130rpx;
-        height: 130rpx;
-        border-radius: 20rpx;
-      }
-    }
-    .list-det {
-      margin-left: 20rpx;
-      & > div {
-        margin-top: 14rpx;
-        &:first-child {
-          margin-top: 0;
-        }
-      }
+      border-radius: 20rpx;
     }
   }
+
+  .list-det {
+    margin-left: 20rpx;
+  }
+
+  .category-name {
+    font-size: 30rpx;
+    font-weight: bold;
+  }
+
+  .list-index {
+    width: 80upx;
+    flex-shrink: 0;
+  }
+
+  .list-item {
+    flex: 1;
+    margin-left: 10upx;
+    position: relative;
+  }
+
+  .item-class-name {
+    font-size: 30upx;
+    font-weight: bold;
+    color: #333333;
+  }
+
+  .item-list-row button {
+    flex-shrink: 0;
+    margin-left: 20upx;
+  }
 }
-.move-btn {
-  position: absolute;
-  top: 65rpx;
-  color: blue;
-  border: 1px solid #cccccc;
-  padding: 5rpx 10rpx;
-  border-radius: 50%;
-}
-.move-btn-line {
+
+.sort-enter-btn {
   position: absolute;
-  top: 65rpx;
-  color: blue;
+  top: 50rpx;
+  right: 10rpx;
 }
 
-// 拖拽相关样式
 .list.dragging {
   border: 2px dashed #007aff !important;
   box-shadow: 0 4px 20px rgba(0, 122, 255, 0.3) !important;
@@ -323,26 +473,4 @@ export default {
   border-radius: 12rpx;
   transform: scale(1.05);
 }
-
-.drag-handle {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  width: 70rpx;
-  height: 70rpx;
-  border-radius: 12rpx;
-  background-color: rgba(0, 0, 0, 0.03);
-  transition: all 0.2s ease;
-  -webkit-touch-callout: none; /* 禁用长按菜单 */
-  -webkit-user-select: none; /* 禁用文本选择 */
-  user-select: none;
-  touch-action: none; /* 禁用浏览器默认触摸行为 */
-
-  /* 增加触摸反馈 */
-  &:active {
-    transform: scale(1.1);
-    background-color: rgba(0, 122, 255, 0.1);
-    box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.2);
-  }
-}
 </style>

+ 1 - 2
hdApp/src/admin/home/apply.vue

@@ -72,8 +72,7 @@ export default {
             { name: '分类', icon: 'category-filled', url: '/admin/goods/categoryV2?tab=0', pf: 1 },
             { name: '排序', icon: 'sort-filled', url: '/admin/goods/categorySort', pf: 1 },
             { name: '场景管理', icon: 'category-filled', url: '/admin/useCase/list', pf: 1 },
-            { name: '涨价管理', icon: 'price-increase', url: '/admin/goods/price-increase', pf: 1 },
-            { name: '花材排序', icon: 'sort-filled', url: '/admin/itemClass/sort', pf: 1 }
+            { name: '涨价管理', icon: 'price-increase', url: '/admin/goods/price-increase', pf: 1 }
           ]
         },
         {