Просмотр исходного кода

feat: support PWA features (#2816)

* feat: support PWA features

* fix: move to global.js
xiaoiver 7 лет назад
Родитель
Сommit
3aa5031970

+ 6 - 0
config/config.js

@@ -23,6 +23,12 @@ const plugins = [
       dynamicImport: {
         loadingComponent: './components/PageLoading/index',
       },
+      pwa: {
+        workboxPluginMode: 'InjectManifest',
+        workboxOptions: {
+          importWorkboxFrom: 'local',
+        },
+      },
       ...(!process.env.TEST && os.platform() === 'darwin'
         ? {
             dll: {

BIN
public/icons/icon-128x128.png


BIN
public/icons/icon-192x192.png


+ 39 - 0
src/global.js

@@ -0,0 +1,39 @@
+import { Modal, message } from 'antd';
+import { formatMessage } from 'umi/locale';
+
+// Notify user if offline now
+window.addEventListener('sw.offline', () => {
+  message.warning(formatMessage({ id: 'app.pwa.offline' }));
+});
+
+// Pop up a prompt on the page asking the user if they want to use the latest version
+window.addEventListener('sw.updated', e => {
+  Modal.confirm({
+    title: formatMessage({ id: 'app.pwa.serviceworker.updated' }),
+    content: formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
+    okText: formatMessage({ id: 'app.pwa.serviceworker.updated.ok' }),
+    onOk: async () => {
+      // Check if there is sw whose state is waiting in ServiceWorkerRegistration
+      // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
+      const worker = e.detail && e.detail.waiting;
+      if (!worker) {
+        return Promise.resolve();
+      }
+      // Send skip-waiting event to waiting SW with MessageChannel
+      await new Promise((resolve, reject) => {
+        const channel = new MessageChannel();
+        channel.port1.onmessage = event => {
+          if (event.data.error) {
+            reject(event.data.error);
+          } else {
+            resolve(event.data);
+          }
+        };
+        worker.postMessage({ type: 'skip-waiting' }, [channel.port2]);
+      });
+      // Refresh current page to use the updated HTML and other assets after SW has skiped waiting
+      window.location.reload(true);
+      return true;
+    },
+  });
+});

+ 2 - 2
src/layouts/BasicLayout.js

@@ -180,11 +180,11 @@ class BasicLayout extends React.PureComponent {
     if (!currRouterData) {
       return 'Ant Design Pro';
     }
-    const message = formatMessage({
+    const pageName = formatMessage({
       id: currRouterData.locale || currRouterData.name,
       defaultMessage: currRouterData.name,
     });
-    return `${message} - Ant Design Pro`;
+    return `${pageName} - Ant Design Pro`;
   };
 
   getLayoutStyle = () => {

+ 2 - 0
src/locales/en-US.js

@@ -8,6 +8,7 @@ import monitor from './en-US/monitor';
 import result from './en-US/result';
 import settingDrawer from './en-US/settingDrawer';
 import settings from './en-US/settings';
+import pwa from './en-US/pwa';
 
 export default {
   'navBar.lang': 'Languages',
@@ -28,4 +29,5 @@ export default {
   ...result,
   ...settingDrawer,
   ...settings,
+  ...pwa,
 };

+ 6 - 0
src/locales/en-US/pwa.js

@@ -0,0 +1,6 @@
+export default {
+  'app.pwa.offline': 'You are offline now',
+  'app.pwa.serviceworker.updated': 'New content is available',
+  'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page',
+  'app.pwa.serviceworker.updated.ok': 'Refresh',
+};

+ 2 - 0
src/locales/pt-BR.js

@@ -8,6 +8,7 @@ import monitor from './pt-BR/monitor';
 import result from './pt-BR/result';
 import settingDrawer from './pt-BR/settingDrawer';
 import settings from './pt-BR/settings';
+import pwa from './pt-BR/pwa';
 
 export default {
   'navBar.lang': 'Idiomas',
@@ -28,4 +29,5 @@ export default {
   ...result,
   ...settingDrawer,
   ...settings,
+  ...pwa,
 };

+ 7 - 0
src/locales/pt-BR/pwa.js

@@ -0,0 +1,7 @@
+export default {
+  'app.pwa.offline': 'Você está offline agora',
+  'app.pwa.serviceworker.updated': 'Novo conteúdo está disponível',
+  'app.pwa.serviceworker.updated.hint':
+    'Por favor, pressione o botão "Atualizar" para recarregar a página atual',
+  'app.pwa.serviceworker.updated.ok': 'Atualizar',
+};

+ 2 - 0
src/locales/zh-CN.js

@@ -8,6 +8,7 @@ import monitor from './zh-CN/monitor';
 import result from './zh-CN/result';
 import settingDrawer from './zh-CN/settingDrawer';
 import settings from './zh-CN/settings';
+import pwa from './zh-CN/pwa';
 
 export default {
   'navBar.lang': '语言',
@@ -28,4 +29,5 @@ export default {
   ...result,
   ...settingDrawer,
   ...settings,
+  ...pwa,
 };

+ 6 - 0
src/locales/zh-CN/pwa.js

@@ -0,0 +1,6 @@
+export default {
+  'app.pwa.offline': '当前处于离线状态',
+  'app.pwa.serviceworker.updated': '有新内容',
+  'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
+  'app.pwa.serviceworker.updated.ok': '刷新',
+};

+ 2 - 0
src/locales/zh-TW.js

@@ -8,6 +8,7 @@ import monitor from './zh-TW/monitor';
 import result from './zh-TW/result';
 import settingDrawer from './zh-TW/settingDrawer';
 import settings from './zh-TW/settings';
+import pwa from './zh-TW/pwa';
 
 export default {
   'navBar.lang': '語言',
@@ -28,4 +29,5 @@ export default {
   ...result,
   ...settingDrawer,
   ...settings,
+  ...pwa,
 };

+ 6 - 0
src/locales/zh-TW/pwa.js

@@ -0,0 +1,6 @@
+export default {
+  'app.pwa.offline': '當前處於離線狀態',
+  'app.pwa.serviceworker.updated': '有新內容',
+  'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面',
+  'app.pwa.serviceworker.updated.ok': '刷新',
+};

+ 17 - 0
src/manifest.json

@@ -0,0 +1,17 @@
+{
+  "name": "ant-design-pro",
+  "short_name": "antd-pro",
+  "display": "standalone",
+  "start_url": "./?utm_source=homescreen",
+  "theme_color": "#002140",
+  "background_color": "#001529",
+  "icons": [
+    {
+      "src": "icons/icon-192x192.png",
+      "sizes": "192x192"
+    },{
+      "src": "icons/icon-128x128.png",
+      "sizes": "128x128"
+    }
+  ]
+}

+ 1 - 1
src/models/login.js

@@ -30,7 +30,7 @@ export default {
           if (redirectUrlParams.origin === urlParams.origin) {
             redirect = redirect.substr(urlParams.origin.length);
             if (redirect.match(/^\/.*#/)) {
-              redirect = redirect.substr(redirect.indexOf('#')+1);
+              redirect = redirect.substr(redirect.indexOf('#') + 1);
             }
           } else {
             window.location.href = redirect;

+ 65 - 0
src/service-worker.js

@@ -0,0 +1,65 @@
+/* globals workbox */
+/* eslint-disable no-restricted-globals */
+workbox.core.setCacheNameDetails({
+  prefix: 'antd-pro',
+  suffix: 'v1',
+});
+// Control all opened tabs ASAP
+workbox.clientsClaim();
+
+/**
+ * Use precaching list generated by workbox in build process.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
+ */
+/* eslint-disable no-underscore-dangle */
+workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
+
+/**
+ * Register a navigation route.
+ * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
+ */
+workbox.routing.registerNavigationRoute('/index.html');
+
+/**
+ * Use runtime cache:
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
+ *
+ * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
+ */
+
+/**
+ * Handle API requests
+ */
+workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst());
+
+/**
+ * Handle third party requests
+ */
+workbox.routing.registerRoute(
+  /^https:\/\/gw.alipayobjects.com\//,
+  workbox.strategies.networkFirst()
+);
+workbox.routing.registerRoute(
+  /^https:\/\/cdnjs.cloudflare.com\//,
+  workbox.strategies.networkFirst()
+);
+workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst());
+
+/**
+ * Response to client after skipping waiting with MessageChannel
+ */
+addEventListener('message', event => {
+  const replyPort = event.ports[0];
+  const message = event.data;
+  if (replyPort && message && message.type === 'skip-waiting') {
+    event.waitUntil(
+      self
+        .skipWaiting()
+        .then(
+          () => replyPort.postMessage({ error: null }),
+          error => replyPort.postMessage({ error })
+        )
+    );
+  }
+});