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