Selaa lähdekoodia

feat(groupPackage): 实现分组菜品收起展开功能- 添加每个分组的独立收起/展开状态控制
- 支持"收起全部"时仅显示第一个分组- 实现分组收起时只展示类别和第一行菜品
- 添加分组和菜品数量限制(最多20个分组,每组最多20个菜品)
- 引入计算属性visibleGroups优化分组显示逻辑
- 增加分组切换动画效果和样式调整- 完善分组删除时同步清理收起状态数组
- 更新UI布局使分组头部信息对齐更合理

congxuesong 1 kuukausi sitten
vanhempi
commit
bdc570987f
1 muutettua tiedostoa jossa 197 lisäystä ja 31 poistoa
  1. 197 31
      src/views/groupPackageManagement/newGroup.vue

+ 197 - 31
src/views/groupPackageManagement/newGroup.vue

@@ -90,14 +90,24 @@
                     {{ allGroupsCollapsed ? "展开全部" : "收起全部" }}
                   </el-button>
                 </div>
-                <div class="package-content-container">
-                  <div v-for="(group, groupIndex) in lifeGroupBuyThalis" :key="groupIndex" class="package-group">
+                <transition-group name="group-fade" tag="div" class="package-content-container">
+                  <div v-for="item in visibleGroups" :key="item.originalIndex" class="package-group">
                     <div class="package-group-header">
+                      <span class="group-index">{{ item.group.groupName }}</span>
                       <div class="header-right">
                         <el-button
+                          type="primary"
+                          link
+                          @click="toggleGroupCollapse(item.originalIndex)"
+                          :icon="isGroupCollapsed(item.originalIndex) ? ArrowDown : ArrowUp"
+                          v-show="item.group.dishes.length > 1"
+                        >
+                          {{ isGroupCollapsed(item.originalIndex) ? "展开" : "收起" }}
+                        </el-button>
+                        <el-button
                           type="danger"
                           link
-                          @click="removeGroup(groupIndex)"
+                          @click="removeGroup(item.originalIndex)"
                           :icon="Delete"
                           v-show="lifeGroupBuyThalis.length > 1"
                         >
@@ -105,35 +115,65 @@
                         </el-button>
                       </div>
                     </div>
-                    <transition name="slide-fade">
-                      <div v-show="!allGroupsCollapsed" class="package-group-content">
-                        <div class="category-row">
-                          <span class="label">类别</span>
-                          <el-input v-model="group.groupName" placeholder="请输入" clearable />
+                    <div class="package-group-content">
+                      <div class="category-row">
+                        <span class="label">类别</span>
+                        <el-input v-model="item.group.groupName" placeholder="请输入" clearable />
+                      </div>
+                      <!-- 第一行菜品,始终显示 -->
+                      <div v-if="item.group.dishes && item.group.dishes.length > 0" class="dish-row">
+                        <span class="label">菜品</span>
+                        <div class="dish-select-block" @click="openDishDialog(item.originalIndex, 0)">
+                          <span v-if="item.group.dishes[0].dishName" class="dish-selected-name"
+                            >{{ item.group.dishes[0].dishName }},¥{{ item.group.dishes[0].dishPrice }}/{{
+                              item.group.dishes[0].dishesUnit
+                            }}</span
+                          >
+                          <span v-else class="dish-placeholder">请选择</span>
                         </div>
-                        <div v-for="(dish, dishIndex) in group.dishes" :key="dishIndex" class="dish-row">
-                          <span class="label">菜品</span>
-                          <div class="dish-select-block" @click="openDishDialog(groupIndex, dishIndex)">
-                            <span v-if="dish.dishName" class="dish-selected-name"
-                              >{{ dish.dishName }},¥{{ dish.dishPrice }}/{{ dish.dishesUnit }}</span
-                            >
-                            <span v-else class="dish-placeholder">请选择</span>
+                        <span class="label">数量</span>
+                        <el-input v-model="item.group.dishes[0].qty" placeholder="请输入" clearable class="quantity-input" />
+                        <el-button
+                          :icon="Delete"
+                          link
+                          type="danger"
+                          @click="removeDish(item.originalIndex, 0)"
+                          class="delete-dish-btn"
+                          v-show="item.group.dishes.length > 1"
+                        />
+                      </div>
+                      <!-- 第二行及以后的菜品,收起时隐藏 -->
+                      <transition name="slide-fade">
+                        <div v-if="!isGroupCollapsed(item.originalIndex)" class="extra-dishes-container">
+                          <div
+                            v-for="(dish, dishIndex) in item.group.dishes"
+                            :key="dishIndex"
+                            class="dish-row"
+                            v-show="dishIndex > 0"
+                          >
+                            <span class="label">菜品</span>
+                            <div class="dish-select-block" @click="openDishDialog(item.originalIndex, dishIndex)">
+                              <span v-if="dish.dishName" class="dish-selected-name"
+                                >{{ dish.dishName }},¥{{ dish.dishPrice }}/{{ dish.dishesUnit }}</span
+                              >
+                              <span v-else class="dish-placeholder">请选择</span>
+                            </div>
+                            <span class="label">数量</span>
+                            <el-input v-model="dish.qty" placeholder="请输入" clearable class="quantity-input" />
+                            <el-button
+                              :icon="Delete"
+                              link
+                              type="danger"
+                              @click="removeDish(item.originalIndex, dishIndex)"
+                              class="delete-dish-btn"
+                            />
                           </div>
-                          <span class="label">数量</span>
-                          <el-input v-model="dish.qty" placeholder="请输入" clearable class="quantity-input" />
-                          <el-button
-                            :icon="Delete"
-                            link
-                            type="danger"
-                            @click="removeDish(groupIndex, dishIndex)"
-                            class="delete-dish-btn"
-                          />
+                          <el-button type="primary" @click="addDish(item.originalIndex)" class="add-dish-btn"> 添加 </el-button>
                         </div>
-                        <el-button type="primary" @click="addDish(groupIndex)" class="add-dish-btn"> 添加 </el-button>
-                      </div>
-                    </transition>
+                      </transition>
+                    </div>
                   </div>
-                </div>
+                </transition-group>
               </div>
             </el-form-item>
           </div>
@@ -365,7 +405,7 @@
  * 团购包管理 - 新增/编辑页面
  * 功能:支持团购包的新增和编辑操作
  */
-import { ref, reactive, onMounted, watch, nextTick } from "vue";
+import { ref, reactive, onMounted, watch, nextTick, computed } from "vue";
 import { ElMessage, ElMessageBox } from "element-plus";
 import { Plus, Delete, ArrowDown, ArrowUp, Picture, Check } from "@element-plus/icons-vue";
 import {
@@ -881,6 +921,16 @@ const currentDishIndex = ref<number>(-1);
 
 // 套餐内容整体展开收起状态
 const allGroupsCollapsed = ref(false);
+// 每个分组的收起状态(数组,索引对应分组索引)
+const groupCollapsedStates = ref<boolean[]>([false]); // 默认第一个分组展开
+
+// 计算属性:过滤要显示的分组(收起全部时只显示第一个分组),保留原始索引
+const visibleGroups = computed(() => {
+  if (allGroupsCollapsed.value) {
+    return lifeGroupBuyThalis.value.map((group, index) => ({ group, originalIndex: index })).filter((_, index) => index === 0);
+  }
+  return lifeGroupBuyThalis.value.map((group, index) => ({ group, originalIndex: index }));
+});
 
 // 标记是否跳过最后一个分组的验证(用于添加新分组时)
 let skipLastGroupValidation = false;
@@ -1001,6 +1051,8 @@ onMounted(async () => {
           ];
         }
       });
+      // 初始化所有分组的收起状态为展开
+      groupCollapsedStates.value = new Array(lifeGroupBuyThalis.value.length).fill(false);
     } else {
       // 如果没有数据,使用默认值
       lifeGroupBuyThalis.value = [
@@ -1018,6 +1070,8 @@ onMounted(async () => {
           ]
         }
       ];
+      // 初始化默认分组的收起状态为展开
+      groupCollapsedStates.value = [false];
     }
     // 确保自定义不可用日期字段存在
     if (storeInfoModel.value.unavailableDates === 2) {
@@ -1161,6 +1215,12 @@ const addGroup = () => {
     lifeGroupBuyThalis.value = [];
   }
 
+  // 检查分组数量限制(最多20个)
+  if (lifeGroupBuyThalis.value.length >= 20) {
+    ElMessage.warning("最多只能添加20个分组");
+    return;
+  }
+
   // 记录旧分组的数量,用于后续只验证旧分组
   const oldGroupsCount = lifeGroupBuyThalis.value.length;
 
@@ -1179,6 +1239,9 @@ const addGroup = () => {
     ]
   });
 
+  // 初始化新分组的收起状态为展开
+  groupCollapsedStates.value.push(false);
+
   // 清除表单验证状态,避免新分组显示验证错误
   // 只清除验证状态,不重新触发验证
   // 让用户操作时(如点击添加菜品)再触发验证
@@ -1204,16 +1267,70 @@ const removeGroup = (groupIndex: number) => {
   })
     .then(() => {
       lifeGroupBuyThalis.value.splice(groupIndex, 1);
+      // 同步删除收起状态数组中的对应项
+      if (groupCollapsedStates.value.length > groupIndex) {
+        groupCollapsedStates.value.splice(groupIndex, 1);
+      }
       ElMessage.success("删除成功");
     })
     .catch(() => {});
 };
 
 /**
+ * 判断分组是否收起
+ * @param groupIndex 分组索引
+ * @returns 是否收起(收起时只显示类别和第一行菜品,隐藏其他菜品行)
+ */
+const isGroupCollapsed = (groupIndex: number): boolean => {
+  // 如果"收起全部"状态为true,第一个分组应该展开(显示所有菜品),其他分组收起
+  if (allGroupsCollapsed.value) {
+    return groupIndex !== 0;
+  }
+  // 否则根据分组的收起状态判断
+  return groupCollapsedStates.value[groupIndex] === true;
+};
+
+/**
+ * 切换单个分组的收起/展开状态
+ * @param groupIndex 分组索引
+ */
+const toggleGroupCollapse = (groupIndex: number) => {
+  // 如果当前是"收起全部"状态,先取消该状态
+  if (allGroupsCollapsed.value) {
+    allGroupsCollapsed.value = false;
+    // 设置所有分组状态:第一个分组当前是展开的,其他分组当前是收起的
+    // 所以第一个分组应该设置为收起,其他分组应该设置为展开
+    groupCollapsedStates.value = new Array(lifeGroupBuyThalis.value.length).fill(true);
+    groupCollapsedStates.value[0] = false; // 第一个分组原本是展开的
+    // 然后切换当前分组的状态
+    groupCollapsedStates.value[groupIndex] = !groupCollapsedStates.value[groupIndex];
+  } else {
+    // 确保数组长度足够
+    while (groupCollapsedStates.value.length <= groupIndex) {
+      groupCollapsedStates.value.push(false);
+    }
+    groupCollapsedStates.value[groupIndex] = !groupCollapsedStates.value[groupIndex];
+  }
+};
+
+/**
  * 切换所有分组的收起/展开状态
+ * 收起全部时保留第一个分组展开
  */
 const toggleAllGroupsCollapse = () => {
-  allGroupsCollapsed.value = !allGroupsCollapsed.value;
+  if (allGroupsCollapsed.value) {
+    // 展开全部:将所有分组设置为展开状态
+    allGroupsCollapsed.value = false;
+    groupCollapsedStates.value = groupCollapsedStates.value.map(() => false);
+  } else {
+    // 收起全部:收起除第一个分组外的所有分组
+    allGroupsCollapsed.value = true;
+    groupCollapsedStates.value = groupCollapsedStates.value.map((_, index) => index !== 0);
+    // 确保数组长度足够
+    while (groupCollapsedStates.value.length < lifeGroupBuyThalis.value.length) {
+      groupCollapsedStates.value.push(true);
+    }
+  }
 };
 
 /**
@@ -1223,6 +1340,12 @@ const toggleAllGroupsCollapse = () => {
 const addDish = (groupIndex: number) => {
   const group = lifeGroupBuyThalis.value[groupIndex];
 
+  // 检查菜品数量限制(每个分组最多20个菜品)
+  if (group.dishes && group.dishes.length >= 20) {
+    ElMessage.warning("每个分组最多只能添加20个菜品");
+    return;
+  }
+
   // 验证当前分组是否填写完整
   // 验证类别
   if (!group.groupName || group.groupName.trim() === "") {
@@ -1749,7 +1872,8 @@ const handleImageParam = (list: any[], result: any[]) => {
 .package-group-header {
   display: flex;
   align-items: center;
-  justify-content: flex-end;
+  justify-content: space-between;
+  height: 50px;
   padding: 12px 16px;
   background-color: #f5f7fa;
   border-bottom: 1px solid #e4e7ed;
@@ -1765,6 +1889,29 @@ const handleImageParam = (list: any[], result: any[]) => {
   gap: 16px;
   padding: 16px;
 }
+.extra-dishes-container {
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+}
+.package-group-collapsed {
+  padding: 16px;
+  background-color: #fafafa;
+  border-top: 1px solid #e4e7ed;
+}
+.collapsed-dish-row {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+}
+.dish-info-text {
+  font-size: 14px;
+  color: #606266;
+  white-space: nowrap;
+}
+.dish-info-text.empty-text {
+  color: #c0c4cc;
+}
 .category-row {
   display: flex;
   gap: 12px;
@@ -1957,4 +2104,23 @@ const handleImageParam = (list: any[], result: any[]) => {
   max-height: 2000px;
   opacity: 1;
 }
+
+/* 分组收起全部动画 */
+.group-fade-enter-active {
+  transition: all 0.3s ease-out;
+}
+.group-fade-leave-active {
+  transition: all 0.3s ease-in;
+}
+.group-fade-enter-from {
+  opacity: 0;
+  transform: translateY(-10px);
+}
+.group-fade-leave-to {
+  opacity: 0;
+  transform: translateY(-10px);
+}
+.group-fade-move {
+  transition: transform 0.3s ease;
+}
 </style>