商品白名单
This commit is contained in:
@@ -110,16 +110,6 @@ export default function HomePage() {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useDidShow(() => {
|
|
||||||
syncTabBarSelected(0);
|
|
||||||
void capturePromoSceneAndTouchScan();
|
|
||||||
void loadMiniHome();
|
|
||||||
void resolveUserCity().then((resolved) => {
|
|
||||||
setDisplayCity(resolved.displayCity);
|
|
||||||
setCityCode(getCityCodeForCatalog(resolved));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const loadProducts = useCallback(() => {
|
const loadProducts = useCallback(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
return request<Product[]>(`/catalog/products?cityCode=${encodeURIComponent(cityCode)}`)
|
return request<Product[]>(`/catalog/products?cityCode=${encodeURIComponent(cityCode)}`)
|
||||||
@@ -130,6 +120,18 @@ export default function HomePage() {
|
|||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, [cityCode]);
|
}, [cityCode]);
|
||||||
|
|
||||||
|
useDidShow(() => {
|
||||||
|
syncTabBarSelected(0);
|
||||||
|
void capturePromoSceneAndTouchScan();
|
||||||
|
void loadMiniHome();
|
||||||
|
// 白名单商品按登录手机号过滤;登录后 switchTab 回首页不会卸载页面,须重新拉列表
|
||||||
|
void loadProducts();
|
||||||
|
void resolveUserCity().then((resolved) => {
|
||||||
|
setDisplayCity(resolved.displayCity);
|
||||||
|
setCityCode(getCityCodeForCatalog(resolved));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadProducts();
|
void loadProducts();
|
||||||
}, [loadProducts]);
|
}, [loadProducts]);
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { View, Text, Image } from '@tarojs/components';
|
import { View, Text, Image } from '@tarojs/components';
|
||||||
import Taro, { usePageScroll, useRouter, useShareAppMessage, useShareTimeline } from '@tarojs/taro';
|
import Taro, {
|
||||||
|
useDidShow,
|
||||||
|
usePageScroll,
|
||||||
|
useRouter,
|
||||||
|
useShareAppMessage,
|
||||||
|
useShareTimeline,
|
||||||
|
} from '@tarojs/taro';
|
||||||
import type { ProductDetailContentDto } from '@dukang/shared-types';
|
import type { ProductDetailContentDto } from '@dukang/shared-types';
|
||||||
import PageShell from '../../components/PageShell';
|
import PageShell from '../../components/PageShell';
|
||||||
import PageNavBar from '../../components/PageNavBar';
|
import PageNavBar from '../../components/PageNavBar';
|
||||||
@@ -51,13 +57,32 @@ export default function ProductDetailPage() {
|
|||||||
setHeaderSolid(scrollTop > 100);
|
setHeaderSolid(scrollTop > 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const loadProduct = useCallback(() => {
|
||||||
if (!productId) return;
|
if (!productId) return;
|
||||||
request<Product>(`/catalog/products/${productId}`)
|
request<Product | null>(`/catalog/products/${productId}`)
|
||||||
.then((p) => setProduct(normalizeFulfillmentFlags(p)))
|
.then((p) => {
|
||||||
.catch((e) => toast(e instanceof Error ? e.message : '加载失败'));
|
if (!p) {
|
||||||
|
setProduct(null);
|
||||||
|
toast('商品不存在或暂未开放');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setProduct(normalizeFulfillmentFlags(p));
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
setProduct(null);
|
||||||
|
toast(e instanceof Error ? e.message : '加载失败');
|
||||||
|
});
|
||||||
}, [productId]);
|
}, [productId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadProduct();
|
||||||
|
}, [loadProduct]);
|
||||||
|
|
||||||
|
// 登录后返回详情须带 token 重拉,否则白名单商品会一直空白
|
||||||
|
useDidShow(() => {
|
||||||
|
loadProduct();
|
||||||
|
});
|
||||||
|
|
||||||
const sharePayload = useMemo(
|
const sharePayload = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
title: product?.name || DEFAULT_SHARE_TITLE,
|
title: product?.name || DEFAULT_SHARE_TITLE,
|
||||||
|
|||||||
@@ -7,6 +7,15 @@ import { JwtService } from '@nestjs/jwt';
|
|||||||
import { CLIENT_APP_ACTOR_MAP, ClientApp } from '@dukang/shared-types';
|
import { CLIENT_APP_ACTOR_MAP, ClientApp } from '@dukang/shared-types';
|
||||||
import type { AuthUser } from './jwt-auth.guard';
|
import type { AuthUser } from './jwt-auth.guard';
|
||||||
|
|
||||||
|
/** C 端 H5 / 小程序同属 USER,目录白名单只认 actorId→手机号,允许端间 token 兼容 */
|
||||||
|
const USER_CLIENT_APPS = new Set<ClientApp>([ClientApp.USER_MINI, ClientApp.USER_H5]);
|
||||||
|
|
||||||
|
function clientAppCompatible(headerApp: ClientApp | undefined, payloadApp: unknown): boolean {
|
||||||
|
if (!headerApp || payloadApp == null) return false;
|
||||||
|
if (headerApp === payloadApp) return true;
|
||||||
|
return USER_CLIENT_APPS.has(headerApp) && USER_CLIENT_APPS.has(payloadApp as ClientApp);
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OptionalJwtAuthGuard implements CanActivate {
|
export class OptionalJwtAuthGuard implements CanActivate {
|
||||||
constructor(private readonly jwtService: JwtService) {}
|
constructor(private readonly jwtService: JwtService) {}
|
||||||
@@ -20,7 +29,7 @@ export class OptionalJwtAuthGuard implements CanActivate {
|
|||||||
try {
|
try {
|
||||||
const payload = this.jwtService.verify(auth.slice(7));
|
const payload = this.jwtService.verify(auth.slice(7));
|
||||||
const clientApp = req.headers['x-client-app'] as ClientApp;
|
const clientApp = req.headers['x-client-app'] as ClientApp;
|
||||||
if (!clientApp || payload.clientApp !== clientApp) return true;
|
if (!clientAppCompatible(clientApp, payload.clientApp)) return true;
|
||||||
const expectedActor = CLIENT_APP_ACTOR_MAP[clientApp];
|
const expectedActor = CLIENT_APP_ACTOR_MAP[clientApp];
|
||||||
if (payload.actorType !== expectedActor) return true;
|
if (payload.actorType !== expectedActor) return true;
|
||||||
req.user = {
|
req.user = {
|
||||||
|
|||||||
@@ -81,11 +81,9 @@ export class CatalogService {
|
|||||||
return serializeBigInt(
|
return serializeBigInt(
|
||||||
visible.map((p) => {
|
visible.map((p) => {
|
||||||
const media = mapProductMedia(p, resourceMap.get(p.id.toString()) ?? []);
|
const media = mapProductMedia(p, resourceMap.get(p.id.toString()) ?? []);
|
||||||
const { visibilityPhones, ...rest } = p;
|
const { visibilityPhones: _phones, visibilityWhitelistEnabled: _wl, ...rest } = p;
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
visibilityWhitelistEnabled: p.visibilityWhitelistEnabled,
|
|
||||||
visibilityPhones: visibilityPhones.map((row) => row.phone),
|
|
||||||
benefitAmount: p.benefitAmount ?? p.price,
|
benefitAmount: p.benefitAmount ?? p.price,
|
||||||
price: Number(p.price),
|
price: Number(p.price),
|
||||||
benefitDisplay: Number(p.benefitAmount ?? p.price),
|
benefitDisplay: Number(p.benefitAmount ?? p.price),
|
||||||
@@ -119,11 +117,9 @@ export class CatalogService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const media = mapProductMedia(product, resources);
|
const media = mapProductMedia(product, resources);
|
||||||
const { visibilityPhones, ...rest } = product;
|
const { visibilityPhones: _phones, visibilityWhitelistEnabled: _wl, ...rest } = product;
|
||||||
return serializeBigInt({
|
return serializeBigInt({
|
||||||
...rest,
|
...rest,
|
||||||
visibilityWhitelistEnabled: product.visibilityWhitelistEnabled,
|
|
||||||
visibilityPhones: visibilityPhones.map((row) => row.phone),
|
|
||||||
benefitAmount: product.benefitAmount ?? product.price,
|
benefitAmount: product.benefitAmount ?? product.price,
|
||||||
price: Number(product.price),
|
price: Number(product.price),
|
||||||
...media,
|
...media,
|
||||||
|
|||||||
Reference in New Issue
Block a user