|
|
@@ -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>
|