[251211] feat: FeaturedBrandsPanel , TopBannerImage Focus
🕐 커밋 시간: 2025. 12. 11. 13:46:11 📊 변경 통계: • 총 파일: 6개 • 추가: +48줄 • 삭제: -16줄 📁 추가된 파일: + com.twin.app.shoptime/src/components/TQRCode/TQRCodeNew.jsx + com.twin.app.shoptime/src/components/TQRCode/TQRCodeNew.module.less 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/ProductInfoSection/QRCode/QRCode.jsx ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerImage.jsx ~ com.twin.app.shoptime/src/views/FeaturedBrandsPanel/TopBannerImage/TopBannerImage.module.less 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선 • 소규모 기능 개선 • 모듈 구조 개선
This commit is contained in:
93
com.twin.app.shoptime/src/components/TQRCode/TQRCodeNew.jsx
Normal file
93
com.twin.app.shoptime/src/components/TQRCode/TQRCodeNew.jsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { getDeviceAdditionInfo } from "../../actions/deviceActions";
|
||||
import { scaleH, scaleW } from "../../utils/helperMethods";
|
||||
|
||||
export default function TQRCodeNew({
|
||||
isBillingProductVisible,
|
||||
ariaLabel,
|
||||
text,
|
||||
width = "128",
|
||||
height = "128",
|
||||
}) {
|
||||
const qrcodeRef = useRef(null);
|
||||
const deviceInfo = useSelector((state) => state.device.deviceInfo);
|
||||
const { entryMenu, nowMenu } = useSelector((state) => state.common.menu);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (!deviceInfo) {
|
||||
dispatch(getDeviceAdditionInfo());
|
||||
}
|
||||
}, [deviceInfo, dispatch]);
|
||||
|
||||
const applyCircularMask = (scaledWidth, scaledHeight) => {
|
||||
if (!qrcodeRef.current) return;
|
||||
|
||||
const canvas = qrcodeRef.current.querySelector('canvas');
|
||||
if (!canvas) return;
|
||||
|
||||
const radius = scaledWidth / 2;
|
||||
|
||||
// 원본 canvas 저장
|
||||
const tempCanvas = document.createElement('canvas');
|
||||
tempCanvas.width = scaledWidth;
|
||||
tempCanvas.height = scaledHeight;
|
||||
const tempCtx = tempCanvas.getContext('2d');
|
||||
tempCtx.drawImage(canvas, 0, 0);
|
||||
|
||||
// 원본 canvas 초기화
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.clearRect(0, 0, scaledWidth, scaledHeight);
|
||||
|
||||
// 원형 마스크 적용
|
||||
ctx.beginPath();
|
||||
ctx.arc(radius, radius, radius, 0, Math.PI * 2);
|
||||
ctx.clip();
|
||||
|
||||
// 이미지 다시 그리기
|
||||
ctx.drawImage(tempCanvas, 0, 0);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "object" && entryMenu && nowMenu) {
|
||||
if (qrcodeRef.current) {
|
||||
while (qrcodeRef.current.firstChild) {
|
||||
qrcodeRef.current.removeChild(qrcodeRef.current.firstChild);
|
||||
}
|
||||
}
|
||||
// nowMenu 데이터를 Base64로 인코딩
|
||||
const encodedNowMenu = encodeURIComponent(nowMenu);
|
||||
const encodeEntryMenu = encodeURIComponent(entryMenu);
|
||||
|
||||
let idx;
|
||||
|
||||
if (deviceInfo === null || !deviceInfo) {
|
||||
idx = 0;
|
||||
} else {
|
||||
idx = deviceInfo?.dvcIndex;
|
||||
}
|
||||
|
||||
const scaledWidth = scaleW(width);
|
||||
const scaledHeight = scaleH(height);
|
||||
|
||||
const qrcode = new window.QRCode(qrcodeRef.current, {
|
||||
text: isBillingProductVisible
|
||||
? text
|
||||
: `${text}&entryMenu=${encodeEntryMenu}&nowMenu=${encodedNowMenu}&idx=${idx}`,
|
||||
width: scaledWidth,
|
||||
height: scaledHeight,
|
||||
correctLevel: window.QRCode.CorrectLevel.L,
|
||||
});
|
||||
|
||||
// QR코드 생성 완료 후 원형 마스킹 적용
|
||||
setTimeout(() => {
|
||||
applyCircularMask(scaledWidth, scaledHeight);
|
||||
}, 100);
|
||||
}
|
||||
}, [text, deviceInfo, entryMenu, nowMenu, isBillingProductVisible, width, height]);
|
||||
|
||||
return <div aria-label={ariaLabel} ref={qrcodeRef} />;
|
||||
}
|
||||
@@ -570,6 +570,21 @@ export default function ProductAllSection({
|
||||
[productType, themeProductInfo, themeProducts, selectedIndex, productInfo]
|
||||
);
|
||||
|
||||
// 🆕 [251211] patnrId=21인 경우 QR 데이터 확인
|
||||
useEffect(() => {
|
||||
if (productData?.patnrId === 21 || productData?.patnrId === "21") {
|
||||
console.log('[QR-Data] patnrId=21 QR 데이터 확인:', {
|
||||
patnrId: productData?.patnrId,
|
||||
prdtId: productData?.prdtId,
|
||||
qrImgUrl: productData?.qrImgUrl,
|
||||
qrCodeUrl: productData?.qrCodeUrl,
|
||||
hasQrImgUrl: !!productData?.qrImgUrl,
|
||||
hasQrCodeUrl: !!productData?.qrCodeUrl,
|
||||
allData: productData,
|
||||
});
|
||||
}
|
||||
}, [productData]);
|
||||
|
||||
// 단품(결제 가능 상품) - DetailPanel.backup.jsx와 동일한 로직
|
||||
const isBillingProductVisible = useMemo(() => {
|
||||
// API Mode: 기존 로직 100% 유지 (절대 수정 안 함)
|
||||
|
||||
@@ -4,6 +4,7 @@ import classNames from 'classnames';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import TQRCode from '../../../../components/TQRCode/TQRCode';
|
||||
import TQRCodeNew from '../../../../components/TQRCode/TQRCodeNew';
|
||||
import { getQRCodeUrl } from '../../../../utils/helperMethods';
|
||||
import css from './QRCode.module.less';
|
||||
|
||||
@@ -56,13 +57,26 @@ export default function QRCode({
|
||||
return detailUrl;
|
||||
}, [productInfo, isShopByMobile, detailUrl]);
|
||||
|
||||
// patnrId === 21인 경우 TQRCodeNew 사용 (원형 QR코드)
|
||||
const isPatnrId21 = productInfo?.patnrId === 21 || productInfo?.patnrId === "21";
|
||||
|
||||
return (
|
||||
<div className={classNames(css.qrcode, kind ? css.detailQrcode : "")}>
|
||||
{/* {qrCodeUrl && <TQRCode text={qrCodeUrl} width="190" height="190" />} */}
|
||||
{kind === "detail" ? (
|
||||
<TQRCode text={qrCodeUrl} width="240" height="240" />
|
||||
isPatnrId21 ? (
|
||||
<TQRCodeNew text={qrCodeUrl} width="240" height="240" />
|
||||
) : (
|
||||
<TQRCode text={qrCodeUrl} width="240" height="240" />
|
||||
)
|
||||
) : (
|
||||
qrCodeUrl && <TQRCode text={qrCodeUrl} width="190" height="190" />
|
||||
qrCodeUrl && (
|
||||
isPatnrId21 ? (
|
||||
<TQRCodeNew text={qrCodeUrl} width="190" height="190" />
|
||||
) : (
|
||||
<TQRCode text={qrCodeUrl} width="190" height="190" />
|
||||
)
|
||||
)
|
||||
)}
|
||||
{/* todo : 시나리오,UI 릴리즈 후 */}
|
||||
<div className={css.tooltip}>
|
||||
|
||||
@@ -2,13 +2,10 @@ import React, { memo, useCallback, useState } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setShowPopup } from "../../../actions/commonActions";
|
||||
import CustomImage from "../../../components/CustomImage/CustomImage";
|
||||
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||
import Spottable from "@enact/spotlight/Spottable";
|
||||
import css from "./TopBannerImage.module.less";
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ leaveFor: { left: "", right: "", down: "best-seller-spotlightId" }, enterTo: "default-element" },
|
||||
"div"
|
||||
);
|
||||
const SpottableDiv = Spottable("div");
|
||||
|
||||
const TopBannerImage = memo(({ banrImgUrl, banrImgNm, banrNm, pupBanrImgUrl, pupBanrImgNm, spotlightId }) => {
|
||||
console.log("[TOP-BANNER-IMG] Rendering with URL:", banrImgUrl);
|
||||
@@ -51,10 +48,9 @@ const TopBannerImage = memo(({ banrImgUrl, banrImgNm, banrNm, pupBanrImgUrl, pup
|
||||
}
|
||||
|
||||
return (
|
||||
<Container
|
||||
<SpottableDiv
|
||||
className={css.topBannerContainer}
|
||||
spotlightId={spotlightId}
|
||||
tabIndex={0}
|
||||
onClick={handleClick}
|
||||
style={{
|
||||
// 이미지 크기에 맞춰 컨테이너 크기 조정
|
||||
@@ -72,7 +68,7 @@ const TopBannerImage = memo(({ banrImgUrl, banrImgNm, banrNm, pupBanrImgUrl, pup
|
||||
height: imageDimensions.height || 'auto'
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
</SpottableDiv>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,25 +1,32 @@
|
||||
@import "../../../style/CommonStyle.module.less";
|
||||
@import "../../../style/utils.module.less";
|
||||
|
||||
.topBannerContainer {
|
||||
position: absolute;
|
||||
right: 60px;
|
||||
top: 48px;
|
||||
padding: 15px;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
// Spotlight 포커스 스타일
|
||||
// Spotlight 포커스 스타일 (TItemCard 방식)
|
||||
&:focus {
|
||||
&::after {
|
||||
.focused(@boxShadow: 10px, @borderRadius: 4px);
|
||||
}
|
||||
}
|
||||
|
||||
// 마우스 호버 스타일
|
||||
&:hover {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
// Spotlight hover 효과
|
||||
&[data-spotlight-id] {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.topBannerImage {
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
// 크기는 JavaScript에서 동적으로 설정
|
||||
border-radius: 4px;
|
||||
}
|
||||
Reference in New Issue
Block a user