浏览代码

创建融合编辑平台

bill 3 年之前
当前提交
5b57a609ef
共有 100 个文件被更改,包括 10388 次插入0 次删除
  1. 24 0
      .gitignore
  2. 3 0
      .vscode/extensions.json
  3. 16 0
      README.md
  4. 13 0
      index.html
  5. 26 0
      package.json
  6. 707 0
      pnpm-lock.yaml
  7. 1 0
      public/vite.svg
  8. 17 0
      src/api/constant.ts
  9. 7 0
      src/api/index.ts
  10. 44 0
      src/api/instance.ts
  11. 31 0
      src/api/model.ts
  12. 235 0
      src/api/setup.ts
  13. 20 0
      src/app.vue
  14. 1 0
      src/assets/vue.svg
  15. 二进制
      src/components/bill-ui/assets/img/icons/toast-error.png
  16. 二进制
      src/components/bill-ui/assets/img/icons/toast-success.png
  17. 二进制
      src/components/bill-ui/assets/img/icons/toast-warn.png
  18. 55 0
      src/components/bill-ui/assets/scss/_base-vars.scss
  19. 332 0
      src/components/bill-ui/assets/scss/_base.scss
  20. 20 0
      src/components/bill-ui/assets/scss/_components.scss
  21. 22 0
      src/components/bill-ui/assets/scss/components/_audio.scss
  22. 168 0
      src/components/bill-ui/assets/scss/components/_bubble.scss
  23. 92 0
      src/components/bill-ui/assets/scss/components/_button.scss
  24. 4 0
      src/components/bill-ui/assets/scss/components/_cropper.scss
  25. 73 0
      src/components/bill-ui/assets/scss/components/_dialog.scss
  26. 3 0
      src/components/bill-ui/assets/scss/components/_floating.scss
  27. 39 0
      src/components/bill-ui/assets/scss/components/_gate.scss
  28. 100 0
      src/components/bill-ui/assets/scss/components/_group.scss
  29. 73 0
      src/components/bill-ui/assets/scss/components/_guide.scss
  30. 75 0
      src/components/bill-ui/assets/scss/components/_icon.scss
  31. 690 0
      src/components/bill-ui/assets/scss/components/_input.scss
  32. 56 0
      src/components/bill-ui/assets/scss/components/_loading.scss
  33. 24 0
      src/components/bill-ui/assets/scss/components/_men-item.scss
  34. 42 0
      src/components/bill-ui/assets/scss/components/_message.scss
  35. 32 0
      src/components/bill-ui/assets/scss/components/_size-animation.scss
  36. 59 0
      src/components/bill-ui/assets/scss/components/_slide.scss
  37. 62 0
      src/components/bill-ui/assets/scss/components/_tip.scss
  38. 81 0
      src/components/bill-ui/assets/scss/components/_toast.scss
  39. 147 0
      src/components/bill-ui/assets/scss/components/_tree.scss
  40. 12 0
      src/components/bill-ui/assets/scss/editor/_head.scss
  41. 8 0
      src/components/bill-ui/assets/scss/editor/_layout.scss
  42. 12 0
      src/components/bill-ui/assets/scss/editor/_main.scss
  43. 27 0
      src/components/bill-ui/assets/scss/editor/_menu.scss
  44. 18 0
      src/components/bill-ui/assets/scss/editor/_toolbar.scss
  45. 16 0
      src/components/bill-ui/assets/scss/editor/_toolbox.scss
  46. 11 0
      src/components/bill-ui/assets/scss/editor/_view.scss
  47. 12 0
      src/components/bill-ui/assets/scss/theme-editor.scss
  48. 56 0
      src/components/bill-ui/components/audio/index.vue
  49. 31 0
      src/components/bill-ui/components/bubble/index.vue
  50. 52 0
      src/components/bill-ui/components/button/index.vue
  51. 95 0
      src/components/bill-ui/components/cropper/cropper.vue
  52. 38 0
      src/components/bill-ui/components/cropper/index.js
  53. 44 0
      src/components/bill-ui/components/dialog/Alert.vue
  54. 62 0
      src/components/bill-ui/components/dialog/Confirm.vue
  55. 17 0
      src/components/bill-ui/components/dialog/Dialog-content.vue
  56. 27 0
      src/components/bill-ui/components/dialog/Dialog.vue
  57. 65 0
      src/components/bill-ui/components/dialog/Toast.vue
  58. 16 0
      src/components/bill-ui/components/dialog/Window.vue
  59. 77 0
      src/components/bill-ui/components/dialog/index.js
  60. 181 0
      src/components/bill-ui/components/floating/index.vue
  61. 1 0
      src/components/bill-ui/components/gate/constant.js
  62. 32 0
      src/components/bill-ui/components/gate/content.vue
  63. 4 0
      src/components/bill-ui/components/gate/index.js
  64. 63 0
      src/components/bill-ui/components/gate/layer.vue
  65. 1 0
      src/components/bill-ui/components/group/constant.js
  66. 4 0
      src/components/bill-ui/components/group/index.js
  67. 35 0
      src/components/bill-ui/components/group/ui-group-option.vue
  68. 69 0
      src/components/bill-ui/components/group/ui-group.vue
  69. 75 0
      src/components/bill-ui/components/guide/index.vue
  70. 37 0
      src/components/bill-ui/components/icon/icon/index.vue
  71. 10 0
      src/components/bill-ui/components/icon/icon/svg/a-connect-dis.svg
  72. 10 0
      src/components/bill-ui/components/icon/icon/svg/a-connect.svg
  73. 11 0
      src/components/bill-ui/components/icon/icon/svg/data_revise_n.svg
  74. 10 0
      src/components/bill-ui/components/icon/icon/svg/dianwei.svg
  75. 9 0
      src/components/bill-ui/components/icon/icon/svg/m-r.svg
  76. 12 0
      src/components/bill-ui/components/icon/icon/svg/point-s.svg
  77. 11 0
      src/components/bill-ui/components/icon/icon/svg/state_gps.svg
  78. 10 0
      src/components/bill-ui/components/icon/icon/svg/weitiao.svg
  79. 11 0
      src/components/bill-ui/components/icon/icon/svg/zoom_n.svg
  80. 21 0
      src/components/bill-ui/components/icon/icon/useSvg.ts
  81. 539 0
      src/components/bill-ui/components/icon/iconfont/demo.css
  82. 2925 0
      src/components/bill-ui/components/icon/iconfont/demo_index.html
  83. 491 0
      src/components/bill-ui/components/icon/iconfont/iconfont.css
  84. 1 0
      src/components/bill-ui/components/icon/iconfont/iconfont.js
  85. 842 0
      src/components/bill-ui/components/icon/iconfont/iconfont.json
  86. 二进制
      src/components/bill-ui/components/icon/iconfont/iconfont.ttf
  87. 二进制
      src/components/bill-ui/components/icon/iconfont/iconfont.woff
  88. 二进制
      src/components/bill-ui/components/icon/iconfont/iconfont.woff2
  89. 75 0
      src/components/bill-ui/components/icon/index.vue
  90. 30 0
      src/components/bill-ui/components/input/check-radio.vue
  91. 21 0
      src/components/bill-ui/components/input/checkbox.vue
  92. 26 0
      src/components/bill-ui/components/input/color.vue
  93. 194 0
      src/components/bill-ui/components/input/file.vue
  94. 148 0
      src/components/bill-ui/components/input/index.vue
  95. 80 0
      src/components/bill-ui/components/input/number.vue
  96. 3 0
      src/components/bill-ui/components/input/password.vue
  97. 28 0
      src/components/bill-ui/components/input/radio.vue
  98. 110 0
      src/components/bill-ui/components/input/range.vue
  99. 168 0
      src/components/bill-ui/components/input/richtext.vue
  100. 0 0
      src/components/bill-ui/components/input/search.vue

+ 24 - 0
.gitignore

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

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}

+ 16 - 0
README.md

@@ -0,0 +1,16 @@
+# Vue 3 + TypeScript + Vite
+
+This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+## Recommended IDE Setup
+
+- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
+
+## Type Support For `.vue` Imports in TS
+
+Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
+
+1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
+2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
+
+You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Vite + Vue + TS</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 26 - 0
package.json

@@ -0,0 +1,26 @@
+{
+  "name": "fuse-code",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vue-tsc --noEmit && vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "axios": "^0.27.2",
+    "mitt": "^3.0.0",
+    "vue": "^3.2.37",
+    "vue-cropper": "^0.5.8",
+    "vue-router": "^4.1.3"
+  },
+  "devDependencies": {
+    "@types/node": "^18.6.5",
+    "@vitejs/plugin-vue": "^3.0.0",
+    "sass": "^1.54.3",
+    "typescript": "^4.6.4",
+    "vite": "^3.0.0",
+    "vue-tsc": "^0.38.4"
+  }
+}

+ 707 - 0
pnpm-lock.yaml

@@ -0,0 +1,707 @@
+lockfileVersion: 5.3
+
+specifiers:
+  '@types/node': ^18.6.5
+  '@vitejs/plugin-vue': ^3.0.0
+  axios: ^0.27.2
+  mitt: ^3.0.0
+  sass: ^1.54.3
+  typescript: ^4.6.4
+  vite: ^3.0.0
+  vue: ^3.2.37
+  vue-cropper: ^0.5.8
+  vue-router: ^4.1.3
+  vue-tsc: ^0.38.4
+
+dependencies:
+  axios: 0.27.2
+  mitt: 3.0.0
+  vue: 3.2.37
+  vue-cropper: 0.5.8
+  vue-router: 4.1.3_vue@3.2.37
+
+devDependencies:
+  '@types/node': 18.6.5
+  '@vitejs/plugin-vue': 3.0.1_vite@3.0.4+vue@3.2.37
+  sass: 1.54.3
+  typescript: 4.7.4
+  vite: 3.0.4_sass@1.54.3
+  vue-tsc: 0.38.9_typescript@4.7.4
+
+packages:
+
+  /@babel/parser/7.18.11:
+    resolution: {integrity: sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+
+  /@esbuild/linux-loong64/0.14.54:
+    resolution: {integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==}
+    engines: {node: '>=12'}
+    cpu: [loong64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /@types/node/18.6.5:
+    resolution: {integrity: sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==}
+    dev: true
+
+  /@vitejs/plugin-vue/3.0.1_vite@3.0.4+vue@3.2.37:
+    resolution: {integrity: sha512-Ll9JgxG7ONIz/XZv3dssfoMUDu9qAnlJ+km+pBA0teYSXzwPCIzS/e1bmwNYl5dcQGs677D21amgfYAnzMl17A==}
+    engines: {node: ^14.18.0 || >=16.0.0}
+    peerDependencies:
+      vite: ^3.0.0
+      vue: ^3.2.25
+    dependencies:
+      vite: 3.0.4_sass@1.54.3
+      vue: 3.2.37
+    dev: true
+
+  /@volar/code-gen/0.38.9:
+    resolution: {integrity: sha512-n6LClucfA+37rQeskvh9vDoZV1VvCVNy++MAPKj2dT4FT+Fbmty/SDQqnsEBtdEe6E3OQctFvA/IcKsx3Mns0A==}
+    dependencies:
+      '@volar/source-map': 0.38.9
+    dev: true
+
+  /@volar/source-map/0.38.9:
+    resolution: {integrity: sha512-ba0UFoHDYry+vwKdgkWJ6xlQT+8TFtZg1zj9tSjj4PykW1JZDuM0xplMotLun4h3YOoYfY9K1huY5gvxmrNLIw==}
+    dev: true
+
+  /@volar/vue-code-gen/0.38.9:
+    resolution: {integrity: sha512-tzj7AoarFBKl7e41MR006ncrEmNPHALuk8aG4WdDIaG387X5//5KhWC5Ff3ZfB2InGSeNT+CVUd74M0gS20rjA==}
+    dependencies:
+      '@volar/code-gen': 0.38.9
+      '@volar/source-map': 0.38.9
+      '@vue/compiler-core': 3.2.37
+      '@vue/compiler-dom': 3.2.37
+      '@vue/shared': 3.2.37
+    dev: true
+
+  /@volar/vue-typescript/0.38.9:
+    resolution: {integrity: sha512-iJMQGU91ADi98u8V1vXd2UBmELDAaeSP0ZJaFjwosClQdKlJQYc6MlxxKfXBZisHqfbhdtrGRyaryulnYtliZw==}
+    dependencies:
+      '@volar/code-gen': 0.38.9
+      '@volar/source-map': 0.38.9
+      '@volar/vue-code-gen': 0.38.9
+      '@vue/compiler-sfc': 3.2.37
+      '@vue/reactivity': 3.2.37
+    dev: true
+
+  /@vue/compiler-core/3.2.37:
+    resolution: {integrity: sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==}
+    dependencies:
+      '@babel/parser': 7.18.11
+      '@vue/shared': 3.2.37
+      estree-walker: 2.0.2
+      source-map: 0.6.1
+
+  /@vue/compiler-dom/3.2.37:
+    resolution: {integrity: sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==}
+    dependencies:
+      '@vue/compiler-core': 3.2.37
+      '@vue/shared': 3.2.37
+
+  /@vue/compiler-sfc/3.2.37:
+    resolution: {integrity: sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==}
+    dependencies:
+      '@babel/parser': 7.18.11
+      '@vue/compiler-core': 3.2.37
+      '@vue/compiler-dom': 3.2.37
+      '@vue/compiler-ssr': 3.2.37
+      '@vue/reactivity-transform': 3.2.37
+      '@vue/shared': 3.2.37
+      estree-walker: 2.0.2
+      magic-string: 0.25.9
+      postcss: 8.4.16
+      source-map: 0.6.1
+
+  /@vue/compiler-ssr/3.2.37:
+    resolution: {integrity: sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==}
+    dependencies:
+      '@vue/compiler-dom': 3.2.37
+      '@vue/shared': 3.2.37
+
+  /@vue/devtools-api/6.2.1:
+    resolution: {integrity: sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==}
+    dev: false
+
+  /@vue/reactivity-transform/3.2.37:
+    resolution: {integrity: sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==}
+    dependencies:
+      '@babel/parser': 7.18.11
+      '@vue/compiler-core': 3.2.37
+      '@vue/shared': 3.2.37
+      estree-walker: 2.0.2
+      magic-string: 0.25.9
+
+  /@vue/reactivity/3.2.37:
+    resolution: {integrity: sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==}
+    dependencies:
+      '@vue/shared': 3.2.37
+
+  /@vue/runtime-core/3.2.37:
+    resolution: {integrity: sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==}
+    dependencies:
+      '@vue/reactivity': 3.2.37
+      '@vue/shared': 3.2.37
+    dev: false
+
+  /@vue/runtime-dom/3.2.37:
+    resolution: {integrity: sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==}
+    dependencies:
+      '@vue/runtime-core': 3.2.37
+      '@vue/shared': 3.2.37
+      csstype: 2.6.20
+    dev: false
+
+  /@vue/server-renderer/3.2.37_vue@3.2.37:
+    resolution: {integrity: sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==}
+    peerDependencies:
+      vue: 3.2.37
+    dependencies:
+      '@vue/compiler-ssr': 3.2.37
+      '@vue/shared': 3.2.37
+      vue: 3.2.37
+    dev: false
+
+  /@vue/shared/3.2.37:
+    resolution: {integrity: sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==}
+
+  /anymatch/3.1.2:
+    resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
+    engines: {node: '>= 8'}
+    dependencies:
+      normalize-path: 3.0.0
+      picomatch: 2.3.1
+    dev: true
+
+  /asynckit/0.4.0:
+    resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+    dev: false
+
+  /axios/0.27.2:
+    resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
+    dependencies:
+      follow-redirects: 1.15.1
+      form-data: 4.0.0
+    transitivePeerDependencies:
+      - debug
+    dev: false
+
+  /binary-extensions/2.2.0:
+    resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
+    engines: {node: '>=8'}
+    dev: true
+
+  /braces/3.0.2:
+    resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+    engines: {node: '>=8'}
+    dependencies:
+      fill-range: 7.0.1
+    dev: true
+
+  /chokidar/3.5.3:
+    resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
+    engines: {node: '>= 8.10.0'}
+    dependencies:
+      anymatch: 3.1.2
+      braces: 3.0.2
+      glob-parent: 5.1.2
+      is-binary-path: 2.1.0
+      is-glob: 4.0.3
+      normalize-path: 3.0.0
+      readdirp: 3.6.0
+    optionalDependencies:
+      fsevents: 2.3.2
+    dev: true
+
+  /combined-stream/1.0.8:
+    resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+    engines: {node: '>= 0.8'}
+    dependencies:
+      delayed-stream: 1.0.0
+    dev: false
+
+  /csstype/2.6.20:
+    resolution: {integrity: sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==}
+    dev: false
+
+  /delayed-stream/1.0.0:
+    resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+    engines: {node: '>=0.4.0'}
+    dev: false
+
+  /esbuild-android-64/0.14.54:
+    resolution: {integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [android]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-android-arm64/0.14.54:
+    resolution: {integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [android]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-darwin-64/0.14.54:
+    resolution: {integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-darwin-arm64/0.14.54:
+    resolution: {integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-freebsd-64/0.14.54:
+    resolution: {integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-freebsd-arm64/0.14.54:
+    resolution: {integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-32/0.14.54:
+    resolution: {integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==}
+    engines: {node: '>=12'}
+    cpu: [ia32]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-64/0.14.54:
+    resolution: {integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-arm/0.14.54:
+    resolution: {integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==}
+    engines: {node: '>=12'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-arm64/0.14.54:
+    resolution: {integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-mips64le/0.14.54:
+    resolution: {integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==}
+    engines: {node: '>=12'}
+    cpu: [mips64el]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-ppc64le/0.14.54:
+    resolution: {integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==}
+    engines: {node: '>=12'}
+    cpu: [ppc64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-riscv64/0.14.54:
+    resolution: {integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==}
+    engines: {node: '>=12'}
+    cpu: [riscv64]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-linux-s390x/0.14.54:
+    resolution: {integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==}
+    engines: {node: '>=12'}
+    cpu: [s390x]
+    os: [linux]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-netbsd-64/0.14.54:
+    resolution: {integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [netbsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-openbsd-64/0.14.54:
+    resolution: {integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [openbsd]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-sunos-64/0.14.54:
+    resolution: {integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [sunos]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-windows-32/0.14.54:
+    resolution: {integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==}
+    engines: {node: '>=12'}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-windows-64/0.14.54:
+    resolution: {integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild-windows-arm64/0.14.54:
+    resolution: {integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /esbuild/0.14.54:
+    resolution: {integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==}
+    engines: {node: '>=12'}
+    hasBin: true
+    requiresBuild: true
+    optionalDependencies:
+      '@esbuild/linux-loong64': 0.14.54
+      esbuild-android-64: 0.14.54
+      esbuild-android-arm64: 0.14.54
+      esbuild-darwin-64: 0.14.54
+      esbuild-darwin-arm64: 0.14.54
+      esbuild-freebsd-64: 0.14.54
+      esbuild-freebsd-arm64: 0.14.54
+      esbuild-linux-32: 0.14.54
+      esbuild-linux-64: 0.14.54
+      esbuild-linux-arm: 0.14.54
+      esbuild-linux-arm64: 0.14.54
+      esbuild-linux-mips64le: 0.14.54
+      esbuild-linux-ppc64le: 0.14.54
+      esbuild-linux-riscv64: 0.14.54
+      esbuild-linux-s390x: 0.14.54
+      esbuild-netbsd-64: 0.14.54
+      esbuild-openbsd-64: 0.14.54
+      esbuild-sunos-64: 0.14.54
+      esbuild-windows-32: 0.14.54
+      esbuild-windows-64: 0.14.54
+      esbuild-windows-arm64: 0.14.54
+    dev: true
+
+  /estree-walker/2.0.2:
+    resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+  /fill-range/7.0.1:
+    resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+    engines: {node: '>=8'}
+    dependencies:
+      to-regex-range: 5.0.1
+    dev: true
+
+  /follow-redirects/1.15.1:
+    resolution: {integrity: sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==}
+    engines: {node: '>=4.0'}
+    peerDependencies:
+      debug: '*'
+    peerDependenciesMeta:
+      debug:
+        optional: true
+    dev: false
+
+  /form-data/4.0.0:
+    resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+    engines: {node: '>= 6'}
+    dependencies:
+      asynckit: 0.4.0
+      combined-stream: 1.0.8
+      mime-types: 2.1.35
+    dev: false
+
+  /fsevents/2.3.2:
+    resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+    requiresBuild: true
+    dev: true
+    optional: true
+
+  /function-bind/1.1.1:
+    resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
+    dev: true
+
+  /glob-parent/5.1.2:
+    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+    engines: {node: '>= 6'}
+    dependencies:
+      is-glob: 4.0.3
+    dev: true
+
+  /has/1.0.3:
+    resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
+    engines: {node: '>= 0.4.0'}
+    dependencies:
+      function-bind: 1.1.1
+    dev: true
+
+  /immutable/4.1.0:
+    resolution: {integrity: sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==}
+    dev: true
+
+  /is-binary-path/2.1.0:
+    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+    engines: {node: '>=8'}
+    dependencies:
+      binary-extensions: 2.2.0
+    dev: true
+
+  /is-core-module/2.10.0:
+    resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==}
+    dependencies:
+      has: 1.0.3
+    dev: true
+
+  /is-extglob/2.1.1:
+    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /is-glob/4.0.3:
+    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+    engines: {node: '>=0.10.0'}
+    dependencies:
+      is-extglob: 2.1.1
+    dev: true
+
+  /is-number/7.0.0:
+    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+    engines: {node: '>=0.12.0'}
+    dev: true
+
+  /magic-string/0.25.9:
+    resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
+    dependencies:
+      sourcemap-codec: 1.4.8
+
+  /mime-db/1.52.0:
+    resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+    engines: {node: '>= 0.6'}
+    dev: false
+
+  /mime-types/2.1.35:
+    resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+    engines: {node: '>= 0.6'}
+    dependencies:
+      mime-db: 1.52.0
+    dev: false
+
+  /mitt/3.0.0:
+    resolution: {integrity: sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==}
+    dev: false
+
+  /nanoid/3.3.4:
+    resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
+  /normalize-path/3.0.0:
+    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
+  /path-parse/1.0.7:
+    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+    dev: true
+
+  /picocolors/1.0.0:
+    resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+
+  /picomatch/2.3.1:
+    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+    engines: {node: '>=8.6'}
+    dev: true
+
+  /postcss/8.4.16:
+    resolution: {integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==}
+    engines: {node: ^10 || ^12 || >=14}
+    dependencies:
+      nanoid: 3.3.4
+      picocolors: 1.0.0
+      source-map-js: 1.0.2
+
+  /readdirp/3.6.0:
+    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+    engines: {node: '>=8.10.0'}
+    dependencies:
+      picomatch: 2.3.1
+    dev: true
+
+  /resolve/1.22.1:
+    resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
+    hasBin: true
+    dependencies:
+      is-core-module: 2.10.0
+      path-parse: 1.0.7
+      supports-preserve-symlinks-flag: 1.0.0
+    dev: true
+
+  /rollup/2.77.2:
+    resolution: {integrity: sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g==}
+    engines: {node: '>=10.0.0'}
+    hasBin: true
+    optionalDependencies:
+      fsevents: 2.3.2
+    dev: true
+
+  /sass/1.54.3:
+    resolution: {integrity: sha512-fLodey5Qd41Pxp/Tk7Al97sViYwF/TazRc5t6E65O7JOk4XF8pzwIW7CvCxYVOfJFFI/1x5+elDyBIixrp+zrw==}
+    engines: {node: '>=12.0.0'}
+    hasBin: true
+    dependencies:
+      chokidar: 3.5.3
+      immutable: 4.1.0
+      source-map-js: 1.0.2
+    dev: true
+
+  /source-map-js/1.0.2:
+    resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
+    engines: {node: '>=0.10.0'}
+
+  /source-map/0.6.1:
+    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+    engines: {node: '>=0.10.0'}
+
+  /sourcemap-codec/1.4.8:
+    resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
+
+  /supports-preserve-symlinks-flag/1.0.0:
+    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+    engines: {node: '>= 0.4'}
+    dev: true
+
+  /to-regex-range/5.0.1:
+    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+    engines: {node: '>=8.0'}
+    dependencies:
+      is-number: 7.0.0
+    dev: true
+
+  /typescript/4.7.4:
+    resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
+    engines: {node: '>=4.2.0'}
+    hasBin: true
+    dev: true
+
+  /vite/3.0.4_sass@1.54.3:
+    resolution: {integrity: sha512-NU304nqnBeOx2MkQnskBQxVsa0pRAH5FphokTGmyy8M3oxbvw7qAXts2GORxs+h/2vKsD+osMhZ7An6yK6F1dA==}
+    engines: {node: ^14.18.0 || >=16.0.0}
+    hasBin: true
+    peerDependencies:
+      less: '*'
+      sass: '*'
+      stylus: '*'
+      terser: ^5.4.0
+    peerDependenciesMeta:
+      less:
+        optional: true
+      sass:
+        optional: true
+      stylus:
+        optional: true
+      terser:
+        optional: true
+    dependencies:
+      esbuild: 0.14.54
+      postcss: 8.4.16
+      resolve: 1.22.1
+      rollup: 2.77.2
+      sass: 1.54.3
+    optionalDependencies:
+      fsevents: 2.3.2
+    dev: true
+
+  /vue-cropper/0.5.8:
+    resolution: {integrity: sha512-Xgi/aLQCcNCiBTUdovy/i4LWx0G7fQnAENBpDWA3J4i87Zlk9DJRksXrClsQ6nnaFRfQVkMimij5GEvAMaXdYw==}
+    dev: false
+
+  /vue-router/4.1.3_vue@3.2.37:
+    resolution: {integrity: sha512-XvK81bcYglKiayT7/vYAg/f36ExPC4t90R/HIpzrZ5x+17BOWptXLCrEPufGgZeuq68ww4ekSIMBZY1qdUdfjA==}
+    peerDependencies:
+      vue: ^3.2.0
+    dependencies:
+      '@vue/devtools-api': 6.2.1
+      vue: 3.2.37
+    dev: false
+
+  /vue-tsc/0.38.9_typescript@4.7.4:
+    resolution: {integrity: sha512-Yoy5phgvGqyF98Fb4mYqboR4Q149jrdcGv5kSmufXJUq++RZJ2iMVG0g6zl+v3t4ORVWkQmRpsV4x2szufZ0LQ==}
+    hasBin: true
+    peerDependencies:
+      typescript: '*'
+    dependencies:
+      '@volar/vue-typescript': 0.38.9
+      typescript: 4.7.4
+    dev: true
+
+  /vue/3.2.37:
+    resolution: {integrity: sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==}
+    dependencies:
+      '@vue/compiler-dom': 3.2.37
+      '@vue/compiler-sfc': 3.2.37
+      '@vue/runtime-dom': 3.2.37
+      '@vue/server-renderer': 3.2.37_vue@3.2.37
+      '@vue/shared': 3.2.37
+    dev: false

文件差异内容过多而无法显示
+ 1 - 0
public/vite.svg


+ 17 - 0
src/api/constant.ts

@@ -0,0 +1,17 @@
+export enum ResCode {
+  TOKEN_INVALID = 4008,
+  SUCCESS = 0
+}
+
+export const ResCodeDesc: { [key in ResCode]: string } = {
+  [ResCode.TOKEN_INVALID]: 'token已失效',
+  [ResCode.SUCCESS]: '请求成功'
+}
+
+export const UPLOAD_HEADS = {
+  'Content-Type': 'multipart/form-data'
+}
+
+
+// 模型列表
+export const MODEL_LIST = ''

+ 7 - 0
src/api/index.ts

@@ -0,0 +1,7 @@
+
+
+export { ResCode } from './constant'
+export type { ResData } from './setup'
+
+export * from './instance'
+export * from './model'

+ 44 - 0
src/api/instance.ts

@@ -0,0 +1,44 @@
+import { axiosFactory } from './setup'
+import { Message } from 'bill/index'
+import { ResCodeDesc } from './constant'
+import { showLoad, hideLoad } from '@/utils'
+
+const instance = axiosFactory()
+
+export const {
+  axios,
+  addUnsetTokenURLS,
+  addReqErrorHandler,
+  addResErrorHandler,
+  delReqErrorHandler,
+  delResErrorHandler,
+  getToken,
+  setToken,
+  delToken,
+  setDefaultURI,
+  addHook,
+  delHook,
+  setHook
+} = instance
+
+addReqErrorHandler(err => {
+  Message.error(err.message)
+})
+
+addResErrorHandler(
+  (response, data) => {
+    if (response.status !== 200) {
+      Message.error(response.statusText)
+    } else if (data) {
+      const msg = data.code && ResCodeDesc[data.code] ? ResCodeDesc[data.code] : data?.message
+      Message.error(msg)
+    }
+  }
+)
+
+addHook({ before: showLoad, after: hideLoad })
+
+// addUnsetTokenURLS(LOGIN)
+setDefaultURI('/api')
+
+export default axios

+ 31 - 0
src/api/model.ts

@@ -0,0 +1,31 @@
+import axios from './instance'
+import { 
+  MODEL_LIST
+} from './constant'
+
+export enum ModelType {
+  SWKK,
+  SWKJ,
+  SWSS,
+  SWMX,
+}
+
+export const ModelTypeDesc: Record<ModelType, string>  = {
+  [ModelType.SWKK]: '四维看看',
+  [ModelType.SWKJ]: '四维看见',
+  [ModelType.SWSS]: '四维深时',
+  [ModelType.SWMX]: '三维模型',
+}
+
+export interface Model {
+  id: string
+  title: string
+  type: ModelType
+  size: number,
+  time: string
+}
+
+export type Models = Model[]
+
+export const getModels = () => 
+  axios.post<Models>(MODEL_LIST)

+ 235 - 0
src/api/setup.ts

@@ -0,0 +1,235 @@
+import Axios from 'axios'
+import { ResCode } from './constant'
+
+import type { AxiosResponse, AxiosRequestConfig } from 'axios'
+
+export type ResErrorHandler = <D, T extends ResData<D>>(response: AxiosResponse<T>, data?: T) => void
+export type ReqErrorHandler = <T>(err: Error, response: AxiosRequestConfig<T>) => void
+export type ResData<T> = { code: ResCode, message: string, data: T }
+export type Hook = {
+  before: (config: AxiosRequestConfig) => void
+  after: (config: AxiosRequestConfig) => void
+}
+
+export const axiosFactory = () => {
+  const axiosRaw = Axios.create()
+  const axiosConfig = {
+    token: localStorage.getItem('token'),
+    unTokenSet: [] as string[],
+    unReqErrorSet: [] as string[],
+    unResErrorSet: [] as string[],
+    resErrorHandler: [] as ResErrorHandler[],
+    reqErrorHandler: [] as ReqErrorHandler[],
+    unLoadingSet: [] as string[],
+    hook: [] as Hook[]
+  }
+
+
+
+  type AxiosConfig = typeof axiosConfig
+  type ExponseApi<K extends keyof AxiosConfig> = 
+    { set: (val: AxiosConfig[K]) => void } 
+      & (AxiosConfig[K] extends Array<any> 
+        ? { 
+            add: (...val: AxiosConfig[K]) => void,
+            del: (...val: AxiosConfig[K]) => void
+          }
+        : { del: () => void })
+
+  const getExponseApi = <K extends keyof AxiosConfig>(key: K): ExponseApi<K> => {
+    let axiosObj = axiosConfig[key] as any[]
+    const apis: any = { 
+      set (val: AxiosConfig[K]) {
+        axiosObj = axiosConfig[key] = val as any
+      }
+    }
+    
+    if (Array.isArray(axiosObj)) {
+      apis.add = (...val: any[]) => {
+        axiosObj.push(...val)
+      }
+      apis.del = (...val: any[]) => {
+        if (val) {
+          apis.set(axiosObj.filter((item: any) => !val?.includes(item)) as any)
+        } else {
+          axiosObj.length = 0
+        }
+      }
+    } else {
+      apis.del = () => {
+        axiosConfig[key] = undefined as any
+      }
+    }
+    return apis
+  }
+
+  const getToken = () => axiosConfig.token
+  const setToken = (token: string) => {
+    localStorage.setItem('token', token)
+    axiosConfig.token = token
+  }
+  const delToken = () => {
+    localStorage.removeItem('token')
+    axiosConfig.token = null
+  }
+  
+  const {
+    set: setUnsetTokenURLS,
+    add: addUnsetTokenURLS,
+    del: delUnsetTokenURLS
+  } = getExponseApi('unTokenSet')
+
+  const {
+    set: setResErrorHandler,
+    add: addResErrorHandler,
+    del: delResErrorHandler
+  } = getExponseApi('resErrorHandler')
+
+  const {
+    set: setUnsetReqErrorURLS,
+    add: addUnsetReqErrorURLS,
+    del: delUnsetReqErrorURLS
+  } = getExponseApi('unReqErrorSet')
+
+  const {
+    set: setReqErrorHandler,
+    add: addReqErrorHandler,
+    del: delReqErrorHandler
+  } = getExponseApi('reqErrorHandler')
+
+  const {
+    set: setUnsetResErrorURLS,
+    add: addUnsetResErrorURLS,
+    del: delUnsetResErrorURLS
+  } = getExponseApi('unResErrorSet')
+
+  const {
+    set: setHook,
+    add: addHook,
+    del: delHook
+  } = getExponseApi('hook')
+
+  const setDefaultURI = (url: string) => {
+    axiosRaw.defaults.baseURL = url
+  }
+
+  const matchURL = (urls: string[], config: AxiosRequestConfig<any>) => 
+    config.url && urls.includes(config.url)
+
+  const callErrorHandler = (key: 'req' | 'res', ...args: any[]) => {
+    Promise.resolve()
+      .then(() => {
+        const api = `${key}ErrorHandler`
+        ;(axiosConfig as any)[api].forEach((handler: any) => handler(...args))
+      })
+  }
+
+  axiosRaw.interceptors.request.use(
+    config => {
+      for (const hook of axiosConfig.hook) {
+        hook.before(config)
+      }
+
+      if (!matchURL(axiosConfig.unTokenSet, config)) {
+        if (!axiosConfig.token) {
+          if (!matchURL(axiosConfig.unReqErrorSet, config)) {
+            const error = new Error('缺少token')
+            callErrorHandler('req', error, config)
+            throw error
+          }
+        } else {
+          config.headers = {
+            ...config.headers,
+            token: axiosConfig.token
+          }
+        }
+      }
+      return config
+    }
+  )
+
+  axiosRaw.interceptors.response.use(
+    (response: AxiosResponse<ResData<any>>) => {
+      for (const hook of axiosConfig.hook) {
+        hook.after(response.config)
+      }
+
+      if (matchURL(axiosConfig.unResErrorSet, response.config)) {
+        return response
+      }
+
+      if (response.status !== 200) {
+        callErrorHandler('res', response)
+        throw new Error(response.statusText)
+      } else if (response.data.code !== ResCode.SUCCESS) {
+        callErrorHandler('res', response, response.data)
+        if (response.data.code === ResCode.TOKEN_INVALID) {
+          delToken()
+        }
+        throw new Error(response.data.message)
+      } else {
+        return response.data.data
+      }
+    },
+    (err) => {
+      for (const hook of axiosConfig.hook) {
+        hook.after(err.config)
+      }
+      if (!matchURL(axiosConfig.unResErrorSet, err.config)) {
+        callErrorHandler('res', err.response)
+      }
+      throw new Error(err.response.statusText)
+    }
+  )
+
+
+  type AxiosProcess = {
+    getUri(config?: AxiosRequestConfig): string;
+    request<R = any, D = any>(config: AxiosRequestConfig<D>): Promise<R>;
+    get<R = any, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
+    delete<R = any, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
+    head<R = any, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
+    options<R = any, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>;
+    post<R = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
+    put<R = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
+    patch<R = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
+    postForm<R = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
+    putForm<R = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
+    patchForm<R = any, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
+  }
+
+  interface AxiosInstanceProcess extends AxiosProcess {
+    <R = any>(config: AxiosRequestConfig): Promise<R>;
+    <R = any>(url: string, config?: AxiosRequestConfig): Promise<R>;
+  }
+
+  const axios: AxiosInstanceProcess = axiosRaw as any
+
+  return {
+    axios,
+    getToken,
+    setToken,
+    delToken,
+    setUnsetTokenURLS,
+    addUnsetTokenURLS,
+    delUnsetTokenURLS,
+    setResErrorHandler,
+    addResErrorHandler,
+    delResErrorHandler,
+    setUnsetReqErrorURLS,
+    addUnsetReqErrorURLS,
+    delUnsetReqErrorURLS,
+    setReqErrorHandler,
+    addReqErrorHandler,
+    delReqErrorHandler,
+    setUnsetResErrorURLS,
+    addUnsetResErrorURLS,
+    delUnsetResErrorURLS,
+    setDefaultURI,
+    setHook,
+    addHook,
+    delHook,
+  }
+}
+
+export default axiosFactory

+ 20 - 0
src/app.vue

@@ -0,0 +1,20 @@
+<template>
+  <component :is="Component" v-if="Component" />
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue'
+import { loaded, error, initialStore } from '@/store'
+import { loadComponent, loadPack } from '@/utils'
+
+loadPack(initialStore)
+
+const Main = loadComponent(() => import('@/layout/main.vue'))
+const Err = loadComponent(() => import('@/components/error/index.vue'))
+
+const Component = computed(() => {
+  if (loaded.value) {
+    return error.value ? Err : Main
+  }
+})
+</script>

+ 1 - 0
src/assets/vue.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

二进制
src/components/bill-ui/assets/img/icons/toast-error.png


二进制
src/components/bill-ui/assets/img/icons/toast-success.png


二进制
src/components/bill-ui/assets/img/icons/toast-warn.png


+ 55 - 0
src/components/bill-ui/assets/scss/_base-vars.scss

@@ -0,0 +1,55 @@
+
+:root {
+  --colors-primary-fill: 255, 255, 255;
+  --colors-primary-base-fill: 0, 200, 175;
+  --colors-primary-base: rgb(var(--colors-primary-base-fill));
+  --colors-primary-hover: #008B7A;
+  --colors-primary-click: #005046;
+
+  --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.6);
+  --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-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-men-color: #999;
+
+  --editor-toolbox-width: 340px;
+  --editor-toolbox-back: var(--editor-menu-back);
+}

+ 332 - 0
src/components/bill-ui/assets/scss/_base.scss

@@ -0,0 +1,332 @@
+/*!
+ * 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;
+}
+
+::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;
+}
+
+
+.strengthen {
+    box-shadow: inset 0 0 1px rgba(255,255,255,.5);
+    border: 1px solid #000;
+}
+
+.strengthen-left {
+    box-shadow: inset 0 0 1px rgba(255,255,255,.5);
+    border-left: 1px solid #000;
+}
+
+.strengthen-right {
+    box-shadow: inset 0 0 1px rgba(255,255,255,.5);
+    border-right: 1px solid #000;
+}
+
+.strengthen-top {
+    box-shadow: inset 0 0 1px rgba(255,255,255,.5);
+    border-top: 1px solid #000;
+}
+
+.strengthen-bottom {
+    box-shadow: inset 0 0 1px rgba(255,255,255,.5);
+    border-bottom: 1px solid #000;
+}
+
+
+.fun-ctrl {
+    transition: color .3s ease;
+    cursor: pointer;
+    color: rgba(255,255,255,0.6) !important;
+    &:hover {
+      color: rgba(255,255,255,1) !important;
+    }
+    &:active {
+      color: var(--colors-primary-base) !important;
+    }
+  }
+
+  .iconfont {
+      font-weight: 400;
+  }

+ 20 - 0
src/components/bill-ui/assets/scss/_components.scss

@@ -0,0 +1,20 @@
+@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";
+@import "components/bubble";
+@import "components/guide";
+@import "components/tip";
+

+ 22 - 0
src/components/bill-ui/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;
+  }
+}

+ 168 - 0
src/components/bill-ui/assets/scss/components/_bubble.scss

@@ -0,0 +1,168 @@
+.bubble {
+  --arrow-width: 45px;
+  --arrow-height: 32px;
+  --back-color: rgba(27, 27, 28, 0.8);
+  --border-color-fill: 0, 0, 0;
+  --border-color: rgb(var(--border-color-fill));
+  --radius: 8px;
+  --padding: 20px 20px;
+  --bottom-left: 40px;
+  position: absolute;
+  z-index: 9;
+  transition: transform .3s ease, opacity .3s ease;
+
+
+
+  >.bubble-layer {
+    // backdrop-filter: blur(4px);
+    position: relative;
+    padding: var(--padding);
+    min-width: calc(3 * var(--arrow-width));
+    min-height: calc(3 * var(--arrow-height));
+    background: var(--back-color);
+    box-shadow: 0px 0px 10px 0px rgba(var(--border-color-fill), 0.3);
+    // border: 1px solid var(--border-color);
+    border-radius: var(--radius);
+
+    > .bubble-arr {
+      position: absolute;
+      display: block;
+      pointer-events: none;
+      margin-left: 1px;
+      z-index: 99;
+      width: 0;
+      height: 0;
+      border-style: solid;
+      border-color: transparent;
+      border-width: calc(var(--arrow-width) / 2);
+    }
+  }
+
+  &.left,
+  &.right {
+    &::after {
+      content: '';
+      position: absolute;
+      z-index: 1;
+      width: calc(var(--arrow-width) / 1.4);
+      height: calc(var(--arrow-width) / 1.3);
+      top: 50%;
+      transform: translateY(calc(-50% + 0.5px));
+    }
+
+    top: 50%;
+    transform: translateY(calc(-50% + 0.5px));
+
+
+    >.bubble-layer > .bubble-arr {
+      top: 50%;
+      transform: translateY(calc(-50% + 0.5px));
+      border-width: calc(var(--arrow-width) / 3);
+
+    }
+    
+    
+    &.fade-enter-active,
+    &.fade-leave-active {
+      transform: translateY(calc(-50% + 0.5px)) scale(1);
+      opacity: 1;
+    }
+    &.fade-enter-from,
+    &.fade-leave-to {
+      transform: translateY(calc(-50% + 0.5px)) scale(0);
+      opacity: 0;
+    }
+  }
+
+  &.left {
+    transform-origin: center right;
+    margin-right: var(--arrow-width);
+    right: 50%;
+    
+    &::after {
+      left: 100%;
+    }
+
+    >.bubble-layer > .bubble-arr {
+      left: calc(100% - 1px);
+      right: calc(-1 * var(--arrow-width) * 1.1);
+      border-left-color: var(--back-color);
+      border-left-width: calc(var(--arrow-width) / 1.3);
+    }
+  }
+
+  &.right {
+    transform-origin: center left;
+    margin-left: var(--arrow-width);
+    left: 50%;
+
+    &::after {
+      right: 100%;
+    }
+
+    >.bubble-layer > .bubble-arr {
+      right: 100%;
+      left: calc(-1 * var(--arrow-width) * 1.1);
+      border-right-color: var(--back-color);
+      border-right-width: calc(var(--arrow-width) / 1.3);
+    }
+  }
+
+
+  &.bottom,
+  &.top {
+
+    &.fade-enter-active,
+    &.fade-leave-active {
+      transform: scale(1);
+      opacity: 1;
+    }
+    &.fade-enter-from,
+    &.fade-leave-to {
+      transform: scale(0);
+      opacity: 0;
+    }
+  }
+
+  &.bottom {
+    top: 100%;
+    left: calc(50% - var(--bottom-left));
+    padding-top: var(--arrow-width);
+    transform-origin: var(--bottom-left) top;
+
+
+    >.bubble-layer > .bubble-arr {
+      border-width: calc(var(--arrow-width) / 3);
+      border-bottom-width: calc(var(--arrow-width) / 1.3);
+      border-bottom-color: var(--back-color);
+
+
+      left: 0;
+      top: calc(-1 * var(--arrow-width));
+      bottom: 100%;
+      transform: translateX(calc(-50% + 0.5px));
+      margin-left: var(--bottom-left);
+      // clip-path: polygon(0 100%, 50% 0, 100% 100%);
+    }
+  }
+
+  &.top {
+    bottom: 100%;
+    left: calc(50% - var(--bottom-left));
+    padding-bottom: var(--arrow-width);
+    transform-origin: var(--bottom-left) bottom;
+
+
+    >.bubble-layer > .bubble-arr {
+      border-width: calc(var(--arrow-width) / 3);
+      border-top-width: calc(var(--arrow-width) / 1.3);
+      border-top-color: var(--back-color);
+
+      left: 0;
+      top: 100%;
+      transform: translateX(calc(-50% + 0.5px));
+      margin-left: var(--bottom-left);
+      // clip-path: polygon(0 100%, 50% 0, 100% 100%);
+    }
+  }
+}

+ 92 - 0
src/components/bill-ui/assets/scss/components/_button.scss

@@ -0,0 +1,92 @@
+@use "sass:map";
+
+.ui-button {
+    width: 100%;
+    height: 34px;
+    border: none;
+    outline: none;
+    border-radius: 4px;
+    font-size: 14px;
+    background: none !important;
+
+    transition: all .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(--colors-primary-base);
+    border: 1px solid var( --color-main-normal);
+    background-color: var( --color-main-normal);
+    &:hover {
+        
+        border-color: #4DD8C7;
+        background-color: #4DD8C7;
+        color: #4DD8C7;
+    }
+    &:active {
+        border-color: var( --color-main-focus);
+        background-color: var( --color-main-focus);
+        color: var( --color-main-focus);
+    }
+}
+
+.ui-button.cancel {
+    color: var( --color-main-normal);
+    border: 1px solid var( --color-main-normal);
+    &:hover {
+        border-color: var( --color-main-hover);
+    }
+    &:active {
+        border-color: var( --color-main-focus);
+    }
+}
+
+.ui-button.primary {
+    background-color: var(--colors-primary-base) !important;
+    color: var(--colors-normal-fill-hover);
+    border: none;
+    opacity: 1;
+
+    &:hover {
+        background-color: #4DD8C7 !important;
+    }
+    &:active {
+        background-color: var(--colors-primary-hover) !important;
+        color: rgba(255,255,255,0.6);
+        background: #005046;
+    }
+}
+
+
+.ui-button.error {
+    color: rgba(250, 63, 72, 0.8);
+    border: 1px solid rgba(250, 63, 72, 0.6);;
+    &:hover {
+        border-color: rgba(250, 63, 72, 1);
+    }
+    &:active {
+        border-color: rgba(250, 63, 72, 0.4);
+    }
+}

+ 4 - 0
src/components/bill-ui/assets/scss/components/_cropper.scss

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

+ 73 - 0
src/components/bill-ui/assets/scss/components/_dialog.scss

@@ -0,0 +1,73 @@
+.ui-dialog {
+    position: fixed;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+    color: #fff;
+    background-color: rgba($color: #000000, $alpha: 0.3);
+    backdrop-filter: blur(1px);
+}
+.ui-dialog__box {
+    position: relative;
+    display: inline-block;
+    min-width: 300px;
+    max-width: calc(100% - 20px);
+    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);
+    &::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: #999999;
+        padding: 0 20px;
+        height: 60px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        border-bottom: solid 1px rgba($color: #ffffff, $alpha: 0.16);
+        font-weight: bold;
+        i{
+            cursor: pointer;
+        }
+    }
+    section {
+        padding: 40px 30px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+    }
+    footer{
+        padding: 20px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        border-top: solid 1px rgba($color: #ffffff, $alpha: 0.16);
+        button{
+            width: 105px;
+            margin-left: 10px;
+            margin-right: 10px;
+        }
+    }
+}
+
+.confirm-content {
+    line-height: 1.8;
+}

+ 3 - 0
src/components/bill-ui/assets/scss/components/_floating.scss

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

+ 39 - 0
src/components/bill-ui/assets/scss/components/_gate.scss

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

+ 100 - 0
src/components/bill-ui/assets/scss/components/_group.scss

@@ -0,0 +1,100 @@
+$padding: 14px;
+
+.ui-group {
+  &:not(:last-child) {
+    margin-bottom: 20px;
+  }
+
+
+  &.control {
+    > .group-title {
+
+      .group-icon {
+        .group-control-icon {
+          transition: transform .1s ease;
+          cursor: pointer;
+        }
+
+        &.show {
+          .group-control-icon {
+            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: flex;
+    margin-bottom: 10px;
+    color: var(--colors-content-color);
+    justify-content: space-between;
+  }
+}

+ 73 - 0
src/components/bill-ui/assets/scss/components/_guide.scss

@@ -0,0 +1,73 @@
+.guide {
+  position: relative;
+
+  &:not(.floating-mode) {
+    &.top {
+      transform: translateY(-100%);
+    }
+  
+    .bubble {
+      --arrow-width: 14px;
+      --arrow-height: 10px;
+      --padding: 10px;
+      --bottom-left: 20px;
+  
+      .bubble-layer {
+        min-height: auto;
+        min-width: auto;
+        padding-right: 30px;
+  
+        .guide-close {
+          position: absolute;
+          right: 10px;
+          top: 10px;
+          // margin-top: 2px;
+          font-size: 12px;
+        }
+      }
+    }
+  
+    .guide-bubble {
+      .default-msg {
+        white-space: nowrap;
+      }
+    }
+  }
+}
+
+.guide-floating {
+  color: #fff;
+  font-size: 14px;
+
+  &.top {
+    transform: translateY(-100%);
+  }
+
+  .bubble {
+    --arrow-width: 14px;
+    --arrow-height: 10px;
+    --padding: 10px;
+    --bottom-left: 20px;
+    position: static;
+
+    .bubble-layer {
+      min-height: auto;
+      min-width: auto;
+      padding-right: 30px;
+
+      .guide-close {
+        position: absolute;
+        right: 10px;
+        top: 10px;
+        // margin-top: 2px;
+        font-size: 12px;
+      }
+    }
+  }
+
+  .guide-bubble {
+    .default-msg {
+      white-space: nowrap;
+    }
+  }
+}

+ 75 - 0
src/components/bill-ui/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;
+  }
+
+  &.fore-show,
+  &: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;
+}
+
+.tip-v-top .tip {
+  bottom: 100%;
+}
+
+.tip-v-center .tip {
+  top: 50%;
+  transform: translateY(-50%);
+}
+
+.tip-v-bottom .tip {
+  top: 100%;
+}

+ 690 - 0
src/components/bill-ui/assets/scss/components/_input.scss

@@ -0,0 +1,690 @@
+.ui-input {
+  display: inline-flex;
+  align-items: center;
+  --base-border-color: rgba(255, 255, 255, 0.2);
+  --colors-content-color: #fff;
+
+  &.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 .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 .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 .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 .3s ease;
+
+      &:focus {
+        border-color: var(--colors-primary-base);
+      }
+
+      &::placeholder { 
+        color: var(--colors-color);
+      }
+    }
+    .input-value {
+      position: absolute;
+    }
+
+    &.pre-suffix {
+      input {
+        padding-left: 30px;
+      }
+      .input-value {
+        left: 30px;
+      }
+      .pre-icon {
+        left: 10px;
+        top: 50%;
+        transform: translateY(-50%);
+      }
+    }
+
+    &.right input {
+      text-align: right;
+    }
+
+    &.suffix {
+      input {
+        padding-right: 30px;
+      }
+      .retouch {
+        position: absolute;
+        right: 10px;
+        top: 50%;
+        transform: translateY(-50%);
+      }
+
+      .len {
+        font-size: var(--small-size);
+        color: rgba(var(--colors-primary-fill), 1);
+
+        span {
+          color: var(--colors-primary-base);;
+        }
+      }
+    }
+
+    &.ready {
+      .retouch,
+      input {
+        transition: all .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 .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;
+    }
+
+
+  }
+
+  .select {
+    input {
+      cursor: pointer;
+    }
+    
+    &.focus {
+      input {
+        border-color: var(--colors-primary-base);
+      }
+      .retouch {
+        transform: translateY(-50%) rotateZ(180deg);
+      }
+    }
+    
+  }
+
+
+  .range {
+    width: 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: 20px;
+      width: 60px;
+    }
+
+    .animation {
+      &.range-content::before,
+      .range-slide {
+        transition: all .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 .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 .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,.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 {
+          right: 10px;
+          top: 0;
+          bottom: 0;
+          display: flex;
+          align-items: center;
+          font-size: 10px;
+          &::after {
+            content: ')';
+          }
+          &::before {
+            
+            content: '(';
+          }
+
+          > 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,.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,.6);
+        border-radius: 50%;
+        cursor: pointer;
+      }
+    }
+  }
+
+  .color {
+    &.default {
+      input {
+        opacity: 1;
+        border: inherit;
+        outline: inherit;
+      }
+    }
+    .replace {
+      pointer-events: none;
+    }
+  }
+}
+
+.select-float {
+  transition: transform .3s ease,
+    opacity .3s ease;
+
+
+  &:not(.show) {
+    opacity: 0;
+    pointer-events: none;
+
+  }
+  &.show {
+    opacity: 1;
+  }
+
+  &.dire-bottom {
+    transform-origin: center top;
+
+    &:not(.show) {
+      transform: scale(1, 0);
+    }
+    &.show {
+      transform: scale(1, 1);
+    }
+  }
+
+  &.dire-top {
+    transform-origin: center bottom;
+
+    &:not(.show) {
+      transform: translateY(-100%) scale(1, 0);
+    }
+
+    &.show {
+      transform: translateY(-100%) scale(1, 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);
+
+  .un-data {
+    padding: 20px 15px;
+    color: rgba(255, 255, 255, 0.3);
+    pointer-events: none;
+    font-size: 14px;
+  }
+
+  .select-options-atom {
+    padding: 10px 10px;
+    font-size: 14px;
+
+    &.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;
+
+}
+

+ 56 - 0
src/components/bill-ui/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) * 0.6);
+        }
+        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;
+    }
+}

+ 24 - 0
src/components/bill-ui/assets/scss/components/_men-item.scss

@@ -0,0 +1,24 @@
+
+
+.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{
+    margin-top: 6px;
+  }
+  &:hover{
+      color: var( --color-main-hover);
+  }
+  &.active{
+      color: var( --color-main-normal);
+      background-color: rgba(255,255,255,0.06);
+  }
+}

+ 42 - 0
src/components/bill-ui/assets/scss/components/_message.scss

@@ -0,0 +1,42 @@
+.ui-message {
+  position: fixed;
+  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%);
+  white-space: nowrap;
+
+  .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%);
+  }
+}

+ 32 - 0
src/components/bill-ui/assets/scss/components/_size-animation.scss

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

+ 59 - 0
src/components/bill-ui/assets/scss/components/_slide.scss

@@ -0,0 +1,59 @@
+
+.ui-slide {
+  position: relative;
+  height: 100%;
+
+  &.stop-animation .ui-gate-slides{
+    transition: none;
+  }
+
+  .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;
+  }
+
+  .ui-gate-layer .ui-gate-slides .ui-gate-content {
+    opacity: 1 !important;
+  }
+
+  .infos {
+    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;
+    left: 0;
+    right: 0;
+    text-align: center;
+    overflow: hidden;
+
+    .tj {
+      height: 100%;
+      display: inline-flex;
+      align-items: center;
+      font-size: 10px;
+
+      span {
+        color: var(--colors-primary-base);
+        margin-right: 4px;
+      }
+    }
+  }
+}

+ 62 - 0
src/components/bill-ui/assets/scss/components/_tip.scss

@@ -0,0 +1,62 @@
+
+.tip-layout {
+  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;
+  }
+
+  &.fore-show,
+  &: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;
+    }
+
+    .tip-v-top .tip {
+      bottom: 100%;
+    }
+
+    .tip-v-center .tip {
+      top: 50%;
+      transform: translateY(-50%);
+    }
+
+    .tip-v-bottom .tip {
+      top: 100%;
+    }
+  }
+}
+

+ 81 - 0
src/components/bill-ui/assets/scss/components/_toast.scss

@@ -0,0 +1,81 @@
+.ui-toast {
+    position: fixed;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    left: 50%;
+    top: 70px;
+    width: 300px;
+    margin-left: -150px;
+    height: 100px;
+    overflow: hidden;
+}
+.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;
+    }
+}

+ 147 - 0
src/components/bill-ui/assets/scss/components/_tree.scss

@@ -0,0 +1,147 @@
+.ui-tree {
+  /* 收缩控件大小 */
+  --ctrl-size: 14px;
+  /* 左边留白大小 */
+  --padding-size: 20px;
+  // 底部margind大小
+  --margin-size: 20px;
+  --border-style: dashed;
+  // --border-style: solid;
+  --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: 0;
+    // 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));
+
+    .not-last-un-child::before,
+    .ui-tree-content::before,
+    .ui-tree-content::after {
+      content: '';
+      position: absolute;
+      border-width: 0;
+      border-style: var(--border-style);
+      border-color: rgba(255,255,255,0.6);
+    }
+    
+
+    > .ui-tree-item > .ui-tree-content::after {
+      left: calc(var(--padding-size) * -1);
+      width: var(--slideWidth);
+      border-bottom-width: var(--border-width);
+      top: var(--half-ctrl);
+      transform: translateX(-100%) translateY(-50%);
+    }
+
+    > .not-last-un-child::before,
+    > .ui-tree-item > .ui-tree-content::before {
+      border-left-width: var(--border-width);
+      top: var(--ctrl-size);
+      bottom: calc(-1 * calc(var(--calc-size) + var(--half-ctrl)));
+      left: calc(-1 * var(--slideWidth));
+      transform:  scale(1);
+      transition: transform .3s ease;
+      transform-origin: top center;
+    }
+
+
+    > .not-last-un-child::before {
+      top: var(--half-ctrl);
+      bottom: calc(-1 * calc(var(--half-ctrl)));
+    }
+
+    > .un-children > .ui-tree-content::before {
+      top: calc(var(--ctrl-size) / 2);
+    }
+
+    > .put > .ui-tree-content::before {
+      transform:  scale(0);
+    }
+
+    &:not(.children) > .un-children > .ui-tree-content::before,
+    &:not(.children) > .not-last-un-child:before,
+    .ui-tree-item.last > .ui-tree-content::before,
+    &:not(.children) > .ui-tree-item > .ui-tree-content::after {
+        display: none !important;
+    }
+  }
+
+}

+ 12 - 0
src/components/bill-ui/assets/scss/editor/_head.scss

@@ -0,0 +1,12 @@
+@use "sass:map";
+
+.ui-editor-head {
+    filter: var(--editor-head-filter);
+    height: var(--editor-head-height);
+    background-color: var(--editor-head-back);
+    position: fixed;
+    left: 0;
+    top: 0;
+    width: 100%;
+    z-index: 1000;
+}

+ 8 - 0
src/components/bill-ui/assets/scss/editor/_layout.scss

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

+ 12 - 0
src/components/bill-ui/assets/scss/editor/_main.scss

@@ -0,0 +1,12 @@
+@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: 3000;
+}

+ 27 - 0
src/components/bill-ui/assets/scss/editor/_menu.scss

@@ -0,0 +1,27 @@
+@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: auto;
+    }
+
+    .ui-editor-menu-item {
+        width: var(--editor-menu-width);
+        height: var(--editor-menu-width);
+    }
+}

+ 18 - 0
src/components/bill-ui/assets/scss/editor/_toolbar.scss

@@ -0,0 +1,18 @@
+@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:60px;
+    background-color: var(--editor-menu-back);
+    pointer-events: all;
+    left: calc(var(--editor-menu-left) + var(--editor-menu-width));
+    z-index: 1;
+    backdrop-filter: blur(4px);
+    transition: all .3s ease;
+}

+ 16 - 0
src/components/bill-ui/assets/scss/editor/_toolbox.scss

@@ -0,0 +1,16 @@
+@use "sass:map";
+
+.ui-editor-toolbox {
+    position: absolute;
+    z-index: 1;
+    right: var(--editor-menu-right);
+    padding: 20px;
+    width:  var(--editor-toolbox-width);;
+    bottom: 0;
+    pointer-events: all;
+    overflow-y: auto;
+    background-color: var(--editor-toolbox-back);
+    backdrop-filter: blur(4px);
+    top: var(--editor-head-height);
+    transition: right ease .3s
+}

+ 11 - 0
src/components/bill-ui/assets/scss/editor/_view.scss

@@ -0,0 +1,11 @@
+@use "sass:map";
+
+.ui-editor-view {
+    display: flex;
+    flex: 1;
+    width: 100%;
+    height: 100%;
+    align-items: flex-end;
+    justify-content: flex-end;
+    overflow: hidden;
+}

+ 12 - 0
src/components/bill-ui/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
src/components/bill-ui/components/audio/index.vue

@@ -0,0 +1,56 @@
+<template>
+    <div class="ui-audio" @click="clickHandler">
+        <audio @play="rotation" ref="audio" autoplay loop>
+            <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>

+ 31 - 0
src/components/bill-ui/components/bubble/index.vue

@@ -0,0 +1,31 @@
+<template>
+    <transition name="fade">
+        <div class="bubble" :class="{ [type]: true, [level]: true }" v-if="show" @click.stop>
+            <div class="bubble-layer">
+                <div class="bubble-arr"></div>
+                <slot />
+            </div>
+        </div>
+    </transition>
+</template>
+
+<script setup>
+defineProps({
+    type: {
+        type: String,
+        default: 'right',
+    },
+    show: {
+        type: Boolean,
+        default: true,
+    },
+    level: {
+        type: String,
+        require: false,
+    },
+})
+</script>
+
+<script>
+export default { name: 'ui-bubble' }
+</script>

+ 52 - 0
src/components/bill-ui/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/index.vue'
+
+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>

+ 95 - 0
src/components/bill-ui/components/cropper/cropper.vue

@@ -0,0 +1,95 @@
+<template>
+  <Confirm title="裁剪" :func="clickHandler">
+    <template v-slot:content>
+      <div class="cropper-layer" :style="style">
+        <VueCropper 
+        ref="vmRef" 
+        v-bind="option"
+        v-on="on" />
+      </div>
+    </template>
+  </Confirm>
+</template>
+
+<script setup>
+import { VueCropper } from 'vue-cropper'
+import Confirm from '../dialog/Confirm.vue'
+import { computed, defineProps, ref } from 'vue'
+// import 'vue-cropper/dist/index.css'
+
+const layerWidth = 500
+const props = defineProps({
+  fixedNumber: {
+    type: Array,
+    default: () => [1, 1]
+  },
+  img: { type: String },
+  cb: {
+    type: Function
+  }
+})
+
+const fixedNumber = props.fixedNumber
+const getHeight = width => (fixedNumber[1] / fixedNumber[0]) * width
+const option = {
+  outputSize: 1,
+  outputType: 'png',
+  info: false,
+  full: true,
+  fixed: true,
+  fixedNumber: fixedNumber,
+  canMove: true,
+  canMoveBox: true,
+  fixedBox: false,
+  original: false,
+  autoCrop: true,
+  autoCropWidth: layerWidth / 2,
+  autoCropHeight: getHeight(layerWidth / 2),
+  centerBox: true,
+  mode: 'contain',
+  maxImgSize: 400,
+  ...props
+}
+
+const style = computed(() => ({
+  width: layerWidth + 'px',
+  height: getHeight(layerWidth) + 'px'
+}))
+
+const vmRef = ref()
+const on = {
+  imgLoad(status) {
+    if (status !== 'success') {
+      props.cb('图片加载失败')
+    }
+  },
+}
+
+const clickHandler = async (status) => {
+  if (status === 'ok') {
+    const data = await Promise.all([
+      new Promise(resolve => vmRef.value.getCropBlob(resolve)),
+      new Promise(resolve => vmRef.value.getCropData(resolve)),
+    ])
+    props.cb(null, data)
+  } else {
+    props.cb()
+  }
+  
+}
+</script>
+
+<style>
+.vue-cropper {
+  background-repeat: repeat;
+}
+
+.cropper-view-box {
+  outline-color: var(--color-main-normal) !important;
+}
+.crop-point {
+  background-color: var(--color-main-normal) !important;
+}
+</style>
+
+<script> export default { name: 'ui-cropper' } </script>

+ 38 - 0
src/components/bill-ui/components/cropper/index.js

@@ -0,0 +1,38 @@
+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)
+                        }
+                    },
+                },
+            })
+        })
+    }
+}
+
+console.log(Cropper)
+export default Cropper

+ 44 - 0
src/components/bill-ui/components/dialog/Alert.vue

@@ -0,0 +1,44 @@
+<template>
+    <ui-dialog>
+        <template v-slot:header>
+            <span>{{ title }}</span>
+            <i class="iconfont icon-close fun-ctrl" @click="close"></i>
+        </template>
+        {{ content }}
+        <template v-slot:footer>
+            <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: {
+        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>

+ 62 - 0
src/components/bill-ui/components/dialog/Confirm.vue

@@ -0,0 +1,62 @@
+<template>
+    <ui-dialog>
+        <template #header>
+            <template v-if="!$slots.header">
+                <span>{{ title }}</span>
+                <i class="iconfont icon-close fun-ctrl" @click="close('no')" v-if="func"></i>
+            </template>
+            <span v-else>{{ title }}</span>
+        </template>
+
+        <template v-if="$slots.content">
+            <slot name="content" />
+        </template>
+        <template v-else>
+            <pre class="confirm-content">{{ content }}</pre>
+        </template>
+
+        <template #footer>
+            <template v-if="!$slots.footer">
+                <ui-button type="submit" @click="close('no')">{{ noText }}</ui-button>
+                <ui-button type="primary" @click="close('ok')">{{ okText }}</ui-button>
+            </template>
+            <slot v-else name="footer" />
+        </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: '取消',
+        },
+        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
src/components/bill-ui/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
src/components/bill-ui/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>

+ 65 - 0
src/components/bill-ui/components/dialog/Toast.vue

@@ -0,0 +1,65 @@
+<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 class="icon"></i>
+                    <div>{{ content }}</div>
+                    <i class="iconfont icon-close close" @click="close"></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,
+    },
+    setup: function (props, ctx) {
+        const show = ref(true)
+        const close = () => {
+            show.value = false
+            nextTick(() => {
+                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(0, 100%, 0);
+}
+.slide-down-leave-active {
+    opacity: 0;
+    transform: translate3d(0, -100%, 0);
+}
+</style>

+ 16 - 0
src/components/bill-ui/components/dialog/Window.vue

@@ -0,0 +1,16 @@
+<template>
+     <ui-dialog v-if="false">
+         <div slot="header">
+             sdfsdf
+         </div>
+     </ui-dialog>
+</template>
+<script>
+import { defineComponent } from 'vue'
+export default defineComponent({
+    name: 'ui-window',
+    setup: function (props, ctx) {
+        
+    },
+})
+</script>

+ 77 - 0
src/components/bill-ui/components/dialog/index.js

@@ -0,0 +1,77 @@
+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,
+            },
+        })
+
+        this.toast.hide = function () {
+            destroy()
+        }
+
+        return this.toast
+    }
+    Dialog.alert = function (options) {
+        if (typeof options == 'string') {
+            options = {
+                content: options,
+            }
+        }
+        return new Promise(resolve => {
+            const props = {
+                ...options,
+                destroy: () => {
+                    destroy()
+                    resolve()
+                },
+            }
+            const { destroy } = mount(Alert, { app, props })
+        })
+    }
+    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

+ 181 - 0
src/components/bill-ui/components/floating/index.vue

@@ -0,0 +1,181 @@
+<template>
+    <teleport :to="mount">
+        <div ref="vmRef" 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, ref } 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,
+    },
+    isTransform: { type: Boolean },
+    class: { type: String },
+    refer: { type: Object },
+    dire: { type: String },
+    width: { type: [Number, String] },
+    height: { type: [Number, String] },
+})
+const emit = defineEmits(['leave', 'enter', 'mouseenter', 'mouseleave'])
+const vmRef = ref()
+
+// 确定方向
+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(() => ({
+    width: width.value && width.value + 'px',
+    height: height.value && height.value + 'px',
+    left: location.x + 'px',
+    top: location.y + 'px',
+    zIndex: zIndex,
+}))
+
+const updateLocation = () => {
+    const pos = getPostionByTarget(props.refer, props.mount, false, props.isTransform)
+    let screenInfo
+    if (!props.isTransform) {
+        screenInfo = scrollParents.value.reduce(
+            (t, c) => {
+                t.y += c.scrollTop
+                t.x += c.scrollLeft
+                return t
+            },
+            { x: 0, y: 0 }
+        )
+    } else {
+        screenInfo = {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
+    }
+}
+
+const inSelf = ev => {
+    return (props.refer && props.refer.contains(ev.target)) || (vmRef.value && vmRef.value.contains(ev.target))
+}
+
+let first = false
+const moveHandler = ev => {
+    const isSelf = inSelf(ev)
+    if (isSelf === first) return
+
+    if (isSelf) {
+        emit('mouseenter')
+    } else {
+        emit('mouseleave')
+    }
+    first = isSelf
+}
+
+window.addEventListener('resize', updateLocation)
+document.documentElement.addEventListener('mousemove', moveHandler)
+onUnmounted(() => {
+    scrollParents.value.forEach(dom => dom.removeEventListener('scroll', updateLocation))
+    window.removeEventListener('resize', updateLocation)
+    document.documentElement.removeEventListener('mousemove', moveHandler)
+})
+
+onActivated(() => {
+    if (props.refer) {
+        updateLocation()
+    }
+})
+
+defineExpose({
+    updateLocation,
+})
+</script>
+
+<script>
+export default { name: 'ui-floating' }
+</script>

+ 1 - 0
src/components/bill-ui/components/gate/constant.js

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

+ 32 - 0
src/components/bill-ui/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
src/components/bill-ui/components/gate/index.js

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

+ 63 - 0
src/components/bill-ui/components/gate/layer.vue

@@ -0,0 +1,63 @@
+<template>
+  <div 
+    class="ui-gate-layer" 
+    :style="{
+      'height': normalizeUnitToStyle(height),
+      '--len': contentInstances.length, 
+      '--current': slideIndex,
+    }">
+    <div class="ui-gate-slides" :class="{absolute}">
+      <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({
+  absolute: {
+    type: Boolean,
+    default: false
+  },
+  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
src/components/bill-ui/components/group/constant.js

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

+ 4 - 0
src/components/bill-ui/components/group/index.js

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

+ 35 - 0
src/components/bill-ui/components/group/ui-group-option.vue

@@ -0,0 +1,35 @@
+<template>
+    <div class="group-option">
+        <span class="group-option-label" v-if="props.label">
+            {{ props.label }}
+            <slot name="icon" class="icon"></slot>
+        </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 && 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>

+ 69 - 0
src/components/bill-ui/components/group/ui-group.vue

@@ -0,0 +1,69 @@
+<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" ctrl 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 class="group-control-icon" type="pull-down" size="12px" ctrl v-if="control" />
+            </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>

+ 75 - 0
src/components/bill-ui/components/guide/index.vue

@@ -0,0 +1,75 @@
+<template>
+    <div class="guide" v-if="mount && (msg || $slots.default)" ref="guideRef" :class="{ 'floating-mode': floatClass }">
+        <slot name="content" :show="shouldShow"></slot>
+        <UIFloating v-if="floatClass" :mount="mountEl" :refer="guideRef" dire="bottom" :class="`guide-floating ${props.floatClass}  ${type}`">
+            <Bubble :show="shouldShow" class="guide-bubble" :type="type">
+                <template v-if="msg">
+                    <p class="default-msg">{{ msg }}</p>
+                </template>
+                <slot v-else></slot>
+
+                <span class="guide-close" @click="shouldShow = false">
+                    <ui-icon type="close" ctrl></ui-icon>
+                </span>
+            </Bubble>
+        </UIFloating>
+
+        <Bubble :show="shouldShow" class="guide-bubble" :type="type" v-else>
+            <template v-if="msg">
+                <p class="default-msg">{{ msg }}</p>
+            </template>
+            <slot v-else></slot>
+
+            <span class="guide-close" @click="shouldShow = false">
+                <ui-icon type="close" ctrl></ui-icon>
+            </span>
+        </Bubble>
+    </div>
+    <slot name="content" v-else :show="false"></slot>
+</template>
+
+<script setup>
+import Bubble from '../bubble'
+import UIFloating from '../floating/index.vue'
+import { ref, watch } from 'vue'
+
+const props = defineProps({
+    mark: {
+        type: String,
+    },
+    msg: {
+        type: String,
+    },
+    floatClass: {
+        type: String,
+    },
+    type: {
+        type: String,
+        default: 'top',
+    },
+})
+
+const mountEl = document.body
+const guideRef = ref()
+const shouldShow = ref(true)
+if (props.mark) {
+    shouldShow.value = !localStorage.getItem(props.mark)
+    if (shouldShow.value) {
+        watch(shouldShow, (newv, oldv) => {
+            if (!newv && oldv) {
+                setTimeout(() => {
+                    localStorage.setItem(props.mark, 1)
+                })
+            }
+        })
+    }
+} else {
+    shouldShow.value = true
+}
+
+const mount = ref(shouldShow.value)
+</script>
+
+<script>
+export default { name: 'ui-guide' }
+</script>

+ 37 - 0
src/components/bill-ui/components/icon/icon/index.vue

@@ -0,0 +1,37 @@
+<template>
+  <i :class="class" :style="style">
+    <i v-html="html"></i>
+    <slot ></slot>
+  </i>
+</template>
+
+<script lang="ts" setup>
+import { ref, watchEffect } from 'vue';
+import { useSvg } from './useSvg'
+
+const props = withDefaults(
+  defineProps<{
+    type: string,
+    fontSize?: string,
+    color?: string,
+    class: any
+    style: any
+  }>(),
+  {
+    fontSize: '14px',
+    color: '#fff'
+  }
+)
+const html = ref('')
+watchEffect(() => {
+  useSvg(props.type).then(h => html.value = h)
+})
+</script>
+
+<style lang="scss" scoped>
+i {
+  display: inline-flex;
+  align-items: center;
+  font-style:normal
+}
+</style>

文件差异内容过多而无法显示
+ 10 - 0
src/components/bill-ui/components/icon/icon/svg/a-connect-dis.svg


文件差异内容过多而无法显示
+ 10 - 0
src/components/bill-ui/components/icon/icon/svg/a-connect.svg


文件差异内容过多而无法显示
+ 11 - 0
src/components/bill-ui/components/icon/icon/svg/data_revise_n.svg


+ 10 - 0
src/components/bill-ui/components/icon/icon/svg/dianwei.svg

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>dianwei</title>
+    <g id="控件" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="dianwei" fill="#FFFFFF">
+            <path d="M8,0 C12.418278,0 16,3.581722 16,8 C16,12.418278 12.418278,16 8,16 C3.581722,16 0,12.418278 0,8 C0,3.581722 3.581722,0 8,0 Z M8,1 C4.13400675,1 1,4.13400675 1,8 C1,11.8659932 4.13400675,15 8,15 C11.8659932,15 15,11.8659932 15,8 C15,4.13400675 11.8659932,1 8,1 Z" id="椭圆形" fill-rule="nonzero"></path>
+            <path d="M8,2 C11.3137085,2 14,4.6862915 14,8 C14,11.3137085 11.3137085,14 8,14 C4.6862915,14 2,11.3137085 2,8 C2,4.6862915 4.6862915,2 8,2 Z M8,4 C5.790861,4 4,5.790861 4,8 C4,10.209139 5.790861,12 8,12 C10.209139,12 12,10.209139 12,8 C12,5.790861 10.209139,4 8,4 Z" id="形状结合" fill-opacity="0.5"></path>
+        </g>
+    </g>
+</svg>

+ 9 - 0
src/components/bill-ui/components/icon/icon/svg/m-r.svg

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>m-r</title>
+    <g id="控件" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="m-r" fill="#FFFFFF">
+            <path d="M5.5,0 C7.15685425,0 8.5,1.34314575 8.5,3 L11,3 C12.6568542,3 14,4.34314575 14,6 L14,10 C14,13.2383969 11.4344251,15.8775718 8.22493826,15.9958615 L8,16 L8,16 C4.6862915,16 2,13.3137085 2,10 L2,6 C2,4.34314575 3.34314575,3 5,3 L7.5,3 L7.5,3 C7.5,1.8954305 6.6045695,1 5.5,1 C5.22385763,1 5,0.776142375 5,0.5 C5,0.223857625 5.22385763,0 5.5,0 Z M11,4 L8,4 L8,8 L13,8 L13,6 C13,4.9456382 12.1841222,4.08183488 11.1492623,4.00548574 L11,4 Z" id="形状结合"></path>
+        </g>
+    </g>
+</svg>

+ 12 - 0
src/components/bill-ui/components/icon/icon/svg/point-s.svg

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>point-s</title>
+    <g id="控件" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="icon/16/icon_point-s" fill="#FFFFFF" fill-rule="nonzero">
+            <g id="point-s">
+                <path d="M8,1 C4.13400675,1 1,4.13400675 1,8 C1,11.8659932 4.13400675,15 8,15 C11.8659932,15 15,11.8659932 15,8 C15,4.13400675 11.8659932,1 8,1 Z" id="路径" fill-opacity="0.500464379"></path>
+                <path d="M8,3 C10.7614237,3 13,5.23857625 13,8 C13,10.7614237 10.7614237,13 8,13 C5.23857625,13 3,10.7614237 3,8 C3,5.23857625 5.23857625,3 8,3 Z" id="路径"></path>
+            </g>
+        </g>
+    </g>
+</svg>

文件差异内容过多而无法显示
+ 11 - 0
src/components/bill-ui/components/icon/icon/svg/state_gps.svg


文件差异内容过多而无法显示
+ 10 - 0
src/components/bill-ui/components/icon/icon/svg/weitiao.svg


文件差异内容过多而无法显示
+ 11 - 0
src/components/bill-ui/components/icon/icon/svg/zoom_n.svg


+ 21 - 0
src/components/bill-ui/components/icon/icon/useSvg.ts

@@ -0,0 +1,21 @@
+import axios from 'axios'
+
+const cache: { [key in string]: Promise<string>} = {}
+const temp = document.createElement('div')
+const loadSvg = async (type: string): Promise<string> => {
+  const data = await import(`./svg/${type}.svg`)
+  const res = await axios.get(data.default)
+
+  temp.innerHTML = res.data
+
+  const $svg = temp.querySelector('svg')
+  $svg.setAttribute('width', '1em')
+  $svg.setAttribute('height', '1em')
+  const $paths = $svg.querySelectorAll('path')
+  Array.from($paths).forEach($path => {
+    $path.setAttribute('fill', 'currentColor')
+  })
+  return temp.innerHTML
+}
+
+export const useSvg = (type: string) => cache[type] || (cache[type] = loadSvg(type))

+ 539 - 0
src/components/bill-ui/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;
+}

文件差异内容过多而无法显示
+ 2925 - 0
src/components/bill-ui/components/icon/iconfont/demo_index.html


+ 491 - 0
src/components/bill-ui/components/icon/iconfont/iconfont.css

@@ -0,0 +1,491 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 2930899 */
+  src: url('iconfont.woff2?t=1658474856187') format('woff2'),
+       url('iconfont.woff?t=1658474856187') format('woff'),
+       url('iconfont.ttf?t=1658474856187') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-point-s:before {
+  content: "\e6d9";
+}
+
+.icon-a-connect-dis:before {
+  content: "\e6d1";
+}
+
+.icon-data_revise_n:before {
+  content: "\e6d2";
+}
+
+.icon-zoom_n:before {
+  content: "\e6d4";
+}
+
+.icon-a-connect:before {
+  content: "\e6d0";
+}
+
+.icon-dianwei:before {
+  content: "\e6cc";
+}
+
+.icon-weitiao:before {
+  content: "\e6cd";
+}
+
+.icon-state_gps:before {
+  content: "\e6ce";
+}
+
+.icon-m-r:before {
+  content: "\e6cf";
+}
+
+.icon-lessen:before {
+  content: "\e6aa";
+}
+
+.icon-change1:before {
+  content: "\e6a8";
+}
+
+.icon-nav:before {
+  content: "\e6a6";
+}
+
+.icon-city:before {
+  content: "\e6ea";
+}
+
+.icon-pipeline:before {
+  content: "\e6eb";
+}
+
+.icon-users:before {
+  content: "\e6ec";
+}
+
+.icon-quanbu:before {
+  content: "\e6e8";
+}
+
+.icon-factory:before {
+  content: "\e6e9";
+}
+
+.icon-list:before {
+  content: "\e69c";
+}
+
+.icon-map-m:before {
+  content: "\e692";
+}
+
+.icon-extend:before {
+  content: "\e690";
+}
+
+.icon-shrink:before {
+  content: "\e691";
+}
+
+.icon-download:before {
+  content: "\e61c";
+}
+
+.icon-logo:before {
+  content: "\e68f";
+}
+
+.icon-d-r:before {
+  content: "\e68d";
+}
+
+.icon-up-a:before {
+  content: "\e68e";
+}
+
+.icon-menu:before {
+  content: "\e689";
+}
+
+.icon-user:before {
+  content: "\e68c";
+}
+
+.icon-password:before {
+  content: "\e68b";
+}
+
+.icon-hole:before {
+  content: "\e68a";
+}
+
+.icon-affirm:before {
+  content: "\e688";
+}
+
+.icon-cancel:before {
+  content: "\e687";
+}
+
+.icon-location:before {
+  content: "\e686";
+}
+
+.icon-pin1:before {
+  content: "\e685";
+}
+
+.icon-lock_s:before {
+  content: "\e683";
+}
+
+.icon-lock_n:before {
+  content: "\e684";
+}
+
+.icon-add_d:before {
+  content: "\e682";
+}
+
+.icon-uploading1:before {
+  content: "\e681";
+}
+
+.icon-move:before {
+  content: "\e680";
+}
+
+.icon-flip:before {
+  content: "\e67e";
+}
+
+.icon-rotate_r:before {
+  content: "\e67f";
+}
+
+.icon-rotate_l:before {
+  content: "\e67d";
+}
+
+.icon-pin:before {
+  content: "\e67c";
+}
+
+.icon-layout:before {
+  content: "\e67b";
+}
+
+.icon-change:before {
+  content: "\e67a";
+}
+
+.icon-iorigin:before {
+  content: "\e678";
+}
+
+.icon-destination: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";
+}
+

文件差异内容过多而无法显示
+ 1 - 0
src/components/bill-ui/components/icon/iconfont/iconfont.js


+ 842 - 0
src/components/bill-ui/components/icon/iconfont/iconfont.json

@@ -0,0 +1,842 @@
+{
+  "id": "2930899",
+  "name": "激光编辑器",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon-",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "30787879",
+      "name": "point-s",
+      "font_class": "point-s",
+      "unicode": "e6d9",
+      "unicode_decimal": 59097
+    },
+    {
+      "icon_id": "30490036",
+      "name": "a-connect-dis",
+      "font_class": "a-connect-dis",
+      "unicode": "e6d1",
+      "unicode_decimal": 59089
+    },
+    {
+      "icon_id": "30490118",
+      "name": "data_revise_n",
+      "font_class": "data_revise_n",
+      "unicode": "e6d2",
+      "unicode_decimal": 59090
+    },
+    {
+      "icon_id": "30490207",
+      "name": "zoom_n",
+      "font_class": "zoom_n",
+      "unicode": "e6d4",
+      "unicode_decimal": 59092
+    },
+    {
+      "icon_id": "30489498",
+      "name": "a-connect",
+      "font_class": "a-connect",
+      "unicode": "e6d0",
+      "unicode_decimal": 59088
+    },
+    {
+      "icon_id": "30474164",
+      "name": "dianwei",
+      "font_class": "dianwei",
+      "unicode": "e6cc",
+      "unicode_decimal": 59084
+    },
+    {
+      "icon_id": "30474165",
+      "name": "weitiao",
+      "font_class": "weitiao",
+      "unicode": "e6cd",
+      "unicode_decimal": 59085
+    },
+    {
+      "icon_id": "30474166",
+      "name": "state_gps",
+      "font_class": "state_gps",
+      "unicode": "e6ce",
+      "unicode_decimal": 59086
+    },
+    {
+      "icon_id": "30474167",
+      "name": "m-r",
+      "font_class": "m-r",
+      "unicode": "e6cf",
+      "unicode_decimal": 59087
+    },
+    {
+      "icon_id": "28884423",
+      "name": "lessen",
+      "font_class": "lessen",
+      "unicode": "e6aa",
+      "unicode_decimal": 59050
+    },
+    {
+      "icon_id": "28211031",
+      "name": "change",
+      "font_class": "change1",
+      "unicode": "e6a8",
+      "unicode_decimal": 59048
+    },
+    {
+      "icon_id": "28171163",
+      "name": "nav",
+      "font_class": "nav",
+      "unicode": "e6a6",
+      "unicode_decimal": 59046
+    },
+    {
+      "icon_id": "27876895",
+      "name": "city",
+      "font_class": "city",
+      "unicode": "e6ea",
+      "unicode_decimal": 59114
+    },
+    {
+      "icon_id": "27876896",
+      "name": "pipeline",
+      "font_class": "pipeline",
+      "unicode": "e6eb",
+      "unicode_decimal": 59115
+    },
+    {
+      "icon_id": "27876897",
+      "name": "users",
+      "font_class": "users",
+      "unicode": "e6ec",
+      "unicode_decimal": 59116
+    },
+    {
+      "icon_id": "27876893",
+      "name": "全部",
+      "font_class": "quanbu",
+      "unicode": "e6e8",
+      "unicode_decimal": 59112
+    },
+    {
+      "icon_id": "27876894",
+      "name": "factory",
+      "font_class": "factory",
+      "unicode": "e6e9",
+      "unicode_decimal": 59113
+    },
+    {
+      "icon_id": "27465117",
+      "name": "list",
+      "font_class": "list",
+      "unicode": "e69c",
+      "unicode_decimal": 59036
+    },
+    {
+      "icon_id": "27097406",
+      "name": "map-m",
+      "font_class": "map-m",
+      "unicode": "e692",
+      "unicode_decimal": 59026
+    },
+    {
+      "icon_id": "27032625",
+      "name": "extend",
+      "font_class": "extend",
+      "unicode": "e690",
+      "unicode_decimal": 59024
+    },
+    {
+      "icon_id": "27032630",
+      "name": "shrink",
+      "font_class": "shrink",
+      "unicode": "e691",
+      "unicode_decimal": 59025
+    },
+    {
+      "icon_id": "22099499",
+      "name": "download",
+      "font_class": "download",
+      "unicode": "e61c",
+      "unicode_decimal": 58908
+    },
+    {
+      "icon_id": "26996611",
+      "name": "logo",
+      "font_class": "logo",
+      "unicode": "e68f",
+      "unicode_decimal": 59023
+    },
+    {
+      "icon_id": "26914809",
+      "name": "d-r",
+      "font_class": "d-r",
+      "unicode": "e68d",
+      "unicode_decimal": 59021
+    },
+    {
+      "icon_id": "26914810",
+      "name": "up-a",
+      "font_class": "up-a",
+      "unicode": "e68e",
+      "unicode_decimal": 59022
+    },
+    {
+      "icon_id": "26911268",
+      "name": "menu",
+      "font_class": "menu",
+      "unicode": "e689",
+      "unicode_decimal": 59017
+    },
+    {
+      "icon_id": "26882575",
+      "name": "user",
+      "font_class": "user",
+      "unicode": "e68c",
+      "unicode_decimal": 59020
+    },
+    {
+      "icon_id": "26881742",
+      "name": "password",
+      "font_class": "password",
+      "unicode": "e68b",
+      "unicode_decimal": 59019
+    },
+    {
+      "icon_id": "26697834",
+      "name": "hole",
+      "font_class": "hole",
+      "unicode": "e68a",
+      "unicode_decimal": 59018
+    },
+    {
+      "icon_id": "26690677",
+      "name": "affirm",
+      "font_class": "affirm",
+      "unicode": "e688",
+      "unicode_decimal": 59016
+    },
+    {
+      "icon_id": "26690641",
+      "name": "cancel",
+      "font_class": "cancel",
+      "unicode": "e687",
+      "unicode_decimal": 59015
+    },
+    {
+      "icon_id": "26669098",
+      "name": "location",
+      "font_class": "location",
+      "unicode": "e686",
+      "unicode_decimal": 59014
+    },
+    {
+      "icon_id": "26656696",
+      "name": "pin",
+      "font_class": "pin1",
+      "unicode": "e685",
+      "unicode_decimal": 59013
+    },
+    {
+      "icon_id": "26651586",
+      "name": "lock_s",
+      "font_class": "lock_s",
+      "unicode": "e683",
+      "unicode_decimal": 59011
+    },
+    {
+      "icon_id": "26651587",
+      "name": "lock_n",
+      "font_class": "lock_n",
+      "unicode": "e684",
+      "unicode_decimal": 59012
+    },
+    {
+      "icon_id": "26631793",
+      "name": "add_d",
+      "font_class": "add_d",
+      "unicode": "e682",
+      "unicode_decimal": 59010
+    },
+    {
+      "icon_id": "26631750",
+      "name": "uploading",
+      "font_class": "uploading1",
+      "unicode": "e681",
+      "unicode_decimal": 59009
+    },
+    {
+      "icon_id": "26625859",
+      "name": "move",
+      "font_class": "move",
+      "unicode": "e680",
+      "unicode_decimal": 59008
+    },
+    {
+      "icon_id": "26625827",
+      "name": "flip",
+      "font_class": "flip",
+      "unicode": "e67e",
+      "unicode_decimal": 59006
+    },
+    {
+      "icon_id": "26625795",
+      "name": "rotate_r",
+      "font_class": "rotate_r",
+      "unicode": "e67f",
+      "unicode_decimal": 59007
+    },
+    {
+      "icon_id": "26625793",
+      "name": "rotate_l",
+      "font_class": "rotate_l",
+      "unicode": "e67d",
+      "unicode_decimal": 59005
+    },
+    {
+      "icon_id": "26621105",
+      "name": "pin",
+      "font_class": "pin",
+      "unicode": "e67c",
+      "unicode_decimal": 59004
+    },
+    {
+      "icon_id": "26508536",
+      "name": "layout",
+      "font_class": "layout",
+      "unicode": "e67b",
+      "unicode_decimal": 59003
+    },
+    {
+      "icon_id": "26392167",
+      "name": "change",
+      "font_class": "change",
+      "unicode": "e67a",
+      "unicode_decimal": 59002
+    },
+    {
+      "icon_id": "26385822",
+      "name": "iorigin",
+      "font_class": "iorigin",
+      "unicode": "e678",
+      "unicode_decimal": 59000
+    },
+    {
+      "icon_id": "26385823",
+      "name": "destination",
+      "font_class": "destination",
+      "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
+    }
+  ]
+}

二进制
src/components/bill-ui/components/icon/iconfont/iconfont.ttf


二进制
src/components/bill-ui/components/icon/iconfont/iconfont.woff


二进制
src/components/bill-ui/components/icon/iconfont/iconfont.woff2


+ 75 - 0
src/components/bill-ui/components/icon/index.vue

@@ -0,0 +1,75 @@
+<template>
+    <Icon class="icon ui-kankan-icon" :class="className" :style="style" :type="type" @click="ev => emit('click', ev)" v-if="svg">
+        <slot></slot>
+        <p class="tip" v-if="tip && os.isPc && !os.isTablet">{{ tip }}</p>
+    </Icon>
+    <i class="iconfont ui-kankan-icon icon" :class="className" :style="style" @click="ev => emit('click', ev)" v-else>
+        <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'
+import Icon from './icon/index.vue'
+import Tip from '../tip'
+
+const props = defineProps({
+    svg: {type: Boolean},
+    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',
+    },
+    foreShow: {
+        type: Boolean,
+    },
+})
+
+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,
+        'fore-show': props.foreShow,
+        [`tip-h-` + props.tipH]: true,
+        [`tip-v-` + props.tipV]: true,
+        ['fun-ctrl']: props.ctrl,
+    }
+    if (props.type) {
+        return {
+            ...base,
+            [`icon-${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
src/components/bill-ui/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
src/components/bill-ui/components/input/checkbox.vue

@@ -0,0 +1,21 @@
+<template>
+    <div class="input checkbox" :style="{ width, height }">
+        <input :id="id" type="checkbox" class="replace-input" :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>

+ 26 - 0
src/components/bill-ui/components/input/color.vue

@@ -0,0 +1,26 @@
+<template>
+    <div class="color input" :class="{ default: !$slots.replace }" :style="{ width, height }">
+        <input :name="name" :disabled="disabled" :id="id" :value="props.modelValue" type="color" class="replace-input" :checked="props.modelValue" @input="inputHandler" />
+        <span class="replace" v-if="$slots.replace">
+            <slot name="replace"></slot>
+        </span>
+    </div>
+</template>
+
+<script setup>
+import { colorPropsDesc } from './state'
+import { randomId } from '../../utils'
+import { defineProps, defineEmits, nextTick } from 'vue'
+const props = defineProps(colorPropsDesc)
+const emit = defineEmits(['update:modelValue'])
+const id = randomId(4)
+
+const inputHandler = ev => {
+    emit('update:modelValue', ev.target.value)
+    nextTick(() => {
+        if (ev.target.value !== props.modelValue.toString()) {
+            ev.target.value = props.modelValue.toString()
+        }
+    })
+}
+</script>

+ 194 - 0
src/components/bill-ui/components/input/file.vue

@@ -0,0 +1,194 @@
+<template>
+    <div class="input file" :class="{ suffix: $slots.icon, disabled: disabled, valuable }">
+        <template v-if="valuable">
+            <slot name="valuable" :key="modelValue" />
+        </template>
+        <input 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 { 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(() => {
+    if (props.maxSize) {
+        const mb = props.maxSize / 1024 / 1024
+        if (mb > 1024) {
+            return mb / 1024 + 'GB'
+        } else {
+            return mb + '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)
+        fileEl.value = ''
+    }
+
+    if (props.accept) {
+        for (const file of files) {
+            const accepts = props.accept.split(',').map(atom => atom.trim())
+            const hname = file.name.substr(file.name.lastIndexOf('.'))
+            if (!accepts.includes(hname)) {
+                return previewError('格式错误', `仅支持${props.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}`)
+            }
+        }
+    }
+
+    if (props.maxSize) {
+        for (const file of files) {
+            if (file.size > props.maxSize) {
+                return previewError('error size', `${file.name}的大小超过${sizeStr.value}`)
+            }
+        }
+    }
+
+    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}个文件!`)
+    }
+
+    emit('update:modelValue', emitData)
+    fileEl.value = ''
+}
+
+defineExpose({
+    input: inputRef,
+})
+</script>

+ 148 - 0
src/components/bill-ui/components/input/index.vue

@@ -0,0 +1,148 @@
+<template>
+    <div class="ui-input" v-if="types[type]" :style="style" :class="{ require: props.require, error: props.error, disabled }" @click="e => emit('click', e)">
+        <component :is="types[type].component" v-bind="childProps" :modelValue="props.modelValue" v-on="targetEmit" ref="vmRef">
+            <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, ref, getCurrentInstance, reactive } 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 color from './color.vue'
+import {
+    inputPropsDesc,
+    textPropsDesc,
+    selectPropsDesc,
+    checkboxPropsDesc,
+    radioPropsDesc,
+    rangePropsDesc,
+    numberPropsDesc,
+    switchPropsDesc,
+    textareaPropsDesc,
+    filePropsDesc,
+    searchPropsDesc,
+    richtextPropsDesc,
+    colorPropsDesc,
+    inputEmitDesc,
+    textEmitsDesc,
+} 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,
+    },
+    color: {
+        component: color,
+        propsDesc: colorPropsDesc,
+    },
+}
+
+const props = defineProps(inputPropsDesc)
+const vmRef = ref()
+const inputRef = ref()
+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
+    }
+    childProps.ref = inputRef
+    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
+})
+
+const instance = getCurrentInstance()
+const targetEmit = computed(() => {
+    const targetEmit = {}
+    const emits = inputEmitDesc[props.type] ? inputEmitDesc[props.type] : ['update:modelValue']
+    emits.forEach(key => {
+        targetEmit[key] = (...args) => {
+            instance.emit(key, ...args)
+        }
+    })
+    return targetEmit
+})
+
+const emit = defineEmits([...textEmitsDesc])
+defineExpose(reactive({ vmRef }))
+</script>
+
+<script>
+export default { name: 'ui-input' }
+</script>

+ 80 - 0
src/components/bill-ui/components/input/number.vue

@@ -0,0 +1,80 @@
+<template>
+    <UIText
+        :key="key"
+        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 key = ref(0)
+const blurHandler = () => {
+    if (props.modelValue) {
+        tempValue.value = props.modelValue.toString()
+    }
+    key.value++
+}
+
+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
src/components/bill-ui/components/input/password.vue

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

+ 28 - 0
src/components/bill-ui/components/input/radio.vue

@@ -0,0 +1,28 @@
+<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" 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>

+ 110 - 0
src/components/bill-ui/components/input/range.vue

@@ -0,0 +1,110 @@
+<template>
+    <div class="input range">
+        <div
+            class="range-content"
+            :class="{ animation: mode === modeEmun.default }"
+            :style="{
+                '--percentage': percenStyle,
+                '--slideSize': os.isPC && !os.isTablet ? 'calc(var(--height) + 8px)' : 'calc(var(--height) + 14px)',
+            }"
+            @click="rangeClickHandler"
+            ref="rangeRef"
+        >
+            <div class="range-locus" ref="locusRef">
+                <span class="range-slide" @click.stop @touchstart="slideDownHandler" @mousedown="slideDownHandler"></span>
+            </div>
+        </div>
+        <UInumber v-if="props.input" :modelValue="modelValue" @update:modelValue="inputUpdateHandler" :min="min" :max="max" :step="step" class="range-text" />
+    </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, defineProps, watchEffect } from 'vue'
+import { rangePropsDesc } from './state'
+import UInumber from './number.vue'
+import { os } from '../../utils/index'
+
+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() {
+        const val = (Number(props.modelValue) - props.min) / (props.max - props.min)
+        return val > props.max ? props.max : val
+    },
+    set(val) {
+        const len = props.max - props.min
+        emit('update:modelValue', getValue(props.min + len * val))
+    },
+})
+
+const percenStyle = computed(() => `${percen.value * 100}%`)
+
+const inputUpdateHandler = val => {
+    emit('update:modelValue', getValue(val))
+}
+
+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(() => {
+    locusWidth.value = locusRef.value.offsetWidth
+    rangeWidth.value = rangeRef.value.offsetWidth
+})
+
+const rangeClickHandler = ev => {
+    percen.value = ev.offsetX / rangeWidth.value
+}
+
+const parent = document.documentElement
+const slideDownHandler = ev => {
+    console.log(ev)
+    ev.preventDefault()
+    const moveStartX = ev.clientX || ev.touches[0].clientX
+    const startPercen = percen.value
+    mode.value = modeEmun.slide
+
+    const moveHandler = ev => {
+        ev.preventDefault()
+        const moveX = (ev.clientX || ev.touches[0].clientX) - moveStartX
+        const readyPercen = startPercen + moveX / locusWidth.value
+
+        percen.value = readyPercen < 0 ? 0 : readyPercen > 1 ? 1 : readyPercen
+    }
+
+    const upHandler = ev => {
+        mode.value = modeEmun.default
+        if (os.isPc && !os.isTablet) {
+            parent.removeEventListener('mousemove', moveHandler, false)
+            parent.removeEventListener('mouseup', upHandler, false)
+        } else {
+            parent.removeEventListener('touchmove', moveHandler)
+            parent.removeEventListener('touchend', upHandler)
+        }
+    }
+
+    if (os.isPc && !os.isTablet) {
+        parent.addEventListener('mousemove', moveHandler, false)
+        parent.addEventListener('mouseup', upHandler, false)
+    } else {
+        parent.addEventListener('touchmove', moveHandler, { passive: false })
+        parent.addEventListener('touchend', upHandler, { passive: false })
+    }
+}
+</script>

+ 168 - 0
src/components/bill-ui/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>

+ 0 - 0
src/components/bill-ui/components/input/search.vue


部分文件因为文件数量过多而无法显示