Jelajahi Sumber

feat: 优化商品信息图片显示

叶静 3 minggu lalu
induk
melakukan
802e4456e3

+ 3 - 3
src/app/shop/admin/goods/goods/index.vue

@@ -50,8 +50,7 @@
               <el-table-column :label="t('modules.goods.goodsInfo')" min-width="300">
                 <template #default="scope">
                   <div class="sa-flex">
-                    <el-image :src="scope.row.image" style="width: 60px; height: 60px; margin-right: 12px"
-                      fit="cover" />
+                    <el-image :src="scope.row.image" class="goods-image" fit="cover" />
                     <div>
                       <div class="goods-title">{{ scope.row.storeName }}</div>
                     </div>
@@ -78,7 +77,7 @@
               <el-table-column prop="itemNumber" :label="t('modules.goods.goodsNumber')" min-width="100"
                 align="center"></el-table-column>
               <el-table-column prop="stock" :label="t('form.stock')" min-width="100" align="center"></el-table-column>
-              <el-table-column sortable="custom" prop="sales" :label="t('modules.goods.sales')" min-width="100"
+              <el-table-column sortable="custom" prop="sa les" :label="t('modules.goods.sales')" min-width="100"
                 align="center"></el-table-column>
               <el-table-column :label="t('modules.goods.goodsSupplier')" min-width="120" align="center">
                 <template #default="scope">
@@ -727,6 +726,7 @@ onMounted(() => {
     line-height: 1.4;
   }
 
+
   .goods-supplier {
     font-size: 12px;
     color: var(--sa-subfont);

+ 280 - 296
src/app/shop/admin/goods/goods/select.vue

@@ -2,39 +2,21 @@
   <el-container class="goods-select">
     <el-container>
       <el-header class="goods-search">
-        <sa-search-simple
-          :searchFields="searchFields"
-          :defaultValues="defaultSearchValues"
-          @search="(val) => getData(1, val)"
-          @reset="getData(1)"
-        />
+        <sa-search-simple :searchFields="searchFields" :defaultValues="defaultSearchValues"
+          @search="(val) => getData(1, val)" @reset="getData(1)" />
       </el-header>
       <el-main v-loading="loading">
-        <el-table
-          class="sa-table"
-          ref="multipleTableRef"
-          :data="table.list"
-          @select="selectRow"
-          @select-all="selectAll"
-          stripe
-        >
+        <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 v-if="modal.params.multiple" type="selection" width="48"></el-table-column>
           <el-table-column prop="id" :label="t('modules.goods.goodsNumber')" align="center" />
           <el-table-column :label="t('modules.goods.goodsInfo')">
             <template #default="scope">
               <div class="sa-flex">
-                <el-image
-                  :src="scope.row.image"
-                  style="width: 60px; height: 60px; margin-right: 12px"
-                  fit="cover"
-                />
+                <el-image :src="scope.row.image" class="goods-image" fit="cover" />
                 <div>
                   <div class="goods-title">{{ scope.row.storeName }}</div>
                 </div>
@@ -47,12 +29,7 @@
               <div>{{ t('modules.goods.onlinePrice') }}: ৳{{ scope.row.price }}</div>
             </template>
           </el-table-column>
-          <el-table-column
-            prop="stock"
-            :label="t('modules.goods.goodsStock')"
-            min-width="100"
-            align="center"
-          >
+          <el-table-column prop="stock" :label="t('modules.goods.goodsStock')" min-width="100" align="center">
             <template #default="scope">
               {{ scope.row.stock || 0 }}
             </template>
@@ -61,13 +38,8 @@
             <template #default="scope">
               <template v-if="modal.params.ftype == 'score_shop'">
                 <span v-if="scope.row.is_score_shop">{{ t('modules.goods.alreadyJoined') }}</span>
-                <el-button
-                  v-if="!scope.row.is_score_shop"
-                  link
-                  type="primary"
-                  @click="singleSelect(scope.row.id)"
-                  >{{ t('modules.goods.join') }}</el-button
-                >
+                <el-button v-if="!scope.row.is_score_shop" link type="primary" @click="singleSelect(scope.row.id)">{{
+                  t('modules.goods.join') }}</el-button>
               </template>
               <template v-else>
                 <el-button link type="primary" @click="singleSelect(scope.row.id)">{{
@@ -78,10 +50,7 @@
           </el-table-column>
         </el-table>
       </el-main>
-      <el-footer
-        class="sa-footer--submit"
-        :class="modal.params.multiple ? 'sa-row-between' : 'sa-row-right'"
-      >
+      <el-footer class="sa-footer--submit" :class="modal.params.multiple ? 'sa-row-between' : 'sa-row-right'">
         <sa-pagination :pageData="pageData" layout="total, prev, pager, next" @updateFn="getData" />
         <el-button v-if="modal.params.multiple" type="primary" @click="confirm">{{
           t('common.confirm')
@@ -92,301 +61,316 @@
 </template>
 
 <script setup>
-  /**@property {Array} ids - 已经选中
-   * @param {Boolean} multiple - 是否多选
-   * @param {Number} max - 多选时最大数量(0 代表限制数量)
-   * @param {String} ftype - 打开来源(score_shop)
-   */
-  import { nextTick, onMounted, reactive, ref } from 'vue';
-  import { api } from '../goods.service';
-  import { ElMessage } from 'element-plus';
-  import { usePagination } from '@/sheep/hooks';
-  import { useI18n } from 'vue-i18n';
+/**@property {Array} ids - 已经选中
+ * @param {Boolean} multiple - 是否多选
+ * @param {Number} max - 多选时最大数量(0 代表限制数量)
+ * @param {String} ftype - 打开来源(score_shop)
+ */
+import { nextTick, onMounted, reactive, ref } from 'vue';
+import { api } from '../goods.service';
+import { ElMessage } from 'element-plus';
+import { usePagination } from '@/sheep/hooks';
+import { useI18n } from 'vue-i18n';
 
-  const { t } = useI18n();
-  const { pageData } = usePagination();
+const { t } = useI18n();
+const { pageData } = usePagination();
 
-  const emit = defineEmits(['modalCallBack']);
-  const props = defineProps(['modal']);
+const emit = defineEmits(['modalCallBack']);
+const props = defineProps(['modal']);
 
-  // 搜索字段配置
-  const searchFields = reactive({
-    title: {
-      type: 'input',
-      get label() {
-        return t('modules.goods.goodsName');
-      },
-      get placeholder() {
-        return t('form.inputNameOrNumber');
-      },
-      width: 200,
+// 搜索字段配置
+const searchFields = reactive({
+  title: {
+    type: 'input',
+    get label() {
+      return t('modules.goods.goodsName');
     },
-  });
+    get placeholder() {
+      return t('form.inputNameOrNumber');
+    },
+    width: 200,
+  },
+});
 
-  // 默认搜索值
-  const defaultSearchValues = reactive({
-    title: '',
-  });
+// 默认搜索值
+const defaultSearchValues = reactive({
+  title: '',
+});
 
-  const loading = ref(true);
-  const table = reactive({
-    list: [],
-    ids: props.modal.params.ids || [],
-    selectedGoods: new Map(), // 使用Map存储选中的商品完整数据,key为商品id
-    preloadedGoods: new Map(), // 存储预加载的商品数据
-  });
-  async function getData(page, searchParams = {}) {
-    if (page) pageData.page = page;
-    loading.value = true;
+const loading = ref(true);
+const table = reactive({
+  list: [],
+  ids: props.modal.params.ids || [],
+  selectedGoods: new Map(), // 使用Map存储选中的商品完整数据,key为商品id
+  preloadedGoods: new Map(), // 存储预加载的商品数据
+});
+async function getData(page, searchParams = {}) {
+  if (page) pageData.page = page;
+  loading.value = true;
 
-    try {
-      const params = {
-        page: pageData.page,
-        size: pageData.size,
-        isShow: 1,
-        ...searchParams,
-      };
-      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;
+  try {
+    const params = {
+      page: pageData.page,
+      size: pageData.size,
+      isShow: 1,
+      ...searchParams,
+    };
+    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;
+      nextTick(() => {
+        setDefaultSelected();
+        // 初始化当前页面的选中商品数据
+        initSelectedGoods();
+      });
     }
+  } catch (error) {
+    console.error('获取商品列表失败:', error);
+    table.list = []; // 确保出错时也有默认值
+  } finally {
+    loading.value = false;
   }
+}
 
-  // 设置默认选中
-  function setDefaultSelected() {
-    // 确保 table.list 存在且为数组
-    if (!table.list || !Array.isArray(table.list)) {
-      console.warn('table.list is not available or not an array:', table.list);
-      return;
-    }
-
-    table.list.forEach((item) => {
-      if (table.ids?.includes(item.id)) {
-        multipleTableRef.value?.toggleRowSelection(item, true);
-        toggleRowSelection('row', [item], item);
-      }
-    });
+// 设置默认选中
+function setDefaultSelected() {
+  // 确保 table.list 存在且为数组
+  if (!table.list || !Array.isArray(table.list)) {
+    console.warn('table.list is not available or not an array:', table.list);
+    return;
   }
 
-  const multipleTableRef = ref();
-  function selectRow(selection, row) {
-    if (
-      !props.modal.params.max ||
-      (props.modal.params.max && props.modal.params.max > table.ids.length)
-    ) {
-      if (table.ids.includes(row.id)) {
-        let index = table.ids.findIndex((id) => id == row.id);
-        table.ids.splice(index, 1);
-        // 从选中商品数据中移除
-        table.selectedGoods.delete(row.id);
-      } else {
-        table.ids.push(row.id);
-        // 添加到选中商品数据中
-        addToSelectedGoods(row);
-      }
+  table.list.forEach((item) => {
+    if (table.ids?.includes(item.id)) {
+      multipleTableRef.value?.toggleRowSelection(item, true);
+      toggleRowSelection('row', [item], item);
     }
-    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) => {
-          if (table.ids.includes(l.id)) {
-            let index = table.ids.findIndex((id) => id == l.id);
-            table.ids.splice(index, 1);
-            // 从选中商品数据中移除
-            table.selectedGoods.delete(l.id);
-          }
-        });
-      } else {
-        // 全选:添加当前页面的所有项
-        table.list.forEach((l) => {
-          if (!table.ids.includes(l.id)) {
-            table.ids.push(l.id);
-            // 添加到选中商品数据中
-            addToSelectedGoods(l);
-          }
-        });
-      }
+  });
+}
+
+const multipleTableRef = ref();
+function selectRow(selection, row) {
+  if (
+    !props.modal.params.max ||
+    (props.modal.params.max && props.modal.params.max > table.ids.length)
+  ) {
+    if (table.ids.includes(row.id)) {
+      let index = table.ids.findIndex((id) => id == row.id);
+      table.ids.splice(index, 1);
+      // 从选中商品数据中移除
+      table.selectedGoods.delete(row.id);
+    } else {
+      table.ids.push(row.id);
+      // 添加到选中商品数据中
+      addToSelectedGoods(row);
     }
-    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) => {
-          if (table.ids?.includes(l.id)) {
-            multipleTableRef.value?.toggleRowSelection(l, true);
-          }
-        });
-      }
-      ElMessage({
-        type: 'warning',
-        message: t('message.selectionLimitReached'),
+  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) => {
+        if (table.ids.includes(l.id)) {
+          let index = table.ids.findIndex((id) => id == l.id);
+          table.ids.splice(index, 1);
+          // 从选中商品数据中移除
+          table.selectedGoods.delete(l.id);
+        }
+      });
+    } else {
+      // 全选:添加当前页面的所有项
+      table.list.forEach((l) => {
+        if (!table.ids.includes(l.id)) {
+          table.ids.push(l.id);
+          // 添加到选中商品数据中
+          addToSelectedGoods(l);
+        }
       });
-      return false;
     }
   }
+  toggleRowSelection('all', selection);
+}
 
-  // 添加商品到选中数据集合
-  function addToSelectedGoods(item) {
-    const goodsData = {
-      id: item.id,
-      title: item.title || item.storeName,
-      image: item.image,
-      price: item.price || item.current_price || item.original_price || item.otPrice,
-      stock: item.stock,
-    };
-    table.selectedGoods.set(item.id, goodsData);
-  }
-
-  // 初始化时添加已选中的商品数据
-  function initSelectedGoods() {
-    table.list.forEach((item) => {
-      if (table.ids.includes(item.id)) {
-        addToSelectedGoods(item);
-      }
-    });
-  }
-
-  // 预加载所有已选中商品的完整数据
-  async function preloadSelectedGoods() {
-    if (!table.ids.length) return;
-
-    try {
-      // 批量获取已选中商品的详细信息
-      const promises = table.ids.map(async (id) => {
-        try {
-          const { code, data } = await api.goods.info(id);
-          if (code === '200' && data) {
-            addToSelectedGoods(data);
-            table.preloadedGoods.set(id, data);
-          }
-        } catch (error) {
-          console.warn(`获取商品${id}详情失败:`, error);
+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) => {
+        if (table.ids?.includes(l.id)) {
+          multipleTableRef.value?.toggleRowSelection(l, true);
         }
       });
-
-      await Promise.allSettled(promises);
-    } catch (error) {
-      console.error('预加载选中商品数据失败:', error);
     }
+    ElMessage({
+      type: 'warning',
+      message: t('message.selectionLimitReached'),
+    });
+    return false;
   }
+}
 
-  function singleSelect(id) {
-    // 找到对应的商品数据
-    const selectedItem = table.list.find((item) => item.id === id);
-    if (selectedItem) {
-      const goodsData = {
-        id: selectedItem.id,
-        title: selectedItem.title || selectedItem.storeName,
-        image: selectedItem.image,
-        price:
-          selectedItem.price ||
-          selectedItem.current_price ||
-          selectedItem.original_price ||
-          selectedItem.otPrice,
-        stock: selectedItem.stock,
-      };
+// 添加商品到选中数据集合
+function addToSelectedGoods(item) {
+  const goodsData = {
+    id: item.id,
+    title: item.title || item.storeName,
+    image: item.image,
+    price: item.price || item.current_price || item.original_price || item.otPrice,
+    stock: item.stock,
+  };
+  table.selectedGoods.set(item.id, goodsData);
+}
 
-      emit('modalCallBack', {
-        event: 'confirm',
-        data: goodsData,
-      });
+// 初始化时添加已选中的商品数据
+function initSelectedGoods() {
+  table.list.forEach((item) => {
+    if (table.ids.includes(item.id)) {
+      addToSelectedGoods(item);
     }
+  });
+}
+
+// 预加载所有已选中商品的完整数据
+async function preloadSelectedGoods() {
+  if (!table.ids.length) return;
+
+  try {
+    // 批量获取已选中商品的详细信息
+    const promises = table.ids.map(async (id) => {
+      try {
+        const { code, data } = await api.goods.info(id);
+        if (code === '200' && data) {
+          addToSelectedGoods(data);
+          table.preloadedGoods.set(id, data);
+        }
+      } catch (error) {
+        console.warn(`获取商品${id}详情失败:`, error);
+      }
+    });
+
+    await Promise.allSettled(promises);
+  } catch (error) {
+    console.error('预加载选中商品数据失败:', error);
   }
+}
 
-  function confirm() {
-    // 将Map转换为数组,获取所有选中的商品数据
-    const selectedGoodsData = Array.from(table.selectedGoods.values());
+function singleSelect(id) {
+  // 找到对应的商品数据
+  const selectedItem = table.list.find((item) => item.id === id);
+  if (selectedItem) {
+    const goodsData = {
+      id: selectedItem.id,
+      title: selectedItem.title || selectedItem.storeName,
+      image: selectedItem.image,
+      price:
+        selectedItem.price ||
+        selectedItem.current_price ||
+        selectedItem.original_price ||
+        selectedItem.otPrice,
+      stock: selectedItem.stock,
+    };
 
     emit('modalCallBack', {
       event: 'confirm',
-      data: selectedGoodsData,
+      data: goodsData,
     });
   }
+}
+
+function confirm() {
+  // 将Map转换为数组,获取所有选中的商品数据
+  const selectedGoodsData = Array.from(table.selectedGoods.values());
 
-  onMounted(async () => {
-    // 先预加载已选中的商品数据
-    await preloadSelectedGoods();
-    // 再获取第一页数据
-    getData();
+  emit('modalCallBack', {
+    event: 'confirm',
+    data: selectedGoodsData,
   });
+}
+
+onMounted(async () => {
+  // 先预加载已选中的商品数据
+  await preloadSelectedGoods();
+  // 再获取第一页数据
+  getData();
+});
 </script>
 <style lang="scss" scoped>
-  .goods-select {
-    .goods-search {
-      --el-header-height: auto;
-      padding-top: var(--sa-padding);
-    }
+.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;
-    }
-    .title {
-      height: 16px;
-      line-height: 16px;
-      font-size: 12px;
-      font-weight: 400;
-      color: var(--sa-subtitle);
-    }
-    .price {
-      line-height: 16px;
-      font-size: 12px;
-      font-weight: 400;
-      color: #ff4d4f;
-    }
+  .sa-footer--submit {
+    height: auto;
+    padding: 16px;
+    border-top: 1px solid var(--sa-border);
+    background: #fff;
+  }
 
-    .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;
-    }
-    .sa-footer--submit {
-      display: flex;
-      align-items: center;
-      flex-wrap: wrap;
-      --el-footer-height: auto;
-      min-height: 60px;
-    }
+  .title {
+    height: 16px;
+    line-height: 16px;
+    font-size: 12px;
+    font-weight: 400;
+    color: var(--sa-subtitle);
   }
-  @media only screen and (max-width: 768px) {
-    .goods-price {
-      display: none;
-    }
+
+  .price {
+    line-height: 16px;
+    font-size: 12px;
+    font-weight: 400;
+    color: #ff4d4f;
+  }
+
+  .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;
+  }
+
+  .goods-image {
+    width: 60px;
+    height: 60px;
+    margin-right: 12px;
+    border-radius: 6px;
+    overflow: hidden;
+    flex-shrink: 0;
+    object-fit: cover;
+    aspect-ratio: 1 / 1;
+  }
+
+  .sa-footer--submit {
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    --el-footer-height: auto;
+    min-height: 60px;
+  }
+}
+
+@media only screen and (max-width: 768px) {
+  .goods-price {
+    display: none;
   }
+}
 </style>

+ 1 - 1
src/app/shop/admin/order/order/detail.vue

@@ -154,7 +154,7 @@
         <el-table class="sa-table" :data="state.orderDetail.orderInfoVO" stripe border>
           <el-table-column :label="t('modules.order.goodsImage')" width="100" align="center">
             <template #default="{ row }">
-              <el-image :src="row.image" style="width: 60px; height: 60px; border-radius: 4px" fit="cover" />
+              <el-image :src="row.image" class="goods-image" fit="cover" />
             </template>
           </el-table-column>
           <el-table-column :label="t('modules.order.goodsName')" prop="productName" />

+ 1 - 2
src/app/shop/admin/order/order/index.vue

@@ -60,8 +60,7 @@
               <el-table-column :label="t('modules.order.goodsInfo')" min-width="300">
                 <template #default="scope">
                   <div class="sa-flex" v-if="scope.row.orderInfoVO && scope.row.orderInfoVO.length > 0">
-                    <el-image :src="scope.row.orderInfoVO[0].image"
-                      style="width: 60px; height: 60px; margin-right: 12px" fit="cover" />
+                    <el-image :src="scope.row.orderInfoVO[0].image" class="goods-image" fit="cover" />
                     <div>
                       <div class="goods-title">{{ scope.row.orderInfoVO[0].productName }}</div>
                     </div>

+ 13 - 0
src/assets/css/sa-global.scss

@@ -506,3 +506,16 @@ button {
   bottom: 0;
   border-bottom: 1px solid var(--el-link-text-color);
 }
+
+.goods-image {
+  width: 64px;
+  height: 64px;
+  margin-right: 12px;
+  border-radius: 6px;
+  overflow: hidden;
+  flex-shrink: 0;
+  img {
+    aspect-ratio: 1 / 1;
+    object-fit: cover !important;
+  }
+}