Просмотр исходного кода

花掌柜-商品分类公告优化

ouyang 2 недель назад
Родитель
Сommit
7e24e31620
2 измененных файлов с 487 добавлено и 565 удалено
  1. 486 486
      hdApp/src/admin/goods/ad-goods-select.vue
  2. 1 79
      hdApp/src/admin/goods/category-ad.vue

+ 486 - 486
hdApp/src/admin/goods/ad-goods-select.vue

@@ -1,486 +1,486 @@
-<!--
-  分类广告 - 商品多选页
-  用途:从花束商品中勾选关联商品,返回商品 id 列表给 category-ad
-  基于 manage 花束面板精简:无打标签/库存/编辑/更多/新增,无花材标签筛选
--->
-<template>
-  <view class="goods-select-page">
-    <view class="search-row">
-      <view class="search-type-wrap">
-        <button class="type-btn" :class="{ active: searchType === 0 }" hover-class="none" @click="changeSearchType(0)">名称</button>
-        <button class="type-btn" :class="{ active: searchType === 1 }" hover-class="none" @click="changeSearchType(1)">编号</button>
-      </view>
-      <view class="search-box">
-        <input v-model="searchText" class="search-input" placeholder="请输入" confirm-type="search" @confirm="searchGoods" />
-      </view>
-      <button class="search-btn" hover-class="none" @click="searchGoods">搜索</button>
-    </view>
-
-    <view class="filter-row">
-      <view class="filter-tabs">
-        <view class="filter-tab" :class="{ active: priceType === -1 }" @click="switchPriceTab(-1)">全部</view>
-        <view class="filter-tab" :class="{ active: priceType === 0 }" @click="switchPriceTab(0)">无价</view>
-        <view class="filter-tab" :class="{ active: priceType === 1 }" @click="switchPriceTab(1)">有价</view>
-      </view>
-      <button class="refresh-btn" hover-class="none" @click="refreshGoods">刷新</button>
-    </view>
-
-    <view class="content-area">
-      <scroll-view scroll-y class="category-list">
-        <view
-          v-for="(item, index) in tabbar"
-          :key="item.id || index"
-          class="category-item"
-          :class="{ active: currentTab === index }"
-          :data-current="index"
-          @tap.stop="switchCategory($event, item)"
-        >
-          <text>{{ item.categoryName || '' }}</text>
-        </view>
-      </scroll-view>
-
-      <scroll-view scroll-y class="list-box" @scrolltolower="goodsReachBottom">
-        <block v-if="!$util.isEmpty(list.data)">
-          <view
-            class="goods-row"
-            v-for="(goodsItem, idx) in list.data"
-            :key="goodsItem.id || idx"
-            :data-id="goodsItem.id"
-            @tap.stop="toggleGoodsByEvent"
-          >
-            <view class="check-box" :class="{ checked: !!selectedMap[String(goodsItem.id)] }">
-              <text v-if="selectedMap[String(goodsItem.id)]" class="check-mark">✓</text>
-            </view>
-            <image class="goods-img" :src="goodsItem.smallCover" mode="aspectFill" />
-            <view class="goods-info">
-              <view class="goods-name">{{ goodsItem.name || '' }}</view>
-              <view class="goods-meta">
-                <text v-if="Number(goodsItem.sn) > 0">#{{ goodsItem.sn }}</text>
-                <text class="price" v-if="goodsItem.priceType == 1">¥{{ goodsItem.price ? parseFloat(goodsItem.price) : 0 }}</text>
-                <text class="price" v-else>---</text>
-              </view>
-            </view>
-          </view>
-        </block>
-        <app-wrapper-empty v-else title="暂无商品" :is-empty="$util.isEmpty(list.data)" />
-      </scroll-view>
-    </view>
-
-    <view class="footer-bar">
-      <text class="selected-tip">已选 {{ selectedCount }} 个</text>
-      <button class="confirm-btn" hover-class="none" @click="confirmSelect">确定</button>
-    </view>
-  </view>
-</template>
-
-<script>
-import AppWrapperEmpty from '@/components/app-wrapper-empty';
-import { list } from '@/mixins';
-import { categoryListInfo, getCategoryToGoods } from '@/api/goods';
-
-export default {
-  name: 'ad-goods-select',
-  components: { AppWrapperEmpty },
-  mixins: [list],
-  data() {
-    return {
-      slotIndex: 0,
-      // 用对象存选中态,小程序端 $set 比数组 splice 更易触发视图更新
-      selectedMap: {},
-      tabbar: [],
-      currentTab: 0,
-      catId: '',
-      catName: '',
-      searchText: '',
-      searchType: 0,
-      lastSearchText: '',
-      priceType: -1,
-      hasStock: -1,
-      autoLoad: false,
-      goodsSelectPageReady: false,
-      selectedIdsParsed: false
-    };
-  },
-  computed: {
-    /** 已选商品数量,供底部栏展示 */
-    selectedCount() {
-      return Object.keys(this.selectedMap).length;
-    }
-  },
-  onLoad(options) {
-    // 兜底解析路由参数;主流程由 globalMixins 触发的 init 执行
-    this.applyPageOptions(options || {});
-    this.ensureLoadCategories();
-  },
-  methods: {
-    /** globalMixins 在 onLoad 会调用 init,需在此拉取分类与商品 */
-    init() {
-      this.applyPageOptions(this.option || {});
-      this.ensureLoadCategories();
-    },
-    /** 解析 slotIndex、已选商品 id,可重复调用但 selectedIds 只回填一次 */
-    applyPageOptions(options) {
-      if (options.slotIndex !== undefined && options.slotIndex !== '') {
-        this.slotIndex = parseInt(options.slotIndex || 0, 10);
-      }
-      if (!this.selectedIdsParsed && options.selectedIds) {
-        this.selectedIdsParsed = true;
-        String(options.selectedIds)
-          .split(',')
-          .filter(Boolean)
-          .forEach((id) => {
-            this.$set(this.selectedMap, String(id), true);
-          });
-      }
-    },
-    /** 避免 init / onLoad 重复请求分类列表 */
-    ensureLoadCategories() {
-      if (this.goodsSelectPageReady) return;
-      this.goodsSelectPageReady = true;
-      this.loadCategories();
-    },
-    /** 花束分类来自 xhCategory,接口返回即为花束分类列表 */
-    loadCategories() {
-      categoryListInfo().then((res) => {
-        const all = res.data || [];
-        this.tabbar = all;
-        if (this.tabbar.length && this.tabbar[0].id) {
-          this.catId = this.tabbar[0].id;
-          this.catName = this.tabbar[0].categoryName;
-          this.refreshGoods();
-        }
-      });
-    },
-    getGoodsList() {
-      return getCategoryToGoods({
-        page: this.list.page,
-        searchText: this.lastSearchText,
-        searchType: this.searchType,
-        catId: this.catId,
-        flowerNum: 0,
-        priceType: this.priceType,
-        hasStock: this.hasStock,
-        requestType: 'goodsList'
-      }).then((res) => {
-        this.completes(res);
-      });
-    },
-    refreshGoods() {
-      this.resetList();
-      this.getGoodsList();
-    },
-    goodsReachBottom() {
-      if (!this.list.finished) {
-        this.getGoodsList();
-      }
-    },
-    /** 切换左侧分类;index 从 data-current 读取,兼容小程序 tap 传参 */
-    switchCategory(e, item) {
-      const index = Number(e.currentTarget.dataset.current);
-      const category = item || this.tabbar[index];
-      if (!category || category.id === undefined || category.id === null) return;
-      if (this.currentTab === index) return;
-      this.currentTab = index;
-      this.catId = category.id;
-      this.catName = category.categoryName || '';
-      this.searchText = '';
-      this.lastSearchText = '';
-      this.refreshGoods();
-    },
-    changeSearchType(i) {
-      this.searchType = i;
-      this.searchGoods();
-    },
-    searchGoods() {
-      this.lastSearchText = this.searchText;
-      if (!this.$util.isEmpty(this.lastSearchText)) {
-        this.catId = 0;
-        this.catName = '花束';
-        this.currentTab = -1;
-      } else if (this.tabbar[0]) {
-        this.currentTab = 0;
-        this.catId = this.tabbar[0].id;
-        this.catName = this.tabbar[0].categoryName;
-      }
-      this.refreshGoods();
-    },
-    switchPriceTab(value) {
-      if (this.priceType === value) return;
-      this.priceType = value;
-      this.refreshGoods();
-    },
-    /** 从 data-id 读取商品 id,避免小程序 tap 回调参数错位 */
-    toggleGoodsByEvent(e) {
-      const id = e.currentTarget.dataset.id;
-      if (id === undefined || id === null || id === '') return;
-      const key = String(id);
-      if (this.selectedMap[key]) {
-        this.$delete(this.selectedMap, key);
-      } else {
-        this.$set(this.selectedMap, key, true);
-      }
-    },
-    confirmSelect() {
-      uni.$emit('categoryAdGoodsSelected', {
-        slotIndex: this.slotIndex,
-        goodsIds: Object.keys(this.selectedMap)
-      });
-      uni.navigateBack();
-    }
-  }
-};
-</script>
-
-<style lang="scss" scoped>
-.goods-select-page {
-  height: 100vh;
-  display: flex;
-  flex-direction: column;
-  background: #fff;
-}
-
-.search-row {
-  display: flex;
-  align-items: center;
-  padding: 16rpx 18rpx;
-  flex-shrink: 0;
-}
-
-.search-type-wrap {
-  display: flex;
-  margin-right: 10rpx;
-}
-
-.type-btn {
-  width: 92rpx;
-  height: 64rpx;
-  line-height: 64rpx;
-  padding: 0;
-  margin: 0 8rpx 0 0;
-  font-size: 24rpx;
-  background: #f5f5f5;
-  color: #333;
-  border-radius: 8rpx;
-}
-
-.type-btn::after {
-  border: none;
-}
-
-.type-btn.active {
-  background: #52c41a;
-  color: #fff;
-}
-
-.search-box {
-  flex: 1;
-  height: 64rpx;
-  border: 2rpx solid #e2e2e2;
-  border-radius: 34rpx;
-  padding: 0 24rpx;
-  box-sizing: border-box;
-  min-width: 0;
-  margin-right: 10rpx;
-}
-
-.search-input {
-  height: 60rpx;
-  font-size: 26rpx;
-}
-
-.search-btn {
-  width: 108rpx;
-  height: 64rpx;
-  line-height: 64rpx;
-  padding: 0;
-  margin: 0;
-  font-size: 26rpx;
-  background: #52c41a;
-  color: #fff;
-  border-radius: 8rpx;
-}
-
-.search-btn::after {
-  border: none;
-}
-
-.filter-row {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 0 18rpx 16rpx;
-  flex-shrink: 0;
-}
-
-.filter-tabs {
-  display: flex;
-}
-
-.filter-tab {
-  margin-right: 40rpx;
-  font-size: 26rpx;
-  color: #111;
-  font-weight: 600;
-  position: relative;
-}
-
-.filter-tab.active {
-  color: #22a500;
-}
-
-.filter-tab.active::after {
-  content: '';
-  position: absolute;
-  left: 0;
-  right: 0;
-  bottom: -6rpx;
-  height: 4rpx;
-  background: #22a500;
-}
-
-.refresh-btn {
-  height: 56rpx;
-  line-height: 56rpx;
-  padding: 0 20rpx;
-  margin: 0;
-  font-size: 24rpx;
-  background: #f5f5f5;
-  color: #333;
-  border-radius: 8rpx;
-}
-
-.refresh-btn::after {
-  border: none;
-}
-
-.content-area {
-  flex: 1;
-  min-height: 0;
-  display: flex;
-  overflow: hidden;
-}
-
-.category-list {
-  width: 202rpx;
-  height: 100%;
-  flex-shrink: 0;
-  background: #fafafa;
-}
-
-.category-item {
-  min-height: 92rpx;
-  padding: 18rpx 10rpx 18rpx 24rpx;
-  font-size: 24rpx;
-  color: #111;
-  font-weight: 600;
-}
-
-.category-item.active {
-  color: #189b00;
-  background: #fff;
-}
-
-.list-box {
-  flex: 1;
-  height: 100%;
-  min-width: 0;
-  padding: 0 18rpx 120rpx;
-  box-sizing: border-box;
-}
-
-.goods-row {
-  display: flex;
-  align-items: center;
-  padding: 20rpx 0;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-
-.check-box {
-  width: 36rpx;
-  height: 36rpx;
-  border: 2rpx solid #ccc;
-  border-radius: 6rpx;
-  margin-right: 16rpx;
-  flex-shrink: 0;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.check-box.checked {
-  background: #52c41a;
-  border-color: #52c41a;
-}
-
-.check-mark {
-  color: #fff;
-  font-size: 24rpx;
-}
-
-.goods-img {
-  width: 120rpx;
-  height: 120rpx;
-  border-radius: 8rpx;
-  margin-right: 16rpx;
-  flex-shrink: 0;
-}
-
-.goods-info {
-  flex: 1;
-  min-width: 0;
-}
-
-.goods-name {
-  font-size: 28rpx;
-  color: #333;
-  line-height: 1.4;
-  margin-bottom: 8rpx;
-}
-
-.goods-meta {
-  font-size: 24rpx;
-  color: #999;
-}
-
-.price {
-  margin-left: 12rpx;
-  color: #e4393c;
-}
-
-.footer-bar {
-  position: fixed;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 20rpx 30rpx;
-  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
-  background: #fff;
-  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.06);
-  z-index: 10;
-}
-
-.selected-tip {
-  font-size: 28rpx;
-  color: #666;
-}
-
-.confirm-btn {
-  width: 240rpx;
-  height: 72rpx;
-  line-height: 72rpx;
-  margin: 0;
-  padding: 0;
-  background: #52c41a;
-  color: #fff;
-  font-size: 28rpx;
-  border-radius: 8rpx;
-}
-
-.confirm-btn::after {
-  border: none;
-}
-</style>
-
+<!--
+  分类广告 - 商品多选页
+  用途:从花束商品中勾选关联商品,返回商品 id 列表给 category-ad
+  基于 manage 花束面板精简:无打标签/库存/编辑/更多/新增,无花材标签筛选
+-->
+<template>
+  <view class="goods-select-page">
+    <view class="search-row">
+      <view class="search-type-wrap">
+        <button class="type-btn" :class="{ active: searchType === 0 }" hover-class="none" @click="changeSearchType(0)">名称</button>
+        <button class="type-btn" :class="{ active: searchType === 1 }" hover-class="none" @click="changeSearchType(1)">编号</button>
+      </view>
+      <view class="search-box">
+        <input v-model="searchText" class="search-input" placeholder="请输入" confirm-type="search" @confirm="searchGoods" />
+      </view>
+      <button class="search-btn" hover-class="none" @click="searchGoods">搜索</button>
+    </view>
+
+    <view class="filter-row">
+      <view class="filter-tabs">
+        <view class="filter-tab" :class="{ active: priceType === -1 }" @click="switchPriceTab(-1)">全部</view>
+        <view class="filter-tab" :class="{ active: priceType === 0 }" @click="switchPriceTab(0)">无价</view>
+        <view class="filter-tab" :class="{ active: priceType === 1 }" @click="switchPriceTab(1)">有价</view>
+      </view>
+      <button class="refresh-btn" hover-class="none" @click="refreshGoods">刷新</button>
+    </view>
+
+    <view class="content-area">
+      <scroll-view scroll-y class="category-list">
+        <view
+          v-for="(item, index) in tabbar"
+          :key="item.id || index"
+          class="category-item"
+          :class="{ active: currentTab === index }"
+          :data-current="index"
+          @tap.stop="switchCategory($event, item)"
+        >
+          <text>{{ item.categoryName || '' }}</text>
+        </view>
+      </scroll-view>
+
+      <scroll-view scroll-y class="list-box" @scrolltolower="goodsReachBottom">
+        <block v-if="!$util.isEmpty(list.data)">
+          <view
+            class="goods-row"
+            v-for="(goodsItem, idx) in list.data"
+            :key="goodsItem.id || idx"
+            :data-id="goodsItem.id"
+            @tap.stop="toggleGoodsByEvent"
+          >
+            <view class="check-box" :class="{ checked: !!selectedMap[String(goodsItem.id)] }">
+              <text v-if="selectedMap[String(goodsItem.id)]" class="check-mark">✓</text>
+            </view>
+            <image class="goods-img" :src="goodsItem.smallCover" mode="aspectFill" />
+            <view class="goods-info">
+              <view class="goods-name">{{ goodsItem.name || '' }}</view>
+              <view class="goods-meta">
+                <text v-if="Number(goodsItem.sn) > 0">#{{ goodsItem.sn }}</text>
+                <text class="price" v-if="goodsItem.priceType == 1">¥{{ goodsItem.price ? parseFloat(goodsItem.price) : 0 }}</text>
+                <text class="price" v-else>---</text>
+              </view>
+            </view>
+          </view>
+        </block>
+        <app-wrapper-empty v-else title="暂无商品" :is-empty="$util.isEmpty(list.data)" />
+      </scroll-view>
+    </view>
+
+    <view class="footer-bar">
+      <text class="selected-tip">已选 {{ selectedCount }} 个</text>
+      <button class="confirm-btn" hover-class="none" @click="confirmSelect">确定</button>
+    </view>
+  </view>
+</template>
+
+<script>
+import AppWrapperEmpty from '@/components/app-wrapper-empty';
+import { list } from '@/mixins';
+import { categoryListInfo, getCategoryToGoods } from '@/api/goods';
+
+export default {
+  name: 'ad-goods-select',
+  components: { AppWrapperEmpty },
+  mixins: [list],
+  data() {
+    return {
+      slotIndex: 0,
+      // 用对象存选中态,小程序端 $set 比数组 splice 更易触发视图更新
+      selectedMap: {},
+      tabbar: [],
+      currentTab: 0,
+      catId: '',
+      catName: '',
+      searchText: '',
+      searchType: 0,
+      lastSearchText: '',
+      priceType: -1,
+      hasStock: -1,
+      autoLoad: false,
+      goodsSelectPageReady: false,
+      selectedIdsParsed: false
+    };
+  },
+  computed: {
+    /** 已选商品数量,供底部栏展示 */
+    selectedCount() {
+      return Object.keys(this.selectedMap).length;
+    }
+  },
+  onLoad(options) {
+    // 兜底解析路由参数;主流程由 globalMixins 触发的 init 执行
+    this.applyPageOptions(options || {});
+    this.ensureLoadCategories();
+  },
+  methods: {
+    /** globalMixins 在 onLoad 会调用 init,需在此拉取分类与商品 */
+    init() {
+      this.applyPageOptions(this.option || {});
+      this.ensureLoadCategories();
+    },
+    /** 解析 slotIndex、已选商品 id,可重复调用但 selectedIds 只回填一次 */
+    applyPageOptions(options) {
+      if (options.slotIndex !== undefined && options.slotIndex !== '') {
+        this.slotIndex = parseInt(options.slotIndex || 0, 10);
+      }
+      if (!this.selectedIdsParsed && options.selectedIds) {
+        this.selectedIdsParsed = true;
+        String(options.selectedIds)
+          .split(',')
+          .filter(Boolean)
+          .forEach((id) => {
+            this.$set(this.selectedMap, String(id), true);
+          });
+      }
+    },
+    /** 避免 init / onLoad 重复请求分类列表 */
+    ensureLoadCategories() {
+      if (this.goodsSelectPageReady) return;
+      this.goodsSelectPageReady = true;
+      this.loadCategories();
+    },
+    /** 花束分类来自 xhCategory,接口返回即为花束分类列表 */
+    loadCategories() {
+      categoryListInfo().then((res) => {
+        const all = res.data || [];
+        this.tabbar = all;
+        if (this.tabbar.length && this.tabbar[0].id) {
+          this.catId = this.tabbar[0].id;
+          this.catName = this.tabbar[0].categoryName;
+          this.refreshGoods();
+        }
+      });
+    },
+    getGoodsList() {
+      return getCategoryToGoods({
+        page: this.list.page,
+        searchText: this.lastSearchText,
+        searchType: this.searchType,
+        catId: this.catId,
+        flowerNum: 0,
+        priceType: this.priceType,
+        hasStock: this.hasStock,
+        requestType: 'goodsList'
+      }).then((res) => {
+        this.completes(res);
+      });
+    },
+    refreshGoods() {
+      this.resetList();
+      this.getGoodsList();
+    },
+    goodsReachBottom() {
+      if (!this.list.finished) {
+        this.getGoodsList();
+      }
+    },
+    /** 切换左侧分类;index 从 data-current 读取,兼容小程序 tap 传参 */
+    switchCategory(e, item) {
+      const index = Number(e.currentTarget.dataset.current);
+      const category = item || this.tabbar[index];
+      if (!category || category.id === undefined || category.id === null) return;
+      if (this.currentTab === index) return;
+      this.currentTab = index;
+      this.catId = category.id;
+      this.catName = category.categoryName || '';
+      this.searchText = '';
+      this.lastSearchText = '';
+      this.refreshGoods();
+    },
+    changeSearchType(i) {
+      this.searchType = i;
+      this.searchGoods();
+    },
+    searchGoods() {
+      this.lastSearchText = this.searchText;
+      if (!this.$util.isEmpty(this.lastSearchText)) {
+        this.catId = 0;
+        this.catName = '花束';
+        this.currentTab = -1;
+      } else if (this.tabbar[0]) {
+        this.currentTab = 0;
+        this.catId = this.tabbar[0].id;
+        this.catName = this.tabbar[0].categoryName;
+      }
+      this.refreshGoods();
+    },
+    switchPriceTab(value) {
+      if (this.priceType === value) return;
+      this.priceType = value;
+      this.refreshGoods();
+    },
+    /** 从 data-id 读取商品 id,避免小程序 tap 回调参数错位 */
+    toggleGoodsByEvent(e) {
+      const id = e.currentTarget.dataset.id;
+      if (id === undefined || id === null || id === '') return;
+      const key = String(id);
+      if (this.selectedMap[key]) {
+        this.$delete(this.selectedMap, key);
+      } else {
+        this.$set(this.selectedMap, key, true);
+      }
+    },
+    confirmSelect() {
+      uni.$emit('categoryAdGoodsSelected', {
+        slotIndex: this.slotIndex,
+        goodsIds: Object.keys(this.selectedMap)
+      });
+      uni.navigateBack();
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.goods-select-page {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+}
+
+.search-row {
+  display: flex;
+  align-items: center;
+  padding: 16rpx 18rpx;
+  flex-shrink: 0;
+}
+
+.search-type-wrap {
+  display: flex;
+  margin-right: 10rpx;
+}
+
+.type-btn {
+  width: 92rpx;
+  height: 64rpx;
+  line-height: 64rpx;
+  padding: 0;
+  margin: 0 8rpx 0 0;
+  font-size: 24rpx;
+  background: #f5f5f5;
+  color: #333;
+  border-radius: 8rpx;
+}
+
+.type-btn::after {
+  border: none;
+}
+
+.type-btn.active {
+  background: #52c41a;
+  color: #fff;
+}
+
+.search-box {
+  flex: 1;
+  height: 64rpx;
+  border: 2rpx solid #e2e2e2;
+  border-radius: 34rpx;
+  padding: 0 24rpx;
+  box-sizing: border-box;
+  min-width: 0;
+  margin-right: 10rpx;
+}
+
+.search-input {
+  height: 60rpx;
+  font-size: 26rpx;
+}
+
+.search-btn {
+  width: 108rpx;
+  height: 64rpx;
+  line-height: 64rpx;
+  padding: 0;
+  margin: 0;
+  font-size: 26rpx;
+  background: #52c41a;
+  color: #fff;
+  border-radius: 8rpx;
+}
+
+.search-btn::after {
+  border: none;
+}
+
+.filter-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 18rpx 16rpx;
+  flex-shrink: 0;
+}
+
+.filter-tabs {
+  display: flex;
+}
+
+.filter-tab {
+  margin-right: 40rpx;
+  font-size: 26rpx;
+  color: #111;
+  font-weight: 600;
+  position: relative;
+}
+
+.filter-tab.active {
+  color: #22a500;
+}
+
+.filter-tab.active::after {
+  content: '';
+  position: absolute;
+  left: 0;
+  right: 0;
+  bottom: -6rpx;
+  height: 4rpx;
+  background: #22a500;
+}
+
+.refresh-btn {
+  height: 56rpx;
+  line-height: 56rpx;
+  padding: 0 20rpx;
+  margin: 0;
+  font-size: 24rpx;
+  background: #f5f5f5;
+  color: #333;
+  border-radius: 8rpx;
+}
+
+.refresh-btn::after {
+  border: none;
+}
+
+.content-area {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  overflow: hidden;
+}
+
+.category-list {
+  width: 202rpx;
+  height: 100%;
+  flex-shrink: 0;
+  background: #fafafa;
+}
+
+.category-item {
+  min-height: 92rpx;
+  padding: 18rpx 10rpx 18rpx 24rpx;
+  font-size: 24rpx;
+  color: #111;
+  font-weight: 600;
+}
+
+.category-item.active {
+  color: #189b00;
+  background: #fff;
+}
+
+.list-box {
+  flex: 1;
+  height: 100%;
+  min-width: 0;
+  padding: 0 18rpx 120rpx;
+  box-sizing: border-box;
+}
+
+.goods-row {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+
+.check-box {
+  width: 36rpx;
+  height: 36rpx;
+  border: 2rpx solid #ccc;
+  border-radius: 6rpx;
+  margin-right: 16rpx;
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.check-box.checked {
+  background: #52c41a;
+  border-color: #52c41a;
+}
+
+.check-mark {
+  color: #fff;
+  font-size: 24rpx;
+}
+
+.goods-img {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 8rpx;
+  margin-right: 16rpx;
+  flex-shrink: 0;
+}
+
+.goods-info {
+  flex: 1;
+  min-width: 0;
+}
+
+.goods-name {
+  font-size: 28rpx;
+  color: #333;
+  line-height: 1.4;
+  margin-bottom: 8rpx;
+}
+
+.goods-meta {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.price {
+  margin-left: 12rpx;
+  color: #e4393c;
+}
+
+.footer-bar {
+  position: fixed;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 20rpx 30rpx;
+  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+  background: #fff;
+  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.06);
+  z-index: 10;
+}
+
+.selected-tip {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.confirm-btn {
+  width: 240rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  margin: 0;
+  padding: 0;
+  background: #52c41a;
+  color: #fff;
+  font-size: 28rpx;
+  border-radius: 8rpx;
+}
+
+.confirm-btn::after {
+  border: none;
+}
+</style>
+

+ 1 - 79
hdApp/src/admin/goods/category-ad.vue

@@ -51,7 +51,7 @@
 
     <view class="bottom-space"></view>
 
-    <!-- 分类多选弹层:支持搜索与已选预览,分类较多时便于查找 -->
+    <!-- 分类多选弹层:支持搜索,分类较多时便于查找 -->
     <view v-if="categoryPickerShow" class="picker-overlay" @touchmove.stop.prevent="preventTouchMove">
       <view class="picker-mask" @tap.stop="closeCategoryPicker"></view>
       <view class="picker-panel category-picker-panel">
@@ -76,23 +76,6 @@
             @tap.stop="clearCategorySearch"
           >清空</text>
         </view>
-        <scroll-view
-          v-if="selectedCategoryItems.length"
-          scroll-x
-          class="picker-selected-row"
-          :show-scrollbar="false"
-        >
-          <view class="picker-selected-wrap">
-            <view
-              class="picker-selected-chip"
-              v-for="item in selectedCategoryItems"
-              :key="item.id"
-            >
-              <text class="chip-name">{{ item.name }}</text>
-              <text class="chip-remove" :data-id="item.id" @tap.stop="removeCategoryPickByEvent">×</text>
-            </view>
-          </view>
-        </scroll-view>
         <scroll-view scroll-y class="picker-body">
           <block v-if="filteredBouquetCategories.length">
             <view
@@ -199,16 +182,6 @@ export default {
         const name = (cat.categoryName || '').toLowerCase();
         return name.indexOf(keyword) > -1;
       });
-    },
-    /** 已选分类标签,便于回显与快速取消 */
-    selectedCategoryItems() {
-      return Object.keys(this.categoryPickMap).map((id) => {
-        const cat = this.bouquetCategories.find((c) => String(c.id) === String(id));
-        return {
-          id,
-          name: cat ? cat.categoryName : id
-        };
-      });
     }
   },
   onLoad() {
@@ -379,12 +352,6 @@ export default {
         this.$set(this.categoryPickMap, key, true);
       }
     },
-    /** 从已选标签移除单个分类 */
-    removeCategoryPickByEvent(e) {
-      const id = e.currentTarget.dataset.id;
-      if (id === undefined || id === null || id === '') return;
-      this.$delete(this.categoryPickMap, String(id));
-    },
     clearCategorySearch() {
       this.categorySearchText = '';
     },
@@ -825,51 +792,6 @@ export default {
   flex-shrink: 0;
 }
 
-.picker-selected-row {
-  width: 100%;
-  white-space: nowrap;
-  border-bottom: 1rpx solid #f5f5f5;
-  flex-shrink: 0;
-}
-
-.picker-selected-wrap {
-  display: flex;
-  flex-direction: row;
-  padding: 16rpx 24rpx;
-}
-
-.picker-selected-chip {
-  display: inline-flex;
-  align-items: center;
-  max-width: 280rpx;
-  height: 52rpx;
-  padding: 0 12rpx 0 16rpx;
-  margin-right: 12rpx;
-  background: #f6ffed;
-  border: 1rpx solid #b7eb8f;
-  border-radius: 26rpx;
-  flex-shrink: 0;
-}
-
-.chip-name {
-  max-width: 220rpx;
-  font-size: 24rpx;
-  color: #389e0d;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-
-.chip-remove {
-  width: 32rpx;
-  height: 32rpx;
-  line-height: 30rpx;
-  text-align: center;
-  margin-left: 8rpx;
-  font-size: 28rpx;
-  color: #999;
-}
-
 .picker-body {
   flex: 1;
   min-height: 0;