sunshibo 4 недель назад
Родитель
Сommit
792f81b1cd

+ 10 - 3
pages/index/index.vue

@@ -120,11 +120,18 @@ function normalizeHomeCuisine(item) {
     if (typeof t === 'string') return { text: t, type: 'normal' };
     return { text: t?.text ?? t?.tagName ?? t?.name ?? t?.label ?? '', type: t?.type ?? 'normal' };
   }).filter((t) => t.text !== '' && t.text != null);
-  const images = item?.images;
-  const imageUrl = Array.isArray(images) ? images[0] : images;
+  // 商品图片:取第一张,若为逗号分隔字符串则截取逗号前的第一张
+  const firstImage = (val) => {
+    if (val == null || val === '') return '';
+    if (Array.isArray(val)) return (val[0] != null && val[0] !== '') ? String(val[0]).split(',')[0].trim() : '';
+    const str = String(val).trim();
+    return str ? str.split(/[,,]/)[0].trim() : '';
+  };
+  const images = item?.images ?? item?.cuisineImage ?? item?.image;
+  const imageUrl = firstImage(images) || firstImage(item?.cuisineImage) || firstImage(item?.image) || '';
   return {
     id: item?.id ?? item?.cuisineId ?? '',
-    image: imageUrl ?? item?.cuisineImage ?? item?.image ?? 'img/icon/shop.png',
+    image: imageUrl || 'img/icon/shop.png',
     name: item?.cuisineName ?? item?.name ?? '',
     sales: item?.monthlySales ?? item?.sales ?? 0,
     tags: tagArrNormalized,

+ 23 - 1
pages/orderFood/components/FoodCard.vue

@@ -1,7 +1,7 @@
 <template>
   <view class="food-card">
     <view class="food-item">
-      <image class="food-image" :src="food.images" mode="aspectFill" @click="handleFoodClick" />
+      <image class="food-image" :src="foodImageUrl" mode="aspectFill" @click="handleFoodClick" />
       <view class="food-info">
         <view class="food-header">
           <view class="food-title">{{ food.name }}</view>
@@ -74,6 +74,28 @@ const isTableware = computed(() => {
   return Number(id) === -1;
 });
 
+// 商品图片:与首页 index 一致,取第一张(逗号分隔则截取逗号前第一张),支持对象 { url/path/src },再通过 getFileUrl 转成完整地址
+function firstImage(val) {
+  if (val == null || val === '') return '';
+  if (Array.isArray(val)) {
+    const first = val[0];
+    if (first != null && first !== '') {
+      if (typeof first === 'object' && first !== null) return first.url ?? first.path ?? first.src ?? first.link ?? '';
+      return String(first).split(/[,,]/)[0].trim();
+    }
+    return '';
+  }
+  if (typeof val === 'object') return val.url ?? val.path ?? val.src ?? val.link ?? '';
+  const str = String(val).trim();
+  return str ? str.split(/[,,]/)[0].trim() : '';
+}
+const foodImageUrl = computed(() => {
+  const f = props.food;
+  if (!f) return getFileUrl('img/icon/shop.png');
+  const imageUrl = firstImage(f.images) || firstImage(f.cuisineImage) || firstImage(f.image) || firstImage(f.imageUrl) || firstImage(f.pic) || firstImage(f.cover) || '';
+  return getFileUrl(imageUrl || 'img/icon/shop.png');
+});
+
 // 后端返回的 tags 统一为 [{ text, type }] 便于绑定(兼容多种字段名与格式)
 const normalizedTags = computed(() => {
   const food = props.food;

+ 18 - 10
pages/orderFood/index.vue

@@ -67,6 +67,14 @@ import { getFileUrl } from "@/utils/file.js";
 import { DiningOrderFood, GetStoreCategories, GetStoreCuisines, GetStoreDetail, getOrderSseConfig, GetOrderCart, PostOrderCartAdd, PostOrderCartUpdate, PostOrderCartClear, PutOrderCartUpdateTableware, GetUserOwnedCouponList } from "@/api/dining.js";
 import { createSSEConnection } from "@/utils/sse.js";
 
+// 商品图片:取第一张,若为逗号分隔字符串则截取逗号前的第一张
+function firstImage(val) {
+  if (val == null || val === '') return '';
+  if (Array.isArray(val)) return (val[0] != null && val[0] !== '') ? String(val[0]).split(/[,,]/)[0].trim() : '';
+  const str = String(val).trim();
+  return str ? str.split(/[,,]/)[0].trim() : '';
+}
+
 const storeName = ref(''); // 店铺名称,用于导航栏标题
 const tableId = ref(''); // 桌号ID,来自上一页 url 参数 tableid,用于接口入参
 const tableNumber = ref(''); // 桌号展示,来自 dining/page-info 接口返回的 tableNumber
@@ -312,11 +320,11 @@ const fetchCuisinesByCategoryId = async (categoryId) => {
   try {
     const res = await GetStoreCuisines({ categoryId });
     const list = res?.list ?? res?.data ?? (Array.isArray(res) ? res : []);
-    const normalized = (Array.isArray(list) ? list : []).map(item => ({
-      ...item,
-      quantity: item.quantity ?? 0,
-      categoryId: item.categoryId ?? categoryId
-    }));
+    const normalized = (Array.isArray(list) ? list : []).map(item => {
+      const rawImg = item.images ?? item.cuisineImage ?? item.image ?? item.imageUrl ?? item.pic ?? item.cover ?? '';
+      const img = firstImage(rawImg) || (typeof rawImg === 'string' ? rawImg : (rawImg && (rawImg.url ?? rawImg.path ?? rawImg.src) ? (rawImg.url ?? rawImg.path ?? rawImg.src) : ''));
+      return { ...item, images: img, image: img, cuisineImage: img, quantity: item.quantity ?? 0, categoryId: item.categoryId ?? categoryId };
+    });
     // 其他分类的菜品原样保留(含已选数量)
     const rest = foodList.value.filter(f => !sameCategory(f.categoryId, categoryId));
     // 本分类:按菜品 id 从整份列表里取已选数量,id 一致则自动带上数量
@@ -757,11 +765,11 @@ onLoad(async (options) => {
             if (firstCategoryId) {
               const cuisinesRes = await GetStoreCuisines({ categoryId: firstCategoryId });
               const cuisinesList = cuisinesRes?.list ?? cuisinesRes?.data ?? (Array.isArray(cuisinesRes) ? cuisinesRes : []);
-              foodList.value = (Array.isArray(cuisinesList) ? cuisinesList : []).map(item => ({
-                ...item,
-                quantity: item.quantity ?? 0,
-                categoryId: item.categoryId ?? firstCategoryId
-              }));
+              foodList.value = (Array.isArray(cuisinesList) ? cuisinesList : []).map(item => {
+                const rawImg = item.images ?? item.cuisineImage ?? item.image ?? item.imageUrl ?? item.pic ?? item.cover ?? '';
+                const img = firstImage(rawImg) || (typeof rawImg === 'string' ? rawImg : (rawImg && (rawImg.url ?? rawImg.path ?? rawImg.src) ? (rawImg.url ?? rawImg.path ?? rawImg.src) : ''));
+                return { ...item, images: img, image: img, cuisineImage: img, quantity: item.quantity ?? 0, categoryId: item.categoryId ?? firstCategoryId };
+              });
               console.log('默认分类菜品:', cuisinesRes);
               mergeCartIntoFoodList();
             }

+ 20 - 1
pages/orderInfo/index.vue

@@ -89,6 +89,22 @@ const currentPage = ref({ current: 1, history: 1 });
 const loading = ref(false);
 const noMore = ref({ current: false, history: false });
 
+// 取第一张图:逗号分隔取首段,数组取首项,对象取 url/path/src(与 orderFood/ FoodCard 一致)
+function firstImage(val) {
+  if (val == null || val === '') return '';
+  if (Array.isArray(val)) {
+    const first = val[0];
+    if (first != null && first !== '') {
+      if (typeof first === 'object' && first !== null) return first.url ?? first.path ?? first.src ?? first.link ?? '';
+      return String(first).split(/[,,]/)[0].trim();
+    }
+    return '';
+  }
+  if (typeof val === 'object') return val.url ?? val.path ?? val.src ?? val.link ?? '';
+  const str = String(val).trim();
+  return str ? str.split(/[,,]/)[0].trim() : '';
+}
+
 // records 每项:order 为订单信息,cuisineItems 为菜品数组;兼容直接平铺的旧结构
 function normalizeOrder(record) {
   const order = record?.order ?? record;
@@ -102,7 +118,10 @@ function normalizeOrder(record) {
 
   const goodsCount = items.reduce((sum, it) => sum + (Number(it?.quantity) || 0), 0) || order?.dinerCount || 0;
   const goodsImages = items
-    .map((it) => it?.cuisineImage ?? it?.image ?? it?.imageUrl ?? '')
+    .map((it) => {
+      const raw = it?.cuisineImage ?? it?.image ?? it?.imageUrl ?? it?.pic ?? it?.cover ?? it?.images ?? '';
+      return firstImage(raw) || (typeof raw === 'string' ? raw.split(/[,,]/)[0]?.trim() : '');
+    })
     .filter((url) => url && String(url).trim());
 
   // storeName 取自 data.records[].order.storeName

+ 25 - 8
pages/orderInfo/orderDetail.vue

@@ -196,12 +196,29 @@ const foodList = ref([]);
 // 菜品详情展示:包含所有项(含餐具 id/cuisineId === -1)
 const displayFoodList = computed(() => foodList.value ?? []);
 
-// 菜品图片:餐具(id/cuisineId=-1)用本地图
+// 取第一张图:逗号分隔取首段,数组取首项,对象取 url/path/src(与 orderInfo/index、FoodCard 一致)
+function firstImage(val) {
+  if (val == null || val === '') return '';
+  if (Array.isArray(val)) {
+    const first = val[0];
+    if (first != null && first !== '') {
+      if (typeof first === 'object' && first !== null) return first.url ?? first.path ?? first.src ?? first.link ?? '';
+      return String(first).split(/[,,]/)[0].trim();
+    }
+    return '';
+  }
+  if (typeof val === 'object') return val.url ?? val.path ?? val.src ?? val.link ?? '';
+  const str = String(val).trim();
+  return str ? str.split(/[,,]/)[0].trim() : '';
+}
+
+// 菜品图片:餐具(id/cuisineId=-1)用本地图;其他取首图并 getFileUrl
 function getItemImage(item) {
   if (Number(item?.id ?? item?.cuisineId) === -1) return '/static/utensilFee.png';
-  const raw = item?.image ?? item?.cuisineImage ?? item?.imageUrl ?? '';
-  if (typeof raw === 'string' && (raw.startsWith('http') || raw.startsWith('//'))) return raw;
-  return getFileUrl(raw || 'img/icon/shop.png');
+  const raw = item?.image ?? item?.cuisineImage ?? item?.imageUrl ?? item?.pic ?? item?.cover ?? item?.images ?? '';
+  const url = firstImage(raw) || (typeof raw === 'string' ? raw.split(/[,,]/)[0]?.trim() : '');
+  if (url && typeof url === 'string' && (url.startsWith('http') || url.startsWith('//'))) return url;
+  return getFileUrl(url || 'img/icon/shop.png');
 }
 
 // 金额保留两位小数
@@ -229,16 +246,16 @@ function normalizeTags(raw) {
   }).filter((t) => t.text !== '' && t.text != null);
 }
 
-// 将接口订单项转为列表项
+// 将接口订单项转为列表项(图片取首张,逗号/数组/对象与 orderInfo/index 一致)
 function normalizeOrderItem(item) {
   const rawTags = item?.tags ?? item?.tagList ?? item?.tagNames ?? item?.labels ?? item?.tag;
-  const images = item?.images ?? item?.image;
-  const imageUrl = Array.isArray(images) ? images[0] : images;
+  const rawImg = item?.images ?? item?.image ?? item?.cuisineImage ?? item?.imageUrl ?? item?.pic ?? item?.cover ?? '';
+  const imageUrl = firstImage(rawImg) || (typeof rawImg === 'string' ? rawImg.split(/[,,]/)[0]?.trim() : '') || 'img/icon/shop.png';
   return {
     id: item?.id ?? item?.cuisineId,
     name: item?.cuisineName ?? item?.name ?? '',
     price: item?.totalPrice ?? item?.unitPrice ?? item?.price ?? 0,
-    image: imageUrl ?? item?.image ?? item?.cuisineImage ?? 'img/icon/shop.png',
+    image: imageUrl,
     quantity: item?.quantity ?? 1,
     tags: normalizeTags(rawTags)
   };

+ 3 - 3
utils/file.js

@@ -27,10 +27,10 @@ export function getFileUrl(url, planB = false) {
 
 	if (/^(https|http):\/\//i.test(url) || /^(data|http|https):/i.test(url) || url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0) return url;
 
-	// 如果第一位非 / 开头 补全指定目录前缀
-	if (!/^\//.test(url) && !planB) url = `/orderFood/${url}`;
+	// 如果第一位非 / 开头 补全指定目录前缀;接口返回的 upload/ 路径不再加 orderFood,直接拼 CDN
+	if (!/^\//.test(url) && !planB && !/^upload\/?/i.test(String(url).trim())) url = `orderFood/${url}`;
 
-	url = url.replace(/\.+\//gi, '/').replace(/~+/gi, '/').replace(/\/+/gi, '/').replace(/\//, '').replace(/\\/g, '/');
+	url = url.replace(/\.+\//gi, '/').replace(/~+/gi, '/').replace(/\/+/gi, '/').replace(/^\/+/, '').replace(/\\/g, '/');
 
 	// CDN 路径 增加回退逻辑,防止当异步数据没有返回时,页面已经开始加载数据,导致图片无法正常显示
 	let prefix = uni.getStorageSync(CDN_CACHE_KEY) || DEFAULT_CDN_URL;