quanwei
3 days ago 04102f7237efefa744090ed7c25f7b5d0807b679
shop_vue/src/views/page/page/diy/model/GroupBuy.vue
@@ -6,7 +6,96 @@
    -->
  <div @click.stop="$parent.$parent.onEditer(index)" class="drag optional" :class="{selected: index === selectedIndex}">
    <div class="supplier" :style="{background: item.style.background}">
      <div class="supplier-time" v-for="(supplier, index) in item.data" :key="index">
      <!-- 筛选栏 -->
      <div class="supplier-set" v-if="item.params.is_sort||(item.params.category==0&&item.params.is_category)||item.params.is_filter" >
        <div class="filter-item" v-if="item.params.is_sort" >
          <span class="filter-text">{{ sortOptions[currentSort] ? sortOptions[currentSort].text : '智能排序' }}</span>
          <span class="filter-icon" :class="{ 'rotate': showSortDropdown }">▼</span>
        </div>
        <div class="filter-item" v-if="item.params.category==0&&item.params.is_category" >
          <span class="filter-text">{{ categoryOptions[currentCategory] ? categoryOptions[currentCategory].name : '分类' }}</span>
          <span class="filter-icon" :class="{ 'rotate': showCategoryDropdown }">▼</span>
        </div>
        <div class="filter-item" v-if="item.params.is_filter" >
          <span class="filter-text">筛选</span>
          <span class="filter-badge" v-if="hasActiveFilters">●</span>
        </div>
      </div>
      <!-- 排序下拉面板 -->
      <div class="sort-dropdown" v-if="showSortDropdown" @click.stop>
        <div class="sort-option" v-for="(option, idx) in sortOptions" :key="idx" @click.stop="selectSort(idx)">
          <span class="sort-option-text" :class="{ 'active': currentSort === idx }">{{ option.text }}</span>
          <span class="sort-option-check" v-if="currentSort === idx">✓</span>
        </div>
      </div>
      <!-- 分类下拉面板 -->
      <div class="category-dropdown" v-if="showCategoryDropdown" @click.stop>
        <div class="sort-option" v-for="(option, idx) in categoryOptions" :key="idx" @click.stop="selectCategory(idx)">
          <span class="sort-option-text" :class="{ 'active': currentCategory === idx }">{{ option.name }}</span>
          <span class="sort-option-check" v-if="currentCategory === idx">✓</span>
        </div>
      </div>
      <!-- 遮罩层 -->
      <div class="mask" v-if="showSortDropdown || showCategoryDropdown || showFilterPanel" @click.stop="closeAllDropdowns"></div>
      <!-- 筛选面板 -->
      <div class="filter-panel" v-if="showFilterPanel" @click.stop>
        <div class="filter-panel-header">
          <span class="filter-panel-title">筛选</span>
          <span class="filter-reset" @click="resetFilters">重置</span>
        </div>
        <div class="filter-section">
          <div class="filter-section-title">距离范围</div>
          <div class="filter-tags">
            <div class="filter-tag" v-for="(distItem, idx) in distanceOptions" :key="idx"
              :class="{ 'active': selectedDistance === idx }" @click.stop="selectDistance(idx)">
              {{ distItem.label }}
            </div>
          </div>
        </div>
        <div class="filter-section">
          <div class="filter-section-title">人均价格</div>
          <div class="filter-tags">
            <div class="filter-tag" v-for="(priceItem, idx) in priceOptions" :key="idx"
              :class="{ 'active': selectedPrice === idx }" @click.stop="selectPrice(idx)">
              {{ priceItem.label }}
            </div>
          </div>
        </div>
        <div class="filter-section">
          <div class="filter-section-title">服务评分</div>
          <div class="filter-tags">
            <div class="filter-tag" v-for="(scoreItem, idx) in scoreOptions" :key="idx"
              :class="{ 'active': selectedScore === idx }" @click.stop="selectScore(idx)">
              {{ scoreItem.label }}
            </div>
          </div>
        </div>
        <div class="filter-section">
          <div class="filter-section-title">优惠类型</div>
          <div class="filter-tags">
            <div class="filter-tag" :class="{ 'active': showCouponOnly }" @click.stop="toggleCouponOnly">
              仅看有券
            </div>
            <div class="filter-tag" :class="{ 'active': showTopRanked }" @click.stop="toggleTopRanked">
              好评榜前10
            </div>
          </div>
        </div>
        <div class="filter-panel-footer">
          <div class="filter-confirm-btn" @click.stop="applyFilters">确定</div>
        </div>
      </div>
      <div class="supplier-time" v-for="(supplier, idx) in filteredData" :key="idx">
        <div class="supplier-data">
          <div class="supplier-name">
            商户名称
@@ -51,6 +140,12 @@
          </div>
        </div>
      </div>
      <!-- 空状态 -->
      <div class="empty-state" v-if="filteredData.length === 0">
        <span class="empty-text">暂无符合条件的商户</span>
      </div>
      <div class="btn-edit-del">
        <div class="btn-del" @click.stop="$parent.$parent.onDeleleItem(index)">删除</div>
      </div>
@@ -61,13 +156,180 @@
<script>
export default {
  data() {
    return {};
    return {
      showSortDropdown: false,
      showCategoryDropdown: false,
      showFilterPanel: false,
      currentSort: 0,
      currentCategory: 0,
      selectedDistance: -1,
      selectedPrice: -1,
      selectedScore: -1,
      showCouponOnly: false,
      showTopRanked: false,
      sortOptions: [],
      categoryOptions: [],
      distanceOptions: [],
      priceOptions: [],
      scoreOptions: []
    };
  },
  created() {
    console.log(this.item)
  },
  props: ['item', 'index', 'selectedIndex'],
  methods: {}
  computed: {
    filteredData() {
      let data = [...(this.item.data || [])];
      // 分类筛选
      if (this.categoryOptions[this.currentCategory] && this.categoryOptions[this.currentCategory].category_id > 0) {
        const categoryId = this.categoryOptions[this.currentCategory].category_id;
        data = data.filter(supplier => supplier.category_id === categoryId);
      }
      // 筛选条件
      data = data.filter(supplier => {
        // 距离筛选
        if (this.selectedDistance >= 0) {
          const distance = this.parseDistance(supplier.distance);
          if (distance > this.distanceOptions[this.selectedDistance].value) {
            return false;
          }
        }
        // 价格筛选
        if (this.selectedPrice >= 0) {
          const price = supplier.average_price || 0;
          const range = this.priceOptions[this.selectedPrice].value;
          if (range.max === -1) {
            if (price < range.min) return false;
          } else {
            if (price < range.min || price >= range.max) return false;
          }
        }
        // 评分筛选
        if (this.selectedScore >= 0) {
          const score = parseFloat(supplier.server_score) || 0;
          if (score < this.scoreOptions[this.selectedScore].value) {
            return false;
          }
        }
        // 仅看有券
        if (this.showCouponOnly && (!supplier.max_reduce_price || supplier.max_reduce_price <= 0)) {
          return false;
        }
        // 好评榜前10
        if (this.showTopRanked && (!supplier.ranking || supplier.ranking > 10)) {
          return false;
        }
        return true;
      });
      // 排序
      const sortType = this.sortOptions[this.currentSort] ? this.sortOptions[this.currentSort].value : 'smart';
      data.sort((a, b) => {
        switch (sortType) {
          case 'distance':
            return this.parseDistance(a.distance) - this.parseDistance(b.distance);
          case 'score':
            return parseFloat(b.server_score) - parseFloat(a.server_score);
          case 'price_low':
            return (a.average_price || 0) - (b.average_price || 0);
          case 'price_high':
            return (b.average_price || 0) - (a.average_price || 0);
          default:
            return 0;
        }
      });
      return data;
    },
    hasActiveFilters() {
      return this.currentCategory > 0 ||
             this.selectedDistance >= 0 ||
             this.selectedPrice >= 0 ||
             this.selectedScore >= 0 ||
             this.showCouponOnly ||
             this.showTopRanked;
    }
  },
  methods: {
    // 获取筛选条件
    getFilterCondition() {
      this.$http.get('/supplier/index/getGroupBuyCondition').then(res => {
        this.sortOptions = res.data.sortOptions || [];
        this.categoryOptions = res.data.category || [];
        this.distanceOptions = res.data.distanceOptions || [];
        this.priceOptions = res.data.priceOptions || [];
        this.scoreOptions = res.data.scoreOptions || [];
      }).catch(err => {
        console.error('获取筛选条件失败', err);
      });
    },
    toggleSort() {
      this.showSortDropdown = !this.showSortDropdown;
      this.showCategoryDropdown = false;
      this.showFilterPanel = false;
    },
    toggleCategory() {
      this.showCategoryDropdown = !this.showCategoryDropdown;
      this.showSortDropdown = false;
      this.showFilterPanel = false;
    },
    toggleFilter() {
      this.showFilterPanel = !this.showFilterPanel;
      this.showSortDropdown = false;
      this.showCategoryDropdown = false;
    },
    closeAllDropdowns() {
      this.showSortDropdown = false;
      this.showCategoryDropdown = false;
      this.showFilterPanel = false;
    },
    selectCategory(index) {
      this.currentCategory = index;
      this.showCategoryDropdown = false;
    },
    selectSort(index) {
      this.currentSort = index;
      this.showSortDropdown = false;
    },
    selectDistance(index) {
      this.selectedDistance = this.selectedDistance === index ? -1 : index;
    },
    selectPrice(index) {
      this.selectedPrice = this.selectedPrice === index ? -1 : index;
    },
    selectScore(index) {
      this.selectedScore = this.selectedScore === index ? -1 : index;
    },
    toggleCouponOnly() {
      this.showCouponOnly = !this.showCouponOnly;
    },
    toggleTopRanked() {
      this.showTopRanked = !this.showTopRanked;
    },
    resetFilters() {
      this.selectedDistance = -1;
      this.selectedPrice = -1;
      this.selectedScore = -1;
      this.showCouponOnly = false;
      this.showTopRanked = false;
    },
    applyFilters() {
      this.showFilterPanel = false;
    },
    parseDistance(distanceStr) {
      if (!distanceStr) return 99999;
      const match = distanceStr.match(/(\d+\.?\d*)/);
      return match ? parseFloat(match[1]) : 99999;
    }
  }
};
</script>
@@ -75,6 +337,222 @@
.supplier{
  padding: 12px;
}
/* 筛选栏 */
.supplier-set{
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}
.filter-item {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 6px 12px;
  background: #f8f8f8;
  border-radius: 15px;
  font-size: 14px;
  color: #333;
  cursor: pointer;
  border-left: none;
  width: auto;
  text-align: center;
}
.filter-text {
  font-size: 14px;
  color: #333;
}
.filter-icon {
  font-size: 10px;
  color: #999;
  transition: transform 0.3s;
}
.filter-icon.rotate {
  transform: rotate(180deg);
}
.filter-badge {
  width: 8px;
  height: 8px;
  background: #c73a4e;
  border-radius: 50%;
  font-size: 0;
}
/* 排序下拉面板 */
.sort-dropdown,
.category-dropdown {
  position: absolute;
  top: 60px;
  z-index: 200;
  width: 150px;
  background: #ffffff;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  overflow: hidden;
}
.sort-dropdown {
  left: 12px;
}
.category-dropdown {
  left: 172px;
}
.sort-option {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 15px;
  border-bottom: 1px solid #f5f5f5;
  cursor: pointer;
}
.sort-option:last-child {
  border-bottom: none;
}
.sort-option:hover {
  background: #f8f8f8;
}
.sort-option-text {
  font-size: 14px;
  color: #333;
}
.sort-option-text.active {
  color: #c73a4e;
  font-weight: bold;
}
.sort-option-check {
  color: #c73a4e;
  font-size: 14px;
  font-weight: bold;
}
/* 遮罩层 */
.mask {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 150;
}
/* 筛选面板 */
.filter-panel {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 200;
  background: #ffffff;
  border-radius: 16px 16px 0 0;
  max-height: 400px;
  overflow-y: auto;
}
.filter-panel-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 15px 12px;
  border-bottom: 1px solid #f5f5f5;
}
.filter-panel-title {
  font-size: 16px;
  font-weight: bold;
  color: #333;
}
.filter-reset {
  font-size: 14px;
  color: #999;
  cursor: pointer;
}
.filter-section {
  padding: 15px 12px;
  border-bottom: 1px solid #f5f5f5;
}
.filter-section-title {
  font-size: 14px;
  font-weight: bold;
  color: #333;
  margin-bottom: 10px;
}
.filter-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.filter-tag {
  padding: 8px 16px;
  background: #f8f8f8;
  border-radius: 4px;
  font-size: 13px;
  color: #666;
  white-space: nowrap;
  cursor: pointer;
}
.filter-tag.active {
  background: #fff5f6;
  color: #c73a4e;
  border: 1px solid #c73a4e;
}
.filter-panel-footer {
  padding: 12px;
  border-top: 1px solid #f5f5f5;
}
.filter-confirm-btn {
  width: 100%;
  height: 44px;
  background: linear-gradient(to right, #c73a4e, #e74c3c);
  border-radius: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffffff;
  font-size: 16px;
  font-weight: bold;
  cursor: pointer;
}
.filter-confirm-btn:hover {
  opacity: 0.9;
}
/* 空状态 */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 50px 0;
}
.empty-text {
  font-size: 14px;
  color: #999;
}
.supplier-time{
  background: #ffff;
  margin-bottom: 10px;