Store phone/tablet/photo-frame shells with catalogs, append pages from extra images or PDFs, and reorder drafts with drag-and-drop. Co-authored-by: Cursor <cursoragent@cursor.com>
379 lines
20 KiB
Markdown
379 lines
20 KiB
Markdown
# کاتالوگ دیجیتال (Digital Catalog)
|
||
|
||
سند مرجع پروژه — ۱۸ اوت ۲۰۲۶، بهروز تا ۱۹ اوت ۲۰۲۶
|
||
قبل از پیادهسازی نوشته شده است. اگر رفتار محصول عوض شد، همین فایل باید بهروز شود.
|
||
|
||
---
|
||
|
||
## ۱. محصول چیست
|
||
|
||
کاربر یک PDF چندصفحهای یا چند تصویر میدهد. سیستم یک **کاتالوگ دیجیتال** میسازد: صفحات با افکت ورقزدن نمایش داده میشوند، یک **لینک مشاهده** و یک **QR** تحویل میشود.
|
||
|
||
هدف دو کار با هم است:
|
||
|
||
1. ساخت و دیدن کاتالوگ همان لحظه
|
||
2. اشتراکگذاری همان کاتالوگ با لینک و QR، بدون ثبتنام
|
||
|
||
کاتالوگها در سایت **لیست نمیشوند**. فقط کسی که لینک یا QR را دارد میتواند ببیند.
|
||
|
||
---
|
||
|
||
## ۲. تصمیمهای قفلشده
|
||
|
||
| موضوع | تصمیم |
|
||
|---|---|
|
||
| ثبتنام / ورود | ندارد. هر بازدیدکننده دامنه میتواند بسازد |
|
||
| گالری عمومی | ندارد |
|
||
| خروجی ساخت | لینک مشاهده + تصویر QR + لینک حذف |
|
||
| ویرایش بعدی | ندارد (صفحات، PDF، افکت و زمان ورق بعد از ساخت عوض نمیشود) |
|
||
| انقضای خودکار | ندارد |
|
||
| زبان رابط | فارسی، راستبهچپ |
|
||
| نسخه اول | ساده؛ بدون حساب کاربری، بدون پنل ادمین، بدون SEO کاتالوگ |
|
||
| دامنهٔ زنده | `https://ktg.montzar.ir` |
|
||
| دیتابیس نسخه اول | SQLite روی دیسک همین پروژه؛ به Postgres مونتایم/سوپرادمین وصل نمیشود |
|
||
|
||
---
|
||
|
||
## ۳. چه چیزهایی نسخه اول نیست
|
||
|
||
اینها عمداً بیرون هستند تا پروژه ساده بماند:
|
||
|
||
- حساب کاربری، نقش، پنل مدیریت
|
||
- گالری، جستجو، نقشه سایت از کاتالوگها
|
||
- ویرایش کاتالوگ بعد از ساخت
|
||
- نقطه کلیکپذیر روی صفحه، فهرست مطالب، ویدیو داخل صفحه
|
||
- موسیقی پسزمینه، پخش خودکار اجباری
|
||
- اپ دسکتاپ / موبایل بومی
|
||
- پرداخت، چندزبانه بودن رابط، تمهای زیاد
|
||
- Three.js و شبیهسازی فیزیک کاغذ از صفر
|
||
|
||
اگر فایل اشتباه آپلود شد: کاتالوگ را با لینک حذف پاک میکند و یکی جدید میسازد.
|
||
|
||
---
|
||
|
||
## ۴. مسیر کاربر
|
||
|
||
```text
|
||
ورود به دامنه
|
||
→ صفحه ساخت (آپلود PDF یا چند تصویر + انتخاب افکت)
|
||
→ پردازش و ذخیره
|
||
→ صفحه نتیجه:
|
||
• لینک مشاهده (کپی)
|
||
• QR (نمایش + دانلود PNG)
|
||
• باز کردن کاتالوگ
|
||
• لینک حذف (یکبار نشان داده میشود)
|
||
→ بیننده با لینک یا اسکن QR کاتالوگ را ورق میزند
|
||
```
|
||
|
||
لینک حذف را باید همان لحظه ذخیره کند. اگر گم شود، راه بازیابی نیست (چون حساب کاربری نداریم). این محدودیت باید روی صفحه نتیجه نوشته شود.
|
||
|
||
---
|
||
|
||
## ۵. امکانات نسخه اول
|
||
|
||
### ساخت
|
||
|
||
- Drag & drop یا انتخاب فایل
|
||
- هنگام خواندن PDF/تصویر، آمادهسازی صفحات و ساخت کاتالوگ، لودینگ با متن پیشرفت نشان داده میشود
|
||
- یک PDF **یا** چند تصویر (نه هر دو با هم در یک کاتالوگ)
|
||
- ترتیب تصاویر = ترتیب انتخاب / ترتیب نام فایل؛ امکان جابهجایی قبل از ساخت
|
||
- انتخاب یکی از افکتهای ورقزدن
|
||
- انتخاب قالب نمایش (بدون قالب، گوشی، تبلت، قاب مدرن/کودکانه/کلاسیک). قالب دور کل کاتالوگ است و صفحات داخل همان صفحه/شیشه ورق میخورند. بعد از ساخت عوض نمیشود
|
||
- تعیین زمان ورقخوردن (۴۰۰ تا ۵۰۰۰ میلیثانیه؛ پیشفرض ۱ ثانیه). بعد از ساخت عوض نمیشود. روی مجله، اسلاید، محو و کارت اعمال میشود؛ اسکرول عمودی فقط فاصلهٔ پخش خودکار را از همین زمان میگیرد
|
||
- عنوان اختیاری (اگر خالی باشد: «کاتالوگ»)
|
||
|
||
### مشاهده
|
||
|
||
- نمایش صفحات با افکت انتخابشده (اسلاید، محو و کارت با انتقال ورودی/خروجی واقعی)
|
||
- ۳ ثانیه پس از باز شدن، پخش خودکار شروع میشود؛ فاصلهٔ بین ورقها برابر زمان ورقخوردن ذخیرهشده بهعلاوهٔ کمی مکث است
|
||
- قبلی / بعدی، کلیدهای جهت، کلیک/اسوایپ روی صفحه
|
||
- بندانگشتی صفحات
|
||
- زوم و تمامصفحه
|
||
- دسکتاپ: فقط افکت `magazine` دو صفحه روبهرو نشان میدهد؛ بقیه همیشه یک صفحه
|
||
- موبایل: همیشه یک صفحه؛ نوار عنوان و دکمهها جمع میشوند و هنگام پخش خودکار مخفی میشوند (دکمهٔ کوچک توقف میماند). اگر ورق سهبعدی روی دستگاه ضعیف بود، همان کاتالوگ با اسلاید افقی دیده میشود (تنظیم ذخیرهشده عوض نمیشود)
|
||
|
||
### اشتراک
|
||
|
||
- آدرس ثابت: `{BASE_URL}/c/{id}`
|
||
- QR همان آدرس را کد میکند
|
||
- دانلود QR به صورت PNG
|
||
|
||
### حذف
|
||
|
||
- لینک حذف فقط روی صفحهٔ نتیجه نشان داده میشود؛ باید همان لحظه کپی و ذخیره شود
|
||
- بعداً فقط وقتی به کار میآید که سازنده بخواهد همین کاتالوگ را پاک کند (باز کردن لینک در مرورگر و تأیید)
|
||
- در برنامه ذخیره یا بازیابی نمیشود؛ اگر گم شود راهی نیست
|
||
- لینک مشاهده برای اشتراک است؛ لینک حذف را نباید پخش کرد
|
||
- بعد از حذف، لینک مشاهده و QR دیگر کار نمیکنند و فایل صفحات از دیسک پاک میشوند
|
||
|
||
---
|
||
|
||
## ۶. افکتهای ورقزدن
|
||
|
||
موقع ساخت، **یکی** انتخاب میشود و برای همیشه روی همان کاتالوگ میماند.
|
||
|
||
| شناسه | نام نمایشی | رفتار |
|
||
|---|---|---|
|
||
| `magazine` | ورق مجله | سهبعدی واقعی؛ افکت اصلی |
|
||
| `slide` | اسلاید افقی | صفحه بعدی از بغل میآید |
|
||
| `fade` | محو شدن | صفحه فعلی محو، بعدی ظاهر میشود |
|
||
| `card` | کارت | ورق دوبعدی مثل کارت |
|
||
| `scroll` | اسکرول عمودی | صفحات زیر هم؛ مناسب موبایل |
|
||
|
||
پیادهسازی: برای `magazine` از کتابخانه **StPageFlip** استفاده میشود. بقیه با Vue + CSS/transition. فیزیک کاغذ از صفر نوشته نمیشود.
|
||
|
||
---
|
||
|
||
## ۷. معماری
|
||
|
||
یک اپلیکیشن با دو بخش در یک مخزن:
|
||
|
||
- **فرانتاند:** Vue 3 — ساخت، نتیجه، نمایش کاتالوگ
|
||
- **بکاند:** API سبک Node — آپلود، ذخیره، سرو کردن صفحات، QR، حذف
|
||
|
||
کاتالوگها روی سرور ذخیره میشوند تا لینک برای همه باز شود. تبدیل PDF به تصویر **در مرورگر با pdf.js** انجام میشود. سرور PDF خام نمیگیرد؛ فقط JPEG صفحات را ذخیره میکند. به ابزار native مثل Poppler نیاز نیست.
|
||
|
||
```text
|
||
مرورگر سرور
|
||
────── ────
|
||
انتخاب PDF/تصویر
|
||
pdf.js → تصویر هر صفحه
|
||
آپلود تصاویر + افکت + عنوان → ذخیره فایلها روی دیسک
|
||
→ ردیف در SQLite
|
||
→ ساخت QR
|
||
صفحه نتیجه ← لینک/QR/حذف
|
||
بیننده /c/:id → متادیتا + آدرس تصاویر
|
||
نمایش با افکت انتخابشده
|
||
```
|
||
|
||
---
|
||
|
||
## ۸. استک قفلشده
|
||
|
||
| لایه | انتخاب | دلیل |
|
||
|---|---|---|
|
||
| UI | Vue 3 + Vite + TypeScript | کامپوننت و state برای آپلود و نمایش |
|
||
| استایل | CSS ساده + RTL | بدون UI kit سنگین |
|
||
| PDF در مرورگر | pdf.js | تبدیل صفحه به تصویر بدون سرور خاص |
|
||
| ورق مجله | StPageFlip | افکت آماده و قابلنگهداری |
|
||
| QR | بسته `qrcode` در سرور | PNG قابل دانلود |
|
||
| API | Hono روی Node | سبک، TypeScript |
|
||
| داده | SQLite از طریق sql.js + Drizzle | بدون کامپایل native (روی ویندوز بدون Visual Studio هم نصب میشود) |
|
||
| فایل صفحات | پوشه محلی دیسک | ساده برای یک سرور |
|
||
| شناسه | nanoid | کوتاه و غیرقابل حدس نسبی |
|
||
|
||
HTML/CSS خالی رد شد: آپلود، تبدیل PDF، چند افکت، موبایل و صفحه نتیجه خیلی زود شلوغ میشود.
|
||
|
||
Nuxt و فریمورک سنگین بکاند رد شد: نمایش کاتالوگ تقریباً همه در کلاینت است؛ یک API کوچک کافی است.
|
||
|
||
---
|
||
|
||
## ۹. مدل داده
|
||
|
||
جدول `catalogs`:
|
||
|
||
| فیلد | نوع | توضیح |
|
||
|---|---|---|
|
||
| `id` | متن، حدود ۱۰ کاراکتر | شناسه عمومی در لینک |
|
||
| `delete_token` | متن، حدود ۲۴ کاراکتر | فقط برای حذف؛ در URL مشاهده نیست |
|
||
| `title` | متن | عنوان |
|
||
| `effect` | متن | یکی از شناسههای بخش ۶ |
|
||
| `frame` | متن | قالب پوسته: `none`، `phone`، `tablet`، `frame-modern`، `frame-kids`، `frame-classic` |
|
||
| `page_count` | عدد | تعداد صفحات ذخیرهشده |
|
||
| `flip_ms` | عدد | مدت انیمیشن ورقخوردن به میلیثانیه (۴۰۰–۵۰۰۰) |
|
||
| `created_at` | زمان | |
|
||
|
||
فایلها:
|
||
|
||
```text
|
||
storage/catalogs/{id}/
|
||
page-001.jpg
|
||
page-002.jpg
|
||
...
|
||
qr.png
|
||
```
|
||
|
||
صفحات با عرض حداکثر ۱۶۰۰ پیکسل، JPEG با کیفیت حدود ۸۵ ذخیره میشوند.
|
||
|
||
---
|
||
|
||
## ۱۰. API
|
||
|
||
پایه: `/api`
|
||
|
||
| متد | مسیر | کار |
|
||
|---|---|---|
|
||
| `POST` | `/catalogs` | ساخت. بدنه: تصاویر JPEG صفحات (نه خود PDF) + `title` + `effect` + `flipMs` + `frame` |
|
||
| `GET` | `/catalogs/:id` | متادیتا برای بیننده (بدون `delete_token`)؛ شامل `flipMs` و `frame` |
|
||
| `GET` | `/catalogs/:id/pages/:n` | فایل تصویر صفحه |
|
||
| `GET` | `/catalogs/:id/qr` | PNG کد QR. عمومی است چون فقط همان لینک مشاهده را کد میکند |
|
||
| `DELETE` | `/catalogs/:id` | حذف؛ کوئری `token` باید با `delete_token` یکی باشد |
|
||
|
||
پاسخ ساخت:
|
||
|
||
```json
|
||
{
|
||
"id": "abc12xyz90",
|
||
"viewUrl": "https://example.com/c/abc12xyz90",
|
||
"deleteUrl": "https://example.com/c/abc12xyz90/delete?token=...",
|
||
"qrUrl": "https://example.com/api/catalogs/abc12xyz90/qr"
|
||
}
|
||
```
|
||
|
||
`deleteUrl` فقط همین یکبار به سازنده داده میشود. از `GET` عمومی برنمیگردد.
|
||
|
||
صفحات فرانت:
|
||
|
||
| مسیر | صفحه |
|
||
|---|---|
|
||
| `/` | ساخت کاتالوگ |
|
||
| `/c/:id` | نمایش کاتالوگ |
|
||
| `/c/:id/done?token=...` | صفحه نتیجه (لینک، QR، حذف) — توکن فقط برای نشان دادن دکمه حذف و جلوگیری از دیدن این صفحه توسط بیننده معمولی |
|
||
| `/c/:id/delete?token=...` | تأیید حذف |
|
||
|
||
بیننده `/c/:id` را میبیند. صفحه نتیجه بدون توکن باز نشود.
|
||
|
||
---
|
||
|
||
## ۱۱. محدودیتها (ضد سوءاستفاده)
|
||
|
||
چون ثبتنام نیست، این سقفها اجباریاند:
|
||
|
||
| مورد | سقف نسخه اول |
|
||
|---|---|
|
||
| حجم هر فایل ورودی | ۱۰۰ مگابایت |
|
||
| تعداد صفحه | حداکثر ۲۰۰ |
|
||
| فرمت PDF | `application/pdf` |
|
||
| فرمت تصویر | jpeg, png, webp, gif |
|
||
| ساخت از یک IP | ۱۰ کاتالوگ در ساعت (شمارنده در حافظه همان پروسه Node؛ یک سرور) |
|
||
| عرض تصویر ذخیرهشده | حداکثر ۱۶۰۰px |
|
||
| زمان ورقخوردن | ۴۰۰ تا ۵۰۰۰ میلیثانیه |
|
||
|
||
اگر سقف رد شود، پیام فارسی واضح نشان داده میشود و چیزی ذخیره نمیشود.
|
||
|
||
---
|
||
|
||
## ۱۲. امنیت و حریم
|
||
|
||
- کاتالوگ **unlisted** است: endpoint لیست وجود ندارد
|
||
- `id` تصادفی است؛ نباید پشت سر هم و قابل حدس باشد
|
||
- حذف فقط با `delete_token`
|
||
- نوع فایل واقعی بررسی شود، نه فقط پسوند
|
||
- فایل آپلودی هرگز به عنوان اسکریپت سرو نشود
|
||
- برای QR باید `PUBLIC_BASE_URL` در محیط سرور تنظیم شود تا لینک داخل QR درست باشد
|
||
|
||
این مدل «امنیت از طریق مخفی بودن لینک» است، نه قفل رمز روی کاتالوگ. هر کس لینک را داشته باشد محتوا را میبیند.
|
||
|
||
---
|
||
|
||
## ۱۳. رابط کاربری (خلاصه)
|
||
|
||
سه صفحه اصلی، فارسی و RTL:
|
||
|
||
1. **ساخت:** ناحیه آپلود، لودینگ هنگام خواندن فایل و ساخت، لیست صفحات، انتخاب افکت، اسلایدر زمان ورقخوردن، دکمه ساخت
|
||
2. **نتیجه:** لینک مشاهده، QR، هشدار «لینک حذف را ذخیره کن»، فیلد لینک حذف، دکمه حذف
|
||
3. **نمایش:** استیج کاتالوگ، کنترلها، بندانگشتی
|
||
|
||
ظاهر نسخه اول تمیز و ساده باشد، نه داشبورد شلوغ.
|
||
|
||
---
|
||
|
||
## ۱۴. اجرا، دامنه و استقرار
|
||
|
||
یک سرور Node کافی است. SQLite و پوشه `storage/` کنار اپ میمانند.
|
||
|
||
توسعه محلی: `npm run dev` — فرانت `http://127.0.0.1:3000`، API روی `3010` و از طریق `/api` پروکسی میشود.
|
||
|
||
### دامنهٔ زنده (۱۹ اوت ۲۰۲۶)
|
||
|
||
- آدرس عمومی: `https://ktg.montzar.ir`
|
||
- کد: `http://get.feep.ir/Mahdi/digitalcatalog.git` (شاخه `main`)
|
||
- مسیر روی سرور: `/var/www/ktg`
|
||
- PM2: نام `ktg-catalog`، **یک اینستنس، fork** (نه cluster؛ sql.js با چند پروسه خراب میشود)
|
||
- پورت داخلی: `3020` — تداخل نکند با مونتایم (`3000`)، فوریسند (`3001`)، سوپرادمین (`3010`)
|
||
- Nginx: فقط `server_name ktg.montzar.ir`؛ همهٔ مسیرها به `127.0.0.1:3020` پروکسی میشوند (`client_max_body_size 100M` فقط در همین vhost)
|
||
- راهنمای قدمبهقدم: [`deploy/DEPLOY.md`](../deploy/DEPLOY.md)
|
||
|
||
این اپ روی همان هاست مشترک مونتایم است (`185.50.38.53`) ولی **جدا** میماند: پوشه، پورت، پروسهٔ PM2، فایل SQLite و دامنهٔ خودش. دیتابیس Postgres و JWT سوپرادمین لمس نمیشود. اگر بعداً لاگین/پروفایل آمد، باز هم داخل همین دامنه و دیتابیس همین پروژه است؛ کوکی نباید روی `.montzar.ir` باشد.
|
||
|
||
متغیرهای محیط:
|
||
|
||
```text
|
||
# توسعه
|
||
PORT=3000
|
||
API_PORT=3010
|
||
PUBLIC_BASE_URL=http://localhost:3000
|
||
STORAGE_DIR=./storage
|
||
DATABASE_PATH=./data/catalogs.sqlite
|
||
|
||
# سرور ktg.montzar.ir
|
||
NODE_ENV=production
|
||
PORT=3020
|
||
PUBLIC_BASE_URL=https://ktg.montzar.ir
|
||
STORAGE_DIR=/var/www/ktg/storage
|
||
DATABASE_PATH=/var/www/ktg/data/catalogs.sqlite
|
||
```
|
||
|
||
`PUBLIC_BASE_URL` همان دامنهای است که داخل QR نوشته میشود. کاتالوگهای ساختهشده با `localhost` روی دامنهٔ زنده معتبر نیستند؛ باید روی سرور دوباره ساخته شوند.
|
||
|
||
آپدیت کد روی سرور: `git pull` سپس `npm ci && npm run build && pm2 restart ktg-catalog`. پوشههای `storage` و `data` و فایل `.env` پاک نشوند.
|
||
|
||
---
|
||
|
||
## ۱۵. گزینههایی که بررسی شد و انتخاب نشد
|
||
|
||
1. **فقط HTML/CSS** — برای دمو کافی است؛ برای آپلود، PDF، چند افکت و لینک عمومی کافی نیست.
|
||
2. **فقط Vue در مرورگر، بدون سرور** — کاتالوگ بعد از بستن تب از بین میرود؛ لینک و QR برای دیگری کار نمیکند.
|
||
3. **Vue + بکاند + حساب کاربری / گالری** — برای نسخه اول اضافه است.
|
||
|
||
انتخاب نهایی: **Vue 3 + API سبک Node + SQLite + فایل روی دیسک.**
|
||
|
||
---
|
||
|
||
## ۱۶. معیار تمام شدن نسخه اول
|
||
|
||
نسخه اول وقتی تمام است که:
|
||
|
||
- [x] با چند تصویر کاتالوگ ساخته شود (تست API با JPEG)
|
||
- [x] با PDF چندصفحهای در مرورگر (بررسی دستی روی دامنهٔ زنده)
|
||
- [x] پنج افکت در UI پیاده شدهاند (بررسی ظاهری باقی است)
|
||
- [x] لینک `/c/:id` از API متادیتا میگیرد
|
||
- [x] QR همان لینک را کد میکند
|
||
- [x] حذف با توکن کاتالوگ را از بین ببرد
|
||
- [x] بدون توکن نشود حذف کرد
|
||
- [x] گالری یا لیست عمومی وجود نداشته باشد
|
||
- [ ] روی موبایل بررسی دستی
|
||
- [x] سقف تعداد صفحه پیام خطا بدهد
|
||
- [x] روی `https://ktg.montzar.ir` زنده است؛ ساخت کاتالوگ و لینک `/c/:id` کار میکند
|
||
|
||
---
|
||
|
||
## ۱۷. کارهای بعدی (عمداً بعد از نسخه اول)
|
||
|
||
فقط اگر بعداً لازم شد، نه الان:
|
||
|
||
- ذخیره لینکهای همین مرورگر در `localStorage`
|
||
- عوض کردن افکت بدون آپلود دوباره
|
||
- انقضای اختیاری
|
||
- رمز روی کاتالوگ
|
||
- هاست چند سروره / S3
|
||
- حساب کاربری، پروفایل و «کاتالوگهای من» — جدا از سوپرادمین/مونتایم؛ دیتابیس و کوکی مال `ktg.montzar.ir`
|
||
|
||
---
|
||
|
||
## تاریخچه تصمیم
|
||
|
||
| تاریخ | تصمیم |
|
||
|---|---|
|
||
| ۲۰۲۶-۰۸-۱۸ | محصول: کاتالوگ دیجیتال از PDF/تصویر با افکت و اشتراک |
|
||
| ۲۰۲۶-۰۸-۱۸ | از اول هم مشاهده همانجا، هم لینک عمومی + QR |
|
||
| ۲۰۲۶-۰۸-۱۸ | بدون ورود؛ هر بازدیدکننده میتواند بسازد |
|
||
| ۲۰۲۶-۰۸-۱۸ | بدون گالری؛ فقط لینک و QR |
|
||
| ۲۰۲۶-۰۸-۱۸ | بعد از ساخت: لینک + QR + لینک حذف؛ بدون ویرایش و بدون انقضا |
|
||
| ۲۰۲۶-۰۸-۱۸ | استک: Vue 3 + Hono + SQLite + pdf.js + StPageFlip |
|
||
| ۲۰۲۶-۰۸-۱۸ | زمان ورقخوردن هنگام ساخت (۴۰۰–۵۰۰۰ms)؛ روی مجله/اسلاید/محو/کارت |
|
||
| ۲۰۲۶-۰۸-۱۸ | لودینگ هنگام خواندن فایل، آمادهسازی صفحات و ساخت کاتالوگ |
|
||
| ۲۰۲۶-۰۸-۱۹ | استقرار روی هاست مشترک؛ دامنه `ktg.montzar.ir`؛ پورت ۳۰۲۰؛ SQLite جدا |
|
||
| ۲۰۲۶-۰۸-۱۹ | مخزن Gitea: `http://get.feep.ir/Mahdi/digitalcatalog.git` |
|
||
| ۲۰۲۶-۰۸-۲۰ | قالب پوسته دور کاتالوگ: گوشی، تبلت، سه قاب عکس؛ SVG/CSS |
|