Flutter: نشر الويب وسطح المكتب متعدد المنصات
اكتب مرة واحدة، شغّل في كل مكان — الويب وسطح المكتب ينقلان كودك من الجوال إلى العالم.
📋 المتطلبات السابقة: يجب أن تكون قد أتقنت ما يلي أولًا
- الدرس 17: قنوات المنصة والتشابك الأصلي
1. ما ستتعلمه
- تحسين بناء Flutter Web: WASM / عرض CanvasKit / استراتيجية URL / SEO
- التخطيط المتجاوب للويب: MediaQuery / LayoutBuilder / تصميم نقاط التوقف التكيفية
- تكييف سطح المكتب: إدارة النوافذ بـ window_manager / شريط القوائم / اختصارات الماوس ولوحة المفاتيح
- التعامل مع اختلافات المنصات: kIsWeb / Platform.isAndroid استيرادات مشروطة وتكييف
- ShopApp: واجهة متجر إلكتروني متجاوبة للويب + لوحة تحكم إدارية لسطح المكتب
2. قصة التركيز على الجوال فقط
(1) المشكلة: نمو مستخدمي الجوال يصل للذروة
نمو DAU لـ ShopApp لبوب على الجوال انخفض من 50% شهريًا إلى 5%. بيانات السوق تُظهر: 40% من طلبات التجارة الإلكترونية تأتي من سطح المكتب (بمتوسط طلب أعلى بمقدار الضعف)، وSEO للويب يجلب زيارات مجانية. لكن بوب لديه قاعدة كود Flutter واحدة فقط — بناء الويب/سطح المكتب من الصفر سيستغرق 3 أشهر على الأقل.
(2) حل Flutter متعدد المنصات
Flutter يُجمّع نفس قاعدة الكود للويب وسطح المكتب — تحتاج فقط للتعامل مع تكييف التخطيط والميزات الخاصة بالمنصة.
# Build for web
flutter build web --wasm
# Build for Windows
flutter build windows
# Build for macOS
flutter build macos
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
(3) النتيجة: صفر تطوير إضافي + 3 أيام لإطلاق نسخة الويب
قضى بوب 3 أيام فقط في تكييف التخطيط، ونسخة الويب كانت جاهزة. لوحة تحكم سطح المكبت أعادت استخدام 80% من كود الجوال وأُنجزت في أسبوعين.
3. بنية النشر متعدد المنصات
graph TD
SC[Single Codebase] --> WEB[Flutter Web]
SC --> DSK[Desktop]
WEB --> WASM[WASM / CanvasKit]
WEB --> SEO2[URL Strategy / SEO]
DSK --> WIN[Windows]
DSK --> MAC[macOS]
DSK --> LNX[Linux]
DSK --> WM[window_manager]
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
(1) نظرة عامة على اختلافات المنصات
| الميزة | الجوال | الويب | سطح المكتب |
|---|---|---|---|
| الإدخال | لمس | ماوس + لوحة مفاتيح | ماوس + لوحة مفاتيح |
| الشاشة | اتجاه ثابت | حجم متغير | نافذة قابلة لتغيير الحجم |
| نظام الملفات | صندوق رمل | بدون | وصول كامل |
| الإشعارات الفورية | FCM/APNs | Notification API | بدون دعم أصلي |
| الدقة | 1x-4x | بكسلات CSS | DPI عالي |
4. بناء وتحسين Flutter Web
(1) مقارنة محركات عرض الويب
| المحرك | التحميل الأول | أداء العرض | التوافق | حالة الاستخدام الموصى بها |
|---|---|---|---|---|
| CanvasKit | أكبر (2MB+) | عالي (GPU) | جيد | الافتراضي للإنتاج |
| عارض HTML | أصغر | منخفض (DOM) | مقبول | تطبيقات خفيفة |
| WASM (Skwasm) | متوسط | الأعلى | Flutter 3.22+ | الافتراضي المستقبلي |
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: إعدادات بناء الويب
# Build with CanvasKit (default)
flutter build web
# Build with WASM (Flutter 3.22+, recommended)
flutter build web --wasm
# Build with HTML renderer
flutter build web --renderer html
# Custom base href for subdirectory deployment
flutter build web --base-href "/shop/"
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
(2) استراتيجية URL
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: استراتيجية URL بالمسار
import 'package:flutter/material.dart';
import 'package:flutter_web_plugins/url_strategy.dart';
// ShopApp comes from project lib/app.dart
void main() {
// Use path URL strategy (clean URLs: /product/42)
usePathUrlStrategy();
// OR use hash URL strategy (compatibility: /#/product/42)
// useHashUrlStrategy();
runApp(const ShopApp());
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
| الاستراتيجية | تنسيق URL | إعدادات الخادم | SEO |
|---|---|---|---|
| المسار | /product/42 |
تحتاج توجيه احتياطي | جيد |
| التجزئة | /#/product/42 |
بدون إعدادات مطلوبة | ضعيف |
(3) تحسين SEO للويب
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: علامات Meta والمعنوية
<!-- web/index.html -->
`<head>`
<meta name="description" content="ShopApp - Million-level products, USD settlement">
<meta name="keywords" content="ecommerce, shop, deals">
<meta property="og:title" content="ShopApp">
<meta property="og:description" content="Cross-platform ecommerce app">
`<title>`ShopApp - Shop the World</title>
</head>
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
5. التخطيط المتجاوب
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: نقاط توقف تكيفية بـ LayoutBuilder
import 'package:flutter/material.dart';
// ResponsiveLayout: three-platform adaptive layout component
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget tablet;
final Widget desktop;
const ResponsiveLayout({
super.key,
required this.mobile,
required this.tablet,
required this.desktop,
});
static bool isMobile(BuildContext context) => MediaQuery.sizeOf(context).width < 600;
static bool isTablet(BuildContext context) =>
MediaQuery.sizeOf(context).width >= 600 && MediaQuery.sizeOf(context).width < 1024;
static bool isDesktop(BuildContext context) => MediaQuery.sizeOf(context).width >= 1024;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 1024) return desktop;
if (constraints.maxWidth >= 600) return tablet;
return mobile;
},
);
}
}
// Usage
ResponsiveLayout(
mobile: const MobileProductGrid(crossAxisCount: 2),
tablet: const TabletProductGrid(crossAxisCount: 3),
desktop: const DesktopProductLayout(), // Sidebar + grid
)
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: تخطيط صفحة رئيسية ShopApp للويب
import 'package:flutter/material.dart';
// CategorySidebar / CartPanel / productCards come from the ShopApp project
class WebHomePage extends StatelessWidget {
const WebHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ShopApp'),
actions: [
// Desktop-only: search bar in AppBar
if (ResponsiveLayout.isDesktop(context))
const SizedBox(width: 300, child: SearchBar()),
IconButton(icon: const Icon(Icons.shopping_cart), onPressed: () {}),
IconButton(icon: const Icon(Icons.person), onPressed: () {}),
],
),
body: ResponsiveLayout(
mobile: _buildMobileLayout(),
tablet: _buildTabletLayout(),
desktop: _buildDesktopLayout(),
),
);
}
Widget _buildDesktopLayout() {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Sidebar categories
SizedBox(
width: 240,
child: CategorySidebar(),
),
// Main content
Expanded(
child: GridView.count(
crossAxisCount: 4,
children: productCards,
),
),
// Cart panel (desktop only)
SizedBox(
width: 300,
child: CartPanel(),
),
],
);
}
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
6. تكييف سطح المكتب
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: window_manager لإدارة النوافذ
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
// Dependency: window_manager: ^0.3.0 (add to dependencies)
// ShopApp comes from project lib/app.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await windowManager.ensureInitialized();
WindowOptions windowOptions = const WindowOptions(
size: Size(1200, 800),
minimumSize: Size(800, 600),
center: true,
backgroundColor: Colors.transparent,
skipTaskbar: false,
titleBarStyle: TitleBarStyle.normal,
title: 'ShopApp',
);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
});
runApp(const ShopApp());
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: اختصارات لوحة المفاتيح
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
// Dependency: go_router: ^13.0.0 (add to dependencies)
// showSearch / router come from the ShopApp project
class ShortcutWrapper extends StatelessWidget {
final Widget child;
const ShortcutWrapper({super.key, required this.child});
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: {
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyF): const Intent(#search),
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyK): const Intent(#cart),
LogicalKeySet(LogicalKeyboardKey.escape): const Intent(#back),
},
child: Actions(
actions: {
Intent(#search): CallbackAction(onInvoke: (_) => showSearch(context)),
Intent(#cart): CallbackAction(onInvoke: (_) => context.go('/cart')),
Intent(#back): CallbackAction(onInvoke: (_) => Navigator.maybePop(context)),
},
child: child,
),
);
}
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
7. التعامل مع اختلافات المنصات
▶ مثال
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
: استيرادات مشروطة وكشف المنصة
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'dart:io' show Platform;
// Note: dart:io is unavailable on Web — real projects need conditional imports
// NativePayButton / CreditCardOption / PayPalOption come from the ShopApp project
class PlatformHelper {
static bool get isWeb => kIsWeb;
static bool get isMobile => !kIsWeb && (Platform.isAndroid || Platform.isIOS);
static bool get isDesktop => !kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux);
static bool get isApple => !kIsWeb && (Platform.isIOS || Platform.isMacOS);
static bool get supportsNativePay => isApple || (!kIsWeb && Platform.isAndroid);
}
// Usage in widget
Widget buildPaymentOptions() {
return Column(children: [
if (PlatformHelper.supportsNativePay)
NativePayButton(),
CreditCardOption(),
if (!PlatformHelper.isWeb)
PayPalOption(), // PayPal SDK not available on web
]);
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
8. مثال كامل: نقطة دخول ShopApp متعددة المنصات
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:window_manager/window_manager.dart';
import 'dart:io' show Platform;
// Dependencies: flutter_riverpod: ^2.0.0, firebase_core: ^2.0.0, window_manager: ^0.3.0
// DefaultFirebaseOptions / initHive / PlatformHelper / ShortcutWrapper / router / ShopApp come from the ShopApp project
// themeModeNotifierProvider / shopAppLightTheme / shopAppDarkTheme come from the ShopApp theme module
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Platform-specific initialization
if (PlatformHelper.isDesktop) {
await _initDesktop();
}
// Common initialization
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await initHive();
runApp(ProviderScope(child: const ShopApp()));
}
Future<void> _initDesktop() async {
await windowManager.ensureInitialized();
const windowOptions = WindowOptions(
size: Size(1200, 800),
minimumSize: Size(800, 600),
title: 'ShopApp Admin',
);
windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
});
}
class ShopApp extends ConsumerWidget {
const ShopApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeModeNotifierProvider);
return MaterialApp.router(
title: PlatformHelper.isDesktop ? 'ShopApp Admin' : 'ShopApp',
theme: shopAppLightTheme,
darkTheme: shopAppDarkTheme,
themeMode: themeMode,
routerConfig: router,
builder: (context, child) {
// Desktop: add shortcuts
if (PlatformHelper.isDesktop) {
return ShortcutWrapper(child: child!);
}
return child!;
},
);
}
}
> الإخراج: شغّل محليًا باستخدام Flutter SDK (Flutter 3.x / Dart 3.x). خادم Piston لا يحتوي على Flutter؛ استخدم `flutter run` على جهازك المحلي للمقارنة. قد تختلف واجهة/حالة المستخدم الفعلية قليلاً بين المنصات.
❓ أسئلة شائعة
📖 ملخص
- Flutter Web يدعم محركي عرض CanvasKit وWASM؛ WASM يقدم أفضل أداء
- استراتيجية URL بالمسار صديقة لـ SEO لكنها تحتاج إعدادات توجيه احتياطي على الخادم
- LayoutBuilder + نقاط التوقف تحقق تخطيطًا تكيفيًا لثلاث منصات: جوال/لوحي/سطح مكتب
- window_manager يدير حجم نافذة سطح المكبت وشريط العنوان والأبعاد الدنيا
- kIsWeb وPlatform.isXxx تكشف المنصة للتعامل الشرطي مع اختلافات المنصات
📝 تمارين
- أساسي (الصعوبة ⭐): ابنِ نسخة ويب بـ
flutter build web، شغّلها في متصفح، واضبط استراتيجية URL بالمسار. - متوسط (الصعوبة ⭐⭐): نفذ ResponsiveLayout بتكييف ثلاث منصات: جوال شبكة عمودين، لوحي ثلاثة أعمدة، سطح مكتب شريط جانبي + أربعة أعمدة.
- متقدم (الصعوبة ⭐⭐⭐): نفذ نسخة ShopApp متعددة المنصات كاملة: واجهة متجر إلكتروني متجاوبة للويب + لوحة تحكم إدارية لسطح المكتب (window_manager + اختصارات لوحة المفاتيح + شريط قوائم)، بمشاركة 90% من الكود.