Эх сурвалжийг харах

feat: update: 修复部分组件兼容问题

叶静 1 долоо хоног өмнө
parent
commit
f8762e56f8
37 өөрчлөгдсөн 2185 нэмэгдсэн , 1309 устгасан
  1. BIN
      public/static/images/shop/decorate/notice-1.png
  2. BIN
      public/static/images/shop/decorate/notice-2.png
  3. BIN
      public/static/images/shop/decorate/notice-3.png
  4. 68 39
      src/app/shop/admin/data/page/select.vue
  5. 8 15
      src/app/shop/admin/decorate/page/component/center/basic/floatMenu/setting.vue
  6. 3 2
      src/app/shop/admin/decorate/page/component/center/basic/guidePage/setting.vue
  7. 10 12
      src/app/shop/admin/decorate/page/component/center/basic/popupImage/setting.vue
  8. 4 3
      src/app/shop/admin/decorate/page/component/center/basic/splashScreen/setting.vue
  9. 16 16
      src/app/shop/admin/decorate/page/component/center/basic/tabbar/setting.vue
  10. 56 7
      src/app/shop/admin/decorate/page/component/center/common/dc-goods-select.vue
  11. 411 0
      src/app/shop/admin/decorate/page/component/center/common/decorate-goods-select.vue
  12. 141 0
      src/app/shop/admin/decorate/page/component/center/common/decorate-product-card.vue
  13. 119 0
      src/app/shop/admin/decorate/page/component/center/common/grid-product-card.vue
  14. 2 1
      src/app/shop/admin/decorate/page/component/center/comp/coupon/setting.vue
  15. 81 87
      src/app/shop/admin/decorate/page/component/center/comp/goodsCard/index.vue
  16. 115 51
      src/app/shop/admin/decorate/page/component/center/comp/goodsCard/setting.vue
  17. 161 92
      src/app/shop/admin/decorate/page/component/center/comp/goodsShelves/index.vue
  18. 118 23
      src/app/shop/admin/decorate/page/component/center/comp/goodsShelves/setting.vue
  19. 4 2
      src/app/shop/admin/decorate/page/component/center/comp/groupon/setting.vue
  20. 23 29
      src/app/shop/admin/decorate/page/component/center/comp/hotzone/setting.vue
  21. 10 10
      src/app/shop/admin/decorate/page/component/center/comp/imageBanner/setting.vue
  22. 4 4
      src/app/shop/admin/decorate/page/component/center/comp/imageBlock/setting.vue
  23. 172 180
      src/app/shop/admin/decorate/page/component/center/comp/imageCube/setting.vue
  24. 24 35
      src/app/shop/admin/decorate/page/component/center/comp/menuButton/setting.vue
  25. 29 45
      src/app/shop/admin/decorate/page/component/center/comp/menuGrid/setting.vue
  26. 21 33
      src/app/shop/admin/decorate/page/component/center/comp/menuList/setting.vue
  27. 36 18
      src/app/shop/admin/decorate/page/component/center/comp/noticeBlock/index.vue
  28. 68 48
      src/app/shop/admin/decorate/page/component/center/comp/noticeBlock/setting.vue
  29. 27 48
      src/app/shop/admin/decorate/page/component/center/comp/scoreGoods/index.vue
  30. 2 1
      src/app/shop/admin/decorate/page/component/center/comp/scoreGoods/setting.vue
  31. 4 2
      src/app/shop/admin/decorate/page/component/center/comp/seckill/setting.vue
  32. 5 7
      src/app/shop/admin/decorate/page/component/center/comp/subscribeWechatOfficialAccount/setting.vue
  33. 15 14
      src/app/shop/admin/decorate/page/component/center/comp/videoPlayer/setting.vue
  34. 235 263
      src/app/shop/admin/decorate/page/component/center/page/setting.vue
  35. 125 186
      src/app/shop/admin/decorate/page/component/right/index.vue
  36. 53 34
      src/app/shop/admin/decorate/page/data.js
  37. 15 2
      src/app/shop/admin/decorate/template/index.vue

BIN
public/static/images/shop/decorate/notice-1.png


BIN
public/static/images/shop/decorate/notice-2.png


BIN
public/static/images/shop/decorate/notice-3.png


+ 68 - 39
src/app/shop/admin/data/page/select.vue

@@ -10,21 +10,23 @@
         <el-aside>
           <div class="left">
             <div class="group" :class="currentGroupIndex == i ? 'is-active' : ''" v-for="(g, i) in pageGroups" :key="i"
-              @click="currentGroupIndex = i">
+              @click="switchGroup(i)">
               <div class="name">{{ g.group }}</div>
             </div>
           </div>
         </el-aside>
         <div class="right">
-          <div class="group" v-for="(g, i) in pageGroups" :key="i" v-show="currentGroupIndex === i">
-            <div class="group-title">{{ g.group }}</div>
-            <div class="link sa-flex sa-flex-wrap">
-              <div class="item" :class="selectedPage.path == page.path ? 'item-active' : ''" v-for="page in g.children"
-                :key="page.path" @click="selectPage(page)">
-                {{ page.name }}
+          <template v-for="(g, i) in pageGroups" :key="i">
+            <div class="group" v-if="currentGroupIndex === i">
+              <div class="group-title">{{ g.group }} (当前索引: {{ currentGroupIndex }})</div>
+              <div class="link sa-flex sa-flex-wrap">
+                <div class="item" :class="selectedPage.path == page.path ? 'item-active' : ''"
+                  v-for="page in g.children" :key="page.path" @click="selectPage(page)">
+                  {{ page.name }}
+                </div>
               </div>
             </div>
-          </div>
+          </template>
         </div>
       </el-container>
     </el-main>
@@ -48,58 +50,85 @@ const selectedPage = ref({});
 // 根据系统实际模块配置的页面路由
 const pageGroups = [
   {
-    group: '商城页面',
+    group: '主要页面',
     children: [
       { name: '首页', path: '/pages/index/index' },
-      { name: '商品分类', path: '/pages/index/category' },
-      { name: '购物车', path: '/pages/index/cart' },
-      { name: '商品列表', path: '/pages/goods/list' },
-      { name: '商品详情', path: '/pages/goods/detail' },
-      { name: '商品搜索', path: '/pages/goods/search' },
+      { name: '收益中心', path: '/pages/income/income' },
+      { name: '个人中心', path: '/pages/mine/mine' },
+      { name: '商品详情', path: '/pages/productDetail/productDetail?productId=xxx' },
+      { name: '搜索', path: '/pages/search/search' },
+    ],
+  },
+  {
+    group: '用户相关',
+    children: [
+      { name: '登录', path: '/pages/login/login' },
+      { name: '注册', path: '/pages/register/register' },
+      { name: '忘记密码', path: '/pages/forgotPassword/forgotPassword' },
+      { name: '个人资料', path: '/pages/mine/myProfile' },
+      { name: '设置', path: '/pages/mine/setting' },
+      { name: '分享', path: '/pages/mine/share' },
+      { name: '我的收藏', path: '/pages/mine/myFavorite' },
     ],
   },
   {
-    group: '用户中心',
+    group: '订单相关',
     children: [
-      { name: '个人中心', path: '/pages/user/index' },
-      { name: '个人资料', path: '/pages/user/profile' },
-      { name: '我的订单', path: '/pages/order/list' },
-      { name: '订单详情', path: '/pages/order/detail' },
-      { name: '收货地址', path: '/pages/user/address' },
-      { name: '我的收藏', path: '/pages/user/collect' },
-      { name: '浏览足迹', path: '/pages/user/history' },
-      { name: '账户余额', path: '/pages/user/wallet' },
+      { name: '我的订单', path: '/pages/myOrders/myOrders' },
+      { name: '订单详情', path: '/pages/myOrders/orderDetail' },
+      { name: '结算', path: '/pages/productDetail/checkOut' },
     ],
   },
   {
-    group: '营销活动',
+    group: '钱包相关',
     children: [
-      { name: '优惠券列表', path: '/pages/coupon/list' },
-      { name: '优惠券详情', path: '/pages/coupon/detail' },
-      { name: '拼团活动', path: '/pages/marketing/group' },
+      { name: '我的钱包', path: '/pages/wallet/myWallet' },
+      { name: '充值', path: '/pages/wallet/recharge' },
+      { name: '提现', path: '/pages/wallet/withdraw' },
+      { name: '充值记录', path: '/pages/wallet/rechargeRecord' },
+      { name: '提现记录', path: '/pages/wallet/withdrawRecord' },
+      { name: '冻结记录', path: '/pages/wallet/frozenRecord' },
     ],
   },
   {
-    group: '内容页面',
+    group: '地址管理',
     children: [
-      { name: '公告列表', path: '/pages/content/notification' },
-      { name: '公告详情', path: '/pages/content/notification-detail' },
-      { name: '广告页面', path: '/pages/content/adv' },
-      { name: '帮助中心', path: '/pages/content/help' },
-      { name: '问答列表', path: '/pages/content/qa' },
-      { name: '富文本页面', path: '/pages/public/richtext' },
+      { name: '地址簿', path: '/pages/mine/addressBook' },
+      { name: '地址操作', path: '/pages/mine/addressBookOperate' },
     ],
   },
   {
-    group: '其他',
+    group: '活动页面',
     children: [
-      { name: '客服中心', path: '/pages/chat/index' },
-      { name: '关于我们', path: '/pages/public/about' },
-      { name: '自定义页面', path: '/pages/index/page' },
+      { name: '任务中心', path: '/pages/missionCenter/missionCenter' },
+      { name: '推荐赚钱', path: '/pages/referEarn/referEarn' },
+      { name: 'VIP会员', path: '/pages/vipMembership/vipMembership' },
+      { name: '热销商品', path: '/pages/bestSellers/bestSellers' },
+      { name: '冠军榜', path: '/pages/topChampions/topChampions' },
+    ],
+  },
+  {
+    group: '其他页面',
+    children: [
+      { name: '帮助中心', path: '/pages/mine/helpCenter' },
+      { name: '帮助详情', path: '/pages/mine/helpCenterDetail' },
+      { name: '通知', path: '/pages/notifications/notifications' },
+      { name: '网页链接', path: '/pages/webLink/webLink' },
     ],
   },
 ];
 
+function switchGroup(index) {
+  console.log('切换到分组:', index, pageGroups[index].group);
+  console.log('切换前 currentGroupIndex:', currentGroupIndex.value);
+  currentGroupIndex.value = index;
+  console.log('切换后 currentGroupIndex:', currentGroupIndex.value);
+  // 强制触发响应式更新
+  setTimeout(() => {
+    console.log('延迟检查 currentGroupIndex:', currentGroupIndex.value);
+  }, 100);
+}
+
 function selectPage(page) {
   selectedPage.value = { ...page };
 }
@@ -197,4 +226,4 @@ function confirm() {
     }
   }
 }
-</style>
+</style>

+ 8 - 15
src/app/shop/admin/decorate/page/component/center/basic/floatMenu/setting.vue

@@ -23,25 +23,18 @@
         </el-form-item>
       </div>
     </div>
-    <dc-list
-      v-model="settingData.list"
-      :itemProp="{ src: '', url: '', title: { text: '', color: '' } }"
-    >
+    <dc-list v-model="settingData.list" :itemProp="{ src: '', url: '', title: { text: '', color: '' } }">
       <template #title>功能图标</template>
       <template #listItem="{ element }">
         <el-form-item label="按钮图片">
           <div class="sa-flex">
-            <sa-uploader v-model="element.src" fileType="image"></sa-uploader>
+            <sa-upload-image v-model="element.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
+              :direct-upload="true" :size="100" />
             <span class="tip">建议尺寸:80*80</span>
           </div>
         </el-form-item>
         <el-form-item label="按钮名称">
-          <dc-text-color
-            v-model="element.title"
-            maxlength="4"
-            showWordLimit
-            placeholder="请输入按钮名称"
-          ></dc-text-color>
+          <dc-text-color v-model="element.title" maxlength="4" showWordLimit placeholder="请输入按钮名称"></dc-text-color>
         </el-form-item>
         <el-form-item label="按钮链接">
           <dc-url v-model="element.url"></dc-url>
@@ -52,9 +45,9 @@
 </template>
 
 <script setup>
-  import dcList from '../../common/dc-list.vue';
-  import dcUrl from '../../common/dc-url.vue';
-  import dcTextColor from '../../common/dc-text-color.vue';
+import dcList from '../../common/dc-list.vue';
+import dcUrl from '../../common/dc-url.vue';
+import dcTextColor from '../../common/dc-text-color.vue';
 
-  const props = defineProps(['settingData']);
+const props = defineProps(['settingData']);
 </script>

+ 3 - 2
src/app/shop/admin/decorate/page/component/center/basic/guidePage/setting.vue

@@ -13,7 +13,8 @@
           </el-radio-group>
         </el-form-item>
         <el-form-item label="上传图片">
-          <sa-uploader v-model="settingData.list" :multiple="true" fileType="image"></sa-uploader>
+          <sa-upload-image v-model="settingData.list" :max-count="10" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
+            :direct-upload="true" :size="100" />
         </el-form-item>
       </div>
     </div>
@@ -21,5 +22,5 @@
 </template>
 
 <script setup>
-  const props = defineProps(['settingData']);
+const props = defineProps(['settingData']);
 </script>

+ 10 - 12
src/app/shop/admin/decorate/page/component/center/basic/popupImage/setting.vue

@@ -11,18 +11,16 @@
         </el-form-item>
       </div>
     </div> -->
-    <dc-list
-      v-model="settingData.list"
-      :itemProp="{
-        src: '',
-        url: '',
-        show: 1,
-      }"
-    >
+    <dc-list v-model="settingData.list" :itemProp="{
+      src: '',
+      url: '',
+      show: 1,
+    }">
       <template #title>展示图标</template>
       <template #listItem="{ element }">
         <el-form-item label="广告图">
-          <sa-uploader v-model="element.src" fileType="image"></sa-uploader>
+          <sa-upload-image v-model="element.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
+            :direct-upload="true" :size="100" />
         </el-form-item>
         <el-form-item label="选择链接">
           <dc-url v-model="element.url"></dc-url>
@@ -39,8 +37,8 @@
 </template>
 
 <script setup>
-  import dcList from '../../common/dc-list.vue';
-  import dcUrl from '../../common/dc-url.vue';
+import dcList from '../../common/dc-list.vue';
+import dcUrl from '../../common/dc-url.vue';
 
-  const props = defineProps(['settingData']);
+const props = defineProps(['settingData']);
 </script>

+ 4 - 3
src/app/shop/admin/decorate/page/component/center/basic/splashScreen/setting.vue

@@ -10,7 +10,8 @@
           </el-radio-group>
         </el-form-item>
         <el-form-item label="上传图片">
-          <sa-uploader v-model="settingData.src" fileType="image"></sa-uploader>
+          <sa-upload-image v-model="settingData.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
+            :direct-upload="true" :size="100" />
         </el-form-item>
         <el-form-item label="跳过时间">
           <el-input v-model="settingData.countdown">
@@ -26,7 +27,7 @@
 </template>
 
 <script setup>
-  import dcUrl from '../../common/dc-url.vue';
+import dcUrl from '../../common/dc-url.vue';
 
-  const props = defineProps(['settingData']);
+const props = defineProps(['settingData']);
 </script>

+ 16 - 16
src/app/shop/admin/decorate/page/component/center/basic/tabbar/setting.vue

@@ -29,27 +29,26 @@
           </el-form-item>
         </div>
       </div>
-      <dc-list
-        v-model="settingData.list"
-        :itemProp="{
-          inactiveIcon: '',
-          activeIcon: '',
-          url: '',
-          text: '',
-        }"
-      >
+      <dc-list v-model="settingData.list" :itemProp="{
+        inactiveIcon: '',
+        activeIcon: '',
+        url: '',
+        text: '',
+      }">
         <template #title>图标设置</template>
         <template #listItem="{ element }">
           <template v-if="settingData.layout == 1 || settingData.layout == 3">
             <el-form-item label="默认图片">
               <div class="sa-flex">
-                <sa-uploader v-model="element.inactiveIcon" fileType="image"></sa-uploader>
+                <sa-upload-image v-model="element.inactiveIcon" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+                  :max-size="5" :direct-upload="true" :size="100" />
                 <span class="tip">建议尺寸:44*44</span>
               </div>
             </el-form-item>
             <el-form-item label="选中图片">
               <div class="sa-flex">
-                <sa-uploader v-model="element.activeIcon" fileType="image"></sa-uploader>
+                <sa-upload-image v-model="element.activeIcon" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+                  :max-size="5" :direct-upload="true" :size="100" />
                 <span class="tip">建议尺寸:44*44</span>
               </div>
             </el-form-item>
@@ -78,7 +77,8 @@
           </el-form-item>
           <el-form-item v-if="settingData.background.type == 'image'" label="选择图片">
             <div class="sa-flex">
-              <sa-uploader v-model="settingData.background.bgImage" fileType="image"></sa-uploader>
+              <sa-upload-image v-model="settingData.background.bgImage" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+                :max-size="5" :direct-upload="true" :size="100" />
             </div>
           </el-form-item>
         </div>
@@ -88,9 +88,9 @@
 </template>
 
 <script setup>
-  import dcList from '../../common/dc-list.vue';
-  import dcUrl from '../../common/dc-url.vue';
-  import dcColorPicker from '../../common/dc-color-picker.vue';
+import dcList from '../../common/dc-list.vue';
+import dcUrl from '../../common/dc-url.vue';
+import dcColorPicker from '../../common/dc-color-picker.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const props = defineProps(['settingData', 'tabType']);
 </script>

+ 56 - 7
src/app/shop/admin/decorate/page/component/center/common/dc-goods-select.vue

@@ -9,7 +9,7 @@
           @end="updateItem">
           <template #item="{ element, index }">
             <div class="goods-item">
-              <sa-image :url="type == 'goods' ? element.image : element.feeds_img" size="44"></sa-image>
+              <sa-image :url="getGoodsImage(element)" size="44"></sa-image>
               <div class="goods-delete" @click="deleteGoods(index)">
                 <el-icon>
                   <Delete />
@@ -39,7 +39,7 @@ export default {
 import { ref, watch } from 'vue';
 import SaDraggable from 'vuedraggable';
 import { useModal } from '@/sheep/hooks';
-import GoodsSelect from '@/app/shop/admin/goods/goods/select.vue';
+import DecorateGoodsSelect from './decorate-goods-select.vue';
 
 const emit = defineEmits(['update:modelValue']);
 const props = defineProps({
@@ -76,7 +76,7 @@ function addGoods() {
   });
   if (props.type == 'goods') {
     useModal(
-      GoodsSelect,
+      DecorateGoodsSelect,
       {
         title: '选择商品',
         multiple: props.multiple,
@@ -85,14 +85,50 @@ function addGoods() {
       },
       {
         confirm: (res) => {
-          listData.value.length = 0;
+          console.log('装修组件接收到的商品数据:', res.data);
+
           if (props.multiple) {
-            res.data.forEach((element) => {
-              listData.value.push(element);
+            // 多选模式:直接使用返回的完整商品数据
+            const selectedGoods = Array.isArray(res.data) ? res.data : [res.data];
+
+            console.log('处理后的商品数据:', selectedGoods);
+
+            // 清空当前列表并添加所有选中的商品
+            listData.value.length = 0;
+            selectedGoods.forEach((element) => {
+              // 确保每个商品都有完整的数据结构
+              const goodsItem = {
+                id: element.id,
+                title: element.title || element.storeName,
+                image: element.image,
+                price: element.price,
+                otPrice: element.otPrice,
+                stock: element.stock,
+                sales: element.sales || element.ficti || 0,
+                storeName: element.storeName || element.title,
+              };
+              listData.value.push(goodsItem);
             });
           } else {
-            listData.value.push(res.data);
+            // 单选模式
+            listData.value.length = 0;
+            const goodsItem = {
+              id: res.data.id,
+              title: res.data.title || res.data.storeName,
+              image: res.data.image,
+              price: res.data.price,
+              otPrice: res.data.otPrice,
+              stock: res.data.stock,
+              sales: res.data.sales || res.data.ficti || 0,
+              storeName: res.data.storeName || res.data.title,
+            };
+            listData.value.push(goodsItem);
           }
+
+          console.log('最终保存的商品数据:', listData.value);
+
+          // 触发更新
+          updateItem();
         },
       },
     );
@@ -124,6 +160,19 @@ function deleteGoods(index) {
 function updateItem() {
   emit('update:modelValue', listData.value);
 }
+
+// 获取商品图片URL
+function getGoodsImage(element) {
+  if (props.type === 'goods') {
+    // 商品类型:优先使用image字段,如果没有则尝试其他可能的字段
+    return element.image || element.goods_image || element.pic || '';
+  } else if (props.type === 'mplive') {
+    // 直播类型
+    return element.feeds_img || element.image || '';
+  }
+  // 默认返回image字段
+  return element.image || '';
+}
 </script>
 <style lang="scss" scoped>
 .dc-goods-select {

+ 411 - 0
src/app/shop/admin/decorate/page/component/center/common/decorate-goods-select.vue

@@ -0,0 +1,411 @@
+<template>
+  <el-container class="decorate-goods-select">
+    <el-container>
+      <el-header class="goods-search">
+        <sa-search-simple :searchFields="searchFields" :defaultValues="defaultSearchValues"
+          v-model="currentSearchParams" @search="handleSearch" @reset="handleReset" />
+      </el-header>
+      <el-main v-loading="loading">
+        <el-table class="sa-table" ref="multipleTableRef" :data="table.list" @select="selectRow" @select-all="selectAll"
+          stripe>
+          <template #empty>
+            <sa-empty />
+          </template>
+          <el-table-column v-if="modal.params.multiple" type="selection" width="48"></el-table-column>
+          <el-table-column prop="id" :label="t('modules.goods.goodsNumber')" align="center" width="80" />
+          <el-table-column :label="t('modules.goods.goodsInfo')" min-width="300">
+            <template #default="scope">
+              <div class="goods-card">
+                <el-image :src="scope.row.image" class="goods-image" fit="cover" />
+                <div class="goods-info">
+                  <div class="goods-title">{{ scope.row.storeName }}</div>
+                  <div class="goods-price-sales">
+                    <span class="price">৳{{ scope.row.price }}</span>
+                    <span class="sales">{{ scope.row.ficti || scope.row.sales || 0 }} Sold</span>
+                  </div>
+                </div>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column v-if="!modal.params.multiple" :label="t('common.actions')" width="80">
+            <template #default="scope">
+              <el-button link type="primary" @click="singleSelect(scope.row.id)">{{
+                t('common.select')
+              }}</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+      </el-main>
+      <el-footer class="sa-footer--submit" :class="modal.params.multiple ? 'sa-row-between' : 'sa-row-right'">
+        <sa-pagination :pageData="pageData" @updateFn="getData" />
+        <el-button v-if="modal.params.multiple" type="primary" @click="confirm">{{
+          t('common.confirm')
+        }}</el-button>
+      </el-footer>
+    </el-container>
+  </el-container>
+</template>
+
+<script setup>
+import { nextTick, onMounted, reactive, ref } from 'vue';
+import { api } from '@/app/shop/admin/goods/goods.service';
+import { ElMessage } from 'element-plus';
+import { usePagination } from '@/sheep/hooks';
+import { useI18n } from 'vue-i18n';
+
+const { t } = useI18n();
+const { pageData } = usePagination();
+
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps(['modal']);
+
+// 搜索字段配置
+const searchFields = reactive({
+  storeName: {
+    type: 'input',
+    get label() {
+      return t('modules.goods.goodsName');
+    },
+    get placeholder() {
+      return t('form.inputNameOrNumber');
+    },
+    width: 200,
+  },
+});
+
+// 默认搜索值
+const defaultSearchValues = reactive({
+  storeName: '',
+});
+
+// 当前搜索条件 - 使用 ref 支持双向绑定
+const currentSearchParams = ref({});
+
+const loading = ref(true);
+const table = reactive({
+  list: [],
+  ids: (props.modal.params.ids || []).map(id => Number(id)),
+  selectedGoods: new Map(),
+  preloadedGoods: new Map(),
+});
+
+async function getData(page, searchParams = null) {
+  if (page) pageData.page = page;
+  loading.value = true;
+
+  try {
+    const finalSearchParams = searchParams !== null ? searchParams : currentSearchParams.value;
+
+    const params = {
+      page: pageData.page,
+      size: pageData.size,
+      isShow: 1,
+      ...finalSearchParams,
+    };
+    const { code, data } = await api.goods.list(params);
+    if (code == '200') {
+      table.list = data.list || [];
+      pageData.page = data.pageNum;
+      pageData.size = data.pageSize;
+      pageData.total = data.total;
+
+      nextTick(() => {
+        setDefaultSelected();
+        initSelectedGoods();
+      });
+    }
+  } catch (error) {
+    console.error('获取商品列表失败:', error);
+    table.list = [];
+  } finally {
+    loading.value = false;
+  }
+}
+
+function handleSearch(searchParams) {
+  getData(1);
+}
+
+function handleReset() {
+  currentSearchParams.value = {};
+  getData(1, {});
+}
+
+function setDefaultSelected() {
+  if (!table.list || !Array.isArray(table.list)) {
+    return;
+  }
+
+  table.list.forEach((item) => {
+    const itemId = Number(item.id);
+    if (table.ids?.includes(itemId)) {
+      multipleTableRef.value?.toggleRowSelection(item, true);
+      toggleRowSelection('row', [item], item);
+    }
+  });
+}
+
+const multipleTableRef = ref();
+function selectRow(selection, row) {
+  const rowId = Number(row.id);
+  if (
+    !props.modal.params.max ||
+    (props.modal.params.max && props.modal.params.max > table.ids.length)
+  ) {
+    if (table.ids.includes(rowId)) {
+      let index = table.ids.findIndex((id) => id === rowId);
+      table.ids.splice(index, 1);
+      table.selectedGoods.delete(rowId);
+    } else {
+      table.ids.push(rowId);
+      addToSelectedGoods(row);
+    }
+  }
+  toggleRowSelection('row', selection, row);
+}
+
+function selectAll(selection) {
+  if (
+    !props.modal.params.max ||
+    (props.modal.params.max && props.modal.params.max > table.ids.length + selection.length)
+  ) {
+    if (selection.length == 0) {
+      table.list.forEach((l) => {
+        const lId = Number(l.id);
+        if (table.ids.includes(lId)) {
+          let index = table.ids.findIndex((id) => id === lId);
+          table.ids.splice(index, 1);
+          table.selectedGoods.delete(lId);
+        }
+      });
+    } else {
+      table.list.forEach((l) => {
+        const lId = Number(l.id);
+        if (!table.ids.includes(lId)) {
+          table.ids.push(lId);
+          addToSelectedGoods(l);
+        }
+      });
+    }
+  }
+  toggleRowSelection('all', selection);
+}
+
+function toggleRowSelection(type, selection, row) {
+  if (props.modal.params.max && props.modal.params.max < selection.length) {
+    if (type == 'row') {
+      multipleTableRef.value.toggleRowSelection(row, false);
+    } else if (type == 'all') {
+      multipleTableRef.value?.clearSelection();
+      table.list.forEach((l) => {
+        const lId = Number(l.id);
+        if (table.ids?.includes(lId)) {
+          multipleTableRef.value?.toggleRowSelection(l, true);
+        }
+      });
+    }
+    ElMessage({
+      type: 'warning',
+      message: t('message.selectionLimitReached'),
+    });
+    return false;
+  }
+}
+
+// 添加商品到选中数据集合 - 装修专用数据结构
+function addToSelectedGoods(item) {
+  const goodsData = {
+    id: Number(item.id),
+    title: item.storeName,
+    image: item.image,
+    price: item.price,
+    otPrice: item.otPrice,
+    stock: item.stock,
+    sales: item.ficti || item.sales || 0,
+    storeName: item.storeName,
+  };
+  table.selectedGoods.set(Number(item.id), goodsData);
+}
+
+function initSelectedGoods() {
+  table.list.forEach((item) => {
+    const itemId = Number(item.id);
+    if (table.ids.includes(itemId) && !table.selectedGoods.has(itemId)) {
+      addToSelectedGoods(item);
+    }
+  });
+}
+
+async function preloadSelectedGoods() {
+  if (!table.ids.length) {
+    console.log('没有需要预加载的商品ID');
+    return;
+  }
+
+  console.log('开始预加载商品数据,IDs:', table.ids);
+
+  try {
+    const promises = table.ids.map(async (id) => {
+      try {
+        const { code, data } = await api.goods.info(id);
+        if (code === '200' && data) {
+          const goodsData = {
+            id: Number(data.id),
+            title: data.storeName,
+            image: data.image,
+            price: data.price,
+            otPrice: data.otPrice,
+            stock: data.stock,
+            sales: data.ficti || data.sales || 0,
+            storeName: data.storeName,
+          };
+          table.selectedGoods.set(Number(id), goodsData);
+          table.preloadedGoods.set(Number(id), data);
+          console.log(`商品${id}预加载成功:`, goodsData);
+        } else {
+          console.warn(`商品${id}详情获取失败,跳过该商品`);
+          // 不创建空的占位数据,直接跳过
+        }
+      } catch (error) {
+        console.warn(`获取商品${id}详情失败:`, error);
+        // 不创建空的占位数据,直接跳过
+      }
+    });
+
+    await Promise.allSettled(promises);
+    console.log('预加载完成,当前selectedGoods:', Array.from(table.selectedGoods.values()));
+  } catch (error) {
+    console.error('预加载选中商品数据失败:', error);
+  }
+}
+
+function singleSelect(id) {
+  const selectedItem = table.list.find((item) => item.id === id);
+  if (selectedItem) {
+    const goodsData = {
+      id: selectedItem.id,
+      title: selectedItem.storeName,
+      image: selectedItem.image,
+      price: selectedItem.price,
+      otPrice: selectedItem.otPrice,
+      stock: selectedItem.stock,
+      sales: selectedItem.ficti || selectedItem.sales || 0,
+      storeName: selectedItem.storeName,
+    };
+
+    emit('modalCallBack', {
+      event: 'confirm',
+      data: goodsData,
+    });
+  }
+}
+
+function confirm() {
+  const selectedGoodsData = Array.from(table.selectedGoods.values()).map(goods => {
+    // 确保每个商品都有完整的数据结构
+    return {
+      id: goods.id,
+      title: goods.title || goods.storeName,
+      image: goods.image,
+      price: goods.price,
+      otPrice: goods.otPrice,
+      stock: goods.stock,
+      sales: goods.sales || goods.ficti || 0,
+      storeName: goods.storeName || goods.title,
+    };
+  });
+
+  console.log('确认选择的商品数据:', selectedGoodsData);
+
+  emit('modalCallBack', {
+    event: 'confirm',
+    data: selectedGoodsData,
+  });
+}
+
+onMounted(async () => {
+  if (props.modal.params.pageSize) {
+    pageData.size = props.modal.params.pageSize;
+  }
+  await preloadSelectedGoods();
+  getData();
+});
+</script>
+
+<style lang="scss" scoped>
+.decorate-goods-select {
+  .goods-search {
+    --el-header-height: auto;
+    padding-top: var(--sa-padding);
+  }
+
+  .sa-footer--submit {
+    height: auto;
+    padding: 16px;
+    border-top: 1px solid var(--sa-border);
+    background: #fff;
+  }
+
+  .goods-card {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+  }
+
+  .goods-image {
+    width: 60px;
+    height: 60px;
+    border-radius: 6px;
+    overflow: hidden;
+    flex-shrink: 0;
+    object-fit: cover;
+    aspect-ratio: 1 / 1;
+  }
+
+  .goods-info {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .goods-title {
+    font-size: 14px;
+    color: #333;
+    line-height: 1.4;
+    word-break: break-word;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 2;
+    line-clamp: 2;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin-bottom: 6px;
+  }
+
+  .goods-price-sales {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 8px;
+  }
+
+  .price {
+    color: #FF334A;
+    font-weight: bold;
+    font-size: 14px;
+  }
+
+  .sales {
+    color: #898989;
+    font-size: 12px;
+    white-space: nowrap;
+  }
+
+  .sa-footer--submit {
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    --el-footer-height: auto;
+    min-height: 60px;
+  }
+}
+</style>

+ 141 - 0
src/app/shop/admin/decorate/page/component/center/common/decorate-product-card.vue

@@ -0,0 +1,141 @@
+<template>
+  <div class="decorate-product-card" :style="{
+    width: `${width}px`,
+    borderRadius: `${borderRadius}px`,
+  }" @click="handleClick">
+    <div class="product-image-container" :style="{
+      width: `${width}px`,
+      height: `${height}px`,
+      borderTopLeftRadius: `${borderRadius}px`,
+      borderTopRightRadius: `${borderRadius}px`,
+    }">
+      <img :src="item.image" class="product-image" :alt="item.title || item.storeName" />
+    </div>
+    <div class="product-info" :style="{
+      fontSize: `${titleFontSize}px`,
+      borderBottomLeftRadius: `${borderRadius}px`,
+      borderBottomRightRadius: `${borderRadius}px`,
+    }">
+      <div class="product-title" :style="{ fontSize: `${titleFontSize}px` }">
+        {{ item.title || item.storeName }}
+      </div>
+      <div class="product-price-sales">
+        <div class="price" :style="{ fontSize: `${titleFontSize}px` }">
+          ৳ {{ formatNumber(item.price) }}
+        </div>
+        <div class="sales">
+          {{ item.sales || item.ficti || 0 }} Sold
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({
+  name: 'DecorateProductCard', // 装修商品卡片组件
+})
+
+const props = defineProps({
+  item: {
+    type: Object,
+    required: true,
+  },
+  titleFontSize: {
+    type: [Number, String],
+    default: 14,
+  },
+  width: {
+    type: [Number, String],
+    default: 180,
+  },
+  height: {
+    type: [Number, String],
+    default: 180,
+  },
+  borderRadius: {
+    type: [Number, String],
+    default: 6,
+  },
+})
+
+const emit = defineEmits(['itemClick'])
+
+function handleClick() {
+  emit('itemClick', props.item)
+}
+
+// 格式化数字
+function formatNumber(num) {
+  if (!num) return '0'
+  return parseFloat(num).toFixed(2)
+}
+</script>
+
+<style lang="scss" scoped>
+.decorate-product-card {
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  cursor: pointer;
+  transition: transform 0.2s ease;
+  border: 1px solid #f0f0f0;
+
+  &:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  }
+}
+
+.product-image-container {
+  overflow: hidden;
+  position: relative;
+}
+
+.product-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  transition: transform 0.3s ease;
+}
+
+.decorate-product-card:hover .product-image {
+  transform: scale(1.05);
+}
+
+.product-info {
+  box-sizing: border-box;
+  width: 100%;
+  background: white;
+  padding: 8px 10px;
+}
+
+.product-title {
+  margin-bottom: 6px;
+  color: #333;
+  line-height: 1.4;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  line-clamp: 2;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  word-break: break-word;
+}
+
+.product-price-sales {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.price {
+  color: #FF334A;
+  font-weight: bold;
+}
+
+.sales {
+  color: #898989;
+  font-size: 12px;
+}
+</style>

+ 119 - 0
src/app/shop/admin/decorate/page/component/center/common/grid-product-card.vue

@@ -0,0 +1,119 @@
+<template>
+  <div class="grid-product-card">
+    <div class="product-image-container">
+      <img
+        :src="item.image"
+        class="product-image"
+        :alt="item.title || item.storeName"
+      />
+    </div>
+    <div class="product-info">
+      <div class="product-title">
+        {{ item.title || item.storeName }}
+      </div>
+      <div class="product-price-sales">
+        <div class="price">
+          ৳ {{ formatNumber(item.price) }}
+        </div>
+        <div class="sales">
+          {{ item.sales || item.ficti || 0 }} Sold
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+defineOptions({
+  name: 'GridProductCard',
+})
+
+const props = defineProps({
+  item: {
+    type: Object,
+    required: true,
+  },
+})
+
+const emit = defineEmits(['itemClick'])
+
+function handleClick() {
+  emit('itemClick', props.item)
+}
+
+// 格式化数字
+function formatNumber(num) {
+  if (!num) return '0.00'
+  return parseFloat(num).toFixed(2)
+}
+</script>
+
+<style lang="scss" scoped>
+.grid-product-card {
+  background: white;
+  border-radius: 8px;
+  overflow: hidden;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  border: 1px solid #f0f0f0;
+  
+  &:hover {
+    transform: translateY(-4px);
+    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
+  }
+}
+
+.product-image-container {
+  width: 100%;
+  height: 200px;
+  overflow: hidden;
+  position: relative;
+}
+
+.product-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  transition: transform 0.3s ease;
+}
+
+.grid-product-card:hover .product-image {
+  transform: scale(1.05);
+}
+
+.product-info {
+  padding: 12px;
+}
+
+.product-title {
+  font-size: 14px;
+  color: #333;
+  line-height: 1.4;
+  margin-bottom: 8px;
+  display: -webkit-box;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  line-clamp: 2;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  word-break: break-word;
+  min-height: 38px;
+}
+
+.product-price-sales {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.price {
+  color: #FF334A;
+  font-weight: bold;
+  font-size: 16px;
+}
+
+.sales {
+  color: #898989;
+  font-size: 12px;
+}
+</style>

+ 2 - 1
src/app/shop/admin/decorate/page/component/center/comp/coupon/setting.vue

@@ -34,7 +34,8 @@
             </el-radio-group>
           </el-form-item>
           <el-form-item label="背景图片">
-            <sa-uploader v-model="settingData.data.fill.bgImage" fileType="image"></sa-uploader>
+            <sa-upload-image v-model="settingData.data.fill.bgImage" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+              :max-size="5" :direct-upload="true" :size="100" />
           </el-form-item>
           <el-form-item label="文字颜色">
             <dc-color-picker v-model="settingData.data.fill.color"></dc-color-picker>

+ 81 - 87
src/app/shop/admin/decorate/page/component/center/comp/goodsCard/index.vue

@@ -4,82 +4,35 @@
       <template v-for="goods in compData.data.goodsList" :key="goods">
         <div class="item" :style="itemStyle">
           <div class="item-wrap" :style="itemWrapStyle">
+            <!-- 商品角标 -->
+            <div v-if="compData.data.tagStyle.show" class="tag">
+              <sa-image :url="compData.data.tagStyle.src" />
+            </div>
             <sa-image :url="goods.image" :radius="0" :suffix="null"></sa-image>
             <div class="desc">
-              <div>
-                <div
-                  v-if="compData.data.goodsFields.title.show"
-                  class="title"
-                  :class="`sa-table-line-${compData.data.mode == 3 ? '2' : '1'}`"
-                  :style="{
-                    color: compData.data.goodsFields.title.color,
-                  }"
-                >
-                  {{ goods.title }}
-                </div>
-                <div
-                  v-if="compData.data.goodsFields.subtitle.show"
-                  class="subtitle sa-table-line-1 sa-m-b-4"
-                  :style="{
-                    color: compData.data.goodsFields.subtitle.color,
-                  }"
-                >
-                  {{ goods.subtitle }}
-                </div>
-                <el-scrollbar class="promos">
-                  <div class="sa-flex sa-m-b-8">
-                    <div class="promo-tag" v-for="item in goods.promos" :key="item">
-                      <span>{{ item.title }}</span>
-                    </div>
-                  </div>
-                </el-scrollbar>
+              <!-- 商品标题 -->
+              <div v-if="compData.data.goodsFields.title?.show !== false" class="title"
+                :style="{ color: compData.data.goodsFields.title?.color || '#333' }">
+                {{ goods.title || goods.storeName }}
               </div>
-              <div>
-                <div class="sa-flex sa-m-b-8">
-                  <div
-                    v-if="compData.data.goodsFields.price.show"
-                    class="price"
-                    :style="{
-                      color: compData.data.goodsFields.price.color,
-                    }"
-                  >
-                    ¥{{ goods.price[0] }}
-                  </div>
-                  <s
-                    v-if="compData.data.goodsFields.original_price.show"
-                    class="original-price sa-m-l-4"
-                    :style="{
-                      color: compData.data.goodsFields.original_price.color,
-                    }"
-                    >¥{{ goods.original_price }}</s
-                  >
+              <div class="price-sales-row">
+                <!-- 商品价格 -->
+                <div v-if="compData.data.goodsFields.price?.show !== false" class="price"
+                  :style="{ color: compData.data.goodsFields.price?.color || '#FF334A' }">
+                  ৳{{ formatPrice(goods.price) }}
                 </div>
-                <div
-                  v-if="compData.data.goodsFields.sales.show"
-                  class="sales"
-                  :style="{
-                    color: compData.data.goodsFields.sales.color,
-                  }"
-                >
-                  已售{{ goods.sales }}件
+                <!-- 销量 -->
+                <div v-if="compData.data.goodsFields.sales?.show !== false" class="sales"
+                  :style="{ color: compData.data.goodsFields.sales?.color || '#898989' }">
+                  {{ goods.sales || goods.ficti || 0 }} Sold
                 </div>
               </div>
-              <div
-                class="button"
-                :style="{
-                  background:
-                    compData.data.buyNowStyle.mode == 1
-                      ? 'linear-gradient(90deg,' +
-                        compData.data.buyNowStyle.color1 +
-                        ',' +
-                        compData.data.buyNowStyle.color2 +
-                        ')'
-                      : compData.data.buyNowStyle.src
-                      ? 'url(' + checkUrl(compData.data.buyNowStyle.src) + ')'
-                      : compData.data.buyNowStyle.src,
-                }"
-              >
-                {{ compData.data.buyNowStyle.mode == 1 ? compData.data.buyNowStyle.text : '' }}
+              <!-- 购买按钮 -->
+              <div class="buy-button" :style="{
+                backgroundColor: compData.data.buyNowStyle?.backgroundColor || '#e61b28',
+                color: compData.data.buyNowStyle?.textColor || 'white'
+              }">
+                {{ compData.data.buyNowStyle?.text || 'Buy Now' }}
               </div>
             </div>
           </div>
@@ -102,7 +55,7 @@ const wrapStyle = computed(() => ({
 }));
 
 const itemStyle = computed(() => ({
-  width: `${props.compData.data.mode == 2 ? '50%' : '100%'}`,
+  width: `${props.compData.data.mode == 1 ? '50%' : '100%'}`,
   padding: `${props.compData.data.space / 2}px`,
   'flex-shrink': 0,
 }));
@@ -110,6 +63,11 @@ const itemStyle = computed(() => ({
 const itemWrapStyle = computed(() => ({
   'border-radius': `${props.compData.data.borderRadiusTop}px ${props.compData.data.borderRadiusTop}px ${props.compData.data.borderRadiusBottom}px ${props.compData.data.borderRadiusBottom}px`,
 }));
+
+function formatPrice(price) {
+  if (!price) return '0.00';
+  return parseFloat(price).toFixed(2);
+}
 </script>
 <style lang="scss" scoped>
 .goods-card {
@@ -119,43 +77,74 @@ const itemWrapStyle = computed(() => ({
     position: relative;
     overflow: hidden;
     font-size: 12px;
+
     .tag {
       position: absolute;
       top: 0;
       left: 0;
       z-index: 2;
+
       .sa-image {
         width: 36px !important;
         height: 22px !important;
       }
     }
+
     .desc {
       padding: 10px;
       flex: 1;
+
       .title {
         font-size: 14px;
+        color: #333;
+        line-height: 1.4;
+        margin-bottom: 8px;
+        display: -webkit-box;
+        -webkit-box-orient: vertical;
+        -webkit-line-clamp: 2;
+        line-clamp: 2;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        word-break: break-word;
       }
-      .promos {
-        height: fit-content;
-      }
-      .promo-tag {
-        flex-shrink: 0;
-        height: 18px;
+
+      .price-sales-row {
         display: flex;
         align-items: center;
-        justify-content: center;
-        border-radius: 4px;
-        border: 1px solid #f00;
-        padding: 0 4px;
+        justify-content: space-between;
+        margin-bottom: 8px;
+      }
+
+      .price {
+        color: #FF334A;
+        font-weight: bold;
+        font-size: 16px;
+      }
+
+      .sales {
+        color: #898989;
         font-size: 12px;
-        color: #f00;
-        margin-right: 4px;
       }
 
-      .promo-tag:last-of-type {
-        margin-right: 0;
+      .buy-button {
+        background: #e61b28;
+        color: white;
+        border: none;
+        border-radius: 4px;
+        padding: 6px 12px;
+        font-size: 12px;
+        font-weight: 500;
+        text-align: center;
+        cursor: pointer;
+        transition: background-color 0.3s ease;
+        width: 100%;
+
+        &:hover {
+          background: #c41620;
+        }
       }
     }
+
     .button {
       position: absolute;
       right: 10px;
@@ -170,26 +159,31 @@ const itemWrapStyle = computed(() => ({
       background-size: 100% 100% !important;
     }
   }
-  .goods-card-wrap-1 {
+
+  .goods-card-wrap-2 {
     .sa-image {
       width: 100%;
       height: 140px;
     }
   }
-  .goods-card-wrap-2 {
+
+  .goods-card-wrap-1 {
     .sa-image {
       width: 100%;
       height: 140px;
     }
   }
+
   .goods-card-wrap-3 {
     .item-wrap {
       display: flex;
       height: 140px;
+
       .sa-image {
         width: 140px;
         height: 140px;
       }
+
       .desc {
         display: flex;
         flex-direction: column;

+ 115 - 51
src/app/shop/admin/decorate/page/component/center/comp/goodsCard/setting.vue

@@ -8,10 +8,10 @@
           <el-form-item label="选择风格">
             <el-radio-group class="custom-radio-button" v-model="settingData.data.mode">
               <el-radio-button :label="1">
-                <sa-icon icon="sa-shop-decorate-goodsCard-mode-1" />
+                <sa-icon icon="sa-shop-decorate-goodsCard-mode-3" />
               </el-radio-button>
               <el-radio-button :label="2">
-                <sa-icon icon="sa-shop-decorate-goodsCard-mode-3" />
+                <sa-icon icon="sa-shop-decorate-goodsCard-mode-1" />
               </el-radio-button>
               <el-radio-button :label="3">
                 <sa-icon icon="sa-shop-decorate-goodsCard-mode-2" />
@@ -21,11 +21,7 @@
           <template v-for="(item, field) in settingData.data.goodsFields" :key="field">
             <template v-if="fieldLabel[field]">
               <el-form-item :label="fieldLabel[field]">
-                <dc-color-picker
-                  v-model="item.color"
-                  v-model:show="item.show"
-                  :isShow="true"
-                ></dc-color-picker>
+                <dc-color-picker v-model="item.color" v-model:show="item.show" :isShow="true"></dc-color-picker>
               </el-form-item>
             </template>
           </template>
@@ -42,40 +38,25 @@
           </el-form-item>
           <el-form-item v-if="settingData.data.tagStyle.show" label="上传图片">
             <div class="sa-flex">
-              <sa-uploader v-model="settingData.data.tagStyle.src" fileType="image"></sa-uploader>
+              <sa-upload-image v-model="settingData.data.tagStyle.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+                :max-size="5" :direct-upload="true" :size="100" />
               <div class="tip">建议尺寸:36*22</div>
             </div>
           </el-form-item>
         </div>
       </div>
       <div class="card">
-        <div class="title">购设置</div>
+        <div class="title">购买按钮设置</div>
         <div class="content">
-          <el-form-item label="加购按钮">
-            <el-radio-group v-model="settingData.data.buyNowStyle.mode">
-              <el-radio :label="1">文字</el-radio>
-              <el-radio :label="2">图片</el-radio>
-            </el-radio-group>
+          <el-form-item label="按钮文字">
+            <el-input v-model="settingData.data.buyNowStyle.text" placeholder="Buy Now"></el-input>
           </el-form-item>
-          <template v-if="settingData.data.buyNowStyle.mode == 1">
-            <el-form-item label="文字">
-              <el-input v-model="settingData.data.buyNowStyle.text"></el-input>
-            </el-form-item>
-            <el-form-item label="背景1">
-              <dc-color-picker v-model="settingData.data.buyNowStyle.color1"></dc-color-picker>
-            </el-form-item>
-            <el-form-item label="背景2">
-              <dc-color-picker v-model="settingData.data.buyNowStyle.color2"></dc-color-picker>
-            </el-form-item>
-          </template>
-          <el-form-item v-if="settingData.data.buyNowStyle.mode == 2" label="图片">
-            <div class="sa-flex"
-              ><sa-uploader
-                v-model="settingData.data.buyNowStyle.src"
-                fileType="image"
-              ></sa-uploader>
-              <div class="tip">建议尺寸:56*56</div>
-            </div>
+          <el-form-item label="按钮颜色">
+            <dc-color-picker v-model="settingData.data.buyNowStyle.backgroundColor"
+              :default-value="'#e61b28'"></dc-color-picker>
+          </el-form-item>
+          <el-form-item label="文字颜色">
+            <dc-color-picker v-model="settingData.data.buyNowStyle.textColor" :default-value="'#fff'"></dc-color-picker>
           </el-form-item>
         </div>
       </div>
@@ -98,22 +79,105 @@
 </template>
 
 <script setup>
-  import { computed } from 'vue';
-  import dcColorPicker from '../../common/dc-color-picker.vue';
-  import dcGoodsSelect from '../../common/dc-goods-select.vue';
-  import dcList from '../../common/dc-list.vue';
-  import dcSlider from '../../common/dc-slider.vue';
-
-  const props = defineProps(['settingData', 'tabType']);
-
-  const fieldLabel = computed(() => {
-    return {
-      title: '商品标题',
-      subtitle: '副标题',
-      price: '商品价格',
-      original_price: '原价',
-      sales: '销量',
-      stock: '库存',
-    };
-  });
+import { computed, onMounted, watch } from 'vue';
+import dcColorPicker from '../../common/dc-color-picker.vue';
+import dcGoodsSelect from '../../common/dc-goods-select.vue';
+import dcList from '../../common/dc-list.vue';
+import dcSlider from '../../common/dc-slider.vue';
+import { api } from '@/app/shop/admin/goods/goods.service';
+
+const props = defineProps(['settingData', 'tabType']);
+
+const fieldLabel = computed(() => {
+  return {
+    title: '商品标题',
+    subtitle: '副标题',
+    price: '商品价格',
+    original_price: '原价',
+    sales: '销量',
+    stock: '库存',
+  };
+});
+
+// 通过goodsIds获取商品详情
+async function loadGoodsFromIds() {
+  if (props.settingData?.data?.goodsIds && props.settingData.data.goodsIds.length > 0) {
+    try {
+      console.log('开始通过goodsIds获取商品详情:', props.settingData.data.goodsIds);
+
+      // 调用商品列表API,传入ids参数
+      const { code, data } = await api.goods.list({
+        ids: props.settingData.data.goodsIds.join(','),
+        pageSize: 100 // 设置足够大的页面大小确保获取所有商品
+      });
+
+      if (code === '200' && data?.list) {
+        // 将获取到的商品数据转换为装修组件需要的格式
+        const goodsList = data.list.map(item => ({
+          id: item.id,
+          title: item.storeName,
+          image: item.image,
+          price: item.price,
+          otPrice: item.otPrice,
+          stock: item.stock,
+          sales: item.ficti || item.sales || 0,
+          storeName: item.storeName,
+        }));
+
+        // 初始化goodsList
+        if (!props.settingData.data.goodsList) {
+          props.settingData.data.goodsList = [];
+        }
+
+        // 只有当goodsList为空时才设置,避免覆盖用户已选择的商品
+        if (props.settingData.data.goodsList.length === 0) {
+          props.settingData.data.goodsList = goodsList;
+          console.log('成功初始化商品列表:', goodsList);
+        }
+      } else {
+        console.warn('获取商品详情失败:', code, data);
+      }
+    } catch (error) {
+      console.error('获取商品详情出错:', error);
+    }
+  }
+}
+
+// 初始化默认值
+function initDefaultValues() {
+  if (props.settingData?.data) {
+    // 设置商品样式默认为一行两个(mode=1)
+    if (!props.settingData.data.mode) {
+      props.settingData.data.mode = 1;
+    }
+
+    // 初始化购买按钮样式默认值
+    if (!props.settingData.data.buyNowStyle) {
+      props.settingData.data.buyNowStyle = {};
+    }
+
+    if (!props.settingData.data.buyNowStyle.text) {
+      props.settingData.data.buyNowStyle.text = 'Buy Now';
+    }
+
+    if (!props.settingData.data.buyNowStyle.backgroundColor) {
+      props.settingData.data.buyNowStyle.backgroundColor = '#e61b28';
+    }
+
+    if (!props.settingData.data.buyNowStyle.textColor) {
+      props.settingData.data.buyNowStyle.textColor = '#fff';
+    }
+
+    // 通过goodsIds加载商品详情
+    loadGoodsFromIds();
+  }
+}
+
+onMounted(() => {
+  initDefaultValues();
+});
+
+watch(() => props.settingData, () => {
+  initDefaultValues();
+}, { deep: true, immediate: true });
 </script>

+ 161 - 92
src/app/shop/admin/decorate/page/component/center/comp/goodsShelves/index.vue

@@ -1,31 +1,40 @@
 <template>
   <div class="goods-sheleves">
-    <div :class="[`goods-wrap-${compData.data.mode}`]" :style="wrapStyle()">
-      <template v-for="good in compData.data.goodsList" :key="good">
-        <div class="item" :style="itemStyle()">
-          <div class="item-wrap">
-            <div class="tag" v-if="compData.data.tagStyle.show">
-              <sa-image :url="compData.data.tagStyle.src" radius="0" :suffix="null"></sa-image>
+    <div class="goods-grid" :style="gridStyle()">
+      <template v-for="good in compData.data.goodsList" :key="good.id">
+        <div class="grid-item">
+          <div class="product-card">
+            <div class="product-image-container">
+              <!-- 商品角标 -->
+              <div v-if="compData.data.tagStyle?.show" class="tag">
+                <img :src="compData.data.tagStyle.src" class="tag-image" />
+              </div>
+              <img :src="good.image" class="product-image" :alt="good.title || good.storeName" />
             </div>
-            <sa-image :url="good.image" size="64" :suffix="null"></sa-image>
-            <div class="desc">
-              <div
-                v-if="compData.data.goodsFields.title.show"
-                class="title sa-m-b-8 sa-table-line-1"
-                :style="{
-                  color: compData.data.goodsFields.title.color,
-                }"
-              >
-                {{ good.title }}
+            <div class="product-info">
+              <!-- 商品标题 -->
+              <div v-if="compData.data.goodsFields?.title?.show !== false" class="product-title"
+                :style="{ color: compData.data.goodsFields?.title?.color || '#333' }">
+                {{ good.title || good.storeName }}
+              </div>
+              <div class="product-price-sales">
+                <!-- 商品价格 -->
+                <div v-if="compData.data.goodsFields?.price?.show !== false" class="price"
+                  :style="{ color: compData.data.goodsFields?.price?.color || '#FF334A' }">
+                  ৳{{ formatPrice(good.price) }}
+                </div>
+                <!-- 销量 -->
+                <div v-if="compData.data.goodsFields?.sales?.show !== false" class="sales"
+                  :style="{ color: compData.data.goodsFields?.sales?.color || '#898989' }">
+                  {{ good.sales || good.ficti || 0 }} Sold
+                </div>
               </div>
-              <div
-                v-if="compData.data.goodsFields.price.show"
-                class="price"
-                :style="{
-                  color: compData.data.goodsFields.price.color,
-                }"
-              >
-                ¥{{ good.price[0] }}
+              <!-- 购买按钮 -->
+              <div class="buy-button" :style="{
+                backgroundColor: compData.data.buyNowStyle?.backgroundColor || '#e61b28',
+                color: compData.data.buyNowStyle?.textColor || 'white'
+              }">
+                {{ compData.data.buyNowStyle?.text || 'Buy Now' }}
               </div>
             </div>
           </div>
@@ -36,83 +45,143 @@
 </template>
 
 <script setup>
-  const props = defineProps(['compData']);
+const props = defineProps(['compData']);
 
-  const w = {
-    1: '50%',
-    2: '33.3%',
-    3: '32%',
+function gridStyle() {
+  const columns = {
+    1: 2, // 2列
+    2: 3, // 3列
+    3: 4, // 4列
   };
 
-  function wrapStyle() {
-    return {
-      display: 'flex',
-      'flex-wrap': `${props.compData.data.mode == 3 ? 'nowrap' : 'wrap'}`,
-      margin: `-${props.compData.data.space / 2}px`,
-    };
-  }
+  return {
+    display: 'grid',
+    gridTemplateColumns: `repeat(${columns[props.compData.data.mode] || 2}, 1fr)`,
+    gap: `${props.compData.data.space || 16}px`,
+    padding: '16px',
+  };
+}
 
-  function itemStyle() {
-    return {
-      width: `${w[props.compData.data.mode]}`,
-      padding: `${props.compData.data.space / 2}px`,
-      'flex-shrink': 0,
-      'border-top-left-radius': `${props.compData.data.borderRadiusTop}px`,
-      'border-top-right-radius': `${props.compData.data.borderRadiusTop}px`,
-      'border-bottom-left-radius': `${props.compData.data.borderRadiusBottom}px`,
-      'border-bottom-right-radius': `${props.compData.data.borderRadiusBottom}px`,
-      overflow: 'hidden',
-    };
-  }
+function formatPrice(price) {
+  if (!price) return '0.00';
+  return parseFloat(price).toFixed(2);
+}
 </script>
 
 <style lang="scss" scoped>
-  .goods-sheleves {
-    .goods-wrap-1,
-    .goods-wrap-2,
-    .goods-wrap-3 {
-      .item-wrap {
-        background: #fff;
-        // box-shadow: 0 5px 21px 0 rgba(234, 234, 234, 0.46);
-        border-radius: 4px;
-        position: relative;
-        .tag {
-          position: absolute;
-          top: 0;
-          left: 0;
-          z-index: 2;
-          .sa-image {
-            width: 36px !important;
-            height: 22px !important;
-          }
-        }
-        .sa-image {
-          width: 100% !important;
-          height: 110px !important;
-        }
-        .desc {
-          padding: 10px;
-          flex: 1;
-          .name {
-            font-size: 13px;
-          }
-          .price {
-            font-size: 12px;
-          }
-        }
-      }
+.goods-sheleves {
+  .goods-grid {
+    background: #f8f9fa;
+    border-radius: 8px;
+  }
+
+  .grid-item {
+    width: 100%;
+  }
+
+  .product-card {
+    background: white;
+    border-radius: 8px;
+    overflow: hidden;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    border: 1px solid #f0f0f0;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+
+    &:hover {
+      transform: translateY(-4px);
+      box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
     }
+  }
+
+  .product-image-container {
+    width: 100%;
+    height: 160px;
+    overflow: hidden;
+    position: relative;
+  }
+
+  .product-image {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+    transition: transform 0.3s ease;
+  }
+
+  .product-card:hover .product-image {
+    transform: scale(1.05);
+  }
+
+  .product-info {
+    padding: 12px;
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+  }
+
+  .product-title {
+    font-size: 14px;
+    line-height: 1.4;
+    margin-bottom: 8px;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 2;
+    line-clamp: 2;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    word-break: break-word;
+    min-height: 38px;
+  }
+
+  .product-price-sales {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-top: auto;
+  }
+
+  .price {
+    font-weight: bold;
+    font-size: 16px;
+  }
+
+  .sales {
+    color: #898989;
+    font-size: 12px;
+  }
+
+  .buy-button {
+    background: #e61b28;
+    color: white;
+    border: none;
+    border-radius: 4px;
+    padding: 8px 16px;
+    font-size: 12px;
+    font-weight: 500;
+    text-align: center;
+    cursor: pointer;
+    margin-top: 8px;
+    transition: background-color 0.3s ease;
 
-    .goods-wrap-1 {
-      .item-wrap {
-        display: flex;
-        height: 64px;
-        font-size: 12px;
-        .sa-image {
-          width: 64px !important;
-          height: 100% !important;
-        }
-      }
+    &:hover {
+      background: #c41620;
     }
   }
+
+  .tag {
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 2;
+  }
+
+  .tag-image {
+    width: 36px;
+    height: 22px;
+    object-fit: cover;
+  }
+}
 </style>

+ 118 - 23
src/app/shop/admin/decorate/page/component/center/comp/goodsShelves/setting.vue

@@ -6,12 +6,12 @@
         <div class="title">商品样式</div>
         <div class="content">
           <el-form-item label="选择风格">
+            <el-radio-button :label="1">
+              <sa-icon icon="sa-shop-decorate-goodsShelves-mode-2" />
+            </el-radio-button>
             <el-radio-group class="custom-radio-button" v-model="settingData.data.mode">
-              <el-radio-button :label="1">
-                <sa-icon icon="sa-shop-decorate-goodsShelves-mode-1" />
-              </el-radio-button>
               <el-radio-button :label="2">
-                <sa-icon icon="sa-shop-decorate-goodsShelves-mode-2" />
+                <sa-icon icon="sa-shop-decorate-goodsShelves-mode-1" />
               </el-radio-button>
               <el-radio-button :label="3">
                 <sa-icon icon="sa-shop-decorate-goodsShelves-mode-3" />
@@ -21,11 +21,7 @@
           <template v-for="(item, field) in settingData.data.goodsFields" :key="field">
             <template v-if="fieldLabel[field]">
               <el-form-item :label="fieldLabel[field]">
-                <dc-color-picker
-                  v-model="item.color"
-                  v-model:show="item.show"
-                  :isShow="true"
-                ></dc-color-picker>
+                <dc-color-picker v-model="item.color" v-model:show="item.show" :isShow="true"></dc-color-picker>
               </el-form-item>
             </template>
           </template>
@@ -42,12 +38,28 @@
           </el-form-item>
           <el-form-item v-if="settingData.data.tagStyle.show" label="上传图片">
             <div class="sa-flex">
-              <sa-uploader v-model="settingData.data.tagStyle.src" fileType="image"></sa-uploader>
+              <sa-upload-image v-model="settingData.data.tagStyle.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+                :max-size="5" :direct-upload="true" :size="100" />
               <div class="tip">建议尺寸:36*22</div>
             </div>
           </el-form-item>
         </div>
       </div>
+      <div class="card">
+        <div class="title">购买按钮设置</div>
+        <div class="content">
+          <el-form-item label="按钮文字">
+            <el-input v-model="settingData.data.buyNowStyle.text" placeholder="Buy Now"></el-input>
+          </el-form-item>
+          <el-form-item label="按钮颜色">
+            <dc-color-picker v-model="settingData.data.buyNowStyle.backgroundColor"
+              :default-value="'#e61b28'"></dc-color-picker>
+          </el-form-item>
+          <el-form-item label="文字颜色">
+            <dc-color-picker v-model="settingData.data.buyNowStyle.textColor" :default-value="'#fff'"></dc-color-picker>
+          </el-form-item>
+        </div>
+      </div>
       <div class="card">
         <div class="title">样式</div>
         <div class="content">
@@ -67,17 +79,100 @@
 </template>
 
 <script setup>
-  import { computed } from 'vue';
-  import dcColorPicker from '../../common/dc-color-picker.vue';
-  import dcGoodsSelect from '../../common/dc-goods-select.vue';
-  import dcSlider from '../../common/dc-slider.vue';
-
-  const props = defineProps(['settingData', 'tabType']);
-
-  const fieldLabel = computed(() => {
-    return {
-      title: '商品标题',
-      price: '商品价格',
-    };
-  });
+import { computed, onMounted, watch } from 'vue';
+import dcColorPicker from '../../common/dc-color-picker.vue';
+import dcGoodsSelect from '../../common/dc-goods-select.vue';
+import dcSlider from '../../common/dc-slider.vue';
+import { api } from '@/app/shop/admin/goods/goods.service';
+
+const props = defineProps(['settingData', 'tabType']);
+
+const fieldLabel = computed(() => {
+  return {
+    title: '商品标题',
+    price: '商品价格',
+  };
+});
+
+// 通过goodsIds获取商品详情
+async function loadGoodsFromIds() {
+  if (props.settingData?.data?.goodsIds && props.settingData.data.goodsIds.length > 0) {
+    try {
+      console.log('商品货架组件开始通过goodsIds获取商品详情:', props.settingData.data.goodsIds);
+
+      // 调用商品列表API,传入ids参数
+      const { code, data } = await api.goods.list({
+        ids: props.settingData.data.goodsIds.join(','),
+        pageSize: 100 // 设置足够大的页面大小确保获取所有商品
+      });
+
+      if (code === '200' && data?.list) {
+        // 将获取到的商品数据转换为装修组件需要的格式
+        const goodsList = data.list.map(item => ({
+          id: item.id,
+          title: item.storeName,
+          image: item.image,
+          price: item.price,
+          otPrice: item.otPrice,
+          stock: item.stock,
+          sales: item.ficti || item.sales || 0,
+          storeName: item.storeName,
+        }));
+
+        // 初始化goodsList
+        if (!props.settingData.data.goodsList) {
+          props.settingData.data.goodsList = [];
+        }
+
+        // 只有当goodsList为空时才设置,避免覆盖用户已选择的商品
+        if (props.settingData.data.goodsList.length === 0) {
+          props.settingData.data.goodsList = goodsList;
+          console.log('商品货架组件成功初始化商品列表:', goodsList);
+        }
+      } else {
+        console.warn('商品货架组件获取商品详情失败:', code, data);
+      }
+    } catch (error) {
+      console.error('商品货架组件获取商品详情出错:', error);
+    }
+  }
+}
+
+// 初始化默认值
+function initDefaultValues() {
+  if (props.settingData?.data) {
+    // 设置商品样式默认为一行两个(mode=1)
+    if (!props.settingData.data.mode) {
+      props.settingData.data.mode = 1;
+    }
+
+    // 初始化购买按钮样式默认值
+    if (!props.settingData.data.buyNowStyle) {
+      props.settingData.data.buyNowStyle = {};
+    }
+
+    if (!props.settingData.data.buyNowStyle.text) {
+      props.settingData.data.buyNowStyle.text = 'Buy Now';
+    }
+
+    if (!props.settingData.data.buyNowStyle.backgroundColor) {
+      props.settingData.data.buyNowStyle.backgroundColor = '#e61b28';
+    }
+
+    if (!props.settingData.data.buyNowStyle.textColor) {
+      props.settingData.data.buyNowStyle.textColor = '#fff';
+    }
+
+    // 通过goodsIds加载商品详情
+    loadGoodsFromIds();
+  }
+}
+
+onMounted(() => {
+  initDefaultValues();
+});
+
+watch(() => props.settingData, () => {
+  initDefaultValues();
+}, { deep: true, immediate: true });
 </script>

+ 4 - 2
src/app/shop/admin/decorate/page/component/center/comp/groupon/setting.vue

@@ -62,7 +62,8 @@
               </el-form-item>
             </template>
             <el-form-item v-if="settingData.data.buyNowStyle.mode == 2" label="图片">
-              <sa-uploader v-model="settingData.data.buyNowStyle.src" fileType="image"></sa-uploader>
+              <sa-upload-image v-model="settingData.data.buyNowStyle.src" :max-count="1"
+                :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
             </el-form-item>
           </div>
         </div>
@@ -78,7 +79,8 @@
           </el-form-item>
           <el-form-item v-if="settingData.data.tagStyle.show == 1" label="上传图片">
             <div class="sa-flex">
-              <sa-uploader v-model="settingData.data.tagStyle.src" fileType="image"></sa-uploader>
+              <sa-upload-image v-model="settingData.data.tagStyle.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+                :max-size="5" :direct-upload="true" :size="100" />
               <span class="tip">建议尺寸:36*22</span>
             </div>
           </el-form-item>

+ 23 - 29
src/app/shop/admin/decorate/page/component/center/comp/hotzone/setting.vue

@@ -6,17 +6,11 @@
         <div class="content">
           <el-form-item label="上传图片">
             <div class="sa-flex">
-              <sa-uploader v-model="settingData.data.src" fileType="image"></sa-uploader>
-              <div class="tip">建议宽度:750</div>
+              <sa-upload-image v-model="settingData.data.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+                :max-size="5" :direct-upload="true" :size="100" />
             </div>
           </el-form-item>
-          <el-button
-            v-if="settingData.data.src"
-            class="add-button"
-            icon="Plus"
-            @click="onSetHotzone"
-            >设置热区</el-button
-          >
+          <el-button v-if="settingData.data.src" class="add-button" icon="Plus" @click="onSetHotzone">设置热区</el-button>
         </div>
       </div>
     </template>
@@ -24,28 +18,28 @@
 </template>
 
 <script setup>
-  import { watch } from 'vue';
-  import { useModal } from '@/sheep/hooks';
-  import Edit from './edit.vue';
+import { watch } from 'vue';
+import { useModal } from '@/sheep/hooks';
+import Edit from './edit.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const props = defineProps(['settingData', 'tabType']);
 
-  watch(
-    () => props.settingData.data.src,
-    () => {
-      props.settingData.data.list = [];
-    },
-  );
+watch(
+  () => props.settingData.data.src,
+  () => {
+    props.settingData.data.list = [];
+  },
+);
 
-  function onSetHotzone() {
-    useModal(
-      Edit,
-      { title: '设置热区', data: props.settingData.data },
-      {
-        confirm: (res) => {
-          props.settingData.data.list = res.data;
-        },
+function onSetHotzone() {
+  useModal(
+    Edit,
+    { title: '设置热区', data: props.settingData.data },
+    {
+      confirm: (res) => {
+        props.settingData.data.list = res.data;
       },
-    );
-  }
+    },
+  );
+}
 </script>

+ 10 - 10
src/app/shop/admin/decorate/page/component/center/comp/imageBanner/setting.vue

@@ -37,10 +37,7 @@
           </el-form-item>
         </div>
       </div>
-      <dc-list
-        v-model="settingData.data.list"
-        :itemProp="{ title: '', type: 'image', src: '', poster: '', url: '' }"
-      >
+      <dc-list v-model="settingData.data.list" :itemProp="{ title: '', type: 'image', src: '', poster: '', url: '' }">
         <template #title>图片上传</template>
         <template #listItem="{ element }">
           <el-form-item label="标题">
@@ -53,10 +50,13 @@
             </el-radio-group>
           </el-form-item>
           <el-form-item label="上传">
-            <sa-uploader v-model="element.src" :fileType="element.type"></sa-uploader>
+            <sa-upload-image v-if="element.type == 'image'" v-model="element.src" :max-count="1"
+              :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
+            <el-input v-else v-model="element.src" placeholder="请输入视频链接" />
           </el-form-item>
           <el-form-item v-if="element.type == 'video'" label="视频封面">
-            <sa-uploader v-model="element.poster" fileType="image"></sa-uploader>
+            <sa-upload-image v-model="element.poster" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
+              :direct-upload="true" :size="100" />
           </el-form-item>
           <el-form-item label="链接">
             <dc-url v-model="element.url"></dc-url>
@@ -68,9 +68,9 @@
 </template>
 
 <script setup>
-  import dcList from '../../common/dc-list.vue';
-  import dcUrl from '../../common/dc-url.vue';
-  import dcSlider from '../../common/dc-slider.vue';
+import dcList from '../../common/dc-list.vue';
+import dcUrl from '../../common/dc-url.vue';
+import dcSlider from '../../common/dc-slider.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const props = defineProps(['settingData', 'tabType']);
 </script>

+ 4 - 4
src/app/shop/admin/decorate/page/component/center/comp/imageBlock/setting.vue

@@ -6,8 +6,8 @@
         <div class="content">
           <el-form-item label="上传图片">
             <div class="sa-flex">
-              <sa-uploader v-model="settingData.data.src" fileType="image"></sa-uploader>
-              <div class="tip">建议宽度:750</div>
+              <sa-upload-image v-model="settingData.data.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+                :max-size="5" :direct-upload="true" :size="100" />
             </div>
           </el-form-item>
           <el-form-item label="链接">
@@ -20,7 +20,7 @@
 </template>
 
 <script setup>
-  import dcUrl from '../../common/dc-url.vue';
+import dcUrl from '../../common/dc-url.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const props = defineProps(['settingData', 'tabType']);
 </script>

+ 172 - 180
src/app/shop/admin/decorate/page/component/center/comp/imageCube/setting.vue

@@ -2,49 +2,36 @@
   <div class="image-cube-setting setting">
     <template v-if="tabType == 'data'">
       <div class="card">
-        <div class="title">魔方样式 <div class="tip">每格尺寸:187*187</div></div>
+        <div class="title">魔方样式 <div class="tip">每格尺寸:187*187</div>
+        </div>
         <div class="content">
           <table class="image-cube-map">
             <tbody>
               <tr v-for="trItem in mapList" :key="trItem">
-                <td
-                  class="map-item"
-                  :class="
-                    activeData.minRow <= trItem &&
+                <td class="map-item" :class="activeData.minRow <= trItem &&
                     trItem <= activeData.maxRow &&
                     activeData.minCol <= tdItem &&
                     tdItem <= activeData.maxCol
-                      ? 'map-item--active'
-                      : ''
-                  "
-                  :style="{
+                    ? 'map-item--active'
+                    : ''
+                  " :style="{
                     width: scale + 'px',
                     height: scale + 'px',
-                  }"
-                  v-for="tdItem in mapList"
-                  :key="tdItem"
-                  @click.stop="selectItem(trItem, tdItem)"
-                  @mouseover="onMouseover(trItem, tdItem)"
-                >
+                  }" v-for="tdItem in mapList" :key="tdItem" @click.stop="selectItem(trItem, tdItem)"
+                  @mouseover="onMouseover(trItem, tdItem)">
                   <el-icon>
                     <Plus />
                   </el-icon>
                 </td>
               </tr>
-              <div
-                class="position-item sa-flex sa-row-center"
+              <div class="position-item sa-flex sa-row-center"
                 :class="selected.active == sindex ? 'position-item--active' : ''"
-                v-for="(style, sindex) in selected.data"
-                :style="{
+                v-for="(style, sindex) in selected.data" :style="{
                   width: style.width * scale + 'px',
                   height: style.height * scale + 'px',
                   top: style.top * scale + 'px',
                   left: style.left * scale + 'px',
-                }"
-                :key="style"
-                @mouseover="onCancelSelect"
-                @click.stop="onPositionSelect(sindex)"
-              >
+                }" :key="style" @mouseover="onCancelSelect" @click.stop="onPositionSelect(sindex)">
                 {{ style.width }}*{{ style.height }}
                 <el-icon class="close" @click.stop="deleteItem(sindex)">
                   <CircleCloseFilled />
@@ -54,10 +41,8 @@
           </table>
           <template v-if="selected.data.length > 0">
             <el-form-item label="上传图片">
-              <sa-uploader
-                v-model="selected.data[selected.active].src"
-                fileType="image"
-              ></sa-uploader>
+              <sa-upload-image v-model="selected.data[selected.active].src" :max-count="1"
+                :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
             </el-form-item>
             <el-form-item label="链接">
               <dc-url v-model="selected.data[selected.active].url"></dc-url>
@@ -79,182 +64,189 @@
 </template>
 
 <script setup>
-  import { reactive, ref, watch } from 'vue';
-  import { cloneDeep } from 'lodash';
-  import dcSlider from '../../common/dc-slider.vue';
-  import dcUrl from '../../common/dc-url.vue';
+import { reactive, ref, watch } from 'vue';
+import { cloneDeep } from 'lodash';
+import dcSlider from '../../common/dc-slider.vue';
+import dcUrl from '../../common/dc-url.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const props = defineProps(['settingData', 'tabType']);
 
-  watch(() => props.settingData, () => {
-    selected.data = props.settingData.data.list || [];
-    selected.active = props.settingData.data.list.length > 0 ? 0 : null;
-  }, {
-    deep: true
-  })
+watch(() => props.settingData, () => {
+  selected.data = props.settingData.data.list || [];
+  selected.active = props.settingData.data.list.length > 0 ? 0 : null;
+}, {
+  deep: true
+})
 
-  const scale = ref(66);
-  const mapList = [1, 2, 3, 4];
-  const start = reactive({ row: 0, col: 0 });
-  const selectFlag = ref(false);
+const scale = ref(66);
+const mapList = [1, 2, 3, 4];
+const start = reactive({ row: 0, col: 0 });
+const selectFlag = ref(false);
 
-  const activeData = reactive({
-    width: 0,
-    height: 0,
-    top: 0,
-    left: 0,
-    minRow: 0,
-    maxRow: 0,
-    minCol: 0,
-    maxCol: 0,
-    src: '',
-    url: '',
-  });
-  let selected = reactive({
-    data: props.settingData.data.list || [],
-    active: props.settingData.data.list.length > 0 ? 0 : null,
-  });
+const activeData = reactive({
+  width: 0,
+  height: 0,
+  top: 0,
+  left: 0,
+  minRow: 0,
+  maxRow: 0,
+  minCol: 0,
+  maxCol: 0,
+  src: '',
+  url: '',
+});
+let selected = reactive({
+  data: props.settingData.data.list || [],
+  active: props.settingData.data.list.length > 0 ? 0 : null,
+});
 
-  function selectItem(row, col) {
-    // 开始的坐标
-    if (!selectFlag.value) {
-      start.row = row;
-      start.col = col;
-    }
-    // 结束存储数据
-    if (selectFlag.value) {
-      selected.data.push(cloneDeep(activeData));
-      selected.active = selected.data.length - 1;
-      clearActiveData();
-      updatelist();
-    }
-    selectFlag.value = !selectFlag.value;
-    onMouseover(row, col);
-  }
-  function onMouseover(row, col) {
-    if (selectFlag.value) {
-      let squaresArr = [
-        start.row + '*' + start.col,
-        row + '*' + col,
-        start.row + '*' + col,
-        row + '*' + start.col,
-      ];
-      let min = squaresArr.sort()[0].split('*');
-      let max = squaresArr.sort()[3].split('*');
-      // 面积不可重叠
-      const flag = selected.data.some((f) => {
-        return isOverlap(f, {
-          width: Math.abs(start.col - col) + 1,
-          height: Math.abs(start.row - row) + 1,
-          left: min[1] - 1,
-          top: min[0] - 1,
-        });
-      });
-      if (!flag) {
-        // 宽高
-        activeData.width = Math.abs(start.col - col) + 1;
-        activeData.height = Math.abs(start.row - row) + 1;
-        // 定位
-        activeData.left = min[1] - 1;
-        activeData.top = min[0] - 1;
-        // xy轴最大最小值
-        activeData.minRow = min[0];
-        activeData.minCol = min[1];
-        activeData.maxRow = max[0];
-        activeData.maxCol = max[1];
-      }
-    }
+function selectItem(row, col) {
+  // 开始的坐标
+  if (!selectFlag.value) {
+    start.row = row;
+    start.col = col;
   }
-  // 删除选中的区域
-  function deleteItem(index) {
-    selected.data.splice(index, 1);
+  // 结束存储数据
+  if (selectFlag.value) {
+    selected.data.push(cloneDeep(activeData));
     selected.active = selected.data.length - 1;
-    updatelist();
-  }
-  // 取消选中区域
-  function onCancelSelect() {
-    selectFlag.value = false;
     clearActiveData();
+    updatelist();
   }
-  function onPositionSelect(index) {
-    selected.active = index;
-  }
-  // 提交
-  function updatelist() {
-    let deleteData = ['minRow', 'maxRow', 'minCol', 'maxCol'];
-    let tempData = [];
-    selected.data.forEach((s) => {
-      let obj = s;
-      deleteData.forEach((d) => {
-        delete obj[d];
+  selectFlag.value = !selectFlag.value;
+  onMouseover(row, col);
+}
+function onMouseover(row, col) {
+  if (selectFlag.value) {
+    let squaresArr = [
+      start.row + '*' + start.col,
+      row + '*' + col,
+      start.row + '*' + col,
+      row + '*' + start.col,
+    ];
+    let min = squaresArr.sort()[0].split('*');
+    let max = squaresArr.sort()[3].split('*');
+    // 面积不可重叠
+    const flag = selected.data.some((f) => {
+      return isOverlap(f, {
+        width: Math.abs(start.col - col) + 1,
+        height: Math.abs(start.row - row) + 1,
+        left: min[1] - 1,
+        top: min[0] - 1,
       });
-      tempData.push(obj);
     });
-    props.settingData.data.list = tempData;
-  }
-  // 清除activeData数据
-  function clearActiveData() {
-    for (var a in activeData) {
-      activeData[a] = 0;
-      if (a == 'src' || a == 'url') {
-        activeData[a] = '';
-      }
+    if (!flag) {
+      // 宽高
+      activeData.width = Math.abs(start.col - col) + 1;
+      activeData.height = Math.abs(start.row - row) + 1;
+      // 定位
+      activeData.left = min[1] - 1;
+      activeData.top = min[0] - 1;
+      // xy轴最大最小值
+      activeData.minRow = min[0];
+      activeData.minCol = min[1];
+      activeData.maxRow = max[0];
+      activeData.maxCol = max[1];
     }
   }
-  // 选择面积不可重叠
-  function isOverlap(obj1, obj2) {
-    const l1 = { x: obj1.left, y: obj1.top };
-    const r1 = { x: obj1.left + obj1.width, y: obj1.top + obj1.height };
-    const l2 = { x: obj2.left, y: obj2.top };
-    const r2 = { x: obj2.left + obj2.width, y: obj2.top + obj2.height };
-    if (l1.x >= r2.x || l2.x >= r1.x || l1.y >= r2.y || l2.y >= r1.y) return false;
-    return true;
+}
+// 删除选中的区域
+function deleteItem(index) {
+  selected.data.splice(index, 1);
+  selected.active = selected.data.length - 1;
+  updatelist();
+}
+// 取消选中区域
+function onCancelSelect() {
+  selectFlag.value = false;
+  clearActiveData();
+}
+function onPositionSelect(index) {
+  selected.active = index;
+}
+// 提交
+function updatelist() {
+  let deleteData = ['minRow', 'maxRow', 'minCol', 'maxCol'];
+  let tempData = [];
+  selected.data.forEach((s) => {
+    let obj = s;
+    deleteData.forEach((d) => {
+      delete obj[d];
+    });
+    tempData.push(obj);
+  });
+  props.settingData.data.list = tempData;
+}
+// 清除activeData数据
+function clearActiveData() {
+  for (var a in activeData) {
+    activeData[a] = 0;
+    if (a == 'src' || a == 'url') {
+      activeData[a] = '';
+    }
   }
+}
+// 选择面积不可重叠
+function isOverlap(obj1, obj2) {
+  const l1 = { x: obj1.left, y: obj1.top };
+  const r1 = { x: obj1.left + obj1.width, y: obj1.top + obj1.height };
+  const l2 = { x: obj2.left, y: obj2.top };
+  const r2 = { x: obj2.left + obj2.width, y: obj2.top + obj2.height };
+  if (l1.x >= r2.x || l2.x >= r1.x || l1.y >= r2.y || l2.y >= r1.y) return false;
+  return true;
+}
 </script>
 
 <style lang="scss" scoped>
-  .image-cube-setting {
-    .image-cube-map {
-      position: relative;
-      margin: 0 auto 16px;
-      border-spacing: 0;
-      border-collapse: collapse;
-      .map-item {
-        text-align: center;
-        border: 1px solid var(--sa-border);
-        box-sizing: border-box;
-        .el-icon {
-          font-size: 20px;
-          color: var(--sa-place);
-        }
-        &--active {
-          background-color: var(--sa-background-hex-hover);
-        }
+.image-cube-setting {
+  .image-cube-map {
+    position: relative;
+    margin: 0 auto 16px;
+    border-spacing: 0;
+    border-collapse: collapse;
+
+    .map-item {
+      text-align: center;
+      border: 1px solid var(--sa-border);
+      box-sizing: border-box;
+
+      .el-icon {
+        font-size: 20px;
+        color: var(--sa-place);
+      }
+
+      &--active {
+        background-color: var(--sa-background-hex-hover);
       }
-      .position-item {
+    }
+
+    .position-item {
+      position: absolute;
+      background: var(--sa-background-hex-active);
+      border: 1px solid var(--el-color-primary);
+      cursor: pointer;
+
+      .close {
+        display: none;
+        font-size: 12px;
+        color: var(--el-color-primary);
+        background: #fff;
+        border-radius: 6px;
         position: absolute;
+        top: -6px;
+        right: -6px;
+        z-index: 10;
+      }
+
+      &--active {
         background: var(--sa-background-hex-active);
         border: 1px solid var(--el-color-primary);
-        cursor: pointer;
+
         .close {
-          display: none;
-          font-size: 12px;
-          color: var(--el-color-primary);
-          background: #fff;
-          border-radius: 6px;
-          position: absolute;
-          top: -6px;
-          right: -6px;
-          z-index: 10;
-        }
-        &--active {
-          background: var(--sa-background-hex-active);
-          border: 1px solid var(--el-color-primary);
-          .close {
-            display: block;
-          }
+          display: block;
         }
       }
     }
   }
+}
 </style>

+ 24 - 35
src/app/shop/admin/decorate/page/component/center/comp/menuButton/setting.vue

@@ -25,38 +25,31 @@
           </el-form-item>
         </div>
       </div>
-      <dc-list
-        v-model="settingData.data.list"
-        :itemProp="{
-          src: '',
-          title: {
-            text: '',
-            color: '#000',
-          },
-          url: '',
-          badge: {
-            show: 0,
-            text: '',
-            color: '#FFFFFF',
-            bgColor: '#FF6000',
-          },
-        }"
-      >
+      <dc-list v-model="settingData.data.list" :itemProp="{
+        src: '',
+        title: {
+          text: '',
+          color: '#000',
+        },
+        url: '',
+        badge: {
+          show: 0,
+          text: '',
+          color: '#FFFFFF',
+          bgColor: '#FF6000',
+        },
+      }">
         <template #title>图标设置</template>
         <template #listItem="{ element }">
           <el-form-item label="图标">
             <div class="sa-flex">
-              <sa-uploader v-model="element.src" fileType="image"></sa-uploader>
+              <sa-upload-image v-model="element.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
+                :direct-upload="true" :size="100" />
               <div class="tip">建议尺寸:98*98</div>
             </div>
           </el-form-item>
           <el-form-item label="标题">
-            <dc-text-color
-              v-model="element.title"
-              placeholder="请输入标题"
-              maxlength="4"
-              show-word-limit
-            ></dc-text-color>
+            <dc-text-color v-model="element.title" placeholder="请输入标题" maxlength="4" show-word-limit></dc-text-color>
           </el-form-item>
           <el-form-item label="链接">
             <dc-url v-model="element.url"></dc-url>
@@ -66,12 +59,8 @@
           </el-form-item>
           <template v-if="element.badge.show">
             <el-form-item label="标签内容">
-              <dc-text-color
-                v-model="element.badge"
-                placeholder="请输入标签内容"
-                maxlength="4"
-                show-word-limit
-              ></dc-text-color>
+              <dc-text-color v-model="element.badge" placeholder="请输入标签内容" maxlength="4"
+                show-word-limit></dc-text-color>
             </el-form-item>
             <el-form-item label="标签背景">
               <dc-color-picker v-model="element.badge.bgColor"></dc-color-picker>
@@ -84,10 +73,10 @@
 </template>
 
 <script setup>
-  import dcColorPicker from '../../common/dc-color-picker.vue';
-  import dcList from '../../common/dc-list.vue';
-  import dcUrl from '../../common/dc-url.vue';
-  import dcTextColor from '../../common/dc-text-color.vue';
+import dcColorPicker from '../../common/dc-color-picker.vue';
+import dcList from '../../common/dc-list.vue';
+import dcUrl from '../../common/dc-url.vue';
+import dcTextColor from '../../common/dc-text-color.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const props = defineProps(['settingData', 'tabType']);
 </script>

+ 29 - 45
src/app/shop/admin/decorate/page/component/center/comp/menuGrid/setting.vue

@@ -19,50 +19,38 @@
           </el-form-item> -->
         </div>
       </div>
-      <dc-list
-        v-model="settingData.data.list"
-        :itemProp="{
-          src: '',
-          title: {
-            text: '',
-            color: '#333',
-          },
-          tip: {
-            text: '',
-            color: '#bbb',
-          },
-          url: '',
-          badge: {
-            show: 0,
-            text: '',
-            color: '#FFFFFF',
-            bgColor: '#FF6000',
-          },
-        }"
-      >
+      <dc-list v-model="settingData.data.list" :itemProp="{
+        src: '',
+        title: {
+          text: '',
+          color: '#333',
+        },
+        tip: {
+          text: '',
+          color: '#bbb',
+        },
+        url: '',
+        badge: {
+          show: 0,
+          text: '',
+          color: '#FFFFFF',
+          bgColor: '#FF6000',
+        },
+      }">
         <template #title>图标设置</template>
         <template #listItem="{ element }">
           <el-form-item label="图标">
             <div class="sa-flex">
-              <sa-uploader v-model="element.src" fileType="image"></sa-uploader>
+              <sa-upload-image v-model="element.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
+                :direct-upload="true" :size="100" />
               <div class="tip">建议尺寸:44*44</div>
             </div>
           </el-form-item>
           <el-form-item label="标题">
-            <dc-text-color
-              v-model="element.title"
-              placeholder="请输入标题"
-              maxlength="4"
-              show-word-limit
-            ></dc-text-color>
+            <dc-text-color v-model="element.title" placeholder="请输入标题" maxlength="4" show-word-limit></dc-text-color>
           </el-form-item>
           <el-form-item label="功能提示">
-            <dc-text-color
-              v-model="element.tip"
-              placeholder="请输入功能提示"
-              maxlength="4"
-              show-word-limit
-            ></dc-text-color>
+            <dc-text-color v-model="element.tip" placeholder="请输入功能提示" maxlength="4" show-word-limit></dc-text-color>
           </el-form-item>
           <el-form-item label="链接">
             <dc-url v-model="element.url"></dc-url>
@@ -72,12 +60,8 @@
           </el-form-item>
           <template v-if="element.badge.show">
             <el-form-item label="标签内容">
-              <dc-text-color
-                v-model="element.badge"
-                placeholder="请输入标签内容"
-                maxlength="4"
-                show-word-limit
-              ></dc-text-color>
+              <dc-text-color v-model="element.badge" placeholder="请输入标签内容" maxlength="4"
+                show-word-limit></dc-text-color>
             </el-form-item>
             <el-form-item label="标签背景">
               <dc-color-picker v-model="element.badge.bgColor"></dc-color-picker>
@@ -90,10 +74,10 @@
 </template>
 
 <script setup>
-  import dcColorPicker from '../../common/dc-color-picker.vue';
-  import dcList from '../../common/dc-list.vue';
-  import dcUrl from '../../common/dc-url.vue';
-  import dcTextColor from '../../common/dc-text-color.vue';
+import dcColorPicker from '../../common/dc-color-picker.vue';
+import dcList from '../../common/dc-list.vue';
+import dcUrl from '../../common/dc-url.vue';
+import dcTextColor from '../../common/dc-text-color.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const props = defineProps(['settingData', 'tabType']);
 </script>

+ 21 - 33
src/app/shop/admin/decorate/page/component/center/comp/menuList/setting.vue

@@ -1,44 +1,32 @@
 <template>
   <div class="setting">
     <template v-if="tabType == 'data'">
-      <dc-list
-        v-model="settingData.data.list"
-        :itemProp="{
-          src: '',
-          title: {
-            text: '',
-            color: '#333',
-          },
-          tip: {
-            text: '',
-            color: '#bbb',
-          },
-          url: '',
-        }"
-      >
+      <dc-list v-model="settingData.data.list" :itemProp="{
+        src: '',
+        title: {
+          text: '',
+          color: '#333',
+        },
+        tip: {
+          text: '',
+          color: '#bbb',
+        },
+        url: '',
+      }">
         <template #title>菜单设置</template>
         <template #listItem="{ element }">
           <el-form-item label="图标">
             <div class="sa-flex">
-              <sa-uploader v-model="element.src" fileType="image"></sa-uploader>
+              <sa-upload-image v-model="element.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']" :max-size="5"
+                :direct-upload="true" :size="100" />
               <div class="tip">建议尺寸:44*44</div>
             </div>
           </el-form-item>
           <el-form-item label="标题">
-            <dc-text-color
-              v-model="element.title"
-              placeholder="请输入标题"
-              maxlength="4"
-              show-word-limit
-            ></dc-text-color>
+            <dc-text-color v-model="element.title" placeholder="请输入标题" maxlength="4" show-word-limit></dc-text-color>
           </el-form-item>
           <el-form-item label="功能提示">
-            <dc-text-color
-              v-model="element.tip"
-              placeholder="请输入功能提示"
-              maxlength="4"
-              show-word-limit
-            ></dc-text-color>
+            <dc-text-color v-model="element.tip" placeholder="请输入功能提示" maxlength="4" show-word-limit></dc-text-color>
           </el-form-item>
           <el-form-item label="链接">
             <dc-url v-model="element.url"></dc-url>
@@ -50,10 +38,10 @@
 </template>
 
 <script setup>
-  import dcList from '../../common/dc-list.vue';
-  import dcUrl from '../../common/dc-url.vue';
-  import dcColorPicker from '../../common/dc-color-picker.vue';
-  import dcTextColor from '../../common/dc-text-color.vue';
+import dcList from '../../common/dc-list.vue';
+import dcUrl from '../../common/dc-url.vue';
+import dcColorPicker from '../../common/dc-color-picker.vue';
+import dcTextColor from '../../common/dc-text-color.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const props = defineProps(['settingData', 'tabType']);
 </script>

+ 36 - 18
src/app/shop/admin/decorate/page/component/center/comp/noticeBlock/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="notice-block sa-flex">
-    <sa-image :url="compData.data.src" radius="0" :suffix="null"></sa-image>
+    <sa-image :url="currentImageSrc" radius="0" :suffix="null"></sa-image>
     <el-divider direction="vertical"></el-divider>
     <div class="text" :style="{ color: compData.data.title.color }">
       {{ compData.data.title.text }}
@@ -9,25 +9,43 @@
 </template>
 
 <script setup>
-  const props = defineProps(['compData']);
+import { computed } from 'vue';
+
+const props = defineProps(['compData']);
+
+// 根据模式计算当前应该显示的图片
+const currentImageSrc = computed(() => {
+  const data = props.compData.data;
+
+  if (data.mode === 2 && data.customSrc && data.customSrc.length > 0) {
+    // 自定义模式且有上传图片
+    return data.customSrc[0];
+  } else {
+    // 系统图标模式或自定义模式无图片时使用默认
+    return data.src || '';
+  }
+});
 </script>
 
 <style lang="scss" scoped>
-  .notice-block {
-    height: 24px;
-    .sa-image {
-      height: 100%;
-    }
-    .el-divider--vertical {
-      margin: 0;
-      border-color: #eee;
-    }
-    .text {
-      min-height: 14px !important;
-      height: 14px;
-      font-size: 14px;
-      padding: 0 0 0 12px;
-      overflow: hidden;
-    }
+.notice-block {
+  height: 24px;
+
+  .sa-image {
+    height: 100%;
+  }
+
+  .el-divider--vertical {
+    margin: 0;
+    border-color: #eee;
+  }
+
+  .text {
+    min-height: 14px !important;
+    height: 14px;
+    font-size: 14px;
+    padding: 0 0 0 12px;
+    overflow: hidden;
   }
+}
 </style>

+ 68 - 48
src/app/shop/admin/decorate/page/component/center/comp/noticeBlock/setting.vue

@@ -12,23 +12,17 @@
           </el-form-item>
           <el-form-item v-if="settingData.data.mode == 1" label="公告图">
             <div class="sa-flex">
-              <sa-image
-                class="notice"
-                :class="[
-                  'notice-' + (nlindex + 1),
-                  settingData.data.src == nl ? 'notice--active' : '',
-                ]"
-                v-for="(nl, nlindex) in noticeList"
-                :key="nl"
-                :url="nl"
-                @click="settingData.data.src = nl"
-              ></sa-image>
+              <sa-image class="notice" :class="[
+                'notice-' + (nlindex + 1),
+                settingData.data.src == nl ? 'notice--active' : '',
+              ]" v-for="(nl, nlindex) in noticeList" :key="nl" :url="nl" @click="settingData.data.src = nl">
+              </sa-image>
             </div>
           </el-form-item>
-          <el-form-item v-if="settingData.data.mode == 2" label="图片">
+          <el-form-item v-if="settingData.data.mode == 2" label="自定义图片">
             <div class="sa-flex">
-              <sa-uploader v-model="settingData.data.src" fileType="image"></sa-uploader>
-              <div class="tip">建议尺寸:24*24</div>
+              <sa-upload-image v-model="settingData.data.customSrc" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+                :max-size="5" :direct-upload="true" :size="100" />
             </div>
           </el-form-item>
         </div>
@@ -37,10 +31,7 @@
         <div class="title">内容设置</div>
         <div class="content">
           <el-form-item label="公告内容">
-            <dc-text-color
-              v-model="settingData.data.title"
-              placeholder="请输入公告内容"
-            ></dc-text-color>
+            <dc-text-color v-model="settingData.data.title" placeholder="请输入公告内容"></dc-text-color>
           </el-form-item>
           <el-form-item label="链接">
             <dc-url v-model="settingData.data.url"></dc-url>
@@ -52,41 +43,70 @@
 </template>
 
 <script setup>
-  import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
-  import dcColorPicker from '../../common/dc-color-picker.vue';
-  import dcUrl from '../../common/dc-url.vue';
-  import dcTextColor from '../../common/dc-text-color.vue';
+import { computed, watch } from 'vue';
+import { checkUrl } from '@/sheep/utils/checkUrlSuffix';
+import dcColorPicker from '../../common/dc-color-picker.vue';
+import dcUrl from '../../common/dc-url.vue';
+import dcTextColor from '../../common/dc-text-color.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const props = defineProps(['settingData', 'tabType']);
 
-  const noticeList = [
-    checkUrl('/static/img/shop/decorate/notice-1.png'),
-    checkUrl('/static/img/shop/decorate/notice-2.png'),
-    checkUrl('/static/img/shop/decorate/notice-3.png'),
-  ];
-</script>
+const noticeList = [
+  checkUrl('/static/images/shop/decorate/notice-1.png'),
+  checkUrl('/static/images/shop/decorate/notice-2.png'),
+  checkUrl('/static/images/shop/decorate/notice-3.png'),
+];
 
-<style lang="scss" scoped>
-  .notice-block-setting {
-    .notice {
-      margin-right: 12px;
-      border: 1px solid var(--sa-border);
-      border-radius: 4px;
-      &--active {
-        border-color: var(--el-color-primary);
-      }
+// 监听模式切换,确保数据正确处理
+watch(() => props.settingData.data.mode, (newMode) => {
+  if (newMode === 1) {
+    // 系统图标模式,使用 src 字段
+    if (!props.settingData.data.src) {
+      props.settingData.data.src = noticeList[0];
     }
-    .notice-1 {
-      width: 72px;
-      height: 30px;
+  } else if (newMode === 2) {
+    // 自定义模式,初始化 customSrc 字段
+    if (!props.settingData.data.customSrc) {
+      props.settingData.data.customSrc = [];
     }
-    .notice-2 {
-      width: 46px;
-      height: 30px;
-    }
-    .notice-3 {
-      width: 34px;
-      height: 30px;
+  }
+}, { immediate: true });
+
+// 计算最终使用的图片地址
+const finalImageSrc = computed(() => {
+  if (props.settingData.data.mode === 1) {
+    return props.settingData.data.src;
+  } else {
+    return props.settingData.data.customSrc?.[0] || '';
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.notice-block-setting {
+  .notice {
+    margin-right: 12px;
+    border: 1px solid var(--sa-border);
+    border-radius: 4px;
+
+    &--active {
+      border-color: var(--el-color-primary);
     }
   }
+
+  .notice-1 {
+    width: 72px;
+    height: 30px;
+  }
+
+  .notice-2 {
+    width: 46px;
+    height: 30px;
+  }
+
+  .notice-3 {
+    width: 34px;
+    height: 30px;
+  }
+}
 </style>

+ 27 - 48
src/app/shop/admin/decorate/page/component/center/comp/scoreGoods/index.vue

@@ -7,69 +7,37 @@
             <sa-image :url="goods.image" :radius="0" :suffix="null"></sa-image>
             <div class="desc">
               <div>
-                <div
-                  v-if="compData.data.goodsFields.title.show"
-                  class="title sa-m-b-8"
-                  :class="`sa-table-line-${compData.data.mode}`"
-                  :style="{
+                <div v-if="compData.data.goodsFields.title.show" class="title sa-m-b-8"
+                  :class="`sa-table-line-${compData.data.mode}`" :style="{
                     color: compData.data.goodsFields.title.color,
-                  }"
-                >
+                  }">
                   {{ goods.title }}
                 </div>
-                <div
-                  v-if="compData.data.goodsFields.subtitle.show"
-                  class="subtitle sa-table-line-1 sa-m-b-12"
-                  :style="{
-                    color: compData.data.goodsFields.subtitle.color,
-                  }"
-                >
+                <div v-if="compData.data.goodsFields.subtitle.show" class="subtitle sa-table-line-1 sa-m-b-12" :style="{
+                  color: compData.data.goodsFields.subtitle.color,
+                }">
                   {{ goods.subtitle }}
                 </div>
               </div>
               <div>
                 <div class="sa-flex">
-                  <div
-                    class="score-price sa-flex"
-                    v-if="compData.data.goodsFields.score_price.show"
-                    :style="{
-                      color: compData.data.goodsFields.score_price.color,
-                    }"
-                  >
+                  <div class="score-price sa-flex" v-if="compData.data.goodsFields.score_price.show" :style="{
+                    color: compData.data.goodsFields.score_price.color,
+                  }">
                     <div v-if="Number(goods.score_price.price)">
-                      ¥{{ goods.score_price.price }}+
+                      ৳{{ goods.score_price?.price || goods.price }}+
                     </div>
                     <img class="score-icon sa-m-r-2" src="/static/images/shop/decorate/score.png" />
                     {{ goods.score_price.score }}
                   </div>
                 </div>
-                <s
-                  v-if="compData.data.goodsFields.price.show"
-                  class="price"
-                  :style="{
-                    color: compData.data.goodsFields.price.color,
-                  }"
-                >
-                  ¥{{ goods.original_price }}
+                <s v-if="compData.data.goodsFields.price.show" class="price" :style="{
+                  color: compData.data.goodsFields.price.color,
+                }">
+                  ৳{{ goods.otPrice }}
                 </s>
-                <div class="sales-stock sa-m-t-4">已售{{ goods.sales }}|库存{{ goods.stock }}</div>
-              </div>
-              <div
-                class="button"
-                :style="{
-                  background:
-                    compData.data.buyNowStyle.mode == 1
-                      ? 'linear-gradient(90deg,' +
-                        compData.data.buyNowStyle.color1 +
-                        ',' +
-                        compData.data.buyNowStyle.color2 +
-                        ')'
-                      : compData.data.buyNowStyle.src
-                      ? 'url(' + checkUrl(compData.data.buyNowStyle.src) + ')'
-                      : compData.data.buyNowStyle.src,
-                }"
-              >
-                {{ compData.data.buyNowStyle.mode == 1 ? compData.data.buyNowStyle.text : '' }}
+                <div class="sales-stock sa-m-t-4">{{ goods.sales || goods.ficti || 0 }} Sold | Stock {{ goods.stock }}
+                </div>
               </div>
             </div>
           </div>
@@ -110,30 +78,37 @@ const itemWrapStyle = computed(() => ({
     position: relative;
     overflow: hidden;
     font-size: 12px;
+
     .tag {
       position: absolute;
       top: 0;
       left: 0;
       z-index: 2;
+
       .sa-image {
         width: 36px !important;
         height: 22px !important;
       }
     }
+
     .desc {
       padding: 10px;
       flex: 1;
+
       .title {
         font-size: 14px;
       }
+
       .sales-stock {
         color: #c4c4c4;
       }
+
       .score-icon {
         width: 18px;
         height: 18px;
       }
     }
+
     .button {
       position: absolute;
       right: 10px;
@@ -148,20 +123,24 @@ const itemWrapStyle = computed(() => ({
       background-size: 100% 100% !important;
     }
   }
+
   .score-goods-wrap-1 {
     .sa-image {
       width: 100%;
       height: 140px;
     }
   }
+
   .score-goods-wrap-2 {
     .item-wrap {
       display: flex;
       height: 140px;
+
       .sa-image {
         width: 140px;
         height: 140px;
       }
+
       .desc {
         display: flex;
         flex-direction: column;

+ 2 - 1
src/app/shop/admin/decorate/page/component/center/comp/scoreGoods/setting.vue

@@ -49,7 +49,8 @@
             </el-form-item>
           </template>
           <el-form-item v-if="settingData.data.buyNowStyle.mode == 2" label="图片">
-            <div class="sa-flex"><sa-uploader v-model="settingData.data.buyNowStyle.src" fileType="image"></sa-uploader>
+            <div class="sa-flex"><sa-upload-image v-model="settingData.data.buyNowStyle.src" :max-count="1"
+                :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
               <div class="tip">建议尺寸:56*56</div>
             </div>
           </el-form-item>

+ 4 - 2
src/app/shop/admin/decorate/page/component/center/comp/seckill/setting.vue

@@ -62,7 +62,8 @@
               </el-form-item>
             </template>
             <el-form-item v-if="settingData.data.buyNowStyle.mode == 2" label="图片">
-              <sa-uploader v-model="settingData.data.buyNowStyle.src" fileType="image"></sa-uploader>
+              <sa-upload-image v-model="settingData.data.buyNowStyle.src" :max-count="1"
+                :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
             </el-form-item>
           </div>
         </div>
@@ -78,7 +79,8 @@
           </el-form-item>
           <el-form-item v-if="settingData.data.tagStyle.show == 1" label="上传图片">
             <div class="sa-flex">
-              <sa-uploader v-model="settingData.data.tagStyle.src" fileType="image"></sa-uploader>
+              <sa-upload-image v-model="settingData.data.tagStyle.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+                :max-size="5" :direct-upload="true" :size="100" />
               <span class="tip">建议尺寸:36*22</span>
             </div>
           </el-form-item>

+ 5 - 7
src/app/shop/admin/decorate/page/component/center/comp/subscribeWechatOfficialAccount/setting.vue

@@ -5,7 +5,8 @@
         <div class="title">关注公众号</div>
         <div class="content">
           <el-form-item label="二维码">
-            <sa-uploader v-model="settingData.data.src"></sa-uploader>
+            <sa-upload-image v-model="settingData.data.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+              :max-size="5" :direct-upload="true" :size="100" />
           </el-form-item>
           <el-form-item label="标题文字">
             <el-input v-model="settingData.data.title.text" placeholder="请输入标题文字"></el-input>
@@ -14,10 +15,7 @@
             <dc-color-picker v-model="settingData.data.title.textColor"></dc-color-picker>
           </el-form-item>
           <el-form-item label="副标题文字">
-            <el-input
-              v-model="settingData.data.subtitle.text"
-              placeholder="请输入副标题文字"
-            ></el-input>
+            <el-input v-model="settingData.data.subtitle.text" placeholder="请输入副标题文字"></el-input>
           </el-form-item>
           <el-form-item label="颜色">
             <dc-color-picker v-model="settingData.data.subtitle.textColor"></dc-color-picker>
@@ -29,7 +27,7 @@
 </template>
 
 <script setup>
-  import dcColorPicker from '../../common/dc-color-picker.vue';
+import dcColorPicker from '../../common/dc-color-picker.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const props = defineProps(['settingData', 'tabType']);
 </script>

+ 15 - 14
src/app/shop/admin/decorate/page/component/center/comp/videoPlayer/setting.vue

@@ -13,7 +13,8 @@
           </el-form-item>
           <el-form-item label="视频封面">
             <div class="sa-flex">
-              <sa-uploader v-model="settingData.data.src" fileType="image"></sa-uploader>
+              <sa-upload-image v-model="settingData.data.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+                :max-size="5" :direct-upload="true" :size="100" />
             </div>
           </el-form-item>
         </div>
@@ -23,20 +24,20 @@
 </template>
 
 <script setup>
-  import { useFile } from '@/sheep/components/sa-file/sa-file-modal.vue';
+import { useFile } from '@/sheep/components/sa-file/sa-file-modal.vue';
 
-  const props = defineProps(['settingData', 'tabType']);
+const props = defineProps(['settingData', 'tabType']);
 
-  function selectVideo() {
-    useFile(
-      {
-        fileType: 'video',
+function selectVideo() {
+  useFile(
+    {
+      fileType: 'video',
+    },
+    {
+      confirm: (data) => {
+        props.settingData.data.videoUrl = data.url;
       },
-      {
-        confirm: (data) => {
-          props.settingData.data.videoUrl = data.url;
-        },
-      },
-    );
-  }
+    },
+  );
+}
 </script>

+ 235 - 263
src/app/shop/admin/decorate/page/component/center/page/setting.vue

@@ -8,8 +8,8 @@
         </el-form-item>
         <el-form-item label="背景图片">
           <div class="sa-flex">
-            <sa-uploader v-model="settingData.background.src" fileType="image"></sa-uploader>
-            <div class="tip">建议宽度:750</div>
+            <sa-upload-image v-model="settingData.background.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+              :max-size="5" :direct-upload="true" :size="100" />
           </div>
         </el-form-item>
       </div>
@@ -22,14 +22,8 @@
             <el-radio label="normal">标准</el-radio>
             <el-radio label="inner">
               沉浸式
-              <el-popover
-                popper-class="title-popover"
-                placement="top-start"
-                :width="232"
-                trigger="hover"
-                :popper-options="{ boundariesElement: 'body' }"
-                content="沉侵式头部仅支持微信小程序、APP 建议页面第一个组件为图片展示类组件"
-              >
+              <el-popover popper-class="title-popover" placement="top-start" :width="232" trigger="hover"
+                :popper-options="{ boundariesElement: 'body' }" content="沉侵式头部仅支持微信小程序、APP 建议页面第一个组件为图片展示类组件">
                 <template #reference>
                   <el-icon class="popover-tip">
                     <Warning />
@@ -44,14 +38,8 @@
             <el-radio :label="0">关闭</el-radio>
             <el-radio :label="1">
               开启
-              <el-popover
-                popper-class="title-popover"
-                placement="top-start"
-                :width="232"
-                trigger="hover"
-                :popper-options="{ boundariesElement: 'body' }"
-                content="常驻显示关闭后,头部小组件将在页面滑动时淡入"
-              >
+              <el-popover popper-class="title-popover" placement="top-start" :width="232" trigger="hover"
+                :popper-options="{ boundariesElement: 'body' }" content="常驻显示关闭后,头部小组件将在页面滑动时淡入">
                 <template #reference>
                   <el-icon class="popover-tip">
                     <Warning />
@@ -71,7 +59,8 @@
           <dc-color-picker v-model="settingData.navbar.color"></dc-color-picker>
         </el-form-item>
         <el-form-item v-if="settingData.navbar.type == 'image'" label="选择图片">
-          <sa-uploader v-model="settingData.navbar.src" fileType="image"></sa-uploader>
+          <sa-upload-image v-model="settingData.navbar.src" :max-count="1" :accept="['jpg', 'jpeg', 'png']"
+            :max-size="5" :direct-upload="true" :size="100" />
         </el-form-item>
       </div>
     </div>
@@ -87,32 +76,20 @@
       <div class="content">
         <table class="navbar-map">
           <tbody>
-            <div
-              class="left"
-              :style="{
-                width: scale * 2 + 1 + 'px',
-              }"
-              v-if="templateDetailType == 'diypage'"
-            >
+            <div class="left" :style="{
+              width: scale * 2 + 1 + 'px',
+            }" v-if="templateDetailType == 'diypage'">
               <img src="/static/images/shop/decorate/header-diypage.png" />
             </div>
             <tr>
-              <td
-                class="map-item"
-                :class="
-                  state.tempData.minCol <= tdItem && tdItem <= state.tempData.maxCol
-                    ? 'map-item--active'
-                    : ''
-                "
-                :style="{
+              <td class="map-item" :class="state.tempData.minCol <= tdItem && tdItem <= state.tempData.maxCol
+                ? 'map-item--active'
+                : ''
+                " :style="{
                   width: scale + 'px',
                   height: scale + 'px',
-                }"
-                v-for="tdItem in 8"
-                :key="tdItem"
-                @click.stop="selectItem(1, tdItem)"
-                @mouseover="onMouseover(1, tdItem)"
-              >
+                }" v-for="tdItem in 8" :key="tdItem" @click.stop="selectItem(1, tdItem)"
+                @mouseover="onMouseover(1, tdItem)">
                 <el-icon>
                   <Plus />
                 </el-icon>
@@ -121,20 +98,14 @@
             <div class="WechatMiniProgram" v-if="platformType == 'WechatMiniProgram'">
               <img :src="'./static/images/shop/decorate/header-' + platformType + '.png'" />
             </div>
-            <div
-              class="position-item sa-flex sa-row-center"
+            <div class="position-item sa-flex sa-row-center"
               :class="state.selectedIndex == sindex ? 'position-item--active' : ''"
-              v-for="(style, sindex) in state.selectedData"
-              :style="{
+              v-for="(style, sindex) in state.selectedData" :style="{
                 width: style.width * scale + 'px',
                 height: style.height * scale + 'px',
                 top: style.top * scale + 'px',
                 left: style.left * scale + 'px',
-              }"
-              :key="style"
-              @mouseover="onCancelSelect"
-              @click.stop="onPositionSelect(sindex)"
-            >
+              }" :key="style" @mouseover="onCancelSelect" @click.stop="onPositionSelect(sindex)">
               {{ style.width }}*{{ style.height }}
               <el-icon class="close" @click.stop="deleteItem(sindex)">
                 <CircleCloseFilled />
@@ -152,25 +123,18 @@
           </el-form-item>
           <template v-if="state.selectedData[state.selectedIndex].type == 'text'">
             <el-form-item label="文字内容">
-              <el-input
-                v-model="state.selectedData[state.selectedIndex].text"
-                maxlength="10"
-                show-word-limit
-              ></el-input>
+              <el-input v-model="state.selectedData[state.selectedIndex].text" maxlength="10"
+                show-word-limit></el-input>
             </el-form-item>
             <el-form-item label="文字颜色">
-              <dc-color-picker
-                v-model="state.selectedData[state.selectedIndex].textColor"
-              ></dc-color-picker>
+              <dc-color-picker v-model="state.selectedData[state.selectedIndex].textColor"></dc-color-picker>
             </el-form-item>
           </template>
           <template v-if="state.selectedData[state.selectedIndex].type == 'image'">
             <el-form-item label="上传图片">
               <div class="sa-flex">
-                <sa-uploader
-                  v-model="state.selectedData[state.selectedIndex].src"
-                  fileType="image"
-                ></sa-uploader>
+                <sa-upload-image v-model="state.selectedData[state.selectedIndex].src" :max-count="1"
+                  :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
                 <div class="tip">建议尺寸:56*56</div>
               </div>
             </el-form-item>
@@ -180,11 +144,8 @@
           </template>
           <template v-if="state.selectedData[state.selectedIndex].type == 'search'">
             <el-form-item label="提示文字">
-              <el-input
-                v-model="state.selectedData[state.selectedIndex].placeholder"
-                maxlength="10"
-                show-word-limit
-              ></el-input>
+              <el-input v-model="state.selectedData[state.selectedIndex].placeholder" maxlength="10"
+                show-word-limit></el-input>
             </el-form-item>
             <el-form-item label="圆角">
               <dc-slider v-model="state.selectedData[state.selectedIndex].borderRadius"></dc-slider>
@@ -197,236 +158,247 @@
 </template>
 
 <script setup>
-  import { computed, reactive, ref, watch } from 'vue';
-  import { cloneDeep } from 'lodash';
-  import dcColorPicker from '../common/dc-color-picker.vue';
-  import dcList from '../common/dc-list.vue';
-  import dcUrl from '../common/dc-url.vue';
-  import dcSlider from '../common/dc-slider.vue';
+import { computed, reactive, ref, watch } from 'vue';
+import { cloneDeep } from 'lodash';
+import dcColorPicker from '../common/dc-color-picker.vue';
+import dcList from '../common/dc-list.vue';
+import dcUrl from '../common/dc-url.vue';
+import dcSlider from '../common/dc-slider.vue';
 
-  const props = defineProps(['settingData', 'platformType', 'templateDetailType']);
+const props = defineProps(['settingData', 'platformType', 'templateDetailType']);
 
-  const pType = computed(() => (props.platformType == 'WechatMiniProgram' ? 'mp' : 'app'));
+const pType = computed(() => (props.platformType == 'WechatMiniProgram' ? 'mp' : 'app'));
 
-  watch(
-    () => pType.value,
-    (v1, v2) => {
-      state.selectedData = props.settingData.navbar.list[v1] || [];
-      state.selectedIndex = props.settingData.navbar.list[v1].length > 0 ? 0 : null;
-    },
-  );
+watch(
+  () => pType.value,
+  (v1, v2) => {
+    state.selectedData = props.settingData.navbar.list[v1] || [];
+    state.selectedIndex = props.settingData.navbar.list[v1].length > 0 ? 0 : null;
+  },
+);
 
-  const state = reactive({
-    tempData: {
-      width: 0,
-      height: 0,
-      top: 0,
-      left: 0,
-      minRow: 0,
-      maxRow: 0,
-      minCol: 0,
-      maxCol: 0,
-      type: 'text',
+const state = reactive({
+  tempData: {
+    width: 0,
+    height: 0,
+    top: 0,
+    left: 0,
+    minRow: 0,
+    maxRow: 0,
+    minCol: 0,
+    maxCol: 0,
+    type: 'text',
 
-      text: '',
-      textColor: '#111111',
+    text: '',
+    textColor: '#111111',
 
-      src: '',
-      url: '',
+    src: '',
+    url: '',
 
-      placeholder: '',
-      borderRadius: 0,
-    },
+    placeholder: '',
+    borderRadius: 0,
+  },
 
-    selectFlag: false,
+  selectFlag: false,
 
-    selectedData: props.settingData.navbar.list[pType.value] || [],
-    selectedIndex: props.settingData.navbar.list[pType.value].length > 0 ? 0 : null,
-  });
+  selectedData: props.settingData.navbar.list[pType.value] || [],
+  selectedIndex: props.settingData.navbar.list[pType.value].length > 0 ? 0 : null,
+});
 
-  const scale = ref(38);
-  const start = reactive({ row: 0, col: 0 });
+const scale = ref(38);
+const start = reactive({ row: 0, col: 0 });
 
-  function selectItem(row, col) {
-    // 开始的坐标
-    if (!state.selectFlag) {
-      start.row = row;
-      start.col = col;
-    }
-    // 结束存储数据
-    if (state.selectFlag) {
-      state.selectedData.push(cloneDeep(state.tempData));
-      state.selectedIndex = state.selectedData.length - 1;
-      clearTempData();
-      updatelist();
-    }
-    state.selectFlag = !state.selectFlag;
-    onMouseover(row, col);
+function selectItem(row, col) {
+  // 开始的坐标
+  if (!state.selectFlag) {
+    start.row = row;
+    start.col = col;
   }
-  function onMouseover(row, col) {
-    if (state.selectFlag) {
-      let squaresArr = [
-        start.row + '*' + start.col,
-        row + '*' + col,
-        start.row + '*' + col,
-        row + '*' + start.col,
-      ];
-      let min = squaresArr.sort()[0].split('*');
-      let max = squaresArr.sort()[3].split('*');
-      // 面积不可重叠
-      const flag = state.selectedData.some((f) => {
-        return isOverlap(f, {
-          width: Math.abs(start.col - col) + 1,
-          height: Math.abs(start.row - row) + 1,
-          left: min[1] - 1,
-          top: min[0] - 1,
-        });
-      });
-      if (!flag) {
-        // 宽高
-        state.tempData.width = Math.abs(start.col - col) + 1;
-        state.tempData.height = Math.abs(start.row - row) + 1;
-        // 定位
-        state.tempData.left = min[1] - 1;
-        state.tempData.top = min[0] - 1;
-        // xy轴最大最小值
-        state.tempData.minRow = min[0];
-        state.tempData.minCol = min[1];
-        state.tempData.maxRow = max[0];
-        state.tempData.maxCol = max[1];
-      }
-    }
-  }
-  // 删除选中的区域
-  function deleteItem(index) {
-    state.selectedData.splice(index, 1);
+  // 结束存储数据
+  if (state.selectFlag) {
+    state.selectedData.push(cloneDeep(state.tempData));
     state.selectedIndex = state.selectedData.length - 1;
-    updatelist();
-  }
-  // 取消选中区域
-  function onCancelSelect() {
-    state.selectFlag = false;
     clearTempData();
+    updatelist();
   }
-  function onPositionSelect(index) {
-    state.selectedIndex = index;
-  }
-  // 提交
-  function updatelist() {
-    let deleteData = ['minRow', 'maxRow', 'minCol', 'maxCol'];
-    let tempData = [];
-    state.selectedData.forEach((s) => {
-      let obj = s;
-      deleteData.forEach((d) => {
-        delete obj[d];
+  state.selectFlag = !state.selectFlag;
+  onMouseover(row, col);
+}
+function onMouseover(row, col) {
+  if (state.selectFlag) {
+    let squaresArr = [
+      start.row + '*' + start.col,
+      row + '*' + col,
+      start.row + '*' + col,
+      row + '*' + start.col,
+    ];
+    let min = squaresArr.sort()[0].split('*');
+    let max = squaresArr.sort()[3].split('*');
+    // 面积不可重叠
+    const flag = state.selectedData.some((f) => {
+      return isOverlap(f, {
+        width: Math.abs(start.col - col) + 1,
+        height: Math.abs(start.row - row) + 1,
+        left: min[1] - 1,
+        top: min[0] - 1,
       });
-      tempData.push(obj);
     });
-    props.settingData.navbar.list[pType.value] = tempData;
+    if (!flag) {
+      // 宽高
+      state.tempData.width = Math.abs(start.col - col) + 1;
+      state.tempData.height = Math.abs(start.row - row) + 1;
+      // 定位
+      state.tempData.left = min[1] - 1;
+      state.tempData.top = min[0] - 1;
+      // xy轴最大最小值
+      state.tempData.minRow = min[0];
+      state.tempData.minCol = min[1];
+      state.tempData.maxRow = max[0];
+      state.tempData.maxCol = max[1];
+    }
   }
-  // 清除数据
-  function clearTempData() {
-    state.tempData = cloneDeep({
-      width: 0,
-      height: 0,
-      top: 0,
-      left: 0,
-      minRow: 0,
-      maxRow: 0,
-      minCol: 0,
-      maxCol: 0,
+}
+// 删除选中的区域
+function deleteItem(index) {
+  state.selectedData.splice(index, 1);
+  state.selectedIndex = state.selectedData.length - 1;
+  updatelist();
+}
+// 取消选中区域
+function onCancelSelect() {
+  state.selectFlag = false;
+  clearTempData();
+}
+function onPositionSelect(index) {
+  state.selectedIndex = index;
+}
+// 提交
+function updatelist() {
+  let deleteData = ['minRow', 'maxRow', 'minCol', 'maxCol'];
+  let tempData = [];
+  state.selectedData.forEach((s) => {
+    let obj = s;
+    deleteData.forEach((d) => {
+      delete obj[d];
+    });
+    tempData.push(obj);
+  });
+  props.settingData.navbar.list[pType.value] = tempData;
+}
+// 清除数据
+function clearTempData() {
+  state.tempData = cloneDeep({
+    width: 0,
+    height: 0,
+    top: 0,
+    left: 0,
+    minRow: 0,
+    maxRow: 0,
+    minCol: 0,
+    maxCol: 0,
 
-      type: 'text',
+    type: 'text',
 
-      text: '',
-      textColor: '#111111',
+    text: '',
+    textColor: '#111111',
 
-      src: '',
-      url: '',
+    src: '',
+    url: '',
 
-      placeholder: '',
-      borderRadius: 0,
-    });
-  }
-  // 选择面积不可重叠
-  function isOverlap(obj1, obj2) {
-    const l1 = { x: obj1.left, y: obj1.top };
-    const r1 = { x: obj1.left + obj1.width, y: obj1.top + obj1.height };
-    const l2 = { x: obj2.left, y: obj2.top };
-    const r2 = { x: obj2.left + obj2.width, y: obj2.top + obj2.height };
-    if (l1.x >= r2.x || l2.x >= r1.x || l1.y >= r2.y || l2.y >= r1.y) return false;
-    return true;
-  }
+    placeholder: '',
+    borderRadius: 0,
+  });
+}
+// 选择面积不可重叠
+function isOverlap(obj1, obj2) {
+  const l1 = { x: obj1.left, y: obj1.top };
+  const r1 = { x: obj1.left + obj1.width, y: obj1.top + obj1.height };
+  const l2 = { x: obj2.left, y: obj2.top };
+  const r2 = { x: obj2.left + obj2.width, y: obj2.top + obj2.height };
+  if (l1.x >= r2.x || l2.x >= r1.x || l1.y >= r2.y || l2.y >= r1.y) return false;
+  return true;
+}
 </script>
 
 <style lang="scss" scoped>
-  .navbar-map {
-    position: relative;
-    margin: 0 auto 16px;
-    border-spacing: 0;
-    border-collapse: collapse;
-    .map-item {
-      text-align: center;
-      border: 1px solid var(--sa-border);
-      box-sizing: border-box;
-      .el-icon {
-        font-size: 20px;
-        color: var(--sa-place);
-      }
-      &--active {
-        background-color: var(--sa-background-hex-hover);
-      }
+.navbar-map {
+  position: relative;
+  margin: 0 auto 16px;
+  border-spacing: 0;
+  border-collapse: collapse;
+
+  .map-item {
+    text-align: center;
+    border: 1px solid var(--sa-border);
+    box-sizing: border-box;
+
+    .el-icon {
+      font-size: 20px;
+      color: var(--sa-place);
     }
-    .left {
-      position: absolute;
-      top: -1px;
-      left: -1px;
-      z-index: 2;
-      height: 40px;
-      background: #fff;
-      display: flex;
-      align-items: center;
-      img {
-        width: 38px;
-      }
+
+    &--active {
+      background-color: var(--sa-background-hex-hover);
     }
-    .WechatMiniProgram {
-      position: absolute;
-      top: -1px;
-      right: -1px;
-      width: 76px;
-      height: 40px;
-      background: #fff;
-      display: flex;
-      align-items: center;
-      img {
-        width: 100%;
-        height: 30px;
-      }
+  }
+
+  .left {
+    position: absolute;
+    top: -1px;
+    left: -1px;
+    z-index: 2;
+    height: 40px;
+    background: #fff;
+    display: flex;
+    align-items: center;
+
+    img {
+      width: 38px;
+    }
+  }
+
+  .WechatMiniProgram {
+    position: absolute;
+    top: -1px;
+    right: -1px;
+    width: 76px;
+    height: 40px;
+    background: #fff;
+    display: flex;
+    align-items: center;
+
+    img {
+      width: 100%;
+      height: 30px;
     }
-    .position-item {
+  }
+
+  .position-item {
+    position: absolute;
+    background: var(--sa-background-hex-active);
+    border: 1px solid var(--el-color-primary);
+    cursor: pointer;
+
+    .close {
+      display: none;
+      font-size: 12px;
+      color: var(--el-color-primary);
+      background: #fff;
+      border-radius: 6px;
       position: absolute;
+      top: -6px;
+      right: -6px;
+      z-index: 10;
+    }
+
+    &--active {
       background: var(--sa-background-hex-active);
       border: 1px solid var(--el-color-primary);
-      cursor: pointer;
+
       .close {
-        display: none;
-        font-size: 12px;
-        color: var(--el-color-primary);
-        background: #fff;
-        border-radius: 6px;
-        position: absolute;
-        top: -6px;
-        right: -6px;
-        z-index: 10;
-      }
-      &--active {
-        background: var(--sa-background-hex-active);
-        border: 1px solid var(--el-color-primary);
-        .close {
-          display: block;
-        }
+        display: block;
       }
     }
   }
+}
 </style>

+ 125 - 186
src/app/shop/admin/decorate/page/component/right/index.vue

@@ -14,41 +14,25 @@
             <div class="tab">
               <div class="tab-wrap sa-flex">
                 <template v-for="ts in tabList">
-                  <div
-                    v-if="
-                      ts.type == 'data' &&
-                      !['userCard', 'orderCard', 'walletCard', 'couponCard'].includes(type)
-                    "
-                    :class="[ts.type, tabType == ts.type ? 'is-active' : '']"
-                    @click="tabType = ts.type"
-                  >
+                  <div v-if="
+                    ts.type == 'data' &&
+                    !['userCard', 'orderCard', 'walletCard', 'couponCard'].includes(type)
+                  " :class="[ts.type, tabType == ts.type ? 'is-active' : '']" @click="tabType = ts.type">
                     {{ ts.name }}
                   </div>
-                  <div
-                    v-if="ts.type == 'style' && !['floatMenu', 'popupImage', 'page'].includes(type)"
-                    :class="[ts.type, tabType == ts.type ? 'is-active' : '']"
-                    @click="tabType = ts.type"
-                  >
+                  <div v-if="ts.type == 'style' && !['floatMenu', 'popupImage', 'page'].includes(type)"
+                    :class="[ts.type, tabType == ts.type ? 'is-active' : '']" @click="tabType = ts.type">
                     {{ ts.name }}
                   </div>
-                  <div
-                    v-if="ts.type == 'css'"
-                    :class="[ts.type, tabType == ts.type ? 'is-active' : '']"
-                    @click="tabType = ts.type"
-                  >
+                  <div v-if="ts.type == 'css'" :class="[ts.type, tabType == ts.type ? 'is-active' : '']"
+                    @click="tabType = ts.type">
                     {{ ts.name }}
                   </div>
                 </template>
               </div>
             </div>
-            <compSetting
-              v-if="tabType == 'data'"
-              :type="type"
-              :settingData="rightData"
-              :tabType="tabType"
-              :platformType="platformType"
-              :templateDetailType="templateDetailType"
-            ></compSetting>
+            <compSetting v-if="tabType == 'data'" :type="type" :settingData="rightData" :tabType="tabType"
+              :platformType="platformType" :templateDetailType="templateDetailType"></compSetting>
             <template v-if="tabType == 'style' && rightData.style">
               <div class="card">
                 <div class="title">组件样式</div>
@@ -60,72 +44,39 @@
                         <el-radio label="image">图片</el-radio>
                       </el-radio-group>
                     </el-form-item>
-                    <el-form-item
-                      v-if="rightData.style.background.type == 'color'"
-                      label="选择颜色"
-                    >
-                      <dc-color-picker
-                        v-model="rightData.style.background.bgColor"
-                      ></dc-color-picker>
+                    <el-form-item v-if="rightData.style.background.type == 'color'" label="选择颜色">
+                      <dc-color-picker v-model="rightData.style.background.bgColor"></dc-color-picker>
                     </el-form-item>
-                    <el-form-item
-                      v-if="rightData.style.background.type == 'image'"
-                      label="选择图片"
-                    >
-                      <sa-uploader
-                        v-model="rightData.style.background.bgImage"
-                        fileType="image"
-                      ></sa-uploader>
+                    <el-form-item v-if="rightData.style.background.type == 'image'" label="选择图片">
+                      <sa-upload-image v-model="rightData.style.background.bgImage" :max-count="1"
+                        :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
                     </el-form-item>
                   </template>
-                  <el-form-item
-                    v-if="rightData.style.marginTop || rightData.style.marginTop == 0"
-                    label="上间距"
-                  >
+                  <el-form-item v-if="rightData.style.marginTop || rightData.style.marginTop == 0" label="上间距">
                     <dc-slider v-model="rightData.style.marginTop"></dc-slider>
                   </el-form-item>
-                  <el-form-item
-                    v-if="rightData.style.marginRight || rightData.style.marginRight == 0"
-                    label="右间距"
-                  >
+                  <el-form-item v-if="rightData.style.marginRight || rightData.style.marginRight == 0" label="右间距">
                     <dc-slider v-model="rightData.style.marginRight"></dc-slider>
                   </el-form-item>
-                  <el-form-item
-                    v-if="rightData.style.marginBottom || rightData.style.marginBottom == 0"
-                    label="下间距"
-                  >
+                  <el-form-item v-if="rightData.style.marginBottom || rightData.style.marginBottom == 0" label="下间距">
                     <dc-slider v-model="rightData.style.marginBottom"></dc-slider>
                   </el-form-item>
-                  <el-form-item
-                    v-if="rightData.style.marginLeft || rightData.style.marginLeft == 0"
-                    label="左间距"
-                  >
+                  <el-form-item v-if="rightData.style.marginLeft || rightData.style.marginLeft == 0" label="左间距">
                     <dc-slider v-model="rightData.style.marginLeft"></dc-slider>
                   </el-form-item>
-                  <el-form-item
-                    v-if="rightData.style.borderRadiusTop || rightData.style.borderRadiusTop == 0"
-                    label="上圆角"
-                  >
+                  <el-form-item v-if="rightData.style.borderRadiusTop || rightData.style.borderRadiusTop == 0"
+                    label="上圆角">
                     <dc-slider v-model="rightData.style.borderRadiusTop"></dc-slider>
                   </el-form-item>
-                  <el-form-item
-                    v-if="
-                      rightData.style.borderRadiusBottom || rightData.style.borderRadiusBottom == 0
-                    "
-                    label="下圆角"
-                  >
+                  <el-form-item v-if="
+                    rightData.style.borderRadiusBottom || rightData.style.borderRadiusBottom == 0
+                  " label="下圆角">
                     <dc-slider v-model="rightData.style.borderRadiusBottom"></dc-slider>
                   </el-form-item>
-                  <el-form-item
-                    v-if="rightData.style.padding || rightData.style.padding == 0"
-                    label="内间距"
-                  >
+                  <el-form-item v-if="rightData.style.padding || rightData.style.padding == 0" label="内间距">
                     <dc-slider v-model="rightData.style.padding"></dc-slider>
                   </el-form-item>
-                  <el-form-item
-                    v-if="rightData.style.height || rightData.style.height == 0"
-                    label="高度"
-                  >
+                  <el-form-item v-if="rightData.style.height || rightData.style.height == 0" label="高度">
                     <dc-slider v-model="rightData.style.height" :mult="6"></dc-slider>
                   </el-form-item>
                 </div>
@@ -142,22 +93,12 @@
                         <el-radio label="image">图片</el-radio>
                       </el-radio-group>
                     </el-form-item>
-                    <el-form-item
-                      v-if="rightData.background.type == 'color'"
-                      label="选择颜色"
-                    >
-                      <dc-color-picker
-                        v-model="rightData.background.bgColor"
-                      ></dc-color-picker>
+                    <el-form-item v-if="rightData.background.type == 'color'" label="选择颜色">
+                      <dc-color-picker v-model="rightData.background.bgColor"></dc-color-picker>
                     </el-form-item>
-                    <el-form-item
-                      v-if="rightData.background.type == 'image'"
-                      label="选择图片"
-                    >
-                      <sa-uploader
-                        v-model="rightData.background.bgImage"
-                        fileType="image"
-                      ></sa-uploader>
+                    <el-form-item v-if="rightData.background.type == 'image'" label="选择图片">
+                      <sa-upload-image v-model="rightData.background.bgImage" :max-count="1"
+                        :accept="['jpg', 'jpeg', 'png']" :max-size="5" :direct-upload="true" :size="100" />
                     </el-form-item>
                   </template>
                 </div>
@@ -178,10 +119,7 @@
         </el-form>
       </template>
     </el-scrollbar>
-    <div
-      class="collapse-icon right-close sa-flex sa-row-center"
-      @click="emit('update:panelCollapse', !panelCollapse)"
-    >
+    <div class="collapse-icon right-close sa-flex sa-row-center" @click="emit('update:panelCollapse', !panelCollapse)">
       <el-icon v-if="!panelCollapse">
         <ArrowLeft />
       </el-icon>
@@ -192,118 +130,119 @@
   </div>
 </template>
 <script setup>
-  /**
-   * @property {String} type 组件类型
-   * @property {Object} rightData 组件数据
-   */
-  import { ref, watch } from 'vue';
-  import { compNameObj } from '../../data';
+/**
+ * @property {String} type 组件类型
+ * @property {Object} rightData 组件数据
+ */
+import { ref, watch } from 'vue';
+import { compNameObj } from '../../data';
 
-  import compSetting from './setting.vue';
+import compSetting from './setting.vue';
 
-  import dcColorPicker from '../center/common/dc-color-picker.vue';
-  import dcSlider from '../center/common/dc-slider.vue';
-  import { isString } from 'lodash';
+import dcColorPicker from '../center/common/dc-color-picker.vue';
+import dcSlider from '../center/common/dc-slider.vue';
+import { isString } from 'lodash';
 
-  const emit = defineEmits(['update:panelCollapse']);
-  const props = defineProps([
-    'panelCollapse',
-    'type',
-    'rightData',
-    'platformType',
-    'templateDetailType',
-  ]);
+const emit = defineEmits(['update:panelCollapse']);
+const props = defineProps([
+  'panelCollapse',
+  'type',
+  'rightData',
+  'platformType',
+  'templateDetailType',
+]);
 
-  const tabType = ref(
-    ['userCard', 'orderCard', 'walletCard', 'couponCard'].includes(props.type) ? 'style' : 'data',
-  );
+const tabType = ref(
+  ['userCard', 'orderCard', 'walletCard', 'couponCard'].includes(props.type) ? 'style' : 'data',
+);
 
-  const tabList = [
-    {
-      name: '内容',
-      type: 'data',
-    },
-    {
-      name: '样式',
-      type: 'style',
-    },
-    {
-      name: '数据',
-      type: 'css',
-    },
-  ];
-  watch(
-    () => props.type,
-    () => {
-      tabType.value = ['userCard', 'orderCard', 'walletCard', 'couponCard'].includes(props.type)
-        ? 'style'
-        : 'data';
-    },
-  );
+const tabList = [
+  {
+    name: '内容',
+    type: 'data',
+  },
+  {
+    name: '样式',
+    type: 'style',
+  },
+  {
+    name: '数据',
+    type: 'css',
+  },
+];
+watch(
+  () => props.type,
+  () => {
+    tabType.value = ['userCard', 'orderCard', 'walletCard', 'couponCard'].includes(props.type)
+      ? 'style'
+      : 'data';
+  },
+);
 </script>
 
 <style lang="scss" scoped>
-  .page-right {
-    flex-shrink: 0;
-    width: 344px;
-    height: 100%;
-    background: var(--sa-background-assist);
-    box-shadow: 0px 0px 0.24rem rgb(0 0 0 / 16%);
-    transition: all 0.2s;
-    position: relative;
-    z-index: 1;
+.page-right {
+  flex-shrink: 0;
+  width: 344px;
+  height: 100%;
+  background: var(--sa-background-assist);
+  box-shadow: 0px 0px 0.24rem rgb(0 0 0 / 16%);
+  transition: all 0.2s;
+  position: relative;
+  z-index: 1;
 
-    .name {
-      padding: 8px 12px;
-      color: var(--sa-subtitle);
-      font-size: 14px;
+  .name {
+    padding: 8px 12px;
+    color: var(--sa-subtitle);
+    font-size: 14px;
 
-      img {
-        width: 24px;
-        height: 24px;
-        margin-right: 8px;
-      }
+    img {
+      width: 24px;
+      height: 24px;
+      margin-right: 8px;
     }
+  }
 
-    .right-close {
-      left: -20px;
-      transform: rotate(180deg);
-    }
+  .right-close {
+    left: -20px;
+    transform: rotate(180deg);
+  }
 
-    .cssCard {
-      background-color: #1e1e1e !important;
-      color: #ccc;
-      margin: 0 20px;
-      padding: 10px;
-      word-break: break-all;
-    }
+  .cssCard {
+    background-color: #1e1e1e !important;
+    color: #ccc;
+    margin: 0 20px;
+    padding: 10px;
+    word-break: break-all;
+  }
 
-    .cssKey {
-      color: #9cdcfe;
-      flex-shrink: 0;
-    }
+  .cssKey {
+    color: #9cdcfe;
+    flex-shrink: 0;
+  }
 
-    .cssValue {
-      color: #ccc;
-    }
+  .cssValue {
+    color: #ccc;
+  }
 
-    &--collapse {
-      width: 0;
-    }
+  &--collapse {
+    width: 0;
   }
-  @media only screen and (max-width: 768px) {
-    .page-right {
-      position: absolute !important;
-      right: 0;
-      z-index: 100;
-      transition: transform 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
+}
+
+@media only screen and (max-width: 768px) {
+  .page-right {
+    position: absolute !important;
+    right: 0;
+    z-index: 100;
+    transition: transform 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
 
-      &--collapse {
-        width: 0 !important;
-      }
+    &--collapse {
+      width: 0 !important;
     }
   }
+}
 </style>
 <style lang="scss">
-  @import './setting.scss';
+@import './setting.scss';
 </style>

+ 53 - 34
src/app/shop/admin/decorate/page/data.js

@@ -81,29 +81,29 @@ export const basicList = [];
 
 // 组件列表
 export const compList = [
-  {
-    name: '会员组件',
-    type: '0',
-    show: ['user', 'diypage'],
-    data: [
-      {
-        name: '会员卡片',
-        type: 'userCard',
-      },
-      {
-        name: '订单卡片',
-        type: 'orderCard',
-      },
-      {
-        name: '资产卡片',
-        type: 'walletCard',
-      },
-      {
-        name: '卡券卡片',
-        type: 'couponCard',
-      },
-    ],
-  },
+  // {
+  //   name: '会员组件',
+  //   type: '0',
+  //   show: ['user', 'diypage'],
+  //   data: [
+  //     {
+  //       name: '会员卡片',
+  //       type: 'userCard',
+  //     },
+  //     {
+  //       name: '订单卡片',
+  //       type: 'orderCard',
+  //     },
+  //     {
+  //       name: '资产卡片',
+  //       type: 'walletCard',
+  //     },
+  //     {
+  //       name: '卡券卡片',
+  //       type: 'couponCard',
+  //     },
+  //   ],
+  // },
   {
     name: '基础组件',
     type: '1',
@@ -138,10 +138,10 @@ export const compList = [
         name: '商品卡片',
         type: 'goodsCard',
       },
-      {
-        name: '商品栏',
-        type: 'goodsShelves',
-      },
+      // {
+      //   name: '商品栏',
+      //   type: 'goodsShelves',
+      // },
     ],
   },
   {
@@ -812,13 +812,32 @@ export const compNameObj = {
 export function handleTempData(data) {
   data.data.forEach(async (t) => {
     if (['goodsCard', 'goodsShelves'].includes(t.type)) {
-      const { error, data } = await goodsApi.goods.select(
-        {
-          search: JSON.stringify({ id: [t.data.goodsIds.join(','), 'in'] }),
-        },
-        'select',
-      );
-      t.data.goodsList = error === 0 ? data : [];
+      try {
+        const { code, data } = await goodsApi.goods.list({
+          ids: t.data.goodsIds,
+          pageSize: 100,
+        });
+
+        if (code === '200' && data?.list) {
+          // 将获取到的商品数据转换为装修组件需要的格式
+          const goodsList = data.list.map((item) => ({
+            id: item.id,
+            title: item.storeName,
+            image: item.image,
+            price: item.price,
+            otPrice: item.otPrice,
+            stock: item.stock,
+            sales: item.ficti || item.sales || 0,
+            storeName: item.storeName,
+          }));
+          t.data.goodsList = goodsList;
+        } else {
+          t.data.goodsList = [];
+        }
+      } catch (error) {
+        console.error('获取商品详情失败:', error);
+        t.data.goodsList = [];
+      }
     } else if (t.type == 'richtext') {
       const { error, data } = await dataApi.richtext.select(
         {

+ 15 - 2
src/app/shop/admin/decorate/template/index.vue

@@ -150,7 +150,7 @@ onMounted(() => {
       position: relative;
       flex-direction: column;
       width: 246px;
-      height: 450px;
+      height: 500px;
       border: 1px solid var(--sa-space);
       box-shadow: 0 0 4px rgba(89, 89, 89, 0.2);
       box-sizing: border-box;
@@ -175,9 +175,22 @@ onMounted(() => {
       .image-preview {
         background: var(--sa-background);
         display: flex;
-        align-items: center;
+        align-items: flex-start;
         justify-content: center;
         overflow: hidden;
+        height: 400px;
+
+        .sa-image {
+          width: 100%;
+          height: 100%;
+
+          :deep(img) {
+            width: 100%;
+            height: 100%;
+            object-fit: cover !important;
+            object-position: top center !important;
+          }
+        }
 
         .no-image {
           display: flex;