liutian 2 年之前
當前提交
c7455ddc51

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+.vscode
+node_modules
+.pnpm-debug.log
+dist
+lib

+ 15 - 0
package.json

@@ -0,0 +1,15 @@
+{
+  "name": "vue-pdf",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "dev": "pnpm --filter @vue-pdf/webview dev",
+    "dev:core": "pnpm --filter @vue-pdf/core build:watch",
+    "build": "pnpm --filter @vue-pdf/webview build",
+    "build:core": "pnpm --filter @vue-pdf/core build:prod"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC"
+}

+ 10 - 0
packages/core/.babelrc

@@ -0,0 +1,10 @@
+{
+  "presets": [
+    [
+      "@babel/preset-env",
+      {
+        "modules": false
+      }
+    ]
+  ]
+}

+ 95 - 0
packages/core/index.js

@@ -0,0 +1,95 @@
+const ComPDFKitViewer = {
+  init (options, element) {
+    return new Promise((resolve) => {
+      element.addEventListener("ready", function ready () {
+        element.removeEventListener('ready', ready)
+        const iframeWindow = element.querySelector('iframe').contentWindow
+
+        Promise.resolve().then(function() {
+          resolve({
+            docViewer: iframeWindow.instance
+          })
+        })
+
+      })
+      const viewer = new Viewer(options, element)
+    })
+  }
+}
+class Viewer {
+  constructor (options, element) {
+    this.instance = null
+    this.options = options
+    this.initialDoc = options.pdfUrl || null
+    this.element = element
+    element.addEventListener("ready", (function ready() {
+      element.removeEventListener("ready", ready)
+    }))
+    this.create()
+  }
+
+  _createEvent (e, t) {
+    let n;
+    try {
+      n = new CustomEvent(e, {
+          detail: t,
+          bubbles: true,
+          cancelable: true
+      })
+    } catch (o) {
+        (n = document.createEvent("Event")).initEvent(e, true, true),
+        n.detail = t
+    }
+    return n
+  }
+
+  create() {
+    if (this.initialDoc) {
+      this.initialDoc = encodeURIComponent(this.initialDoc)
+      this._create()
+    } else {
+      this._create()
+    }
+  }
+
+  _create () {
+    if (!this._trigger) {
+      this._trigger = function(e, t) {
+        var n = this._createEvent(e, t);
+        this.element.dispatchEvent(n)
+      }
+    }
+    this.createViewer()
+  }
+
+  createViewer () {
+    const self = this
+    let webviewerUrl = './webviewer/index.html'
+    if (this.initialDoc) {
+      webviewerUrl += "#d=\"".concat(decodeURIComponent(this.initialDoc)).concat("\"")
+    }
+    const iframe = document.createElement("iframe")
+    iframe.id = "webviewer-".concat(this.id)
+    iframe.src = webviewerUrl
+    iframe.title = "webviewer"
+    iframe.frameBorder = 0
+    iframe.width = "100%"
+    iframe.height = "100%"
+    iframe.setAttribute("allowfullscreen", true)
+    iframe.setAttribute("webkitallowfullscreen", true)
+    iframe.setAttribute("mozallowfullscreen", true)
+    this.iframe = iframe
+    this.options?.backgroundColor && iframe.setAttribute("data-bgcolor", this.options.backgroundColor),
+    this.options?.assetPath && iframe.setAttribute("data-assetpath", encodeURIComponent(this.options.assetPath)),
+    this.loadListener = function() {
+      var $iframe = self.iframe;
+      self.instance = $iframe.contentWindow.instance;
+      $iframe.contentWindow.instance.initApiUrl(self.options)
+      self._trigger("ready");
+    },
+    iframe.addEventListener("load", this.loadListener),
+    this.element.appendChild(iframe)
+  }
+}
+
+export default ComPDFKitViewer

+ 32 - 0
packages/core/package.json

@@ -0,0 +1,32 @@
+{
+  "name": "@vue-pdf/core",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "type": "module",
+  "scripts": {
+    "clear": "rimraf lib",
+    "build": "rollup -c rollup.config.js",
+    "build:watch": "rollup -c rollup.config.js --watch --sourcemap",
+    "build:prod": "cross-env NODE_ENV=production rollup -c rollup.config.js"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "pdfjs-dist": "^3.3.122"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.19.6",
+    "@babel/plugin-transform-runtime": "^7.19.6",
+    "@babel/preset-env": "^7.19.4",
+    "@babel/runtime-corejs3": "^7.20.1",
+    "@rollup/plugin-babel": "^6.0.2",
+    "@rollup/plugin-commonjs": "^23.0.2",
+    "@rollup/plugin-node-resolve": "^15.0.1",
+    "cross-env": "^7.0.3",
+    "rollup": "^3.2.3",
+    "rollup-plugin-node-builtins": "^2.1.2",
+    "rollup-plugin-uglify": "^6.0.4"
+  }
+}

+ 42 - 0
packages/core/rollup.config.js

@@ -0,0 +1,42 @@
+import nodeResolve from '@rollup/plugin-node-resolve'
+import commonjs from "@rollup/plugin-commonjs"
+import babel from '@rollup/plugin-babel'
+import { uglify } from 'rollup-plugin-uglify'
+
+export default {
+  input: "./src/index.js",
+  // input: "./math.js",
+  output: [
+    {
+      format: "es",
+      name: 'ComPDFKitViewer',
+      file: "../webview/lib/webview.min.js",
+      sourcemap: true
+    }
+  ],
+  plugins: [
+    nodeResolve({
+      preferBuiltins: true,
+      mainFields: ['browser']
+    }),
+    commonjs(),
+    // (uglify()),
+    // (process.env === 'production' && uglify()),
+    babel({
+      exclude: 'node_modules/**', // 防止打包node_modules下的文件
+      // 使用预设
+      presets: [['@babel/preset-env', {
+        "modules": false,
+        // 目标浏览器
+        "targets": {
+          "edge": '17',
+          "firefox": '60',
+          "chrome": '67',
+          "safari": '10.0',
+          'ie': '10',
+        },
+      }]]
+    })
+  ],
+  sourcemap: false
+};

+ 168 - 0
packages/webview/.eslintrc

@@ -0,0 +1,168 @@
+{
+  "parser": "@babel/eslint-parser",
+  "plugins": ["babel", "vue", "import", "node", "promise"],
+  "env": {
+    "browser": true,
+    "node": true,
+    "es6": true,
+    "jest": true
+  },
+  "extends": [
+    "plugin:vue/vue3-essential",
+    "eslint:recommended"
+  ],
+  "parserOptions": {
+    "ecmaVersion": "latest"
+  },
+  "rules": {
+    "radix": "off",
+    "array-callback-return": "error",
+    "object-curly-spacing": ["error", "always"],
+    "curly": ["error", "all"],
+    "brace-style": ["error", "1tbs"],
+    "space-before-blocks": "error",
+    "space-before-function-paren": ["error", {
+      "anonymous": "never",
+      "named": "never",
+      "asyncArrow": "always"
+    }],
+    "keyword-spacing": ["error", { "before": true, "after": true }],
+    "no-undef": "error",
+    "no-trailing-spaces": "error",
+    "semi": "error",
+    "arrow-parens": ["error", "always"],
+    "camelcase": "error",
+    "arrow-body-style": "off",
+    "array-bracket-spacing": "error",
+    "quotes": ["error", "single", { "avoidEscape": true }],
+    "prefer-template": "error",
+    "no-tabs": "error",
+    "import/no-duplicates": "error",
+    "no-unused-vars": "error",
+    "no-unused-expressions": "off",
+    "no-useless-rename": "off",
+    "no-await-in-loop": "off",
+    "no-lonely-if": "off",
+    "guard-for-in": "off",
+    "function-paren-newline": "off",
+    "indent": ["error", 2, { "SwitchCase": 1 }],
+    "no-case-declarations": "off",
+    "no-restricted-syntax": "off",
+    "no-new": "off",
+    "symbol-description": "off",
+    "comma-dangle": "off",
+    "no-empty": [2, { "allowEmptyCatch":  true }],
+    "lines-between-class-members": "off",
+    "no-fallthrough": "off",
+    "func-names": "off",
+    "operator-linebreak": "off",
+    "no-var": 2,
+    "quote-props": "off",
+    "prefer-arrow-callback": "off", // would like to remove this rule https://eslint.org/docs/rules/prefer-arrow-callback#require-using-arrow-functions-for-callbacks-prefer-arrow-callback
+    "dot-notation": "off",
+    "class-methods-use-this": "off",
+    "object-curly-newline": "off",
+    "vars-on-top": "off",
+    "prefer-destructuring": "off",
+    "eol-last": "off",
+    "max-len": "off",
+    "prefer-rest-params": "off", // would like to remove this rule https://eslint.org/docs/rules/prefer-rest-params#suggest-using-the-rest-parameters-instead-of-arguments-prefer-rest-params
+    "no-underscore-dangle": "off",
+    "object-shorthand": "off", // would like to remove this rule https://eslint.org/docs/rules/object-shorthand#require-object-literal-shorthand-syntax-object-shorthand
+    "no-console": ["error", { "allow": ["warn", "error"] }],
+    "no-param-reassign": "off",
+    "no-plusplus": "off",
+    "consistent-return": "off",
+    "new-cap": "off",
+    "linebreak-style": "off",
+    "no-throw-literal": "off",
+    "no-script-url": "off",
+    "no-restricted-globals": "off", // would like to remove this rule https://eslint.org/docs/rules/no-restricted-globals#disallow-specific-global-variables-no-restricted-globals
+    "no-multi-assign": "off", // would like to remove this rule https://eslint.org/docs/rules/no-multi-assign#disallow-use-of-chained-assignment-expressions-no-multi-assign
+    "no-bitwise": "off",
+    "no-prototype-builtins": "off",
+    "no-nested-ternary": "off",
+    "prefer-promise-reject-errors": "off",
+    "prefer-spread": "off", // would like to remove this rule https://eslint.org/docs/rules/prefer-spread#suggest-using-spread-syntax-instead-of-apply-prefer-spread
+    "no-mixed-operators": "off",
+    "no-cond-assign": "off",
+    "no-extend-native": "off", // would be nice to remove, not critical https://eslint.org/docs/rules/no-extend-native#disallow-extending-of-native-objects-no-extend-native
+    "no-restricted-properties": "off",
+    "no-proto": "off", // would like to remove this rule https://eslint.org/docs/rules/no-proto#disallow-use-of-__proto__-no-proto
+    "no-continue": "off",
+    "default-case": "off",
+    "no-shadow": "off", // would like to eventually remove this but its super hard right now https://eslint.org/docs/rules/no-shadow#disallow-variable-declarations-from-shadowing-variables-declared-in-the-outer-scope-no-shadow
+    "no-useless-escape": "off",
+    "wrap-iife": "off",
+    "import/no-cycle": "off",
+    "import/order": "off",
+    "import/named": "off",
+    "import/no-named-as-default": "off",
+    "import/prefer-default-export": "off",
+    "import/no-extraneous-dependencies": "off",
+    "import/no-unresolved": "off",
+    "import/no-webpack-loader-syntax": "off",
+    "import/extensions": [
+      "error",
+      "ignorePackages",
+      {
+        "js": "never",
+        "ts": "never"
+      }
+    ],
+    "@typescript-eslint/no-useless-constructor": "off",
+    "@typescript-eslint/explicit-function-return-type": "off",
+    "@typescript-eslint/no-this-alias": "off",
+    "@typescript-eslint/no-empty-function": "off",
+    "@typescript-eslint/camelcase": "off",
+    "@typescript-eslint/ban-ts-ignore": "off",
+    "@typescript-eslint/no-explicit-any": "off",
+    "@typescript-eslint/no-empty-interface": "off",
+    "@typescript-eslint/no-use-before-define": "off",
+    "@typescript-eslint/class-name-casing": "off",
+    "@typescript-eslint/no-unused-vars": "off",
+    "@pdftron/webviewer/no-string-events": "off",
+    "react/prop-types": "off"
+  },
+  "overrides": [
+    {
+      "files": "**/*.ts",
+      "rules": {
+        "no-useless-constructor": "off"
+      }
+    },
+    {
+      "files": "**/*.stories.js",
+      "rules": {
+        "no-console": "off",
+        "react/prop-types": "off",
+        "no-alert": "off",
+        "no-unused-vars": "off",
+        "@typescript-eslint/no-useless-constructor": "off",
+        "no-useless-constructor": "off"
+      }
+    },
+    {
+      "files": "**/*.spec.js",
+      "rules": {
+        "no-console": "off",
+        "no-undef": "off",
+        "no-unused-vars": "off",
+        "no-alert": "off",
+        "@typescript-eslint/no-useless-constructor": "off",
+        "no-useless-constructor": "off"
+      }
+    },
+    {
+      "files": "**/*.test.js",
+      "rules": {
+        "no-console": "off",
+        "no-undef": "off",
+        "no-unused-vars": "off",
+        "no-alert": "off",
+        "@typescript-eslint/no-useless-constructor": "off",
+        "no-useless-constructor": "off"
+      }
+    }
+  ]
+}

+ 28 - 0
packages/webview/.gitignore

@@ -0,0 +1,28 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 1 - 0
packages/webview/.npmrc

@@ -0,0 +1 @@
+shamefully-hoist=true

+ 41 - 0
packages/webview/README.md

@@ -0,0 +1,41 @@
+# webview
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vitejs.dev/config/).
+
+## Project Setup
+
+```sh
+npm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+npm run dev
+```
+
+### Compile and Minify for Production
+
+```sh
+npm run build
+```
+
+### Run Unit Tests with [Vitest](https://vitest.dev/)
+
+```sh
+npm run test:unit
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+npm run lint
+```

+ 13 - 0
packages/webview/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="/favicon.ico">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>ComPDFKit Web Viewer</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>

+ 8 - 0
packages/webview/jsconfig.json

@@ -0,0 +1,8 @@
+{
+  "compilerOptions": {
+    "baseUrl": "./",
+    "paths": {
+      "@/*": ["src/*"]
+    }
+  }
+}

+ 33 - 0
packages/webview/package.json

@@ -0,0 +1,33 @@
+{
+  "name": "@vue-pdf/webview",
+  "version": "0.0.0",
+  "scripts": {
+    "dev": "vite --host 0.0.0.0 --port 3032",
+    "build": "vite build",
+    "preview": "vite preview",
+    "test:unit": "vitest --environment jsdom --root src/",
+    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
+  },
+  "dependencies": {
+    "@vue-pdf/core": "1.0.0",
+    "@vueuse/core": "^9.12.0",
+    "@vueuse/integrations": "^9.5.0",
+    "axios": "^0.27.2",
+    "dayjs": "^1.11.6",
+    "pinia": "^2.0.23",
+    "vue": "^3.2.41"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^3.1.2",
+    "@vue/test-utils": "^2.1.0",
+    "eslint": "^8.22.0",
+    "eslint-plugin-vue": "^9.3.0",
+    "jsdom": "^20.0.1",
+    "naive-ui": "^2.34.3",
+    "sass": "^1.55.0",
+    "unplugin-vue-components": "^0.22.9",
+    "vite": "^3.1.8",
+    "vite-svg-plugin": "^1.0.2",
+    "vitest": "^0.24.3"
+  }
+}

二進制
packages/webview/public/favicon.ico


+ 102 - 0
packages/webview/src/assets/base.css

@@ -0,0 +1,102 @@
+body {
+  min-height: 100vh;
+  color: var(--c-text);
+  background: var(--c-bg);
+  transition: color 0.5s, background-color 0.5s;
+  line-height: 1.6;
+  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
+    Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
+  font-size: 15px;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+#printContainer {
+  display: none;
+}
+
+@page {
+  margin: 0;
+  size: auto;
+}
+
+@media print {
+  body {
+    background: rgba(0, 0, 0, 0) none;
+  }
+  #outerContainer {
+    display: none;
+  }
+  #printContainer {
+    display: block;
+    height: 100%;
+  }
+  /* wrapper around (scaled) print canvas elements */
+  #printContainer > .printedPage {
+    /* The wrapper always cover the whole page. */
+    height: 100%;
+    width: 100%;
+
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+  }
+
+  #printContainer > .printedPage:not(:last-child) {
+    page-break-after: always;
+  }
+
+  #printContainer > .xfaPrintedPage .xfaPage {
+    position: absolute;
+  }
+
+  #printContainer > .xfaPrintedPage {
+    page-break-after: always;
+    page-break-inside: avoid;
+    width: 100%;
+    height: 100%;
+    position: relative;
+  }
+
+  #printContainer > .printedPage canvas,
+  #printContainer > .printedPage img {
+    /* The intrinsic canvas / image size will make sure that we fit the page. */
+    max-width: 100%;
+    max-height: 100%;
+
+    direction: ltr;
+  }
+}
+
+.annotationContainer > div {
+  position: absolute;
+  z-index: 1;
+  overflow: hidden;
+  cursor: pointer;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+  pointer-events: none;
+}
+.annotationContainer {
+  position: absolute;
+  left: 0;
+  top: 0;
+  z-index: 3;
+  pointer-events: auto;
+}
+.annotationContainer > div line {
+  pointer-events: painted;
+  pointer-events: painted;
+  -webkit-user-select: auto;
+  -moz-user-select: auto;
+  -ms-user-select: auto;
+  user-select: auto;
+  stroke-linecap: butt;
+  stroke-linejoin: miter;
+}
+.point-none {
+  pointer-events: auto;
+}

+ 94 - 0
packages/webview/src/assets/main.scss

@@ -0,0 +1,94 @@
+@import './base.css';
+
+* {
+  transition: background-color .25s ease;
+}
+
+* {
+  &::-webkit-scrollbar {
+    width: 4px;
+    &:horizontal {
+      height: 4px;
+    }
+  }
+
+  &::-webkit-scrollbar-track {
+    width: 4px;
+    border-radius: 4px;
+    background: none;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    border-radius: 4px;
+    background-color: var(--c-scrollbar-bg);
+    border-top: 2px solid transparent;
+  }
+}
+
+.DocumentContainer {
+  border-right: 3px solid transparent;
+}
+
+html, body , #app {
+  width: 100%;
+  max-width: 100vw;
+  height: 100%;
+  margin: 0;
+  padding: 0;
+}
+
+input, button, label, textarea, form, select, #app {
+  font-size: 13px;
+}
+
+input, textarea, select {
+  border-radius: 2px;
+
+  &:focus {
+    outline: none;
+  }
+}
+
+#app {
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  width: 100%;
+  height: 100%;
+  margin: 0;
+  padding: 0;
+  -webkit-font-smoothing: antialiased;
+
+  >.content {
+    display: flex;
+    flex-direction: row;
+    flex: 1;
+    overflow: auto;
+  }
+
+  *, *:before, *:after {
+    box-sizing: border-box;
+  }
+
+  div {
+    -webkit-tap-highlight-color: transparent;
+  }
+
+  canvas {
+    user-select: none;
+
+    &.ql-clipboard {
+      user-select: text;
+    }
+  }
+
+  textarea {
+    appearance: none;
+    user-select: text !important;
+  }
+
+  input[type=text] {
+    appearance: none;
+    user-select: text !important;
+  }
+}

+ 25 - 0
packages/webview/src/helpers/utils.js

@@ -0,0 +1,25 @@
+const uploadFile = (extension) =>
+  new Promise((resolve) => {
+    const fileInput = document.createElement('input');
+    fileInput.type = 'file';
+    fileInput.accept = extension;
+    fileInput.onchange = ()=> {
+      if (fileInput.files) {
+        const reader = new FileReader();
+        reader.onload = () => {
+          const contents = reader.result;
+          resolve(contents);
+        };
+
+        if (extension.includes('xfdf')) {
+          reader.readAsText(fileInput.files[0]);
+        } else {
+          reader.readAsDataURL(fileInput.files[0]);
+        }
+      }
+    };
+    document.body.appendChild(fileInput);
+    fileInput.click();
+  });
+
+export { uploadFile }

+ 11 - 0
packages/webview/src/main.js

@@ -0,0 +1,11 @@
+import { createApp } from 'vue'
+import App from '@/components/App/index.vue'
+import { setupStore } from '@/stores'
+
+import './assets/main.scss'
+
+const app = createApp(App)
+
+setupStore(app)
+
+app.mount('#app')

+ 8 - 0
packages/webview/src/stores/index.js

@@ -0,0 +1,8 @@
+import { createPinia } from 'pinia'
+const store = createPinia()
+
+export function setupStore(app) {
+  app.use(store)
+}
+
+export { store }

+ 21 - 0
packages/webview/src/stores/modules/viewer.js

@@ -0,0 +1,21 @@
+import { defineStore } from 'pinia'
+
+export const useViewerStore = defineStore({
+  id: 'viewer',
+  state: () => ({
+    fullMode: false,
+    currentPage: 0,
+    scale: '',
+    themeMode: 'Light',
+    pageMode: 0,
+    scrollMode: 'Vertical',
+    pageLabels: [],
+    zoomLevel: ['auto', 'page-fit', 0.5, 1, 1.25, 1.5, 2, 2.5],
+    activeTab: 0,
+    activeLeftPanel: 'THUMBS',
+    activeLeftPanelTab: 'THUMBS',
+    activeStickNote: false,
+    activeActiveMeasure: false,
+    activeHand: false
+  })
+})

+ 36 - 0
packages/webview/vite.config.js

@@ -0,0 +1,36 @@
+import { fileURLToPath, URL } from 'node:url'
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import Components from 'unplugin-vue-components/vite'
+import { svgBuilder } from 'vite-svg-plugin'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+  build: {
+    sourcemap: true,
+  },
+  plugins: [
+    vue(),
+    Components(),
+    svgBuilder({
+      path: './src/assets/icons/',
+      prefix: ''
+    })
+  ],
+  resolve: {
+    alias: {
+      '@': fileURLToPath(new URL('./src', import.meta.url))
+    }
+  },
+  server: {
+    port: '3000',
+    proxy: {
+      '/api': {
+        target: 'http://81.68.234.235:8910/',
+        changeOrigin: true,
+        rewrite: (path) => path.replace(/^\/api/, '') // 不可以省略rewrite
+      }
+    }
+  }
+})

+ 2 - 0
pnpm-workspace.yaml

@@ -0,0 +1,2 @@
+packages:
+  - 'packages/*'

+ 99 - 0
webpack.config.dev.js

@@ -0,0 +1,99 @@
+const path = require('path');
+const webpack = require('webpack');
+
+module.exports = {
+  name: 'ui',
+  mode: 'development',
+  devtool: 'cheap-module-eval-source-map',
+  entry: [
+    'webpack-hot-middleware/client?name=ui&path=/__webpack_hmr',
+    path.resolve(__dirname, 'src'),
+  ],
+  output: {
+    path: path.resolve(__dirname, 'src'),
+    filename: 'webviewer-ui.min.js',
+    chunkFilename: 'chunks/[name].chunk.js',
+    publicPath: '/',
+  },
+  plugins: [new webpack.HotModuleReplacementPlugin()],
+  module: {
+    rules: [
+      {
+        test: /\.js$/,
+        use: {
+          loader: 'babel-loader',
+          options: {
+            presets: [
+              '@babel/preset-react',
+              [
+                '@babel/preset-env',
+                {
+                  useBuiltIns: 'entry',
+                  corejs: 3,
+                },
+              ],
+            ],
+            plugins: [
+              'react-hot-loader/babel',
+              '@babel/plugin-proposal-function-sent',
+              '@babel/plugin-proposal-export-namespace-from',
+              '@babel/plugin-proposal-numeric-separator',
+              '@babel/plugin-proposal-throw-expressions',
+              '@babel/plugin-proposal-class-properties',
+              '@babel/plugin-proposal-optional-chaining',
+            ],
+          },
+        },
+        include: [path.resolve(__dirname, 'src')],
+      },
+      {
+        test: /\.scss$/,
+        use: [
+          'style-loader',
+          'css-loader',
+          {
+            loader: 'postcss-loader',
+            options: {
+              ident: 'postcss',
+              plugins: loader => [
+                require('postcss-import')({ root: loader.resourcePath }),
+                require('postcss-preset-env')(),
+                require('cssnano')(),
+              ],
+            },
+          },
+          'sass-loader',
+        ],
+        include: path.resolve(__dirname, 'src'),
+      },
+      {
+        test: /\.svg$/,
+        use: ['svg-inline-loader'],
+      },
+      {
+        test: /\.woff(2)?$/,
+        use: [
+          {
+            loader: 'file-loader',
+            options: {
+              name: '[name].[ext]',
+            },
+          },
+        ],
+      },
+    ],
+  },
+  resolve: {
+    alias: {
+      src: path.resolve(__dirname, 'src/'),
+      components: path.resolve(__dirname, 'src/components/'),
+      constants: path.resolve(__dirname, 'src/constants/'),
+      helpers: path.resolve(__dirname, 'src/helpers/'),
+      hooks: path.resolve(__dirname, 'src/hooks/'),
+      actions: path.resolve(__dirname, 'src/redux/actions/'),
+      reducers: path.resolve(__dirname, 'src/redux/reducers/'),
+      selectors: path.resolve(__dirname, 'src/redux/selectors/'),
+      core: path.resolve(__dirname, 'src/core/'),
+    },
+  }
+};

+ 134 - 0
webpack.config.prod.js

@@ -0,0 +1,134 @@
+const path = require('path');
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
+const MiniCssExtractPlugin = require('mini-css-extract-plugin');
+
+module.exports = {
+  mode: 'production',
+  entry: path.resolve(__dirname, 'src'),
+  output: {
+    path: path.resolve(__dirname, 'build'),
+    filename: 'webviewer-ui.min.js',
+    chunkFilename: 'chunks/[name].chunk.js',
+    publicPath: './',
+  },
+  plugins: [
+    new CopyWebpackPlugin([
+      {
+        from: './src/index.core.html',
+        to: '../build/index.html',
+      },
+      {
+        from: './i18n',
+        to: '../build/i18n',
+      },
+      {
+        from: './assets',
+        to: '../build/assets',
+        ignore: ['icons/*.svg'],
+      },
+      {
+        from: './src/configorigin.txt',
+        to: '../build/configorigin.txt',
+      },
+    ]),
+    new MiniCssExtractPlugin({
+      filename: 'style.css',
+      chunkFilename: 'chunks/[name].chunk.css'
+    }),
+    // new BundleAnalyzerPlugin()
+  ],
+  module: {
+    rules: [
+      {
+        test: /\.js$/,
+        use: {
+          loader: 'babel-loader',
+          options: {
+            presets: [
+              '@babel/preset-react',
+              [
+                '@babel/preset-env',
+                {
+                  useBuiltIns: 'entry',
+                  corejs: 3,
+                },
+              ],
+            ],
+            plugins: [
+              '@babel/plugin-proposal-function-sent',
+              '@babel/plugin-proposal-export-namespace-from',
+              '@babel/plugin-proposal-numeric-separator',
+              '@babel/plugin-proposal-throw-expressions',
+              '@babel/plugin-proposal-class-properties',
+              '@babel/plugin-proposal-optional-chaining',
+            ],
+          },
+        },
+        include: [path.resolve(__dirname, 'src'), path.resolve(__dirname, 'node_modules')],
+        exclude: function(modulePath) {
+          return /node_modules/.test(modulePath) && !/node_modules.+react-dnd/.test(modulePath);
+        }
+      },
+      {
+        test: /\.scss$/,
+        use: [
+          MiniCssExtractPlugin.loader,
+          'css-loader',
+          {
+            loader: 'postcss-loader',
+            options: {
+              ident: 'postcss',
+              plugins: loader => [
+                require('postcss-import')({ root: loader.resourcePath }),
+                require('postcss-preset-env')(),
+                require('cssnano')(),
+              ],
+            },
+          },
+          'sass-loader',
+        ],
+        include: path.resolve(__dirname, 'src'),
+      },
+      {
+        test: /\.svg$/,
+        use: ['svg-inline-loader'],
+      },
+      {
+        test: /\.woff(2)?$/,
+        use: [
+          {
+            loader: 'file-loader',
+            options: {
+              name: '[name].[ext]',
+              // this is used to overwrite the publicPath that is specified in the output object,
+              // to make the url of the fonts be relative to the minified style.css
+              publicPath: './assets/fonts',
+              outputPath: '/assets/fonts',
+            },
+          },
+        ],
+      },
+    ],
+  },
+  resolve: {
+    alias: {
+      src: path.resolve(__dirname, 'src/'),
+      components: path.resolve(__dirname, 'src/components/'),
+      constants: path.resolve(__dirname, 'src/constants/'),
+      helpers: path.resolve(__dirname, 'src/helpers/'),
+      hooks: path.resolve(__dirname, 'src/hooks/'),
+      actions: path.resolve(__dirname, 'src/redux/actions/'),
+      reducers: path.resolve(__dirname, 'src/redux/reducers/'),
+      selectors: path.resolve(__dirname, 'src/redux/selectors/'),
+      core: path.resolve(__dirname, 'src/core/'),
+    },
+  },
+  optimization: {
+    splitChunks: {
+      automaticNameDelimiter: '.',
+      minSize: 0,
+    },
+  },
+  devtool: 'source-map',
+};