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

feat(home-nav): 支持金刚区关联花材分类

- 后台新增花材分类单选并保留关联类型
- 商城点击花材导航后跳转分类页并自动展开定位
- 扩展预设图标解析范围至 29 个
shizhongqi 13 часов назад
Родитель
Сommit
1f0ef7c145

+ 9 - 3
hdApp/src/admin/homePageConfig/navGrid.vue

@@ -123,15 +123,21 @@ import { getNavGrid, saveNavGrid } from '@/api/home-page-config'
 const DRAFT_KEY = 'navGridDraft'
 const DRAFT_KEY = 'navGridDraft'
 const MAX_NAV_COUNT = 20
 const MAX_NAV_COUNT = 20
 
 
-/** 默认图标库:preset:N → OSS 路径,与编辑页一致 */
+/** 默认图标库数量:须与后端 HomePageModuleClass::NAV_PRESET_ICON_COUNT 一致,否则新图标在列表会空白 */
+const NAV_PRESET_ICON_COUNT = 29
+/** 默认图标库:preset:N → OSS 路径,与编辑页/后端图库一致 */
 const PRESET_ICON_PATH_PREFIX = 'hhb/images/navGrid-default/navGrid-default'
 const PRESET_ICON_PATH_PREFIX = 'hhb/images/navGrid-default/navGrid-default'
 
 
-/** 由 preset:N 解析出静态图相对路径;非法则返回空 */
+/**
+ * 由 preset:N 解析出静态图相对路径;非法或超出图库数量则返回空(空路径会导致 <image> 空白)
+ * @param {string} icon 草稿/接口里的图标标识,如 preset:13
+ * @returns {string} OSS 相对路径
+ */
 function getPresetIconPath(icon) {
 function getPresetIconPath(icon) {
   const m = String(icon || '').match(/^preset:(\d+)$/)
   const m = String(icon || '').match(/^preset:(\d+)$/)
   if (!m) return ''
   if (!m) return ''
   const n = parseInt(m[1], 10)
   const n = parseInt(m[1], 10)
-  if (n < 1 || n > 12) return ''
+  if (n < 1 || n > NAV_PRESET_ICON_COUNT) return ''
   return `${PRESET_ICON_PATH_PREFIX}${n}.png`
   return `${PRESET_ICON_PATH_PREFIX}${n}.png`
 }
 }
 
 

+ 141 - 14
hdApp/src/admin/homePageConfig/navGridEdit.vue

@@ -1,6 +1,6 @@
 <!--
 <!--
   分类金刚区导航项编辑页
   分类金刚区导航项编辑页
-  新增/编辑单个导航:名称、图标(默认图库 PNG 或上传)、关联分类/场景;保存回写列表页草稿后返回。
+  新增/编辑单个导航:名称、图标(默认图库 PNG 或上传)、关联花束分类/场景/花材分类;保存回写列表页草稿后返回。
 -->
 -->
 <template>
 <template>
   <view class="page-container">
   <view class="page-container">
@@ -75,8 +75,8 @@
         </view>
         </view>
       </view>
       </view>
 
 
-      <!-- 分类多选 -->
-      <view v-if="form.type === 3" class="field-row">
+      <!-- 花束分类多选 -->
+      <view v-if="form.type === TYPE_CATEGORY" class="field-row">
         <text class="field-label"><text class="required">*</text>关联内容</text>
         <text class="field-label"><text class="required">*</text>关联内容</text>
         <view
         <view
           class="inline-select"
           class="inline-select"
@@ -105,7 +105,7 @@
       </view>
       </view>
 
 
       <!-- 场景多选 -->
       <!-- 场景多选 -->
-      <view v-if="form.type === 4" class="field-row">
+      <view v-if="form.type === TYPE_USE_CASE" class="field-row">
         <text class="field-label"><text class="required">*</text>关联内容</text>
         <text class="field-label"><text class="required">*</text>关联内容</text>
         <view
         <view
           class="inline-select"
           class="inline-select"
@@ -132,6 +132,35 @@
           <view v-if="!useCaseData.length" class="panel-empty">暂无场景</view>
           <view v-if="!useCaseData.length" class="panel-empty">暂无场景</view>
         </view>
         </view>
       </view>
       </view>
+
+      <!-- 花材分类单选:商城首页点击后定位到该分类下花材 -->
+      <view v-if="form.type === TYPE_ITEM_CLASS" class="field-row">
+        <text class="field-label"><text class="required">*</text>关联内容</text>
+        <view
+          class="inline-select"
+          :class="{ open: itemClassPanelShow }"
+          @click="toggleItemClassPanel"
+        >
+          <text class="inline-select-text" :class="{ placeholder: !itemClassSelectedName }">
+            {{ itemClassSelectedName || '请选择花材分类 (单选)' }}
+          </text>
+          <text class="inline-arrow">{{ itemClassPanelShow ? '▲' : '▼' }}</text>
+        </view>
+        <view v-if="itemClassPanelShow" class="checkbox-panel">
+          <view
+            v-for="cls in itemClassData"
+            :key="cls.id"
+            class="checkbox-item"
+            @click="selectItemClassId(cls.id)"
+          >
+            <view class="radio-box" :class="{ checked: isItemClassChecked(cls.id) }">
+              <text v-if="isItemClassChecked(cls.id)" class="check-mark">✓</text>
+            </view>
+            <text class="checkbox-name">{{ cls.name }}</text>
+          </view>
+          <view v-if="!itemClassData.length" class="panel-empty">暂无花材分类</view>
+        </view>
+      </view>
     </view>
     </view>
 
 
     <view class="bottom-space"></view>
     <view class="bottom-space"></view>
@@ -144,14 +173,19 @@
 
 
 <script>
 <script>
 /**
 /**
- * 金刚区单项编辑:名称/图标/关联;默认图标库由后端接口返回
+ * 金刚区单项编辑:名称/图标/关联(花束分类多选、场景多选、花材分类单选);默认图标库由后端接口返回
  */
  */
 import { APIHOST } from '@/config'
 import { APIHOST } from '@/config'
 import { getClass } from '@/api/category'
 import { getClass } from '@/api/category'
 import { useCaseList } from '@/api/goods'
 import { useCaseList } from '@/api/goods'
 import { getNavGridPresetIcons } from '@/api/home-page-config'
 import { getNavGridPresetIcons } from '@/api/home-page-config'
+import { getAllItemClass } from '@/api/item-class'
 
 
 const DRAFT_KEY = 'navGridDraft'
 const DRAFT_KEY = 'navGridDraft'
+/** 关联类型:与后端 HomePageModuleClass 常量一致 */
+const TYPE_CATEGORY = 3
+const TYPE_USE_CASE = 4
+const TYPE_ITEM_CLASS = 5
 
 
 const EMPTY_FORM = () => ({
 const EMPTY_FORM = () => ({
   id: '',
   id: '',
@@ -159,7 +193,7 @@ const EMPTY_FORM = () => ({
   icon: '',
   icon: '',
   iconType: 1,
   iconType: 1,
   enabled: 1,
   enabled: 1,
-  type: 3,
+  type: TYPE_CATEGORY,
   value: ''
   value: ''
 })
 })
 
 
@@ -167,25 +201,31 @@ export default {
   name: 'homePageNavGridEdit',
   name: 'homePageNavGridEdit',
   data() {
   data() {
     return {
     return {
+      TYPE_CATEGORY,
+      TYPE_USE_CASE,
+      TYPE_ITEM_CLASS,
       editId: '',
       editId: '',
       form: EMPTY_FORM(),
       form: EMPTY_FORM(),
       iconTab: 'preset',
       iconTab: 'preset',
       /** 默认图标库,init 时从 /home-page-config/get-nav-grid-preset-icons 拉取 */
       /** 默认图标库,init 时从 /home-page-config/get-nav-grid-preset-icons 拉取 */
       presetIcons: [],
       presetIcons: [],
       typeOptions: [
       typeOptions: [
-        { label: '分类', value: 3 },
-        { label: '场景', value: 4 }
+        { label: '花束分类', value: TYPE_CATEGORY },
+        { label: '场景', value: TYPE_USE_CASE },
+        { label: '花材分类', value: TYPE_ITEM_CLASS }
       ],
       ],
       categoryData: [],
       categoryData: [],
       useCaseData: [],
       useCaseData: [],
+      itemClassData: [],
       categoryPanelShow: false,
       categoryPanelShow: false,
       useCasePanelShow: false,
       useCasePanelShow: false,
+      itemClassPanelShow: false,
       uploading: false
       uploading: false
     }
     }
   },
   },
   computed: {
   computed: {
     categorySelectedNames() {
     categorySelectedNames() {
-      if (this.form.type !== 3) return ''
+      if (this.form.type !== TYPE_CATEGORY) return ''
       const ids = this.parseIds(this.form.value)
       const ids = this.parseIds(this.form.value)
       if (!ids.length) return ''
       if (!ids.length) return ''
       return ids
       return ids
@@ -196,7 +236,7 @@ export default {
         .join('、')
         .join('、')
     },
     },
     useCaseSelectedNames() {
     useCaseSelectedNames() {
-      if (this.form.type !== 4) return ''
+      if (this.form.type !== TYPE_USE_CASE) return ''
       const ids = this.parseIds(this.form.value)
       const ids = this.parseIds(this.form.value)
       if (!ids.length) return ''
       if (!ids.length) return ''
       return ids
       return ids
@@ -205,17 +245,28 @@ export default {
           return uc ? uc.useCaseName : id
           return uc ? uc.useCaseName : id
         })
         })
         .join('、')
         .join('、')
+    },
+    /**
+     * 花材分类单选展示名;接口返回字段为 name
+     */
+    itemClassSelectedName() {
+      if (this.form.type !== TYPE_ITEM_CLASS) return ''
+      const id = String(this.form.value || '')
+      if (!id) return ''
+      const cls = this.itemClassData.find((c) => String(c.id) === id)
+      return cls ? cls.name : id
     }
     }
   },
   },
   methods: {
   methods: {
     /**
     /**
-     * 初始化:加载分类/场景/默认图标库,并从草稿恢复当前编辑项或新建空表单。
+     * 初始化:加载花束分类/场景/花材分类/默认图标库,并从草稿恢复当前编辑项或新建空表单。
      */
      */
     init() {
     init() {
       this.editId = (this.option && this.option.id) ? String(this.option.id) : ''
       this.editId = (this.option && this.option.id) ? String(this.option.id) : ''
       this.loadPresetIcons()
       this.loadPresetIcons()
       this.loadCategory()
       this.loadCategory()
       this.loadUseCase()
       this.loadUseCase()
+      this.loadItemClass()
       this.loadFromDraft()
       this.loadFromDraft()
       uni.setNavigationBarTitle({
       uni.setNavigationBarTitle({
         title: this.editId ? '编辑导航项' : '新增导航项'
         title: this.editId ? '编辑导航项' : '新增导航项'
@@ -257,6 +308,9 @@ export default {
         this.form = { ...EMPTY_FORM(), id: this.editId || '' }
         this.form = { ...EMPTY_FORM(), id: this.editId || '' }
       }
       }
     },
     },
+    /**
+     * 规范化编辑项;type 仅接受 3 分类 / 4 场景 / 5 花材分类,其余回退分类
+     */
     normalizeForm(item) {
     normalizeForm(item) {
       return {
       return {
         id: String(item.id || ''),
         id: String(item.id || ''),
@@ -264,10 +318,20 @@ export default {
         icon: item.icon || '',
         icon: item.icon || '',
         iconType: parseInt(item.iconType, 10) === 2 ? 2 : 1,
         iconType: parseInt(item.iconType, 10) === 2 ? 2 : 1,
         enabled: item.enabled == 1 ? 1 : 0,
         enabled: item.enabled == 1 ? 1 : 0,
-        type: parseInt(item.type, 10) === 4 ? 4 : 3,
+        type: this.normalizeNavType(item.type),
         value: item.value ? String(item.value) : ''
         value: item.value ? String(item.value) : ''
       }
       }
     },
     },
+    /**
+     * 把草稿/接口里的关联类型收敛到合法取值,避免旧数据把花材分类误当成花束分类
+     */
+    normalizeNavType(type) {
+      const t = parseInt(type, 10)
+      if (t === TYPE_USE_CASE || t === TYPE_ITEM_CLASS) {
+        return t
+      }
+      return TYPE_CATEGORY
+    },
     loadCategory() {
     loadCategory() {
       getClass().then((res) => {
       getClass().then((res) => {
         if (res.code == 1) {
         if (res.code == 1) {
@@ -282,6 +346,27 @@ export default {
         }
         }
       })
       })
     },
     },
+    /**
+     * 拉取全部花材分类(item-class/all),供关联类型=花材分类时单选
+     */
+    loadItemClass() {
+      getAllItemClass().then((res) => {
+        if (res.code != 1) {
+          this.itemClassData = []
+          return
+        }
+        const data = res.data
+        if (Array.isArray(data)) {
+          this.itemClassData = data
+        } else if (data && Array.isArray(data.list)) {
+          this.itemClassData = data.list
+        } else {
+          this.itemClassData = []
+        }
+      }).catch(() => {
+        this.itemClassData = []
+      })
+    },
     parseIds(value) {
     parseIds(value) {
       if (!value) return []
       if (!value) return []
       return String(value).split(',').filter(Boolean)
       return String(value).split(',').filter(Boolean)
@@ -300,16 +385,27 @@ export default {
       if (this.form.type === type) return
       if (this.form.type === type) return
       this.form.type = type
       this.form.type = type
       this.form.value = ''
       this.form.value = ''
-      this.categoryPanelShow = type === 3
-      this.useCasePanelShow = type === 4
+      this.categoryPanelShow = type === TYPE_CATEGORY
+      this.useCasePanelShow = type === TYPE_USE_CASE
+      this.itemClassPanelShow = type === TYPE_ITEM_CLASS
     },
     },
     toggleCategoryPanel() {
     toggleCategoryPanel() {
       this.categoryPanelShow = !this.categoryPanelShow
       this.categoryPanelShow = !this.categoryPanelShow
       this.useCasePanelShow = false
       this.useCasePanelShow = false
+      this.itemClassPanelShow = false
     },
     },
     toggleUseCasePanel() {
     toggleUseCasePanel() {
       this.useCasePanelShow = !this.useCasePanelShow
       this.useCasePanelShow = !this.useCasePanelShow
       this.categoryPanelShow = false
       this.categoryPanelShow = false
+      this.itemClassPanelShow = false
+    },
+    /**
+     * 展开/收起花材分类单选面板
+     */
+    toggleItemClassPanel() {
+      this.itemClassPanelShow = !this.itemClassPanelShow
+      this.categoryPanelShow = false
+      this.useCasePanelShow = false
     },
     },
     isCategoryChecked(id) {
     isCategoryChecked(id) {
       return this.parseIds(this.form.value).indexOf(String(id)) > -1
       return this.parseIds(this.form.value).indexOf(String(id)) > -1
@@ -317,6 +413,9 @@ export default {
     isUseCaseChecked(id) {
     isUseCaseChecked(id) {
       return this.parseIds(this.form.value).indexOf(String(id)) > -1
       return this.parseIds(this.form.value).indexOf(String(id)) > -1
     },
     },
+    isItemClassChecked(id) {
+      return String(this.form.value || '') === String(id)
+    },
     toggleCategoryId(id) {
     toggleCategoryId(id) {
       const key = String(id)
       const key = String(id)
       let ids = this.parseIds(this.form.value)
       let ids = this.parseIds(this.form.value)
@@ -339,6 +438,13 @@ export default {
       }
       }
       this.form.value = ids.join(',')
       this.form.value = ids.join(',')
     },
     },
+    /**
+     * 花材分类单选:点已选项可取消,保存时仍会校验必填
+     */
+    selectItemClassId(id) {
+      const key = String(id)
+      this.form.value = this.form.value === key ? '' : key
+    },
     chooseImage() {
     chooseImage() {
       if (this.uploading) return
       if (this.uploading) return
       uni.chooseImage({
       uni.chooseImage({
@@ -519,6 +625,7 @@ export default {
   background: #fff;
   background: #fff;
   margin-right: 12upx;
   margin-right: 12upx;
   border-radius: 8upx;
   border-radius: 8upx;
+  white-space: nowrap;
 
 
   &:last-child {
   &:last-child {
     margin-right: 0;
     margin-right: 0;
@@ -531,6 +638,10 @@ export default {
   }
   }
 }
 }
 
 
+.type-tab {
+  font-size: 24upx;
+}
+
 .preset-grid {
 .preset-grid {
   @include disFlex(flex-start, flex-start);
   @include disFlex(flex-start, flex-start);
   flex-wrap: wrap;
   flex-wrap: wrap;
@@ -688,6 +799,22 @@ export default {
   }
   }
 }
 }
 
 
+/* 花材分类单选:圆形,与花束分类多选方框区分 */
+.radio-box {
+  width: 34upx;
+  height: 34upx;
+  border: 2upx solid #ccc;
+  border-radius: 50%;
+  margin-right: 12upx;
+  @include disFlex(center, center);
+  flex-shrink: 0;
+
+  &.checked {
+    background: #09C567;
+    border-color: #09C567;
+  }
+}
+
 .check-mark {
 .check-mark {
   color: #fff;
   color: #fff;
   font-size: 22upx;
   font-size: 22upx;

+ 8 - 2
hdApp/src/admin/homePageConfig/previewParts/navGrid.vue

@@ -15,15 +15,21 @@
 </template>
 </template>
 
 
 <script>
 <script>
+/** 默认图标库数量:须与后端 HomePageModuleClass::NAV_PRESET_ICON_COUNT 一致 */
+const NAV_PRESET_ICON_COUNT = 29
 /** 默认图标库相对路径前缀,与后台编辑页一致 */
 /** 默认图标库相对路径前缀,与后台编辑页一致 */
 const PRESET_ICON_PATH_PREFIX = 'hhb/images/navGrid-default/navGrid-default'
 const PRESET_ICON_PATH_PREFIX = 'hhb/images/navGrid-default/navGrid-default'
 
 
-/** 由 preset:N 解析 OSS 相对路径 */
+/**
+ * 由 preset:N 解析 OSS 相对路径;超出图库数量返回空,避免预览区图标空白
+ * @param {string} icon 如 preset:13
+ * @returns {string}
+ */
 function getPresetIconPath(icon) {
 function getPresetIconPath(icon) {
   const m = String(icon || '').match(/^preset:(\d+)$/)
   const m = String(icon || '').match(/^preset:(\d+)$/)
   if (!m) return ''
   if (!m) return ''
   const n = parseInt(m[1], 10)
   const n = parseInt(m[1], 10)
-  if (n < 1 || n > 12) return ''
+  if (n < 1 || n > NAV_PRESET_ICON_COUNT) return ''
   return `${PRESET_ICON_PATH_PREFIX}${n}.png`
   return `${PRESET_ICON_PATH_PREFIX}${n}.png`
 }
 }
 
 

+ 31 - 7
mallApp/src/components/home/navGrid.vue

@@ -1,7 +1,7 @@
 <!--
 <!--
   店铺首页-分类金刚区导航
   店铺首页-分类金刚区导航
   按 cols(4/5) 渲染图标网格;默认图标库 preset:N 映射 OSS 静态图,上传图标用后端 iconUrl;
   按 cols(4/5) 渲染图标网格;默认图标库 preset:N 映射 OSS 静态图,上传图标用后端 iconUrl;
-  点击导航项跳转商品列表页,并按关联类型(分类/场景)带上初始筛选条件
+  点击导航项:花束分类/场景跳转商品列表并带筛选;花材分类跳转店铺分类页并定位到对应花材分类
 -->
 -->
 <template>
 <template>
   <view v-if="data && data.enabled == 1 && data.list && data.list.length" class="home-nav-grid" :class="'cols-' + (data.cols || 5)">
   <view v-if="data && data.enabled == 1 && data.list && data.list.length" class="home-nav-grid" :class="'cols-' + (data.cols || 5)">
@@ -18,15 +18,25 @@
 import { parseFilterIds, encodeFilterValue, stashGoodsListFilter } from '@/utils/goodsListFilter'
 import { parseFilterIds, encodeFilterValue, stashGoodsListFilter } from '@/utils/goodsListFilter'
 import AppLazyImg from '@/components/app-lazy-img.vue'
 import AppLazyImg from '@/components/app-lazy-img.vue'
 
 
+/** 默认图标库数量:须与后端 HomePageModuleClass::NAV_PRESET_ICON_COUNT 一致 */
+const NAV_PRESET_ICON_COUNT = 29
 /** 默认图标库相对路径前缀,与 hdApp 后台 navGrid 编辑页一致 */
 /** 默认图标库相对路径前缀,与 hdApp 后台 navGrid 编辑页一致 */
 const PRESET_ICON_PATH_PREFIX = 'hhb/images/navGrid-default/navGrid-default'
 const PRESET_ICON_PATH_PREFIX = 'hhb/images/navGrid-default/navGrid-default'
+/** 关联类型:与后端 HomePageModuleClass 一致;3 花束分类 / 4 场景 / 5 花材分类 */
+const TYPE_CATEGORY = 3
+const TYPE_USE_CASE = 4
+const TYPE_ITEM_CLASS = 5
 
 
-/** 由 preset:N 解析 OSS 相对路径;非法返回空 */
+/**
+ * 由 preset:N 解析 OSS 相对路径;非法或超出图库数量返回空
+ * @param {string} icon 如 preset:13
+ * @returns {string}
+ */
 function getPresetIconPath(icon) {
 function getPresetIconPath(icon) {
   const m = String(icon || '').match(/^preset:(\d+)$/)
   const m = String(icon || '').match(/^preset:(\d+)$/)
   if (!m) return ''
   if (!m) return ''
   const n = parseInt(m[1], 10)
   const n = parseInt(m[1], 10)
-  if (n < 1 || n > 12) return ''
+  if (n < 1 || n > NAV_PRESET_ICON_COUNT) return ''
   return `${PRESET_ICON_PATH_PREFIX}${n}.png`
   return `${PRESET_ICON_PATH_PREFIX}${n}.png`
 }
 }
 
 
@@ -74,17 +84,31 @@ export default {
       return item.iconUrl || this.iconFullUrl(item.icon)
       return item.iconUrl || this.iconFullUrl(item.icon)
     },
     },
     /**
     /**
-     * 点击金刚区项:跳转商品列表,并按关联类型带上初始筛选
-     * type=3 分类 → filterType=3;type=4 场景 → filterType=4
+     * 点击金刚区项:
+     * type=3 花束分类 / type=4 场景 → 商品列表并带筛选
+     * type=5 花材分类 → 店铺分类页并定位到该花材分类
      * 多 ID 不用逗号拼 URL(微信会截断),改用竖线,并写入缓存供列表页勾选高亮
      * 多 ID 不用逗号拼 URL(微信会截断),改用竖线,并写入缓存供列表页勾选高亮
      */
      */
     onTap(item) {
     onTap(item) {
       const account = this.account || ''
       const account = this.account || ''
       const hdId = this.hdId || ''
       const hdId = this.hdId || ''
-      let url = `/pages/goods/list?account=${account}&hdId=${hdId}`
       const type = parseInt(item && item.type, 10)
       const type = parseInt(item && item.type, 10)
       const ids = parseFilterIds(item && item.value)
       const ids = parseFilterIds(item && item.value)
-      if ((type === 3 || type === 4) && ids.length) {
+      // 花材分类:与底部「分类」Tab 一样用 redirectTo,避免首页上再叠一层带 Tab 的分类页
+      if (type === TYPE_ITEM_CLASS && ids.length) {
+        this.pageTo({
+          url: '/pages/home/shop-category',
+          type: 2,
+          query: {
+            account,
+            hdId,
+            itemClassId: ids[0]
+          }
+        })
+        return
+      }
+      let url = `/pages/goods/list?account=${account}&hdId=${hdId}`
+      if ((type === TYPE_CATEGORY || type === TYPE_USE_CASE) && ids.length) {
         stashGoodsListFilter(type, ids)
         stashGoodsListFilter(type, ids)
         url += `&filterType=${type}&filterValue=${encodeFilterValue(ids)}`
         url += `&filterType=${type}&filterValue=${encodeFilterValue(ids)}`
       }
       }

+ 45 - 3
mallApp/src/pages/home/shop-category.vue

@@ -4,6 +4,7 @@
   顶部搜索框点搜索/键盘确认后跳转商品列表页,并带上 searchText 关键词
   顶部搜索框点搜索/键盘确认后跳转商品列表页,并带上 searchText 关键词
   未登录:可浏览分类与商品列表,不弹登录;公告详情/列表、加购与去结算弹 wangCg
   未登录:可浏览分类与商品列表,不弹登录;公告详情/列表、加购与去结算弹 wangCg
   路由:pages/home/shop-category?account=xxx&hdId=xxx
   路由:pages/home/shop-category?account=xxx&hdId=xxx
+  首页金刚区花材分类:额外带 itemClassId,进入后展开花材并选中该分类
 -->
 -->
 <template>
 <template>
   <view class="shop-category-page">
   <view class="shop-category-page">
@@ -63,7 +64,13 @@
     <!-- 主体:左侧分类 + 右侧商品 -->
     <!-- 主体:左侧分类 + 右侧商品 -->
     <view class="main-body">
     <view class="main-body">
       <!-- 左侧分类导航:花材区与花束区分开 -->
       <!-- 左侧分类导航:花材区与花束区分开 -->
-      <scroll-view scroll-y class="side-nav" :show-scrollbar="false">
+      <scroll-view
+        scroll-y
+        class="side-nav"
+        :show-scrollbar="false"
+        :scroll-into-view="sideNavIntoView"
+        scroll-with-animation
+      >
         <!-- 花材置顶 -->
         <!-- 花材置顶 -->
         <block v-if="showItemCate && itemCateTop">
         <block v-if="showItemCate && itemCateTop">
           <view
           <view
@@ -88,6 +95,7 @@
               <view
               <view
                 v-for="cls in normalItemClasses"
                 v-for="cls in normalItemClasses"
                 :key="cls.classId"
                 :key="cls.classId"
+                :id="itemNavDomId(cls.classId)"
                 class="item-cate-sub"
                 class="item-cate-sub"
                 :class="{ 'item-cate-sub--active': isItemNavActive(cls.classId) }"
                 :class="{ 'item-cate-sub--active': isItemNavActive(cls.classId) }"
                 :data-id="cls.classId"
                 :data-id="cls.classId"
@@ -138,6 +146,7 @@
               <view
               <view
                 v-for="cls in normalItemClasses"
                 v-for="cls in normalItemClasses"
                 :key="cls.classId"
                 :key="cls.classId"
+                :id="itemNavDomId(cls.classId)"
                 class="item-cate-sub"
                 class="item-cate-sub"
                 :class="{ 'item-cate-sub--active': isItemNavActive(cls.classId) }"
                 :class="{ 'item-cate-sub--active': isItemNavActive(cls.classId) }"
                 :data-id="cls.classId"
                 :data-id="cls.classId"
@@ -411,7 +420,11 @@ export default {
       currentNoticeIndex: 0,
       currentNoticeIndex: 0,
       adList: [],
       adList: [],
       currentAdIndex: 0,
       currentAdIndex: 0,
-      form: { categoryId: '' }
+      form: { categoryId: '' },
+      /** 首页金刚区传入的花材分类 id,数据就绪后定位一次即清空 */
+      pendingLocateItemClassId: '',
+      /** 左侧分类滚动定位锚点(对应 item-nav-{classId}) */
+      sideNavIntoView: ''
     }
     }
   },
   },
   computed: {
   computed: {
@@ -507,6 +520,11 @@ export default {
       this.shopAccount = this.option.account
       this.shopAccount = this.option.account
         ? String(this.option.account)
         ? String(this.option.account)
         : (uni.getStorageSync('account') || '')
         : (uni.getStorageSync('account') || '')
+      // 首页金刚区花材分类:带 itemClassId 进入后展开并选中对应分类
+      this.pendingLocateItemClassId = this.option.itemClassId
+        ? String(this.option.itemClassId)
+        : ''
+      this.sideNavIntoView = ''
       // 与 item 页一致:先写入 account / hdId,供 item/index 等接口使用
       // 与 item 页一致:先写入 account / hdId,供 item/index 等接口使用
       if (this.shopAccount) {
       if (this.shopAccount) {
         uni.setStorageSync('account', this.shopAccount)
         uni.setStorageSync('account', this.shopAccount)
@@ -739,8 +757,26 @@ export default {
         imgUrl: item.img ? (item.img.indexOf('http') === 0 ? item.img : `${base}/${item.img.replace(/^\//, '')}`) : ''
         imgUrl: item.img ? (item.img.indexOf('http') === 0 ? item.img : `${base}/${item.img.replace(/^\//, '')}`) : ''
       })).filter((item) => item.imgUrl)
       })).filter((item) => item.imgUrl)
     },
     },
-    /** 首次选中:花材置顶时优先第一个花材分类;否则有花束选花束;仅花材时选第一个花材 */
+    /**
+     * 首次选中:若从首页金刚区带了 itemClassId 且分类存在,则展开花材并定位;
+     * 否则花材置顶时优先第一个花材分类;再否则有花束选花束;仅花材时选第一个花材
+     */
     initDefaultNav() {
     initDefaultNav() {
+      const locateId = this.pendingLocateItemClassId
+      this.pendingLocateItemClassId = ''
+      if (locateId) {
+        const found = this.itemClassList.some((c) => String(c.classId) === String(locateId))
+        if (found) {
+          this.itemExpanded = true
+          this.selectNav('item', String(locateId))
+          this.$nextTick(() => {
+            this.$nextTick(() => {
+              this.sideNavIntoView = this.itemNavDomId(locateId)
+            })
+          })
+          return
+        }
+      }
       const items = this.normalItemClasses
       const items = this.normalItemClasses
       if (this.showItemCate && this.itemCateTop && items.length) {
       if (this.showItemCate && this.itemCateTop && items.length) {
         this.selectNav('item', String(items[0].classId))
         this.selectNav('item', String(items[0].classId))
@@ -754,6 +790,12 @@ export default {
         this.selectNav('item', String(items[0].classId))
         this.selectNav('item', String(items[0].classId))
       }
       }
     },
     },
+    /**
+     * 左侧花材分类节点 id,供 scroll-into-view 定位(不能以数字开头)
+     */
+    itemNavDomId(classId) {
+      return 'item-nav-' + classId
+    },
     isItemNavActive(classId) {
     isItemNavActive(classId) {
       return this.activeNav.type === 'item' && String(this.activeNav.id) === String(classId)
       return this.activeNav.type === 'item' && String(this.activeNav.id) === String(classId)
     },
     },