tangning 1 год назад
Сommit
35bf0f1b26
100 измененных файлов с 18661 добавлено и 0 удалено
  1. 24 0
      .gitignore
  2. 3 0
      .vscode/extensions.json
  3. 25 0
      .vscode/settings.json
  4. 29 0
      1.6-default.json
  5. 34 0
      package.json
  6. 3 0
      packages/qjkankan-components/.browserslistrc
  7. 1 0
      packages/qjkankan-components/.env
  8. 23 0
      packages/qjkankan-components/.gitignore
  9. 19 0
      packages/qjkankan-components/README.md
  10. 3 0
      packages/qjkankan-components/babel.config.js
  11. 505 0
      packages/qjkankan-components/package-lock.json
  12. 17 0
      packages/qjkankan-components/package.json
  13. BIN
      packages/qjkankan-components/public/favicon.ico
  14. 24 0
      packages/qjkankan-components/public/index.html
  15. 8793 0
      packages/qjkankan-components/public/static/lib/quill/quill.min.js
  16. 950 0
      packages/qjkankan-components/public/static/lib/quill/quill.snow.css
  17. 8 0
      packages/qjkankan-components/src/__test__/editor/App.vue
  18. 6 0
      packages/qjkankan-components/src/__test__/editor/main.js
  19. BIN
      packages/qjkankan-components/src/assets/img/icons/toast-error.png
  20. BIN
      packages/qjkankan-components/src/assets/img/icons/toast-success.png
  21. BIN
      packages/qjkankan-components/src/assets/img/icons/toast-warn.png
  22. 79 0
      packages/qjkankan-components/src/assets/scss/_base-vars.scss
  23. 293 0
      packages/qjkankan-components/src/assets/scss/_base.scss
  24. 16 0
      packages/qjkankan-components/src/assets/scss/_components.scss
  25. 22 0
      packages/qjkankan-components/src/assets/scss/components/_audio.scss
  26. 82 0
      packages/qjkankan-components/src/assets/scss/components/_button.scss
  27. 4 0
      packages/qjkankan-components/src/assets/scss/components/_cropper.scss
  28. 88 0
      packages/qjkankan-components/src/assets/scss/components/_dialog.scss
  29. 3 0
      packages/qjkankan-components/src/assets/scss/components/_floating.scss
  30. 30 0
      packages/qjkankan-components/src/assets/scss/components/_gate.scss
  31. 94 0
      packages/qjkankan-components/src/assets/scss/components/_group.scss
  32. 75 0
      packages/qjkankan-components/src/assets/scss/components/_icon.scss
  33. 656 0
      packages/qjkankan-components/src/assets/scss/components/_input.scss
  34. 56 0
      packages/qjkankan-components/src/assets/scss/components/_loading.scss
  35. 27 0
      packages/qjkankan-components/src/assets/scss/components/_men-item.scss
  36. 41 0
      packages/qjkankan-components/src/assets/scss/components/_message.scss
  37. 28 0
      packages/qjkankan-components/src/assets/scss/components/_size-animation.scss
  38. 27 0
      packages/qjkankan-components/src/assets/scss/components/_slide.scss
  39. 90 0
      packages/qjkankan-components/src/assets/scss/components/_toast.scss
  40. 173 0
      packages/qjkankan-components/src/assets/scss/components/_tree.scss
  41. 24 0
      packages/qjkankan-components/src/assets/scss/editor/_head.scss
  42. 7 0
      packages/qjkankan-components/src/assets/scss/editor/_layout.scss
  43. 15 0
      packages/qjkankan-components/src/assets/scss/editor/_main.scss
  44. 37 0
      packages/qjkankan-components/src/assets/scss/editor/_menu.scss
  45. 27 0
      packages/qjkankan-components/src/assets/scss/editor/_toolbar.scss
  46. 31 0
      packages/qjkankan-components/src/assets/scss/editor/_toolbox.scss
  47. 15 0
      packages/qjkankan-components/src/assets/scss/editor/_view.scss
  48. 12 0
      packages/qjkankan-components/src/assets/scss/theme-editor.scss
  49. 56 0
      packages/qjkankan-components/src/components/audio/index.vue
  50. 52 0
      packages/qjkankan-components/src/components/button/index.vue
  51. 8 0
      packages/qjkankan-components/src/components/cropper/cropper.vue
  52. 37 0
      packages/qjkankan-components/src/components/cropper/index.js
  53. 52 0
      packages/qjkankan-components/src/components/dialog/Alert.vue
  54. 58 0
      packages/qjkankan-components/src/components/dialog/Confirm.vue
  55. 17 0
      packages/qjkankan-components/src/components/dialog/Dialog-content.vue
  56. 27 0
      packages/qjkankan-components/src/components/dialog/Dialog.vue
  57. 70 0
      packages/qjkankan-components/src/components/dialog/Toast.vue
  58. 63 0
      packages/qjkankan-components/src/components/dialog/Window.vue
  59. 93 0
      packages/qjkankan-components/src/components/dialog/index.js
  60. 160 0
      packages/qjkankan-components/src/components/floating/index.vue
  61. 1 0
      packages/qjkankan-components/src/components/gate/constant.js
  62. 32 0
      packages/qjkankan-components/src/components/gate/content.vue
  63. 4 0
      packages/qjkankan-components/src/components/gate/index.js
  64. 59 0
      packages/qjkankan-components/src/components/gate/layer.vue
  65. 1 0
      packages/qjkankan-components/src/components/group/constant.js
  66. 4 0
      packages/qjkankan-components/src/components/group/index.js
  67. 32 0
      packages/qjkankan-components/src/components/group/ui-group-option.vue
  68. 93 0
      packages/qjkankan-components/src/components/group/ui-group.vue
  69. 539 0
      packages/qjkankan-components/src/components/icon/iconfont/demo.css
  70. 1913 0
      packages/qjkankan-components/src/components/icon/iconfont/demo_index.html
  71. 315 0
      packages/qjkankan-components/src/components/icon/iconfont/iconfont.css
  72. 60 0
      packages/qjkankan-components/src/components/icon/iconfont/iconfont.js
  73. 534 0
      packages/qjkankan-components/src/components/icon/iconfont/iconfont.json
  74. BIN
      packages/qjkankan-components/src/components/icon/iconfont/iconfont.ttf
  75. BIN
      packages/qjkankan-components/src/components/icon/iconfont/iconfont.woff
  76. BIN
      packages/qjkankan-components/src/components/icon/iconfont/iconfont.woff2
  77. 66 0
      packages/qjkankan-components/src/components/icon/index.vue
  78. 30 0
      packages/qjkankan-components/src/components/input/check-radio.vue
  79. 21 0
      packages/qjkankan-components/src/components/input/checkbox.vue
  80. 199 0
      packages/qjkankan-components/src/components/input/file.vue
  81. 136 0
      packages/qjkankan-components/src/components/input/index.vue
  82. 76 0
      packages/qjkankan-components/src/components/input/number.vue
  83. 3 0
      packages/qjkankan-components/src/components/input/password.vue
  84. 20 0
      packages/qjkankan-components/src/components/input/radio.vue
  85. 117 0
      packages/qjkankan-components/src/components/input/range.vue
  86. 168 0
      packages/qjkankan-components/src/components/input/richtext.vue
  87. 81 0
      packages/qjkankan-components/src/components/input/search.vue
  88. 138 0
      packages/qjkankan-components/src/components/input/select.vue
  89. 241 0
      packages/qjkankan-components/src/components/input/state.js
  90. 22 0
      packages/qjkankan-components/src/components/input/switch.vue
  91. 89 0
      packages/qjkankan-components/src/components/input/test.vue
  92. 68 0
      packages/qjkankan-components/src/components/input/text.vue
  93. 71 0
      packages/qjkankan-components/src/components/input/textarea.vue
  94. 35 0
      packages/qjkankan-components/src/components/loading/Loading.vue
  95. 36 0
      packages/qjkankan-components/src/components/loading/index.js
  96. 38 0
      packages/qjkankan-components/src/components/menu-item/index.vue
  97. 49 0
      packages/qjkankan-components/src/components/message/index.js
  98. 57 0
      packages/qjkankan-components/src/components/message/message.vue
  99. 101 0
      packages/qjkankan-components/src/components/scrollbar/index.css
  100. 0 0
      packages/qjkankan-components/src/components/scrollbar/index.js

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+.DS_Store
+node_modules
+dist
+
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+!.vscode/extensions.json

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["lokalise.i18n-ally"]
+}

+ 25 - 0
.vscode/settings.json

@@ -0,0 +1,25 @@
+{
+  "auto-close-tag.activationOnLanguage": ["xml", "html", "krpano-xml"],
+  "cSpell.words": ["handleclearify", "Krpano"],
+
+  "i18n-ally.localesPaths": [
+    "packages/qjkankan-editor/src/lang",
+    "packages/qjkankan-view/src/locales"
+  ],
+
+  "i18n-ally.keystyle": "nested",
+  // "i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
+  "i18n-ally.namespace": true,
+
+  "i18n-ally.enabledParsers": ["json", "js"],
+
+  "i18n-ally.sortKeys": true,
+
+  "i18n-ally.enabledFrameworks": ["vue", "react"],
+
+  "i18n-ally.sourceLanguage": "zh",
+
+  "i18n-ally.displayLanguage": "zh",
+
+  "i18n-ally.extract.keygenStyle": "camelCase"
+}

+ 29 - 0
1.6-default.json

@@ -0,0 +1,29 @@
+{
+    "scenes": [
+        {
+            // 1.6加 (整个)
+            "customMasks": {
+                "sky": {
+                    "fodderId": "",
+                    "icon": "",
+                    "scale": 1,
+                    "antidistorted": true,
+                    "isShow": false
+                },
+                "earth": {
+                    "fodderId": "",
+                    "icon": "",
+                    "scale": 1,
+                    "antidistorted": true,
+                    "isShow": false
+                }
+            },
+            "initVisual": {
+                "vlookat": 0,
+                "hlookat": 0,
+                "vlookatmin": -90, // 1.6加
+                "vlookatmax": 90 // 1.6加
+            }
+        }
+    ]
+}

+ 34 - 0
package.json

@@ -0,0 +1,34 @@
+{
+  "name": "root",
+  "private": true,
+  "workspaces": [
+    "packages/*"
+  ],
+  "devDependencies": {
+    "lerna": "^5.1.6",
+    "lint-staged": "^10.5.4"
+  },
+  "scripts": {
+    "edit:test-serve": "pnpm --filter @qjkankan/editor run serve-testdev",
+    "edit:build-testprod": "pnpm --filter  @qjkankan/editor run build-testprod",
+    "view:serve": "pnpm --filter @qjkankan/view run serve",
+    "view:build-testprod": "pnpm --filter @qjkankan/view run build-testprod",
+    "kankan-view:serve": "pnpm --filter @qjkankan/kankan-view run serve",
+    "kankan-view:build-testprod": "pnpm --filter @qjkankan/kankan-view run build-testprod"
+
+  },
+  "dependencies": {
+    "npm-run-all": "^4.1.5"
+  },
+  "lint-staged": {
+    "*.js": [
+      "prettier --write"
+    ],
+    "*.ts": [
+      "prettier --write"
+    ],
+    "*.vue": [
+      "prettier --write"
+    ]
+  }
+}

+ 3 - 0
packages/qjkankan-components/.browserslistrc

@@ -0,0 +1,3 @@
+> 1%
+last 2 versions
+not dead

+ 1 - 0
packages/qjkankan-components/.env

@@ -0,0 +1 @@
+VUE_APP_STATIC_DIR=static

+ 23 - 0
packages/qjkankan-components/.gitignore

@@ -0,0 +1,23 @@
+.DS_Store
+node_modules
+/dist
+
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 19 - 0
packages/qjkankan-components/README.md

@@ -0,0 +1,19 @@
+# kankan-components
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 3 - 0
packages/qjkankan-components/babel.config.js

@@ -0,0 +1,3 @@
+module.exports = {
+    presets: ['@vue/cli-plugin-babel/preset']
+}

+ 505 - 0
packages/qjkankan-components/package-lock.json

@@ -0,0 +1,505 @@
+{
+  "name": "@qjkankan/components",
+  "version": "1.2.0",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "@qjkankan/components",
+      "version": "1.2.0",
+      "dependencies": {
+        "c-scrollbar": "^0.1.6",
+        "vue-cropper": "^1.0.2",
+        "vue3-smooth-scrollbar": "^1.0.2"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.19.3",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.19.3.tgz",
+      "integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ==",
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@vue/compiler-core": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.40.tgz",
+      "integrity": "sha512-2Dc3Stk0J/VyQ4OUr2yEC53kU28614lZS+bnrCbFSAIftBJ40g/2yQzf4mPBiFuqguMB7hyHaujdgZAQ67kZYA==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/shared": "3.2.40",
+        "estree-walker": "^2.0.2",
+        "source-map": "^0.6.1"
+      }
+    },
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz",
+      "integrity": "sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==",
+      "dependencies": {
+        "@vue/compiler-core": "3.2.40",
+        "@vue/shared": "3.2.40"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz",
+      "integrity": "sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.40",
+        "@vue/compiler-dom": "3.2.40",
+        "@vue/compiler-ssr": "3.2.40",
+        "@vue/reactivity-transform": "3.2.40",
+        "@vue/shared": "3.2.40",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7",
+        "postcss": "^8.1.10",
+        "source-map": "^0.6.1"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz",
+      "integrity": "sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.2.40",
+        "@vue/shared": "3.2.40"
+      }
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.40.tgz",
+      "integrity": "sha512-N9qgGLlZmtUBMHF9xDT4EkD9RdXde1Xbveb+niWMXuHVWQP5BzgRmE3SFyUBBcyayG4y1lhoz+lphGRRxxK4RA==",
+      "dependencies": {
+        "@vue/shared": "3.2.40"
+      }
+    },
+    "node_modules/@vue/reactivity-transform": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.40.tgz",
+      "integrity": "sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.40",
+        "@vue/shared": "3.2.40",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.40.tgz",
+      "integrity": "sha512-U1+rWf0H8xK8aBUZhnrN97yoZfHbjgw/bGUzfgKPJl69/mXDuSg8CbdBYBn6VVQdR947vWneQBFzdhasyzMUKg==",
+      "dependencies": {
+        "@vue/reactivity": "3.2.40",
+        "@vue/shared": "3.2.40"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.40.tgz",
+      "integrity": "sha512-AO2HMQ+0s2+MCec8hXAhxMgWhFhOPJ/CyRXnmTJ6XIOnJFLrH5Iq3TNwvVcODGR295jy77I6dWPj+wvFoSYaww==",
+      "dependencies": {
+        "@vue/runtime-core": "3.2.40",
+        "@vue/shared": "3.2.40",
+        "csstype": "^2.6.8"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.40.tgz",
+      "integrity": "sha512-gtUcpRwrXOJPJ4qyBpU3EyxQa4EkV8I4f8VrDePcGCPe4O/hd0BPS7v9OgjIQob6Ap8VDz9G+mGTKazE45/95w==",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.2.40",
+        "@vue/shared": "3.2.40"
+      },
+      "peerDependencies": {
+        "vue": "3.2.40"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.40.tgz",
+      "integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
+    },
+    "node_modules/c-scrollbar": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmmirror.com/c-scrollbar/-/c-scrollbar-0.1.7.tgz",
+      "integrity": "sha512-xD0VLJOmZ9MFhO6dGEF+Vr7zEk5QpH/oPmdONuWvWV14XbHiFd4A8A67rg+Nxcs5qjPIHunflbe+ZUHTvUdN8g==",
+      "dependencies": {
+        "vue": "^3.0.0"
+      }
+    },
+    "node_modules/core-js": {
+      "version": "3.25.5",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.25.5.tgz",
+      "integrity": "sha512-nbm6eZSjm+ZuBQxCUPQKQCoUEfFOXjUZ8dTTyikyKaWrTYmAVbykQfwsKE5dBK88u3QCkCrzsx/PPlKfhsvgpw==",
+      "hasInstallScript": true
+    },
+    "node_modules/csstype": {
+      "version": "2.6.21",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz",
+      "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+    },
+    "node_modules/lodash.clamp": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/lodash.clamp/-/lodash.clamp-4.0.3.tgz",
+      "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg=="
+    },
+    "node_modules/lodash.debounce": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+      "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
+    },
+    "node_modules/magic-string": {
+      "version": "0.25.9",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz",
+      "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+      "dependencies": {
+        "sourcemap-codec": "^1.4.8"
+      }
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.4",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz",
+      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz",
+      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+    },
+    "node_modules/postcss": {
+      "version": "8.4.17",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.17.tgz",
+      "integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
+      "dependencies": {
+        "nanoid": "^3.3.4",
+        "picocolors": "^1.0.0",
+        "source-map-js": "^1.0.2"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/smooth-scrollbar": {
+      "version": "8.8.1",
+      "resolved": "https://registry.npmmirror.com/smooth-scrollbar/-/smooth-scrollbar-8.8.1.tgz",
+      "integrity": "sha512-FQDRtdLTRye8LdRHohNAxh0hyo7gl8+APfA+8Qu5S38MBqR8/WqOoRVzjizH6FTdbU1qGsB7gs8LhMeFJ4Jr8g==",
+      "dependencies": {
+        "core-js": "^3.6.4",
+        "lodash.clamp": "^4.0.3",
+        "lodash.debounce": "^4.0.8",
+        "tslib": "^1.10.0"
+      }
+    },
+    "node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/source-map-js": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz",
+      "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/sourcemap-codec": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
+    },
+    "node_modules/tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+    },
+    "node_modules/vue": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-3.2.40.tgz",
+      "integrity": "sha512-1mGHulzUbl2Nk3pfvI5aXYYyJUs1nm4kyvuz38u4xlQkLUn1i2R7nDbI4TufECmY8v1qNBHYy62bCaM+3cHP2A==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.2.40",
+        "@vue/compiler-sfc": "3.2.40",
+        "@vue/runtime-dom": "3.2.40",
+        "@vue/server-renderer": "3.2.40",
+        "@vue/shared": "3.2.40"
+      }
+    },
+    "node_modules/vue-cropper": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/vue-cropper/-/vue-cropper-1.0.5.tgz",
+      "integrity": "sha512-D4XXdqWmMWRLOIV9LIh7/mkH6OBOMQDFbRjwntkxmAtxOtwpC9U5ZZ6lSXw5F5cbd4g8znDjk6MuCwIL+fZSrA=="
+    },
+    "node_modules/vue3-smooth-scrollbar": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/vue3-smooth-scrollbar/-/vue3-smooth-scrollbar-1.0.2.tgz",
+      "integrity": "sha512-TfO0WGbA5KzAosHMmIa4UfB5LcSR3YefNgRbTBZrKw4A5F58vIMALqwXeSCoLtsywdLInN+7u8Fq9cRDiT9EXg==",
+      "dependencies": {
+        "core-js": "^3.8.3",
+        "lodash": "^4.17.21",
+        "smooth-scrollbar": "^8.6.2",
+        "vue": "^3.0.4"
+      },
+      "peerDependencies": {
+        "smooth-scrollbar": "^8.6.2"
+      }
+    }
+  },
+  "dependencies": {
+    "@babel/parser": {
+      "version": "7.19.3",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.19.3.tgz",
+      "integrity": "sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ=="
+    },
+    "@vue/compiler-core": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.2.40.tgz",
+      "integrity": "sha512-2Dc3Stk0J/VyQ4OUr2yEC53kU28614lZS+bnrCbFSAIftBJ40g/2yQzf4mPBiFuqguMB7hyHaujdgZAQ67kZYA==",
+      "requires": {
+        "@babel/parser": "^7.16.4",
+        "@vue/shared": "3.2.40",
+        "estree-walker": "^2.0.2",
+        "source-map": "^0.6.1"
+      }
+    },
+    "@vue/compiler-dom": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz",
+      "integrity": "sha512-OZCNyYVC2LQJy4H7h0o28rtk+4v+HMQygRTpmibGoG9wZyomQiS5otU7qo3Wlq5UfHDw2RFwxb9BJgKjVpjrQw==",
+      "requires": {
+        "@vue/compiler-core": "3.2.40",
+        "@vue/shared": "3.2.40"
+      }
+    },
+    "@vue/compiler-sfc": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz",
+      "integrity": "sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==",
+      "requires": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.40",
+        "@vue/compiler-dom": "3.2.40",
+        "@vue/compiler-ssr": "3.2.40",
+        "@vue/reactivity-transform": "3.2.40",
+        "@vue/shared": "3.2.40",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7",
+        "postcss": "^8.1.10",
+        "source-map": "^0.6.1"
+      }
+    },
+    "@vue/compiler-ssr": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz",
+      "integrity": "sha512-80cQcgasKjrPPuKcxwuCx7feq+wC6oFl5YaKSee9pV3DNq+6fmCVwEEC3vvkf/E2aI76rIJSOYHsWSEIxK74oQ==",
+      "requires": {
+        "@vue/compiler-dom": "3.2.40",
+        "@vue/shared": "3.2.40"
+      }
+    },
+    "@vue/reactivity": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.2.40.tgz",
+      "integrity": "sha512-N9qgGLlZmtUBMHF9xDT4EkD9RdXde1Xbveb+niWMXuHVWQP5BzgRmE3SFyUBBcyayG4y1lhoz+lphGRRxxK4RA==",
+      "requires": {
+        "@vue/shared": "3.2.40"
+      }
+    },
+    "@vue/reactivity-transform": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.2.40.tgz",
+      "integrity": "sha512-HQUCVwEaacq6fGEsg2NUuGKIhUveMCjOk8jGHqLXPI2w6zFoPrlQhwWEaINTv5kkZDXKEnCijAp+4gNEHG03yw==",
+      "requires": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.40",
+        "@vue/shared": "3.2.40",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7"
+      }
+    },
+    "@vue/runtime-core": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.2.40.tgz",
+      "integrity": "sha512-U1+rWf0H8xK8aBUZhnrN97yoZfHbjgw/bGUzfgKPJl69/mXDuSg8CbdBYBn6VVQdR947vWneQBFzdhasyzMUKg==",
+      "requires": {
+        "@vue/reactivity": "3.2.40",
+        "@vue/shared": "3.2.40"
+      }
+    },
+    "@vue/runtime-dom": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.2.40.tgz",
+      "integrity": "sha512-AO2HMQ+0s2+MCec8hXAhxMgWhFhOPJ/CyRXnmTJ6XIOnJFLrH5Iq3TNwvVcODGR295jy77I6dWPj+wvFoSYaww==",
+      "requires": {
+        "@vue/runtime-core": "3.2.40",
+        "@vue/shared": "3.2.40",
+        "csstype": "^2.6.8"
+      }
+    },
+    "@vue/server-renderer": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.2.40.tgz",
+      "integrity": "sha512-gtUcpRwrXOJPJ4qyBpU3EyxQa4EkV8I4f8VrDePcGCPe4O/hd0BPS7v9OgjIQob6Ap8VDz9G+mGTKazE45/95w==",
+      "requires": {
+        "@vue/compiler-ssr": "3.2.40",
+        "@vue/shared": "3.2.40"
+      }
+    },
+    "@vue/shared": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.2.40.tgz",
+      "integrity": "sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ=="
+    },
+    "c-scrollbar": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmmirror.com/c-scrollbar/-/c-scrollbar-0.1.7.tgz",
+      "integrity": "sha512-xD0VLJOmZ9MFhO6dGEF+Vr7zEk5QpH/oPmdONuWvWV14XbHiFd4A8A67rg+Nxcs5qjPIHunflbe+ZUHTvUdN8g==",
+      "requires": {
+        "vue": "^3.0.0"
+      }
+    },
+    "core-js": {
+      "version": "3.25.5",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.25.5.tgz",
+      "integrity": "sha512-nbm6eZSjm+ZuBQxCUPQKQCoUEfFOXjUZ8dTTyikyKaWrTYmAVbykQfwsKE5dBK88u3QCkCrzsx/PPlKfhsvgpw=="
+    },
+    "csstype": {
+      "version": "2.6.21",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-2.6.21.tgz",
+      "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
+    },
+    "estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+    },
+    "lodash.clamp": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/lodash.clamp/-/lodash.clamp-4.0.3.tgz",
+      "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg=="
+    },
+    "lodash.debounce": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+      "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
+    },
+    "magic-string": {
+      "version": "0.25.9",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.25.9.tgz",
+      "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+      "requires": {
+        "sourcemap-codec": "^1.4.8"
+      }
+    },
+    "nanoid": {
+      "version": "3.3.4",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz",
+      "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
+    },
+    "picocolors": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz",
+      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+    },
+    "postcss": {
+      "version": "8.4.17",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.17.tgz",
+      "integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==",
+      "requires": {
+        "nanoid": "^3.3.4",
+        "picocolors": "^1.0.0",
+        "source-map-js": "^1.0.2"
+      }
+    },
+    "smooth-scrollbar": {
+      "version": "8.8.1",
+      "resolved": "https://registry.npmmirror.com/smooth-scrollbar/-/smooth-scrollbar-8.8.1.tgz",
+      "integrity": "sha512-FQDRtdLTRye8LdRHohNAxh0hyo7gl8+APfA+8Qu5S38MBqR8/WqOoRVzjizH6FTdbU1qGsB7gs8LhMeFJ4Jr8g==",
+      "requires": {
+        "core-js": "^3.6.4",
+        "lodash.clamp": "^4.0.3",
+        "lodash.debounce": "^4.0.8",
+        "tslib": "^1.10.0"
+      }
+    },
+    "source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+    },
+    "source-map-js": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz",
+      "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
+    },
+    "sourcemap-codec": {
+      "version": "1.4.8",
+      "resolved": "https://registry.npmmirror.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
+    },
+    "tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+    },
+    "vue": {
+      "version": "3.2.40",
+      "resolved": "https://registry.npmmirror.com/vue/-/vue-3.2.40.tgz",
+      "integrity": "sha512-1mGHulzUbl2Nk3pfvI5aXYYyJUs1nm4kyvuz38u4xlQkLUn1i2R7nDbI4TufECmY8v1qNBHYy62bCaM+3cHP2A==",
+      "requires": {
+        "@vue/compiler-dom": "3.2.40",
+        "@vue/compiler-sfc": "3.2.40",
+        "@vue/runtime-dom": "3.2.40",
+        "@vue/server-renderer": "3.2.40",
+        "@vue/shared": "3.2.40"
+      }
+    },
+    "vue-cropper": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/vue-cropper/-/vue-cropper-1.0.5.tgz",
+      "integrity": "sha512-D4XXdqWmMWRLOIV9LIh7/mkH6OBOMQDFbRjwntkxmAtxOtwpC9U5ZZ6lSXw5F5cbd4g8znDjk6MuCwIL+fZSrA=="
+    },
+    "vue3-smooth-scrollbar": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/vue3-smooth-scrollbar/-/vue3-smooth-scrollbar-1.0.2.tgz",
+      "integrity": "sha512-TfO0WGbA5KzAosHMmIa4UfB5LcSR3YefNgRbTBZrKw4A5F58vIMALqwXeSCoLtsywdLInN+7u8Fq9cRDiT9EXg==",
+      "requires": {
+        "core-js": "^3.8.3",
+        "lodash": "^4.17.21",
+        "smooth-scrollbar": "^8.6.2",
+        "vue": "^3.0.4"
+      }
+    }
+  }
+}

+ 17 - 0
packages/qjkankan-components/package.json

@@ -0,0 +1,17 @@
+{
+  "name": "@qjkankan/components",
+  "version": "1.2.0",
+  "private": true,
+  "main": "dist/components.common.js",
+  "module": "src/index.js",
+  "scripts": {
+    "start": "vue-cli-service serve",
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build --target lib --name components src/index.js"
+  },
+  "dependencies": {
+    "c-scrollbar": "^0.1.6",
+    "vue-cropper": "^1.0.2",
+    "vue3-smooth-scrollbar": "^1.0.2"
+  }
+}

BIN
packages/qjkankan-components/public/favicon.ico


+ 24 - 0
packages/qjkankan-components/public/index.html

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="">
+
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <!-- <meta name="viewport" content="width=device-width,initial-scale=1.0"> -->
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+  <link rel="stylesheet" href="<%= VUE_APP_STATIC_DIR %>/lib/quill/quill.snow.css" />
+  <title><%= htmlWebpackPlugin.options.title %></title>
+</head>
+
+<body>
+  <noscript>
+    <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
+      Please enable it to continue.</strong>
+  </noscript>
+  <div id="app"></div>
+  <script src="<%= VUE_APP_STATIC_DIR %>/lib/quill/quill.min.js"></script>
+  <!-- built files will be auto injected -->
+</body>
+
+</html>

Разница между файлами не показана из-за своего большого размера
+ 8793 - 0
packages/qjkankan-components/public/static/lib/quill/quill.min.js


+ 950 - 0
packages/qjkankan-components/public/static/lib/quill/quill.snow.css

@@ -0,0 +1,950 @@
+/*!
+ * Quill Editor v1.3.6
+ * https://quilljs.com/
+ * Copyright (c) 2014, Jason Chen
+ * Copyright (c) 2013, salesforce.com
+ */
+ .ql-container {
+    box-sizing: border-box;
+    font-family: Helvetica, Arial, sans-serif;
+    font-size: 13px;
+    height: 100%;
+    margin: 0px;
+    position: relative;
+  }
+  .ql-container.ql-disabled .ql-tooltip {
+    visibility: hidden;
+  }
+  .ql-container.ql-disabled .ql-editor ul[data-checked] > li::before {
+    pointer-events: none;
+  }
+  .ql-clipboard {
+    left: -100000px;
+    height: 1px;
+    overflow-y: hidden;
+    position: absolute;
+    top: 50%;
+  }
+  .ql-clipboard p {
+    margin: 0;
+    padding: 0;
+  }
+  .ql-editor {
+    box-sizing: border-box;
+    line-height: 1.42;
+    height: 100%;
+    outline: none;
+    overflow-y: auto;
+    padding: 12px 15px;
+    tab-size: 4;
+    -moz-tab-size: 4;
+    text-align: left;
+    white-space: pre-wrap;
+    word-wrap: break-word;
+  }
+  .ql-editor > * {
+    cursor: text;
+  }
+  .ql-editor p,
+  .ql-editor ol,
+  .ql-editor ul,
+  .ql-editor pre,
+  .ql-editor blockquote,
+  .ql-editor h1,
+  .ql-editor h2,
+  .ql-editor h3,
+  .ql-editor h4,
+  .ql-editor h5,
+  .ql-editor h6 {
+    margin: 0;
+    padding: 0;
+    counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
+  }
+  .ql-editor ol,
+  .ql-editor ul {
+    padding-left: 1.5em;
+  }
+  .ql-editor ol > li,
+  .ql-editor ul > li {
+    list-style-type: none;
+  }
+  .ql-editor ul > li::before {
+    content: '\2022';
+  }
+  .ql-editor ul[data-checked=true],
+  .ql-editor ul[data-checked=false] {
+    pointer-events: none;
+  }
+  .ql-editor ul[data-checked=true] > li *,
+  .ql-editor ul[data-checked=false] > li * {
+    pointer-events: all;
+  }
+  .ql-editor ul[data-checked=true] > li::before,
+  .ql-editor ul[data-checked=false] > li::before {
+    color: #777;
+    cursor: pointer;
+    pointer-events: all;
+  }
+  .ql-editor ul[data-checked=true] > li::before {
+    content: '\2611';
+  }
+  .ql-editor ul[data-checked=false] > li::before {
+    content: '\2610';
+  }
+  .ql-editor li::before {
+    display: inline-block;
+    white-space: nowrap;
+    width: 1.2em;
+  }
+  .ql-editor li:not(.ql-direction-rtl)::before {
+    margin-left: -1.5em;
+    margin-right: 0.3em;
+    text-align: right;
+  }
+  .ql-editor li.ql-direction-rtl::before {
+    margin-left: 0.3em;
+    margin-right: -1.5em;
+  }
+  .ql-editor ol li:not(.ql-direction-rtl),
+  .ql-editor ul li:not(.ql-direction-rtl) {
+    padding-left: 1.5em;
+  }
+  .ql-editor ol li.ql-direction-rtl,
+  .ql-editor ul li.ql-direction-rtl {
+    padding-right: 1.5em;
+  }
+  .ql-editor ol li {
+    counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
+    counter-increment: list-0;
+  }
+  .ql-editor ol li:before {
+    content: counter(list-0, decimal) '. ';
+  }
+  .ql-editor ol li.ql-indent-1 {
+    counter-increment: list-1;
+  }
+  .ql-editor ol li.ql-indent-1:before {
+    content: counter(list-1, lower-alpha) '. ';
+  }
+  .ql-editor ol li.ql-indent-1 {
+    counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
+  }
+  .ql-editor ol li.ql-indent-2 {
+    counter-increment: list-2;
+  }
+  .ql-editor ol li.ql-indent-2:before {
+    content: counter(list-2, lower-roman) '. ';
+  }
+  .ql-editor ol li.ql-indent-2 {
+    counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9;
+  }
+  .ql-editor ol li.ql-indent-3 {
+    counter-increment: list-3;
+  }
+  .ql-editor ol li.ql-indent-3:before {
+    content: counter(list-3, decimal) '. ';
+  }
+  .ql-editor ol li.ql-indent-3 {
+    counter-reset: list-4 list-5 list-6 list-7 list-8 list-9;
+  }
+  .ql-editor ol li.ql-indent-4 {
+    counter-increment: list-4;
+  }
+  .ql-editor ol li.ql-indent-4:before {
+    content: counter(list-4, lower-alpha) '. ';
+  }
+  .ql-editor ol li.ql-indent-4 {
+    counter-reset: list-5 list-6 list-7 list-8 list-9;
+  }
+  .ql-editor ol li.ql-indent-5 {
+    counter-increment: list-5;
+  }
+  .ql-editor ol li.ql-indent-5:before {
+    content: counter(list-5, lower-roman) '. ';
+  }
+  .ql-editor ol li.ql-indent-5 {
+    counter-reset: list-6 list-7 list-8 list-9;
+  }
+  .ql-editor ol li.ql-indent-6 {
+    counter-increment: list-6;
+  }
+  .ql-editor ol li.ql-indent-6:before {
+    content: counter(list-6, decimal) '. ';
+  }
+  .ql-editor ol li.ql-indent-6 {
+    counter-reset: list-7 list-8 list-9;
+  }
+  .ql-editor ol li.ql-indent-7 {
+    counter-increment: list-7;
+  }
+  .ql-editor ol li.ql-indent-7:before {
+    content: counter(list-7, lower-alpha) '. ';
+  }
+  .ql-editor ol li.ql-indent-7 {
+    counter-reset: list-8 list-9;
+  }
+  .ql-editor ol li.ql-indent-8 {
+    counter-increment: list-8;
+  }
+  .ql-editor ol li.ql-indent-8:before {
+    content: counter(list-8, lower-roman) '. ';
+  }
+  .ql-editor ol li.ql-indent-8 {
+    counter-reset: list-9;
+  }
+  .ql-editor ol li.ql-indent-9 {
+    counter-increment: list-9;
+  }
+  .ql-editor ol li.ql-indent-9:before {
+    content: counter(list-9, decimal) '. ';
+  }
+  .ql-editor .ql-indent-1:not(.ql-direction-rtl) {
+    padding-left: 3em;
+  }
+  .ql-editor li.ql-indent-1:not(.ql-direction-rtl) {
+    padding-left: 4.5em;
+  }
+  .ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right {
+    padding-right: 3em;
+  }
+  .ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right {
+    padding-right: 4.5em;
+  }
+  .ql-editor .ql-indent-2:not(.ql-direction-rtl) {
+    padding-left: 6em;
+  }
+  .ql-editor li.ql-indent-2:not(.ql-direction-rtl) {
+    padding-left: 7.5em;
+  }
+  .ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right {
+    padding-right: 6em;
+  }
+  .ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right {
+    padding-right: 7.5em;
+  }
+  .ql-editor .ql-indent-3:not(.ql-direction-rtl) {
+    padding-left: 9em;
+  }
+  .ql-editor li.ql-indent-3:not(.ql-direction-rtl) {
+    padding-left: 10.5em;
+  }
+  .ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right {
+    padding-right: 9em;
+  }
+  .ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right {
+    padding-right: 10.5em;
+  }
+  .ql-editor .ql-indent-4:not(.ql-direction-rtl) {
+    padding-left: 12em;
+  }
+  .ql-editor li.ql-indent-4:not(.ql-direction-rtl) {
+    padding-left: 13.5em;
+  }
+  .ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right {
+    padding-right: 12em;
+  }
+  .ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right {
+    padding-right: 13.5em;
+  }
+  .ql-editor .ql-indent-5:not(.ql-direction-rtl) {
+    padding-left: 15em;
+  }
+  .ql-editor li.ql-indent-5:not(.ql-direction-rtl) {
+    padding-left: 16.5em;
+  }
+  .ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right {
+    padding-right: 15em;
+  }
+  .ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right {
+    padding-right: 16.5em;
+  }
+  .ql-editor .ql-indent-6:not(.ql-direction-rtl) {
+    padding-left: 18em;
+  }
+  .ql-editor li.ql-indent-6:not(.ql-direction-rtl) {
+    padding-left: 19.5em;
+  }
+  .ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right {
+    padding-right: 18em;
+  }
+  .ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right {
+    padding-right: 19.5em;
+  }
+  .ql-editor .ql-indent-7:not(.ql-direction-rtl) {
+    padding-left: 21em;
+  }
+  .ql-editor li.ql-indent-7:not(.ql-direction-rtl) {
+    padding-left: 22.5em;
+  }
+  .ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right {
+    padding-right: 21em;
+  }
+  .ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right {
+    padding-right: 22.5em;
+  }
+  .ql-editor .ql-indent-8:not(.ql-direction-rtl) {
+    padding-left: 24em;
+  }
+  .ql-editor li.ql-indent-8:not(.ql-direction-rtl) {
+    padding-left: 25.5em;
+  }
+  .ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right {
+    padding-right: 24em;
+  }
+  .ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right {
+    padding-right: 25.5em;
+  }
+  .ql-editor .ql-indent-9:not(.ql-direction-rtl) {
+    padding-left: 27em;
+  }
+  .ql-editor li.ql-indent-9:not(.ql-direction-rtl) {
+    padding-left: 28.5em;
+  }
+  .ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right {
+    padding-right: 27em;
+  }
+  .ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right {
+    padding-right: 28.5em;
+  }
+  .ql-editor .ql-video {
+    display: block;
+    max-width: 100%;
+  }
+  .ql-editor .ql-video.ql-align-center {
+    margin: 0 auto;
+  }
+  .ql-editor .ql-video.ql-align-right {
+    margin: 0 0 0 auto;
+  }
+  .ql-editor .ql-bg-black {
+    background-color: #000;
+  }
+  .ql-editor .ql-bg-red {
+    background-color: #e60000;
+  }
+  .ql-editor .ql-bg-orange {
+    background-color: #f90;
+  }
+  .ql-editor .ql-bg-yellow {
+    background-color: #ff0;
+  }
+  .ql-editor .ql-bg-green {
+    background-color: #008a00;
+  }
+  .ql-editor .ql-bg-blue {
+    background-color: #06c;
+  }
+  .ql-editor .ql-bg-purple {
+    background-color: #93f;
+  }
+  .ql-editor .ql-color-white {
+    color: #fff;
+  }
+  .ql-editor .ql-color-red {
+    color: #e60000;
+  }
+  .ql-editor .ql-color-orange {
+    color: #f90;
+  }
+  .ql-editor .ql-color-yellow {
+    color: #ff0;
+  }
+  .ql-editor .ql-color-green {
+    color: #008a00;
+  }
+  .ql-editor .ql-color-blue {
+    color: #06c;
+  }
+  .ql-editor .ql-color-purple {
+    color: #93f;
+  }
+  .ql-editor .ql-font-serif {
+    font-family: Georgia, Times New Roman, serif;
+  }
+  .ql-editor .ql-font-monospace {
+    font-family: Monaco, Courier New, monospace;
+  }
+  .ql-editor .ql-size-small {
+    font-size: 0.75em;
+  }
+  .ql-editor .ql-size-large {
+    font-size: 1.5em;
+  }
+  .ql-editor .ql-size-huge {
+    font-size: 2.5em;
+  }
+  .ql-editor .ql-direction-rtl {
+    direction: rtl;
+    text-align: inherit;
+  }
+  .ql-editor .ql-align-center {
+    text-align: center;
+  }
+  .ql-editor .ql-align-justify {
+    text-align: justify;
+  }
+  .ql-editor .ql-align-right {
+    text-align: right;
+  }
+  .ql-editor.ql-blank::before {
+    color: rgba(0,0,0,0.6);
+    content: attr(data-placeholder);
+    font-style: normal;
+    left: 15px;
+    pointer-events: none;
+    position: absolute;
+    right: 15px;
+    color: #c7c7c7;
+  }
+  .ql-snow.ql-toolbar:after,
+  .ql-snow .ql-toolbar:after {
+    clear: both;
+    content: '';
+    display: table;
+  }
+  .ql-snow.ql-toolbar button,
+  .ql-snow .ql-toolbar button {
+    background: none;
+    border: none;
+    cursor: pointer;
+    display: inline-block;
+    float: left;
+    height: 24px;
+    padding: 3px 5px;
+    width: 28px;
+  }
+  .ql-snow.ql-toolbar button svg,
+  .ql-snow .ql-toolbar button svg {
+    float: left;
+    height: 100%;
+  }
+  .ql-snow.ql-toolbar button:active:hover,
+  .ql-snow .ql-toolbar button:active:hover {
+    outline: none;
+  }
+  .ql-snow.ql-toolbar input.ql-image[type=file],
+  .ql-snow .ql-toolbar input.ql-image[type=file] {
+    display: none;
+  }
+  .ql-snow.ql-toolbar button:hover,
+  .ql-snow .ql-toolbar button:hover,
+  .ql-snow.ql-toolbar button:focus,
+  .ql-snow .ql-toolbar button:focus,
+  .ql-snow.ql-toolbar button.ql-active,
+  .ql-snow .ql-toolbar button.ql-active,
+  .ql-snow.ql-toolbar .ql-picker-label:hover,
+  .ql-snow .ql-toolbar .ql-picker-label:hover,
+  .ql-snow.ql-toolbar .ql-picker-label.ql-active,
+  .ql-snow .ql-toolbar .ql-picker-label.ql-active,
+  .ql-snow.ql-toolbar .ql-picker-item:hover,
+  .ql-snow .ql-toolbar .ql-picker-item:hover,
+  .ql-snow.ql-toolbar .ql-picker-item.ql-selected,
+  .ql-snow .ql-toolbar .ql-picker-item.ql-selected {
+    color: #06c;
+  }
+  .ql-snow.ql-toolbar button:hover .ql-fill,
+  .ql-snow .ql-toolbar button:hover .ql-fill,
+  .ql-snow.ql-toolbar button:focus .ql-fill,
+  .ql-snow .ql-toolbar button:focus .ql-fill,
+  .ql-snow.ql-toolbar button.ql-active .ql-fill,
+  .ql-snow .ql-toolbar button.ql-active .ql-fill,
+  .ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill,
+  .ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill,
+  .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill,
+  .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill,
+  .ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill,
+  .ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill,
+  .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill,
+  .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill,
+  .ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill,
+  .ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill,
+  .ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill,
+  .ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill,
+  .ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill,
+  .ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill,
+  .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
+  .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
+  .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
+  .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
+  .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
+  .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
+  .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,
+  .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill {
+    fill: #06c;
+  }
+  .ql-snow.ql-toolbar button:hover .ql-stroke,
+  .ql-snow .ql-toolbar button:hover .ql-stroke,
+  .ql-snow.ql-toolbar button:focus .ql-stroke,
+  .ql-snow .ql-toolbar button:focus .ql-stroke,
+  .ql-snow.ql-toolbar button.ql-active .ql-stroke,
+  .ql-snow .ql-toolbar button.ql-active .ql-stroke,
+  .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,
+  .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke,
+  .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke,
+  .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke,
+  .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke,
+  .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke,
+  .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
+  .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
+  .ql-snow.ql-toolbar button:hover .ql-stroke-miter,
+  .ql-snow .ql-toolbar button:hover .ql-stroke-miter,
+  .ql-snow.ql-toolbar button:focus .ql-stroke-miter,
+  .ql-snow .ql-toolbar button:focus .ql-stroke-miter,
+  .ql-snow.ql-toolbar button.ql-active .ql-stroke-miter,
+  .ql-snow .ql-toolbar button.ql-active .ql-stroke-miter,
+  .ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
+  .ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
+  .ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
+  .ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
+  .ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
+  .ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
+  .ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,
+  .ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter {
+    stroke: #06c;
+  }
+  @media (pointer: coarse) {
+    .ql-snow.ql-toolbar button:hover:not(.ql-active),
+    .ql-snow .ql-toolbar button:hover:not(.ql-active) {
+      color: #444;
+    }
+    .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,
+    .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,
+    .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,
+    .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill {
+      fill: #444;
+    }
+    .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,
+    .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,
+    .ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,
+    .ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter {
+      stroke: #444;
+    }
+  }
+  .ql-snow {
+    box-sizing: border-box;
+  }
+  .ql-snow * {
+    box-sizing: border-box;
+  }
+  .ql-snow .ql-hidden {
+    display: none;
+  }
+  .ql-snow .ql-out-bottom,
+  .ql-snow .ql-out-top {
+    visibility: hidden;
+  }
+  .ql-snow .ql-tooltip {
+    position: absolute;
+    transform: translateY(10px);
+  }
+  .ql-snow .ql-tooltip a {
+    cursor: pointer;
+    text-decoration: none;
+  }
+  .ql-snow .ql-tooltip.ql-flip {
+    transform: translateY(-10px);
+  }
+  .ql-snow .ql-formats {
+    display: inline-block;
+    vertical-align: middle;
+  }
+  .ql-snow .ql-formats:after {
+    clear: both;
+    content: '';
+    display: table;
+  }
+  .ql-snow .ql-stroke {
+    fill: none;
+    stroke: #444;
+    stroke-linecap: round;
+    stroke-linejoin: round;
+    stroke-width: 2;
+  }
+  .ql-snow .ql-stroke-miter {
+    fill: none;
+    stroke: #444;
+    stroke-miterlimit: 10;
+    stroke-width: 2;
+  }
+  .ql-snow .ql-fill,
+  .ql-snow .ql-stroke.ql-fill {
+    fill: #444;
+  }
+  .ql-snow .ql-empty {
+    fill: none;
+  }
+  .ql-snow .ql-even {
+    fill-rule: evenodd;
+  }
+  .ql-snow .ql-thin,
+  .ql-snow .ql-stroke.ql-thin {
+    stroke-width: 1;
+  }
+  .ql-snow .ql-transparent {
+    opacity: 0.4;
+  }
+  .ql-snow .ql-direction svg:last-child {
+    display: none;
+  }
+  .ql-snow .ql-direction.ql-active svg:last-child {
+    display: inline;
+  }
+  .ql-snow .ql-direction.ql-active svg:first-child {
+    display: none;
+  }
+  .ql-snow .ql-editor h1 {
+    font-size: 2em;
+  }
+  .ql-snow .ql-editor h2 {
+    font-size: 1.5em;
+  }
+  .ql-snow .ql-editor h3 {
+    font-size: 1.17em;
+  }
+  .ql-snow .ql-editor h4 {
+    font-size: 1em;
+  }
+  .ql-snow .ql-editor h5 {
+    font-size: 0.83em;
+  }
+  .ql-snow .ql-editor h6 {
+    font-size: 0.67em;
+  }
+  .ql-snow .ql-editor a {
+    text-decoration: underline;
+  }
+  .ql-snow .ql-editor blockquote {
+    border-left: 4px solid #ccc;
+    margin-bottom: 5px;
+    margin-top: 5px;
+    padding-left: 16px;
+  }
+  .ql-snow .ql-editor code,
+  .ql-snow .ql-editor pre {
+    background-color: #f0f0f0;
+    border-radius: 3px;
+  }
+  .ql-snow .ql-editor pre {
+    white-space: pre-wrap;
+    margin-bottom: 5px;
+    margin-top: 5px;
+    padding: 5px 10px;
+  }
+  .ql-snow .ql-editor code {
+    font-size: 85%;
+    padding: 2px 4px;
+  }
+  .ql-snow .ql-editor pre.ql-syntax {
+    background-color: #23241f;
+    color: #f8f8f2;
+    overflow: visible;
+  }
+  .ql-snow .ql-editor img {
+    max-width: 100%;
+  }
+  .ql-snow .ql-picker {
+    color: #444;
+    display: inline-block;
+    float: left;
+    font-size: 14px;
+    font-weight: 500;
+    height: 24px;
+    position: relative;
+    vertical-align: middle;
+  }
+  .ql-snow .ql-picker-label {
+    cursor: pointer;
+    display: inline-block;
+    height: 100%;
+    padding-left: 8px;
+    padding-right: 2px;
+    position: relative;
+    width: 100%;
+  }
+  .ql-snow .ql-picker-label::before {
+    display: inline-block;
+    line-height: 22px;
+  }
+  .ql-snow .ql-picker-options {
+    background-color: #fff;
+    display: none;
+    min-width: 100%;
+    padding: 4px 8px;
+    position: absolute;
+    white-space: nowrap;
+  }
+  .ql-snow .ql-picker-options .ql-picker-item {
+    cursor: pointer;
+    display: block;
+    padding-bottom: 5px;
+    padding-top: 5px;
+  }
+  .ql-snow .ql-picker.ql-expanded .ql-picker-label {
+    color: #ccc;
+    z-index: 2;
+  }
+  .ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill {
+    fill: #ccc;
+  }
+  .ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke {
+    stroke: #ccc;
+  }
+  .ql-snow .ql-picker.ql-expanded .ql-picker-options {
+    display: block;
+    margin-top: -1px;
+    top: 100%;
+    z-index: 1;
+  }
+  .ql-snow .ql-color-picker,
+  .ql-snow .ql-icon-picker {
+    width: 28px;
+  }
+  .ql-snow .ql-color-picker .ql-picker-label,
+  .ql-snow .ql-icon-picker .ql-picker-label {
+    padding: 2px 4px;
+  }
+  .ql-snow .ql-color-picker .ql-picker-label svg,
+  .ql-snow .ql-icon-picker .ql-picker-label svg {
+    right: 4px;
+  }
+  .ql-snow .ql-icon-picker .ql-picker-options {
+    padding: 4px 0px;
+  }
+  .ql-snow .ql-icon-picker .ql-picker-item {
+    height: 24px;
+    width: 24px;
+    padding: 2px 4px;
+  }
+  .ql-snow .ql-color-picker .ql-picker-options {
+    padding: 3px 5px;
+    width: 152px;
+  }
+  .ql-snow .ql-color-picker .ql-picker-item {
+    border: 1px solid transparent;
+    float: left;
+    height: 16px;
+    margin: 2px;
+    padding: 0px;
+    width: 16px;
+  }
+  .ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg {
+    position: absolute;
+    margin-top: -9px;
+    right: 0;
+    top: 50%;
+    width: 18px;
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before,
+  .ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before,
+  .ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before,
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before,
+  .ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before,
+  .ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before {
+    content: attr(data-label);
+  }
+  .ql-snow .ql-picker.ql-header {
+    width: 98px;
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-label::before,
+  .ql-snow .ql-picker.ql-header .ql-picker-item::before {
+    content: 'Normal';
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
+    content: 'Heading 1';
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
+    content: 'Heading 2';
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
+    content: 'Heading 3';
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
+    content: 'Heading 4';
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
+    content: 'Heading 5';
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
+    content: 'Heading 6';
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
+    font-size: 2em;
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
+    font-size: 1.5em;
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
+    font-size: 1.17em;
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
+    font-size: 1em;
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
+    font-size: 0.83em;
+  }
+  .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
+    font-size: 0.67em;
+  }
+  .ql-snow .ql-picker.ql-font {
+    width: 108px;
+  }
+  .ql-snow .ql-picker.ql-font .ql-picker-label::before,
+  .ql-snow .ql-picker.ql-font .ql-picker-item::before {
+    content: 'Sans Serif';
+  }
+  .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
+  .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
+    content: 'Serif';
+  }
+  .ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
+  .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
+    content: 'Monospace';
+  }
+  .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
+    font-family: Georgia, Times New Roman, serif;
+  }
+  .ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
+    font-family: Monaco, Courier New, monospace;
+  }
+  .ql-snow .ql-picker.ql-size {
+    width: 98px;
+  }
+  .ql-snow .ql-picker.ql-size .ql-picker-label::before,
+  .ql-snow .ql-picker.ql-size .ql-picker-item::before {
+    content: 'Normal';
+  }
+  .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,
+  .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
+    content: 'Small';
+  }
+  .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,
+  .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
+    content: 'Large';
+  }
+  .ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,
+  .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
+    content: 'Huge';
+  }
+  .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
+    font-size: 10px;
+  }
+  .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
+    font-size: 18px;
+  }
+  .ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
+    font-size: 32px;
+  }
+  .ql-snow .ql-color-picker.ql-background .ql-picker-item {
+    background-color: #fff;
+  }
+  .ql-snow .ql-color-picker.ql-color .ql-picker-item {
+    background-color: #000;
+  }
+  .ql-toolbar.ql-snow {
+    border: 1px solid #ccc;
+    box-sizing: border-box;
+    font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
+    padding: 8px;
+  }
+  .ql-toolbar.ql-snow .ql-formats {
+    margin-right: 15px;
+  }
+  .ql-toolbar.ql-snow .ql-picker-label {
+    border: 1px solid transparent;
+  }
+  .ql-toolbar.ql-snow .ql-picker-options {
+    border: 1px solid transparent;
+    box-shadow: rgba(0,0,0,0.2) 0 2px 8px;
+  }
+  .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label {
+    border-color: #ccc;
+  }
+  .ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options {
+    border-color: #ccc;
+  }
+  .ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,
+  .ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover {
+    border-color: #000;
+  }
+  .ql-toolbar.ql-snow + .ql-container.ql-snow {
+    border-top: 0px;
+  }
+  .ql-snow .ql-tooltip {
+    background-color: #fff;
+    border: 1px solid #ccc;
+    box-shadow: 0px 0px 5px #ddd;
+    color: #444;
+    padding: 5px 12px;
+    white-space: nowrap;
+  }
+  .ql-snow .ql-tooltip::before {
+    content: "Visit URL:";
+    line-height: 26px;
+    margin-right: 8px;
+  }
+  .ql-snow .ql-tooltip input[type=text] {
+    display: none;
+    border: 1px solid #ccc;
+    font-size: 13px;
+    height: 26px;
+    margin: 0px;
+    padding: 3px 5px;
+    width: 170px;
+  }
+  .ql-snow .ql-tooltip a.ql-preview {
+    display: inline-block;
+    max-width: 200px;
+    overflow-x: hidden;
+    text-overflow: ellipsis;
+    vertical-align: top;
+  }
+  .ql-snow .ql-tooltip a.ql-action::after {
+    border-right: 1px solid #ccc;
+    content: 'Edit';
+    margin-left: 16px;
+    padding-right: 8px;
+  }
+  .ql-snow .ql-tooltip a.ql-remove::before {
+    content: 'Remove';
+    margin-left: 8px;
+  }
+  .ql-snow .ql-tooltip a {
+    line-height: 26px;
+  }
+  .ql-snow .ql-tooltip.ql-editing a.ql-preview,
+  .ql-snow .ql-tooltip.ql-editing a.ql-remove {
+    display: none;
+  }
+  .ql-snow .ql-tooltip.ql-editing input[type=text] {
+    display: inline-block;
+  }
+  .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
+    border-right: 0px;
+    content: 'Save';
+    padding-right: 0px;
+  }
+  .ql-snow .ql-tooltip[data-mode=link]::before {
+    content: "Enter link:";
+  }
+  .ql-snow .ql-tooltip[data-mode=formula]::before {
+    content: "Enter formula:";
+  }
+  .ql-snow .ql-tooltip[data-mode=video]::before {
+    content: "Enter video:";
+  }
+  .ql-snow a {
+    color: #06c;
+  }
+  .ql-container.ql-snow {
+    border: 1px solid #ccc;
+  }
+
+  .ql-editor a{
+      color: #02c8ae;
+  }

+ 8 - 0
packages/qjkankan-components/src/__test__/editor/App.vue

@@ -0,0 +1,8 @@
+<template>
+    <Layout />
+</template>
+
+<script setup>
+import Layout from '@/components/editor/Layout'
+</script>
+

+ 6 - 0
packages/qjkankan-components/src/__test__/editor/main.js

@@ -0,0 +1,6 @@
+import '@/assets/theme.editor.scss'
+import { createApp } from 'vue'
+
+import App from './App.vue'
+
+createApp(App).mount('#app')

BIN
packages/qjkankan-components/src/assets/img/icons/toast-error.png


BIN
packages/qjkankan-components/src/assets/img/icons/toast-success.png


BIN
packages/qjkankan-components/src/assets/img/icons/toast-warn.png


+ 79 - 0
packages/qjkankan-components/src/assets/scss/_base-vars.scss

@@ -0,0 +1,79 @@
+
+:root {
+  --colors-primary-fill: 255, 255, 255;
+  --colors-primary-base-fill: 0, 118, 246;
+  --colors-primary-base: rgb(var(--colors-primary-base-fill));
+  --colors-primary-hover: #0076f6;
+
+
+  --colors-cancel-base-fill: 49, 49, 49;
+  --colors-cancel-base: rgb(var(--colors-cancel-base-fill));
+  --colors-cancel-hover: rgb(60,60,60);
+
+
+  // --colors-primary-hover: #008B7A;
+  --colors-primary-active: #1682f8;
+  --colors-primary-click: #1682f8;
+  --colors-warn: #1682f8;
+  --colors-color: #999;
+  --colors-border-color: rgba(var(--colors-primary-fill), 0.16);
+  --colors-content-color: rgb(--colors-primary-fill);
+  
+  
+  --colors-normal-back: rgba(var(--colors-primary-fill), 0.1);
+  --colors-normal-base: rgba(var(--colors-primary-fill), 0.4);
+  --colors-normal-hover: rgba(var(--colors-primary-fill), 1);
+  --colors-normal-click: var(--colors-primary-click);
+
+  --colors-normal-fill-back: var(--colors-normal-back);
+  --colors-normal-fill-base: var(--colors-normal-base);
+  --colors-normal-fill-hover: var(--colors-normal-hover);
+  --colors-normal-fill-click: var(--colors-primary-click);
+
+  --colors-error-fill: 250, 63, 72;
+  
+  --small-size: 12px;
+  --medium-size: 14px;
+  --big-size: 16px;
+
+
+  // 正常(取消)
+  --color-cancel-normal: var(--colors-cancel-base);
+  // 悬停
+  --color-cancel-hover: var(--colors-cancel-hover);
+  // 点击
+  --color-cancel-focus: var(--colors-cancel-click);
+
+
+
+  // 正常
+  --color-main-normal: var(--colors-primary-base);
+  // 悬停
+  --color-main-hover: var(--colors-primary-hover);
+  // 点击
+  --color-main-focus: var(--colors-primary-click);
+
+
+  --editor-head-filter: blur(0px);
+  --editor-head-height: 50px;
+  
+  --editor-head-back: rgba(20, 20, 20, 0.86);
+
+  --editor-menu-filter: var(--editor-head-filter);
+  --editor-menu-width: 80px;
+  --editor-menu-left: 0px;
+  --editor-menu-right: 0px;
+  --editer-menu-fill: 27, 27, 28;
+  --editor-menu-back: rgba(var(--editer-menu-fill), 0.8);
+  --editor-menu-active-back: rgba(var(--colors-primary-fill), 0.06);
+  --editor-menu-color: #999;
+  --editor-menu-active: rgba(255,255,255,0.06);;
+
+
+  --editor-toolbox-top: var(--editor-head-height);
+  --editor-toolbox-left: calc(var(--editor-menu-left) + var(--editor-menu-width));
+  --editor-toolbox-width: 340px;
+  --editor-toolbox-back: var(--editor-menu-back);
+  --editor-toolbox-padding: 20px;
+  --editor-toolbar-height: 60px;
+}

+ 293 - 0
packages/qjkankan-components/src/assets/scss/_base.scss

@@ -0,0 +1,293 @@
+/*!
+ * ress.css • v4.0.0
+ * MIT License
+ * github.com/filipelinhares/ress
+ */
+
+/* # =================================================================
+   # Global selectors
+   # ================================================================= */
+
+html {
+    box-sizing: border-box;
+    -webkit-text-size-adjust: 100%; /* Prevent adjustments of font size after orientation changes in iOS */
+    word-break: normal;
+    -moz-tab-size: 4;
+    tab-size: 4;
+}
+
+*,
+::before,
+::after {
+    background-repeat: no-repeat; /* Set `background-repeat: no-repeat` to all elements and pseudo elements */
+    box-sizing: inherit;
+    appearance: none;
+    -webkit-tap-highlight-color: rgba(255,255,255,0);
+    text-rendering: optimizeLegibility!important;
+    -webkit-font-smoothing: antialiased!important;
+}
+
+::before,
+::after {
+    text-decoration: inherit; /* Inherit text-decoration and vertical align to ::before and ::after pseudo elements */
+    vertical-align: inherit;
+}
+
+* {
+    padding: 0; /* Reset `padding` and `margin` of all elements */
+    margin: 0;
+}
+
+/* # =================================================================
+     # General elements
+     # ================================================================= */
+
+hr {
+    overflow: visible; /* Show the overflow in Edge and IE */
+    height: 0; /* Add the correct box sizing in Firefox */
+    color: inherit; /* Correct border color in Firefox. */
+}
+
+details,
+main {
+    display: block; /* Render the `main` element consistently in IE. */
+}
+
+summary {
+    display: list-item; /* Add the correct display in all browsers */
+}
+
+small {
+    font-size: 80%; /* Set font-size to 80% in `small` elements */
+}
+
+[hidden] {
+    display: none; /* Add the correct display in IE */
+}
+
+abbr[title] {
+    border-bottom: none; /* Remove the bottom border in Chrome 57 */
+    /* Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari */
+    text-decoration: underline;
+    text-decoration: underline dotted;
+}
+
+a {
+    background-color: transparent; /* Remove the gray background on active links in IE 10 */
+}
+
+a:active,
+a:hover {
+    outline-width: 0; /* Remove the outline when hovering in all browsers */
+}
+
+code,
+kbd,
+pre,
+samp {
+    font-family: monospace, monospace; /* Specify the font family of code elements */
+}
+
+pre {
+    font-size: 1em; /* Correct the odd `em` font sizing in all browsers */
+}
+
+b,
+strong {
+    font-weight: bolder; /* Add the correct font weight in Chrome, Edge, and Safari */
+}
+
+/* https://gist.github.com/unruthless/413930 */
+sub,
+sup {
+    font-size: 75%;
+    line-height: 0;
+    position: relative;
+    vertical-align: baseline;
+}
+
+sub {
+    bottom: -0.25em;
+}
+
+sup {
+    top: -0.5em;
+}
+
+table {
+    border-color: inherit; /* Correct border color in all Chrome, Edge, and Safari. */
+    text-indent: 0; /* Remove text indentation in Chrome, Edge, and Safari */
+}
+
+/* # =================================================================
+     # Forms
+     # ================================================================= */
+
+input {
+    border-radius: 0;
+}
+
+/* Replace pointer cursor in disabled elements */
+[disabled] {
+    cursor: default;
+    user-select: none;
+}
+
+[type='number']::-webkit-inner-spin-button,
+[type='number']::-webkit-outer-spin-button {
+    height: auto; /* Correct the cursor style of increment and decrement buttons in Chrome */
+}
+
+[type='search'] {
+    -webkit-appearance: textfield; /* Correct the odd appearance in Chrome and Safari */
+    outline-offset: -2px; /* Correct the outline style in Safari */
+}
+
+[type='search']::-webkit-search-decoration {
+    -webkit-appearance: none; /* Remove the inner padding in Chrome and Safari on macOS */
+}
+
+textarea {
+    overflow: auto; /* Internet Explorer 11+ */
+    resize: vertical; /* Specify textarea resizability */
+}
+
+button,
+input,
+optgroup,
+select,
+textarea {
+    font: inherit; /* Specify font inheritance of form elements */
+}
+
+optgroup {
+    font-weight: bold; /* Restore the font weight unset by the previous rule */
+}
+
+button {
+    overflow: visible; /* Address `overflow` set to `hidden` in IE 8/9/10/11 */
+}
+
+button,
+select {
+    text-transform: none; /* Firefox 40+, Internet Explorer 11- */
+}
+
+/* Apply cursor pointer to button elements */
+button,
+[type='button'],
+[type='reset'],
+[type='submit'],
+[role='button'] {
+    cursor: pointer;
+    color: inherit;
+}
+
+/* Remove inner padding and border in Firefox 4+ */
+button::-moz-focus-inner,
+[type='button']::-moz-focus-inner,
+[type='reset']::-moz-focus-inner,
+[type='submit']::-moz-focus-inner {
+    border-style: none;
+    padding: 0;
+}
+
+/* Replace focus style removed in the border reset above */
+button:-moz-focusring,
+[type='button']::-moz-focus-inner,
+[type='reset']::-moz-focus-inner,
+[type='submit']::-moz-focus-inner {
+    outline: 1px dotted #ccc;
+}
+
+button,
+  html [type='button'], /* Prevent a WebKit bug where (2) destroys native `audio` and `video`controls in Android 4 */
+  [type='reset'],
+  [type='submit'] {
+    -webkit-appearance: button; /* Correct the inability to style clickable types in iOS */
+}
+
+/* Remove the default button styling in all browsers */
+button,
+input,
+select,
+textarea {
+    background-color: transparent;
+    border-style: none;
+}
+
+a:focus,
+button:focus,
+input:focus,
+select:focus,
+textarea:focus {
+    outline-width: 0;
+}
+
+/* Style select like a standard input */
+select {
+    -moz-appearance: none; /* Firefox 36+ */
+    -webkit-appearance: none; /* Chrome 41+ */
+}
+
+select::-ms-expand {
+    display: none; /* Internet Explorer 11+ */
+}
+
+select::-ms-value {
+    color: currentColor; /* Internet Explorer 11+ */
+}
+
+legend {
+    border: 0; /* Correct `color` not being inherited in IE 8/9/10/11 */
+    color: inherit; /* Correct the color inheritance from `fieldset` elements in IE */
+    display: table; /* Correct the text wrapping in Edge and IE */
+    max-width: 100%; /* Correct the text wrapping in Edge and IE */
+    white-space: normal; /* Correct the text wrapping in Edge and IE */
+    max-width: 100%; /* Correct the text wrapping in Edge 18- and IE */
+}
+
+::-webkit-file-upload-button {
+    /* Correct the inability to style clickable types in iOS and Safari */
+    -webkit-appearance: button;
+    color: inherit;
+    font: inherit; /* Change font properties to `inherit` in Chrome and Safari */
+}
+
+/* # =================================================================
+     # Specify media element style
+     # ================================================================= */
+
+img {
+    border-style: none; /* Remove border when inside `a` element in IE 8/9/10 */
+}
+
+/* Add the correct vertical alignment in Chrome, Firefox, and Opera */
+progress {
+    vertical-align: baseline;
+}
+
+/* # =================================================================
+     # Accessibility
+     # ================================================================= */
+
+/* Specify the progress cursor of updating elements */
+[aria-busy='true'] {
+    cursor: progress;
+}
+
+/* Specify the pointer cursor of trigger elements */
+[aria-controls] {
+    cursor: pointer;
+}
+
+/* Specify the unstyled cursor of disabled, not-editable, or otherwise inoperable elements */
+[aria-disabled='true'] {
+    cursor: default;
+}
+
+.disabled,
+:disabled{
+    opacity: 0.3 !important;
+    pointer-events: none !important;
+}

+ 16 - 0
packages/qjkankan-components/src/assets/scss/_components.scss

@@ -0,0 +1,16 @@
+@import "components/loading";
+@import "components/dialog";
+@import "components/toast";
+@import "components/tree";
+@import "components/input";
+@import "components/button";
+@import "components/group";
+@import "components/floating";
+// @import "components/icon";
+@import "components/size-animation";
+@import "components/men-item";
+@import "components/gate";
+@import "components/slide";
+@import "components/audio";
+@import "components/message";
+@import "components/cropper";

+ 22 - 0
packages/qjkankan-components/src/assets/scss/components/_audio.scss

@@ -0,0 +1,22 @@
+.audio {
+  display: inline-block;
+  cursor: pointer;
+  
+  > span {
+    --height: 18px;
+    width: 3px;
+    height: calc(var(--height) * var(--percent));
+    background: var(--colors-primary-base);
+    display: inline-block;
+    transition: height .2s linear;
+
+
+    &:not(:last-child) {
+      margin-right: 2px;
+    }
+  }
+
+  audio {
+    display: none;
+  }
+}

+ 82 - 0
packages/qjkankan-components/src/assets/scss/components/_button.scss

@@ -0,0 +1,82 @@
+@use "sass:map";
+
+.ui-button {
+  width: 100%;
+  height: 36px;
+  border: none;
+  outline: none;
+  border-radius: 4px;
+  font-size: 14px;
+  background: none !important;
+
+  transition: all 0.3s ease;
+
+  .ui-button-icon {
+    margin-right: 0.6em;
+  }
+}
+
+.ui-button.customize {
+  background: none;
+  color: rgba(var(--color), 0.8);
+  border: 1px solid rgba(var(--color), 0.6);
+}
+
+.ui-button.normal {
+  color: var(--colors-color);
+  border: 1px solid var(--colors-normal-base);
+  &:hover {
+    color: var(--colors-normal-hover);
+    border: 1px solid var(--colors-normal-hover);
+  }
+  &:active {
+    color: var(--colors-normal-click);
+    border: 1px solid var(--colors-normal-click);
+  }
+}
+
+.ui-button.submit {
+  color: var(--color-main-hover);
+  border: 1px solid var(--color-main-normal);
+  background-color: var(--color-main-normal);
+  &:hover {
+    border-color: var(--color-main-hover);
+    background-color: var(--color-main-hover);
+  }
+  &:active {
+    border-color: var(--color-main-focus);
+    background-color: var(--color-main-focus);
+  }
+}
+
+.ui-button.cancel {
+  color: var(--colors-content-color);
+  border: none;
+  background-color: var(--colors-cancel-base) !important;
+  &:hover {
+    border: none;
+  }
+  &:active {
+    border: none;
+  }
+}
+
+.ui-button.primary {
+  background-color: var(--colors-primary-base) !important;
+  color: var(--colors-normal-fill-hover);
+  border: none;
+  opacity: 1;
+
+  // &:active,
+  &:hover {
+    // opacity: 0.8;
+    background: var(--colors-primary-hover) !important;
+    border: none;
+    // background: #4DD8C7 !important;
+  }
+  &:active {
+    background-color: var(--colors-primary-active) !important;
+    border: none;
+    color: rgba(255, 255, 255, 0.6);
+  }
+}

+ 4 - 0
packages/qjkankan-components/src/assets/scss/components/_cropper.scss

@@ -0,0 +1,4 @@
+.cropper-layer {
+  // height: 500px;
+  // width: 500px;
+}

+ 88 - 0
packages/qjkankan-components/src/assets/scss/components/_dialog.scss

@@ -0,0 +1,88 @@
+.ui-dialog {
+    position: fixed;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+    color: #fff;
+    backdrop-filter: blur(1px);
+}
+.ui-dialog__box {
+    position: relative;
+    display: inline-block;
+    min-width: 400px;
+    min-height: 100px;
+    background-color: rgba($color: #1a1a1a, $alpha: 0.8);
+    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.7);
+    border-radius: 4px;
+    border: 1px solid #000000;
+    // backdrop-filter: blur(400px);
+    backdrop-filter: blur(4px);
+    &::after {
+        content: '';
+        position: absolute;
+        left: 1px;
+        right: 1px;
+        bottom: 1px;
+        top: 1px;
+        border: 1px solid rgba($color: #fff, $alpha: 0.1);
+        border-radius: 4px;
+        z-index: 0;
+        pointer-events: none;
+    }
+    header {
+        color: #fff;
+        padding: 0 26px;
+        height: 60px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        font-weight: bold;
+        i {
+            cursor: pointer;
+            color: #969799;
+        }
+    }
+    section {
+        padding: 40px 26px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        .message {
+            text-align: center;
+            line-height: 1.7;
+        }
+    }
+    footer {
+        padding: 20px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        button {
+            width: 105px;
+            margin-left: 10px;
+            margin-right: 10px;
+        }
+    }
+}
+[is-mobile]{
+  .ui-dialog__box{
+    // width: 8.9333rem;
+    // min-width: 5.3333rem;
+    // min-height: 1.3333rem;
+    // section{
+    //   padding: 1.0667rem .5333rem .8rem;
+    // }
+    // footer{
+    //   padding:  0 0 .8rem;
+    //   border-top: none;
+    // }
+    // header{
+    //   display: none;
+    // }
+  } 
+} 

+ 3 - 0
packages/qjkankan-components/src/assets/scss/components/_floating.scss

@@ -0,0 +1,3 @@
+.ui-floating {
+  position: absolute;
+}

+ 30 - 0
packages/qjkankan-components/src/assets/scss/components/_gate.scss

@@ -0,0 +1,30 @@
+.ui-gate-layer {
+  --len: 1;
+  --current: 0;
+  width: 100%;
+  height: 100%;
+  overflow: hidden !important;
+
+  .ui-gate-slides {
+    --content-width: calc(var(--len) * 100%);
+    --item-width: calc(100% / var(--len));
+
+    width: var(--content-width);
+    height: 100%;
+    transition: transform .3s ease;
+    transform: translateX(calc(-1 * var(--current) * var(--item-width)));
+
+    .ui-gate-content {
+      width: var(--item-width);
+      height: 100%;
+      float: left;
+      opacity: 0;
+      transition: opacity .3s ease;
+
+      &.active {
+        opacity: 1;
+      }
+    }
+  }
+
+}

+ 94 - 0
packages/qjkankan-components/src/assets/scss/components/_group.scss

@@ -0,0 +1,94 @@
+$padding: 14px;
+
+.ui-group {
+  &:not(:last-child) {
+    margin-bottom: 20px;
+  }
+
+
+  &.control {
+    > .group-title {
+
+      .group-icon {
+        transition: transform .1s ease;
+        cursor: pointer;
+
+        &.show {
+          transform: rotateZ(180deg);
+        }
+      }
+    }
+    > div.group-title .group-icon.show {
+      transform: translateY(-50%) rotateZ(180deg);
+    }
+
+    .group-content {
+      overflow: hidden;
+      &.ready {
+        transition: max-height .1s ease;
+      }
+    }
+  }
+
+  > .group-title {
+    font-size: var( --big-size);
+    margin-bottom: $padding;
+    color: var(--colors-color);
+
+    .group-icon {
+      display: inline-flex;
+      align-items: center;
+    }
+  }
+
+  > div.group-title {
+    position: relative;
+
+    .group-icon {
+      position: absolute;
+      width: 14px;
+      right: 0;
+      top: 50%;
+      transform: translateY(-50%);
+    }
+  }
+
+  > h3.group-title {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+
+  .border-bottom {
+    padding-bottom: $padding;
+    border-bottom: 1px solid var(--colors-border-color);
+  }
+  .border-top {
+    padding-top: $padding;
+    border-top: 1px solid var(--colors-border-color);
+  }
+
+  > .group-content {
+    font-size: var( --medium-size);
+
+    &.border-bottom {
+      margin-bottom: 0;
+    }
+    &.border-top {
+      margin-top: 0;
+    }
+  }
+
+}
+
+.group-option {
+  &:not(:last-child) {
+    margin-bottom: $padding;
+  }
+
+  > .group-option-label {
+    display: block;
+    margin-bottom: 10px;
+    color: var(--colors-content-color);
+  }
+}

+ 75 - 0
packages/qjkankan-components/src/assets/scss/components/_icon.scss

@@ -0,0 +1,75 @@
+.ui-kankan-icon.iconfont {
+  color: currentColor;
+  font-size: 1em;
+
+  &.small {
+    font-size: 12px;
+  }
+
+  &.medium {
+    font-size: 16px;
+  }
+
+  &.big {
+    font-size: 20px;
+  }
+}
+
+.icon {
+  position: relative;
+  
+
+  .tip {
+    color: rgba(255,255,255,1);
+    position: absolute;
+    transform-origin: top center;
+    background: #000000;
+    border-radius: 4px;
+    opacity: 0;
+    padding: 10px;
+    margin: 10px;
+    font-size: 12px;
+    transition: opacity .3s ease;
+    pointer-events: none;
+    white-space: nowrap;
+  }
+
+  &:hover {
+    z-index: 999;
+    .tip {
+      opacity: 0.8;
+    }
+  }
+}
+
+  
+.tip-h-right .tip{
+  right: 0;
+  margin-right: 0;
+}
+
+.tip-h-left .tip {
+  left: 0;
+  margin-left: 0;
+}
+
+.tip-h-center .tip {
+  left: 50%;
+  transform: translateX(-50%);
+  margin-left: 0;
+  margin-right: 0;
+  z-index: 10;
+}
+
+.tip-v-top .tip {
+  bottom: 100%;
+}
+
+.tip-v-center .tip {
+  top: 50%;
+  transform: translateY(-50%);
+}
+
+.tip-v-bottom .tip {
+  top: 100%;
+}

+ 656 - 0
packages/qjkankan-components/src/assets/scss/components/_input.scss

@@ -0,0 +1,656 @@
+.ui-input {
+    display: inline-flex;
+    align-items: center;
+    --base-border-color: rgba(255, 255, 255, 0.4);
+    --colors-normal-back: rgba(26,27,29,0.8);
+    --colors-content-color: #fff;
+    height: 100%;
+    &.error {
+        position: relative;
+        --colors-primary-base: #fa3f48;
+        --base-border-color: #fa3f48;
+        .error-msg {
+            top: 100%;
+            position: absolute;
+            color: var(--colors-primary-base);
+            margin-top: 5px;
+        }
+    }
+
+    &.require {
+        position: relative;
+
+        &::before {
+            content: '*';
+            position: absolute;
+            top: 50%;
+            transform: translateY(-50%);
+            right: 100%;
+            margin-right: 4px;
+            color: #fa3f48;
+            line-height: 1.5em;
+        }
+    }
+
+    .input {
+        position: relative;
+        align-items: center;
+        display: inline-flex;
+
+        .input-div,
+        textarea,
+        input {
+            width: 100%;
+            height: 100%;
+            outline: none;
+            border: none;
+            font-size: 14px;
+            color: var(--colors-content-color);
+            padding-left: 4px;
+            resize: none;
+
+            & + .replace {
+                position: absolute;
+                z-index: 1;
+            }
+
+            &.replace-input {
+                opacity: 0;
+                cursor: pointer;
+            }
+        }
+
+        .pre-icon {
+            position: absolute;
+            z-index: 1;
+        }
+    }
+
+    .label {
+        cursor: pointer;
+        margin-left: 7px;
+    }
+
+    .radio,
+    .checkbox {
+        width: 16px;
+        height: 16px;
+
+        input {
+            & + .replace {
+                color: var(--colors-color);
+                border: 1px solid currentColor;
+                background-color: var(--colors-normal-back);
+                left: 0;
+                top: 0;
+                right: 0;
+                bottom: 0;
+                pointer-events: none;
+                transition: all 0.1s linear;
+            }
+
+            &:focus + .replace {
+                border-color: var(--colors-primary-base);
+            }
+
+            &:checked + .replace {
+                color: var(--colors-primary-base);
+            }
+        }
+    }
+
+    .checkbox input {
+        & + .replace {
+            border-radius: 4px;
+            .icon {
+                position: absolute;
+                left: 50%;
+                top: 50%;
+                transform: translate(-50%, -50%) scale(0);
+                transition: all 0.1s linear;
+            }
+        }
+
+        &:checked + .replace {
+            .icon {
+                transform: translate(-50%, -50%) scale(1);
+            }
+        }
+    }
+
+    .radio input {
+        & + .replace {
+            border-radius: 50%;
+            &::after {
+                content: '';
+                border-radius: 50%;
+                position: absolute;
+                left: 50%;
+                top: 50%;
+                transform: translate(-50%, -50%) scale(0);
+                transition: all 0.1s linear;
+                width: 60%;
+                height: 60%;
+                background-color: currentColor;
+            }
+        }
+
+        &:checked + .replace::after {
+            transform: translate(-50%, -50%) scale(1);
+        }
+    }
+
+    .text {
+        width: 100%;
+        height: 100%;
+        border-radius: 4px;
+
+        input {
+            background: var(--colors-normal-back);
+            height: 100%;
+            padding: 8px 10px;
+            border-radius: 4px;
+            border: 1px solid var(--base-border-color);
+            transition: border 0.3s ease;
+
+            &:focus {
+                border-color: var(--colors-primary-base);
+            }
+            &.warn {
+                border-color: var(--colors-warn);
+                &:focus {
+                    border-color: var(--colors-warn);
+                }
+            }
+
+            &::placeholder {
+                color: var(--colors-color);
+            }
+        }
+
+        &.pre-suffix {
+            input {
+                padding-left: 30px;
+            }
+            .pre-icon {
+                left: 10px;
+                top: 50%;
+                transform: translateY(-50%);
+            }
+        }
+
+        &.right input {
+            text-align: right;
+        }
+
+        &.suffix {
+            input {
+                padding-right: 60px;
+            }
+            .retouch {
+                position: absolute;
+                right: 10px;
+                top: 50%;
+                transform: translateY(-50%);
+                z-index: 10;
+                cursor: pointer;
+            }
+
+            .len {
+                font-size: var(--small-size);
+                color: rgba(var(--colors-primary-fill), 1);
+
+                span {
+                    color: var(--colors-primary-base);
+                }
+            }
+        }
+
+        &.ready {
+            .retouch,
+            input {
+                transition: all 0.1s linear;
+            }
+        }
+    }
+
+    .textarea {
+        width: 100%;
+        height: 100%;
+        min-height: 50px;
+
+        > .replace {
+            border-radius: 4px;
+            left: 0;
+            top: 0;
+            right: 0;
+            bottom: 0;
+            pointer-events: none;
+            background: var(--colors-normal-back);
+            border: 1px solid var(--base-border-color);
+            transition: border 0.3s ease;
+        }
+
+        .input-div {
+            overflow-y: auto;
+
+            a {
+                color: var(--color-main-normal);
+            }
+        }
+        .input-div,
+        textarea {
+            height: 100%;
+            width: 100%;
+            padding: 10px;
+
+            &:focus + .replace {
+                border-color: var(--colors-primary-base);
+            }
+
+            &::placeholder {
+                color: var(--colors-color);
+            }
+        }
+
+        &.right .input-div,
+        &.right textarea {
+            text-align: right;
+        }
+
+        &.suffix {
+            --bar-height: 30px;
+
+            .input-div,
+            textarea {
+                margin-bottom: var(--bar-height);
+                height: calc(100% - var(--bar-height));
+            }
+
+            > .retouch {
+                position: absolute;
+                right: 0;
+                left: 0;
+                bottom: 0;
+                background-color: rgba(var(--colors-primary-fill), 0.1);
+                height: var(--bar-height);
+                display: flex;
+                padding: 0 10px;
+                align-items: center;
+                justify-content: space-between;
+            }
+
+            .len {
+                justify-self: end;
+                font-size: var(--small-size);
+                color: rgba(var(--colors-primary-fill), 1);
+
+                span {
+                    color: var(--colors-primary-base);
+                }
+            }
+        }
+    }
+
+    .number {
+        input {
+            -moz-appearance: textfield;
+        }
+        input::-webkit-inner-spin-button,
+        input::-webkit-outer-spin-button {
+            -webkit-appearance: none;
+            margin: 0;
+        }
+
+        .ctrls {
+            position: absolute;
+            inset: 2px 0;
+            width: 8px;
+            .icon {
+                position: absolute;
+                right: 0;
+
+                &.up {
+                    bottom: 0;
+                }
+                &.down {
+                    top: 0;
+                }
+            }
+        }
+
+        &.ctrl.suffix input {
+            padding-right: 20px;
+            z-index: 1;
+        }
+    }
+
+    .select {
+        input {
+            cursor: pointer;
+            &.ui-text {
+                padding-right: 0;
+            }
+            &.icon {
+                padding-right: 30px;
+            }
+        }
+
+        &.focus {
+            input {
+                border-color: var(--colors-primary-base);
+            }
+            .retouch {
+                transform: translateY(-50%) rotateZ(180deg);
+            }
+        }
+    }
+
+    .range {
+        width: 100%;
+        height: 100%;
+        display: flex;
+        --height: 6px;
+        --slideSize: calc(var(--height) + 8px);
+
+        .range-content {
+            flex: 1;
+            background-color: var(--colors-normal-back);
+            position: relative;
+            cursor: pointer;
+        }
+
+        .range-content::before,
+        .range-content {
+            height: var(--height);
+            border-radius: calc(var(--height) / 2);
+        }
+
+        .range-content::before,
+        .range-content .range-slide {
+            content: '';
+            position: absolute;
+        }
+
+        .range-content::before {
+            pointer-events: none;
+            left: 0;
+            top: 0;
+            width: var(--percentage);
+            background-color: var(--colors-primary-base);
+        }
+
+        .range-locus {
+            width: calc(100% - var(--slideSize));
+            height: var(--height);
+            position: relative;
+
+            .range-slide {
+                cursor: pointer;
+                height: var(--slideSize);
+                width: var(--slideSize);
+                top: 50%;
+                left: var(--percentage);
+                transform: translateY(-50%);
+                background-color: var(--colors-content-color);
+                border-radius: 50%;
+            }
+        }
+
+        .range-text {
+            margin-left: 10px;
+            width: 65px;
+        }
+
+        .animation {
+            &.range-content::before,
+            .range-slide {
+                transition: all 0.1s linear;
+            }
+        }
+    }
+
+    .switch {
+        --height: 24px;
+        width: 50px;
+        height: var(--height);
+
+        .replace {
+            background-color: rgba(255, 255, 255, 0.3);
+            left: 0;
+            top: 0;
+            right: 0;
+            bottom: 0;
+            border-radius: calc(var(--height) / 2);
+            pointer-events: none;
+            position: relative;
+            transition: background-color 0.3s ease;
+
+            &::after {
+                content: '';
+                --padding: 3px;
+                --size: calc(var(--height) - var(--padding) * 2);
+                position: absolute;
+                width: var(--size);
+                height: var(--size);
+                top: var(--padding);
+                background: var(--colors-content-color);
+                border-radius: 50%;
+                left: var(--padding);
+                transition: left 0.3s ease;
+            }
+        }
+
+        input:checked + .replace {
+            background-color: var(--colors-primary-base);
+
+            &::after {
+                left: calc(100% - var(--size) - var(--padding));
+            }
+        }
+    }
+
+    .file {
+        width: 100%;
+        height: 100%;
+
+        input {
+            cursor: pointer;
+            opacity: 0;
+        }
+        .use-replace {
+            position: absolute;
+        }
+        .use-replace,
+        .replace {
+            left: 0;
+            right: 0;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            pointer-events: none;
+        }
+
+        &:not(.valuable) {
+            .replace {
+                top: 0;
+                bottom: 0;
+                background: rgba(var(--colors-primary-fill), 0.1);
+                border-radius: 4px;
+                border: 1px solid rgba(var(--colors-primary-fill), 0.2);
+                // position: relative;
+
+                .placeholder {
+                    text-align: center;
+                    max-width: 80%;
+
+                    p:not(:last-child) {
+                        margin-bottom: 10px;
+                    }
+
+                    .bottom {
+                        font-size: 12px;
+                        color: rgba(255, 255, 255, 0.3);
+                        width: 90%;
+                        position: absolute;
+                        bottom: 10px;
+                        left: 50%;
+                        transform: translateX(-50%);
+                        text-align: left;
+                    }
+                }
+            }
+
+            input {
+                width: 100%;
+                height: 100%;
+
+                &:focus + .replace {
+                    border-color: var(--colors-primary-base);
+                }
+            }
+        }
+        &.valuable {
+            background: rgba(var(--colors-primary-fill), 0.1);
+            border-radius: 4px;
+            border: 1px solid rgba(var(--colors-primary-fill), 0.2);
+
+            input,
+            .replace {
+                position: absolute;
+                bottom: 0;
+                background: linear-gradient(180deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.5) 100%);
+                height: 32px;
+                line-height: 32px;
+
+                .tj {
+                    position: absolute;
+                    right: 10px;
+                    top: 0;
+                    bottom: 0;
+                    display: flex;
+                    align-items: center;
+                    font-size: 10px;
+
+                    > span {
+                        color: var(--colors-primary-base);
+                        margin-right: 4px;
+                    }
+                }
+            }
+
+            .icons {
+                position: absolute;
+                right: 10px;
+                top: 0;
+
+                span {
+                    width: 24px;
+                    height: 24px;
+                    border-radius: 50%;
+                    background: rgba(0, 0, 0, 0.3);
+                    font-size: 12px;
+                    color: rgba(255, 255, 255, 0.6);
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    margin-top: 10px;
+                }
+            }
+        }
+    }
+
+    .search {
+        .retouch {
+            transform: translateY(-50%) !important;
+
+            .clear {
+                // background-color: rgba(255,255,255,.3);
+                font-size: 16px;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                color: rgba(255, 255, 255, 0.6);
+                border-radius: 50%;
+                cursor: pointer;
+            }
+        }
+    }
+
+    .color {
+        &.default {
+            input {
+                opacity: 1;
+                border: inherit;
+                outline: inherit;
+            }
+        }
+        .replace {
+            pointer-events: none;
+        }
+    }
+}
+
+.select-float {
+    transition: transform 0.3s ease, opacity 0.3s ease;
+    transform-origin: center top;
+
+    &:not(.show) {
+        transform: scale(1, 0);
+        opacity: 0;
+        pointer-events: none;
+    }
+    &.show {
+        transform: scale(1, 1);
+        opacity: 1;
+    }
+}
+
+.select-replace {
+    --colors-content-color: #fff;
+
+    list-style: none;
+    max-height: 288px;
+    background: rgba(26, 26, 26, 0.8);
+    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3), inset 0 0 1px rgb(255 255 255 / 90%);
+    backdrop-filter: blur(4px);
+    border-radius: 4px;
+    overflow-y: auto;
+    color: var(--colors-content-color);
+
+    li {
+        padding: 10px 10px;
+        font-size: 14px;
+
+        &.un-data {
+            padding: 20px;
+            color: rgba(255, 255, 255, 0.3);
+            pointer-events: none;
+        }
+        &.active {
+            background: var(--colors-normal-back);
+            color: var(--colors-primary-base);
+        }
+
+        &:not(.active):hover {
+            cursor: pointer;
+            background-color: var(--colors-primary-base);
+        }
+    }
+}
+
+.is-hidden {
+    position: absolute;
+    left: -10000px;
+    top: -10000px;
+}
+.no-vip {
+    .file {
+        input {
+            pointer-events: none;
+        }
+        label {
+            pointer-events: none;
+        }
+    }
+}

+ 56 - 0
packages/qjkankan-components/src/assets/scss/components/_loading.scss

@@ -0,0 +1,56 @@
+.ui-loading {
+    position: absolute;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+    background-color: rgba($color: #000000, $alpha: 0.3);
+    --width: 15px;
+    --color: #fff;
+}
+.ui-loading__box {
+    position: relative;
+    width: 100px;
+    height: 100px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    .default {
+        div {
+            width: var(--width);
+            height: var(--width);
+            background: var(--color);
+            border-radius: 50%;
+            display: inline-block;
+            margin-left: calc(var(--width));
+        }
+        div:nth-child(1) {
+            animation: ui-loading-default 1s -0.5s linear infinite;
+        }
+        div:nth-child(2) {
+            animation: ui-loading-default 1s -0.25s linear infinite;
+        }
+        div:nth-child(3) {
+            animation: ui-loading-default 1s 0s linear infinite;
+        }
+    }
+}
+
+@keyframes ui-loading-default {
+    0% {
+        transform: scale(1);
+        opacity: 1;
+    }
+    50% {
+        transform: scale(0.5);
+        opacity: 0.5;
+    }
+    100% {
+        transform: scale(1);
+        opacity: 0.8;
+    }
+}

+ 27 - 0
packages/qjkankan-components/src/assets/scss/components/_men-item.scss

@@ -0,0 +1,27 @@
+
+
+.ui-menu-item {
+  height: 100%;
+  width: 100%;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  color: var(--editor-men-color);
+  transition: all .3s ease;
+
+  span{
+    width: 100%;
+    text-align: center;
+    margin-top: 6px;
+    overflow: hidden;
+  }
+  &:hover{
+      color: var( --color-main-hover);
+  }
+  &.active{
+      color: var( --color-main-normal);
+      background-color: var(--editor-menu-active);
+  }
+}

+ 41 - 0
packages/qjkankan-components/src/assets/scss/components/_message.scss

@@ -0,0 +1,41 @@
+.ui-message {
+  position: absolute;
+  left: 50%;
+  top: 70px;
+  height: 40px;
+  padding: 0 20px;
+  background: rgba(20,20,20,0.7);
+  box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.3);
+  border-radius: 4px;
+  border: 1px solid #000000;
+  backdrop-filter: blur(4px);
+  color: #fff;
+  display: flex;
+  font-size: 14px;
+  align-items: center;
+  transition: all .5s ease;
+  opacity: 1;
+  transform: translateX(-50%);
+
+  .icon {
+    font-size: 16px;
+    margin-right: 10px;
+  }
+
+  &.success .icon {
+    color: #43c665;
+  }
+  &.warning .icon {
+    color: #f49b42;
+  }
+  &.error .icon {
+    color: #f34447;
+  }
+
+
+  &.fade-enter-from,
+  &.fade-leave-to {
+    opacity: 0;
+    transform: translateX(-50%) translateY(-100%);
+  }
+}

+ 28 - 0
packages/qjkankan-components/src/assets/scss/components/_size-animation.scss

@@ -0,0 +1,28 @@
+.ui-size-animation {
+
+  &.height {
+    overflow: hidden;
+  }
+
+  &:not(.ready) {
+    opacity: 0;
+  }
+  &.ready {
+    transition: max-height .2s ease;
+  }
+
+  &.scale {
+    transform-origin: center top;
+    transform: scaleY(0); 
+
+    &.show {
+      transform: scaleY(1); 
+    }
+
+    &.ready {
+      transition: max-height .2s ease,
+        transform .2s ease;
+    }
+  }
+
+}

+ 27 - 0
packages/qjkankan-components/src/assets/scss/components/_slide.scss

@@ -0,0 +1,27 @@
+
+.ui-slide {
+  position: relative;
+  height: 100%;
+
+  .right,
+  .left {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    background-color: rgba(0,0,0,0.3);
+    width: 30px;
+    height: 30px;
+    border-radius: 50%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    cursor: pointer;
+  }
+  .right {
+    right: 10px;
+  }
+
+  .left {
+    left: 10px;
+  }
+}

+ 90 - 0
packages/qjkankan-components/src/assets/scss/components/_toast.scss

@@ -0,0 +1,90 @@
+.ui-toast {
+    position: fixed;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    top: 30px;
+    left: 0;
+    right: 0;
+    min-width: 100px;
+    height: 100px;
+    overflow: hidden;
+    pointer-events:none;
+}
+.ui-toast__box {
+    color: #fff;
+    font-size: 14px;
+    position: relative;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background-color: rgba($color: #1a1a1a, $alpha: 0.8);
+    box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.7);
+    border-radius: 4px;
+    border: 1px solid #000000;
+    padding: 8px 20px;
+    pointer-events: all;
+
+    &.fixed,
+    &.success,
+    &.error,
+    &.warn{
+        i{
+            display:inline-block;
+        }
+    }
+    &.success {
+        .icon {
+            background-image: url('#{$img-base-path}icons/toast-success.png');
+        }
+    }
+    &.error {
+        .icon {
+            background-image: url('#{$img-base-path}icons/toast-error.png');
+        }
+    }
+    &.warn {
+        .icon {
+            background-image: url('#{$img-base-path}icons/toast-warn.png');
+        }
+    }
+    &::after {
+        content: '';
+        position: absolute;
+        left: 1px;
+        right: 1px;
+        bottom: 1px;
+        top: 1px;
+        border: 1px solid rgba($color: #fff, $alpha: 0.1);
+        border-radius: 4px;
+        z-index: 0;
+        pointer-events: none;
+    }
+
+    // >i{
+    //     display: none;
+    // }
+
+    .icon{
+        margin-right: 10px;
+        font-size: 0;
+        width: 16px;
+        height: 16px;
+        background-repeat: no-repeat;
+        background-position: center center;
+        background-size: contain;
+    }
+    .close{
+        cursor: pointer;
+        font-size: 14px;
+        margin-left: 20px;
+    }
+}
+.ui-toast__msg{
+  display: flex;
+  align-items: center;
+  img{
+    width:24px;
+    height: 24px;
+  }
+}

+ 173 - 0
packages/qjkankan-components/src/assets/scss/components/_tree.scss

@@ -0,0 +1,173 @@
+.ui-tree {
+  /* 收缩控件大小 */
+  --ctrl-size: 14px;
+  /* 左边留白大小 */
+  --padding-size: 20px;
+  // 底部margind大小
+  --margin-size: 20px;
+  --border-style: dashed;
+  --border-width: 1px;
+  
+  // 用于计算,防止样式覆盖而失效
+  --calc-size: var(--padding-size);
+  --half-ctrl: calc(var(--ctrl-size) / 2);
+  --half-margin: calc(var(--margin-size) / 2);
+
+  &.flat {
+    .ui-tree-item {
+      --padding-size: 20px;
+      // padding-left: 0;
+      margin-left: calc(-1 * var(--padding-size));
+    }
+  }
+
+  color: var(--colors-normal-base);;
+
+  .ui-tree-item{
+    list-style: none;
+    padding-left: var(--padding-size);
+    position: relative;
+
+    &.un-children {
+      --padding-size: 0;
+    }
+  }
+
+  .ui-tree-content {
+    margin-bottom: var(--margin-size);
+    position: relative;
+  }
+
+  .ui-tree-ctrl {
+    position: absolute;
+    width: var(--ctrl-size);
+    height: var(--ctrl-size);
+    left: calc(var(--padding-size) * -1);
+    top: 50%;
+    transform: translateY(-50%);
+    border: 1px solid currentColor;
+    line-height: var(--ctrl-size);
+    border-radius: calc(var(--ctrl-size) / 6);
+    cursor: pointer;
+
+    &::before,
+    &::after {
+      content: '';
+      height: 1px;
+      width: 60%;
+      background-color: currentColor;
+      position: absolute;
+      left: 50%;
+      top: 50%;
+    }
+
+    &::before {
+      transform: translate(-50%, -50%);
+    }
+
+    &::after {
+      transform: translate(-50%, -50%) rotateZ(90deg);
+      transition: transform .3s ease;
+    }
+
+    &.open::after {
+      transform: translate(-50%, -50%) rotateZ(90deg) scale(0);
+    }
+  }
+  .ui-tree-item-child {
+    --offset: calc(var(--calc-size) * 2);
+    width: calc(100% + var(--offset));
+    padding-left: var(--offset);
+    margin-left: calc(-1 * var(--offset));
+    padding-top: var(--margin-size);
+    margin-top: calc(-1 * var(--margin-size));
+    // overflow: hidden;
+    transition: all .3s ease !important;
+  }
+
+  &.stroke {
+    --slideWidth: calc(var(--padding-size) - var(--half-ctrl));
+
+    .ui-tree-auxiliary::after,
+    .ui-tree-auxiliary::before,
+    .ui-tree-item::before,
+    .ui-tree-content::after {
+      content: '';
+      position: absolute;
+    }
+
+    .ui-tree-content::after {
+      left: calc(var(--padding-size) * -1);
+      width: var(--slideWidth);
+      border-bottom: var(--border-width) var(--border-style) currentColor;
+      top: 50%;
+      transform: translateX(-100%) translateY(-50%);
+    }
+
+    .ui-tree-auxiliary::after,
+    .ui-tree-auxiliary::before,
+    .ui-tree-item.un-children:last-child::before,
+    .ui-tree-item:not(:last-child):before {
+      border-left: var(--border-width) var(--border-style) currentColor;
+      transform: translateX(calc(var(--slideWidth) * -1));
+      left: 0;
+    }
+
+    .ui-tree-item:not(:last-child):before  {
+      top: 0;
+      bottom: calc(-1 * var(--half-ctrl));
+    }
+
+    .ui-tree-item.un-children:last-child::before {
+      top: 0;
+      bottom: calc(50% + var(--half-margin));
+    }
+
+    .ui-tree-auxiliary::before,
+    .ui-tree-auxiliary::after {
+      height: calc(50% - var(--half-ctrl));
+      transition: height .3s ease
+    }
+
+    .ui-tree-auxiliary::before {
+      height: calc(50% - var(--half-ctrl));
+      left: calc(var(--padding-size) * -1);
+      top: var(--half-ctrl);
+    }
+
+    .ui-tree-auxiliary::after {
+      height: calc(50% - var(--half-ctrl) + var(--margin-size));
+      bottom: calc(-1 * var(--margin-size));
+    }
+    
+    .first.ui-tree-auxiliary::before {
+      display: none;
+    }
+
+    .alone .ui-tree-auxiliary::before {
+      display: inherit;
+      height: 50%;
+      top: 0;
+    }
+
+    &:not(.children) > {
+      .ui-tree-item > .ui-tree-content > .ui-tree-auxiliary::before,
+      .ui-tree-item::before,
+      .ui-tree-item > .ui-tree-content::after {
+        display: none;
+      }
+    }
+
+    .ui-tree-item.put {
+      .ui-tree-item-child {
+        padding-top: 0;
+        margin-top: 0;
+      }
+
+      > .ui-tree-content .ui-tree-auxiliary::after {
+        height: 0;
+      }
+    }
+  }
+
+}

+ 24 - 0
packages/qjkankan-components/src/assets/scss/editor/_head.scss

@@ -0,0 +1,24 @@
+@use "sass:map";
+
+.ui-editor-head {
+    filter: var(--editor-head-filter);
+    height: var(--editor-head-height);
+    background-color: var(--editor-head-back);
+    position: fixed;
+    backdrop-filter: blur(4px);
+    left: 0;
+    top: 0;
+    width: 100%;
+    z-index: 4000;
+    &::after{
+        position: absolute;
+        content: "";
+        bottom: 0;
+        left: 0;
+        right: 0;
+        height: 0px;
+        line-height: 0;
+        border-top: solid 1px rgba(255, 255, 255, 0.16);
+        border-bottom: solid 1px #000;
+    }
+}

+ 7 - 0
packages/qjkankan-components/src/assets/scss/editor/_layout.scss

@@ -0,0 +1,7 @@
+.ui-editor-layout {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    color: #fff;
+    font-size: 14px;
+}

+ 15 - 0
packages/qjkankan-components/src/assets/scss/editor/_main.scss

@@ -0,0 +1,15 @@
+@use "sass:map";
+
+.ui-editor-main {
+    filter: var(--editor-menu-filter); 
+    position: fixed;
+    top: var(--editor-head-height);
+    left: 0;
+    right: 0;
+    bottom: 0;
+    pointer-events: none;
+    z-index: 4000;
+}
+[is-mobile]  .ui-editor-main {
+    top:0;
+}

+ 37 - 0
packages/qjkankan-components/src/assets/scss/editor/_menu.scss

@@ -0,0 +1,37 @@
+@use "sass:map";
+
+.ui-editor-menu {
+    user-select: none;
+    width: var(--editor-menu-width);
+    filter: var(--editor-menu-filter); 
+    background-color: var(--editor-menu-back);
+    position: fixed;
+    left: var(--editor-menu-left);
+    top: var(--editor-head-height);
+    bottom: 0;
+    z-index: 2000;
+    overflow: hidden;
+
+    backdrop-filter: blur(4px);
+
+    >div{
+        height: 100%;
+        width: 100%;
+        overflow: visible;
+    }
+
+    .ui-editor-menu-item {
+        width: var(--editor-menu-width);
+        height: var(--editor-menu-width);
+    }
+
+    &::after{
+        position: absolute;
+        content: "";
+        top: 0;
+        bottom: 0;
+        right: 0;
+        border-left: solid 1px rgba(255, 255, 255, 0.16);
+        border-right: solid 1px #000;
+    }
+}

+ 27 - 0
packages/qjkankan-components/src/assets/scss/editor/_toolbar.scss

@@ -0,0 +1,27 @@
+@use "sass:map";
+
+.ui-editor-toolbar {
+    position: absolute;
+    bottom: 0;
+    right: calc(var(--editor-toolbox-width) + var(--editor-menu-right));
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex: 1;
+    height: var(--editor-toolbar-height);
+    background-color: var(--editor-menu-back);
+    pointer-events: all;
+    left: var(--editor-toolbox-left);
+    z-index: 1;
+    transition: all 0.3s ease;
+    &.full {
+        left: calc(0px - var(--editor-menu-width)) !important;
+        right: 0;
+    }
+    &.full-left {
+        left: calc(0px - var(--editor-menu-width)) !important;
+    }
+    &.full-right {
+        right: 0;
+    }
+}

+ 31 - 0
packages/qjkankan-components/src/assets/scss/editor/_toolbox.scss

@@ -0,0 +1,31 @@
+@use "sass:map";
+
+.ui-editor-toolbox {
+    position: absolute;
+    z-index: 1;
+    right: var(--editor-menu-right);
+    padding: var(--editor-toolbox-padding);
+    width:  var(--editor-toolbox-width);;
+    bottom: 0;
+    pointer-events: all;
+    overflow-y: auto;
+    background-color: var(--editor-toolbox-back);
+    top: var(--editor-toolbox-top);
+    transition: right ease .3s;
+    filter: var(--editor-head-filter);
+    backdrop-filter: blur(4px);
+    ul,li{
+        list-style: none;
+        margin:0;
+        padding: 0;
+    }
+    &::after{
+        position: absolute;
+        content: "";
+        top: 0;
+        bottom: 0;
+        left: 0;
+        border-right: solid 1px rgba(255, 255, 255, 0.16);
+        border-left: solid 1px #000;
+    }
+}

+ 15 - 0
packages/qjkankan-components/src/assets/scss/editor/_view.scss

@@ -0,0 +1,15 @@
+@use "sass:map";
+
+.ui-editor-view {
+    display: flex;
+    flex: 1;
+    width: 100%;
+    height: 100%;
+    color: #fff;
+    font-size: 14px;
+    // background-color: #fff;
+    align-items: flex-end;
+    justify-content: flex-end;
+    overflow: hidden;
+}
+

+ 12 - 0
packages/qjkankan-components/src/assets/scss/theme-editor.scss

@@ -0,0 +1,12 @@
+@import "base";
+@import "base-vars";
+@import "components";
+
+@import "editor/layout";
+@import "editor/head";
+@import "editor/menu";
+@import "editor/main";
+@import "editor/view";
+@import "editor/toolbox";
+@import "editor/toolbar";
+

+ 56 - 0
packages/qjkankan-components/src/components/audio/index.vue

@@ -0,0 +1,56 @@
+<template>
+    <div class="ui-audio" @click="clickHandler">
+        <audio loop @play="rotation" ref="audio">
+            <source :src="src" />
+        </audio>
+        <span v-for="random in randoms" :style="{ '--percent': random }" />
+    </div>
+</template>
+
+<script setup>
+import { defineProps, ref, watchEffect, defineExpose } from 'vue'
+defineProps({
+    src: String,
+})
+const audio = ref()
+const randoms = ref([1, 0.5, 1, 0.5])
+const playIng = ref(false)
+
+let timeout
+const rotation = () => {
+    if (!playIng.value) return
+    for (let i = 0; i < randoms.value.length; i++) {
+        randoms.value[i] = Math.random()
+    }
+    timeout = setTimeout(rotation, 200)
+}
+
+watchEffect(() => {
+    if (audio.value) {
+        if (playIng.value) {
+            audio.value.play()
+        } else {
+            audio.value.pause()
+        }
+        clearTimeout(timeout)
+        rotation()
+    }
+})
+
+const clickHandler = () => {
+    playIng.value = !playIng.value
+}
+
+defineExpose({
+    play() {
+        playIng.value = true
+    },
+    pause() {
+        playIng.value = false
+    },
+})
+</script>
+
+<script>
+export default { name: 'ui-audio' }
+</script>

+ 52 - 0
packages/qjkankan-components/src/components/button/index.vue

@@ -0,0 +1,52 @@
+<template>
+  <button class="ui-button" 
+    :class="className"
+    :style="style">
+    <UIIcon :type="icon" v-if="icon" class="ui-button-icon" />
+    <slot></slot>
+  </button>
+</template>
+
+
+<script setup>
+import { defineProps, computed } from 'vue'
+import { normalizeUnitToStyle } from '../../utils/index'
+import UIIcon from '../icon'
+
+const props = defineProps({
+  type: {
+    type: String,
+    default: 'normal',
+  },
+  color: {
+    type: String
+  },
+  width: {
+    type: [String, Number]
+  },
+  icon: {
+    type: String
+  }
+})
+
+const custom = `customize`
+const className = computed(
+  () => props.color ? custom : props.type
+)
+
+const style = computed(
+  () => { 
+    const style = {
+      width: normalizeUnitToStyle(props.width)
+    }
+
+    if (className.value === custom) {
+      style['--color'] = props.color
+    }
+    return style
+  }
+)
+
+</script>
+
+<script> export default { name: 'ui-button' } </script>

+ 8 - 0
packages/qjkankan-components/src/components/cropper/cropper.vue

@@ -0,0 +1,8 @@
+<template>
+    <div>cropper</div>
+</template>
+<script>
+export default {
+    
+}
+</script>

+ 37 - 0
packages/qjkankan-components/src/components/cropper/index.js

@@ -0,0 +1,37 @@
+import Cropper from './cropper.vue'
+import { mount } from '../../utils/componentHelper'
+import { toRawType } from '../../utils/index'
+
+Cropper.use = function use(app) {
+    const isCropper = false
+    Cropper.open = function (config) {
+        if (isCropper) {
+            return Promise.reject('正在裁剪')
+        }
+        if (toRawType(config) === 'String') {
+            config = { img: config }
+        }
+        if (!config || !config.img) {
+            return Promise.reject('请传入裁剪图片')
+        }
+
+        return new Promise((resolve, reject) => {
+            const { destroy } = mount(Cropper, {
+                app,
+                props: {
+                    ...config,
+                    cb(err, data) {
+                        destroy()
+                        if (err) {
+                            reject(err)
+                        } else {
+                            resolve(data)
+                        }
+                    },
+                },
+            })
+        })
+    }
+}
+
+export default Cropper

+ 52 - 0
packages/qjkankan-components/src/components/dialog/Alert.vue

@@ -0,0 +1,52 @@
+<template>
+    <ui-dialog>
+        <template v-slot:header>
+            <span>{{ title }}</span>
+            <i v-if="showCloseIcon" class="iconfont icon-close" @click="close"></i>
+        </template>
+        {{ content }}
+        <template v-slot:footer v-if="showFooter">
+            <ui-button type="submit" @click="close">{{ okText }}</ui-button>
+        </template>
+    </ui-dialog>
+</template>
+<script>
+import { defineComponent } from 'vue'
+import { isFunction, omit } from '../../utils'
+export default defineComponent({
+    name: 'ui-alert',
+    props: {
+        showCloseIcon: {
+            type: Boolean,
+            default: true,
+        },
+        showFooter: {
+            type: Boolean,
+            default: true,
+        },
+        title: {
+            type: String,
+            default: '提示',
+        },
+        okText: {
+            type: String,
+            default: '确定',
+        },
+        func: Function,
+        content: String,
+        destroy: Function,
+    },
+    setup: function (props, ctx) {
+        const close = () => {
+            if (isFunction(props.func) && props.func() === false) {
+                return
+            }
+            isFunction(props.destroy) && props.destroy()
+        }
+        return {
+            ...omit(props, 'destroy', 'func'),
+            close,
+        }
+    },
+})
+</script>

+ 58 - 0
packages/qjkankan-components/src/components/dialog/Confirm.vue

@@ -0,0 +1,58 @@
+<template>
+    <ui-dialog>
+        <template v-slot:header>
+            <span>{{ title }}</span>
+            <i class="iconfont icon-close" @click="close('no')"></i>
+        </template>
+        <template v-if="$slots.content">
+            <slot name="content" />
+        </template>
+        <template v-else>
+            <div class="message" v-html="content"></div>
+        </template>
+        <template v-slot:footer>
+            <ui-button v-if="!single" type="cancel" @click="close('no')">{{ noText }}</ui-button>
+            <ui-button type="primary" @click="close('ok')">{{ okText }}</ui-button>
+        </template>
+    </ui-dialog>
+</template>
+<script>
+import { defineComponent } from 'vue'
+import { isFunction, omit } from '../../utils'
+export default defineComponent({
+    name: 'ui-confirm',
+    props: {
+        title: {
+            type: String,
+            default: '提示',
+        },
+        okText: {
+            type: String,
+            default: '确定',
+        },
+        noText: {
+            type: String,
+            default: '取消',
+        },
+        single: {
+            type: Boolean,
+            default: false,
+        },
+        func: Function,
+        content: String,
+        destroy: Function,
+    },
+    setup: function (props, ctx) {
+        const close = result => {
+            if (isFunction(props.func) && props.func(result) === false) {
+                return
+            }
+            isFunction(props.destroy) && props.destroy()
+        }
+        return {
+            ...omit(props, 'destroy', 'func'),
+            close,
+        }
+    },
+})
+</script>

+ 17 - 0
packages/qjkankan-components/src/components/dialog/Dialog-content.vue

@@ -0,0 +1,17 @@
+<template>
+  <div class="ui-dialog__box">
+      <header v-if="$slots.header">
+          <slot name="header"></slot>
+      </header>
+      <section>
+          <slot></slot>
+      </section>
+      <footer v-if="$slots.footer">
+          <slot name="footer"></slot>
+      </footer>
+  </div>
+</template>
+
+<script>
+export default { name: 'ui-dialog-content' }
+</script>

+ 27 - 0
packages/qjkankan-components/src/components/dialog/Dialog.vue

@@ -0,0 +1,27 @@
+<template>
+    <teleport to="body">
+        <div class="ui-dialog" :style="{ zIndex: zIndex }" v-if="show">
+            <dialog-content>
+                <template v-for="(slot, name) in $slots" v-slot:[name]="raw">
+                    <slot :name="name" v-bind="raw" />
+                </template>
+            </dialog-content>
+        </div>
+    </teleport>
+</template>
+<script>
+import { defineComponent, ref } from 'vue'
+import zindex from '../../utils/zindex'
+import DialogContent from './Dialog-content.vue'
+export default defineComponent({
+    name: "ui-dialog",
+    setup: function (props, ctx) {
+        const show = ref(true);
+        return {
+            show,
+            zIndex: zindex(),
+        };
+    },
+    components: { DialogContent }
+})
+</script>

+ 70 - 0
packages/qjkankan-components/src/components/dialog/Toast.vue

@@ -0,0 +1,70 @@
+<template>
+    <teleport to="body">
+        <transition name="slide-down" mode="out-in" appear>
+            <div class="ui-toast" :style="{ zIndex: zIndex }" v-if="show">
+                <div class="ui-toast__box" :class="[type]">
+                    <i v-if="type !== 'fixed' && type" class="icon"></i>
+                    <div class="ui-toast__msg" v-html="content"></div>
+                    <i class="iconfont icon-close close" @click="close" v-if="showClose"></i>
+                </div>
+            </div>
+        </transition>
+    </teleport>
+</template>
+<script>
+import { defineComponent, onMounted, nextTick, ref } from 'vue'
+import zindex from '../../utils/zindex'
+export default defineComponent({
+    name: 'ui-toast',
+    props: {
+        type: String,
+        delay: Number,
+        content: String,
+        destroy: Function,
+        close: Function,
+        showClose: Boolean,
+    },
+    setup: function (props, ctx) {
+        const show = ref(true)
+        const close = () => {
+            show.value = false
+            nextTick(() => {
+                if (typeof props.close == 'function') {
+                    props.close()
+                }
+                typeof props.destroy === 'function' && props.destroy()
+            })
+        }
+
+        if (props.type !== 'fixed') {
+            setTimeout(() => close(), props.delay || 3000)
+        }
+        return {
+            show,
+            type: props.type,
+            close,
+            content: props.content,
+            zIndex: zindex(),
+        }
+    },
+})
+</script>
+<style lang="scss" scoped>
+.slide-down-enter-active,
+.slide-down-leave-active {
+    will-change: transform;
+    transition: all 0.35s ease-in-out;
+}
+.slide-down-enter-from {
+    opacity: 0;
+    transform: translate3d(0, -100%, 0);
+}
+.slide-down-enter {
+    opacity: 1;
+    transform: translate3d(-50%, 100%, 0);
+}
+.slide-down-leave-active {
+    opacity: 0;
+    transform: translate3d(0, -100%, 0);
+}
+</style>

+ 63 - 0
packages/qjkankan-components/src/components/dialog/Window.vue

@@ -0,0 +1,63 @@
+<template>
+    <ui-dialog>
+        <template v-slot:header>
+            <span style="font-size: 16px">{{ title }}</span>
+            <i class="iconfont icon-bs_close" @click="onNo" v-if="showCloseIcon"></i>
+        </template>
+        <template v-if="$slots.content">
+            <slot name="content" />
+        </template>
+        <template v-else>
+            {{ content }}
+        </template>
+        <template v-slot:footer>
+            <ui-button type="cancel" @click="onNo" v-if="showCancelButton">{{ noText }}</ui-button>
+            <ui-button :class="{ disabled: !canSubmit }" type="submit primary" @click="onOk">{{ okText }}</ui-button>
+        </template>
+    </ui-dialog>
+</template>
+<script>
+import { defineComponent } from 'vue'
+export default defineComponent({
+    name: 'ui-window',
+    props: {
+        title: {
+            type: String,
+            default: '提示'
+        },
+        okText: {
+            type: String,
+            default: '确定'
+        },
+        noText: {
+            type: String,
+            default: '取消'
+        },
+        showCloseIcon: {
+            type: Boolean,
+            default: true
+        },
+        showCancelButton: {
+            type: Boolean,
+            default: true
+        },
+        canSubmit: {
+            type: Boolean,
+            default: true
+        }
+    },
+    emits: ['ok', 'no'],
+    setup: function(props, ctx) {
+        const onNo = () => {
+            ctx.emit('no')
+        }
+        const onOk = () => {
+            ctx.emit('ok')
+        }
+        return {
+            onNo,
+            onOk
+        }
+    }
+})
+</script>

+ 93 - 0
packages/qjkankan-components/src/components/dialog/index.js

@@ -0,0 +1,93 @@
+import Dialog from './Dialog'
+import Window from './Window'
+import Toast from './Toast'
+import Alert from './Alert'
+import Confirm from './Confirm'
+import DialogContent from './Dialog-content'
+import { mount } from '../../utils/componentHelper'
+
+Dialog.use = function use(app) {
+    Dialog.toast = function (options) {
+        if (typeof options == 'string') {
+            options = {
+                content: options,
+            }
+        }
+
+        const { destroy, vNode, el } = mount(Toast, {
+            app,
+            props: {
+                ...options,
+                destroy: () => destroy()
+            },
+        })
+
+        if (!Dialog.toast._destroys) {
+            Dialog.toast._destroys = []
+        }
+        Dialog.toast._destroys.push(destroy)
+
+        return {
+            hide: function () {
+                let destroy = null
+                while ((destroy = Dialog.toast._destroys.shift()) && destroy) {
+                    destroy()
+                }
+            }.bind(this),
+        }
+    }
+    Dialog.toast.hide = function () {
+        if (Dialog.toast._destroys && Dialog.toast._destroys.length) {
+            let destroy = Dialog.toast._destroys.pop()
+            destroy && destroy()
+        }
+    }
+    Dialog.alert = function (options) {
+        if (typeof options == 'string') {
+            options = {
+                content: options,
+            }
+        }
+
+        const { destroy } = mount(Alert, {
+            app,
+            props: { ...options, destroy: () => destroy() },
+        })
+
+        this.alert.hide = function () {
+            destroy()
+        }
+
+        return this.alert
+    }
+
+    Dialog.confirm = function (options) {
+        if (typeof options == 'string') {
+            options = {
+                content: options,
+            }
+        }
+
+        let promise
+        if (!options.func) {
+            promise = new Promise(resolve => {
+                options.func = result => resolve(result === 'ok')
+            })
+        }
+
+        const { destroy } = mount(Confirm, {
+            app,
+            props: { ...options, destroy: () => destroy() },
+        })
+
+        this.confirm.hide = function () {
+            destroy()
+        }
+
+        return promise || this.confirm
+    }
+}
+
+export { Window, Toast, Alert, Confirm, DialogContent }
+
+export default Dialog

+ 160 - 0
packages/qjkankan-components/src/components/floating/index.vue

@@ -0,0 +1,160 @@
+<template>
+    <teleport :to="mount">
+        <div class="ui-floating" :style="style" :class="props.class" @mouseenter="emit('enter')" @mouseleave="emit('leave')">
+            <slot />
+        </div>
+    </teleport>
+</template>
+
+<script setup>
+import { defineProps, defineExpose, onUnmounted, reactive, watch, computed, onUpdated, onActivated } from 'vue'
+import { getPostionByTarget, getScrollParents, getZIndex } from '../../utils'
+
+const Horizontal = {
+    center: 'center',
+    right: 'right',
+    left: 'left',
+}
+const Vertical = {
+    center: 'center',
+    top: 'top',
+    bottom: 'bottom',
+}
+const Divide = '-'
+
+const props = defineProps({
+    mount: {
+        require: true,
+        default: document.body,
+    },
+    class: { type: String },
+    refer: { type: Object },
+    dire: { type: String },
+    width: { type: [Number, String] },
+    height: { type: [Number, String] },
+})
+const emit = defineEmits(['leave', 'enter'])
+
+// 确定方向
+const dires = computed(() => {
+    const dire = props.dire || `${Vertical.bottom}${Divide}${Horizontal.left}`
+    const isPreset = (preset, val) => Object.keys(preset).some(key => preset[key] === val)
+
+    let [horizontal, vertical] = dire.split(Divide)
+
+    if (!horizontal || !isPreset(Horizontal, horizontal)) {
+        horizontal = Horizontal.left
+    }
+    if (!vertical || !isPreset(Vertical, vertical)) {
+        vertical = Vertical.bottom
+    }
+    return [horizontal, vertical]
+})
+
+const normalizeUnit = (unit, total) => {
+    if (unit === void 0) {
+        return void 0
+    } else if (typeof unit === 'number') {
+        return unit ? ((unit <= 1) & (unit >= 0) ? total * unit : unit) : void 0
+    } else if (unit.includes('px')) {
+        return normalizeUnit(parseFloat(unit), total)
+    } else if (unit.includes('%')) {
+        return normalizeUnit(parseFloat(unit) / 100, total)
+    }
+}
+
+const width = computed(() => props.refer && normalizeUnit(props.width, props.refer.offsetWidth))
+const height = computed(() => props.refer && normalizeUnit(props.height, props.refer.offsetHeight))
+
+const location = reactive({ x: 0, y: 0 })
+const scrollParents = computed(() => (props.refer ? getScrollParents(props.refer, props.mount) : []))
+
+watch(
+    [scrollParents, props],
+    ([newParents], [oldParents]) => {
+        oldParents && oldParents.forEach(dom => dom.removeEventListener('scroll', updateLocation))
+        newParents.forEach(dom => dom.addEventListener('scroll', updateLocation))
+        if (props.refer) {
+            setTimeout(() => updateLocation())
+        }
+    },
+    { immediate: true }
+)
+
+const zIndex = getZIndex()
+
+const style = computed(() => {
+    let style = {
+        width: width.value && width.value + 'px',
+        height: height.value && height.value + 'px',
+        left: location.x + 'px',
+        top: location.y + 'px',
+        zIndex: zIndex,
+    }
+    if (location.x > 0 && location.y > 0) {
+        return style
+    }
+})
+
+const updateLocation = () => {
+    const pos = getPostionByTarget(props.refer, props.mount)
+    const screenInfo = scrollParents.value.reduce(
+        (t, c) => {
+            t.y += c.scrollTop
+            t.x += c.scrollLeft
+            return t
+        },
+        { x: 0, y: 0 }
+    )
+
+    const [horizontal, vertical] = dires.value
+    const start = {
+        x: pos.x - screenInfo.x,
+        y: pos.y - screenInfo.y,
+    }
+
+    switch (horizontal) {
+        case Horizontal.left:
+            location.x = start.x
+            break
+        case Horizontal.right:
+            location.x = start.x + pos.width
+            break
+        case Horizontal.center:
+            location.x = start.x + pos.width / 2
+            break
+    }
+
+    switch (vertical) {
+        case Vertical.top:
+            location.y = start.y
+            break
+        case Vertical.bottom:
+            location.y = start.y + pos.height
+            break
+        case Vertical.center:
+            location.y = start.y + pos.height / 2
+            break
+    }
+}
+
+window.addEventListener('resize', updateLocation)
+onUnmounted(() => {
+    scrollParents.value.forEach(dom => dom.removeEventListener('scroll', updateLocation))
+    window.removeEventListener('resize', updateLocation)
+})
+
+onActivated(() => {
+    if (props.refer) {
+        updateLocation()
+    }
+})
+
+defineExpose({
+    updateLocation,
+})
+</script>
+
+<script>
+export default { name: 'ui-floating' }
+</script>

+ 1 - 0
packages/qjkankan-components/src/components/gate/constant.js

@@ -0,0 +1 @@
+export const Relation = Symbol('relation')

+ 32 - 0
packages/qjkankan-components/src/components/gate/content.vue

@@ -0,0 +1,32 @@
+<template>
+  <div class="ui-gate-content" :class="{active}" v-if="brotherInstances">
+    <slot :active="active" />
+  </div>
+</template>
+
+<script setup>
+import { 
+  onBeforeMount, 
+  ref, 
+  inject, 
+  getCurrentInstance, 
+  onUnmounted 
+} from 'vue'
+import { Relation } from './constant'
+
+const active = ref(false)
+const brotherInstances = inject(Relation).value
+
+if (brotherInstances) {
+  onBeforeMount(() => brotherInstances.push(active))
+  onUnmounted(() => {
+    const index = brotherInstances.indexOf(active)
+    if (~index) {
+      brotherInstances.splice(index, 1)
+    }
+  })
+}
+
+</script>
+
+<script> export default { name: 'ui-gate-content' } </script>

+ 4 - 0
packages/qjkankan-components/src/components/gate/index.js

@@ -0,0 +1,4 @@
+import Gate from './layer.vue'
+import GateContent from './content.vue'
+
+export { Gate, GateContent }

+ 59 - 0
packages/qjkankan-components/src/components/gate/layer.vue

@@ -0,0 +1,59 @@
+<template>
+  <div 
+    class="ui-gate-layer" 
+    :style="{
+      'height': normalizeUnitToStyle(height),
+      '--len': contentInstances.length, 
+      '--current': slideIndex,
+    }">
+    <div class="ui-gate-slides">
+      <slot />
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { 
+  ref, 
+  defineProps,
+  watchEffect,
+  computed,
+  provide,
+  watch
+} from 'vue'
+import { normalizeUnitToStyle } from '../../utils'
+import { Relation } from './constant'
+
+const contentInstances = ref([])
+const props = defineProps({
+  index: {
+    type: [Number, String],
+    default: 0
+  },
+  height: {
+    type: [Number, String]
+  }
+})
+
+const slideIndex = computed(
+  () => props.index > contentInstances.value.length - 1
+    ? contentInstances.value.length - 1
+    : props.index < 0
+      ? 0
+      : props.index
+)
+
+watch(
+  [contentInstances, slideIndex],
+  () => {
+    for (let i = 0; i < contentInstances.value.length; i++) {
+      const instance = contentInstances.value[i]
+      instance.value = i === slideIndex.value
+    }
+  }
+)
+
+provide(Relation, contentInstances)
+</script>
+
+<script> export default { name: 'ui-gate' } </script>

+ 1 - 0
packages/qjkankan-components/src/components/group/constant.js

@@ -0,0 +1 @@
+export const Relation = Symbol('group-children')

+ 4 - 0
packages/qjkankan-components/src/components/group/index.js

@@ -0,0 +1,4 @@
+import Group from './ui-group.vue'
+import GroupOption from './ui-group-option.vue'
+
+export { Group, GroupOption }

+ 32 - 0
packages/qjkankan-components/src/components/group/ui-group-option.vue

@@ -0,0 +1,32 @@
+<template>
+  <div class="group-option">
+    <span class="group-option-label" v-if="props.label">
+      {{ props.label }}
+    </span>
+    <slot />
+  </div>
+</template>
+
+<script setup>
+import { Relation } from './constant'
+import { inject, onBeforeMount, onUnmounted, getCurrentInstance } from 'vue'
+const props = defineProps({
+  label: String
+})
+
+const brotherInstances = inject(Relation)
+const instance = getCurrentInstance()
+
+if (brotherInstances.value) {
+  onBeforeMount(() => brotherInstances.value = [...brotherInstances.value, instance])
+  onUnmounted(() => {
+    const index = brotherInstances.value.indexOf(instance)
+    if (~index) {
+      brotherInstances.value.splice(index, 1)
+      brotherInstances.value = [...brotherInstances.value]
+    }
+  })
+}
+</script>
+
+<script> export default { name: 'ui-group-option' } </script>

+ 93 - 0
packages/qjkankan-components/src/components/group/ui-group.vue

@@ -0,0 +1,93 @@
+<template>
+  <div
+    class="ui-group" 
+    :class="{ control }">
+    <template v-if="!$slots.header">
+      <h3 
+        v-if="props.title" 
+        class="group-title" 
+        :class="!$slots.default && contentStyle">
+        {{props.title}}
+        <span 
+          class="group-icon" 
+          :class="animationRef && { show: animationRef.show }"
+          @click="control && animationRef.changeShow()"
+          v-if="$slots.icon || control">
+          <slot name="icon" v-if="$slots.icon"></slot>
+          <icon type="pull-down" size="12px" v-else />
+        </span>
+      </h3>
+    </template>
+    <div 
+      v-else
+      class="group-title" 
+      :class="!$slots.default && contentStyle">
+      <slot name="header" />
+
+      <span 
+          class="group-icon" 
+          :class="animationRef && { show: animationRef.show }"
+          @click="control && animationRef.changeShow()"
+          v-if="$slots.icon || control">
+          <slot name="icon" v-if="$slots.icon"></slot>
+          <icon type="pull-down" size="12px" v-else />
+        </span>
+    </div>
+
+    <template v-if="$slots.default">
+      <UISizeAnimation 
+        ref="animationRef"  
+        class="group-content" 
+        :class="contentStyle"
+        v-if="control">
+        <slot />
+      </UISizeAnimation>
+      <div 
+        class="group-content" 
+        :class="contentStyle"
+        v-else>
+        <slot />
+      </div>
+    </template>
+  </div>
+</template>
+
+
+<script setup>
+import icon from '../icon'
+import UISizeAnimation from '../size-animation'
+import { watchEffect, watch, ref, computed, provide } from 'vue'
+import { Relation } from './constant'
+
+const animationRef = ref(null)
+const props = defineProps({
+  title: String,
+  border: Boolean,
+  borderTop: Boolean,
+  borderBottom: Boolean,
+  control: Boolean,
+  show: Boolean
+})
+
+const contentStyle = computed(() => ({
+  'border-bottom': props.borderBottom || props.border, 
+  'border-top': props.borderTop || props.border
+}))
+
+
+const contentInstances = ref([])
+provide(Relation, contentInstances)
+
+watchEffect(() => {
+  if (animationRef.value) {
+    animationRef.value.changeShow(props.show)
+  }
+})
+
+watch(contentInstances, () => {
+  animationRef.value && animationRef.value.refer()
+})
+
+</script>
+
+<script> export default { name: 'ui-group' } </script>

+ 539 - 0
packages/qjkankan-components/src/components/icon/iconfont/demo.css

@@ -0,0 +1,539 @@
+/* Logo 字体 */
+@font-face {
+  font-family: "iconfont logo";
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
+  src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
+    url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
+}
+
+.logo {
+  font-family: "iconfont logo";
+  font-size: 160px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+/* tabs */
+.nav-tabs {
+  position: relative;
+}
+
+.nav-tabs .nav-more {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  height: 42px;
+  line-height: 42px;
+  color: #666;
+}
+
+#tabs {
+  border-bottom: 1px solid #eee;
+}
+
+#tabs li {
+  cursor: pointer;
+  width: 100px;
+  height: 40px;
+  line-height: 40px;
+  text-align: center;
+  font-size: 16px;
+  border-bottom: 2px solid transparent;
+  position: relative;
+  z-index: 1;
+  margin-bottom: -1px;
+  color: #666;
+}
+
+
+#tabs .active {
+  border-bottom-color: #f00;
+  color: #222;
+}
+
+.tab-container .content {
+  display: none;
+}
+
+/* 页面布局 */
+.main {
+  padding: 30px 100px;
+  width: 960px;
+  margin: 0 auto;
+}
+
+.main .logo {
+  color: #333;
+  text-align: left;
+  margin-bottom: 30px;
+  line-height: 1;
+  height: 110px;
+  margin-top: -50px;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.main .logo a {
+  font-size: 160px;
+  color: #333;
+}
+
+.helps {
+  margin-top: 40px;
+}
+
+.helps pre {
+  padding: 20px;
+  margin: 10px 0;
+  border: solid 1px #e7e1cd;
+  background-color: #fffdef;
+  overflow: auto;
+}
+
+.icon_lists {
+  width: 100% !important;
+  overflow: hidden;
+  *zoom: 1;
+}
+
+.icon_lists li {
+  width: 100px;
+  margin-bottom: 10px;
+  margin-right: 20px;
+  text-align: center;
+  list-style: none !important;
+  cursor: default;
+}
+
+.icon_lists li .code-name {
+  line-height: 1.2;
+}
+
+.icon_lists .icon {
+  display: block;
+  height: 100px;
+  line-height: 100px;
+  font-size: 42px;
+  margin: 10px auto;
+  color: #333;
+  -webkit-transition: font-size 0.25s linear, width 0.25s linear;
+  -moz-transition: font-size 0.25s linear, width 0.25s linear;
+  transition: font-size 0.25s linear, width 0.25s linear;
+}
+
+.icon_lists .icon:hover {
+  font-size: 100px;
+}
+
+.icon_lists .svg-icon {
+  /* 通过设置 font-size 来改变图标大小 */
+  width: 1em;
+  /* 图标和文字相邻时,垂直对齐 */
+  vertical-align: -0.15em;
+  /* 通过设置 color 来改变 SVG 的颜色/fill */
+  fill: currentColor;
+  /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
+      normalize.css 中也包含这行 */
+  overflow: hidden;
+}
+
+.icon_lists li .name,
+.icon_lists li .code-name {
+  color: #666;
+}
+
+/* markdown 样式 */
+.markdown {
+  color: #666;
+  font-size: 14px;
+  line-height: 1.8;
+}
+
+.highlight {
+  line-height: 1.5;
+}
+
+.markdown img {
+  vertical-align: middle;
+  max-width: 100%;
+}
+
+.markdown h1 {
+  color: #404040;
+  font-weight: 500;
+  line-height: 40px;
+  margin-bottom: 24px;
+}
+
+.markdown h2,
+.markdown h3,
+.markdown h4,
+.markdown h5,
+.markdown h6 {
+  color: #404040;
+  margin: 1.6em 0 0.6em 0;
+  font-weight: 500;
+  clear: both;
+}
+
+.markdown h1 {
+  font-size: 28px;
+}
+
+.markdown h2 {
+  font-size: 22px;
+}
+
+.markdown h3 {
+  font-size: 16px;
+}
+
+.markdown h4 {
+  font-size: 14px;
+}
+
+.markdown h5 {
+  font-size: 12px;
+}
+
+.markdown h6 {
+  font-size: 12px;
+}
+
+.markdown hr {
+  height: 1px;
+  border: 0;
+  background: #e9e9e9;
+  margin: 16px 0;
+  clear: both;
+}
+
+.markdown p {
+  margin: 1em 0;
+}
+
+.markdown>p,
+.markdown>blockquote,
+.markdown>.highlight,
+.markdown>ol,
+.markdown>ul {
+  width: 80%;
+}
+
+.markdown ul>li {
+  list-style: circle;
+}
+
+.markdown>ul li,
+.markdown blockquote ul>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown>ul li p,
+.markdown>ol li p {
+  margin: 0.6em 0;
+}
+
+.markdown ol>li {
+  list-style: decimal;
+}
+
+.markdown>ol li,
+.markdown blockquote ol>li {
+  margin-left: 20px;
+  padding-left: 4px;
+}
+
+.markdown code {
+  margin: 0 3px;
+  padding: 0 5px;
+  background: #eee;
+  border-radius: 3px;
+}
+
+.markdown strong,
+.markdown b {
+  font-weight: 600;
+}
+
+.markdown>table {
+  border-collapse: collapse;
+  border-spacing: 0px;
+  empty-cells: show;
+  border: 1px solid #e9e9e9;
+  width: 95%;
+  margin-bottom: 24px;
+}
+
+.markdown>table th {
+  white-space: nowrap;
+  color: #333;
+  font-weight: 600;
+}
+
+.markdown>table th,
+.markdown>table td {
+  border: 1px solid #e9e9e9;
+  padding: 8px 16px;
+  text-align: left;
+}
+
+.markdown>table th {
+  background: #F7F7F7;
+}
+
+.markdown blockquote {
+  font-size: 90%;
+  color: #999;
+  border-left: 4px solid #e9e9e9;
+  padding-left: 0.8em;
+  margin: 1em 0;
+}
+
+.markdown blockquote p {
+  margin: 0;
+}
+
+.markdown .anchor {
+  opacity: 0;
+  transition: opacity 0.3s ease;
+  margin-left: 8px;
+}
+
+.markdown .waiting {
+  color: #ccc;
+}
+
+.markdown h1:hover .anchor,
+.markdown h2:hover .anchor,
+.markdown h3:hover .anchor,
+.markdown h4:hover .anchor,
+.markdown h5:hover .anchor,
+.markdown h6:hover .anchor {
+  opacity: 1;
+  display: inline-block;
+}
+
+.markdown>br,
+.markdown>p>br {
+  clear: both;
+}
+
+
+.hljs {
+  display: block;
+  background: white;
+  padding: 0.5em;
+  color: #333333;
+  overflow-x: auto;
+}
+
+.hljs-comment,
+.hljs-meta {
+  color: #969896;
+}
+
+.hljs-string,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-strong,
+.hljs-emphasis,
+.hljs-quote {
+  color: #df5000;
+}
+
+.hljs-keyword,
+.hljs-selector-tag,
+.hljs-type {
+  color: #a71d5d;
+}
+
+.hljs-literal,
+.hljs-symbol,
+.hljs-bullet,
+.hljs-attribute {
+  color: #0086b3;
+}
+
+.hljs-section,
+.hljs-name {
+  color: #63a35c;
+}
+
+.hljs-tag {
+  color: #333333;
+}
+
+.hljs-title,
+.hljs-attr,
+.hljs-selector-id,
+.hljs-selector-class,
+.hljs-selector-attr,
+.hljs-selector-pseudo {
+  color: #795da3;
+}
+
+.hljs-addition {
+  color: #55a532;
+  background-color: #eaffea;
+}
+
+.hljs-deletion {
+  color: #bd2c00;
+  background-color: #ffecec;
+}
+
+.hljs-link {
+  text-decoration: underline;
+}
+
+/* 代码高亮 */
+/* PrismJS 1.15.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+code[class*="language-"],
+pre[class*="language-"] {
+  color: black;
+  background: none;
+  text-shadow: 0 1px white;
+  font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+  text-align: left;
+  white-space: pre;
+  word-spacing: normal;
+  word-break: normal;
+  word-wrap: normal;
+  line-height: 1.5;
+
+  -moz-tab-size: 4;
+  -o-tab-size: 4;
+  tab-size: 4;
+
+  -webkit-hyphens: none;
+  -moz-hyphens: none;
+  -ms-hyphens: none;
+  hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection,
+pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection,
+code[class*="language-"] ::-moz-selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection,
+pre[class*="language-"] ::selection,
+code[class*="language-"]::selection,
+code[class*="language-"] ::selection {
+  text-shadow: none;
+  background: #b3d4fc;
+}
+
+@media print {
+
+  code[class*="language-"],
+  pre[class*="language-"] {
+    text-shadow: none;
+  }
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+  padding: 1em;
+  margin: .5em 0;
+  overflow: auto;
+}
+
+:not(pre)>code[class*="language-"],
+pre[class*="language-"] {
+  background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre)>code[class*="language-"] {
+  padding: .1em;
+  border-radius: .3em;
+  white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+  color: slategray;
+}
+
+.token.punctuation {
+  color: #999;
+}
+
+.namespace {
+  opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+  color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+  color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+  color: #9a6e3a;
+  background: hsla(0, 0%, 100%, .5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+  color: #07a;
+}
+
+.token.function,
+.token.class-name {
+  color: #DD4A68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+  color: #e90;
+}
+
+.token.important,
+.token.bold {
+  font-weight: bold;
+}
+
+.token.italic {
+  font-style: italic;
+}
+
+.token.entity {
+  cursor: help;
+}

Разница между файлами не показана из-за своего большого размера
+ 1913 - 0
packages/qjkankan-components/src/components/icon/iconfont/demo_index.html


+ 315 - 0
packages/qjkankan-components/src/components/icon/iconfont/iconfont.css

@@ -0,0 +1,315 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 2930899 */
+  src: url('iconfont.woff2?t=1639107133391') format('woff2'),
+       url('iconfont.woff?t=1639107133391') format('woff'),
+       url('iconfont.ttf?t=1639107133391') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-destination:before {
+  content: "\e678";
+}
+
+.icon-origin:before {
+  content: "\e679";
+}
+
+.icon-state_e:before {
+  content: "\e624";
+}
+
+.icon-state_f:before {
+  content: "\e625";
+}
+
+.icon-state_s:before {
+  content: "\e626";
+}
+
+.icon-clear:before {
+  content: "\e676";
+}
+
+.icon-cut:before {
+  content: "\e677";
+}
+
+.icon-copy:before {
+  content: "\e675";
+}
+
+.icon-v-m:before {
+  content: "\e674";
+}
+
+.icon-f-m:before {
+  content: "\e671";
+}
+
+.icon-v-r:before {
+  content: "\e672";
+}
+
+.icon-f-l:before {
+  content: "\e673";
+}
+
+.icon-h-l:before {
+  content: "\e66d";
+}
+
+.icon-h-m:before {
+  content: "\e66e";
+}
+
+.icon-v-l:before {
+  content: "\e66f";
+}
+
+.icon-h-r:before {
+  content: "\e670";
+}
+
+.icon-share:before {
+  content: "\e66c";
+}
+
+.icon-magnify:before {
+  content: "\e66b";
+}
+
+.icon-a-2d:before {
+  content: "\e669";
+}
+
+.icon-a-3d:before {
+  content: "\e66a";
+}
+
+.icon-left:before {
+  content: "\e668";
+}
+
+.icon-video:before {
+  content: "\e667";
+}
+
+.icon-pic:before {
+  content: "\e64e";
+}
+
+.icon-full:before {
+  content: "\e638";
+}
+
+.icon-del:before {
+  content: "\e632";
+}
+
+.icon-link:before {
+  content: "\e618";
+}
+
+.icon-uploading:before {
+  content: "\e619";
+}
+
+.icon-complete:before {
+  content: "\e61b";
+}
+
+.icon-web:before {
+  content: "\e635";
+}
+
+.icon-music:before {
+  content: "\e637";
+}
+
+.icon-element:before {
+  content: "\e666";
+}
+
+.icon-add:before {
+  content: "\e631";
+}
+
+.icon-edit:before {
+  content: "\e61f";
+}
+
+.icon-info:before {
+  content: "\e65e";
+}
+
+.icon-transmit:before {
+  content: "\e65f";
+}
+
+.icon-room:before {
+  content: "\e660";
+}
+
+.icon-point:before {
+  content: "\e661";
+}
+
+.icon-bulid:before {
+  content: "\e662";
+}
+
+.icon-floor:before {
+  content: "\e663";
+}
+
+.icon-pull-up1:before {
+  content: "\e664";
+}
+
+.icon-pull-down1:before {
+  content: "\e665";
+}
+
+.icon-checkbox1:before {
+  content: "\e65d";
+}
+
+.icon-minimize:before {
+  content: "\e65c";
+}
+
+.icon-reset:before {
+  content: "\e65a";
+}
+
+.icon-refresh:before {
+  content: "\e65b";
+}
+
+.icon-checkbox:before {
+  content: "\e659";
+}
+
+.icon-pull-down:before {
+  content: "\e61d";
+}
+
+.icon-pull-up:before {
+  content: "\e61e";
+}
+
+.icon-show_pic_s:before {
+  content: "\e658";
+}
+
+.icon-show_pic_n:before {
+  content: "\e650";
+}
+
+.icon-show_dot_s:before {
+  content: "\e64f";
+}
+
+.icon-show_dot_n:before {
+  content: "\e657";
+}
+
+.icon-share1:before {
+  content: "\e656";
+}
+
+.icon-portrait:before {
+  content: "\e655";
+}
+
+.icon-course:before {
+  content: "\e652";
+}
+
+.icon-self-more:before {
+  content: "\e64b";
+}
+
+.icon-search:before {
+  content: "\e64c";
+}
+
+.icon-pull-more:before {
+  content: "\e64d";
+}
+
+.icon-switch:before {
+  content: "\e651";
+}
+
+.icon-nav-hotspot:before {
+  content: "\e64a";
+}
+
+.icon-nav-measure:before {
+  content: "\e649";
+}
+
+.icon-nav-edit:before {
+  content: "\e642";
+}
+
+.icon-nav-setup:before {
+  content: "\e648";
+}
+
+.icon-close:before {
+  content: "\e633";
+}
+
+.icon-nav-browse:before {
+  content: "\e63d";
+}
+
+.icon-nav-correct:before {
+  content: "\e63e";
+}
+
+.icon-nav_data-setup:before {
+  content: "\e63f";
+}
+
+.icon-nav-coord:before {
+  content: "\e641";
+}
+
+.icon-nav-geography:before {
+  content: "\e643";
+}
+
+.icon-nav-space:before {
+  content: "\e644";
+}
+
+.icon-nav-download:before {
+  content: "\e645";
+}
+
+.icon-nav-data:before {
+  content: "\e646";
+}
+
+.icon-nav-house:before {
+  content: "\e647";
+}
+
+.icon-eye-s:before {
+  content: "\e653";
+}
+
+.icon-eye-n:before {
+  content: "\e654";
+}
+

Разница между файлами не показана из-за своего большого размера
+ 60 - 0
packages/qjkankan-components/src/components/icon/iconfont/iconfont.js


+ 534 - 0
packages/qjkankan-components/src/components/icon/iconfont/iconfont.json

@@ -0,0 +1,534 @@
+{
+  "id": "2930899",
+  "name": "激光编辑器",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon-",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "26384825",
+      "name": "destination",
+      "font_class": "destination",
+      "unicode": "e678",
+      "unicode_decimal": 59000
+    },
+    {
+      "icon_id": "26384826",
+      "name": "origin",
+      "font_class": "origin",
+      "unicode": "e679",
+      "unicode_decimal": 59001
+    },
+    {
+      "icon_id": "22132762",
+      "name": "state_e",
+      "font_class": "state_e",
+      "unicode": "e624",
+      "unicode_decimal": 58916
+    },
+    {
+      "icon_id": "22132763",
+      "name": "state_f",
+      "font_class": "state_f",
+      "unicode": "e625",
+      "unicode_decimal": 58917
+    },
+    {
+      "icon_id": "22132764",
+      "name": "state_s",
+      "font_class": "state_s",
+      "unicode": "e626",
+      "unicode_decimal": 58918
+    },
+    {
+      "icon_id": "26198013",
+      "name": "clear",
+      "font_class": "clear",
+      "unicode": "e676",
+      "unicode_decimal": 58998
+    },
+    {
+      "icon_id": "26198014",
+      "name": "cut",
+      "font_class": "cut",
+      "unicode": "e677",
+      "unicode_decimal": 58999
+    },
+    {
+      "icon_id": "26189441",
+      "name": "copy",
+      "font_class": "copy",
+      "unicode": "e675",
+      "unicode_decimal": 58997
+    },
+    {
+      "icon_id": "26077357",
+      "name": "v-m",
+      "font_class": "v-m",
+      "unicode": "e674",
+      "unicode_decimal": 58996
+    },
+    {
+      "icon_id": "26077354",
+      "name": "f-m",
+      "font_class": "f-m",
+      "unicode": "e671",
+      "unicode_decimal": 58993
+    },
+    {
+      "icon_id": "26077355",
+      "name": "v-r",
+      "font_class": "v-r",
+      "unicode": "e672",
+      "unicode_decimal": 58994
+    },
+    {
+      "icon_id": "26077356",
+      "name": "f-l",
+      "font_class": "f-l",
+      "unicode": "e673",
+      "unicode_decimal": 58995
+    },
+    {
+      "icon_id": "26077350",
+      "name": "h-l",
+      "font_class": "h-l",
+      "unicode": "e66d",
+      "unicode_decimal": 58989
+    },
+    {
+      "icon_id": "26077351",
+      "name": "h-m",
+      "font_class": "h-m",
+      "unicode": "e66e",
+      "unicode_decimal": 58990
+    },
+    {
+      "icon_id": "26077352",
+      "name": "v-l",
+      "font_class": "v-l",
+      "unicode": "e66f",
+      "unicode_decimal": 58991
+    },
+    {
+      "icon_id": "26077353",
+      "name": "h-r",
+      "font_class": "h-r",
+      "unicode": "e670",
+      "unicode_decimal": 58992
+    },
+    {
+      "icon_id": "26077297",
+      "name": "share",
+      "font_class": "share",
+      "unicode": "e66c",
+      "unicode_decimal": 58988
+    },
+    {
+      "icon_id": "26077227",
+      "name": "magnify",
+      "font_class": "magnify",
+      "unicode": "e66b",
+      "unicode_decimal": 58987
+    },
+    {
+      "icon_id": "26077202",
+      "name": "2d",
+      "font_class": "a-2d",
+      "unicode": "e669",
+      "unicode_decimal": 58985
+    },
+    {
+      "icon_id": "26077203",
+      "name": "3d",
+      "font_class": "a-3d",
+      "unicode": "e66a",
+      "unicode_decimal": 58986
+    },
+    {
+      "icon_id": "26008932",
+      "name": "left",
+      "font_class": "left",
+      "unicode": "e668",
+      "unicode_decimal": 58984
+    },
+    {
+      "icon_id": "26008166",
+      "name": "video",
+      "font_class": "video",
+      "unicode": "e667",
+      "unicode_decimal": 58983
+    },
+    {
+      "icon_id": "23786363",
+      "name": "pic",
+      "font_class": "pic",
+      "unicode": "e64e",
+      "unicode_decimal": 58958
+    },
+    {
+      "icon_id": "23773141",
+      "name": "full",
+      "font_class": "full",
+      "unicode": "e638",
+      "unicode_decimal": 58936
+    },
+    {
+      "icon_id": "23773069",
+      "name": "del",
+      "font_class": "del",
+      "unicode": "e632",
+      "unicode_decimal": 58930
+    },
+    {
+      "icon_id": "22099479",
+      "name": "link",
+      "font_class": "link",
+      "unicode": "e618",
+      "unicode_decimal": 58904
+    },
+    {
+      "icon_id": "22099480",
+      "name": "uploading",
+      "font_class": "uploading",
+      "unicode": "e619",
+      "unicode_decimal": 58905
+    },
+    {
+      "icon_id": "22099484",
+      "name": "complete",
+      "font_class": "complete",
+      "unicode": "e61b",
+      "unicode_decimal": 58907
+    },
+    {
+      "icon_id": "23773072",
+      "name": "web",
+      "font_class": "web",
+      "unicode": "e635",
+      "unicode_decimal": 58933
+    },
+    {
+      "icon_id": "23773074",
+      "name": "music",
+      "font_class": "music",
+      "unicode": "e637",
+      "unicode_decimal": 58935
+    },
+    {
+      "icon_id": "25764812",
+      "name": "element",
+      "font_class": "element",
+      "unicode": "e666",
+      "unicode_decimal": 58982
+    },
+    {
+      "icon_id": "23773068",
+      "name": "add",
+      "font_class": "add",
+      "unicode": "e631",
+      "unicode_decimal": 58929
+    },
+    {
+      "icon_id": "22099525",
+      "name": "edit",
+      "font_class": "edit",
+      "unicode": "e61f",
+      "unicode_decimal": 58911
+    },
+    {
+      "icon_id": "25713499",
+      "name": "info",
+      "font_class": "info",
+      "unicode": "e65e",
+      "unicode_decimal": 58974
+    },
+    {
+      "icon_id": "25713500",
+      "name": "transmit",
+      "font_class": "transmit",
+      "unicode": "e65f",
+      "unicode_decimal": 58975
+    },
+    {
+      "icon_id": "25713501",
+      "name": "room",
+      "font_class": "room",
+      "unicode": "e660",
+      "unicode_decimal": 58976
+    },
+    {
+      "icon_id": "25713502",
+      "name": "point",
+      "font_class": "point",
+      "unicode": "e661",
+      "unicode_decimal": 58977
+    },
+    {
+      "icon_id": "25713503",
+      "name": "bulid",
+      "font_class": "bulid",
+      "unicode": "e662",
+      "unicode_decimal": 58978
+    },
+    {
+      "icon_id": "25713504",
+      "name": "floor",
+      "font_class": "floor",
+      "unicode": "e663",
+      "unicode_decimal": 58979
+    },
+    {
+      "icon_id": "25713602",
+      "name": "pull-up",
+      "font_class": "pull-up1",
+      "unicode": "e664",
+      "unicode_decimal": 58980
+    },
+    {
+      "icon_id": "25713603",
+      "name": "pull-down",
+      "font_class": "pull-down1",
+      "unicode": "e665",
+      "unicode_decimal": 58981
+    },
+    {
+      "icon_id": "25671886",
+      "name": "checkbox",
+      "font_class": "checkbox1",
+      "unicode": "e65d",
+      "unicode_decimal": 58973
+    },
+    {
+      "icon_id": "25655073",
+      "name": "minimize",
+      "font_class": "minimize",
+      "unicode": "e65c",
+      "unicode_decimal": 58972
+    },
+    {
+      "icon_id": "25654903",
+      "name": "reset",
+      "font_class": "reset",
+      "unicode": "e65a",
+      "unicode_decimal": 58970
+    },
+    {
+      "icon_id": "25654904",
+      "name": "refresh",
+      "font_class": "refresh",
+      "unicode": "e65b",
+      "unicode_decimal": 58971
+    },
+    {
+      "icon_id": "25654199",
+      "name": "checkbox",
+      "font_class": "checkbox",
+      "unicode": "e659",
+      "unicode_decimal": 58969
+    },
+    {
+      "icon_id": "22099518",
+      "name": "pull-down",
+      "font_class": "pull-down",
+      "unicode": "e61d",
+      "unicode_decimal": 58909
+    },
+    {
+      "icon_id": "22099519",
+      "name": "pull-up",
+      "font_class": "pull-up",
+      "unicode": "e61e",
+      "unicode_decimal": 58910
+    },
+    {
+      "icon_id": "25633777",
+      "name": "show_pic_s",
+      "font_class": "show_pic_s",
+      "unicode": "e658",
+      "unicode_decimal": 58968
+    },
+    {
+      "icon_id": "25633765",
+      "name": "show_pic_n",
+      "font_class": "show_pic_n",
+      "unicode": "e650",
+      "unicode_decimal": 58960
+    },
+    {
+      "icon_id": "25633715",
+      "name": "show_dot_s",
+      "font_class": "show_dot_s",
+      "unicode": "e64f",
+      "unicode_decimal": 58959
+    },
+    {
+      "icon_id": "25633717",
+      "name": "show_dot_n",
+      "font_class": "show_dot_n",
+      "unicode": "e657",
+      "unicode_decimal": 58967
+    },
+    {
+      "icon_id": "25632561",
+      "name": "share",
+      "font_class": "share1",
+      "unicode": "e656",
+      "unicode_decimal": 58966
+    },
+    {
+      "icon_id": "25631621",
+      "name": "portrait",
+      "font_class": "portrait",
+      "unicode": "e655",
+      "unicode_decimal": 58965
+    },
+    {
+      "icon_id": "25631501",
+      "name": "course",
+      "font_class": "course",
+      "unicode": "e652",
+      "unicode_decimal": 58962
+    },
+    {
+      "icon_id": "25631463",
+      "name": "self-more",
+      "font_class": "self-more",
+      "unicode": "e64b",
+      "unicode_decimal": 58955
+    },
+    {
+      "icon_id": "25631464",
+      "name": "search",
+      "font_class": "search",
+      "unicode": "e64c",
+      "unicode_decimal": 58956
+    },
+    {
+      "icon_id": "25631466",
+      "name": "pull-more",
+      "font_class": "pull-more",
+      "unicode": "e64d",
+      "unicode_decimal": 58957
+    },
+    {
+      "icon_id": "25631470",
+      "name": "switch",
+      "font_class": "switch",
+      "unicode": "e651",
+      "unicode_decimal": 58961
+    },
+    {
+      "icon_id": "25631455",
+      "name": "nav-hotspot",
+      "font_class": "nav-hotspot",
+      "unicode": "e64a",
+      "unicode_decimal": 58954
+    },
+    {
+      "icon_id": "25631400",
+      "name": "nav-measure",
+      "font_class": "nav-measure",
+      "unicode": "e649",
+      "unicode_decimal": 58953
+    },
+    {
+      "icon_id": "25631122",
+      "name": "nav-edit",
+      "font_class": "nav-edit",
+      "unicode": "e642",
+      "unicode_decimal": 58946
+    },
+    {
+      "icon_id": "25631133",
+      "name": "nav-setup",
+      "font_class": "nav-setup",
+      "unicode": "e648",
+      "unicode_decimal": 58952
+    },
+    {
+      "icon_id": "23773070",
+      "name": "close",
+      "font_class": "close",
+      "unicode": "e633",
+      "unicode_decimal": 58931
+    },
+    {
+      "icon_id": "25629542",
+      "name": "nav-browse",
+      "font_class": "nav-browse",
+      "unicode": "e63d",
+      "unicode_decimal": 58941
+    },
+    {
+      "icon_id": "25629543",
+      "name": "nav-correct",
+      "font_class": "nav-correct",
+      "unicode": "e63e",
+      "unicode_decimal": 58942
+    },
+    {
+      "icon_id": "25629544",
+      "name": "nav_data-setup",
+      "font_class": "nav_data-setup",
+      "unicode": "e63f",
+      "unicode_decimal": 58943
+    },
+    {
+      "icon_id": "25629546",
+      "name": "nav-coord",
+      "font_class": "nav-coord",
+      "unicode": "e641",
+      "unicode_decimal": 58945
+    },
+    {
+      "icon_id": "25629548",
+      "name": "nav-geography",
+      "font_class": "nav-geography",
+      "unicode": "e643",
+      "unicode_decimal": 58947
+    },
+    {
+      "icon_id": "25629549",
+      "name": "nav-space",
+      "font_class": "nav-space",
+      "unicode": "e644",
+      "unicode_decimal": 58948
+    },
+    {
+      "icon_id": "25629550",
+      "name": "nav-download",
+      "font_class": "nav-download",
+      "unicode": "e645",
+      "unicode_decimal": 58949
+    },
+    {
+      "icon_id": "25629551",
+      "name": "nav-data",
+      "font_class": "nav-data",
+      "unicode": "e646",
+      "unicode_decimal": 58950
+    },
+    {
+      "icon_id": "25629552",
+      "name": "nav-house",
+      "font_class": "nav-house",
+      "unicode": "e647",
+      "unicode_decimal": 58951
+    },
+    {
+      "icon_id": "22930372",
+      "name": "eye-s",
+      "font_class": "eye-s",
+      "unicode": "e653",
+      "unicode_decimal": 58963
+    },
+    {
+      "icon_id": "22930373",
+      "name": "eye-n",
+      "font_class": "eye-n",
+      "unicode": "e654",
+      "unicode_decimal": 58964
+    }
+  ]
+}

BIN
packages/qjkankan-components/src/components/icon/iconfont/iconfont.ttf


BIN
packages/qjkankan-components/src/components/icon/iconfont/iconfont.woff


BIN
packages/qjkankan-components/src/components/icon/iconfont/iconfont.woff2


+ 66 - 0
packages/qjkankan-components/src/components/icon/index.vue

@@ -0,0 +1,66 @@
+<template>
+    <i class="iconfont ui-kankan-icon icon" :class="className" :style="style" @click="ev => emit('click', ev)">
+        <slot></slot>
+
+        <p class="tip" v-if="tip && os.isPc && !os.isTablet">{{ tip }}</p>
+    </i>
+</template>
+
+<script setup>
+import { defineProps, computed, defineEmits } from 'vue'
+import { normalizeUnitToStyle, os } from '../../utils'
+
+const props = defineProps({
+    type: { type: String },
+    size: { type: [Number, String] },
+    color: { type: String },
+    small: { type: Boolean },
+    ctrl: { type: Boolean },
+    medium: { type: Boolean },
+    big: { type: Boolean },
+    disabled: { type: Boolean },
+    tip: { type: String },
+    tipH: {
+        type: String,
+        default: 'center',
+    },
+    tipV: {
+        type: String,
+        default: 'bottom',
+    },
+})
+
+const style = computed(() => ({
+    'font-size': normalizeUnitToStyle(props.size),
+    color: props.color,
+}))
+const className = computed(() => {
+    const base = {
+        small: props.small,
+        medium: props.medium,
+        big: props.big,
+        disabled: props.disabled,
+        [`tip-h-` + props.tipH]: true,
+        [`tip-v-` + props.tipV]: true,
+        ['fun-ctrl']: props.ctrl,
+    }
+    if (props.type) {
+        return {
+            ...base,
+            [`icon-${props.type}`]: props.type,
+            [`iconzm-${props.type}`]: props.type,
+        }
+    } else {
+        return base
+    }
+})
+
+const emit = defineEmits(['click'])
+</script>
+
+<style>
+/* @import url('./iconfont/iconfont.css'); */
+</style>
+<script>
+export default { name: 'ui-icon' }
+</script>

+ 30 - 0
packages/qjkankan-components/src/components/input/check-radio.vue

@@ -0,0 +1,30 @@
+<template>
+  <div class="input radio" :style="{ width, height }">
+    <input 
+      :disabled="disabled"
+      :id="id"
+      type="checkbox" 
+      :checked="props.modelValue"
+      @input="ev => emit('update:modelValue', ev.target.checked)"
+    >
+    <span class="replace">
+      <icon 
+        type="checkbox" 
+        :size="width > height ? height: width" 
+      />
+    </span>
+  </div>
+  <label class="label" v-if="props.label" :for="id">
+    {{ props.label }}
+  </label>
+</template>
+
+<script setup>
+import icon from '../icon'
+import { checkboxPropsDesc } from './state'
+import { randomId } from '../../utils'
+import { defineProps, defineEmits } from 'vue'
+const props = defineProps(checkboxPropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const id = randomId(4)
+</script>

+ 21 - 0
packages/qjkankan-components/src/components/input/checkbox.vue

@@ -0,0 +1,21 @@
+<template>
+    <div class="input checkbox" :style="{ width, height }" :class="{ disabled }">
+        <input :disabled="disabled" :id="id" type="checkbox" class="replace-input" :checked="props.modelValue" @input="ev => emit('update:modelValue', ev.target.checked)" />
+        <span class="replace">
+            <icon :type="props.modelValue ? 'checkbox' : 'nor'" :size="width > height ? height : width" />
+        </span>
+    </div>
+    <label class="label" v-if="props.label" :for="id">
+        {{ props.label }}
+    </label>
+</template>
+
+<script setup>
+import icon from '../icon'
+import { checkboxPropsDesc } from './state'
+import { randomId } from '../../utils'
+import { defineProps, defineEmits } from 'vue'
+const props = defineProps(checkboxPropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const id = randomId(4)
+</script>

+ 199 - 0
packages/qjkankan-components/src/components/input/file.vue

@@ -0,0 +1,199 @@
+<template>
+    <div class="input file" :class="{ suffix: $slots.icon, disabled: disabled, valuable }">
+        <template v-if="valuable">
+            <slot name="valuable" :key="modelValue" />
+        </template>
+        <input title="" class="ui-text" type="file" ref="inputRef" :accept="accept" :multiple="multiple" @change="selectFileHandler" v-if="!maxLen || maxLen > modelValue.length" />
+        <template v-if="!$slots.replace">
+            <span class="replace">
+                <div class="placeholder" v-if="!valuable">
+                    <p><ui-icon type="add" /></p>
+                    <p>{{ placeholder }}</p>
+                    <p class="bottom">
+                        <template v-if="!othPlaceholder">
+                            <template v-if="accept">支持 {{ accept }} 等格式,</template>
+                            <template v-if="normalizeScale">宽*高比例 {{ scale }},</template>
+                            <template v-if="maxSize">大小不超过 {{ sizeStr }}{{ maxLen ? ',' : '' }}</template>
+                            <template v-if="maxLen">个数不超过 {{ maxLen }}个</template>
+                        </template>
+                        <template v-else>
+                            {{ othPlaceholder }}
+                        </template>
+                    </p>
+                </div>
+
+                <span v-else v-if="!maxLen || maxLen > modelValue.length">
+                    {{ multiple ? '继续添加' : '替换' }}
+                </span>
+                <span class="tj" v-if="maxLen && modelValue.length">
+                    <span>{{ modelValue.length || 0 }}</span> / {{ maxLen }}
+                </span>
+            </span>
+        </template>
+        <div class="use-replace" v-else>
+            <slot name="replace" />
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { filePropsDesc } from './state'
+import { toRawType } from '../../utils'
+import Message from '../message'
+import Dialog from '../dialog'
+import { defineProps, defineEmits, defineExpose, ref, computed } from 'vue'
+const props = defineProps({
+    ...filePropsDesc,
+})
+const emit = defineEmits(['update:modelValue'])
+const inputRef = ref(null)
+const normalizeScale = computed(() => {
+    if (props.scale) {
+        const [w, h] = props.scale.split(':')
+        if (Number(w) && Number(h)) {
+            return [Number(w), Number(h)]
+        }
+    }
+})
+
+const valuable = computed(() => (Array.isArray(props.modelValue) ? props.modelValue.length : !!props.modelValue))
+const sizeStr = computed(() => props.maxSize && props.maxSize / 1024 / 1024 + 'MB')
+
+const supports = {
+    image: {
+        types: ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'],
+        preview(file, url) {
+            return new Promise((resolve, reject) => {
+                const img = new Image()
+                img.onload = () => resolve([img.width, img.height, file])
+                img.onerror = reject
+                img.src = url
+            })
+        },
+    },
+    video: {
+        types: ['video/mp4'],
+        preview(file, url) {
+            return new Promise((resolve, reject) => {
+                const video = document.createElement('video')
+                video.preload = 'metadata'
+                video.onloadedmetadata = () => resolve([video.videoWidth, video.videoHeight, file])
+                video.onerror = reject
+                video.src = url
+            })
+        },
+    },
+}
+
+const producePreviews = files =>
+    Promise.all(
+        files.map(
+            file =>
+                new Promise((resolve, reject) => {
+                    const fr = new FileReader()
+                    fr.onloadend = e => resolve(e.target.result)
+                    fr.onerror = e => loaderror(file, reject(e))
+                    fr.readAsDataURL(file)
+                })
+        )
+    )
+
+const calcScale = (w, h) => parseInt((w / h) * 1000)
+
+const selectFileHandler = async ev => {
+    const fileEl = ev.target
+    const files = Array.from(fileEl.files)
+    const previewError = (e, msg = `预览加载失败!`) => {
+        console.error(e)
+        // Message.error(msg)
+        Dialog.toast({
+            content: msg,
+            type: 'error',
+        })
+        fileEl.value = ''
+    }
+
+    if (props.accept) {
+        for (const file of files) {
+            const accepts = props.accept.split(',').map(atom => {
+                return atom.trim()
+            })
+            const hname = file.name.substr(file.name.lastIndexOf('.')).toLowerCase()
+            if (!accepts.includes(hname)) {
+                // return previewError('格式错误', `仅支持${props.accept}格式文件`)
+                let accept = props.accept.split('.').join('').replace(',', '/').toLowerCase()
+                return previewError('格式错误', `${t('limit.formFile', { form: accept })}`)
+            }
+        }
+    }
+
+    let previews
+    if (props.preview || normalizeScale.value) {
+        try {
+            previews = await producePreviews(files)
+        } catch (e) {
+            return previewError(e)
+        }
+    }
+
+    if (normalizeScale.value) {
+        const sizesConfirm = []
+        for (let i = 0; i < files.length; i++) {
+            const support = Object.values(supports).find(support => support.types.includes(files[i].type))
+            if (support) {
+                sizesConfirm.push(support.preview(files[i], previews[i]))
+            }
+        }
+
+        let sizes
+        try {
+            sizes = await Promise.all(sizesConfirm)
+        } catch (e) {
+            return previewError(e)
+        }
+
+        for (const [w, h, file] of sizes) {
+            const scaleDiff = calcScale(...normalizeScale.value) - calcScale(w, h)
+
+            if (Math.abs(scaleDiff) > 300) {
+                // return previewError('error scale', `${file.name}的比例部位不为${props.scale}`)
+                return previewError('error scale', `${t('limit.scaleFile', { fileName: file.name, scale: props.scale })}`)
+            }
+        }
+    }
+
+    if (props.maxSize) {
+        for (const file of files) {
+            if (file.size > props.maxSize) {
+                // return previewError('error size', `${file.name}的大小超过${sizeStr.value}`)
+                // return previewError('error size', `${file.name}的大小超过${sizeStr.value}`)
+
+                let accept = props.accept.split('.').join('').replace(',', '/').toLowerCase()
+                return previewError('格式错误', `${t('limit.formSize', { size: sizeStr.value, form: accept })}`)
+            }
+        }
+    }
+
+    const value = props.modelValue ? (props.multiple ? (toRawType(props.modelValue) === 'Array' ? props.modelValue : [props.modelValue]) : null) : props.multiple ? [] : null
+
+    const emitData = props.multiple
+        ? props.preview
+            ? [...value, ...files.map((file, i) => ({ file, preview: previews[i] }))]
+            : [...value, files]
+        : props.preview
+        ? { file: files[0], preview: previews[0] }
+        : files[0]
+
+    if (Array.isArray(emitData) && props.maxLen && emitData.length > props.maxLen) {
+        // return previewError('err len', `最多仅支持${props.maxLen}个文件!`)
+        return previewError('err len', `${t('limit.maxLengthFile', { length: props.maxLen })}`)
+    }
+
+    emit('update:modelValue', emitData)
+    fileEl.value = ''
+}
+
+defineExpose({
+    input: inputRef,
+})
+</script>

+ 136 - 0
packages/qjkankan-components/src/components/input/index.vue

@@ -0,0 +1,136 @@
+<template>
+  <div class="ui-input" 
+    v-if="types[type]" 
+    :style="style" 
+    :class="{ require: props.require, error: props.error }">
+    <component 
+      :is="types[type].component" 
+      v-bind="childProps"
+      :modelValue="props.modelValue"
+      @update:modelValue="newValue => emit('update:modelValue', newValue)"
+    >
+      <template v-for="(slot, name) in $slots" v-slot:[name]="raw">
+        <slot :name="name" v-bind="raw" />
+      </template>
+    </component>
+    <slot />
+
+    <p class="error-msg" v-if="error">{{error}}</p>
+  </div>
+</template>
+
+
+<script setup>
+import { computed } from 'vue'
+import radio from './radio.vue'
+import checkbox from './checkbox.vue'
+import text from './text.vue'
+import select from './select.vue'
+import range from './range.vue'
+import textarea from './textarea.vue'
+import number from './number.vue'
+import uiSwitch from './switch.vue'
+import file from './file.vue'
+import search from './search.vue'
+import richtext from './richtext.vue'
+import {
+  inputPropsDesc, 
+  textPropsDesc, 
+  selectPropsDesc, 
+  checkboxPropsDesc,
+  radioPropsDesc,
+  rangePropsDesc,
+  numberPropsDesc,
+  switchPropsDesc,
+  textareaPropsDesc,
+  filePropsDesc,
+  searchPropsDesc,
+  richtextPropsDesc
+} from './state'
+
+const types = { 
+  checkbox: {
+    component: checkbox,
+    propsDesc: checkboxPropsDesc
+  }, 
+  text: {
+    component: text,
+    propsDesc: textPropsDesc
+  }, 
+  select: {
+    component: select,
+    propsDesc: selectPropsDesc
+  },
+  radio: {
+    component: radio,
+    propsDesc: radioPropsDesc
+  },
+  range: {
+    component: range,
+    propsDesc: rangePropsDesc
+  },
+  number: {
+    component: number,
+    propsDesc: numberPropsDesc
+  },
+  switch: {
+    component: uiSwitch,
+    propsDesc: switchPropsDesc
+  },
+  textarea: {
+    component: textarea,
+    propsDesc: textareaPropsDesc
+  },
+  file: {
+    component: file,
+    propsDesc: filePropsDesc
+    
+  },
+  search: {
+    component: search,
+    propsDesc: searchPropsDesc
+  },
+  richtext: {
+    component: richtext,
+    propsDesc: richtextPropsDesc
+  }
+}
+
+const props = defineProps(inputPropsDesc)
+
+const emit = defineEmits(['update:modelValue'])
+const type = computed(() => 
+  types[props.type] ? props.type : 'text'
+)
+const childProps = computed(() => {
+  const retain = Object.keys(types[type.value].propsDesc)
+  const childProps = { }
+  for (let key in props) {
+    if (retain.includes(key)) {
+      childProps[key] = props[key]
+    }
+  }
+  if (!types[props.type]) {
+    childProps.type = props.type
+  }
+  return childProps
+})
+
+const style = computed(() => {
+  const style = { }
+  const keys = Object.keys(childProps.value)
+
+  if (!keys.includes('width')) {
+    style.width = props.width
+  }
+
+  if (!keys.includes('height')) {
+    style.height = props.height
+  }
+
+  return style
+})
+
+</script>
+
+<script> export default { name: 'ui-input' } </script>

+ 76 - 0
packages/qjkankan-components/src/components/input/number.vue

@@ -0,0 +1,76 @@
+<template>
+    <UIText
+        class="number ready"
+        :class="{ ctrl }"
+        type="number"
+        :right="right"
+        :modelValue="tempValue"
+        :placeholder="placeholder"
+        @update:modelValue="updateTempValue"
+        :other="{ min, max, step }"
+        @blur="blurHandler"
+        :readonly="!inInput"
+    >
+        <template v-for="(slot, name) in $slots" v-slot:[name]="raw">
+            <slot :name="name" v-bind="raw" />
+        </template>
+        <template #icon v-if="ctrl">
+            <div class="ctrls">
+                <Icon type="up-a" ctrl class="up" @click="updateModelValue(normValue(modelValue) + step)" />
+                <Icon type="d-r" ctrl class="down" @click="updateModelValue(normValue(modelValue) - step)" />
+            </div>
+        </template>
+    </UIText>
+</template>
+
+<script setup>
+import UIText from './text'
+import { numberPropsDesc } from './state'
+import { defineProps, defineEmits, computed, watchEffect, ref } from 'vue'
+import { toRawType } from '../../utils'
+import Icon from '../icon'
+
+const emit = defineEmits(['update:modelValue'])
+const props = defineProps(numberPropsDesc)
+
+const isNumber = raw => !(toRawType(raw) === 'Number' ? isNaN(raw) : isNaN(Number(raw)))
+const tempValue = ref(props.modelValue)
+
+watchEffect(() => {
+    tempValue.value = props.modelValue
+})
+const updateTempValue = val => {
+    tempValue.value = val
+    const tval = Number(val)
+    if (!isNaN(tval) && tval !== props.modelValue) {
+        updateModelValue(tval)
+    }
+}
+
+const blurHandler = () => {
+    tempValue.value = props.modelValue
+    updateModelValue(props.modelValue)
+}
+
+const normValue = val => {
+    val = Number(val)
+    if (isNaN(val)) {
+        return props.min || 0
+    } else {
+        return val
+    }
+}
+
+const updateModelValue = val => {
+    val = normValue(val)
+    if (isNumber(props.min)) {
+        let min = Number(props.min)
+        val = val < min ? min : val
+    }
+    if (isNumber(props.max)) {
+        let max = Number(props.max)
+        val = val > max ? max : val
+    }
+    emit('update:modelValue', val)
+}
+</script>

+ 3 - 0
packages/qjkankan-components/src/components/input/password.vue

@@ -0,0 +1,3 @@
+<template>
+
+</template>

+ 20 - 0
packages/qjkankan-components/src/components/input/radio.vue

@@ -0,0 +1,20 @@
+<template>
+    <div class="input radio" :style="{ width, height }">
+        <input :name="name" :disabled="disabled" :id="id" type="radio" class="replace-input" :checked="props.modelValue" @change="ev => emit('update:modelValue', ev.target.checked)" />
+        <span class="replace"></span>
+    </div>
+    <label class="label" v-if="props.label || props.icon" :for="id">
+        <Icon :type="props.icon" :tip="props.tip" v-if="props.icon" />
+        {{ props.label }}
+    </label>
+</template>
+
+<script setup>
+import Icon from '../icon'
+import { radioPropsDesc } from './state'
+import { randomId } from '../../utils'
+import { defineProps, defineEmits } from 'vue'
+const props = defineProps(radioPropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const id = randomId(4)
+</script>

+ 117 - 0
packages/qjkankan-components/src/components/input/range.vue

@@ -0,0 +1,117 @@
+<template>
+    <div class="input range">
+        <div class="range-content" :class="{ animation: mode === modeEmun.default }" :style="{ '--percentage': percenStyle }" @click="rangeClickHandler" ref="rangeRef">
+            <div class="range-locus" ref="locusRef">
+                <span class="range-slide" @click.stop @mousedown="slideDownHandler"></span>
+            </div>
+        </div>
+        <UItext v-if="inch" :modelValue="inch" class="range-text" style="pointer-events: none" />
+        <UInumber v-else :modelValue="modelValue" @update:modelValue="inputUpdateHandler" :min="min" :max="max" :step="step" :ctrl="false" right class="range-text" />
+    </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, defineProps, watchEffect, nextTick } from 'vue'
+import { rangePropsDesc } from './state'
+import UInumber from './number.vue'
+import UItext from './text.vue'
+const props = defineProps(rangePropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const getValue = value => {
+    const calcStep = Math.ceil(1 / props.step)
+    const calcValue = Math.round(value * calcStep)
+    const calcMin = props.min * calcStep
+    const calcMax = props.max * calcStep
+
+    const newVal = calcValue >= calcMax ? calcMax : calcValue <= calcMin ? calcMin : calcValue - (calcValue % (calcStep * props.step))
+
+    return newVal / calcStep
+}
+
+const percen = computed({
+    get() {
+        return (Number(props.modelValue) - props.min) / (props.max - props.min)
+    },
+    set(val) {
+        const len = props.max - props.min
+        let num
+        if (props.limit > 0 && props.min + len * val > props.limit) {
+            num = getValue(props.limit)
+        } else {
+            num = getValue(props.min + len * val)
+        }
+
+        emit('update:modelValue', num)
+    },
+})
+
+const percenStyle = computed(() => `${percen.value * 100}%`)
+
+const inputUpdateHandler = val => {
+    let num
+    if (props.limit > 0 && val > props.limit) {
+        num = getValue(props.limit)
+    } else {
+        num = getValue(val)
+    }
+    emit('update:modelValue', num)
+}
+
+const modeEmun = {
+    slide: 0,
+    default: 1,
+}
+const mode = ref(modeEmun.default)
+const locusWidth = ref(0)
+const locusRef = ref(null)
+const rangeWidth = ref(0)
+const rangeRef = ref(null)
+onMounted(() => {
+    nextTick(() => {
+        locusWidth.value = locusRef.value.offsetWidth
+        rangeWidth.value = rangeRef.value.offsetWidth
+    })
+})
+
+const rangeClickHandler = ev => {
+    percen.value = ev.offsetX / rangeWidth.value
+    console.log(ev.offsetX, rangeWidth.value)
+    //首次点击,获取limit的值
+    let t = setTimeout(() => {
+        const len = props.max - props.min
+        if (props.limit > 0 && props.min + len * percen.value > props.limit) {
+            percen.value = (props.limit - props.min) / len
+        } else {
+            // percen.value = ev.offsetX / rangeWidth.value
+        }
+        clearTimeout(t)
+    }, 0)
+}
+
+const parent = document.documentElement
+const player = document.querySelector('.player[name="main"]')
+const slideDownHandler = ev => {
+    ev.preventDefault()
+    const moveStartX = ev.clientX
+    const startPercen = percen.value
+    mode.value = modeEmun.slide
+    const moveHandler = ev => {
+        ev.preventDefault()
+        const moveX = ev.clientX - moveStartX
+
+        const readyPercen = startPercen + moveX / locusWidth.value
+
+        percen.value = readyPercen < 0 ? 0 : readyPercen > 1 ? 1 : readyPercen
+    }
+
+    const upHandler = ev => {
+        mode.value = modeEmun.default
+        parent.removeEventListener('mousemove', moveHandler, false)
+        parent.removeEventListener('mouseup', upHandler, false)
+        player.removeEventListener('mouseup', upHandler, false)
+    }
+    parent.addEventListener('mousemove', moveHandler, false)
+    parent.addEventListener('mouseup', upHandler, false)
+    player.addEventListener('mouseup', upHandler, false)
+}
+</script>

+ 168 - 0
packages/qjkankan-components/src/components/input/richtext.vue

@@ -0,0 +1,168 @@
+<template>
+    <div class="input textarea" :class="{ suffix: $slots.icon || maxlength, disabled, right }" ref="textRef">
+        <div
+            contenteditable="true"
+            class="ui-text input-div"
+            @input="inputHandler"
+            :placeholder="props.placeholder"
+            :readonly="readonly"
+            @click="emit('click')"
+            @focus="focusHandler"
+            @blur="blurHandler"
+            @paste="pasteHandler"
+            @compositionstart="compositionstartHandler"
+            @compositionend="compositionendHandler"
+            ref="inputRef"
+            v-bind="other"
+        />
+        <span class="replace"></span>
+        <span v-if="$slots.icon || props.maxlength" class="retouch">
+            <slot name="icon" />
+            <span v-if="props.maxlength" class="len">
+                <span>{{ length }}</span> / {{ maxlength }}
+            </span>
+        </span>
+    </div>
+</template>
+
+<script setup>
+import { richtextPropsDesc } from './state'
+import { defineProps, defineEmits, defineExpose, nextTick, ref, watchEffect } from 'vue'
+const props = defineProps({
+    ...richtextPropsDesc,
+})
+
+const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'click', ''])
+const textRef = ref(null)
+const inputRef = ref(null)
+const length = ref(0)
+
+const updateContent = html => {
+    inputRef.value.innerHTML = html
+    length.value = inputRef.value.textContent.length
+}
+
+watchEffect(() => {
+    if (inputRef.value && props.modelValue !== inputRef.value.innerHTML) {
+        updateContent(props.modelValue)
+    }
+})
+
+let inComposition = false
+const compositionstartHandler = () => {
+    inComposition = true
+}
+const compositionendHandler = ev => {
+    inComposition = false
+    inputHandler(ev)
+}
+
+const inputHandler = ev => {
+    if (inComposition) return
+    if (!props.maxlength || ev.target.textContent.length <= Number(props.maxlength)) {
+        length.value = inputRef.value.textContent.length
+        emit('update:modelValue', ev.target.innerHTML)
+    } else {
+        nextTick(() => {
+            if (ev.target.innerHTML !== props.modelValue.toString()) {
+                updateContent(props.modelValue.toString())
+                inputFocus()
+            }
+        })
+    }
+}
+//获取当前光标位置
+const getCursortPosition = function (element = inputRef.value) {
+    var caretOffset = 0
+    var doc = element.ownerDocument || element.document
+    var win = doc.defaultView || doc.parentWindow
+    var sel
+    if (typeof win.getSelection != 'undefined') {
+        //谷歌、火狐
+        sel = win.getSelection()
+        if (sel.rangeCount > 0) {
+            //选中的区域
+            var range = win.getSelection().getRangeAt(0)
+            var preCaretRange = range.cloneRange() //克隆一个选中区域
+            preCaretRange.selectNodeContents(element) //设置选中区域的节点内容为当前节点
+            preCaretRange.setEnd(range.endContainer, range.endOffset) //重置选中区域的结束位置
+            caretOffset = preCaretRange.toString().length
+        }
+    } else if ((sel = doc.selection) && sel.type != 'Control') {
+        //IE
+        var textRange = sel.createRange()
+        var preCaretTextRange = doc.body.createTextRange()
+        preCaretTextRange.moveToElementText(element)
+        preCaretTextRange.setEndPoint('EndToEnd', textRange)
+        caretOffset = preCaretTextRange.text.length
+    }
+    return caretOffset
+}
+
+let interval
+const focusHandler = ev => {
+    clearInterval(interval)
+    interval = setInterval(() => {
+        console.log(getCursortPosition())
+        emit('updatePos', getCursortPosition())
+    }, 100)
+    emit('focus')
+}
+const blurHandler = () => {
+    clearInterval(interval)
+    emit('blur')
+}
+
+const inputFocus = () => {
+    inputRef.value.focus()
+    const range = window.getSelection()
+    range.selectAllChildren(inputRef.value)
+    range.collapseToEnd()
+}
+
+const getPasteText = text => {
+    if (!props.maxlength) {
+        return text
+    }
+
+    const $el = document.createElement('div')
+    $el.innerHTML = text
+    if ($el.textContent.length > props.maxlength - length.value) {
+        return $el.textContent.substring(0, props.maxlength - length.value)
+    } else {
+        return text
+    }
+}
+
+const pasteHandler = event => {
+    event.preventDefault()
+    var text
+    var clp = (event.originalEvent || event).clipboardData
+    // 兼容针对于opera ie等浏览器
+    if (clp === undefined || clp === null) {
+        text = window.clipboardData.getData('text') || ''
+        if (text !== '') {
+            if (window.getSelection) {
+                // 针对于ie11 10 9 safari
+                var newNode = document.createElement('span')
+                newNode.innerHTML = getPasteText(text)
+                window.getSelection().getRangeAt(0).insertNode(newNode)
+            } else {
+                document.selection.createRange().pasteHTML(text)
+            }
+        }
+    } else {
+        // 兼容chorme或hotfire
+        text = clp.getData('text/plain') || ''
+        if (text !== '') {
+            document.execCommand('insertText', false, getPasteText(text))
+        }
+    }
+}
+
+defineExpose({
+    root: textRef,
+    input: inputRef,
+    getCursortPosition: getCursortPosition,
+})
+</script>

+ 81 - 0
packages/qjkankan-components/src/components/input/search.vue

@@ -0,0 +1,81 @@
+<template>
+  <UISelect 
+    ref="selectVM"
+    className="search"
+    v-bind="props" 
+    :readonly="false" 
+    @update:modelValue="update"
+    :labelValue="labelValue"
+    :options="options">
+    <template v-for="(slot, name) in $slots" v-slot:[name]="raw">
+      <slot :name="name" v-bind="raw" />
+    </template>
+    <template v-slot:icon>
+      <ui-icon type="close" class="clear" small @click="clearHandler" v-if="labelValue" />
+    </template>
+    <template v-slot:preIcon>
+      <ui-icon type="search" color="rgba(255,255,255,.3)" small />
+  </template>
+  </UISelect>
+</template>
+
+<script setup>
+import { ref, watchEffect, defineEmits, onUnmounted } from 'vue'
+import { searchPropsDesc } from './state'
+import UISelect from './select.vue'
+
+const props = defineProps(searchPropsDesc)
+const labelValue = ref('')
+const options = ref([]);
+const selectVM = ref()
+const emit = defineEmits(['update:modelValue'])
+
+watchEffect(() => {
+  options.value = labelValue.value 
+    ? props.options.filter((
+        {label}) => label.includes(labelValue.value)
+      )
+    : props.options
+})
+
+const clearHandler = () => {
+  emit('update:modelValue', null)
+  update('')
+  inputEl.focus()
+}
+
+const update = (val) => {
+  const valItem = props.options.find(({value}) => value === val)
+  const labelItem = props.options.find(({label}) => label === val)
+  const item = valItem || labelItem
+
+  if (item) {
+    labelValue.value = item.label
+    emit('update:modelValue', item.value)
+  } else if (typeof val === 'string'){
+    labelValue.value = val  
+  }
+}
+
+update(props.modelValue)
+const blurHandler = () => {
+  setTimeout(() => update(props.modelValue), 100)
+}
+
+let inputEl
+watchEffect(() => {
+  if (selectVM.value) {
+    if (inputEl) {
+      inputEl.removeEventListener('blur', blurHandler)  
+      inputEl = null
+    }
+    inputEl = selectVM.value.vmRef.input
+    inputEl.addEventListener('blur', blurHandler)
+  }
+})
+
+onUnmounted(() => {
+  inputEl && inputEl.removeEventListener('blur', blurHandler)  
+})
+
+</script>

+ 138 - 0
packages/qjkankan-components/src/components/input/select.vue

@@ -0,0 +1,138 @@
+<template>
+    <UItext
+        :disabled="props.disabled"
+        class="select ready"
+        :class="{
+            focus: showOption,
+            [className]: className,
+        }"
+        ref="vmRef"
+        :modelValue="typeof labelValue === 'string' ? labelValue : inputValue"
+        @update:modelValue="val => emit('update:modelValue', val)"
+        :width="props.width"
+        :height="props.height"
+        :readonly="readonly"
+        :placeholder="props.placeholder"
+        @blur="blurHandler"
+        @focus="showHandler"
+        @click="clickShowHandler"
+    >
+        <template v-slot:icon>
+            <icon type="pull-down" small v-if="!$slots.icon" />
+            <slot name="icon" v-else />
+        </template>
+        <template v-slot:preIcon v-if="$slots.preIcon">
+            <slot name="preIcon" />
+        </template>
+    </UItext>
+
+    <UIFloating
+        :mount="mountEl"
+        :refer="vmRef && vmRef.root"
+        width="100%"
+        :class="{ show: showOption || props.showOptions, [`dire-${dire}`]: true, ...(floatingClass ? { [floatingClass]: true } : {}) }"
+        class="select-float"
+        :dire="dire === 'top' ? 'left-top' : 'left-bottom'"
+    >
+        <slot name="floating-pre" />
+        <div class="select-replace">
+            <ul>
+                <li
+                    v-if="props.options.length"
+                    v-for="option in props.options"
+                    :key="option.value"
+                    :class="{ active: props.modelValue === option.value }"
+                    @mousedown="ev => optionClickHandler(ev, option)"
+                >
+                    <template v-if="$slots.option">
+                        <slot name="option" :raw="option" :active="props.modelValue === option.value" />
+                    </template>
+                    <template v-else>{{ option.label }}</template>
+                </li>
+                <li v-else class="un-data">{{ unplaceholder }}</li>
+            </ul>
+        </div>
+    </UIFloating>
+</template>
+
+<script setup>
+import UItext from './text.vue'
+import UIFloating from '../floating/index.vue'
+import { ref, onUnmounted, computed, defineExpose, watchEffect } from 'vue'
+import { selectPropsDesc } from './state'
+import icon from '../icon'
+
+const props = defineProps({
+    ...selectPropsDesc,
+    readonly: {
+        type: Boolean,
+        default: true,
+    },
+    className: {
+        type: String,
+    },
+    labelValue: {
+        type: String,
+        require: false,
+    },
+    dbhide: {
+        type: Boolean,
+        default: true,
+    },
+})
+const emit = defineEmits(['update:modelValue'])
+const vmRef = ref(null)
+const showOption = ref(false)
+const mountEl = document.body
+
+const inputValue = computed(() => {
+    const selectOption = props.options.find(({ value }) => value === props.modelValue)
+    return selectOption ? selectOption.label : ''
+})
+
+const optionClickHandler = (ev, option) => {
+    if (props.stopEl && props.stopEl.toUpperCase() === ev.target.tagName.toUpperCase()) {
+        setTimeout(() => {
+            vmRef.value.input.focus()
+        })
+    } else {
+        clickCount = 0
+        emit('update:modelValue', option.value)
+        vmRef.value.input.focus()
+        showOption.value = false
+    }
+}
+
+let clickCount = 0
+const clickShowHandler = () => {
+    clickCount++
+    if (showOption.value && props.dbhide && !(clickCount % 2)) {
+        showOption.value = false
+        vmRef.value.input.blur()
+    } else {
+        showHandler()
+    }
+}
+
+const showHandler = () => {
+    clearTimeout(timeout)
+    showOption.value = true
+    vmRef.value.input.focus()
+}
+
+let timeout
+const blurHandler = () =>
+    (timeout = setTimeout(() => {
+        showOption.value = false
+        clickCount = 0
+    }, 16))
+
+defineExpose({
+    vmRef,
+    animationRef: {
+        changeShow(show) {
+            showOption.value = show
+        },
+    },
+})
+</script>

+ 241 - 0
packages/qjkankan-components/src/components/input/state.js

@@ -0,0 +1,241 @@
+const instalcePublic = {
+    name: {
+        type: String,
+    },
+    disabled: {
+        type: [Boolean],
+    },
+    modelValue: {
+        required: false,
+        default: '',
+    },
+    placeholder: {
+        require: false,
+        default: '请输入',
+    },
+}
+
+export const colorPropsDesc = {
+    ...instalcePublic,
+    width: {
+        type: String,
+        default: '100px',
+    },
+    height: {
+        type: String,
+        default: '34px',
+    },
+}
+
+export const filePropsDesc = {
+    ...instalcePublic,
+    placeholder: {
+        require: false,
+        default: '请选择',
+    },
+    othPlaceholder: {
+        require: false,
+        default: '',
+    },
+    accept: {
+        type: String,
+    },
+    scale: {
+        type: String,
+    },
+    multiple: {
+        type: Boolean,
+    },
+    preview: {
+        type: Boolean,
+    },
+    maxSize: {
+        type: Number,
+    },
+    maxLen: {
+        type: Number,
+    },
+}
+
+export const switchPropsDesc = {
+    ...instalcePublic,
+    width: {
+        type: [Number, String],
+    },
+    height: {
+        type: [Number, String],
+    },
+}
+
+export const checkboxPropsDesc = {
+    ...switchPropsDesc,
+    label: {
+        type: String,
+        required: false,
+    },
+}
+
+export const radioPropsDesc = {
+    ...checkboxPropsDesc,
+    icon: {
+        type: String,
+    },
+    tip: {
+        type: String,
+    },
+}
+
+export const textPropsDesc = {
+    ...instalcePublic,
+    maxlength: {
+        type: [String, Number],
+    },
+    placeholder: {
+        type: String,
+        default: '请输入',
+    },
+    readonly: {
+        type: Boolean,
+        default: false,
+    },
+    other: {
+        type: Object,
+        default: () => ({}),
+    },
+    right: {
+        type: Boolean,
+    },
+}
+
+export const textEmitsDesc = ['update:modelValue', 'focus', 'blur', 'click', 'keydown']
+
+export const textareaPropsDesc = {
+    ...textPropsDesc,
+    rich: {
+        type: Boolean,
+    },
+}
+
+export const richtextPropsDesc = {
+    ...textareaPropsDesc,
+    onUpdatePos: Function,
+}
+
+export const selectPropsDesc = {
+    ...textPropsDesc,
+    stopEl: {
+        type: String,
+        require: false,
+    },
+    floatingClass: {
+        type: String,
+        require: false,
+    },
+    showOptions: {
+        type: Boolean,
+        require: false,
+    },
+    placeholder: { ...textPropsDesc.placeholder, default: '请选择' },
+    unplaceholder: { ...textPropsDesc.placeholder, default: '暂无选项' },
+    options: {
+        type: Array,
+        default: () => [],
+    },
+    dire: {
+        type: String,
+        default: 'bottom',
+    },
+}
+
+export const searchPropsDesc = {
+    ...selectPropsDesc,
+    unplaceholder: { ...textPropsDesc.placeholder, default: '无搜索结果' },
+}
+
+export const numberPropsDesc = {
+    ...textPropsDesc,
+    inInput: {
+        type: Boolean,
+        default: true,
+    },
+    ctrl: {
+        type: Boolean,
+        default: true,
+    },
+    step: {
+        type: Number,
+        require: true,
+        default: 1,
+    },
+    min: {
+        type: [Number, String],
+        require: false,
+    },
+    max: {
+        type: [Number, String],
+        require: false,
+    },
+    limit: {
+        type: Number,
+        default: 0,
+    },
+    inch: {
+        type: [Boolean, String],
+        default: false,
+    },
+}
+
+export const rangePropsDesc = {
+    ...numberPropsDesc,
+    min: { ...numberPropsDesc.min, require: true },
+    min: { ...numberPropsDesc.min, require: true },
+}
+
+const summary = {
+    ...checkboxPropsDesc,
+    ...radioPropsDesc,
+    ...selectPropsDesc,
+    ...textPropsDesc,
+    ...rangePropsDesc,
+    ...numberPropsDesc,
+    ...switchPropsDesc,
+    ...textareaPropsDesc,
+    ...filePropsDesc,
+    ...searchPropsDesc,
+    ...richtextPropsDesc,
+    ...colorPropsDesc,
+}
+for (let key in summary) {
+    summary[key] = {
+        ...summary[key],
+        default: undefined,
+    }
+}
+
+export const inputEmitDesc = {
+    text: textEmitsDesc,
+}
+
+export const inputPropsDesc = {
+    ...summary,
+    type: {
+        type: String,
+        required: true,
+        default: 'text',
+    },
+    width: {
+        type: [Number, String],
+    },
+    height: {
+        type: [Number, String],
+    },
+    require: {
+        type: Boolean,
+    },
+    error: {
+        type: String,
+    },
+    disabled: {
+        type: Boolean,
+    },
+}

+ 22 - 0
packages/qjkankan-components/src/components/input/switch.vue

@@ -0,0 +1,22 @@
+<template>
+  <div class="input switch" :style="{ width, height }" :class="{disabled}">
+    <input 
+      class="replace-input"
+      :disabled="disabled"
+      :id="id"
+      type="checkbox" 
+      :checked="props.modelValue"
+      @input="ev => emit('update:modelValue', ev.target.checked)"
+    >
+    <span class="replace"></span>
+  </div>
+</template>
+
+<script setup>
+import { switchPropsDesc } from './state'
+import { randomId } from '../../utils'
+import { defineProps, defineEmits } from 'vue'
+const props = defineProps(switchPropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const id = randomId(4)
+</script>

+ 89 - 0
packages/qjkankan-components/src/components/input/test.vue

@@ -0,0 +1,89 @@
+<template>
+  <UItext
+    :disabled="props.disabled"
+    class="select" 
+    :class="{ready, focus: showOptions}" 
+    ref="vmRef"
+    v-model="inputValue"
+    :width="props.width"
+    :height="props.height"
+    readonly
+    :placeholder="props.placeholder"
+    @blur="blurHandler"
+    @click="changShow(!showOptions)">
+    <template v-slot:icon>
+      <icon type="pull-down" small />
+    </template>
+  </UItext>
+
+  <UIFloating :mount="mountEl" :refer="vmRef && vmRef.root">
+    <ul 
+      class="select-replace"
+      :class="{ ready }"
+      :style="originHeight && {'max-height': maxHeight + 'px'}"
+      ref="contentRef">
+      <li 
+        v-for="option in props.options" 
+        :key="option.value"
+        :class="{active: props.modelValue === option.value}"
+        @click="optionClickHandler(option)">
+        {{option.label}}
+      </li>
+    </ul>
+  </UIFloating>
+</template>
+
+<script setup>
+import UItext from './text.vue'
+import UIFloating from '../floating/index.vue'
+import { ref, onUnmounted, computed } from 'vue'
+import { selectPropsDesc } from './state'
+import icon from '../icon'
+import { changeWHFactory } from '../../utils'
+
+const props = defineProps(selectPropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const vmRef = ref(null)
+const mountEl = document.body
+
+const inputValue = computed(() => {
+  const selectOption = props.options.find(
+    ({value}) => value === props.modelValue
+  )
+  return selectOption ? selectOption.label : ''
+})
+
+const [ 
+  contentRef, 
+  changShow, 
+  maxHeight,
+  originHeight,
+  showOptions,
+  ready
+] = changeWHFactory()
+
+
+const optionClickHandler = (option) => {
+  emit('update:modelValue', option.value)
+  vmRef.value.input.focus()
+  changShow(false)
+}
+
+let timeout
+const blurHandler = () => 
+  timeout = setTimeout(() => changShow(false), 500)
+
+const attach = document.documentElement
+const htmlClickHandler = ev => {
+  if (vmRef.value.root.contains(ev.target)) {
+    clearTimeout(blurHandler)    
+  } else {
+    changShow(false)
+  }
+}
+
+attach.addEventListener('click', htmlClickHandler)
+onUnmounted(() => {
+  attach.removeEventListener('click', htmlClickHandler)
+})
+</script>

+ 68 - 0
packages/qjkankan-components/src/components/input/text.vue

@@ -0,0 +1,68 @@
+<template>
+    <div @click="emit('click')" class="input text" :class="{ suffix: $slots.icon || maxlength, disabled, right, 'pre-suffix': $slots.preIcon }" ref="textRef">
+        <template v-if="type === 'password'">
+            <div class="is-hidden">
+                <input type="text" class="is-hidden" />
+                <input type="password" class="is-hidden" />
+            </div>
+        </template>
+
+        <span v-if="$slots.preIcon" class="pre-icon">
+            <slot name="preIcon" />
+        </span>
+        <input
+            class="ui-text"
+            :class="{ icon: $slots.icon }"
+            :type="type"
+            :value="modelValue"
+            autocomplete="off"
+            @input="inputHandler"
+            :placeholder="props.placeholder"
+            :readonly="readonly"
+            :maxlength="props.maxlength"
+            @focus="emit('focus')"
+            @blur="emit('blur')"
+            ref="inputRef"
+            v-bind="other"
+        />
+        <span v-if="$slots.icon || props.maxlength" class="retouch">
+            <slot name="icon" />
+            <span v-if="props.maxlength" class="len">
+                <span>{{ modelValue.length }}</span> / {{ maxlength }}
+            </span>
+        </span>
+        <slot />
+    </div>
+</template>
+
+<script setup>
+import { textPropsDesc } from './state'
+import { defineProps, defineEmits, defineExpose, nextTick, ref } from 'vue'
+const props = defineProps({
+    type: {
+        type: String,
+        default: 'text',
+    },
+    ...textPropsDesc,
+})
+const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'click'])
+const test = () => {
+    emit('focus')
+}
+const textRef = ref(null)
+const inputRef = ref(null)
+
+const inputHandler = ev => {
+    emit('update:modelValue', ev.target.value)
+    nextTick(() => {
+        if (ev.target.value !== props.modelValue.toString()) {
+            ev.target.value = props.modelValue.toString()
+        }
+    })
+}
+
+defineExpose({
+    root: textRef,
+    input: inputRef,
+})
+</script>

+ 71 - 0
packages/qjkankan-components/src/components/input/textarea.vue

@@ -0,0 +1,71 @@
+<template>
+  <div
+    class="input textarea" 
+    :class="{suffix: $slots.icon || maxlength, disabled, right}"
+    ref="textRef">
+      {{modelValue}}
+    <textarea 
+      class="ui-text"
+      :value="modelValue"
+      @input="inputHandler"
+      :placeholder="props.placeholder"
+      :readonly="readonly"
+      :maxlength="props.maxlength"
+      @click="emit('click')"
+      @focus="emit('focus')"
+      @blur="emit('blur')"
+      ref="inputRef"
+      v-bind="other"
+    />
+    <span class="replace"></span>
+    <span v-if="$slots.icon || props.maxlength"  class="retouch">
+      <slot name="icon" />
+      <span v-if="props.maxlength" class="len">
+        <span>{{modelValue.length}}</span> / {{maxlength}}
+      </span>
+    </span>
+  </div>
+</template>
+
+<script setup>
+import { textareaPropsDesc } from './state'
+import { 
+  defineProps, 
+  defineEmits, 
+  defineExpose,
+  nextTick,
+  ref
+} from 'vue'
+const props = defineProps({
+  type: {
+    type: String,
+    default: 'text'
+  },
+  ...textareaPropsDesc
+})
+
+console.log(props)
+const emit = defineEmits([
+  'update:modelValue',
+  'focus',
+  'blur',
+  'click'
+])
+const textRef = ref(null)
+const inputRef = ref(null)
+
+
+const inputHandler = ev => {
+  emit('update:modelValue', ev.target.value)
+  nextTick(() => {
+    if (ev.target.value !== props.modelValue.toString()) {
+      ev.target.value = props.modelValue.toString()
+    }
+  })
+}
+
+defineExpose({
+  root: textRef,
+  input: inputRef
+})
+</script>

+ 35 - 0
packages/qjkankan-components/src/components/loading/Loading.vue

@@ -0,0 +1,35 @@
+<template>
+    <teleport :to="el">
+        <div class="ui-loading" :style="{ zIndex, ['--width']: size + 'px', ['--color']: color }">
+            <div class="ui-loading__box">
+                <div class="default">
+                    <div></div>
+                    <div></div>
+                    <div></div>
+                </div>
+            </div>
+        </div>
+    </teleport>
+</template>
+<script setup>
+import { defineProps } from 'vue'
+import getZIndex from '../../utils/zindex'
+
+defineProps({
+    el: {
+        default: 'body',
+    },
+    size: {
+        default: 15,
+    },
+    color: {
+        default: '#fff',
+    },
+})
+
+const zIndex = getZIndex()
+</script>
+
+<script>
+export default { name: 'ui-loading' }
+</script>

+ 36 - 0
packages/qjkankan-components/src/components/loading/index.js

@@ -0,0 +1,36 @@
+import Loading from './Loading'
+import { mount } from '../../utils/componentHelper'
+
+const seat = 1
+const closeStack = []
+Loading.use = function use(app) {
+    Loading.show = function (config) {
+        if (closeStack.length) {
+            closeStack.push(seat)
+        } else {
+            const { destroy } = mount(Loading, {
+                app,
+                props: { ...config },
+            })
+            closeStack.push(destroy)
+        }
+    }
+    Loading.hide = function () {
+        if (closeStack.length) {
+            const close = closeStack.pop()
+            if (close !== seat) {
+                close()
+            }
+        }
+    }
+    Loading.hideAll = function () {
+        for (const close of closeStack) {
+            if (close !== seat) {
+                close()
+            }
+        }
+        closeStack.length = 0
+    }
+}
+
+export default Loading

+ 38 - 0
packages/qjkankan-components/src/components/menu-item/index.vue

@@ -0,0 +1,38 @@
+<template>
+  <div 
+    class="ui-menu-item" 
+    :class="{ active }" 
+    ref="self" 
+    @mouseenter="emit('enter')" 
+    @mouseleave="emit('leave')"
+    @click="emit('click')">
+    <UIIcon :type="icon" size="18px" />
+    <span>{{ text }}</span>
+    <slot />
+  </div>
+</template>
+
+<script setup>
+import UIIcon from '../icon'
+import { defineProps, defineExpose, defineEmits, ref } from 'vue'
+
+const self = ref(null)
+const props = defineProps({
+  icon: {
+    type: String,
+    require: true
+  },
+  text: {
+    type: String,
+    require: true
+  },
+  active: {
+    type: Boolean
+  }
+})
+const emit = defineEmits(['leave', 'enter', 'click'])
+
+defineExpose({ dom: self })
+</script>
+
+<script> export default { name: 'ui-menu-item' } </script>

+ 49 - 0
packages/qjkankan-components/src/components/message/index.js

@@ -0,0 +1,49 @@
+import Message from './message.vue'
+import { mount } from '../../utils/componentHelper'
+import { toRawType } from '../../utils/index'
+import { computed, ref } from 'vue'
+
+const types = ['success', 'warning', 'error']
+
+Message.use = function use(app) {
+    const indexs = ref([])
+    Message.show = function (config) {
+        if (toRawType(config) === 'String') {
+            config = { msg: config }
+        }
+
+        config.time = config.time || 3000
+        config.type = types.includes(config.type) ? config.type : types[0]
+
+        const instance = ref(null)
+        const index = computed(() => (instance.value ? indexs.value.indexOf(instance) : 0))
+        const hide = () => {
+            instance.value.destroy()
+            indexs.value = indexs.value.filter(i => i !== instance)
+        }
+
+        instance.value = mount(Message, {
+            app,
+            props: {
+                ...config,
+                index,
+                destroy: hide,
+            },
+        })
+        indexs.value.push(instance)
+    }
+
+    for (const type of types) {
+        Message[type] = config => {
+            if (toRawType(config) === 'String') {
+                config = {
+                    msg: config,
+                    type,
+                }
+            }
+            return Message.show(config)
+        }
+    }
+}
+
+export default Message

+ 57 - 0
packages/qjkankan-components/src/components/message/message.vue

@@ -0,0 +1,57 @@
+<template>
+  <teleport to="body">
+    <transition name="fade">
+      <div 
+        class="ui-message" 
+        :style="{ zIndex: zIndex, marginTop: `${index.value * 60}px` }" 
+        :class="type" v-if="show">
+        <ui-icon :type="icons[type]" class="icon" />
+        <p>{{ msg }}</p>
+      </div>
+    </transition>
+  </teleport>
+</template>
+
+
+<script setup>
+import uiIcon from '../icon'
+import getZindex from '../../utils/zindex'
+import { defineProps, onMounted, ref, nextTick } from 'vue'
+
+const props = defineProps({
+  msg: {
+    type: String
+  },
+  type: {
+    type: String
+  },
+  time: {
+    type: Number
+  },
+  destroy: {
+    type: Function
+  },
+  index: {}
+})
+const zIndex = getZindex()
+const icons = {
+  success: 'state_s',
+  warning: 'state_e',
+  error: 'state_f'
+}
+const show = ref(false)
+
+if (props.time) {
+  setTimeout(
+    () => {
+      show.value = false
+      setTimeout(props.destroy, 500)
+    }, 
+    props.time
+  )
+}
+
+onMounted(() => nextTick(() => show.value = true))
+</script>
+
+<script> export default { name: 'ui-message' } </script>

+ 101 - 0
packages/qjkankan-components/src/components/scrollbar/index.css

@@ -0,0 +1,101 @@
+.x-scrollbar {
+    position: relative;
+}
+
+.x-scrollbar__container {
+    position: relative;
+    z-index: 1;
+    width: 100%;
+    height: 100%;
+    overflow: auto;
+}
+
+.x-scrollbar__container--hideScrollbar {
+    scrollbar-width: none !important;
+    -ms-overflow-style: none !important;
+}
+
+.x-scrollbar__container--hideScrollbar::-webkit-scrollbar {
+    display: none !important;
+}
+
+.x-scrollbar__container--preventDefault {
+    overscroll-behavior: contain;
+    -ms-scroll-chaining: none;
+}
+
+.x-scrollbar__content {
+    display: inline-block;
+    vertical-align: middle;
+    min-width: 100%;
+}
+
+/* 轨道 */
+.x-scrollbar__track-x,
+.x-scrollbar__track-y {
+  overflow: hidden;
+    opacity: 1;
+    position: absolute;
+    z-index: 2;
+    transition: background-color 0.2s linear, opacity 0.2s linear;
+}
+
+.x-scrollbar__track-x {
+    left: 0;
+    bottom: 0;
+    width: 100%;
+    height: 8px;
+}
+
+.x-scrollbar__track-y {
+    top: 0;
+    right: 0;
+    height: 100%;
+    width: 8px;
+}
+
+/* 滑块 */
+.x-scrollbar__thumb-x,
+.x-scrollbar__thumb-y {
+    position: absolute;
+    background-color: rgba(255, 255, 255, 0.2);
+    border-radius: 5px;
+    transition: height 0.2s ease-in-out, width 0.2s ease-in-out;
+}
+
+.x-scrollbar__thumb-x {
+    height: 5px;
+    bottom: 0px;
+}
+
+.x-scrollbar__thumb-y {
+    width: 5px;
+    right: 0px;
+}
+
+/* 激活后大小 */
+.x-scrollbar__track-x:hover .x-scrollbar__thumb-x,
+.x-scrollbar__track--draging .x-scrollbar__thumb-x {
+    height: 8px;
+}
+
+.x-scrollbar__track-y:hover .x-scrollbar__thumb-y,
+.x-scrollbar__track--draging .x-scrollbar__thumb-y {
+    width: 8px;
+}
+
+/* 鼠标移入容器 => 显示滑块 */
+.x-scrollbar-keep > .x-scrollbar__track-x,
+.x-scrollbar-keep > .x-scrollbar__track-y,
+.x-scrollbar:hover > .x-scrollbar__track-x,
+.x-scrollbar:hover > .x-scrollbar__track-y {
+    opacity: 1;
+}
+
+/* 鼠标移入轨道 || 拖动过程中 => 显示轨道 & 高亮滑块 */
+.x-scrollbar__track-x:hover,
+.x-scrollbar__track-y:hover,
+.x-scrollbar__track-x.x-scrollbar__track--draging,
+.x-scrollbar__track-y.x-scrollbar__track--draging {
+    opacity: 1 !important;
+}

+ 0 - 0
packages/qjkankan-components/src/components/scrollbar/index.js


Некоторые файлы не были показаны из-за большого количества измененных файлов