13 KiB
بازطراحی حرفهای پوستر اشتراکگذاری پزشک (دانلود)
پروژه
nobat724_front
نقش
بهعنوان متخصص UI/UX عمل کن. خروجی باید یک پوستر برند-محورِ تمیز، مدرن، خوانا و «قابلاشتراک در شبکههای اجتماعی» باشد — نه صرفاً یک باکس اطلاعات.
زمینه
در صفحهی عمومی پزشک (/doctor/[slug]) دکمهی «اشتراک گذاری» یک Modal باز میکند و گزینهی «دانلود» یک پوستر PDF از پزشک میسازد (via html2canvas + jsPDF). پوستر فعلی ساده و نامتوازن است: چیدمان دو ستونهی خام، تایپوگرافی بدون سلسلهمراتب، بدون slogan/امتیاز/شبکههای اجتماعی، لوگو خیلی کوچک، و ابعاد ثابت 600×700 که با محتوای متغیر (لیست خدمات/آدرس بلند) سرریز/نامرتب میشود.
مکانیزم آمادهسازی تصویر قبلاً درست شده: List.js قبل از html2canvas منتظر لود همهی <img>ها و آماده شدن QR میماند، و عکس پروفایل از DoctorAvatar مشترک با صفحه میآید. این بازطراحی فقط لایهی بصری/چیدمان پوستر است؛ منطق دانلود و انتظار تصویر را نشکن.
هدف
پوستر را به بهترین شکل ممکن بازطراحی کن با این الزامات کاربر:
- لوگوی سایت حتماً حاضر و واضح باشد (نه لوگوی ۳۰px گوشه) — در هدر بهصورت برند، و بهصورت واترمارک ظریف در فوتر.
- کیفیت دانلود بالا و خروجی «شبکهاجتماعیپسند».
- RTL، فونت Vazir، هماهنگ با هویت بصری سایت.
فایلهای مرتبط
| فایل | نقش |
|---|---|
components/doctor/poster/index.js |
چیدمان اصلی پوستر — بازطراحی کامل |
components/doctor/poster/ImageProfile.js |
عکس پزشک (DoctorAvatar، ring) |
components/doctor/poster/QrImg.js |
QR + دامنه؛ onReady را نگهدار |
components/doctor/poster/Services.js |
لیست خدمات (data.expertise) |
components/doctor/poster/Address.js |
آدرس (data.address[0].address) |
components/doctor/poster/Telefon.js |
تلفنها (data.address[*].phone) |
components/doctor/detailDoctor/Share.js |
کانتینر مخفی hiddenRef (ابعاد ثابت) + Modal |
components/doctor/detailDoctor/List.js |
handleDownloadPDF (html2canvas + jsPDF) — منطق را نگهدار، فقط ابعاد/نام فایل |
lib/getStateInfoClient.js |
matchedCity (شامل logo_url, site_name, slogan, social_media, domain, contact_phone) |
app/globals.css |
کلاس .bg-poster (خط ۸۳۸) و .bg-stroke-circle (خط ۸۴۶) |
data/city.json |
فیلدهای هر شهر: logo_url, site_name, slogan, social_media, domain |
public/assets/images/ |
logo.png, logo-2.png, poster.png, stroke-circle.png |
وضعیت فعلی
components/doctor/poster/index.js (چیدمان فعلی خام):
<div className="bg-poster overflow-hidden !pt-[32px] p-[24px] relative flex justify-between items-start">
<div>
<div className="flex items-center justify-start gap-[5px]">
<Image src="/assets/images/logo.png" width={30} height={30} alt="logo" />
<p className="text-[#E7EEF6] text-[16px] font-bold !-translate-y-1">{siteName}</p>
</div>
<p className="mt-[40px] ... text-[20px] font-bold">{data?.name}</p>
<p className="text-[24px] mt-[20px] ...">تخصص: {/* specialties */}</p>
<Services data={data} />
<Address data={data} />
<Telefon data={data} />
</div>
<div className="flex flex-col justify-between items-end min-w-[200px]">
<ImageProfile data={data} />
<QrImg onReady={onQrReady} />
</div>
</div>
components/doctor/detailDoctor/Share.js (کانتینر مخفی — ابعاد ثابت فعلی):
<div className="fixed min-w-[600px] min-h-[700px] w-[600px] h-[700px] top-0 left-0 opacity-0 pointer-events-none -z-10">
<div ref={hiddenRef}>
<Poster data={data} onQrReady={handleQrReady} />
</div>
</div>
components/doctor/detailDoctor/List.js (دانلود — این منطق را نگهدار):
await waitForImages(element);
const canvas = await html2canvas(element, {
backgroundColor: "#ffffff", useCORS: true, scale: 2, imageTimeout: 15000,
});
const imgData = canvas.toDataURL("image/png");
const imgWidth = 210;
const pxPerMm = canvas.width / imgWidth;
const imgHeight = canvas.height / pxPerMm;
const pdf = new jsPDF({ orientation: "portrait", unit: "mm", format: [imgWidth, imgHeight] });
pdf.addImage(imgData, "PNG", 0, 0, imgWidth, imgHeight);
pdf.save("poster.pdf");
دادههای در دسترس
- پزشک (
data):name,specialties[].name,expertise[].name(خدمات),address[].address,address[].phone,experience,point(امتیاز),satisfaction,medical_system_code,img[0].url,social_media. - شهر (
getStateInfoClient().matchedCity):logo_url,site_name,slogan,domain,contact_phone,social_media(instagram/telegram/whatsapp). - QR: از
getStateInfoClient().fullUrl(لینک همین صفحه).
وظایف
۱. کانواس ثابت و باکیفیت
ابعاد پوستر را به یک کانواس ثابت با نسبت مناسب شبکههای اجتماعی ببر تا خروجی همیشه مرتب و پرکیفیت باشد. پیشنهاد: نسبت ۴:۵ (مثلاً 1080×1350) یا استوری 1080×1920. نسبت ۴:۵ برای پست اینستاگرام بهینه است.
- در
Share.jsابعاد کانتینر مخفی را به همان کانواس تغییر بده (مثلاًw-[1080px] h-[1350px]). چون این عنصر مخفی و خارج از viewport رندر میشود، سایز بزرگ مشکلی ندارد. - در
List.jsمیتوانیscaleرا روی2نگه داری (خروجی ۲۱۶۰×۲۷۰۰). اگر سایز کانواس را بزرگ کردی و خروجی سنگین شد،scale: 1.5هم قابل قبول است. نام فایل را معنادار کن:`poster-${data?.name ?? 'doctor'}.pdf`(کاراکترهای غیرمجاز را با-جایگزین کن).
۲. سیستم بصری (بهعنوان UI/UX)
یک زبان بصری منسجم بساز؛ همهی مقادیر زیر پیشنهادیاند، خودت بهعنوان طراح تنظیم کن:
- پسزمینه: گرادیان برند (آبی تیره → آبی، همخانواده با رنگ فعلی
#3987D4/#E7EEF6). میتوانی.bg-poster(تصویرposter.png) را نگه داری یا با گرادیان CSS جایگزین کنی. اگر گرادیان میسازی، از رنگهای rgb/hex استفاده کن (Tailwind v3، بدون oklch) تاhtml2canvasدرست رندر کند. - کارتهای شیشهای/بخشبندی: بخش خدمات/آدرس/تلفن را داخل کارتهای نیمهشفاف با گوشهی گرد و فاصلهگذاری یکنواخت بگذار تا سلسلهمراتب دیده شود.
- تایپوگرافی: سلسلهمراتب واضح — نام پزشک بزرگترین (bold)، تخصص متوسط، برچسب بخشها (خدمات/آدرس/تلفن) کوچکتر با آیکون. فونت فقط Vazir (از قبل در پروژه).
- رنگ تأکید: نارنجی موجود (
CircleOrangeSm) برای بولتها/CTA حفظ شود. - آواتار: عکس دایرهای با حلقهی ظریف (
.bg-stroke-circleفعلی یا ring با border)، سایز بزرگتر و بهعنوان کانون بصری بالای پوستر.
۳. هدر برند + لوگو (الزام کاربر)
هدر شامل:
- لوگوی سایت واضح: از
matchedCity?.logo_urlاستفاده کن؛ اگرnullبود fallback به/assets/images/logo.png(یاlogo-2.pngهرکدام مناسبتر). اندازهی محسوس (مثلاً ارتفاع ۴۸–۶۴px)، نه ۳۰px. site_nameشهر +domainکنارش.- در صورت وجود
slogan، یک خط زیر لوگو.
مثال (pseudocode):
const logoSrc = matchedCity?.logo_url || "/assets/images/logo.png";
// ...
<header className="flex items-center gap-3">
<img src={logoSrc} alt="logo" className="h-14 w-auto" />
<div className="flex flex-col">
<span className="text-[24px] font-bold text-white">{matchedCity?.site_name}</span>
<span className="text-[14px] text-[#B9D2EC]">{matchedCity?.domain}</span>
</div>
</header>
نکته html2canvas: اگر
logo_urlاز دامنهی remote بیاید و آن سرور هدر CORS نفرستد، ممکن است در canvas آلوده/خالی شود. برای remote از<img crossOrigin="anonymous">استفاده کن و اگر لود نشد به لوگوی local برگرد (onError → fallback). لوگوی local همیشه امن است.
۴. بدنه — بخشهای اطلاعاتی با مدیریت سرریز
- هویت پزشک: نام + برچسب تخصصها بهصورت chipهای کوچک (بهجای متن پیوسته با
|). ردیف badgeها: سابقه (experienceسال)، امتیاز (pointبا آیکون ستاره)، رضایت (satisfaction%)، کد نظام (medical_system_code) — هرکدام موجود بود. - خدمات (
expertise): حداکثر N مورد (مثلاً ۶) نشان بده؛ اگر بیشتر بود «+X مورد دیگر». از overflow جلوگیری کن (کانواس ثابت است، محتوای متغیر نباید بشکند). - آدرس: یک آدرس اصلی؛ متن بلند را با
line-clamp(۲ خط) محدود کن. - تلفن: شمارهی اصلی؛ اگر چند شماره بود حداکثر ۲ مورد.
dir="ltr"برای شمارهها. - همهی اینها را طوری بچین که در ارتفاع ثابت کانواس جا شود؛ فضای خالی را با فاصلهگذاری متوازن پر کن، نه با کشیدن یک بخش.
۵. فوتر — QR + CTA + واترمارک لوگو
- بلوک QR (همان
QrImg) با یک برچسب CTA: «برای رزرو نوبت اسکن کنید» و دامنه زیر آن. - آیکون/آیدی شبکههای اجتماعی از
matchedCity?.social_media(اینستاگرام/تلگرام/واتساپ) اگر موجود بود. - واترمارک لوگو: نسخهی کمرنگ لوگو در گوشه/پسزمینهی فوتر برای تأکید برند (الزام «لوگوی سایتم باید باشد»).
QrImgبایدonReadyرا همچنان صدا بزند (منطق انتظار دانلود درList.jsبه آن وابسته است) — این prop و رفتارش را نگهدار.
۶. حفظ سازگاری با پایپلاین دانلود
List.js:waitForImages,imageTimeout,useCORS, و گاردqrReadyرا نگهدار؛ فقط ابعاد format و نام فایل را در صورت نیاز بهروزرسانی کن.Share.js: کانتینر باید همچنان رندرشده ولی مخفی بماند (opacity-0 pointer-events-none -z-10, نهdisplay:none) تا تصاویر لود شوند و html2canvas بتواند snapshot بگیرد.Posterهمچنان props{ data, onQrReady }را بگیرد.
نکات مهم
- Tailwind v3 است (رنگها rgb/hex، بدون oklch) — از رنگهای مدرن غیرقابلرندر در html2canvas پرهیز کن.
- فونت فقط Vazir؛ فونت جدید اضافه نکن. مطمئن شو وزنهای استفادهشده در
globals.cssتعریف شدهاند (font-display swap) تا در snapshot درست بیایند. - همهی assetها local ترجیح داده شوند؛ برای remote (مثل
logo_url) ازcrossOrigin="anonymous"+ fallback استفاده کن. - تصاویر
next/imageدر پوستر: ترجیحاًpriorityبده تا lazy-load نشوند (مثلImageProfile). برای المانهای تزئینی میتوانی از<img>ساده استفاده کنی. - خروجی نهایی را واقعی تست کن: باز کردن Modal → دانلود → باز کردن PDF و بررسی: لوگو واضح، عکس پزشک، QR، خدمات/آدرس/تلفن مرتب، بدون سرریز، و چیدمان متوازن برای پزشکی با دادهی کم و پزشکی با دادهی زیاد (edge case).
- RTL: چیدمان و alignment راستبهچپ درست باشد؛ شمارهها
ltr. - کیفیت: متن نباید تار شود — کانواس ثابت +
scale ≥ 1.5.