sunshibo hai 1 semana
pai
achega
305efb19c3

+ 12 - 47
App.vue

@@ -2,51 +2,10 @@
 import initConfig from '@/initConfig.js';
 // #ifdef MP-WEIXIN
 import { SCAN_QR_CACHE } from '@/settings/enums.js';
+import { parseSceneToStoreTable, isScanEntryAllowed } from '@/utils/qrScene.js';
+import { syncM2GenericPricingStorage } from '@/utils/m2GenericApiPath.js';
 // #endif
 
-/** 从 scene 解析:支持小程序码 s=店铺id&t=桌号id、storeId_tableId、纯数字桌号、JSON 等 */
-function parseSceneToStoreTable(sceneStr) {
-	const trim = (v) => (v == null ? '' : String(v).trim());
-	let storeId = '';
-	let tableId = '';
-	if (!sceneStr) return { storeId, tableId };
-	let decoded = sceneStr;
-	try {
-		decoded = decodeURIComponent(decoded);
-	} catch (_) {}
-	decoded = trim(decoded);
-	const parseKv = (text) => {
-		const out = { storeId: '', tableId: '' };
-		text.split('&').forEach((pair) => {
-			const eq = pair.indexOf('=');
-			if (eq > 0) {
-				const k = pair.substring(0, eq).trim().toLowerCase();
-				let v = pair.substring(eq + 1).trim();
-				try { v = decodeURIComponent(v); } catch (_) {}
-				v = trim(v);
-				if (['s', 'storeid', 'store_id'].includes(k)) out.storeId = v;
-				if (['t', 'tableid', 'table_id', 'tableno', 'table'].includes(k)) out.tableId = v;
-			}
-		});
-		return out;
-	};
-	try {
-		if (decoded.startsWith('{') && decoded.endsWith('}')) {
-			const obj = JSON.parse(decoded);
-			return { storeId: trim(obj?.storeId ?? obj?.store_id ?? obj?.s ?? ''), tableId: trim(obj?.tableId ?? obj?.table_id ?? obj?.tableid ?? obj?.t ?? obj?.tableNo ?? obj?.table ?? '') };
-		}
-		if (/^\d+_\d+$/.test(decoded)) {
-			const [s, t] = decoded.split('_');
-			return { storeId: trim(s), tableId: trim(t) };
-		}
-		if (/^\d+$/.test(decoded)) return { storeId: '', tableId: decoded };
-		return parseKv(decoded);
-	} catch (err) {
-		console.warn('parseSceneToStoreTable', err);
-	}
-	return { storeId, tableId };
-}
-
 export default {
 	onLaunch: function (e) {
 		// 只有小程序执行
@@ -60,11 +19,17 @@ export default {
 			const q = qEnc != null && qEnc !== '' ? decodeURIComponent(String(qEnc)) : '';
 			const rawFromQR = scene || q || '';
 			if (rawFromQR) {
-				const { storeId, tableId } = parseSceneToStoreTable(rawFromQR);
-				const payload = { raw: rawFromQR, storeId, tableId };
+				const { storeId, tableId, m } = parseSceneToStoreTable(rawFromQR);
+				const payload = { raw: rawFromQR, storeId, tableId, m };
 				uni.setStorageSync(SCAN_QR_CACHE, JSON.stringify(payload));
-				if (storeId) uni.setStorageSync('currentStoreId', storeId);
-				if (tableId) uni.setStorageSync('currentTableId', tableId);
+				syncM2GenericPricingStorage(m);
+				// 美食(m=1/无 m) 或 通用价目(m=2) 写入桌店;其它 m 不写
+				if (isScanEntryAllowed(m)) {
+					if (storeId) uni.setStorageSync('currentStoreId', storeId);
+					if (tableId) uni.setStorageSync('currentTableId', tableId);
+				}
+			} else {
+				syncM2GenericPricingStorage('');
 			}
 		} catch (err) {
 			console.warn('缓存二维码启动参数失败', err);

+ 9 - 4
api/dining.js

@@ -1,6 +1,7 @@
 import { api } from '@/utils/request.js';
 import { BASE_API_URL } from '@/settings/siteSetting.js';
 import { useUserStore } from '@/store/user.js';
+import { rewriteM2DocumentedApiPath, isM2GenericPricingApiActive } from '@/utils/m2GenericApiPath.js';
 
 // 微信登录
 export const DiningUserWechatLogin = (params) => api.post({ url: '/dining/user/wechatLogin', params });
@@ -91,9 +92,12 @@ export const GetStoreCategories = (params) => api.get({ url: '/store/info/catego
 // 根据菜品种类获取菜品(入参 categoryId,GET /store/info/cuisines?categoryId=)
 export const GetStoreCuisines = (params) => api.get({ url: '/store/info/cuisines', params });
 
-// 左侧分类+右侧菜品一体(GET /dining/store/info/categories-with-cuisines,入参 storeId,keyword 选填)
-export const GetCategoriesWithCuisines = (params) =>
-  api.get({ url: '/store/info/categories-with-cuisines', params });
+// 左侧分类+右侧菜品一体(GET /store/info/categories-with-cuisines;扫码 m=2 通用价目时传 menuType=2)
+export const GetCategoriesWithCuisines = (params) => {
+  const q = { ...(params || {}) };
+  if (isM2GenericPricingApiActive()) q.menuType = 2;
+  return api.get({ url: '/store/info/categories-with-cuisines', params: q });
+};
 
 // 菜品详情(GET /store/dining/cuisine/{cuisineId},入参 cuisineId 菜品ID、tableId 桌号ID 传 query)
 export const GetCuisineDetail = (cuisineId, tableId) => {
@@ -263,8 +267,9 @@ export const PostOrderComplete = (orderId) =>
  */
 export function getOrderSseConfig(tableId) {
   const userStore = useUserStore();
+  const path = rewriteM2DocumentedApiPath(`/store/order/sse/${encodeURIComponent(tableId)}`);
   return {
-    url: `${BASE_API_URL}/store/order/sse/${encodeURIComponent(tableId)}`,
+    url: `${BASE_API_URL}${path}`,
     header: { Authorization: userStore.getToken || '' },
     timeout: 0
   };

+ 282 - 0
api/genericDining.js

@@ -0,0 +1,282 @@
+/**
+ * 与 dining.js 接口一一对应,路径中的 dining 改为 generic-dining。
+ * 用于扫码进入且 m≠1 时的业务后端(非美食专线)。
+ */
+import { api } from '@/utils/request.js';
+import { BASE_API_URL } from '@/settings/siteSetting.js';
+import { useUserStore } from '@/store/user.js';
+
+// 微信登录
+export const DiningUserWechatLogin = (params) =>
+  api.post({ url: '/generic-dining/user/wechatLogin', params });
+
+// 获取用户信息(GET /generic-dining/user/getUserInfo),登录后调用以拿到头像等
+export const GetUserInfo = () => api.get({ url: '/generic-dining/user/getUserInfo' });
+
+// 更新用户个人信息(POST /generic-dining/user/updateProfile,body 为 dto)
+export const PostUpdateProfile = (dto) =>
+  api.post({ url: '/generic-dining/user/updateProfile', params: dto });
+
+// 文件上传(POST multipart,返回图片 URL/path 供 avatarUrl 使用)
+function uploadFileToServerImpl(filePath) {
+  const userStore = useUserStore();
+  return new Promise((resolve, reject) => {
+    uni.uploadFile({
+      url: BASE_API_URL + '/generic-dining/file/upload',
+      filePath,
+      name: 'file',
+      header: { Authorization: userStore.getToken || '' },
+      success: (res) => {
+        if (res.statusCode === 200) {
+          try {
+            const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
+            const payload = data?.data ?? data;
+            const url = payload?.url ?? payload?.path ?? payload?.relativePath ?? (typeof payload === 'string' ? payload : '');
+            resolve(url || '');
+          } catch (e) {
+            reject(e);
+          }
+        } else {
+          reject(new Error(res.data || '上传失败'));
+        }
+      },
+      fail: reject
+    });
+  });
+}
+export const uploadFileToServer = uploadFileToServerImpl;
+export const uploadFileToSever = uploadFileToServerImpl; // 兼容拼写错误
+
+// 点餐页数据(入参 dinerCount 就餐人数、tableId 桌号)
+export const DiningOrderFood = (params) => api.get({ url: '/store/generic-dining/page-info', params });
+
+// 服务费估算
+export const GetServiceFeeEstimate = (params) => {
+  const q = {};
+  if (params?.storeId != null && params.storeId !== '') q.storeId = String(params.storeId);
+  if (params?.tableId != null && params.tableId !== '') q.tableId = String(params.tableId);
+  if (params?.dinerCount != null && params.dinerCount !== '') q.dinerCount = params.dinerCount;
+  if (params?.goodsSubtotal != null && params.goodsSubtotal !== '') q.goodsSubtotal = params.goodsSubtotal;
+  return api.get({ url: '/store/generic-dining/service-fee/estimate', params: q, loading: false });
+};
+
+// 桌台预约记录详情
+export const GetReservationDetailByStoreTableRecord = (params) => {
+  const q = {};
+  if (params?.storeId != null && params?.storeId !== '') q.storeId = String(params.storeId);
+  if (params?.userReservationTableId != null && params?.userReservationTableId !== '') {
+    q.userReservationTableId = String(params.userReservationTableId);
+  }
+  return api.get({
+    url: '/store/generic-dining/reservation/detail-by-store-table-record',
+    params: q,
+    loading: false
+  });
+};
+
+// 查询桌位是否已选就餐人数
+export const GetTableDiningStatus = (tableId, extra = {}) =>
+  api.get({
+    url: '/store/generic-dining/table-generic-dining-status',
+    params: { tableId: tableId != null && tableId !== '' ? String(tableId) : '' },
+    ...extra
+  });
+
+// 到店就餐信息提交
+export const PostDiningWalkInReservation = (dto) =>
+  api.post({ url: '/store/generic-dining/walk-in/reservation', params: dto });
+
+// 门店详情(GET /store/info/detail/{storeId},入参 storeId)
+export const GetStoreDetail = (storeId) =>
+  api.get({ url: `/store/info/detail/${encodeURIComponent(storeId)}` });
+
+// 菜品种类(入参 storeId,GET /store/info/categories?storeId=)
+export const GetStoreCategories = (params) => api.get({ url: '/store/info/categories', params });
+
+// 根据菜品种类获取菜品(入参 categoryId,GET /store/info/cuisines?categoryId=)
+export const GetStoreCuisines = (params) => api.get({ url: '/store/info/cuisines', params });
+
+// 左侧分类+右侧菜品一体(通用价目 alienDining:固定 menuType=2)
+export const GetCategoriesWithCuisines = (params) =>
+  api.get({
+    url: '/store/info/categories-with-cuisines',
+    params: { ...(params || {}), menuType: 2 }
+  });
+
+// 菜品详情
+export const GetCuisineDetail = (cuisineId, tableId) => {
+  const id = cuisineId != null ? encodeURIComponent(cuisineId) : '';
+  const url = id ? `/store/generic-dining/cuisine/${id}` : '/store/generic-dining/cuisine/0';
+  const params = tableId != null && tableId !== '' ? { tableId } : {};
+  return api.get({ url, params });
+};
+
+// 门店全部优惠券列表
+function getStoreAllCouponListImpl(params) {
+  return api.get({
+    url: '/generic-dining/coupon/getStoreAllCouponList',
+    params: {
+      storeId: params?.storeId,
+      tab: 1,
+      page: params?.page ?? 1,
+      size: params?.size ?? 10
+    }
+  });
+}
+export const GetStoreAllCouponList = getStoreAllCouponListImpl;
+export const GetStoreUsableCouponList = getStoreAllCouponListImpl;
+
+// 用户已领/可用优惠券
+function getUserOwnedCouponListImpl(params) {
+  return api.get({
+    url: '/generic-dining/coupon/userOwnedByStore',
+    params: {
+      storeId: params?.storeId
+    }
+  });
+}
+export const GetUserOwnedCouponList = getUserOwnedCouponListImpl;
+
+// 用户优惠券列表
+export const GetUserCouponList = (params) =>
+  api.get({
+    url: '/generic-dining/coupon/getUserCouponList',
+    params: {
+      storeId: params?.storeId,
+      tabType: params?.tabType ?? 0,
+      page: params?.page ?? 1,
+      size: params?.size ?? 20
+    }
+  });
+
+// 获取购物车(路径无 dining)
+export const GetOrderCart = (tableId) =>
+  api.get({ url: `/store/order/cart/${encodeURIComponent(tableId)}` });
+
+// 加入购物车
+export const PostOrderCartAdd = (dto) =>
+  api.post({ url: '/store/order/cart/add', params: dto });
+
+// 更新购物车
+export const PostOrderCartUpdate = (params) =>
+  api.put({ url: '/store/order/cart/update', params, formUrlEncoded: true });
+
+// 清空购物车
+export const PostOrderCartClear = (tableId) => {
+  const id = tableId != null ? String(tableId) : '';
+  const url = id ? `/store/order/cart/clear?tableId=${encodeURIComponent(id)}` : '/store/order/cart/clear';
+  return api.delete({ url, params: id ? { tableId: id } : {} });
+};
+
+// 锁定订单
+function postOrderLockImpl(params) {
+  const tableId = params?.tableId != null ? String(params.tableId) : '';
+  const url = tableId
+    ? `/store/generic-dining/order/lock?tableId=${encodeURIComponent(tableId)}`
+    : '/store/generic-dining/order/lock';
+  return api.post({ url, params: {} });
+}
+export const PostOrderLock = postOrderLockImpl;
+
+// 解锁订单
+function postOrderUnlockImpl(params) {
+  const tableId = params?.tableId != null ? String(params.tableId) : '';
+  const url = tableId
+    ? `/store/generic-dining/order/unlock?tableId=${encodeURIComponent(tableId)}`
+    : '/store/generic-dining/order/unlock';
+  return api.post({ url, params: {} });
+}
+export const PostOrderUnlock = postOrderUnlockImpl;
+export const postOrderUnlock = postOrderUnlockImpl;
+
+// 结算页锁定订单
+function postOrderSettlementLockImpl(params) {
+  const orderId = params?.orderId != null ? String(params.orderId) : '';
+  const url = orderId
+    ? `/store/generic-dining/order/settlement/lock?orderId=${encodeURIComponent(orderId)}`
+    : '/store/generic-dining/order/settlement/lock';
+  return api.post({ url, params: {} });
+}
+export const PostOrderSettlementLock = postOrderSettlementLockImpl;
+
+// 结算页解锁订单
+function postOrderSettlementUnlockImpl(params) {
+  const orderId = params?.orderId != null ? String(params.orderId) : '';
+  const url = orderId
+    ? `/store/generic-dining/order/settlement/unlock?orderId=${encodeURIComponent(orderId)}`
+    : '/store/generic-dining/order/settlement/unlock';
+  return api.post({ url, params: {} });
+}
+export const PostOrderSettlementUnlock = postOrderSettlementUnlockImpl;
+
+// 创建订单
+export const PostOrderCreate = (dto) =>
+  api.post({ url: '/store/order/create', params: dto });
+
+// 订单分页列表
+export const GetOrderPage = (params) =>
+  api.get({ url: '/store/order/page', params });
+
+// 我的订单
+export const GetMyOrders = (params) =>
+  api.get({ url: '/store/order/my-orders', params });
+
+// 订单详情
+export const GetOrderInfo = (orderId) =>
+  api.get({ url: `/store/order/info/${encodeURIComponent(orderId)}` });
+
+// 订单支付
+export const PostOrderPay = (params) => {
+  const query = {
+    orderNo: params.orderNo,
+    payType: 'wechatPayMininProgram',
+    payer: params.payer,
+    price: params.price,
+    subject: params.subject ?? '订单支付',
+    storeId: params.storeId
+  };
+  if (params.couponId != null && params.couponId !== '') {
+    query.couponId = params.couponId;
+  }
+  if (params.payerId != null && params.payerId !== '') {
+    query.payerId = params.payerId;
+  }
+  if (params.discountAmount != null && params.discountAmount !== '') {
+    query.discountAmount = params.discountAmount;
+  }
+  if (params.payAmount != null && params.payAmount !== '') {
+    query.payAmount = params.payAmount;
+  }
+  return api.get({
+    url: '/payment/prePay',
+    params: query
+  });
+};
+
+// 根据第三方订单号查询支付结果
+export const GetSearchOrderByOutTradeNoPath = (params) =>
+  api.get({
+    url: '/payment/searchOrderByOutTradeNoPath',
+    params: {
+      payType: params.payType ?? 'wechatPayMininProgram',
+      transactionId: params.transactionId,
+      storeId: params.storeId
+    },
+    loading: false
+  });
+
+// 订单翻转/完成
+export const PostOrderComplete = (orderId) =>
+  api.post({ url: `/store/order/complete/${encodeURIComponent(orderId)}`, loading: false });
+
+/**
+ * 订单 SSE 接口配置(路径无 dining,与美食线一致)
+ */
+export function getOrderSseConfig(tableId) {
+  const userStore = useUserStore();
+  return {
+    url: `${BASE_API_URL}/store/order/sse/${encodeURIComponent(tableId)}`,
+    header: { Authorization: userStore.getToken || '' },
+    timeout: 0
+  };
+}

+ 10 - 113
components/TabBar.vue

@@ -31,6 +31,8 @@
 import { computed, unref } from 'vue';
 import { getFileUrl } from '@/utils/file.js';
 import { SCAN_QR_CACHE } from '@/settings/enums.js';
+import { parseQrScanResult, isScanEntryAllowed } from '@/utils/qrScene.js';
+import { syncM2GenericPricingStorage } from '@/utils/m2GenericApiPath.js';
 import { useUserStore } from '@/store/user.js';
 import { TOKEN } from '@/settings/enums.js';
 
@@ -48,117 +50,7 @@ const getTabBarWrapStyle = computed(() => ({
 	position: props.fixed ? 'fixed' : 'relative'
 }));
 
-// 解析扫码内容:支持小程序码 path、scene,以及 s=店铺id&t=桌号id、URL、JSON 等格式
-function parseScanResult(str) {
-	const trim = (v) => (v == null ? '' : String(v).trim());
-	let storeId = '';
-	let tableId = '';
-	if (!str) return { storeId, tableId };
-	let s = trim(str);
-	const parseKv = (text) => {
-		const out = { storeId: '', tableId: '' };
-		const keys = {
-			store: ['s', 'storeid', 'store_id'],
-			table: ['t', 'tableid', 'table_id', 'tableno', 'table']
-		};
-		text.split('&').forEach((pair) => {
-			const eq = pair.indexOf('=');
-			if (eq > 0) {
-				const k = pair.substring(0, eq).trim().toLowerCase();
-				let v = pair.substring(eq + 1).trim();
-				try {
-					v = decodeURIComponent(v);
-				} catch (_) {}
-				v = trim(v);
-				if (keys.store.includes(k)) out.storeId = v;
-				if (keys.table.includes(k)) out.tableId = v;
-			}
-		});
-		return out;
-	};
-	const parseScene = (sceneStr) => {
-		let decoded = sceneStr;
-		try {
-			decoded = decodeURIComponent(decoded);
-		} catch (_) {}
-		decoded = trim(decoded);
-		// scene 为 base64 编码的 JSON(小程序码常见)
-		if (/^[A-Za-z0-9+/=]+$/.test(decoded) && decoded.length > 20) {
-			try {
-				const jsonStr = typeof atob === 'function' ? atob(decoded) : '';
-				if (jsonStr && jsonStr.startsWith('{')) {
-					const obj = JSON.parse(jsonStr);
-					return {
-						storeId: trim(obj?.storeId ?? obj?.store_id ?? obj?.s ?? ''),
-						tableId: trim(obj?.tableId ?? obj?.table_id ?? obj?.tableid ?? obj?.t ?? obj?.tableNo ?? obj?.table ?? '')
-					};
-				}
-			} catch (_) {}
-		}
-		// scene 为 JSON 字符串
-		if (decoded.startsWith('{') && decoded.endsWith('}')) {
-			try {
-				const obj = JSON.parse(decoded);
-				return {
-					storeId: trim(obj?.storeId ?? obj?.store_id ?? obj?.s ?? ''),
-					tableId: trim(obj?.tableId ?? obj?.table_id ?? obj?.tableid ?? obj?.t ?? obj?.tableNo ?? obj?.table ?? '')
-				};
-			} catch (_) {}
-		}
-		// scene 为 storeId_tableId 下划线分隔(常见小程序码格式)
-		if (/^\d+_\d+$/.test(decoded)) {
-			const [sid, tid] = decoded.split('_');
-			return { storeId: trim(sid), tableId: trim(tid) };
-		}
-		// scene 仅为数字,视为桌号
-		if (/^\d+$/.test(decoded)) {
-			return { storeId: '', tableId: decoded };
-		}
-		const fromKv = parseKv(decoded);
-		if (!fromKv.tableId && /^\d+$/.test(decoded)) fromKv.tableId = decoded;
-		return fromKv;
-	};
-	try {
-		// 1. 小程序码 path 格式:pages/xxx?scene=xxx
-		const qIdx = s.indexOf('?');
-		const hIdx = s.indexOf('#');
-		if (qIdx >= 0) {
-			const queryPart = hIdx >= 0 ? s.substring(qIdx + 1, hIdx) : s.substring(qIdx + 1);
-			const params = new Map();
-			queryPart.split('&').forEach((pair) => {
-				const eq = pair.indexOf('=');
-				if (eq > 0) {
-					const k = pair.substring(0, eq).trim();
-					let v = pair.substring(eq + 1).trim();
-					try {
-						v = decodeURIComponent(v);
-					} catch (_) {}
-					params.set(k, v);
-				}
-			});
-			const scene = params.get('scene') || params.get('Scene');
-			if (scene) {
-				return parseScene(scene);
-			}
-			return parseKv(queryPart);
-		}
-		// 2. JSON 格式
-		if ((s.startsWith('{') && s.endsWith('}')) || (s.startsWith('[') && s.endsWith(']'))) {
-			const obj = JSON.parse(s);
-			return {
-				storeId: trim(obj?.storeId ?? obj?.store_id ?? obj?.s ?? ''),
-				tableId: trim(obj?.tableId ?? obj?.table_id ?? obj?.tableid ?? obj?.t ?? obj?.tableNo ?? obj?.table ?? '')
-			};
-		}
-		// 3. key=value 格式
-		return parseKv(s);
-	} catch (err) {
-		console.warn('parseScanResult', err);
-	}
-	return { storeId, tableId };
-}
-
-// 点击扫码点餐:未登录提示请登录;已登录扫码后跳转点餐页
+// 点击扫码点餐:未登录提示请登录;已登录扫码后跳转点餐页(m=1 或无 m 为美食,走 dining)
 function handleScanOrder() {
 	const userStore = useUserStore();
 	const token = userStore.getToken || uni.getStorageSync(TOKEN) || '';
@@ -170,9 +62,14 @@ function handleScanOrder() {
 		scanType: ['wxCode', 'qrCode', 'barCode'],
 		success: (res) => {
 			const result = (res?.path || res?.result || '').trim();
-			const { storeId, tableId } = parseScanResult(result);
-			const payload = { raw: result, storeId, tableId };
+			const { storeId, tableId, m } = parseQrScanResult(result);
+			const payload = { raw: result, storeId, tableId, m };
 			uni.setStorageSync(SCAN_QR_CACHE, JSON.stringify(payload));
+			syncM2GenericPricingStorage(m);
+			if (!isScanEntryAllowed(m)) {
+				uni.showToast({ title: '请扫描正确的点餐二维码', icon: 'none' });
+				return;
+			}
 			if (storeId) uni.setStorageSync('currentStoreId', storeId);
 			if (tableId) uni.setStorageSync('currentTableId', tableId);
 			const diners = uni.getStorageSync('currentDiners') || '1';

+ 37 - 49
pages/launch/index.vue

@@ -10,76 +10,64 @@ import { onLoad } from '@dcloudio/uni-app';
 import * as diningApi from '@/api/dining.js';
 import { useUserStore } from '@/store/user.js';
 import { TOKEN } from '@/settings/enums.js';
+import { parseSceneToStoreTable, isScanEntryAllowed } from '@/utils/qrScene.js';
+import { SCAN_QR_CACHE } from '@/settings/enums.js';
+import { syncM2GenericPricingStorage } from '@/utils/m2GenericApiPath.js';
 
 const userStore = useUserStore();
 
-/** 从 scene 解析 storeId、tableId(与 App.vue parseSceneToStoreTable 逻辑一致) */
-function parseSceneToStoreTable(sceneStr) {
-  const trim = (v) => (v == null ? '' : String(v).trim());
-  let storeId = '';
-  let tableId = '';
-  if (!sceneStr) return { storeId, tableId };
-  let decoded = String(sceneStr).trim();
-  try { decoded = decodeURIComponent(decoded); } catch (_) {}
-  const parseKv = (text) => {
-    text.split('&').forEach((pair) => {
-      const eq = pair.indexOf('=');
-      if (eq > 0) {
-        const k = pair.substring(0, eq).trim().toLowerCase();
-        const v = pair.substring(eq + 1).trim();
-        if (['s', 'storeid', 'store_id'].includes(k)) storeId = v;
-        if (['t', 'tableid', 'table_id', 'tableno', 'table'].includes(k)) tableId = v;
-      }
-    });
-  };
-  try {
-    if (decoded.startsWith('{') && decoded.endsWith('}')) {
-      const obj = JSON.parse(decoded);
-      return { storeId: trim(obj?.storeId ?? obj?.store_id ?? obj?.s ?? ''), tableId: trim(obj?.tableId ?? obj?.table_id ?? obj?.tableid ?? obj?.t ?? obj?.tableNo ?? obj?.table ?? '') };
-    }
-    if (/^\d+_\d+$/.test(decoded)) {
-      const [s, t] = decoded.split('_');
-      return { storeId: trim(s), tableId: trim(t) };
-    }
-    if (/^\d+$/.test(decoded)) return { storeId: '', tableId: decoded };
-    parseKv(decoded);
-  } catch (_) {}
-  return { storeId: trim(storeId), tableId: trim(tableId) };
+function trim(v) {
+  return v == null ? '' : String(v).trim();
 }
 
-/** 从启动参数/query 中解析 tableid、storeId,兼容微信小程序 scene、query 等 */
+/** 从启动参数/query 中解析 tableid、storeId、m(m=1 美食,走 dining) */
 function getIdsFromOptions(options) {
-  if (!options || typeof options !== 'object') return { tableid: '', storeId: '' };
+  if (!options || typeof options !== 'object') return { tableid: '', storeId: '', m: '' };
   const q = options.query || options;
+  let m = trim(q.m ?? q.mode ?? '');
   const directTable = q.tableId ?? q.tableid ?? q.table_id ?? q.t ?? '';
   if (directTable) {
     const directStore = q.storeId ?? q.storeid ?? q.s ?? '';
-    return { tableid: String(directTable).trim(), storeId: String(directStore || '').trim() };
+    return { tableid: String(directTable).trim(), storeId: String(directStore || '').trim(), m };
   }
   const scene = q.scene ?? q.q ?? '';
   if (scene) {
-    const { storeId: s, tableId: t } = parseSceneToStoreTable(scene);
-    return { tableid: t, storeId: s };
+    const { storeId: s, tableId: t, m: sm } = parseSceneToStoreTable(scene);
+    return { tableid: t, storeId: s, m: m || sm || '' };
   }
-  return { tableid: '', storeId: '' };
+  return { tableid: '', storeId: '', m };
 }
 
 async function doRedirect(options = {}) {
-  let tableid = uni.getStorageSync('currentTableId') || '';
-  if (!tableid) {
-    const { tableid: t, storeId: s } = getIdsFromOptions(options);
-    tableid = t;
-    if (tableid) uni.setStorageSync('currentTableId', tableid);
-    if (s) uni.setStorageSync('currentStoreId', s);
+  let { tableid, storeId: optStore, m } = getIdsFromOptions(options);
+  if (trim(m) === '') {
+    try {
+      const c = uni.getStorageSync(SCAN_QR_CACHE);
+      if (c) m = trim(JSON.parse(c).m ?? '');
+    } catch (_) {}
+  }
+  syncM2GenericPricingStorage(m);
+  if (!isScanEntryAllowed(m)) {
+    uni.showToast({ title: '请扫描正确的点餐二维码', icon: 'none' });
+    uni.reLaunch({ url: '/pages/index/index' });
+    return;
+  }
+
+  let tableidFinal = uni.getStorageSync('currentTableId') || '';
+  if (!tableidFinal) {
+    tableidFinal = tableid;
+    if (tableidFinal) uni.setStorageSync('currentTableId', tableidFinal);
+    if (optStore) uni.setStorageSync('currentStoreId', optStore);
   }
-  if (!tableid) {
+  const tableidResolved = tableidFinal;
+  if (!tableidResolved) {
     console.log('[launch] 无 tableid,跳转首页');
     uni.reLaunch({ url: '/pages/index/index' });
     return;
   }
   try {
-    console.log('[launch] 调用 GetTableDiningStatus, tableid:', tableid);
-    const res = await diningApi.GetTableDiningStatus(tableid);
+    console.log('[launch] 调用 GetTableDiningStatus, tableid:', tableidResolved);
+    const res = await diningApi.GetTableDiningStatus(tableidResolved);
     const raw = (res && typeof res === 'object') ? res : {};
     // 兼容多种返回:{ inDining: true }、{ data: { inDining: true } }、直接返回 true
     const inDining =
@@ -95,13 +83,13 @@ async function doRedirect(options = {}) {
     if (inDining) {
       uni.setStorageSync('currentDiners', dinerCount);    
       uni.reLaunch({
-        url: `/pages/orderFood/index?tableid=${encodeURIComponent(tableid)}&diners=${encodeURIComponent(dinerCount)}`
+        url: `/pages/orderFood/index?tableid=${encodeURIComponent(tableidResolved)}&diners=${encodeURIComponent(dinerCount)}`
       });
       
     }
     else{
       uni.reLaunch({
-          url: `/pages/numberOfDiners/index?inDining=1&tableid=${encodeURIComponent(tableid)}&diners=${encodeURIComponent(dinerCount)}`
+          url: `/pages/numberOfDiners/index?inDining=1&tableid=${encodeURIComponent(tableidResolved)}&diners=${encodeURIComponent(dinerCount)}`
         });
     }
 

+ 20 - 3
pages/orderFood/components/FoodCard.vue

@@ -7,8 +7,8 @@
           <view class="food-title">{{ food.name }}</view>
           <view class="food-price">
             <text class="price-symbol">¥</text>
-            <text class="price-main">{{ getPriceMain(food.totalPrice) }}</text>
-            <text class="price-decimal" v-if="getPriceDecimal(food.totalPrice)">.{{ getPriceDecimal(food.totalPrice) }}</text>
+            <text class="price-main">{{ getPriceMain(displayUnitPrice) }}</text>
+            <text class="price-decimal" v-if="getPriceDecimal(displayUnitPrice)">.{{ getPriceDecimal(displayUnitPrice) }}</text>
           </view>
         </view>
         <view class="food-desc">{{ food.dishReview }}</view>
@@ -84,10 +84,27 @@ function firstImage(val) {
 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) || '';
+  const imageUrl =
+    firstImage(f.images) ||
+    firstImage(f.imageContent) ||
+    firstImage(f.cuisineImage) ||
+    firstImage(f.image) ||
+    firstImage(f.imageUrl) ||
+    firstImage(f.pic) ||
+    firstImage(f.cover) ||
+    '';
   return getFileUrl(imageUrl || 'img/icon/shop.png');
 });
 
+/** 单价展示:兼容美食 totalPrice 与通用价目 currentPrice */
+const displayUnitPrice = computed(() => {
+  const f = props.food;
+  if (!f) return 0;
+  const v = f.totalPrice ?? f.currentPrice ?? f.price ?? f.salePrice ?? f.unitPrice ?? f.originalPrice ?? 0;
+  const n = Number(v);
+  return Number.isFinite(n) && !Number.isNaN(n) ? n : 0;
+});
+
 // 后端返回的 tags 统一为 [{ text, type }] 便于绑定(兼容多种字段名与格式)
 const normalizedTags = computed(() => {
   const food = props.food;

+ 66 - 13
pages/orderFood/index.vue

@@ -383,15 +383,27 @@ const fetchCategoriesWithCuisines = async (storeId, keyword = '') => {
     const res = await GetCategoriesWithCuisines(params);
     const raw = res?.data ?? res ?? {};
     const list = raw?.list ?? raw?.data ?? (Array.isArray(raw) ? raw : []);
-    const listArr = Array.isArray(list) ? list : [];
-    // cuisines 为空的项不加入列表,左侧分类与右侧分段都不展示
+    let listArr = Array.isArray(list) ? list : [];
+    // 兼容单条 { category, prices } 包在 data 里且非数组
+    if (listArr.length === 0 && raw && typeof raw === 'object') {
+      const one = !Array.isArray(raw.data) && raw.data && typeof raw.data === 'object' ? raw.data : raw;
+      if (
+        one &&
+        typeof one === 'object' &&
+        one.category != null &&
+        (Array.isArray(one.prices) || Array.isArray(one.cuisines) || Array.isArray(one.cuisineList))
+      ) {
+        listArr = [one];
+      }
+    }
+    // cuisines / prices(通用价目 menuType=2)为空的项不展示
     const dataList = listArr.filter((item) => {
-      const arr = item.cuisines ?? item.cuisineList ?? [];
+      const arr = item.cuisines ?? item.cuisineList ?? item.prices ?? item.priceList ?? [];
       return Array.isArray(arr) && arr.length > 0;
     });
     let cats = [];
     let cuisines = [];
-    // 接口返回 data 数组,每项为 { category: { id, categoryName, ... }, cuisines: [...] }
+    // 接口返回 data 数组:美食 { category, cuisines };通用价目 { category, prices }
     if (dataList.length > 0 && dataList[0].category != null) {
       cats = dataList.map((item) => {
         const c = item.category || {};
@@ -404,14 +416,20 @@ const fetchCategoriesWithCuisines = async (storeId, keyword = '') => {
       cuisines = dataList.flatMap((item) => {
         const c = item.category || {};
         const cid = c.id ?? c.categoryId;
-        const arr = item.cuisines ?? item.cuisineList ?? [];
+        const arr = item.cuisines ?? item.cuisineList ?? item.prices ?? item.priceList ?? [];
         return (Array.isArray(arr) ? arr : []).map((dish) => {
-          const ids = Array.isArray(dish.categoryIds) ? dish.categoryIds : (dish.categoryId != null ? [dish.categoryId] : [cid]);
-          return { ...dish, categoryId: dish.categoryId ?? dish.categoryIds?.[0] ?? cid, categoryIds: ids };
+          const ids = Array.isArray(dish.categoryIds)
+            ? dish.categoryIds
+            : dish.categoryId != null
+              ? [dish.categoryId]
+              : cid != null && cid !== ''
+                ? [cid]
+                : [];
+          return { ...dish, categoryId: dish.categoryId ?? dish.categoryIds?.[0] ?? cid, categoryIds: ids.length ? ids : [cid].filter(Boolean) };
         });
       });
-    } else if (dataList.length > 0 && (dataList[0].cuisines != null || dataList[0].cuisineList != null)) {
-      const cuisinesKey = dataList[0].cuisines != null ? 'cuisines' : 'cuisineList';
+    } else if (dataList.length > 0 && (dataList[0].cuisines != null || dataList[0].cuisineList != null || dataList[0].prices != null)) {
+      const cuisinesKey = dataList[0].cuisines != null ? 'cuisines' : dataList[0].cuisineList != null ? 'cuisineList' : 'prices';
       cats = dataList.map((c) => ({
         id: c.id ?? c.categoryId,
         categoryId: c.id ?? c.categoryId,
@@ -434,10 +452,45 @@ const fetchCategoriesWithCuisines = async (storeId, keyword = '') => {
     const nextCats = Array.isArray(cats) ? cats : [];
     categories.value = nextCats;
     const normalized = cuisines.map((item) => {
-      const categoryId = item.categoryId ?? (categories.value[0]?.id ?? categories.value[0]?.categoryId);
-      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, categoryIds: item.categoryIds ?? [categoryId] };
+      const fallbackCat = categories.value[0]?.id ?? categories.value[0]?.categoryId;
+      const categoryId = item.categoryId ?? fallbackCat;
+      const rawImg =
+        item.images ??
+        item.imageContent ??
+        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
+            : '');
+      const priceNum =
+        Number(item.currentPrice ?? item.originalPrice ?? item.totalPrice ?? item.price ?? item.salePrice ?? item.unitPrice ?? 0) || 0;
+      const dishId = item.cuisineId ?? item.id;
+      let catIds = item.categoryIds ?? (item.categoryId != null ? [item.categoryId] : categoryId != null && categoryId !== '' ? [categoryId] : []);
+      if (!Array.isArray(catIds)) catIds = [catIds];
+      catIds = [...new Set(catIds.map((x) => String(x)).filter(Boolean))];
+      if (!catIds.length && categoryId != null && categoryId !== '') catIds = [String(categoryId)];
+      return {
+        ...item,
+        id: item.id ?? dishId,
+        cuisineId: dishId,
+        images: img,
+        image: img,
+        cuisineImage: img,
+        totalPrice: priceNum,
+        unitPrice: priceNum,
+        price: priceNum,
+        quantity: item.quantity ?? 0,
+        categoryId: catIds[0] ?? categoryId,
+        categoryIds: catIds.length ? catIds : categoryId != null && categoryId !== '' ? [String(categoryId)] : []
+      };
     });
     // 同一菜品可能出现在多个分类下,按 id 去重并合并 categoryIds
     const byId = new Map();

+ 4 - 1
settings/enums.js

@@ -13,7 +13,10 @@ export const OPEN_ID = "OPEN_ID"
 // 登录成功后重定向地址
 export const REDIRECT_KEY = "REDIRECT"
 
-// 扫码点餐:从二维码获取的信息缓存(raw 原始内容、storeId、tableId)
+// 扫码点餐:从二维码获取的信息缓存(raw、storeId、tableId、m;m=1 为美食走 dining
 export const SCAN_QR_CACHE = "SCAN_QR_CACHE"
 
+// m=2(通用价目)时 request 仅改写接口文档 4.1 / 4.2 所列路径
+export const USE_M2_GENERIC_PRICING_API = "USE_M2_GENERIC_PRICING_API"
+
 

+ 5 - 1
settings/siteSetting.js

@@ -5,10 +5,14 @@ export const DEFAULT_CDN_URL = 'https://alien-volume.oss-cn-beijing.aliyuncs.com
 export const UPLOAD = 'https://alien-volume.oss-cn-beijing.aliyuncs.com/';
 
 // uat环境
-export const BASE_API_URL = 'https://uat.ailien.shop/alienDining';
+// export const BASE_API_URL = 'https://uat.ailien.shop/alienDining';
 
 // 测试环境
 // export const BASE_API_URL = 'https://test.ailien.shop/alienDining';
 
+
+// 测试环境
+export const BASE_API_URL = 'http://192.168.10.84:8000/alienDining';
+
 /** 文件上传 请求地址 */
 export const UPLOAD_URL = 'https://api.xxxxxx.cn/File/UploadImg';

+ 66 - 0
utils/m2GenericApiPath.js

@@ -0,0 +1,66 @@
+import { USE_M2_GENERIC_PRICING_API } from '@/settings/enums.js';
+
+/** 与 qrScene 一致:m=2 为通用价目扫码 */
+export function isGenericPricingM2(m) {
+  const s = String(m ?? '').trim();
+  if (s === '2') return true;
+  const n = Number(s);
+  return !Number.isNaN(n) && n === 2;
+}
+
+export function syncM2GenericPricingStorage(m) {
+  try {
+    uni.setStorageSync(USE_M2_GENERIC_PRICING_API, isGenericPricingM2(m) ? '1' : '0');
+  } catch (_) {}
+}
+
+export function isM2GenericPricingApiActive() {
+  try {
+    return uni.getStorageSync(USE_M2_GENERIC_PRICING_API) === '1';
+  } catch (_) {
+    return false;
+  }
+}
+
+/**
+ * 仅改写文档 4.1 点餐域、4.2 购物车/下单/SSE;其余 URL 不变。
+ * @param {string} fullUrl path 可含 query
+ */
+export function rewriteM2DocumentedApiPath(fullUrl) {
+  if (!fullUrl || typeof fullUrl !== 'string' || !isM2GenericPricingApiActive()) return fullUrl;
+
+  const qIndex = fullUrl.indexOf('?');
+  const path = qIndex >= 0 ? fullUrl.slice(0, qIndex) : fullUrl;
+  const query = qIndex >= 0 ? fullUrl.slice(qIndex) : '';
+
+  // 4.2 订单域:/store/order/cart*、create、sse
+  if (path.startsWith('/store/order/cart')) {
+    return path.replace(/^\/store\/order\/cart/, '/store/generic-order/cart') + query;
+  }
+  if (path === '/store/order/create') {
+    return '/store/generic-order/create' + query;
+  }
+  if (path.startsWith('/store/order/sse/')) {
+    return path.replace(/^\/store\/order\/sse\//, '/store/generic-order/sse/') + query;
+  }
+
+  // 4.1 点餐域:白名单路径 /store/dining/ -> /store/generic-dining/
+  if (!path.startsWith('/store/dining/')) return fullUrl;
+  const rest = path.slice('/store/dining/'.length);
+  const allowed =
+    rest === 'page-info' ||
+    rest === 'search' ||
+    rest === 'cuisines' ||
+    rest.startsWith('cuisine/') ||
+    rest === 'order/confirm' ||
+    rest === 'coupons/available' ||
+    rest === 'coupon/receive' ||
+    rest === 'order/lock' ||
+    rest === 'order/unlock' ||
+    rest === 'order/check-lock' ||
+    rest === 'order/settlement' ||
+    rest === 'order/settlement/lock' ||
+    rest === 'order/settlement/unlock';
+  if (!allowed) return fullUrl;
+  return `/store/generic-dining/${rest}${query}`;
+}

+ 148 - 0
utils/qrScene.js

@@ -0,0 +1,148 @@
+/**
+ * 扫码 / 小程序 scene 解析:店铺、桌号、业务线 m。
+ * 无 m 或 m=1:美食(原 /store/dining、/store/order 等)。
+ * m=2:通用价目,仅文档所列接口改为 generic-dining / generic-order(见 utils/m2GenericApiPath.js)。
+ * 其它显式 m:不允许进入扫码点餐链路。
+ */
+
+export function isDiningBusinessMode(m) {
+  if (m == null || String(m).trim() === '') return true;
+  const s = String(m).trim();
+  const n = Number(s);
+  if (!Number.isNaN(n)) return n === 1;
+  return s === '1';
+}
+
+/** 是否允许通过扫码进入点餐流程(美食 m=1/无 m,或通用价目 m=2) */
+export function isScanEntryAllowed(m) {
+  if (m == null || String(m).trim() === '') return true;
+  const s = String(m).trim();
+  const n = Number(s);
+  if (!Number.isNaN(n)) return n === 1 || n === 2;
+  return s === '1' || s === '2';
+}
+
+function trim(v) {
+  return v == null ? '' : String(v).trim();
+}
+
+/**
+ * 解析单段 scene 字符串(已解码或待解码的 query 片段)
+ * @returns {{ storeId: string, tableId: string, m: string }}
+ */
+export function parseSceneToStoreTable(sceneStr) {
+  let storeId = '';
+  let tableId = '';
+  let m = '';
+  if (!sceneStr) return { storeId, tableId, m };
+  let decoded = String(sceneStr).trim();
+  try {
+    decoded = decodeURIComponent(decoded);
+  } catch (_) {}
+  decoded = trim(decoded);
+
+  const parseKv = (text) => {
+    const out = { storeId: '', tableId: '', m: '' };
+    text.split('&').forEach((pair) => {
+      const eq = pair.indexOf('=');
+      if (eq > 0) {
+        const k = pair.substring(0, eq).trim().toLowerCase();
+        let v = pair.substring(eq + 1).trim();
+        try {
+          v = decodeURIComponent(v);
+        } catch (_) {}
+        v = trim(v);
+        if (['s', 'storeid', 'store_id'].includes(k)) out.storeId = v;
+        if (['t', 'tableid', 'table_id', 'tableno', 'table'].includes(k)) out.tableId = v;
+        if (k === 'm' || k === 'mode') out.m = v;
+      }
+    });
+    return out;
+  };
+
+  try {
+    // 小程序码 scene 为 base64(JSON)
+    if (/^[A-Za-z0-9+/=]+$/.test(decoded) && decoded.length > 20) {
+      try {
+        const jsonStr = typeof atob !== 'undefined' ? atob(decoded) : '';
+        if (jsonStr && jsonStr.startsWith('{')) {
+          const obj = JSON.parse(jsonStr);
+          return {
+            storeId: trim(obj?.storeId ?? obj?.store_id ?? obj?.s ?? ''),
+            tableId: trim(obj?.tableId ?? obj?.table_id ?? obj?.tableid ?? obj?.t ?? obj?.tableNo ?? obj?.table ?? ''),
+            m: trim(obj?.m ?? obj?.mode ?? '')
+          };
+        }
+      } catch (_) {}
+    }
+    if (decoded.startsWith('{') && decoded.endsWith('}')) {
+      const obj = JSON.parse(decoded);
+      return {
+        storeId: trim(obj?.storeId ?? obj?.store_id ?? obj?.s ?? ''),
+        tableId: trim(obj?.tableId ?? obj?.table_id ?? obj?.tableid ?? obj?.t ?? obj?.tableNo ?? obj?.table ?? ''),
+        m: trim(obj?.m ?? obj?.mode ?? '')
+      };
+    }
+    if (/^\d+_\d+$/.test(decoded)) {
+      const [s, t] = decoded.split('_');
+      return { storeId: trim(s), tableId: trim(t), m: '' };
+    }
+    if (/^\d+$/.test(decoded)) return { storeId: '', tableId: decoded, m: '' };
+    const kv = parseKv(decoded);
+    return { storeId: kv.storeId, tableId: kv.tableId, m: kv.m };
+  } catch (err) {
+    console.warn('parseSceneToStoreTable', err);
+  }
+  return { storeId, tableId, m };
+}
+
+/**
+ * 微信扫一扫 / 小程序码 path 等完整字符串 → 店铺、桌号、m
+ */
+export function parseQrScanResult(str) {
+  const empty = { storeId: '', tableId: '', m: '' };
+  if (!str) return empty;
+  const s = trim(str);
+  try {
+    const qIdx = s.indexOf('?');
+    const hIdx = s.indexOf('#');
+    if (qIdx >= 0) {
+      const queryPart = hIdx >= 0 ? s.substring(qIdx + 1, hIdx) : s.substring(qIdx + 1);
+      const params = new Map();
+      queryPart.split('&').forEach((pair) => {
+        const eq = pair.indexOf('=');
+        if (eq > 0) {
+          const k = pair.substring(0, eq).trim();
+          let v = pair.substring(eq + 1).trim();
+          try {
+            v = decodeURIComponent(v);
+          } catch (_) {}
+          params.set(k, v);
+        }
+      });
+      const scene = params.get('scene') || params.get('Scene');
+      if (scene) return parseSceneToStoreTable(scene);
+      return parseSceneToStoreTable(queryPart);
+    }
+    if ((s.startsWith('{') && s.endsWith('}')) || (s.startsWith('[') && s.endsWith(']'))) {
+      const obj = JSON.parse(s);
+      return {
+        storeId: trim(obj?.storeId ?? obj?.store_id ?? obj?.s ?? ''),
+        tableId: trim(obj?.tableId ?? obj?.table_id ?? obj?.tableid ?? obj?.t ?? obj?.tableNo ?? obj?.table ?? ''),
+        m: trim(obj?.m ?? obj?.mode ?? '')
+      };
+    }
+    if (/^[A-Za-z0-9+/=]+$/.test(s) && s.length > 20) {
+      try {
+        const jsonStr = typeof atob !== 'undefined' ? atob(s) : '';
+        if (jsonStr && jsonStr.startsWith('{')) {
+          return parseSceneToStoreTable(jsonStr);
+        }
+      } catch (_) {}
+    }
+    return parseSceneToStoreTable(s);
+  } catch (err) {
+    console.warn('parseQrScanResult', err);
+  }
+  return empty;
+}

+ 3 - 0
utils/request.js

@@ -2,6 +2,7 @@ import { useUserStore } from '@/store/user.js';
 import { nextTick } from 'vue';
 import { BASE_API_URL } from '@/settings/siteSetting.js';
 import { REDIRECT_KEY } from '@/settings/enums.js';
+import { rewriteM2DocumentedApiPath } from '@/utils/m2GenericApiPath.js';
 
 let requestCount = 0,
 	isShowingDialog = false;
@@ -72,6 +73,8 @@ export class Request {
 			params = Object.assign(params || {}, { _t: now });
 		}
 
+		url = rewriteM2DocumentedApiPath(url);
+
 		let data = params;
 		if (formUrlEncoded && params && typeof params === 'object') {
 			data = toFormUrlEncoded(params);