commit 1c30c4a1e25c56833eb0f2af834be333bcb5ea23 Author: riskcn <riskcn@163.com> Date: Tue Nov 5 09:59:02 2024 +0800 first commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3454886 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false diff --git a/.env b/.env new file mode 100644 index 0000000..5399ead --- /dev/null +++ b/.env @@ -0,0 +1,11 @@ +#VITE_APP_NAME=珞璜临港产业城 +#VITE_API_URL=http://apil.cqtlcm.com/api +#VITE_HOME_URL=http://lhyq.cqtlcm.com + +#VITE_APP_NAME=滨江新城 +#VITE_API_URL=http://apib.cqtlcm.com/api +#VITE_HOME_URL=http://bjxc.cqtlcm.com + +VITE_APP_NAME=科学城江津管委会 +VITE_API_URL=http://apik.cqtlcm.com/api +VITE_HOME_URL=http://kxc.cqtlcm.com \ No newline at end of file diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..5399ead --- /dev/null +++ b/.env.development @@ -0,0 +1,11 @@ +#VITE_APP_NAME=珞璜临港产业城 +#VITE_API_URL=http://apil.cqtlcm.com/api +#VITE_HOME_URL=http://lhyq.cqtlcm.com + +#VITE_APP_NAME=滨江新城 +#VITE_API_URL=http://apib.cqtlcm.com/api +#VITE_HOME_URL=http://bjxc.cqtlcm.com + +VITE_APP_NAME=科学城江津管委会 +VITE_API_URL=http://apik.cqtlcm.com/api +VITE_HOME_URL=http://kxc.cqtlcm.com \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..5399ead --- /dev/null +++ b/.env.production @@ -0,0 +1,11 @@ +#VITE_APP_NAME=珞璜临港产业城 +#VITE_API_URL=http://apil.cqtlcm.com/api +#VITE_HOME_URL=http://lhyq.cqtlcm.com + +#VITE_APP_NAME=滨江新城 +#VITE_API_URL=http://apib.cqtlcm.com/api +#VITE_HOME_URL=http://bjxc.cqtlcm.com + +VITE_APP_NAME=科学城江津管委会 +VITE_API_URL=http://apik.cqtlcm.com/api +VITE_HOME_URL=http://kxc.cqtlcm.com \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..46b1426 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +public +src/assets +dist +node_modules diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..3f64efc --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,78 @@ +// @ts-check +const { defineConfig } = require('eslint-define-config'); + +module.exports = defineConfig({ + root: true, + env: { + browser: true, + node: true, + es6: true + }, + parser: 'vue-eslint-parser', + extends: [ + 'plugin:vue/vue3-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended' + ], + parserOptions: { + parser: '@typescript-eslint/parser', + ecmaVersion: 2020, + sourceType: 'module', + jsxPragma: 'React', + ecmaFeatures: { + jsx: true + } + }, + rules: { + 'vue/script-setup-uses-vars': 'error', + '@typescript-eslint/ban-ts-ignore': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-empty-function': 'off', + 'vue/custom-event-name-casing': 'off', + 'no-use-before-define': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_' + } + ], + 'no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_' + } + ], + 'space-before-function-paren': 'off', + 'vue/attributes-order': 'off', + 'vue/one-component-per-file': 'off', + 'vue/html-closing-bracket-newline': 'off', + 'vue/max-attributes-per-line': 'off', + 'vue/multiline-html-element-content-newline': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/attribute-hyphenation': 'off', + 'vue/require-default-prop': 'off', + 'vue/html-self-closing': [ + 'error', + { + html: { + void: 'always', + normal: 'never', + component: 'always' + }, + svg: 'always', + math: 'always' + } + ], + 'vue/v-on-event-hyphenation': 'off', + 'vue/multi-word-component-names': 'off' + } +}); diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eca40d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +.DS_Store +node_modules +/dist +/dist-ssr + +# local env files +#.env.local +#.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +.pnpm-debug.log +.eslintcache + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f7e39e6 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +/dist/* +.local +.output.js +/node_modules/** + +**/*.svg +**/*.sh + +/public/* diff --git a/README.md b/README.md new file mode 100644 index 0000000..a797a27 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Vue 3 + Typescript + Vite + +This template should help get you started developing with Vue 3 and Typescript in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur). Make sure to enable `vetur.experimental.templateInterpolationService` in settings! + +### If Using `<script setup>` + +[`<script setup>`](https://github.com/vuejs/rfcs/pull/227) is a feature that is currently in RFC stage. To get proper IDE support for the syntax, use [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar) instead of Vetur (and disable Vetur). + +## 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 use the following: + +### If Using Volar + +Run `Volar: Switch TS Plugin on/off` from VSCode command palette. + +### If Using Vetur + +1. Install and add `@vuedx/typescript-plugin-vue` to the [plugins section](https://www.typescriptlang.org/tsconfig#plugins) in `tsconfig.json` +2. Delete `src/shims-vue.d.ts` as it is no longer needed to provide module info to Typescript +3. Open `src/main.ts` in VSCode +4. Open the VSCode command palette +5. Search and run "Select TypeScript version" -> "Use workspace version" diff --git a/components.d.ts b/components.d.ts new file mode 100644 index 0000000..6429be2 --- /dev/null +++ b/components.d.ts @@ -0,0 +1,2 @@ +import 'ant-design-vue/typings/global'; +import 'ele-admin-pro/typings/global'; diff --git a/index.html b/index.html new file mode 100644 index 0000000..f8aba46 --- /dev/null +++ b/index.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<html lang="zh"> + <head> + <meta charset="UTF-8" /> + <link rel="icon" href="/favicon.ico" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>EleAdminPro</title> + <style> + .ele-admin-loading { + width: 36px; + font-size: 0; + display: inline-block; + transform: rotate(45deg); + animation: loadingRotate 1.2s infinite linear; + position: relative; + top: calc(50% - 18px); + left: calc(50% - 18px); + } + + .ele-admin-loading span { + width: 10px; + height: 10px; + margin: 4px; + border-radius: 50%; + background: #1890ff; + display: inline-block; + opacity: 0.9; + } + + .ele-admin-loading span:nth-child(2) { + opacity: 0.7; + } + + .ele-admin-loading span:nth-child(3) { + opacity: 0.5; + } + + .ele-admin-loading span:nth-child(4) { + opacity: 0.3; + } + + @keyframes loadingRotate { + to { + transform: rotate(405deg); + } + } + + #app > .ele-admin-loading { + position: fixed; + } + </style> + </head> + <body> + <div id="app"> + <div class="ele-admin-loading"> + <span></span> + <span></span> + <span></span> + <span></span> + </div> + </div> + <script type="module" src="/src/main.ts"></script> + </body> +</html> diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..87253d8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8353 @@ +{ + "name": "ele-admin-pro-template", + "version": "1.11.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ele-admin-pro-template", + "version": "1.11.1", + "dependencies": { + "@amap/amap-jsapi-loader": "^1.0.1", + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-vue": "^6.1.0", + "@bytemd/plugin-gfm": "^1.21.0", + "ant-design-vue": "^3.2.17", + "axios": "^1.3.5", + "bytemd": "^1.21.0", + "countup.js": "^2.6.0", + "cropperjs": "^1.5.13", + "dayjs": "^1.11.7", + "echarts": "^5.4.2", + "echarts-wordcloud": "^2.1.0", + "ele-admin-pro": "^1.11.1", + "github-markdown-css": "^5.2.0", + "jsbarcode": "^3.11.5", + "lodash-es": "^4.17.21", + "nprogress": "^0.2.0", + "pinia": "^2.0.34", + "sortablejs": "^1.15.0", + "tinymce": "^5.10.7", + "vue": "^3.2.47", + "vue-echarts": "^6.5.4", + "vue-i18n": "^9.2.2", + "vue-router": "^4.1.6", + "vuedraggable": "^4.1.0", + "xgplayer": "^3.0.1", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.7", + "@types/node": "^18.15.11", + "@types/nprogress": "^0.2.0", + "@types/sortablejs": "^1.15.1", + "@typescript-eslint/eslint-plugin": "^5.58.0", + "@typescript-eslint/parser": "^5.58.0", + "@vitejs/plugin-legacy": "^4.0.2", + "@vitejs/plugin-vue": "^4.1.0", + "@vue/compiler-sfc": "^3.2.47", + "eslint": "^8.38.0", + "eslint-config-prettier": "^8.8.0", + "eslint-define-config": "^1.18.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-vue": "^9.11.0", + "less": "^4.1.3", + "postcss": "^8.4.22", + "prettier": "^2.8.7", + "rimraf": "^5.0.0", + "terser": "^5.16.9", + "typescript": "^5.0.4", + "unplugin-vue-components": "^0.24.1", + "vite": "^4.2.1", + "vite-plugin-compression": "^0.5.1", + "vue-eslint-parser": "^9.1.1", + "vue-tsc": "^1.2.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@amap/amap-jsapi-loader": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@amap/amap-jsapi-loader/-/amap-jsapi-loader-1.0.1.tgz", + "integrity": "sha512-nPyLKt7Ow/ThHLkSvn2etQlUzqxmTVgK7bIgwdBRTg2HK5668oN7xVxkaiRe3YZEzGzfV2XgH5Jmu2T73ljejw==" + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.0.0.tgz", + "integrity": "sha512-iVm/9PfGCbC0dSMBrz7oiEXZaaGH7ceU40OJEfKmyuzR9R5CRimJYPlRiFtMQGQcbNMea/ePcoIebi4ASGYXtg==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.3.0.tgz", + "integrity": "sha512-WOgvdH/1Wl8Z7VXigRbCa5djO14zxrNTzvrAQzhWiBQtEKT0uTc8K1ltjKZ8U1gPn/wXhMA8/jE39SJl0WNxSg==" + }, + "node_modules/@ant-design/icons-vue": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons-vue/-/icons-vue-6.1.0.tgz", + "integrity": "sha512-EX6bYm56V+ZrKN7+3MT/ubDkvJ5rK/O2t380WFRflDcVFgsvl3NLH7Wxeau6R8DbrO5jWR6DSTC3B6gYFp77AA==", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-svg": "^4.2.1" + }, + "peerDependencies": { + "vue": ">=3.0.3" + } + }, + "node_modules/@ant-design/icons-vue/node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.5.tgz", + "integrity": "sha512-dlR6LdS+0SzOAPx/TPRhnoi7hE251OVeT2Snw0RguNbBSbjUHdWr0l3vcUUDg26rEysT89kCbtw1lVorBXLLCg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", + "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.10", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.10.tgz", + "integrity": "sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.10", + "@babel/generator": "^7.22.10", + "@babel/helper-compilation-targets": "^7.22.10", + "@babel/helper-module-transforms": "^7.22.9", + "@babel/helpers": "^7.22.10", + "@babel/parser": "^7.22.10", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.10", + "@babel/types": "^7.22.10", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", + "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.10", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.10.tgz", + "integrity": "sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz", + "integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz", + "integrity": "sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz", + "integrity": "sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz", + "integrity": "sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", + "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", + "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz", + "integrity": "sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-wrap-function": "^7.22.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz", + "integrity": "sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz", + "integrity": "sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.10.tgz", + "integrity": "sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.10", + "@babel/types": "^7.22.10" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", + "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", + "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", + "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", + "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", + "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", + "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", + "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz", + "integrity": "sha512-eueE8lvKVzq5wIObKK/7dvoeKJ+xc6TvRn6aysIjS6pSCeLy7S/eVi7pEQknZqyqvzaNKdDtem8nUNTBgDVR2g==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", + "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.10.tgz", + "integrity": "sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", + "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", + "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", + "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", + "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.10.tgz", + "integrity": "sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", + "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", + "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", + "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", + "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", + "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", + "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", + "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", + "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", + "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", + "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", + "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", + "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", + "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", + "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", + "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", + "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", + "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", + "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", + "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", + "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", + "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz", + "integrity": "sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", + "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", + "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", + "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", + "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz", + "integrity": "sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", + "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", + "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", + "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", + "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", + "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", + "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz", + "integrity": "sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", + "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", + "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", + "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.10.tgz", + "integrity": "sha512-riHpLb1drNkpLlocmSyEg4oYJIQFeXAK/d7rI6mbD0XsvoTOOweXDmQPG/ErxsEhWk3rl3Q/3F6RFQlVFS8m0A==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.10", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.10", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.22.10", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.6", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.22.10", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.5", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.5", + "@babel/plugin-transform-for-of": "^7.22.5", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.5", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-modules-systemjs": "^7.22.5", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", + "@babel/plugin-transform-numeric-separator": "^7.22.5", + "@babel/plugin-transform-object-rest-spread": "^7.22.5", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.10", + "@babel/plugin-transform-parameters": "^7.22.5", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.5", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.10", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.10", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "@babel/types": "^7.22.10", + "babel-plugin-polyfill-corejs2": "^0.4.5", + "babel-plugin-polyfill-corejs3": "^0.8.3", + "babel-plugin-polyfill-regenerator": "^0.5.2", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.10.tgz", + "integrity": "sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", + "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.10", + "@babel/generator": "^7.22.10", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.10", + "@babel/types": "^7.22.10", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.22.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", + "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bytemd/plugin-gfm": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@bytemd/plugin-gfm/-/plugin-gfm-1.21.0.tgz", + "integrity": "sha512-ZlrLa+Nl80gUDeC1hTnyRDfgJU3DGQVjQvX9rIIitUCler+KsAiagEnng6S/W2SZNpv+f8eWpVNL8KA8X3d7Tg==", + "dependencies": { + "remark-gfm": "^3.0.1" + }, + "peerDependencies": { + "bytemd": "^1.5.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.0.tgz", + "integrity": "sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz", + "integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@intlify/core-base": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz", + "integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==", + "dependencies": { + "@intlify/devtools-if": "9.2.2", + "@intlify/message-compiler": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@intlify/devtools-if": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz", + "integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==", + "dependencies": { + "@intlify/shared": "9.2.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz", + "integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==", + "dependencies": { + "@intlify/shared": "9.2.2", + "source-map": "0.6.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@intlify/shared": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz", + "integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@intlify/vue-devtools": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz", + "integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==", + "dependencies": { + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.3.tgz", + "integrity": "sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@simonwep/pickr": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@simonwep/pickr/-/pickr-1.8.2.tgz", + "integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==", + "dependencies": { + "core-js": "^3.15.1", + "nanopop": "^2.1.0" + } + }, + "node_modules/@types/codemirror": { + "version": "5.60.8", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" + }, + "node_modules/@types/hast": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.5.tgz", + "integrity": "sha512-SvQi0L/lNpThgPoleH53cdjB3y9zpLlVjRbqB3rH8hx1jiRSBGAhyjV3H+URFjNVRqt2EdYNrbZE5IsGlNfpRg==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.197", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.197.tgz", + "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.8", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.8.tgz", + "integrity": "sha512-euY3XQcZmIzSy7YH5+Unb3b2X12Wtk54YWINBvvGQ5SmMvwb11JQskGsfkH/5HXK77Kr8GF0wkVDIxzAisWtog==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/mdast": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.12.tgz", + "integrity": "sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + }, + "node_modules/@types/node": { + "version": "18.17.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.5.tgz", + "integrity": "sha512-xNbS75FxH6P4UXTPUJp/zNPq6/xsfdJKussCWNOnz4aULWIRwMgP1LgaB5RiBnMX1DPCYenuqGZfnIAx5mbFLA==", + "dev": true + }, + "node_modules/@types/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@types/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-1cYJrqq9GezNFPsWTZpFut/d4CjpZqA0vhqDUPFWYKF1oIyBz5qnoYMzR+0C/T96t3ebLAC1SSnwrVOm5/j74A==", + "dev": true + }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==" + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@types/sortablejs": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.1.tgz", + "integrity": "sha512-g/JwBNToh6oCTAwNS8UGVmjO7NLDKsejVhvE4x1eWiPTC3uCuNsa/TD4ssvX3du+MLiM+SHPNDuijp8y76JzLQ==", + "dev": true + }, + "node_modules/@types/tern": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", + "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/unist": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.7.tgz", + "integrity": "sha512-cputDpIbFgLUaGQn6Vqg3/YsJwxUwHLO13v3i5ouxT4lat0khip9AEWxtERujXV9wxIB1EyF97BSJFt6vpdI8g==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-legacy": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-4.1.1.tgz", + "integrity": "sha512-um3gbVouD2Q/g19C0qpDfHwveXDCAHzs8OC3e9g6aXpKoD1H14himgs7wkMnhAynBJy7QqUoZNAXDuqN8zLR2g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.22.9", + "@babel/preset-env": "^7.22.9", + "browserslist": "^4.21.9", + "core-js": "^3.31.1", + "magic-string": "^0.30.1", + "regenerator-runtime": "^0.13.11", + "systemjs": "^6.14.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "peerDependencies": { + "terser": "^5.4.0", + "vite": "^4.0.0" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", + "integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==", + "dev": true, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.0.tgz", + "integrity": "sha512-ddyWwSYqcbEZNFHm+Z3NZd6M7Ihjcwl/9B5cZd8kECdimVXUFdFi60XHWD27nrWtUQIsUYIG7Ca1WBwV2u2LSQ==", + "dev": true, + "dependencies": { + "@volar/source-map": "1.10.0" + } + }, + "node_modules/@volar/source-map": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.0.tgz", + "integrity": "sha512-/ibWdcOzDGiq/GM1JU2eX8fH1bvAhl66hfe8yEgLEzg9txgr6qb5sQ/DEz5PcDL75tF5H5sCRRwn8Eu8ezi9mw==", + "dev": true, + "dependencies": { + "muggle-string": "^0.3.1" + } + }, + "node_modules/@volar/typescript": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.0.tgz", + "integrity": "sha512-OtqGtFbUKYC0pLNIk3mHQp5xWnvL1CJIUc9VE39VdZ/oqpoBh5jKfb9uJ45Y4/oP/WYTrif/Uxl1k8VTPz66Gg==", + "dev": true, + "dependencies": { + "@volar/language-core": "1.10.0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "dependencies": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "dependencies": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", + "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" + }, + "node_modules/@vue/language-core": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.8.tgz", + "integrity": "sha512-i4KMTuPazf48yMdYoebTkgSOJdFraE4pQf0B+FTOFkbB+6hAfjrSou/UmYWRsWyZV6r4Rc6DDZdI39CJwL0rWw==", + "dev": true, + "dependencies": { + "@volar/language-core": "~1.10.0", + "@volar/source-map": "~1.10.0", + "@vue/compiler-dom": "^3.3.0", + "@vue/reactivity": "^3.3.0", + "@vue/shared": "^3.3.0", + "minimatch": "^9.0.0", + "muggle-string": "^0.3.1", + "vue-template-compiler": "^2.7.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "dependencies": { + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", + "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "dependencies": { + "@vue/reactivity": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", + "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "dependencies": { + "@vue/runtime-core": "3.3.4", + "@vue/shared": "3.3.4", + "csstype": "^3.1.1" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", + "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "dependencies": { + "@vue/compiler-ssr": "3.3.4", + "@vue/shared": "3.3.4" + }, + "peerDependencies": { + "vue": "3.3.4" + } + }, + "node_modules/@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" + }, + "node_modules/@vue/typescript": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.8.tgz", + "integrity": "sha512-jUnmMB6egu5wl342eaUH236v8tdcEPXXkPgj+eI/F6JwW/lb+yAU6U07ZbQ3MVabZRlupIlPESB7ajgAGixhow==", + "dev": true, + "dependencies": { + "@volar/typescript": "~1.10.0", + "@vue/language-core": "1.8.8" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ant-design-vue": { + "version": "3.2.20", + "resolved": "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-3.2.20.tgz", + "integrity": "sha512-YWpMfGaGoRastIXEYfCoJiaRiDHk4chqtYhlKQM5GqPt6NfvrM1Vg2e60yHtjxlZjed91wCMm0rAmyUr7Hwzdg==", + "dependencies": { + "@ant-design/colors": "^6.0.0", + "@ant-design/icons-vue": "^6.1.0", + "@babel/runtime": "^7.10.5", + "@ctrl/tinycolor": "^3.4.0", + "@simonwep/pickr": "~1.8.0", + "array-tree-filter": "^2.1.0", + "async-validator": "^4.0.0", + "dayjs": "^1.10.5", + "dom-align": "^1.12.1", + "dom-scroll-into-view": "^2.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.15", + "resize-observer-polyfill": "^1.5.1", + "scroll-into-view-if-needed": "^2.2.25", + "shallow-equal": "^1.0.0", + "vue-types": "^3.0.0", + "warning": "^4.0.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design-vue" + }, + "peerDependencies": { + "vue": ">=3.2.0" + } + }, + "node_modules/ant-design-vue/node_modules/@ant-design/colors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-6.0.0.tgz", + "integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==", + "dependencies": { + "@ctrl/tinycolor": "^3.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-tree-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-tree-filter/-/array-tree-filter-2.1.0.tgz", + "integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz", + "integrity": "sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz", + "integrity": "sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.2", + "core-js-compat": "^3.31.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz", + "integrity": "sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytemd": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/bytemd/-/bytemd-1.21.0.tgz", + "integrity": "sha512-6nc658omwzcLdc/lT24w8G2x5pptZXiMyrQPbFuHwhYbmrLnsmKLm+9klsOx2/Lg2cYHYb2WzVh7zKZ9MZCVdg==", + "dependencies": { + "@popperjs/core": "^2.11.7", + "@types/codemirror": "^5.60.7", + "@types/hast": "^2.3.4", + "@types/lodash-es": "^4.17.7", + "@types/mdast": "^3.0.11", + "codemirror-ssr": "^0.65.0", + "hast-util-sanitize": "^4.1.0", + "lodash-es": "^4.17.21", + "rehype-raw": "^6.1.1", + "rehype-sanitize": "^5.0.1", + "rehype-stringify": "^9.0.3", + "remark-parse": "^10.0.1", + "remark-rehype": "^10.1.0", + "select-files": "^1.0.1", + "tippy.js": "^6.3.7", + "unified": "^10.1.2", + "unist-util-visit": "^4.1.2", + "vfile": "^5.3.7", + "word-count": "^0.2.2" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001520", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001520.tgz", + "integrity": "sha512-tahF5O9EiiTzwTUqAeFjIZbn4Dnqxzz7ktrgGlMYNLH43Ul26IgTMH/zvL3DG0lZxBYnlT04axvInszUsZULdA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/codemirror-ssr": { + "version": "0.65.0", + "resolved": "https://registry.npmjs.org/codemirror-ssr/-/codemirror-ssr-0.65.0.tgz", + "integrity": "sha512-ofTAfPkQV56SYFfymNMYJ1ELo3+Jnkw3mOLgnIiQjhonwNmNzX1OFvnihAnYRXL0PWl2kT7s0gKrLc2ExshK4g==", + "peerDependencies": { + "@types/codemirror": "^5.0.0" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/compute-scroll-into-view": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-js": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.32.0.tgz", + "integrity": "sha512-rd4rYZNlF3WuoYuRIDEmbR/ga9CeuWX9U05umAvgrrZoHY4Z++cp/xwPQMvUpBB4Ag6J8KfD80G0zwCyaSxDww==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.0.tgz", + "integrity": "sha512-7a9a3D1k4UCVKnLhrgALyFcP7YCsLOQIxPd0dKjf/6GuPcgyiGP70ewWdCGrSK7evyhymi0qO4EqCmSJofDeYw==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.9" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/countup.js": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.7.0.tgz", + "integrity": "sha512-IP9nYLGgW//0If73eXQdFlReGhpFGHaStqB1v82FknxnUWueF6HFuuOXySW4sEDMc88PsZL1EOn/NPkfTZalmQ==" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cropperjs": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.13.tgz", + "integrity": "sha512-by7jKAo73y5/Do0K6sxdTKHgndY0NMjG2bEdgeJxycbcmHuCiMXqw8sxy5C5Y5WTOTcDGmbT7Sr5CgKOXR06OA==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/danmu.js": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/danmu.js/-/danmu.js-1.1.11.tgz", + "integrity": "sha512-beL1hDnjvzvvXs2CuearTybkB+VzdyrMsBgRxIRO9FD6r5TakJ7O5D72sZz7Q7rfV1jwDUO1zlFwxrS0SNZTBQ==", + "dependencies": { + "event-emitter": "^0.3.5" + } + }, + "node_modules/dayjs": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", + "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-align": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.4.tgz", + "integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw==" + }, + "node_modules/dom-scroll-into-view": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz", + "integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w==" + }, + "node_modules/downloadjs": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/downloadjs/-/downloadjs-1.4.7.tgz", + "integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q==" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/echarts": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", + "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.4.4" + } + }, + "node_modules/echarts-wordcloud": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/echarts-wordcloud/-/echarts-wordcloud-2.1.0.tgz", + "integrity": "sha512-Kt1JmbcROgb+3IMI48KZECK2AP5lG6bSsOEs+AsuwaWJxQom31RTNd6NFYI01E/YaI1PFZeueaupjlmzSQasjQ==", + "peerDependencies": { + "echarts": "^5.0.1" + } + }, + "node_modules/ele-admin-pro": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/ele-admin-pro/-/ele-admin-pro-1.11.1.tgz", + "integrity": "sha512-3gTCTrQ6XCnfMP8nNZZFXaoRcNpOMLpK2mEsu+21f3w5ID/BFhyOxWSngJL/LMPelwSpK+PwRclp0r3Wu16J7Q==", + "peerDependencies": { + "ant-design-vue": ">=3.1.0", + "vue": ">=3.1.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.490", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz", + "integrity": "sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/es5-ext": { + "version": "0.10.62", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", + "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "hasInstallScript": true, + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.47.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz", + "integrity": "sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "^8.47.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz", + "integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-define-config": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/eslint-define-config/-/eslint-define-config-1.23.0.tgz", + "integrity": "sha512-4mMyu0JuBkQHsCtR+42irIQdFLmLIW+pMAVcyOV/gZRL4O1R8iuH0eMG3oL3Cbi1eo9fDAfT5CIHVHgdyxcf6w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/Shinigami92" + }, + { + "type": "paypal", + "url": "https://www.paypal.com/donate/?hosted_button_id=L7GY729FBKTZY" + } + ], + "engines": { + "node": "^16.13.0 || >=18.0.0", + "npm": ">=7.0.0", + "pnpm": ">= 8.6.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", + "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.17.0.tgz", + "integrity": "sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.13", + "semver": "^7.5.4", + "vue-eslint-parser": "^9.3.1", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "dependencies": { + "type": "^2.7.2" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/github-markdown-css": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.2.0.tgz", + "integrity": "sha512-hq5RaCInSUZ48bImOZpkppW2/MT44StRgsbsZ8YA4vJFwLKB/Vo3k7R2t+pUGqO+ThG0QDMi96TewV/B3vyItg==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.3.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.3.tgz", + "integrity": "sha512-92vPiMb/iqpmEgsOoIDvTjc50wf9CCCvMzsi6W0JLPeUKE8TWP1a73PgqSrqy7iAZxaSD1YdzU7QZR5LF51MJw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.0.3", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-sanitize": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.1.0.tgz", + "integrity": "sha512-Hd9tU0ltknMGRDv+d6Ro/4XKzBqQnP/EZrpiTbpFYfXv/uOhWeKc+2uajcbEvAEH98VZd7eII2PiXm13RihnLw==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", + "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^7.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.3.tgz", + "integrity": "sha512-pF0kfjmg8DJLxDrizHoCZGUFz4P4czQ3HyfW4BU0ffebYkzAVlBywp5zaxW/TM+r0sGbmrQdi8EQQVTJFxnGsQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbarcode": { + "version": "3.11.5", + "resolved": "https://registry.npmjs.org/jsbarcode/-/jsbarcode-3.11.5.tgz", + "integrity": "sha512-zv3KsH51zD00I/LrFzFSM6dst7rDn0vIMzaiZFL7qusTjPZiPtxg3zxetp0RR7obmjTw4f6NyGgbdkBCgZUIrA==", + "bin": { + "auto.js": "bin/barcodes/CODE128/auto.js", + "Barcode.js": "bin/barcodes/Barcode.js", + "barcodes": "bin/barcodes", + "canvas.js": "bin/renderers/canvas.js", + "checksums.js": "bin/barcodes/MSI/checksums.js", + "codabar": "bin/barcodes/codabar", + "CODE128": "bin/barcodes/CODE128", + "CODE128_AUTO.js": "bin/barcodes/CODE128/CODE128_AUTO.js", + "CODE128.js": "bin/barcodes/CODE128/CODE128.js", + "CODE128A.js": "bin/barcodes/CODE128/CODE128A.js", + "CODE128B.js": "bin/barcodes/CODE128/CODE128B.js", + "CODE128C.js": "bin/barcodes/CODE128/CODE128C.js", + "CODE39": "bin/barcodes/CODE39", + "constants.js": "bin/barcodes/ITF/constants.js", + "defaults.js": "bin/options/defaults.js", + "EAN_UPC": "bin/barcodes/EAN_UPC", + "EAN.js": "bin/barcodes/EAN_UPC/EAN.js", + "EAN13.js": "bin/barcodes/EAN_UPC/EAN13.js", + "EAN2.js": "bin/barcodes/EAN_UPC/EAN2.js", + "EAN5.js": "bin/barcodes/EAN_UPC/EAN5.js", + "EAN8.js": "bin/barcodes/EAN_UPC/EAN8.js", + "encoder.js": "bin/barcodes/EAN_UPC/encoder.js", + "ErrorHandler.js": "bin/exceptions/ErrorHandler.js", + "exceptions": "bin/exceptions", + "exceptions.js": "bin/exceptions/exceptions.js", + "fixOptions.js": "bin/help/fixOptions.js", + "GenericBarcode": "bin/barcodes/GenericBarcode", + "getOptionsFromElement.js": "bin/help/getOptionsFromElement.js", + "getRenderProperties.js": "bin/help/getRenderProperties.js", + "help": "bin/help", + "index.js": "bin/renderers/index.js", + "index.tmp.js": "bin/barcodes/index.tmp.js", + "ITF": "bin/barcodes/ITF", + "ITF.js": "bin/barcodes/ITF/ITF.js", + "ITF14.js": "bin/barcodes/ITF/ITF14.js", + "JsBarcode.js": "bin/JsBarcode.js", + "linearizeEncodings.js": "bin/help/linearizeEncodings.js", + "merge.js": "bin/help/merge.js", + "MSI": "bin/barcodes/MSI", + "MSI.js": "bin/barcodes/MSI/MSI.js", + "MSI10.js": "bin/barcodes/MSI/MSI10.js", + "MSI1010.js": "bin/barcodes/MSI/MSI1010.js", + "MSI11.js": "bin/barcodes/MSI/MSI11.js", + "MSI1110.js": "bin/barcodes/MSI/MSI1110.js", + "object.js": "bin/renderers/object.js", + "options": "bin/options", + "optionsFromStrings.js": "bin/help/optionsFromStrings.js", + "pharmacode": "bin/barcodes/pharmacode", + "renderers": "bin/renderers", + "shared.js": "bin/renderers/shared.js", + "svg.js": "bin/renderers/svg.js", + "UPC.js": "bin/barcodes/EAN_UPC/UPC.js", + "UPCE.js": "bin/barcodes/EAN_UPC/UPCE.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", + "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", + "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", + "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", + "dependencies": { + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", + "dependencies": { + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", + "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", + "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", + "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", + "dependencies": { + "@types/mdast": "^3.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", + "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", + "dependencies": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", + "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", + "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", + "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", + "dependencies": { + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", + "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", + "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", + "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", + "dependencies": { + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", + "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", + "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nanopop": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/nanopop/-/nanopop-2.3.0.tgz", + "integrity": "sha512-fzN+T2K7/Ah25XU02MJkPZ5q4Tj5FpjmIYq4rvoHX4yb16HzFdCO6JxFFn5Y/oBhQ8no8fUZavnyIv9/+xkBBw==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/needle": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.2.0.tgz", + "integrity": "sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==", + "dev": true, + "optional": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinia": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz", + "integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.0", + "vue-demi": ">=0.14.5" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.3.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.4.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz", + "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/property-information": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz", + "integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", + "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/rehype-raw": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-6.1.1.tgz", + "integrity": "sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==", + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-raw": "^7.2.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-sanitize": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-5.0.1.tgz", + "integrity": "sha512-da/jIOjq8eYt/1r9GN6GwxIR3gde7OZ+WV8pheu1tL8K0D9KxM2AyMh+UEfke+FfdM3PvGHeYJU0Td5OWa7L5A==", + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-sanitize": "^4.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-9.0.3.tgz", + "integrity": "sha512-kWiZ1bgyWlgOxpqD5HnxShKAdXtb2IUljn3hQAhySeak6IOQPPt6DeGnsIh4ixm7yKJWzm8TXFuC/lPfcWHJqw==", + "dependencies": { + "@types/hast": "^2.0.0", + "hast-util-to-html": "^8.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz", + "integrity": "sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-to-hast": "^12.1.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resize-detector": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/resize-detector/-/resize-detector-0.3.0.tgz", + "integrity": "sha512-R/tCuvuOHQ8o2boRP6vgx8hXCCy87H1eY9V5imBYeVNyNVpuL9ciReSccLj2gDcax9+2weXy3bc8Vv+NRXeEvQ==" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, + "node_modules/resolve": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", + "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz", + "integrity": "sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg==", + "dev": true, + "dependencies": { + "glob": "^10.2.5" + }, + "bin": { + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.0.tgz", + "integrity": "sha512-d7zhvo1OUY2SXSM6pfNjgD5+d0Nz87CUp4mt8l/GgVP3oBsPwzNvSzyu1me6BSG9JIgWNTVcafIXBIyM8yQ3yw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "optional": true + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true, + "optional": true + }, + "node_modules/scroll-into-view-if-needed": { + "version": "2.2.31", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", + "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "dependencies": { + "compute-scroll-into-view": "^1.0.20" + } + }, + "node_modules/select-files": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/select-files/-/select-files-1.0.1.tgz", + "integrity": "sha512-8h4DSpjfFa0hyMP3z3ye4SxyhdaE5RgaXeScRpH7xl4YblnZSHwexmLdLNdSKwTO8H9ccDKj7Votz0io+18+BQ==" + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/shallow-equal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.1.tgz", + "integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sortablejs": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", + "integrity": "sha512-bv9qgVMjUMf89wAvM6AxVvS/4MX3sPeN0+agqShejLU5z5GX4C75ow1O2e5k4L6XItUyAK3gH6AxSbXrOM5e8w==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/systemjs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.14.1.tgz", + "integrity": "sha512-8ftwWd+XnQtZ/aGbatrN4QFNGrKJzmbtixW+ODpci7pyoTajg4sonPP8aFLESAcuVxaC1FyDESt+SpfFCH9rZQ==", + "dev": true + }, + "node_modules/terser": { + "version": "5.19.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", + "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tinymce": { + "version": "5.10.7", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-5.10.7.tgz", + "integrity": "sha512-9UUjaO0R7FxcFo0oxnd1lMs7H+D0Eh+dDVo5hKbVe1a+VB0nit97vOqlinj+YwgoBDt6/DSCUoWqAYlLI8BLYA==" + }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unplugin": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.4.0.tgz", + "integrity": "sha512-5x4eIEL6WgbzqGtF9UV8VEC/ehKptPXDS6L2b0mv4FRMkJxRtjaJfOWDd6a8+kYbqsjklix7yWP0N3SUepjXcg==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "chokidar": "^3.5.3", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.5.0" + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.24.1.tgz", + "integrity": "sha512-T3A8HkZoIE1Cja95xNqolwza0yD5IVlgZZ1PVAGvVCx8xthmjsv38xWRCtHtwl+rvZyL9uif42SRkDGw9aCfMA==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.7.2", + "@rollup/pluginutils": "^5.0.2", + "chokidar": "^3.5.3", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.0", + "minimatch": "^7.4.2", + "resolve": "^1.22.1", + "unplugin": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/unplugin-vue-components/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", + "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-compression": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz", + "integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "debug": "^4.3.3", + "fs-extra": "^10.0.0" + }, + "peerDependencies": { + "vite": ">=2.0.0" + } + }, + "node_modules/vite-plugin-compression/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/vite-plugin-compression/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/vite-plugin-compression/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/vite-plugin-compression/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/vite-plugin-compression/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/vite-plugin-compression/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/vue": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", + "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/vue-echarts": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/vue-echarts/-/vue-echarts-6.6.1.tgz", + "integrity": "sha512-EpreTzlNeJ+eaUn0AhXEmKJk98xJGecgTqAdyZovoXWnhTxnlW2HuBM0ei3y8rLw1JCUabf8/sYvxjlr8SzBKQ==", + "hasInstallScript": true, + "dependencies": { + "resize-detector": "^0.3.0", + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.5", + "echarts": "^5.4.1", + "vue": "^2.6.12 || ^3.1.1" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-echarts/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz", + "integrity": "sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/vue-i18n": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz", + "integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==", + "dependencies": { + "@intlify/core-base": "9.2.2", + "@intlify/shared": "9.2.2", + "@intlify/vue-devtools": "9.2.2", + "@vue/devtools-api": "^6.2.1" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-router": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz", + "integrity": "sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", + "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.8.tgz", + "integrity": "sha512-bSydNFQsF7AMvwWsRXD7cBIXaNs/KSjvzWLymq/UtKE36697sboX4EccSHFVxvgdBlI1frYPc/VMKJNB7DFeDQ==", + "dev": true, + "dependencies": { + "@vue/language-core": "1.8.8", + "@vue/typescript": "1.8.8", + "semver": "^7.3.8" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/vue-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/vue-types/-/vue-types-3.0.2.tgz", + "integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==", + "dependencies": { + "is-plain-object": "3.0.1" + }, + "engines": { + "node": ">=10.15.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vuedraggable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", + "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==", + "dependencies": { + "sortablejs": "1.14.0" + }, + "peerDependencies": { + "vue": "^3.0.1" + } + }, + "node_modules/vuedraggable/node_modules/sortablejs": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz", + "integrity": "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word-count": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/word-count/-/word-count-0.2.2.tgz", + "integrity": "sha512-tPRTbQ+nTCPY3F0z1f/y0PX22ScE6l/4/8j9KqA3h77JhlZ/w6cbVS8LIO5Pq/aV96SWBOoiE2IEgzxF0Cn+kA==" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xgplayer": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/xgplayer/-/xgplayer-3.0.7.tgz", + "integrity": "sha512-lGkcwsxtD4hLXRfXx4pqDKrz74qnpcYXb0g1v9uMiAYawYV8jppf9lr8mFlahGOJTSf+WpcsYiquuO90PWxFsg==", + "dependencies": { + "danmu.js": ">=1.1.6", + "delegate": "^3.2.0", + "downloadjs": "1.4.7", + "eventemitter3": "^4.0.7", + "xgplayer-subtitles": "3.0.7" + }, + "peerDependencies": { + "core-js": ">=3.12.1" + } + }, + "node_modules/xgplayer-subtitles": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/xgplayer-subtitles/-/xgplayer-subtitles-3.0.7.tgz", + "integrity": "sha512-28q33G8JsYQREvQwRNyuCmprZD7+Y30BhV0RpciHTH4dBAoA78Xtj4gp47UeNjmZCb6P1QdnRtk4ZPDkF1Yxog==", + "dependencies": { + "eventemitter3": "^4.0.7" + }, + "peerDependencies": { + "core-js": ">=3.12.1" + } + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zrender": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz", + "integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a0e0285 --- /dev/null +++ b/package.json @@ -0,0 +1,69 @@ +{ + "name": "ele-admin-pro-template", + "version": "1.11.1", + "private": true, + "scripts": { + "dev": "vite", + "serve": "vite build && vite preview", + "build": "vite build", + "lint:eslint": "eslint --cache --max-warnings 0 \"src/**/*.{vue,ts}\" --fix", + "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite/", + "clean:lib": "rimraf node_modules" + }, + "dependencies": { + "@amap/amap-jsapi-loader": "^1.0.1", + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-vue": "^6.1.0", + "@bytemd/plugin-gfm": "^1.21.0", + "ant-design-vue": "^3.2.17", + "axios": "^1.3.5", + "bytemd": "^1.21.0", + "countup.js": "^2.6.0", + "cropperjs": "^1.5.13", + "dayjs": "^1.11.7", + "echarts": "^5.4.2", + "echarts-wordcloud": "^2.1.0", + "ele-admin-pro": "^1.11.1", + "github-markdown-css": "^5.2.0", + "jsbarcode": "^3.11.5", + "lodash-es": "^4.17.21", + "nprogress": "^0.2.0", + "pinia": "^2.0.34", + "sortablejs": "^1.15.0", + "tinymce": "^5.10.7", + "vue": "^3.2.47", + "vue-echarts": "^6.5.4", + "vue-i18n": "^9.2.2", + "vue-router": "^4.1.6", + "vuedraggable": "^4.1.0", + "xgplayer": "^3.0.1", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.7", + "@types/node": "^18.15.11", + "@types/nprogress": "^0.2.0", + "@types/sortablejs": "^1.15.1", + "@typescript-eslint/eslint-plugin": "^5.58.0", + "@typescript-eslint/parser": "^5.58.0", + "@vitejs/plugin-legacy": "^4.0.2", + "@vitejs/plugin-vue": "^4.1.0", + "@vue/compiler-sfc": "^3.2.47", + "eslint": "^8.38.0", + "eslint-config-prettier": "^8.8.0", + "eslint-define-config": "^1.18.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-vue": "^9.11.0", + "less": "^4.1.3", + "postcss": "^8.4.22", + "prettier": "^2.8.7", + "rimraf": "^5.0.0", + "terser": "^5.16.9", + "typescript": "^5.0.4", + "unplugin-vue-components": "^0.24.1", + "vite": "^4.2.1", + "vite-plugin-compression": "^0.5.1", + "vue-eslint-parser": "^9.1.1", + "vue-tsc": "^1.2.0" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..fa4aabc --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,3 @@ +module.exports = { + plugins: {} +}; diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..33ad45b --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,19 @@ +module.exports = { + printWidth: 80, + tabWidth: 2, + useTabs: false, + semi: true, + singleQuote: true, + quoteProps: 'as-needed', + jsxSingleQuote: false, + trailingComma: 'none', + bracketSpacing: true, + bracketSameLine: false, + arrowParens: 'always', + requirePragma: false, + insertPragma: false, + proseWrap: 'never', + htmlWhitespaceSensitivity: 'strict', + vueIndentScriptAndStyle: true, + endOfLine: 'lf' +}; diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..bac3b38 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/json/china-provinces.geo.json b/public/json/china-provinces.geo.json new file mode 100644 index 0000000..be70ca7 --- /dev/null +++ b/public/json/china-provinces.geo.json @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":"65","size":"550","name":"新疆","cp":[84.9023,42.148],"childNum":18},"geometry":{"type":"Polygon","coordinates":[[[96.416,42.7588],[96.416,42.7148],[95.9766,42.4951],[96.0645,42.3193],[96.2402,42.2314],[95.9766,41.9238],[95.2734,41.6162],[95.1855,41.792],[94.5703,41.4844],[94.043,41.0889],[93.8672,40.6934],[93.0762,40.6494],[92.6367,39.6387],[92.373,39.3311],[92.373,39.1113],[92.373,39.0234],[90.1758,38.4961],[90.3516,38.2324],[90.6152,38.3203],[90.5273,37.8369],[91.0547,37.4414],[91.3184,37.0898],[90.7031,36.7822],[90.791,36.6064],[91.0547,36.5186],[91.0547,36.0791],[90.8789,36.0352],[90,36.2549],[89.9121,36.0791],[89.7363,36.0791],[89.209,36.2988],[88.7695,36.3428],[88.5938,36.4746],[87.3633,36.4307],[86.2207,36.167],[86.1328,35.8594],[85.6055,35.6836],[85.0781,35.7275],[84.1992,35.376],[83.1445,35.4199],[82.8809,35.6836],[82.4414,35.7275],[82.002,35.332],[81.6504,35.2441],[80.4199,35.4199],[80.2441,35.2881],[80.332,35.1563],[80.2441,35.2002],[79.8926,34.8047],[79.8047,34.4971],[79.1016,34.4531],[79.0137,34.3213],[78.2227,34.7168],[78.0469,35.2441],[78.0469,35.5078],[77.4316,35.4639],[76.8164,35.6396],[76.5527,35.8594],[76.2012,35.8154],[75.9375,36.0352],[76.0254,36.4746],[75.8496,36.6943],[75.498,36.7383],[75.4102,36.958],[75.0586,37.002],[74.8828,36.9141],[74.7949,37.0459],[74.5313,37.0898],[74.5313,37.2217],[74.8828,37.2217],[75.1465,37.4414],[74.8828,37.5732],[74.9707,37.749],[74.8828,38.4521],[74.3555,38.6719],[74.1797,38.6719],[74.0918,38.54],[73.8281,38.584],[73.7402,38.8477],[73.8281,38.9795],[73.4766,39.375],[73.916,39.5068],[73.916,39.6826],[73.8281,39.7705],[74.0039,40.0342],[74.8828,40.3418],[74.7949,40.5176],[75.2344,40.4297],[75.5859,40.6494],[75.7617,40.2979],[76.377,40.3857],[76.9043,41.001],[77.6074,41.001],[78.1348,41.2207],[78.1348,41.3965],[80.1563,42.0557],[80.2441,42.2754],[80.1563,42.627],[80.2441,42.8467],[80.5078,42.8906],[80.4199,43.0664],[80.7715,43.1982],[80.4199,44.165],[80.4199,44.6045],[79.9805,44.8242],[79.9805,44.9561],[81.7383,45.3955],[82.0898,45.2197],[82.5293,45.2197],[82.2656,45.6592],[83.0566,47.2412],[83.6719,47.0215],[84.7266,47.0215],[84.9023,46.8896],[85.5176,47.0654],[85.6934,47.2852],[85.5176,48.1201],[85.7813,48.4277],[86.5723,48.5596],[86.8359,48.8232],[86.748,48.9551],[86.8359,49.1309],[87.8027,49.1748],[87.8906,48.999],[87.7148,48.9111],[88.0664,48.7354],[87.9785,48.6035],[88.5059,48.3838],[88.6816,48.1641],[89.1211,47.9883],[89.5605,48.0322],[89.7363,47.8564],[90.0879,47.8564],[90.3516,47.6807],[90.5273,47.2412],[90.8789,46.9775],[91.0547,46.582],[90.8789,46.3184],[91.0547,46.0107],[90.7031,45.7471],[90.7031,45.5273],[90.8789,45.2197],[91.582,45.0879],[93.5156,44.9561],[94.7461,44.3408],[95.3613,44.2969],[95.3613,44.0332],[95.5371,43.9014],[95.8887,43.2422],[96.3281,42.9346],[96.416,42.7588]]]}},{"type":"Feature","properties":{"id":"54","size":"550","name":"西藏","cp":[87.8695,31.6846],"childNum":7},"geometry":{"type":"Polygon","coordinates":[[[79.0137,34.3213],[79.1016,34.4531],[79.8047,34.4971],[79.8926,34.8047],[80.2441,35.2002],[80.332,35.1563],[80.2441,35.2881],[80.4199,35.4199],[81.6504,35.2441],[82.002,35.332],[82.4414,35.7275],[82.8809,35.6836],[83.1445,35.4199],[84.1992,35.376],[85.0781,35.7275],[85.6055,35.6836],[86.1328,35.8594],[86.2207,36.167],[87.3633,36.4307],[88.5938,36.4746],[88.7695,36.3428],[89.209,36.2988],[89.7363,36.0791],[89.3848,36.0352],[89.4727,35.9033],[89.7363,35.7715],[89.7363,35.4199],[89.4727,35.376],[89.4727,35.2441],[89.5605,34.8926],[89.8242,34.8486],[89.7363,34.6729],[89.8242,34.3652],[89.6484,34.0137],[90.0879,33.4863],[90.7031,33.1348],[91.4063,33.1348],[91.9336,32.8271],[92.1973,32.8271],[92.2852,32.7393],[92.9883,32.7393],[93.5156,32.4756],[93.7793,32.5635],[94.1309,32.4316],[94.6582,32.6074],[95.1855,32.4316],[95.0098,32.2998],[95.1855,32.3438],[95.2734,32.2119],[95.3613,32.168],[95.3613,31.9922],[95.4492,31.8164],[95.8008,31.6846],[95.9766,31.8164],[96.1523,31.5967],[96.2402,31.9482],[96.5039,31.7285],[96.8555,31.6846],[96.7676,31.9922],[97.2949,32.0801],[97.3828,32.5635],[97.7344,32.5195],[98.1738,32.3438],[98.4375,31.8604],[98.877,31.4209],[98.6133,31.2012],[98.9648,30.7617],[99.1406,29.2676],[98.9648,29.1357],[98.9648,28.8281],[98.7891,28.8721],[98.7891,29.0039],[98.7012,28.916],[98.6133,28.5205],[98.7891,28.3447],[98.7012,28.2129],[98.3496,28.125],[98.2617,28.3887],[98.1738,28.125],[97.5586,28.5205],[97.2949,28.0811],[97.3828,27.9053],[97.0313,27.7295],[96.5039,28.125],[95.7129,28.2568],[95.3613,28.125],[95.2734,27.9492],[94.2188,27.5537],[93.8672,27.0264],[93.6035,26.9385],[92.1094,26.8506],[92.0215,27.4658],[91.582,27.5537],[91.582,27.9053],[91.4063,28.0371],[91.0547,27.8613],[90.7031,28.0811],[89.8242,28.2129],[89.6484,28.1689],[89.1211,27.5977],[89.1211,27.334],[89.0332,27.2021],[88.7695,27.4219],[88.8574,27.9932],[88.6816,28.125],[88.1543,27.9053],[87.8906,27.9492],[87.7148,27.8174],[87.0996,27.8174],[86.748,28.125],[86.5723,28.125],[86.4844,27.9053],[86.1328,28.125],[86.0449,27.9053],[85.6934,28.3447],[85.6055,28.2568],[85.166,28.3447],[85.166,28.6523],[84.9023,28.5645],[84.4629,28.7402],[84.2871,28.8721],[84.1992,29.2236],[84.1113,29.2676],[83.584,29.1797],[83.2324,29.5752],[82.1777,30.0586],[82.0898,30.3223],[81.3867,30.3662],[81.2109,30.0146],[81.0352,30.2344],[80.0684,30.5859],[79.7168,30.9375],[79.0137,31.0693],[78.75,31.333],[78.8379,31.5967],[78.6621,31.8164],[78.75,31.9043],[78.4863,32.124],[78.3984,32.5195],[78.75,32.6953],[78.9258,32.3438],[79.2773,32.5635],[79.1016,33.1787],[78.6621,33.6621],[78.6621,34.1016],[78.9258,34.1455],[79.0137,34.3213]]]}},{"type":"Feature","properties":{"id":"15","size":"450","name":"内蒙古","cp":[111.670801,41.818311],"childNum":12},"geometry":{"type":"Polygon","coordinates":[[[97.207,42.8027],[99.4922,42.583],[100.8105,42.6709],[101.7773,42.4951],[102.041,42.2314],[102.7441,42.1436],[103.3594,41.8799],[103.8867,41.792],[104.502,41.8799],[104.502,41.6602],[105.0293,41.5723],[105.7324,41.9238],[107.4023,42.4512],[109.4238,42.4512],[110.3906,42.7588],[111.0059,43.3301],[111.9727,43.6816],[111.9727,43.8135],[111.4453,44.3848],[111.7969,45],[111.9727,45.0879],[113.6426,44.7363],[114.1699,44.9561],[114.5215,45.3955],[115.6641,45.4395],[116.1914,45.7031],[116.2793,45.9668],[116.543,46.2744],[117.334,46.3623],[117.4219,46.582],[117.7734,46.5381],[118.3008,46.7578],[118.7402,46.7139],[118.916,46.7578],[119.0918,46.6699],[119.707,46.626],[119.9707,46.7139],[119.707,47.1973],[118.4766,47.9883],[117.8613,48.0322],[117.334,47.6807],[116.8066,47.9004],[116.1914,47.8564],[115.9277,47.6807],[115.5762,47.9004],[115.4883,48.1641],[115.8398,48.252],[115.8398,48.5596],[116.7188,49.834],[117.7734,49.5264],[118.5645,49.9219],[119.2676,50.0977],[119.3555,50.3174],[119.1797,50.3613],[119.5313,50.7568],[119.5313,50.8887],[119.707,51.0645],[120.1465,51.6797],[120.6738,51.9434],[120.7617,52.1191],[120.7617,52.251],[120.5859,52.3389],[120.6738,52.5146],[120.4102,52.6465],[120.0586,52.6025],[120.0586,52.7344],[120.8496,53.2617],[121.4648,53.3496],[121.8164,53.042],[121.2012,52.5586],[121.6406,52.4268],[121.7285,52.2949],[121.9922,52.2949],[122.168,52.5146],[122.6953,52.251],[122.6074,52.0752],[122.959,51.3281],[123.3105,51.2402],[123.6621,51.3721],[124.3652,51.2842],[124.541,51.3721],[124.8926,51.3721],[125.0684,51.6357],[125.332,51.6357],[126.0352,51.0205],[125.7715,50.7568],[125.7715,50.5371],[125.332,50.1416],[125.1563,49.834],[125.2441,49.1748],[124.8047,49.1309],[124.4531,48.1201],[124.2773,48.5156],[122.4316,47.373],[123.0469,46.7139],[123.3984,46.8896],[123.3984,46.9775],[123.4863,46.9775],[123.5742,46.8457],[123.5742,46.8896],[123.5742,46.6699],[123.0469,46.582],[123.2227,46.2305],[122.7832,46.0107],[122.6953,45.7031],[122.4316,45.8789],[122.2559,45.791],[121.8164,46.0107],[121.7285,45.7471],[121.9043,45.7031],[122.2559,45.2637],[122.0801,44.8682],[122.3438,44.2529],[123.1348,44.4727],[123.4863,43.7256],[123.3105,43.5059],[123.6621,43.374],[123.5742,43.0225],[123.3105,42.9785],[123.1348,42.8027],[122.7832,42.7148],[122.3438,42.8467],[122.3438,42.6709],[121.9922,42.7148],[121.7285,42.4512],[121.4648,42.4951],[120.498,42.0996],[120.1465,41.7041],[119.8828,42.1875],[119.5313,42.3633],[119.3555,42.2754],[119.2676,41.7041],[119.4434,41.6162],[119.2676,41.3086],[118.3887,41.3086],[118.125,41.748],[118.3008,41.792],[118.3008,42.0996],[118.125,42.0557],[117.9492,42.2314],[118.0371,42.4072],[117.7734,42.627],[117.5098,42.583],[117.334,42.4512],[116.8945,42.4072],[116.8066,42.0117],[116.2793,42.0117],[116.0156,41.792],[115.9277,41.9238],[115.2246,41.5723],[114.9609,41.6162],[114.873,42.0996],[114.5215,42.1436],[114.1699,41.792],[114.2578,41.5723],[113.9063,41.4404],[113.9941,41.2207],[113.9063,41.1328],[114.082,40.7373],[114.082,40.5176],[113.8184,40.5176],[113.5547,40.3418],[113.2031,40.3857],[112.7637,40.166],[112.3242,40.2539],[111.9727,39.5947],[111.4453,39.6387],[111.3574,39.4189],[111.0938,39.375],[111.0938,39.5947],[110.6543,39.2871],[110.127,39.4629],[110.2148,39.2871],[109.8633,39.2432],[109.9512,39.1553],[108.9844,38.3203],[109.0723,38.0127],[108.8965,37.9688],[108.8086,38.0127],[108.7207,37.7051],[108.1934,37.6172],[107.666,37.8809],[107.3145,38.1006],[106.7871,38.1885],[106.5234,38.3203],[106.9629,38.9795],[106.7871,39.375],[106.3477,39.2871],[105.9082,38.7158],[105.8203,37.793],[104.3262,37.4414],[103.4473,37.8369],[103.3594,38.0127],[103.5352,38.1445],[103.4473,38.3643],[104.2383,38.9795],[104.0625,39.4189],[103.3594,39.3311],[103.0078,39.1113],[102.4805,39.2432],[101.8652,39.1113],[102.041,38.8916],[101.7773,38.6719],[101.3379,38.7598],[101.25,39.0234],[100.9863,38.9355],[100.8105,39.4189],[100.5469,39.4189],[100.0195,39.7705],[99.4922,39.8584],[100.1074,40.2539],[100.1953,40.6494],[99.9316,41.001],[99.2285,40.8691],[99.0527,40.6934],[98.9648,40.7813],[98.7891,40.6055],[98.5254,40.7373],[98.6133,40.6494],[98.3496,40.5615],[98.3496,40.9131],[97.4707,41.4844],[97.8223,41.6162],[97.8223,41.748],[97.207,42.8027]]]}},{"type":"Feature","properties":{"id":"63","size":"800","name":"青海","cp":[95.2402,35.4199],"childNum":8},"geometry":{"type":"Polygon","coordinates":[[[89.7363,36.0791],[89.9121,36.0791],[90,36.2549],[90.8789,36.0352],[91.0547,36.0791],[91.0547,36.5186],[90.791,36.6064],[90.7031,36.7822],[91.3184,37.0898],[91.0547,37.4414],[90.5273,37.8369],[90.6152,38.3203],[90.3516,38.2324],[90.1758,38.4961],[92.373,39.0234],[92.373,39.1113],[93.1641,39.1992],[93.1641,38.9795],[93.6914,38.9355],[93.8672,38.7158],[94.3066,38.7598],[94.5703,38.3643],[95.0098,38.4082],[95.4492,38.2764],[95.7129,38.3643],[96.2402,38.1006],[96.416,38.2324],[96.6797,38.1885],[96.6797,38.4521],[97.1191,38.584],[97.0313,39.1992],[98.1738,38.8037],[98.3496,39.0234],[98.6133,38.9355],[98.7891,39.0674],[99.1406,38.9355],[99.8438,38.3643],[100.1953,38.2764],[100.0195,38.4521],[100.1074,38.4961],[100.459,38.2764],[100.7227,38.2324],[101.1621,37.8369],[101.5137,37.8809],[101.7773,37.6172],[101.9531,37.7051],[102.1289,37.4414],[102.5684,37.1777],[102.4805,36.958],[102.6563,36.8262],[102.5684,36.7383],[102.832,36.3428],[103.0078,36.2549],[102.9199,36.0791],[102.9199,35.9033],[102.6563,35.7715],[102.832,35.5957],[102.4805,35.5957],[102.3047,35.4199],[102.3926,35.2002],[101.9531,34.8486],[101.9531,34.6289],[102.2168,34.4092],[102.1289,34.2773],[101.6895,34.1016],[100.9863,34.3652],[100.8105,34.2773],[101.25,33.6621],[101.5137,33.7061],[101.6016,33.5303],[101.7773,33.5303],[101.6895,33.3105],[101.7773,33.2227],[101.6016,33.1348],[101.1621,33.2227],[101.25,32.6953],[100.7227,32.6514],[100.7227,32.5195],[100.3711,32.7393],[100.1074,32.6514],[100.1074,32.8711],[99.8438,33.0029],[99.7559,32.7393],[99.2285,32.915],[99.2285,33.0469],[98.877,33.1787],[98.4375,34.0576],[97.8223,34.1895],[97.6465,34.1016],[97.7344,33.9258],[97.3828,33.8818],[97.4707,33.5742],[97.7344,33.3984],[97.3828,32.8711],[97.4707,32.6953],[97.7344,32.5195],[97.3828,32.5635],[97.2949,32.0801],[96.7676,31.9922],[96.8555,31.6846],[96.5039,31.7285],[96.2402,31.9482],[96.1523,31.5967],[95.9766,31.8164],[95.8008,31.6846],[95.4492,31.8164],[95.3613,31.9922],[95.3613,32.168],[95.2734,32.2119],[95.1855,32.3438],[95.0098,32.2998],[95.1855,32.4316],[94.6582,32.6074],[94.1309,32.4316],[93.7793,32.5635],[93.5156,32.4756],[92.9883,32.7393],[92.2852,32.7393],[92.1973,32.8271],[91.9336,32.8271],[91.4063,33.1348],[90.7031,33.1348],[90.0879,33.4863],[89.6484,34.0137],[89.8242,34.3652],[89.7363,34.6729],[89.8242,34.8486],[89.5605,34.8926],[89.4727,35.2441],[89.4727,35.376],[89.7363,35.4199],[89.7363,35.7715],[89.4727,35.9033],[89.3848,36.0352],[89.7363,36.0791]]]}},{"type":"Feature","properties":{"id":"51","size":"900","name":"四川","cp":[101.9199,30.1904],"childNum":21},"geometry":{"type":"Polygon","coordinates":[[[101.7773,33.5303],[101.8652,33.5742],[101.9531,33.4424],[101.8652,33.0908],[102.4805,33.4424],[102.2168,33.9258],[102.9199,34.3213],[103.0957,34.1895],[103.1836,33.7939],[104.1504,33.6182],[104.2383,33.3984],[104.4141,33.3105],[104.3262,33.2227],[104.4141,33.0469],[104.3262,32.8711],[104.4141,32.7393],[105.2051,32.6074],[105.3809,32.7393],[105.3809,32.8711],[105.4688,32.915],[105.5566,32.7393],[106.084,32.8711],[106.084,32.7393],[106.3477,32.6514],[107.0508,32.6953],[107.1387,32.4756],[107.2266,32.4316],[107.4023,32.5195],[108.0176,32.168],[108.2813,32.2559],[108.5449,32.2119],[108.3691,32.168],[108.2813,31.9043],[108.5449,31.6846],[108.1934,31.5088],[107.9297,30.8496],[107.4902,30.8496],[107.4023,30.7617],[107.4902,30.6299],[107.0508,30.0146],[106.7871,30.0146],[106.6113,30.3223],[106.2598,30.1904],[105.8203,30.4541],[105.6445,30.2783],[105.5566,30.1025],[105.7324,29.8828],[105.293,29.5313],[105.4688,29.3115],[105.7324,29.2676],[105.8203,28.96],[106.2598,28.8721],[106.3477,28.5205],[105.9961,28.7402],[105.6445,28.4326],[105.9082,28.125],[106.1719,28.125],[106.3477,27.8174],[105.6445,27.6416],[105.5566,27.7734],[105.293,27.7295],[105.2051,27.9932],[105.0293,28.0811],[104.8535,27.9053],[104.4141,27.9492],[104.3262,28.0371],[104.4141,28.125],[104.4141,28.2568],[104.2383,28.4326],[104.4141,28.6084],[103.8867,28.6523],[103.7988,28.3008],[103.4473,28.125],[103.4473,27.7734],[102.9199,27.29],[103.0078,26.3672],[102.6563,26.1914],[102.5684,26.3672],[102.1289,26.1035],[101.8652,26.0596],[101.6016,26.2354],[101.6895,26.3672],[101.4258,26.5869],[101.4258,26.8066],[101.4258,26.7188],[101.1621,27.0264],[101.1621,27.1582],[100.7227,27.8613],[100.3711,27.8174],[100.2832,27.7295],[100.0195,28.125],[100.1953,28.3447],[99.668,28.8281],[99.4043,28.5205],[99.4043,28.1689],[99.2285,28.3008],[99.1406,29.2676],[98.9648,30.7617],[98.6133,31.2012],[98.877,31.4209],[98.4375,31.8604],[98.1738,32.3438],[97.7344,32.5195],[97.4707,32.6953],[97.3828,32.8711],[97.7344,33.3984],[97.4707,33.5742],[97.3828,33.8818],[97.7344,33.9258],[97.6465,34.1016],[97.8223,34.1895],[98.4375,34.0576],[98.877,33.1787],[99.2285,33.0469],[99.2285,32.915],[99.7559,32.7393],[99.8438,33.0029],[100.1074,32.8711],[100.1074,32.6514],[100.3711,32.7393],[100.7227,32.5195],[100.7227,32.6514],[101.25,32.6953],[101.1621,33.2227],[101.6016,33.1348],[101.7773,33.2227],[101.6895,33.3105],[101.7773,33.5303]]]}},{"type":"Feature","properties":{"id":"23","size":"700","name":"黑龙江","cp":[128.642464,46.756967],"childNum":13},"geometry":{"type":"Polygon","coordinates":[[[121.4648,53.3496],[123.6621,53.5693],[124.8926,53.0859],[125.0684,53.2178],[125.5957,53.0859],[125.6836,52.9102],[126.123,52.7783],[126.0352,52.6025],[126.2109,52.5146],[126.3867,52.2949],[126.3867,52.207],[126.5625,52.1631],[126.4746,51.9434],[126.9141,51.3721],[126.8262,51.2842],[127.002,51.3281],[126.9141,51.1084],[127.2656,50.7568],[127.3535,50.2734],[127.6172,50.2295],[127.5293,49.8779],[127.793,49.6143],[128.7598,49.5703],[129.1113,49.3506],[129.4629,49.4385],[130.2539,48.8672],[130.6934,48.8672],[130.5176,48.6475],[130.8691,48.2959],[130.6934,48.1201],[131.0449,47.6807],[132.5391,47.7246],[132.627,47.9443],[133.0664,48.1201],[133.5059,48.1201],[134.209,48.3838],[135.0879,48.4277],[134.7363,48.252],[134.5605,47.9883],[134.7363,47.6807],[134.5605,47.4609],[134.3848,47.4609],[134.209,47.2852],[134.209,47.1533],[133.8574,46.5381],[133.9453,46.2744],[133.5059,45.835],[133.418,45.5713],[133.2422,45.5273],[133.0664,45.1318],[132.8906,45.0439],[131.9238,45.3516],[131.5723,45.0439],[131.0449,44.8682],[131.3086,44.0771],[131.2207,43.7256],[131.3086,43.4619],[130.8691,43.418],[130.5176,43.6377],[130.3418,43.9893],[129.9902,43.8574],[129.9023,44.0332],[129.8145,43.9014],[129.2871,43.8135],[129.1992,43.5938],[128.8477,43.5498],[128.4961,44.165],[128.4082,44.4727],[128.0566,44.3408],[128.0566,44.1211],[127.7051,44.1211],[127.5293,44.6045],[127.0898,44.6045],[127.002,44.7803],[127.0898,45],[126.9141,45.1318],[126.5625,45.2637],[126.0352,45.1758],[125.7715,45.3076],[125.6836,45.5273],[125.0684,45.3955],[124.8926,45.5273],[124.3652,45.4395],[124.0137,45.7471],[123.9258,46.2305],[123.2227,46.2305],[123.0469,46.582],[123.5742,46.6699],[123.5742,46.8896],[123.5742,46.8457],[123.4863,46.9775],[123.3984,46.9775],[123.3984,46.8896],[123.0469,46.7139],[122.4316,47.373],[124.2773,48.5156],[124.4531,48.1201],[124.8047,49.1309],[125.2441,49.1748],[125.1563,49.834],[125.332,50.1416],[125.7715,50.5371],[125.7715,50.7568],[126.0352,51.0205],[125.332,51.6357],[125.0684,51.6357],[124.8926,51.3721],[124.541,51.3721],[124.3652,51.2842],[123.6621,51.3721],[123.3105,51.2402],[122.959,51.3281],[122.6074,52.0752],[122.6953,52.251],[122.168,52.5146],[121.9922,52.2949],[121.7285,52.2949],[121.6406,52.4268],[121.2012,52.5586],[121.8164,53.042],[121.4648,53.3496]]]}},{"type":"Feature","properties":{"id":"62","size":"690","name":"甘肃","cp":[103.823557,36.058039],"childNum":14},"geometry":{"type":"Polygon","coordinates":[[[96.416,42.7148],[97.207,42.8027],[97.8223,41.748],[97.8223,41.6162],[97.4707,41.4844],[98.3496,40.9131],[98.3496,40.5615],[98.6133,40.6494],[98.5254,40.7373],[98.7891,40.6055],[98.9648,40.7813],[99.0527,40.6934],[99.2285,40.8691],[99.9316,41.001],[100.1953,40.6494],[100.1074,40.2539],[99.4922,39.8584],[100.0195,39.7705],[100.5469,39.4189],[100.8105,39.4189],[100.9863,38.9355],[101.25,39.0234],[101.3379,38.7598],[101.7773,38.6719],[102.041,38.8916],[101.8652,39.1113],[102.4805,39.2432],[103.0078,39.1113],[103.3594,39.3311],[104.0625,39.4189],[104.2383,38.9795],[103.4473,38.3643],[103.5352,38.1445],[103.3594,38.0127],[103.4473,37.8369],[104.3262,37.4414],[104.5898,37.4414],[104.5898,37.2217],[104.8535,37.2217],[105.293,36.8262],[105.2051,36.6943],[105.4688,36.123],[105.293,35.9912],[105.3809,35.7715],[105.7324,35.7275],[105.8203,35.5518],[105.9961,35.4639],[105.9082,35.4199],[105.9961,35.4199],[106.084,35.376],[106.2598,35.4199],[106.3477,35.2441],[106.5234,35.332],[106.4355,35.6836],[106.6992,35.6836],[106.9629,35.8154],[106.875,36.123],[106.5234,36.2549],[106.5234,36.4746],[106.4355,36.5625],[106.6113,36.7822],[106.6113,37.0898],[107.3145,37.0898],[107.3145,36.9141],[108.7207,36.3428],[108.6328,35.9912],[108.5449,35.8594],[108.6328,35.5518],[108.5449,35.2881],[107.7539,35.2881],[107.7539,35.1123],[107.8418,35.0244],[107.666,34.9365],[107.2266,34.8926],[106.9629,35.0684],[106.6113,35.0684],[106.5234,34.7607],[106.3477,34.585],[106.6992,34.3213],[106.5234,34.2773],[106.6113,34.1455],[106.4355,33.9258],[106.5234,33.5303],[105.9961,33.6182],[105.7324,33.3984],[105.9961,33.1787],[105.9082,33.0029],[105.4688,32.915],[105.3809,32.8711],[105.3809,32.7393],[105.2051,32.6074],[104.4141,32.7393],[104.3262,32.8711],[104.4141,33.0469],[104.3262,33.2227],[104.4141,33.3105],[104.2383,33.3984],[104.1504,33.6182],[103.1836,33.7939],[103.0957,34.1895],[102.9199,34.3213],[102.2168,33.9258],[102.4805,33.4424],[101.8652,33.0908],[101.9531,33.4424],[101.8652,33.5742],[101.7773,33.5303],[101.6016,33.5303],[101.5137,33.7061],[101.25,33.6621],[100.8105,34.2773],[100.9863,34.3652],[101.6895,34.1016],[102.1289,34.2773],[102.2168,34.4092],[101.9531,34.6289],[101.9531,34.8486],[102.3926,35.2002],[102.3047,35.4199],[102.4805,35.5957],[102.832,35.5957],[102.6563,35.7715],[102.9199,35.9033],[102.9199,36.0791],[103.0078,36.2549],[102.832,36.3428],[102.5684,36.7383],[102.6563,36.8262],[102.4805,36.958],[102.5684,37.1777],[102.1289,37.4414],[101.9531,37.7051],[101.7773,37.6172],[101.5137,37.8809],[101.1621,37.8369],[100.7227,38.2324],[100.459,38.2764],[100.1074,38.4961],[100.0195,38.4521],[100.1953,38.2764],[99.8438,38.3643],[99.1406,38.9355],[98.7891,39.0674],[98.6133,38.9355],[98.3496,39.0234],[98.1738,38.8037],[97.0313,39.1992],[97.1191,38.584],[96.6797,38.4521],[96.6797,38.1885],[96.416,38.2324],[96.2402,38.1006],[95.7129,38.3643],[95.4492,38.2764],[95.0098,38.4082],[94.5703,38.3643],[94.3066,38.7598],[93.8672,38.7158],[93.6914,38.9355],[93.1641,38.9795],[93.1641,39.1992],[92.373,39.1113],[92.373,39.3311],[92.6367,39.6387],[93.0762,40.6494],[93.8672,40.6934],[94.043,41.0889],[94.5703,41.4844],[95.1855,41.792],[95.2734,41.6162],[95.9766,41.9238],[96.2402,42.2314],[96.0645,42.3193],[95.9766,42.4951],[96.416,42.7148]]]}},{"type":"Feature","properties":{"id":"53","size":"1200","name":"云南","cp":[101.512251,24.740609],"childNum":16},"geometry":{"type":"Polygon","coordinates":[[[98.1738,28.125],[98.2617,28.3887],[98.3496,28.125],[98.7012,28.2129],[98.7891,28.3447],[98.6133,28.5205],[98.7012,28.916],[98.7891,29.0039],[98.7891,28.8721],[98.9648,28.8281],[98.9648,29.1357],[99.1406,29.2676],[99.2285,28.3008],[99.4043,28.1689],[99.4043,28.5205],[99.668,28.8281],[100.1953,28.3447],[100.0195,28.125],[100.2832,27.7295],[100.3711,27.8174],[100.7227,27.8613],[101.1621,27.1582],[101.1621,27.0264],[101.4258,26.7188],[101.4258,26.8066],[101.4258,26.5869],[101.6895,26.3672],[101.6016,26.2354],[101.8652,26.0596],[102.1289,26.1035],[102.5684,26.3672],[102.6563,26.1914],[103.0078,26.3672],[102.9199,27.29],[103.4473,27.7734],[103.4473,28.125],[103.7988,28.3008],[103.8867,28.6523],[104.4141,28.6084],[104.2383,28.4326],[104.4141,28.2568],[104.4141,28.125],[104.3262,28.0371],[104.4141,27.9492],[104.8535,27.9053],[105.0293,28.0811],[105.2051,27.9932],[105.293,27.7295],[105.2051,27.3779],[104.5898,27.334],[104.4141,27.4658],[104.1504,27.2461],[103.8867,27.4219],[103.623,27.0264],[103.7109,26.9824],[103.7109,26.7627],[103.8867,26.543],[104.4141,26.6748],[104.6777,26.4111],[104.3262,25.708],[104.8535,25.2246],[104.5898,25.0488],[104.6777,24.9609],[104.502,24.7412],[104.6777,24.3457],[104.7656,24.4775],[105.0293,24.4336],[105.2051,24.082],[105.4688,24.0381],[105.5566,24.126],[105.9961,24.126],[106.1719,23.8184],[106.1719,23.5547],[105.6445,23.4229],[105.5566,23.2031],[105.293,23.3789],[104.8535,23.1592],[104.7656,22.8516],[104.3262,22.6758],[104.1504,22.8076],[103.9746,22.5439],[103.623,22.7637],[103.5352,22.5879],[103.3594,22.8076],[103.0957,22.4561],[102.4805,22.7637],[102.3047,22.4121],[101.8652,22.3682],[101.7773,22.5],[101.6016,22.1924],[101.8652,21.6211],[101.7773,21.1377],[101.6016,21.2256],[101.25,21.1816],[101.1621,21.7529],[100.6348,21.4453],[100.1074,21.4893],[99.9316,22.0605],[99.2285,22.1484],[99.4043,22.5879],[99.3164,22.7197],[99.4922,23.0713],[98.877,23.2031],[98.7012,23.9502],[98.877,24.126],[98.1738,24.082],[97.7344,23.8623],[97.5586,23.9063],[97.7344,24.126],[97.6465,24.4336],[97.5586,24.4336],[97.5586,24.7412],[97.7344,24.8291],[97.8223,25.2686],[98.1738,25.4004],[98.1738,25.6201],[98.3496,25.5762],[98.5254,25.8398],[98.7012,25.8838],[98.6133,26.0596],[98.7012,26.1475],[98.7891,26.5869],[98.7012,27.5098],[98.5254,27.6416],[98.3496,27.5098],[98.1738,28.125]]]}},{"type":"Feature","properties":{"id":"45","size":"1450","name":"广西","cp":[107.7813,23.6426],"childNum":14},"geometry":{"type":"Polygon","coordinates":[[[104.502,24.7412],[104.6777,24.6094],[105.2051,24.9609],[105.9961,24.6533],[106.1719,24.7852],[106.1719,24.9609],[106.875,25.1807],[107.0508,25.2686],[106.9629,25.4883],[107.2266,25.6201],[107.4902,25.2246],[107.7539,25.2246],[107.8418,25.1367],[108.1055,25.2246],[108.1934,25.4443],[108.3691,25.5322],[108.6328,25.3125],[108.6328,25.5762],[109.0723,25.5322],[108.9844,25.752],[109.3359,25.708],[109.5117,26.0156],[109.7754,25.8838],[109.9512,26.1914],[110.2148,25.9717],[110.5664,26.3232],[111.1816,26.3232],[111.2695,26.2354],[111.2695,25.8838],[111.4453,25.8398],[111.0059,25.0049],[111.0938,24.9609],[111.3574,25.1367],[111.5332,24.6533],[111.709,24.7852],[112.0605,24.7412],[111.8848,24.6533],[112.0605,24.3457],[111.8848,24.2139],[111.8848,23.9941],[111.7969,23.8184],[111.6211,23.8184],[111.6211,23.6865],[111.3574,23.4668],[111.4453,23.0273],[111.2695,22.8076],[110.7422,22.5439],[110.7422,22.2803],[110.6543,22.1484],[110.3027,22.1484],[110.3027,21.8848],[109.9512,21.8408],[109.8633,21.665],[109.7754,21.6211],[109.7754,21.4014],[109.5996,21.4453],[109.1602,21.3574],[109.248,20.874],[109.0723,20.9619],[109.0723,21.5332],[108.7207,21.5332],[108.6328,21.665],[108.2813,21.4893],[107.8418,21.6211],[107.4023,21.6211],[107.0508,21.7969],[107.0508,21.9287],[106.6992,22.0166],[106.6113,22.4121],[106.7871,22.7637],[106.6992,22.8955],[105.9082,22.9395],[105.5566,23.0713],[105.5566,23.2031],[105.6445,23.4229],[106.1719,23.5547],[106.1719,23.8184],[105.9961,24.126],[105.5566,24.126],[105.4688,24.0381],[105.2051,24.082],[105.0293,24.4336],[104.7656,24.4775],[104.6777,24.3457],[104.502,24.7412]]]}},{"type":"Feature","properties":{"id":"43","size":"1700","name":"湖南","cp":[111.782279,28.09409],"childNum":14},"geometry":{"type":"Polygon","coordinates":[[[109.248,28.4766],[109.248,29.1357],[109.5117,29.6191],[109.6875,29.6191],[109.7754,29.751],[110.4785,29.6631],[110.6543,29.751],[110.4785,30.0146],[110.8301,30.1465],[111.7969,29.9268],[112.2363,29.5313],[112.5,29.6191],[112.6758,29.5752],[112.9395,29.7949],[113.0273,29.751],[112.9395,29.4873],[113.0273,29.4434],[113.5547,29.8389],[113.5547,29.707],[113.7305,29.5752],[113.6426,29.3115],[113.7305,29.0918],[113.9063,29.0479],[114.1699,28.8281],[114.082,28.5645],[114.2578,28.3447],[113.7305,27.9492],[113.6426,27.5977],[113.6426,27.3779],[113.8184,27.29],[113.7305,27.1143],[113.9063,26.9385],[113.9063,26.6309],[114.082,26.5869],[113.9941,26.1914],[114.2578,26.1475],[113.9941,26.0596],[113.9063,25.4443],[113.6426,25.3125],[113.2031,25.5322],[112.8516,25.3564],[113.0273,25.2246],[113.0273,24.9609],[112.8516,24.917],[112.5879,25.1367],[112.2363,25.1807],[112.1484,24.873],[112.0605,24.7412],[111.709,24.7852],[111.5332,24.6533],[111.3574,25.1367],[111.0938,24.9609],[111.0059,25.0049],[111.4453,25.8398],[111.2695,25.8838],[111.2695,26.2354],[111.1816,26.3232],[110.5664,26.3232],[110.2148,25.9717],[109.9512,26.1914],[109.7754,25.8838],[109.5117,26.0156],[109.4238,26.2793],[109.248,26.3232],[109.4238,26.5869],[109.3359,26.7188],[109.5117,26.8066],[109.5117,27.0264],[109.3359,27.1582],[108.8965,27.0264],[108.8086,27.1143],[109.4238,27.5977],[109.3359,27.9053],[109.3359,28.2568],[109.248,28.4766]]]}},{"type":"Feature","properties":{"id":"61","size":"1150","name":"陕西","cp":[108.948024,34.263161],"childNum":10},"geometry":{"type":"Polygon","coordinates":[[[105.4688,32.915],[105.9082,33.0029],[105.9961,33.1787],[105.7324,33.3984],[105.9961,33.6182],[106.5234,33.5303],[106.4355,33.9258],[106.6113,34.1455],[106.5234,34.2773],[106.6992,34.3213],[106.3477,34.585],[106.5234,34.7607],[106.6113,35.0684],[106.9629,35.0684],[107.2266,34.8926],[107.666,34.9365],[107.8418,35.0244],[107.7539,35.1123],[107.7539,35.2881],[108.5449,35.2881],[108.6328,35.5518],[108.5449,35.8594],[108.6328,35.9912],[108.7207,36.3428],[107.3145,36.9141],[107.3145,37.0898],[107.3145,37.6172],[107.666,37.8809],[108.1934,37.6172],[108.7207,37.7051],[108.8086,38.0127],[108.8965,37.9688],[109.0723,38.0127],[108.9844,38.3203],[109.9512,39.1553],[109.8633,39.2432],[110.2148,39.2871],[110.127,39.4629],[110.6543,39.2871],[111.0938,39.5947],[111.0938,39.375],[111.1816,39.2432],[110.918,38.7158],[110.8301,38.4961],[110.4785,38.1885],[110.4785,37.9688],[110.8301,37.6611],[110.3906,37.002],[110.4785,36.123],[110.5664,35.6396],[110.2148,34.8926],[110.2148,34.6729],[110.3906,34.585],[110.4785,34.2334],[110.6543,34.1455],[110.6543,33.8379],[111.0059,33.5303],[111.0059,33.2666],[110.7422,33.1348],[110.5664,33.2666],[110.3027,33.1787],[109.5996,33.2666],[109.4238,33.1348],[109.7754,33.0469],[109.7754,32.915],[110.127,32.7393],[110.127,32.6074],[109.6875,32.6074],[109.5117,32.4316],[109.5996,31.7285],[109.248,31.7285],[109.0723,31.9482],[108.5449,32.2119],[108.2813,32.2559],[108.0176,32.168],[107.4023,32.5195],[107.2266,32.4316],[107.1387,32.4756],[107.0508,32.6953],[106.3477,32.6514],[106.084,32.7393],[106.084,32.8711],[105.5566,32.7393],[105.4688,32.915]]]}},{"type":"Feature","properties":{"id":"44","size":"1600","name":"广东","cp":[113.280637,23.125178],"childNum":21},"geometry":{"type":"Polygon","coordinates":[[[109.7754,21.4014],[109.7754,21.6211],[109.8633,21.665],[109.9512,21.8408],[110.3027,21.8848],[110.3027,22.1484],[110.6543,22.1484],[110.7422,22.2803],[110.7422,22.5439],[111.2695,22.8076],[111.4453,23.0273],[111.3574,23.4668],[111.6211,23.6865],[111.6211,23.8184],[111.7969,23.8184],[111.8848,23.9941],[111.8848,24.2139],[112.0605,24.3457],[111.8848,24.6533],[112.0605,24.7412],[112.1484,24.873],[112.2363,25.1807],[112.5879,25.1367],[112.8516,24.917],[113.0273,24.9609],[113.0273,25.2246],[112.8516,25.3564],[113.2031,25.5322],[113.6426,25.3125],[113.9063,25.4443],[113.9941,25.2686],[114.6094,25.4004],[114.7852,25.2686],[114.6973,25.1367],[114.4336,24.9609],[114.1699,24.6973],[114.4336,24.5215],[115.4004,24.7852],[115.8398,24.5654],[115.752,24.7852],[115.9277,24.917],[116.2793,24.7852],[116.3672,24.873],[116.543,24.6094],[116.7188,24.6533],[116.9824,24.1699],[116.9824,23.9063],[117.1582,23.5547],[117.334,23.2471],[116.8945,23.3789],[116.6309,23.1152],[116.543,22.8516],[115.9277,22.7197],[115.6641,22.7637],[115.5762,22.6318],[115.0488,22.6758],[114.6094,22.3682],[114.3457,22.5439],[113.9941,22.5],[113.8184,22.1924],[114.3457,22.1484],[114.4336,22.0166],[114.082,21.9287],[113.9941,21.7969],[113.5547,22.0166],[113.1152,21.8408],[112.9395,21.5771],[112.4121,21.4453],[112.2363,21.5332],[111.5332,21.4893],[111.2695,21.3574],[110.7422,21.3574],[110.6543,21.2256],[110.7422,20.918],[110.4785,20.874],[110.6543,20.2588],[110.5664,20.2588],[110.3906,20.127],[110.0391,20.127],[109.8633,20.127],[109.8633,20.3027],[109.5996,20.918],[109.7754,21.4014],[109.7754,21.4014]],[[113.5986,22.1649],[113.6096,22.1265],[113.5547,22.11],[113.5437,22.2034],[113.5767,22.2034],[113.5986,22.1649]]]}},{"type":"Feature","properties":{"id":"22","size":"1120","name":"吉林","cp":[125.7746,43.5938],"childNum":9},"geometry":{"type":"Polygon","coordinates":[[[123.2227,46.2305],[123.9258,46.2305],[124.0137,45.7471],[124.3652,45.4395],[124.8926,45.5273],[125.0684,45.3955],[125.6836,45.5273],[125.7715,45.3076],[126.0352,45.1758],[126.5625,45.2637],[126.9141,45.1318],[127.0898,45],[127.002,44.7803],[127.0898,44.6045],[127.5293,44.6045],[127.7051,44.1211],[128.0566,44.1211],[128.0566,44.3408],[128.4082,44.4727],[128.4961,44.165],[128.8477,43.5498],[129.1992,43.5938],[129.2871,43.8135],[129.8145,43.9014],[129.9023,44.0332],[129.9902,43.8574],[130.3418,43.9893],[130.5176,43.6377],[130.8691,43.418],[131.3086,43.4619],[131.3086,43.3301],[131.1328,42.9346],[130.4297,42.7148],[130.6055,42.6709],[130.6055,42.4512],[130.2539,42.7588],[130.2539,42.8906],[130.166,42.9785],[129.9023,43.0225],[129.7266,42.4951],[129.375,42.4512],[128.9355,42.0117],[128.0566,42.0117],[128.3203,41.5723],[128.1445,41.3525],[127.0898,41.5283],[127.1777,41.5723],[126.9141,41.792],[126.6504,41.6602],[126.4746,41.3965],[126.123,40.957],[125.6836,40.8691],[125.5957,40.9131],[125.7715,41.2207],[125.332,41.6602],[125.332,41.9678],[125.4199,42.0996],[125.332,42.1436],[124.8926,42.8027],[124.8926,43.0664],[124.7168,43.0664],[124.4531,42.8467],[124.2773,43.2422],[123.8379,43.4619],[123.6621,43.374],[123.3105,43.5059],[123.4863,43.7256],[123.1348,44.4727],[122.3438,44.2529],[122.0801,44.8682],[122.2559,45.2637],[121.9043,45.7031],[121.7285,45.7471],[121.8164,46.0107],[122.2559,45.791],[122.4316,45.8789],[122.6953,45.7031],[122.7832,46.0107],[123.2227,46.2305]]]}},{"type":"Feature","properties":{"id":"13","size":"1300","name":"河北","cp":[114.502461,38.045474],"childNum":11},"geometry":{"type":"MultiPolygon","coordinates":[[[[114.5215,39.5068],[114.3457,39.8584],[113.9941,39.9902],[114.5215,40.3418],[114.3457,40.3857],[114.2578,40.6055],[114.082,40.7373],[113.9063,41.1328],[113.9941,41.2207],[113.9063,41.4404],[114.2578,41.5723],[114.1699,41.792],[114.5215,42.1436],[114.873,42.0996],[114.9609,41.6162],[115.2246,41.5723],[115.9277,41.9238],[116.0156,41.792],[116.2793,42.0117],[116.8066,42.0117],[116.8945,42.4072],[117.334,42.4512],[117.5098,42.583],[117.7734,42.627],[118.0371,42.4072],[117.9492,42.2314],[118.125,42.0557],[118.3008,42.0996],[118.3008,41.792],[118.125,41.748],[118.3887,41.3086],[119.2676,41.3086],[118.8281,40.8252],[119.2676,40.5176],[119.5313,40.5615],[119.707,40.1221],[119.8828,39.9463],[119.5313,39.6826],[119.4434,39.4189],[118.916,39.0674],[118.4766,38.9355],[118.125,39.0234],[118.0371,39.1992],[118.0371,39.2432],[117.8613,39.4189],[117.9492,39.5947],[117.6855,39.5947],[117.5098,39.7705],[117.5098,39.9902],[117.6855,39.9902],[117.6855,40.0781],[117.4219,40.21],[117.2461,40.5176],[117.4219,40.6494],[116.9824,40.6934],[116.6309,41.0449],[116.3672,40.9131],[116.4551,40.7813],[116.1914,40.7813],[116.1035,40.6055],[115.752,40.5615],[115.9277,40.2539],[115.4004,39.9463],[115.4883,39.6387],[115.752,39.5068],[116.1914,39.5947],[116.3672,39.4629],[116.543,39.5947],[116.8066,39.5947],[116.8945,39.1113],[116.7188,38.9355],[116.7188,38.8037],[117.2461,38.54],[117.5977,38.6279],[117.9492,38.3203],[117.4219,37.8369],[116.8066,37.8369],[116.4551,37.4854],[116.2793,37.5732],[116.2793,37.3535],[116.0156,37.3535],[115.752,36.9141],[115.3125,36.5186],[115.4883,36.167],[115.3125,36.0791],[115.1367,36.2109],[114.9609,36.0791],[114.873,36.123],[113.7305,36.3428],[113.4668,36.6504],[113.7305,36.8701],[113.7305,37.1338],[114.1699,37.6611],[113.9941,37.7051],[113.8184,38.1445],[113.5547,38.2764],[113.5547,38.54],[113.8184,38.8037],[113.8184,38.9355],[113.9063,39.0234],[114.3457,39.0674],[114.5215,39.5068]]],[[[117.2461,40.0781],[117.1582,39.8145],[117.1582,39.6387],[116.8945,39.6826],[116.8945,39.8145],[116.8066,39.9902],[117.2461,40.0781]]]]}},{"type":"Feature","properties":{"id":"42","size":"1500","name":"湖北","cp":[113.298572,30.684355],"childNum":17},"geometry":{"type":"Polygon","coordinates":[[[110.2148,31.1572],[110.127,31.377],[109.6875,31.5527],[109.7754,31.6846],[109.5996,31.7285],[109.5117,32.4316],[109.6875,32.6074],[110.127,32.6074],[110.127,32.7393],[109.7754,32.915],[109.7754,33.0469],[109.4238,33.1348],[109.5996,33.2666],[110.3027,33.1787],[110.5664,33.2666],[110.7422,33.1348],[111.0059,33.2666],[111.5332,32.6074],[112.3242,32.3438],[113.2031,32.4316],[113.4668,32.2998],[113.7305,32.4316],[113.8184,31.8604],[113.9941,31.7725],[114.1699,31.8604],[114.5215,31.7725],[114.6094,31.5527],[114.7852,31.4648],[115.1367,31.5967],[115.2246,31.4209],[115.4004,31.4209],[115.5762,31.2012],[116.0156,31.0254],[115.752,30.6738],[116.1035,30.1904],[116.1035,29.8389],[115.9277,29.707],[115.4883,29.7949],[114.873,29.3994],[114.2578,29.3555],[113.9063,29.0479],[113.7305,29.0918],[113.6426,29.3115],[113.7305,29.5752],[113.5547,29.707],[113.5547,29.8389],[113.0273,29.4434],[112.9395,29.4873],[113.0273,29.751],[112.9395,29.7949],[112.6758,29.5752],[112.5,29.6191],[112.2363,29.5313],[111.7969,29.9268],[110.8301,30.1465],[110.4785,30.0146],[110.6543,29.751],[110.4785,29.6631],[109.7754,29.751],[109.6875,29.6191],[109.5117,29.6191],[109.248,29.1357],[109.0723,29.3555],[108.9844,29.3115],[108.6328,29.8389],[108.457,29.7949],[108.5449,30.2344],[108.457,30.4102],[108.6328,30.5859],[108.8086,30.498],[109.0723,30.6299],[109.1602,30.542],[109.248,30.6299],[109.4238,30.542],[109.8633,30.8936],[110.0391,30.8057],[110.2148,31.1572]]]}},{"type":"Feature","properties":{"id":"52","size":"2000","name":"贵州","cp":[106.6113,26.9385],"childNum":9},"geometry":{"type":"Polygon","coordinates":[[[104.1504,27.2461],[104.4141,27.4658],[104.5898,27.334],[105.2051,27.3779],[105.293,27.7295],[105.5566,27.7734],[105.6445,27.6416],[106.3477,27.8174],[106.1719,28.125],[105.9082,28.125],[105.6445,28.4326],[105.9961,28.7402],[106.3477,28.5205],[106.5234,28.5645],[106.4355,28.7842],[106.5234,28.7842],[106.6113,28.6523],[106.6113,28.5205],[106.6992,28.4766],[106.875,28.7842],[107.4023,28.8721],[107.4023,29.1797],[107.5781,29.2236],[107.8418,29.1357],[107.8418,29.0039],[108.2813,29.0918],[108.3691,28.6523],[108.5449,28.6523],[108.5449,28.3887],[108.7207,28.4766],[108.7207,28.2129],[109.0723,28.2129],[109.248,28.4766],[109.3359,28.2568],[109.3359,27.9053],[109.4238,27.5977],[108.8086,27.1143],[108.8965,27.0264],[109.3359,27.1582],[109.5117,27.0264],[109.5117,26.8066],[109.3359,26.7188],[109.4238,26.5869],[109.248,26.3232],[109.4238,26.2793],[109.5117,26.0156],[109.3359,25.708],[108.9844,25.752],[109.0723,25.5322],[108.6328,25.5762],[108.6328,25.3125],[108.3691,25.5322],[108.1934,25.4443],[108.1055,25.2246],[107.8418,25.1367],[107.7539,25.2246],[107.4902,25.2246],[107.2266,25.6201],[106.9629,25.4883],[107.0508,25.2686],[106.875,25.1807],[106.1719,24.9609],[106.1719,24.7852],[105.9961,24.6533],[105.2051,24.9609],[104.6777,24.6094],[104.502,24.7412],[104.6777,24.9609],[104.5898,25.0488],[104.8535,25.2246],[104.3262,25.708],[104.6777,26.4111],[104.4141,26.6748],[103.8867,26.543],[103.7109,26.7627],[103.7109,26.9824],[103.623,27.0264],[103.8867,27.4219],[104.1504,27.2461]]]}},{"type":"Feature","properties":{"id":"37","size":"1500","name":"山东","cp":[118.7402,36.4307],"childNum":17},"geometry":{"type":"Polygon","coordinates":[[[115.4883,36.167],[115.3125,36.5186],[115.752,36.9141],[116.0156,37.3535],[116.2793,37.3535],[116.2793,37.5732],[116.4551,37.4854],[116.8066,37.8369],[117.4219,37.8369],[117.9492,38.3203],[118.125,38.1445],[118.916,38.1445],[119.3555,37.6611],[119.0039,37.5293],[119.0039,37.3535],[119.3555,37.1338],[119.707,37.1338],[119.8828,37.3975],[120.498,37.8369],[120.5859,38.1445],[120.9375,38.4521],[121.0254,37.8369],[121.2012,37.6611],[121.9043,37.4854],[122.168,37.6172],[122.2559,37.4854],[122.6074,37.4854],[122.6953,37.3535],[122.6074,36.9141],[122.4316,36.7822],[121.8164,36.8701],[121.7285,36.6943],[121.1133,36.6064],[121.1133,36.4307],[121.377,36.2549],[120.7617,36.167],[120.9375,35.8594],[120.6738,36.0352],[119.707,35.4639],[119.9707,34.9805],[119.3555,35.0244],[119.2676,35.1123],[118.916,35.0244],[118.7402,34.7168],[118.4766,34.6729],[118.3887,34.4092],[118.2129,34.4092],[118.125,34.6289],[117.9492,34.6729],[117.5977,34.4531],[117.334,34.585],[117.2461,34.4531],[116.8066,34.9365],[116.4551,34.8926],[116.3672,34.6289],[116.1914,34.585],[115.5762,34.585],[115.4004,34.8486],[114.7852,35.0684],[115.0488,35.376],[115.2246,35.4199],[115.4883,35.7275],[116.1035,36.0791],[115.3125,35.8154],[115.4883,36.167]]]}},{"type":"Feature","properties":{"id":"36","size":"1700","name":"江西","cp":[115.592151,27.676493],"childNum":11},"geometry":{"type":"Polygon","coordinates":[[[114.2578,28.3447],[114.082,28.5645],[114.1699,28.8281],[113.9063,29.0479],[114.2578,29.3555],[114.873,29.3994],[115.4883,29.7949],[115.9277,29.707],[116.1035,29.8389],[116.2793,29.7949],[116.7188,30.0586],[116.8945,29.9268],[116.7188,29.751],[116.7188,29.6191],[117.1582,29.707],[117.0703,29.8389],[117.1582,29.9268],[117.5098,29.6191],[118.0371,29.5752],[118.2129,29.3994],[118.0371,29.1797],[118.0371,29.0479],[118.3887,28.7842],[118.4766,28.3447],[118.4766,28.3008],[118.3008,28.0811],[117.7734,27.8174],[117.5098,27.9932],[116.9824,27.6416],[117.1582,27.29],[117.0703,27.1143],[116.543,26.8066],[116.6309,26.4551],[116.3672,26.2354],[116.4551,26.1035],[116.1914,25.8838],[116.0156,25.2686],[115.8398,25.2246],[115.9277,24.917],[115.752,24.7852],[115.8398,24.5654],[115.4004,24.7852],[114.4336,24.5215],[114.1699,24.6973],[114.4336,24.9609],[114.6973,25.1367],[114.7852,25.2686],[114.6094,25.4004],[113.9941,25.2686],[113.9063,25.4443],[113.9941,26.0596],[114.2578,26.1475],[113.9941,26.1914],[114.082,26.5869],[113.9063,26.6309],[113.9063,26.9385],[113.7305,27.1143],[113.8184,27.29],[113.6426,27.3779],[113.6426,27.5977],[113.7305,27.9492],[114.2578,28.3447]]]}},{"type":"Feature","properties":{"id":"41","size":"1700","name":"河南","cp":[113.0668,33.8818],"childNum":17},"geometry":{"type":"Polygon","coordinates":[[[110.3906,34.585],[110.8301,34.6289],[111.1816,34.8047],[111.5332,34.8486],[111.7969,35.0684],[112.0605,35.0684],[112.0605,35.2881],[112.7637,35.2002],[113.1152,35.332],[113.6426,35.6836],[113.7305,36.3428],[114.873,36.123],[114.9609,36.0791],[115.1367,36.2109],[115.3125,36.0791],[115.4883,36.167],[115.3125,35.8154],[116.1035,36.0791],[115.4883,35.7275],[115.2246,35.4199],[115.0488,35.376],[114.7852,35.0684],[115.4004,34.8486],[115.5762,34.585],[116.1914,34.585],[116.1914,34.4092],[116.543,34.2773],[116.6309,33.9258],[116.1914,33.7061],[116.0156,33.9697],[115.6641,34.0576],[115.5762,33.9258],[115.5762,33.6621],[115.4004,33.5303],[115.3125,33.1787],[114.873,33.1348],[114.873,33.0029],[115.1367,32.8711],[115.2246,32.6074],[115.5762,32.4316],[115.8398,32.5195],[115.9277,31.7725],[115.4883,31.6846],[115.4004,31.4209],[115.2246,31.4209],[115.1367,31.5967],[114.7852,31.4648],[114.6094,31.5527],[114.5215,31.7725],[114.1699,31.8604],[113.9941,31.7725],[113.8184,31.8604],[113.7305,32.4316],[113.4668,32.2998],[113.2031,32.4316],[112.3242,32.3438],[111.5332,32.6074],[111.0059,33.2666],[111.0059,33.5303],[110.6543,33.8379],[110.6543,34.1455],[110.4785,34.2334],[110.3906,34.585]]]}},{"type":"Feature","properties":{"id":"21","size":"1500","name":"辽宁","cp":[122.0438,41.0889],"childNum":14},"geometry":{"type":"Polygon","coordinates":[[[119.2676,41.3086],[119.4434,41.6162],[119.2676,41.7041],[119.3555,42.2754],[119.5313,42.3633],[119.8828,42.1875],[120.1465,41.7041],[120.498,42.0996],[121.4648,42.4951],[121.7285,42.4512],[121.9922,42.7148],[122.3438,42.6709],[122.3438,42.8467],[122.7832,42.7148],[123.1348,42.8027],[123.3105,42.9785],[123.5742,43.0225],[123.6621,43.374],[123.8379,43.4619],[124.2773,43.2422],[124.4531,42.8467],[124.7168,43.0664],[124.8926,43.0664],[124.8926,42.8027],[125.332,42.1436],[125.4199,42.0996],[125.332,41.9678],[125.332,41.6602],[125.7715,41.2207],[125.5957,40.9131],[125.6836,40.8691],[124.541,40.21],[124.1016,39.6826],[123.3984,39.6826],[123.1348,39.4189],[123.1348,39.0234],[122.0801,39.0234],[121.5527,38.7158],[121.1133,38.6719],[120.9375,38.9795],[121.377,39.1992],[121.2012,39.5508],[122.0801,40.3857],[121.9922,40.6934],[121.7285,40.8252],[121.2012,40.8252],[120.5859,40.21],[119.8828,39.9463],[119.707,40.1221],[119.5313,40.5615],[119.2676,40.5176],[118.8281,40.8252],[119.2676,41.3086]]]}},{"type":"Feature","properties":{"id":"14","size":"1450","name":"山西","cp":[111.849248,36.857014],"childNum":11},"geometry":{"type":"Polygon","coordinates":[[[110.918,38.7158],[111.1816,39.2432],[111.0938,39.375],[111.3574,39.4189],[111.4453,39.6387],[111.9727,39.5947],[112.3242,40.2539],[112.7637,40.166],[113.2031,40.3857],[113.5547,40.3418],[113.8184,40.5176],[114.082,40.5176],[114.082,40.7373],[114.2578,40.6055],[114.3457,40.3857],[114.5215,40.3418],[113.9941,39.9902],[114.3457,39.8584],[114.5215,39.5068],[114.3457,39.0674],[113.9063,39.0234],[113.8184,38.9355],[113.8184,38.8037],[113.5547,38.54],[113.5547,38.2764],[113.8184,38.1445],[113.9941,37.7051],[114.1699,37.6611],[113.7305,37.1338],[113.7305,36.8701],[113.4668,36.6504],[113.7305,36.3428],[113.6426,35.6836],[113.1152,35.332],[112.7637,35.2002],[112.0605,35.2881],[112.0605,35.0684],[111.7969,35.0684],[111.5332,34.8486],[111.1816,34.8047],[110.8301,34.6289],[110.3906,34.585],[110.2148,34.6729],[110.2148,34.8926],[110.5664,35.6396],[110.4785,36.123],[110.3906,37.002],[110.8301,37.6611],[110.4785,37.9688],[110.4785,38.1885],[110.8301,38.4961],[110.918,38.7158]]]}},{"type":"Feature","properties":{"id":"34","size":"1700","name":"安徽","cp":[117.283042,31.26119],"childNum":17},"geometry":{"type":"Polygon","coordinates":[[[116.6309,33.9258],[116.543,34.2773],[116.1914,34.4092],[116.1914,34.585],[116.3672,34.6289],[116.8945,34.4092],[117.1582,34.0576],[117.5977,34.0137],[117.7734,33.7061],[118.125,33.75],[117.9492,33.2227],[118.0371,33.1348],[118.2129,33.2227],[118.3008,32.7832],[118.7402,32.7393],[118.916,32.959],[119.1797,32.8271],[119.1797,32.4756],[118.5645,32.5635],[118.6523,32.2119],[118.4766,32.168],[118.3887,31.9482],[118.916,31.5527],[118.7402,31.377],[118.8281,31.2451],[119.3555,31.2891],[119.4434,31.1572],[119.6191,31.1133],[119.6191,31.0693],[119.4434,30.6738],[119.2676,30.6299],[119.3555,30.4102],[118.916,30.3223],[118.916,29.9707],[118.7402,29.707],[118.2129,29.3994],[118.0371,29.5752],[117.5098,29.6191],[117.1582,29.9268],[117.0703,29.8389],[117.1582,29.707],[116.7188,29.6191],[116.7188,29.751],[116.8945,29.9268],[116.7188,30.0586],[116.2793,29.7949],[116.1035,29.8389],[116.1035,30.1904],[115.752,30.6738],[116.0156,31.0254],[115.5762,31.2012],[115.4004,31.4209],[115.4883,31.6846],[115.9277,31.7725],[115.8398,32.5195],[115.5762,32.4316],[115.2246,32.6074],[115.1367,32.8711],[114.873,33.0029],[114.873,33.1348],[115.3125,33.1787],[115.4004,33.5303],[115.5762,33.6621],[115.5762,33.9258],[115.6641,34.0576],[116.0156,33.9697],[116.1914,33.7061],[116.6309,33.9258]]]}},{"type":"Feature","properties":{"id":"35","size":"2000","name":"福建","cp":[118.306239,26.075302],"childNum":9},"geometry":{"type":"Polygon","coordinates":[[[118.4766,28.3008],[118.8281,28.2568],[118.7402,28.0371],[118.916,27.4658],[119.2676,27.4219],[119.6191,27.6855],[119.7949,27.29],[120.2344,27.4219],[120.4102,27.1582],[120.7617,27.0264],[120.6738,26.8945],[120.2344,26.8506],[120.2344,26.7188],[120.4102,26.6748],[120.498,26.3672],[120.2344,26.2793],[120.4102,26.1475],[120.0586,26.1914],[119.9707,25.9277],[119.7949,25.9277],[119.9707,25.4004],[119.7949,25.2686],[119.5313,25.1367],[119.4434,25.0049],[119.2676,25.0928],[118.916,24.8291],[118.6523,24.5215],[118.4766,24.5215],[118.4766,24.4336],[118.2129,24.3457],[118.2129,24.1699],[117.8613,23.9941],[117.7734,23.7744],[117.5098,23.5986],[117.1582,23.5547],[116.9824,23.9063],[116.9824,24.1699],[116.7188,24.6533],[116.543,24.6094],[116.3672,24.873],[116.2793,24.7852],[115.9277,24.917],[115.8398,25.2246],[116.0156,25.2686],[116.1914,25.8838],[116.4551,26.1035],[116.3672,26.2354],[116.6309,26.4551],[116.543,26.8066],[117.0703,27.1143],[117.1582,27.29],[116.9824,27.6416],[117.5098,27.9932],[117.7734,27.8174],[118.3008,28.0811],[118.4766,28.3008]]]}},{"type":"Feature","properties":{"id":"33","size":"2100","name":"浙江","cp":[120.498,29.0918],"childNum":11},"geometry":{"type":"Polygon","coordinates":[[[118.2129,29.3994],[118.7402,29.707],[118.916,29.9707],[118.916,30.3223],[119.3555,30.4102],[119.2676,30.6299],[119.4434,30.6738],[119.6191,31.0693],[119.6191,31.1133],[119.9707,31.1572],[120.498,30.8057],[120.9375,31.0254],[121.2891,30.6738],[121.9922,30.8057],[122.6953,30.8936],[122.8711,30.7178],[122.959,30.1465],[122.6074,30.1025],[122.6074,29.9268],[122.168,29.5313],[122.3438,28.8721],[121.9922,28.8721],[121.9922,28.4326],[121.7285,28.3447],[121.7285,28.2129],[121.4648,28.2129],[121.5527,28.0371],[121.2891,27.9492],[121.1133,27.4219],[120.6738,27.334],[120.6738,27.1582],[120.9375,27.0264],[120.7617,27.0264],[120.4102,27.1582],[120.2344,27.4219],[119.7949,27.29],[119.6191,27.6855],[119.2676,27.4219],[118.916,27.4658],[118.7402,28.0371],[118.8281,28.2568],[118.4766,28.3008],[118.4766,28.3447],[118.3887,28.7842],[118.0371,29.0479],[118.0371,29.1797],[118.2129,29.3994]]]}},{"type":"Feature","properties":{"id":"32","size":"1950","name":"江苏","cp":[119.767413,33.041544],"childNum":13},"geometry":{"type":"Polygon","coordinates":[[[116.3672,34.6289],[116.4551,34.8926],[116.8066,34.9365],[117.2461,34.4531],[117.334,34.585],[117.5977,34.4531],[117.9492,34.6729],[118.125,34.6289],[118.2129,34.4092],[118.3887,34.4092],[118.4766,34.6729],[118.7402,34.7168],[118.916,35.0244],[119.2676,35.1123],[119.3555,35.0244],[119.3555,34.8486],[119.707,34.585],[120.3223,34.3652],[120.9375,33.0469],[121.0254,32.6514],[121.377,32.4756],[121.4648,32.168],[121.9043,31.9922],[121.9922,31.6846],[121.9922,31.5967],[121.2012,31.8604],[121.1133,31.7285],[121.377,31.5088],[121.2012,31.4648],[120.9375,31.0254],[120.498,30.8057],[119.9707,31.1572],[119.6191,31.1133],[119.4434,31.1572],[119.3555,31.2891],[118.8281,31.2451],[118.7402,31.377],[118.916,31.5527],[118.3887,31.9482],[118.4766,32.168],[118.6523,32.2119],[118.5645,32.5635],[119.1797,32.4756],[119.1797,32.8271],[118.916,32.959],[118.7402,32.7393],[118.3008,32.7832],[118.2129,33.2227],[118.0371,33.1348],[117.9492,33.2227],[118.125,33.75],[117.7734,33.7061],[117.5977,34.0137],[117.1582,34.0576],[116.8945,34.4092],[116.3672,34.6289]]]}},{"type":"Feature","properties":{"id":"50","size":"2380","name":"重庆","cp":[107.304962,29.533155],"childNum":40},"geometry":{"type":"Polygon","coordinates":[[[108.5449,31.6846],[108.2813,31.9043],[108.3691,32.168],[108.5449,32.2119],[109.0723,31.9482],[109.248,31.7285],[109.5996,31.7285],[109.7754,31.6846],[109.6875,31.5527],[110.127,31.377],[110.2148,31.1572],[110.0391,30.8057],[109.8633,30.8936],[109.4238,30.542],[109.248,30.6299],[109.1602,30.542],[109.0723,30.6299],[108.8086,30.498],[108.6328,30.5859],[108.457,30.4102],[108.5449,30.2344],[108.457,29.7949],[108.6328,29.8389],[108.9844,29.3115],[109.0723,29.3555],[109.248,29.1357],[109.248,28.4766],[109.0723,28.2129],[108.7207,28.2129],[108.7207,28.4766],[108.5449,28.3887],[108.5449,28.6523],[108.3691,28.6523],[108.2813,29.0918],[107.8418,29.0039],[107.8418,29.1357],[107.5781,29.2236],[107.4023,29.1797],[107.4023,28.8721],[106.875,28.7842],[106.6992,28.4766],[106.6113,28.5205],[106.6113,28.6523],[106.5234,28.7842],[106.4355,28.7842],[106.5234,28.5645],[106.3477,28.5205],[106.2598,28.8721],[105.8203,28.96],[105.7324,29.2676],[105.4688,29.3115],[105.293,29.5313],[105.7324,29.8828],[105.5566,30.1025],[105.6445,30.2783],[105.8203,30.4541],[106.2598,30.1904],[106.6113,30.3223],[106.7871,30.0146],[107.0508,30.0146],[107.4902,30.6299],[107.4023,30.7617],[107.4902,30.8496],[107.9297,30.8496],[108.1934,31.5088],[108.5449,31.6846]]]}},{"type":"Feature","properties":{"id":"64","size":"2100","name":"宁夏","cp":[105.9961,37.3096],"childNum":5},"geometry":{"type":"Polygon","coordinates":[[[104.3262,37.4414],[105.8203,37.793],[105.9082,38.7158],[106.3477,39.2871],[106.7871,39.375],[106.9629,38.9795],[106.5234,38.3203],[106.7871,38.1885],[107.3145,38.1006],[107.666,37.8809],[107.3145,37.6172],[107.3145,37.0898],[106.6113,37.0898],[106.6113,36.7822],[106.4355,36.5625],[106.5234,36.4746],[106.5234,36.2549],[106.875,36.123],[106.9629,35.8154],[106.6992,35.6836],[106.4355,35.6836],[106.5234,35.332],[106.3477,35.2441],[106.2598,35.4199],[106.084,35.376],[105.9961,35.4199],[106.084,35.4639],[105.9961,35.4639],[105.8203,35.5518],[105.7324,35.7275],[105.3809,35.7715],[105.293,35.9912],[105.4688,36.123],[105.2051,36.6943],[105.293,36.8262],[104.8535,37.2217],[104.5898,37.2217],[104.5898,37.4414],[104.3262,37.4414]]]}},{"type":"Feature","properties":{"id":"46","size":"4500","name":"海南","cp":[109.9512,19.2041],"childNum":18},"geometry":{"type":"Polygon","coordinates":[[[108.6328,19.3799],[109.0723,19.6436],[109.248,19.9512],[109.5996,20.0391],[110.0391,20.127],[110.3906,20.127],[110.5664,20.2588],[110.6543,20.2588],[111.0938,19.9512],[111.2695,19.9951],[110.6543,19.1602],[110.5664,18.6768],[110.2148,18.5889],[110.0391,18.3691],[109.8633,18.3691],[109.6875,18.1055],[108.9844,18.2813],[108.6328,18.457],[108.6328,19.3799]]]}},{"type":"Feature","properties":{"id":"71","size":"3000","name":"台湾","cp":[120.0254,23.5986],"childNum":1},"geometry":{"type":"Polygon","coordinates":[[[121.9043,25.0488],[121.9922,25.0049],[121.8164,24.7412],[121.9043,24.5654],[121.6406,24.0381],[121.377,23.1152],[121.0254,22.6758],[120.8496,22.0605],[120.7617,21.9287],[120.6738,22.3242],[120.2344,22.5879],[120.0586,23.0713],[120.1465,23.6865],[121.0254,25.0488],[121.5527,25.3125],[121.9043,25.0488]]]}},{"type":"Feature","properties":{"id":"11","size":"5000","name":"北京","cp":[116.4551,40.2539],"childNum":19},"geometry":{"type":"Polygon","coordinates":[[[117.4219,40.21],[117.334,40.1221],[117.2461,40.0781],[116.8066,39.9902],[116.8945,39.8145],[116.8945,39.6826],[116.8066,39.5947],[116.543,39.5947],[116.3672,39.4629],[116.1914,39.5947],[115.752,39.5068],[115.4883,39.6387],[115.4004,39.9463],[115.9277,40.2539],[115.752,40.5615],[116.1035,40.6055],[116.1914,40.7813],[116.4551,40.7813],[116.3672,40.9131],[116.6309,41.0449],[116.9824,40.6934],[117.4219,40.6494],[117.2461,40.5176],[117.4219,40.21]]]}},{"type":"Feature","properties":{"id":"12","size":"5000","name":"天津","cp":[117.4219,39.4189],"childNum":18},"geometry":{"type":"Polygon","coordinates":[[[116.8066,39.5947],[116.8945,39.6826],[117.1582,39.6387],[117.1582,39.8145],[117.2461,40.0781],[117.334,40.1221],[117.4219,40.21],[117.6855,40.0781],[117.6855,39.9902],[117.5098,39.9902],[117.5098,39.7705],[117.6855,39.5947],[117.9492,39.5947],[117.8613,39.4189],[118.0371,39.2432],[118.0371,39.1992],[117.8613,39.1113],[117.5977,38.6279],[117.2461,38.54],[116.7188,38.8037],[116.7188,38.9355],[116.8945,39.1113],[116.8066,39.5947]]]}},{"type":"Feature","properties":{"id":"31","size":"7500","name":"上海","cp":[121.4648,31.2891],"childNum":19},"geometry":{"type":"Polygon","coordinates":[[[120.9375,31.0254],[121.2012,31.4648],[121.377,31.5088],[121.1133,31.7285],[121.2012,31.8604],[121.9922,31.5967],[121.9043,31.1572],[121.9922,30.8057],[121.2891,30.6738],[120.9375,31.0254]]]}},{"type":"Feature","properties":{"id":"81","size":"18000","name":"香港","cp":[114.1178,22.3242],"childNum":1},"geometry":{"type":"Polygon","coordinates":[[[114.6094,22.4121],[114.5215,22.1484],[114.3457,22.1484],[113.9063,22.1484],[113.8184,22.1924],[113.9063,22.4121],[114.1699,22.5439],[114.3457,22.5439],[114.4336,22.5439],[114.4336,22.4121],[114.6094,22.4121]]]}},{"type":"Feature","properties":{"id":"82","size":"27","name":"澳门","cp":[111.5547,22.1484],"childNum":1},"geometry":{"type":"Polygon","coordinates":[[[113.5986,22.1649],[113.6096,22.1265],[113.5547,22.11],[113.5437,22.2034],[113.5767,22.2034],[113.5986,22.1649]]]}}]} diff --git a/public/json/regions-data.json b/public/json/regions-data.json new file mode 100644 index 0000000..4cbd130 --- /dev/null +++ b/public/json/regions-data.json @@ -0,0 +1 @@ +[{"label":"北京","value":"110000","children":[{"value":"110100","label":"北京市","children":[{"value":"110101","label":"东城区"},{"value":"110102","label":"西城区"},{"value":"110103","label":"崇文区"},{"value":"110104","label":"宣武区"},{"value":"110105","label":"朝阳区"},{"value":"110106","label":"丰台区"},{"value":"110107","label":"石景山区"},{"value":"110108","label":"海淀区"},{"value":"110109","label":"门头沟区"},{"value":"110111","label":"房山区"},{"value":"110112","label":"通州区"},{"value":"110113","label":"顺义区"},{"value":"110114","label":"昌平区"},{"value":"110115","label":"大兴区"},{"value":"110116","label":"怀柔区"},{"value":"110117","label":"平谷区"},{"value":"110228","label":"密云县"},{"value":"110229","label":"延庆县"}]}]},{"label":"天津","value":"120000","children":[{"value":"120100","label":"天津市","children":[{"value":"120101","label":"和平区"},{"value":"120102","label":"河东区"},{"value":"120103","label":"河西区"},{"value":"120104","label":"南开区"},{"value":"120105","label":"河北区"},{"value":"120106","label":"红桥区"},{"value":"120107","label":"塘沽区"},{"value":"120108","label":"汉沽区"},{"value":"120109","label":"大港区"},{"value":"120110","label":"东丽区"},{"value":"120111","label":"西青区"},{"value":"120112","label":"津南区"},{"value":"120113","label":"北辰区"},{"value":"120114","label":"武清区"},{"value":"120115","label":"宝坻区"},{"value":"120116","label":"滨海新区"},{"value":"120221","label":"宁河县"},{"value":"120223","label":"静海县"},{"value":"120225","label":"蓟县"}]}]},{"label":"河北省","value":"130000","children":[{"value":"130100","label":"石家庄市","children":[{"value":"130102","label":"长安区"},{"value":"130103","label":"桥东区"},{"value":"130104","label":"桥西区"},{"value":"130105","label":"新华区"},{"value":"130107","label":"井陉矿区"},{"value":"130108","label":"裕华区"},{"value":"130121","label":"井陉县"},{"value":"130123","label":"正定县"},{"value":"130124","label":"栾城县"},{"value":"130125","label":"行唐县"},{"value":"130126","label":"灵寿县"},{"value":"130127","label":"高邑县"},{"value":"130128","label":"深泽县"},{"value":"130129","label":"赞皇县"},{"value":"130130","label":"无极县"},{"value":"130131","label":"平山县"},{"value":"130132","label":"元氏县"},{"value":"130133","label":"赵县"},{"value":"130181","label":"辛集市"},{"value":"130182","label":"藁城市"},{"value":"130183","label":"晋州市"},{"value":"130184","label":"新乐市"},{"value":"130185","label":"鹿泉市"}]},{"value":"130200","label":"唐山市","children":[{"value":"130202","label":"路南区"},{"value":"130203","label":"路北区"},{"value":"130204","label":"古冶区"},{"value":"130205","label":"开平区"},{"value":"130207","label":"丰南区"},{"value":"130208","label":"丰润区"},{"value":"130223","label":"滦县"},{"value":"130224","label":"滦南县"},{"value":"130225","label":"乐亭县"},{"value":"130227","label":"迁西县"},{"value":"130229","label":"玉田县"},{"value":"130230","label":"唐海县"},{"value":"130281","label":"遵化市"},{"value":"130283","label":"迁安市"}]},{"value":"130300","label":"秦皇岛市","children":[{"value":"130302","label":"海港区"},{"value":"130303","label":"山海关区"},{"value":"130304","label":"北戴河区"},{"value":"130321","label":"青龙满族自治县"},{"value":"130322","label":"昌黎县"},{"value":"130323","label":"抚宁县"},{"value":"130324","label":"卢龙县"},{"value":"130399","label":"经济技术开发区"}]},{"value":"130400","label":"邯郸市","children":[{"value":"130402","label":"邯山区"},{"value":"130403","label":"丛台区"},{"value":"130404","label":"复兴区"},{"value":"130406","label":"峰峰矿区"},{"value":"130421","label":"邯郸县"},{"value":"130423","label":"临漳县"},{"value":"130424","label":"成安县"},{"value":"130425","label":"大名县"},{"value":"130426","label":"涉县"},{"value":"130427","label":"磁县"},{"value":"130428","label":"肥乡县"},{"value":"130429","label":"永年县"},{"value":"130430","label":"邱县"},{"value":"130431","label":"鸡泽县"},{"value":"130432","label":"广平县"},{"value":"130433","label":"馆陶县"},{"value":"130434","label":"魏县"},{"value":"130435","label":"曲周县"},{"value":"130481","label":"武安市"}]},{"value":"130500","label":"邢台市","children":[{"value":"130502","label":"桥东区"},{"value":"130503","label":"桥西区"},{"value":"130521","label":"邢台县"},{"value":"130522","label":"临城县"},{"value":"130523","label":"内丘县"},{"value":"130524","label":"柏乡县"},{"value":"130525","label":"隆尧县"},{"value":"130526","label":"任县"},{"value":"130527","label":"南和县"},{"value":"130528","label":"宁晋县"},{"value":"130529","label":"巨鹿县"},{"value":"130530","label":"新河县"},{"value":"130531","label":"广宗县"},{"value":"130532","label":"平乡县"},{"value":"130533","label":"威县"},{"value":"130534","label":"清河县"},{"value":"130535","label":"临西县"},{"value":"130581","label":"南宫市"},{"value":"130582","label":"沙河市"}]},{"value":"130600","label":"保定市","children":[{"value":"130602","label":"新市区"},{"value":"130603","label":"北市区"},{"value":"130604","label":"南市区"},{"value":"130621","label":"满城县"},{"value":"130622","label":"清苑县"},{"value":"130623","label":"涞水县"},{"value":"130624","label":"阜平县"},{"value":"130625","label":"徐水县"},{"value":"130626","label":"定兴县"},{"value":"130627","label":"唐县"},{"value":"130628","label":"高阳县"},{"value":"130629","label":"容城县"},{"value":"130630","label":"涞源县"},{"value":"130631","label":"望都县"},{"value":"130632","label":"安新县"},{"value":"130633","label":"易县"},{"value":"130634","label":"曲阳县"},{"value":"130635","label":"蠡县"},{"value":"130636","label":"顺平县"},{"value":"130637","label":"博野县"},{"value":"130638","label":"雄县"},{"value":"130681","label":"涿州市"},{"value":"130682","label":"定州市"},{"value":"130683","label":"安国市"},{"value":"130684","label":"高碑店市"},{"value":"130698","label":"高开区"}]},{"value":"130700","label":"张家口市","children":[{"value":"130702","label":"桥东区"},{"value":"130703","label":"桥西区"},{"value":"130705","label":"宣化区"},{"value":"130706","label":"下花园区"},{"value":"130721","label":"宣化县"},{"value":"130722","label":"张北县"},{"value":"130723","label":"康保县"},{"value":"130724","label":"沽源县"},{"value":"130725","label":"尚义县"},{"value":"130726","label":"蔚县"},{"value":"130727","label":"阳原县"},{"value":"130728","label":"怀安县"},{"value":"130729","label":"万全县"},{"value":"130730","label":"怀来县"},{"value":"130731","label":"涿鹿县"},{"value":"130732","label":"赤城县"},{"value":"130733","label":"崇礼县"}]},{"value":"130800","label":"承德市","children":[{"value":"130802","label":"双桥区"},{"value":"130803","label":"双滦区"},{"value":"130804","label":"鹰手营子矿区"},{"value":"130821","label":"承德县"},{"value":"130822","label":"兴隆县"},{"value":"130823","label":"平泉县"},{"value":"130824","label":"滦平县"},{"value":"130825","label":"隆化县"},{"value":"130826","label":"丰宁满族自治县"},{"value":"130827","label":"宽城满族自治县"},{"value":"130828","label":"围场满族蒙古族自治县"}]},{"value":"130900","label":"沧州市","children":[{"value":"130902","label":"新华区"},{"value":"130903","label":"运河区"},{"value":"130921","label":"沧县"},{"value":"130922","label":"青县"},{"value":"130923","label":"东光县"},{"value":"130924","label":"海兴县"},{"value":"130925","label":"盐山县"},{"value":"130926","label":"肃宁县"},{"value":"130927","label":"南皮县"},{"value":"130928","label":"吴桥县"},{"value":"130929","label":"献县"},{"value":"130930","label":"孟村回族自治县"},{"value":"130981","label":"泊头市"},{"value":"130982","label":"任丘市"},{"value":"130983","label":"黄骅市"},{"value":"130984","label":"河间市"}]},{"value":"131000","label":"廊坊市","children":[{"value":"131002","label":"安次区"},{"value":"131003","label":"广阳区"},{"value":"131022","label":"固安县"},{"value":"131023","label":"永清县"},{"value":"131024","label":"香河县"},{"value":"131025","label":"大城县"},{"value":"131026","label":"文安县"},{"value":"131028","label":"大厂回族自治县"},{"value":"131051","label":"开发区"},{"value":"131052","label":"燕郊经济技术开发区"},{"value":"131081","label":"霸州市"},{"value":"131082","label":"三河市"}]},{"value":"131100","label":"衡水市","children":[{"value":"131102","label":"桃城区"},{"value":"131121","label":"枣强县"},{"value":"131122","label":"武邑县"},{"value":"131123","label":"武强县"},{"value":"131124","label":"饶阳县"},{"value":"131125","label":"安平县"},{"value":"131126","label":"故城县"},{"value":"131127","label":"景县"},{"value":"131128","label":"阜城县"},{"value":"131181","label":"冀州市"},{"value":"131182","label":"深州市"}]}]},{"label":"山西省","value":"140000","children":[{"value":"140100","label":"太原市","children":[{"value":"140105","label":"小店区"},{"value":"140106","label":"迎泽区"},{"value":"140107","label":"杏花岭区"},{"value":"140108","label":"尖草坪区"},{"value":"140109","label":"万柏林区"},{"value":"140110","label":"晋源区"},{"value":"140121","label":"清徐县"},{"value":"140122","label":"阳曲县"},{"value":"140123","label":"娄烦县"},{"value":"140181","label":"古交市"}]},{"value":"140200","label":"大同市","children":[{"value":"140202","label":"城区"},{"value":"140203","label":"矿区"},{"value":"140211","label":"南郊区"},{"value":"140212","label":"新荣区"},{"value":"140221","label":"阳高县"},{"value":"140222","label":"天镇县"},{"value":"140223","label":"广灵县"},{"value":"140224","label":"灵丘县"},{"value":"140225","label":"浑源县"},{"value":"140226","label":"左云县"},{"value":"140227","label":"大同县"}]},{"value":"140300","label":"阳泉市","children":[{"value":"140302","label":"城区"},{"value":"140303","label":"矿区"},{"value":"140311","label":"郊区"},{"value":"140321","label":"平定县"},{"value":"140322","label":"盂县"}]},{"value":"140400","label":"长治市","children":[{"value":"140421","label":"长治县"},{"value":"140423","label":"襄垣县"},{"value":"140424","label":"屯留县"},{"value":"140425","label":"平顺县"},{"value":"140426","label":"黎城县"},{"value":"140427","label":"壶关县"},{"value":"140428","label":"长子县"},{"value":"140429","label":"武乡县"},{"value":"140430","label":"沁县"},{"value":"140431","label":"沁源县"},{"value":"140481","label":"潞城市"},{"value":"140482","label":"城区"},{"value":"140483","label":"郊区"},{"value":"140484","label":"高新区"}]},{"value":"140500","label":"晋城市","children":[{"value":"140502","label":"城区"},{"value":"140521","label":"沁水县"},{"value":"140522","label":"阳城县"},{"value":"140524","label":"陵川县"},{"value":"140525","label":"泽州县"},{"value":"140581","label":"高平市"}]},{"value":"140600","label":"朔州市","children":[{"value":"140602","label":"朔城区"},{"value":"140603","label":"平鲁区"},{"value":"140621","label":"山阴县"},{"value":"140622","label":"应县"},{"value":"140623","label":"右玉县"},{"value":"140624","label":"怀仁县"}]},{"value":"140700","label":"晋中市","children":[{"value":"140702","label":"榆次区"},{"value":"140721","label":"榆社县"},{"value":"140722","label":"左权县"},{"value":"140723","label":"和顺县"},{"value":"140724","label":"昔阳县"},{"value":"140725","label":"寿阳县"},{"value":"140726","label":"太谷县"},{"value":"140727","label":"祁县"},{"value":"140728","label":"平遥县"},{"value":"140729","label":"灵石县"},{"value":"140781","label":"介休市"}]},{"value":"140800","label":"运城市","children":[{"value":"140802","label":"盐湖区"},{"value":"140821","label":"临猗县"},{"value":"140822","label":"万荣县"},{"value":"140823","label":"闻喜县"},{"value":"140824","label":"稷山县"},{"value":"140825","label":"新绛县"},{"value":"140826","label":"绛县"},{"value":"140827","label":"垣曲县"},{"value":"140828","label":"夏县"},{"value":"140829","label":"平陆县"},{"value":"140830","label":"芮城县"},{"value":"140881","label":"永济市"},{"value":"140882","label":"河津市"}]},{"value":"140900","label":"忻州市","children":[{"value":"140902","label":"忻府区"},{"value":"140921","label":"定襄县"},{"value":"140922","label":"五台县"},{"value":"140923","label":"代县"},{"value":"140924","label":"繁峙县"},{"value":"140925","label":"宁武县"},{"value":"140926","label":"静乐县"},{"value":"140927","label":"神池县"},{"value":"140928","label":"五寨县"},{"value":"140929","label":"岢岚县"},{"value":"140930","label":"河曲县"},{"value":"140931","label":"保德县"},{"value":"140932","label":"偏关县"},{"value":"140981","label":"原平市"}]},{"value":"141000","label":"临汾市","children":[{"value":"141002","label":"尧都区"},{"value":"141021","label":"曲沃县"},{"value":"141022","label":"翼城县"},{"value":"141023","label":"襄汾县"},{"value":"141024","label":"洪洞县"},{"value":"141025","label":"古县"},{"value":"141026","label":"安泽县"},{"value":"141027","label":"浮山县"},{"value":"141028","label":"吉县"},{"value":"141029","label":"乡宁县"},{"value":"141030","label":"大宁县"},{"value":"141031","label":"隰县"},{"value":"141032","label":"永和县"},{"value":"141033","label":"蒲县"},{"value":"141034","label":"汾西县"},{"value":"141081","label":"侯马市"},{"value":"141082","label":"霍州市"}]},{"value":"141100","label":"吕梁市","children":[{"value":"141102","label":"离石区"},{"value":"141121","label":"文水县"},{"value":"141122","label":"交城县"},{"value":"141123","label":"兴县"},{"value":"141124","label":"临县"},{"value":"141125","label":"柳林县"},{"value":"141126","label":"石楼县"},{"value":"141127","label":"岚县"},{"value":"141128","label":"方山县"},{"value":"141129","label":"中阳县"},{"value":"141130","label":"交口县"},{"value":"141181","label":"孝义市"},{"value":"141182","label":"汾阳市"}]}]},{"label":"内蒙古自治区","value":"150000","children":[{"value":"150100","label":"呼和浩特市","children":[{"value":"150102","label":"新城区"},{"value":"150103","label":"回民区"},{"value":"150104","label":"玉泉区"},{"value":"150105","label":"赛罕区"},{"value":"150121","label":"土默特左旗"},{"value":"150122","label":"托克托县"},{"value":"150123","label":"和林格尔县"},{"value":"150124","label":"清水河县"},{"value":"150125","label":"武川县"}]},{"value":"150200","label":"包头市","children":[{"value":"150202","label":"东河区"},{"value":"150203","label":"昆都仑区"},{"value":"150204","label":"青山区"},{"value":"150205","label":"石拐区"},{"value":"150206","label":"白云矿区"},{"value":"150207","label":"九原区"},{"value":"150221","label":"土默特右旗"},{"value":"150222","label":"固阳县"},{"value":"150223","label":"达尔罕茂明安联合旗"}]},{"value":"150300","label":"乌海市","children":[{"value":"150302","label":"海勃湾区"},{"value":"150303","label":"海南区"},{"value":"150304","label":"乌达区"}]},{"value":"150400","label":"赤峰市","children":[{"value":"150402","label":"红山区"},{"value":"150403","label":"元宝山区"},{"value":"150404","label":"松山区"},{"value":"150421","label":"阿鲁科尔沁旗"},{"value":"150422","label":"巴林左旗"},{"value":"150423","label":"巴林右旗"},{"value":"150424","label":"林西县"},{"value":"150425","label":"克什克腾旗"},{"value":"150426","label":"翁牛特旗"},{"value":"150428","label":"喀喇沁旗"},{"value":"150429","label":"宁城县"},{"value":"150430","label":"敖汉旗"}]},{"value":"150500","label":"通辽市","children":[{"value":"150502","label":"科尔沁区"},{"value":"150521","label":"科尔沁左翼中旗"},{"value":"150522","label":"科尔沁左翼后旗"},{"value":"150523","label":"开鲁县"},{"value":"150524","label":"库伦旗"},{"value":"150525","label":"奈曼旗"},{"value":"150526","label":"扎鲁特旗"},{"value":"150581","label":"霍林郭勒市"}]},{"value":"150600","label":"鄂尔多斯市","children":[{"value":"150602","label":"东胜区"},{"value":"150621","label":"达拉特旗"},{"value":"150622","label":"准格尔旗"},{"value":"150623","label":"鄂托克前旗"},{"value":"150624","label":"鄂托克旗"},{"value":"150625","label":"杭锦旗"},{"value":"150626","label":"乌审旗"},{"value":"150627","label":"伊金霍洛旗"}]},{"value":"150700","label":"呼伦贝尔市","children":[{"value":"150702","label":"海拉尔区"},{"value":"150721","label":"阿荣旗"},{"value":"150722","label":"莫力达瓦达斡尔族自治旗"},{"value":"150723","label":"鄂伦春自治旗"},{"value":"150724","label":"鄂温克族自治旗"},{"value":"150725","label":"陈巴尔虎旗"},{"value":"150726","label":"新巴尔虎左旗"},{"value":"150727","label":"新巴尔虎右旗"},{"value":"150781","label":"满洲里市"},{"value":"150782","label":"牙克石市"},{"value":"150783","label":"扎兰屯市"},{"value":"150784","label":"额尔古纳市"},{"value":"150785","label":"根河市"}]},{"value":"150800","label":"巴彦淖尔市","children":[{"value":"150802","label":"临河区"},{"value":"150821","label":"五原县"},{"value":"150822","label":"磴口县"},{"value":"150823","label":"乌拉特前旗"},{"value":"150824","label":"乌拉特中旗"},{"value":"150825","label":"乌拉特后旗"},{"value":"150826","label":"杭锦后旗"}]},{"value":"150900","label":"乌兰察布市","children":[{"value":"150902","label":"集宁区"},{"value":"150921","label":"卓资县"},{"value":"150922","label":"化德县"},{"value":"150923","label":"商都县"},{"value":"150924","label":"兴和县"},{"value":"150925","label":"凉城县"},{"value":"150926","label":"察哈尔右翼前旗"},{"value":"150927","label":"察哈尔右翼中旗"},{"value":"150928","label":"察哈尔右翼后旗"},{"value":"150929","label":"四子王旗"},{"value":"150981","label":"丰镇市"}]},{"value":"152200","label":"兴安盟","children":[{"value":"152201","label":"乌兰浩特市"},{"value":"152202","label":"阿尔山市"},{"value":"152221","label":"科尔沁右翼前旗"},{"value":"152222","label":"科尔沁右翼中旗"},{"value":"152223","label":"扎赉特旗"},{"value":"152224","label":"突泉县"}]},{"value":"152500","label":"锡林郭勒盟","children":[{"value":"152501","label":"二连浩特市"},{"value":"152502","label":"锡林浩特市"},{"value":"152522","label":"阿巴嘎旗"},{"value":"152523","label":"苏尼特左旗"},{"value":"152524","label":"苏尼特右旗"},{"value":"152525","label":"东乌珠穆沁旗"},{"value":"152526","label":"西乌珠穆沁旗"},{"value":"152527","label":"太仆寺旗"},{"value":"152528","label":"镶黄旗"},{"value":"152529","label":"正镶白旗"},{"value":"152530","label":"正蓝旗"},{"value":"152531","label":"多伦县"}]},{"value":"152900","label":"阿拉善盟","children":[{"value":"152921","label":"阿拉善左旗"},{"value":"152922","label":"阿拉善右旗"},{"value":"152923","label":"额济纳旗"}]}]},{"label":"辽宁省","value":"210000","children":[{"value":"210100","label":"沈阳市","children":[{"value":"210102","label":"和平区"},{"value":"210103","label":"沈河区"},{"value":"210104","label":"大东区"},{"value":"210105","label":"皇姑区"},{"value":"210106","label":"铁西区"},{"value":"210111","label":"苏家屯区"},{"value":"210112","label":"东陵区"},{"value":"210113","label":"新城子区"},{"value":"210114","label":"于洪区"},{"value":"210122","label":"辽中县"},{"value":"210123","label":"康平县"},{"value":"210124","label":"法库县"},{"value":"210181","label":"新民市"},{"value":"210182","label":"浑南新区"},{"value":"210183","label":"张士开发区"},{"value":"210184","label":"沈北新区"}]},{"value":"210200","label":"大连市","children":[{"value":"210202","label":"中山区"},{"value":"210203","label":"西岗区"},{"value":"210204","label":"沙河口区"},{"value":"210211","label":"甘井子区"},{"value":"210212","label":"旅顺口区"},{"value":"210213","label":"金州区"},{"value":"210224","label":"长海县"},{"value":"210251","label":"开发区"},{"value":"210281","label":"瓦房店市"},{"value":"210282","label":"普兰店市"},{"value":"210283","label":"庄河市"},{"value":"210297","label":"岭前区"}]},{"value":"210300","label":"鞍山市","children":[{"value":"210302","label":"铁东区"},{"value":"210303","label":"铁西区"},{"value":"210304","label":"立山区"},{"value":"210311","label":"千山区"},{"value":"210321","label":"台安县"},{"value":"210323","label":"岫岩满族自治县"},{"value":"210351","label":"高新区"},{"value":"210381","label":"海城市"}]},{"value":"210400","label":"抚顺市","children":[{"value":"210402","label":"新抚区"},{"value":"210403","label":"东洲区"},{"value":"210404","label":"望花区"},{"value":"210411","label":"顺城区"},{"value":"210421","label":"抚顺县"},{"value":"210422","label":"新宾满族自治县"},{"value":"210423","label":"清原满族自治县"}]},{"value":"210500","label":"本溪市","children":[{"value":"210502","label":"平山区"},{"value":"210503","label":"溪湖区"},{"value":"210504","label":"明山区"},{"value":"210505","label":"南芬区"},{"value":"210521","label":"本溪满族自治县"},{"value":"210522","label":"桓仁满族自治县"}]},{"value":"210600","label":"丹东市","children":[{"value":"210602","label":"元宝区"},{"value":"210603","label":"振兴区"},{"value":"210604","label":"振安区"},{"value":"210624","label":"宽甸满族自治县"},{"value":"210681","label":"东港市"},{"value":"210682","label":"凤城市"}]},{"value":"210700","label":"锦州市","children":[{"value":"210702","label":"古塔区"},{"value":"210703","label":"凌河区"},{"value":"210711","label":"太和区"},{"value":"210726","label":"黑山县"},{"value":"210727","label":"义县"},{"value":"210781","label":"凌海市"},{"value":"210782","label":"北镇市"}]},{"value":"210800","label":"营口市","children":[{"value":"210802","label":"站前区"},{"value":"210803","label":"西市区"},{"value":"210804","label":"鲅鱼圈区"},{"value":"210811","label":"老边区"},{"value":"210881","label":"盖州市"},{"value":"210882","label":"大石桥市"}]},{"value":"210900","label":"阜新市","children":[{"value":"210902","label":"海州区"},{"value":"210903","label":"新邱区"},{"value":"210904","label":"太平区"},{"value":"210905","label":"清河门区"},{"value":"210911","label":"细河区"},{"value":"210921","label":"阜新蒙古族自治县"},{"value":"210922","label":"彰武县"}]},{"value":"211000","label":"辽阳市","children":[{"value":"211002","label":"白塔区"},{"value":"211003","label":"文圣区"},{"value":"211004","label":"宏伟区"},{"value":"211005","label":"弓长岭区"},{"value":"211011","label":"太子河区"},{"value":"211021","label":"辽阳县"},{"value":"211081","label":"灯塔市"}]},{"value":"211100","label":"盘锦市","children":[{"value":"211102","label":"双台子区"},{"value":"211103","label":"兴隆台区"},{"value":"211121","label":"大洼县"},{"value":"211122","label":"盘山县"}]},{"value":"211200","label":"铁岭市","children":[{"value":"211202","label":"银州区"},{"value":"211204","label":"清河区"},{"value":"211221","label":"铁岭县"},{"value":"211223","label":"西丰县"},{"value":"211224","label":"昌图县"},{"value":"211281","label":"调兵山市"},{"value":"211282","label":"开原市"}]},{"value":"211300","label":"朝阳市","children":[{"value":"211302","label":"双塔区"},{"value":"211303","label":"龙城区"},{"value":"211321","label":"朝阳县"},{"value":"211322","label":"建平县"},{"value":"211324","label":"喀喇沁左翼蒙古族自治县"},{"value":"211381","label":"北票市"},{"value":"211382","label":"凌源市"}]},{"value":"211400","label":"葫芦岛市","children":[{"value":"211402","label":"连山区"},{"value":"211403","label":"龙港区"},{"value":"211404","label":"南票区"},{"value":"211421","label":"绥中县"},{"value":"211422","label":"建昌县"},{"value":"211481","label":"兴城市"}]}]},{"label":"吉林省","value":"220000","children":[{"value":"220100","label":"长春市","children":[{"value":"220102","label":"南关区"},{"value":"220103","label":"宽城区"},{"value":"220104","label":"朝阳区"},{"value":"220105","label":"二道区"},{"value":"220106","label":"绿园区"},{"value":"220112","label":"双阳区"},{"value":"220122","label":"农安县"},{"value":"220181","label":"九台市"},{"value":"220182","label":"榆树市"},{"value":"220183","label":"德惠市"},{"value":"220184","label":"高新技术产业开发区"},{"value":"220185","label":"汽车产业开发区"},{"value":"220186","label":"经济技术开发区"},{"value":"220187","label":"净月旅游开发区"}]},{"value":"220200","label":"吉林市","children":[{"value":"220202","label":"昌邑区"},{"value":"220203","label":"龙潭区"},{"value":"220204","label":"船营区"},{"value":"220211","label":"丰满区"},{"value":"220221","label":"永吉县"},{"value":"220281","label":"蛟河市"},{"value":"220282","label":"桦甸市"},{"value":"220283","label":"舒兰市"},{"value":"220284","label":"磐石市"}]},{"value":"220300","label":"四平市","children":[{"value":"220302","label":"铁西区"},{"value":"220303","label":"铁东区"},{"value":"220322","label":"梨树县"},{"value":"220323","label":"伊通满族自治县"},{"value":"220381","label":"公主岭市"},{"value":"220382","label":"双辽市"}]},{"value":"220400","label":"辽源市","children":[{"value":"220402","label":"龙山区"},{"value":"220403","label":"西安区"},{"value":"220421","label":"东丰县"},{"value":"220422","label":"东辽县"}]},{"value":"220500","label":"通化市","children":[{"value":"220502","label":"东昌区"},{"value":"220503","label":"二道江区"},{"value":"220521","label":"通化县"},{"value":"220523","label":"辉南县"},{"value":"220524","label":"柳河县"},{"value":"220581","label":"梅河口市"},{"value":"220582","label":"集安市"}]},{"value":"220600","label":"白山市","children":[{"value":"220602","label":"八道江区"},{"value":"220621","label":"抚松县"},{"value":"220622","label":"靖宇县"},{"value":"220623","label":"长白朝鲜族自治县"},{"value":"220625","label":"江源市"},{"value":"220681","label":"临江市"}]},{"value":"220700","label":"松原市","children":[{"value":"220702","label":"宁江区"},{"value":"220721","label":"前郭尔罗斯蒙古族自治县"},{"value":"220722","label":"长岭县"},{"value":"220723","label":"乾安县"},{"value":"220724","label":"扶余县"}]},{"value":"220800","label":"白城市","children":[{"value":"220802","label":"洮北区"},{"value":"220821","label":"镇赉县"},{"value":"220822","label":"通榆县"},{"value":"220881","label":"洮南市"},{"value":"220882","label":"大安市"}]},{"value":"222400","label":"延边朝鲜族自治州","children":[{"value":"222401","label":"延吉市"},{"value":"222402","label":"图们市"},{"value":"222403","label":"敦化市"},{"value":"222404","label":"珲春市"},{"value":"222405","label":"龙井市"},{"value":"222406","label":"和龙市"},{"value":"222424","label":"汪清县"},{"value":"222426","label":"安图县"}]}]},{"label":"黑龙江省","value":"230000","children":[{"value":"230100","label":"哈尔滨市","children":[{"value":"230102","label":"道里区"},{"value":"230103","label":"南岗区"},{"value":"230104","label":"道外区"},{"value":"230106","label":"香坊区"},{"value":"230107","label":"动力区"},{"value":"230108","label":"平房区"},{"value":"230109","label":"松北区"},{"value":"230111","label":"呼兰区"},{"value":"230123","label":"依兰县"},{"value":"230124","label":"方正县"},{"value":"230125","label":"宾县"},{"value":"230126","label":"巴彦县"},{"value":"230127","label":"木兰县"},{"value":"230128","label":"通河县"},{"value":"230129","label":"延寿县"},{"value":"230181","label":"阿城市"},{"value":"230182","label":"双城市"},{"value":"230183","label":"尚志市"},{"value":"230184","label":"五常市"},{"value":"230185","label":"阿城市"}]},{"value":"230200","label":"齐齐哈尔市","children":[{"value":"230202","label":"龙沙区"},{"value":"230203","label":"建华区"},{"value":"230204","label":"铁锋区"},{"value":"230205","label":"昂昂溪区"},{"value":"230206","label":"富拉尔基区"},{"value":"230207","label":"碾子山区"},{"value":"230208","label":"梅里斯达斡尔族区"},{"value":"230221","label":"龙江县"},{"value":"230223","label":"依安县"},{"value":"230224","label":"泰来县"},{"value":"230225","label":"甘南县"},{"value":"230227","label":"富裕县"},{"value":"230229","label":"克山县"},{"value":"230230","label":"克东县"},{"value":"230231","label":"拜泉县"},{"value":"230281","label":"讷河市"}]},{"value":"230300","label":"鸡西市","children":[{"value":"230302","label":"鸡冠区"},{"value":"230303","label":"恒山区"},{"value":"230304","label":"滴道区"},{"value":"230305","label":"梨树区"},{"value":"230306","label":"城子河区"},{"value":"230307","label":"麻山区"},{"value":"230321","label":"鸡东县"},{"value":"230381","label":"虎林市"},{"value":"230382","label":"密山市"}]},{"value":"230400","label":"鹤岗市","children":[{"value":"230402","label":"向阳区"},{"value":"230403","label":"工农区"},{"value":"230404","label":"南山区"},{"value":"230405","label":"兴安区"},{"value":"230406","label":"东山区"},{"value":"230407","label":"兴山区"},{"value":"230421","label":"萝北县"},{"value":"230422","label":"绥滨县"}]},{"value":"230500","label":"双鸭山市","children":[{"value":"230502","label":"尖山区"},{"value":"230503","label":"岭东区"},{"value":"230505","label":"四方台区"},{"value":"230506","label":"宝山区"},{"value":"230521","label":"集贤县"},{"value":"230522","label":"友谊县"},{"value":"230523","label":"宝清县"},{"value":"230524","label":"饶河县"}]},{"value":"230600","label":"大庆市","children":[{"value":"230602","label":"萨尔图区"},{"value":"230603","label":"龙凤区"},{"value":"230604","label":"让胡路区"},{"value":"230605","label":"红岗区"},{"value":"230606","label":"大同区"},{"value":"230621","label":"肇州县"},{"value":"230622","label":"肇源县"},{"value":"230623","label":"林甸县"},{"value":"230624","label":"杜尔伯特蒙古族自治县"}]},{"value":"230700","label":"伊春市","children":[{"value":"230702","label":"伊春区"},{"value":"230703","label":"南岔区"},{"value":"230704","label":"友好区"},{"value":"230705","label":"西林区"},{"value":"230706","label":"翠峦区"},{"value":"230707","label":"新青区"},{"value":"230708","label":"美溪区"},{"value":"230709","label":"金山屯区"},{"value":"230710","label":"五营区"},{"value":"230711","label":"乌马河区"},{"value":"230712","label":"汤旺河区"},{"value":"230713","label":"带岭区"},{"value":"230714","label":"乌伊岭区"},{"value":"230715","label":"红星区"},{"value":"230716","label":"上甘岭区"},{"value":"230722","label":"嘉荫县"},{"value":"230781","label":"铁力市"}]},{"value":"230800","label":"佳木斯市","children":[{"value":"230802","label":"永红区"},{"value":"230803","label":"向阳区"},{"value":"230804","label":"前进区"},{"value":"230805","label":"东风区"},{"value":"230811","label":"郊区"},{"value":"230822","label":"桦南县"},{"value":"230826","label":"桦川县"},{"value":"230828","label":"汤原县"},{"value":"230833","label":"抚远县"},{"value":"230881","label":"同江市"},{"value":"230882","label":"富锦市"}]},{"value":"230900","label":"七台河市","children":[{"value":"230902","label":"新兴区"},{"value":"230903","label":"桃山区"},{"value":"230904","label":"茄子河区"},{"value":"230921","label":"勃利县"}]},{"value":"231000","label":"牡丹江市","children":[{"value":"231002","label":"东安区"},{"value":"231003","label":"阳明区"},{"value":"231004","label":"爱民区"},{"value":"231005","label":"西安区"},{"value":"231024","label":"东宁县"},{"value":"231025","label":"林口县"},{"value":"231081","label":"绥芬河市"},{"value":"231083","label":"海林市"},{"value":"231084","label":"宁安市"},{"value":"231085","label":"穆棱市"}]},{"value":"231100","label":"黑河市","children":[{"value":"231102","label":"爱辉区"},{"value":"231121","label":"嫩江县"},{"value":"231123","label":"逊克县"},{"value":"231124","label":"孙吴县"},{"value":"231181","label":"北安市"},{"value":"231182","label":"五大连池市"}]},{"value":"231200","label":"绥化市","children":[{"value":"231202","label":"北林区"},{"value":"231221","label":"望奎县"},{"value":"231222","label":"兰西县"},{"value":"231223","label":"青冈县"},{"value":"231224","label":"庆安县"},{"value":"231225","label":"明水县"},{"value":"231226","label":"绥棱县"},{"value":"231281","label":"安达市"},{"value":"231282","label":"肇东市"},{"value":"231283","label":"海伦市"}]},{"value":"232700","label":"大兴安岭地区","children":[{"value":"232721","label":"呼玛县"},{"value":"232722","label":"塔河县"},{"value":"232723","label":"漠河县"},{"value":"232724","label":"加格达奇区"}]}]},{"label":"上海","value":"310000","children":[{"value":"310100","label":"上海市","children":[{"value":"310101","label":"黄浦区"},{"value":"310103","label":"卢湾区"},{"value":"310104","label":"徐汇区"},{"value":"310105","label":"长宁区"},{"value":"310106","label":"静安区"},{"value":"310107","label":"普陀区"},{"value":"310108","label":"闸北区"},{"value":"310109","label":"虹口区"},{"value":"310110","label":"杨浦区"},{"value":"310112","label":"闵行区"},{"value":"310113","label":"宝山区"},{"value":"310114","label":"嘉定区"},{"value":"310115","label":"浦东新区"},{"value":"310116","label":"金山区"},{"value":"310117","label":"松江区"},{"value":"310118","label":"青浦区"},{"value":"310119","label":"南汇区"},{"value":"310120","label":"奉贤区"},{"value":"310152","label":"川沙区"},{"value":"310230","label":"崇明县"}]}]},{"label":"江苏省","value":"320000","children":[{"value":"320100","label":"南京市","children":[{"value":"320102","label":"玄武区"},{"value":"320103","label":"白下区"},{"value":"320104","label":"秦淮区"},{"value":"320105","label":"建邺区"},{"value":"320106","label":"鼓楼区"},{"value":"320107","label":"下关区"},{"value":"320111","label":"浦口区"},{"value":"320113","label":"栖霞区"},{"value":"320114","label":"雨花台区"},{"value":"320115","label":"江宁区"},{"value":"320116","label":"六合区"},{"value":"320124","label":"溧水县"},{"value":"320125","label":"高淳县"}]},{"value":"320200","label":"无锡市","children":[{"value":"320202","label":"崇安区"},{"value":"320203","label":"南长区"},{"value":"320204","label":"北塘区"},{"value":"320205","label":"锡山区"},{"value":"320206","label":"惠山区"},{"value":"320211","label":"滨湖区"},{"value":"320281","label":"江阴市"},{"value":"320282","label":"宜兴市"},{"value":"320296","label":"新区"}]},{"value":"320300","label":"徐州市","children":[{"value":"320302","label":"鼓楼区"},{"value":"320303","label":"云龙区"},{"value":"320304","label":"九里区"},{"value":"320305","label":"贾汪区"},{"value":"320311","label":"泉山区"},{"value":"320321","label":"丰县"},{"value":"320322","label":"沛县"},{"value":"320323","label":"铜山县"},{"value":"320324","label":"睢宁县"},{"value":"320381","label":"新沂市"},{"value":"320382","label":"邳州市"}]},{"value":"320400","label":"常州市","children":[{"value":"320402","label":"天宁区"},{"value":"320404","label":"钟楼区"},{"value":"320405","label":"戚墅堰区"},{"value":"320411","label":"新北区"},{"value":"320412","label":"武进区"},{"value":"320481","label":"溧阳市"},{"value":"320482","label":"金坛市"}]},{"value":"320500","label":"苏州市","children":[{"value":"320502","label":"沧浪区"},{"value":"320503","label":"平江区"},{"value":"320504","label":"金阊区"},{"value":"320505","label":"虎丘区"},{"value":"320506","label":"吴中区"},{"value":"320507","label":"相城区"},{"value":"320581","label":"常熟市"},{"value":"320582","label":"张家港市"},{"value":"320583","label":"昆山市"},{"value":"320584","label":"吴江市"},{"value":"320585","label":"太仓市"},{"value":"320594","label":"新区"},{"value":"320595","label":"园区"}]},{"value":"320600","label":"南通市","children":[{"value":"320602","label":"崇川区"},{"value":"320611","label":"港闸区"},{"value":"320612","label":"通州区"},{"value":"320621","label":"海安县"},{"value":"320623","label":"如东县"},{"value":"320681","label":"启东市"},{"value":"320682","label":"如皋市"},{"value":"320683","label":"通州市"},{"value":"320684","label":"海门市"},{"value":"320693","label":"开发区"}]},{"value":"320700","label":"连云港市","children":[{"value":"320703","label":"连云区"},{"value":"320705","label":"新浦区"},{"value":"320706","label":"海州区"},{"value":"320721","label":"赣榆县"},{"value":"320722","label":"东海县"},{"value":"320723","label":"灌云县"},{"value":"320724","label":"灌南县"}]},{"value":"320800","label":"淮安市","children":[{"value":"320802","label":"清河区"},{"value":"320803","label":"楚州区"},{"value":"320804","label":"淮阴区"},{"value":"320811","label":"清浦区"},{"value":"320826","label":"涟水县"},{"value":"320829","label":"洪泽县"},{"value":"320830","label":"盱眙县"},{"value":"320831","label":"金湖县"}]},{"value":"320900","label":"盐城市","children":[{"value":"320902","label":"亭湖区"},{"value":"320903","label":"盐都区"},{"value":"320921","label":"响水县"},{"value":"320922","label":"滨海县"},{"value":"320923","label":"阜宁县"},{"value":"320924","label":"射阳县"},{"value":"320925","label":"建湖县"},{"value":"320981","label":"东台市"},{"value":"320982","label":"大丰市"}]},{"value":"321000","label":"扬州市","children":[{"value":"321002","label":"广陵区"},{"value":"321003","label":"邗江区"},{"value":"321011","label":"维扬区"},{"value":"321023","label":"宝应县"},{"value":"321081","label":"仪征市"},{"value":"321084","label":"高邮市"},{"value":"321088","label":"江都市"},{"value":"321092","label":"经济开发区"}]},{"value":"321100","label":"镇江市","children":[{"value":"321102","label":"京口区"},{"value":"321111","label":"润州区"},{"value":"321112","label":"丹徒区"},{"value":"321181","label":"丹阳市"},{"value":"321182","label":"扬中市"},{"value":"321183","label":"句容市"}]},{"value":"321200","label":"泰州市","children":[{"value":"321202","label":"海陵区"},{"value":"321203","label":"高港区"},{"value":"321281","label":"兴化市"},{"value":"321282","label":"靖江市"},{"value":"321283","label":"泰兴市"},{"value":"321284","label":"姜堰市"}]},{"value":"321300","label":"宿迁市","children":[{"value":"321302","label":"宿城区"},{"value":"321311","label":"宿豫区"},{"value":"321322","label":"沭阳县"},{"value":"321323","label":"泗阳县"},{"value":"321324","label":"泗洪县"}]}]},{"label":"浙江省","value":"330000","children":[{"value":"330100","label":"杭州市","children":[{"value":"330102","label":"上城区"},{"value":"330103","label":"下城区"},{"value":"330104","label":"江干区"},{"value":"330105","label":"拱墅区"},{"value":"330106","label":"西湖区"},{"value":"330108","label":"滨江区"},{"value":"330109","label":"萧山区"},{"value":"330110","label":"余杭区"},{"value":"330122","label":"桐庐县"},{"value":"330127","label":"淳安县"},{"value":"330182","label":"建德市"},{"value":"330183","label":"富阳市"},{"value":"330185","label":"临安市"}]},{"value":"330200","label":"宁波市","children":[{"value":"330203","label":"海曙区"},{"value":"330204","label":"江东区"},{"value":"330205","label":"江北区"},{"value":"330206","label":"北仑区"},{"value":"330211","label":"镇海区"},{"value":"330212","label":"鄞州区"},{"value":"330225","label":"象山县"},{"value":"330226","label":"宁海县"},{"value":"330281","label":"余姚市"},{"value":"330282","label":"慈溪市"},{"value":"330283","label":"奉化市"}]},{"value":"330300","label":"温州市","children":[{"value":"330302","label":"鹿城区"},{"value":"330303","label":"龙湾区"},{"value":"330304","label":"瓯海区"},{"value":"330322","label":"洞头县"},{"value":"330324","label":"永嘉县"},{"value":"330326","label":"平阳县"},{"value":"330327","label":"苍南县"},{"value":"330328","label":"文成县"},{"value":"330329","label":"泰顺县"},{"value":"330381","label":"瑞安市"},{"value":"330382","label":"乐清市"}]},{"value":"330400","label":"嘉兴市","children":[{"value":"330402","label":"南湖区"},{"value":"330411","label":"秀洲区"},{"value":"330421","label":"嘉善县"},{"value":"330424","label":"海盐县"},{"value":"330481","label":"海宁市"},{"value":"330482","label":"平湖市"},{"value":"330483","label":"桐乡市"}]},{"value":"330500","label":"湖州市","children":[{"value":"330502","label":"吴兴区"},{"value":"330503","label":"南浔区"},{"value":"330521","label":"德清县"},{"value":"330522","label":"长兴县"},{"value":"330523","label":"安吉县"}]},{"value":"330600","label":"绍兴市","children":[{"value":"330602","label":"越城区"},{"value":"330621","label":"绍兴县"},{"value":"330624","label":"新昌县"},{"value":"330681","label":"诸暨市"},{"value":"330682","label":"上虞市"},{"value":"330683","label":"嵊州市"}]},{"value":"330700","label":"金华市","children":[{"value":"330702","label":"婺城区"},{"value":"330703","label":"金东区"},{"value":"330723","label":"武义县"},{"value":"330726","label":"浦江县"},{"value":"330727","label":"磐安县"},{"value":"330781","label":"兰溪市"},{"value":"330782","label":"义乌市"},{"value":"330783","label":"东阳市"},{"value":"330784","label":"永康市"}]},{"value":"330800","label":"衢州市","children":[{"value":"330802","label":"柯城区"},{"value":"330803","label":"衢江区"},{"value":"330822","label":"常山县"},{"value":"330824","label":"开化县"},{"value":"330825","label":"龙游县"},{"value":"330881","label":"江山市"}]},{"value":"330900","label":"舟山市","children":[{"value":"330902","label":"定海区"},{"value":"330903","label":"普陀区"},{"value":"330921","label":"岱山县"},{"value":"330922","label":"嵊泗县"}]},{"value":"331000","label":"台州市","children":[{"value":"331002","label":"椒江区"},{"value":"331003","label":"黄岩区"},{"value":"331004","label":"路桥区"},{"value":"331021","label":"玉环县"},{"value":"331022","label":"三门县"},{"value":"331023","label":"天台县"},{"value":"331024","label":"仙居县"},{"value":"331081","label":"温岭市"},{"value":"331082","label":"临海市"}]},{"value":"331100","label":"丽水市","children":[{"value":"331102","label":"莲都区"},{"value":"331121","label":"青田县"},{"value":"331122","label":"缙云县"},{"value":"331123","label":"遂昌县"},{"value":"331124","label":"松阳县"},{"value":"331125","label":"云和县"},{"value":"331126","label":"庆元县"},{"value":"331127","label":"景宁畲族自治县"},{"value":"331181","label":"龙泉市"}]}]},{"label":"安徽省","value":"340000","children":[{"value":"340100","label":"合肥市","children":[{"value":"340102","label":"瑶海区"},{"value":"340103","label":"庐阳区"},{"value":"340104","label":"蜀山区"},{"value":"340111","label":"包河区"},{"value":"340121","label":"长丰县"},{"value":"340122","label":"肥东县"},{"value":"340123","label":"肥西县"},{"value":"340151","label":"高新区"},{"value":"340191","label":"中区"},{"value":"341400","label":"巢湖市"},{"value":"341402","label":"居巢区"},{"value":"341421","label":"庐江县"}]},{"value":"340200","label":"芜湖市","children":[{"value":"340202","label":"镜湖区"},{"value":"340203","label":"弋江区"},{"value":"340207","label":"鸠江区"},{"value":"340208","label":"三山区"},{"value":"340221","label":"芜湖县"},{"value":"340222","label":"繁昌县"},{"value":"340223","label":"南陵县"},{"value":"341422","label":"无为县"}]},{"value":"340300","label":"蚌埠市","children":[{"value":"340302","label":"龙子湖区"},{"value":"340303","label":"蚌山区"},{"value":"340304","label":"禹会区"},{"value":"340311","label":"淮上区"},{"value":"340321","label":"怀远县"},{"value":"340322","label":"五河县"},{"value":"340323","label":"固镇县"}]},{"value":"340400","label":"淮南市","children":[{"value":"340402","label":"大通区"},{"value":"340403","label":"田家庵区"},{"value":"340404","label":"谢家集区"},{"value":"340405","label":"八公山区"},{"value":"340406","label":"潘集区"},{"value":"340421","label":"凤台县"}]},{"value":"340500","label":"马鞍山市","children":[{"value":"340502","label":"金家庄区"},{"value":"340503","label":"花山区"},{"value":"340504","label":"雨山区"},{"value":"340521","label":"当涂县"},{"value":"341423","label":"含山县"},{"value":"341424","label":"和县"}]},{"value":"340600","label":"淮北市","children":[{"value":"340602","label":"杜集区"},{"value":"340603","label":"相山区"},{"value":"340604","label":"烈山区"},{"value":"340621","label":"濉溪县"}]},{"value":"340700","label":"铜陵市","children":[{"value":"340702","label":"铜官山区"},{"value":"340703","label":"狮子山区"},{"value":"340711","label":"郊区"},{"value":"340721","label":"铜陵县"}]},{"value":"340800","label":"安庆市","children":[{"value":"340802","label":"迎江区"},{"value":"340803","label":"大观区"},{"value":"340811","label":"宜秀区"},{"value":"340822","label":"怀宁县"},{"value":"340823","label":"枞阳县"},{"value":"340824","label":"潜山县"},{"value":"340825","label":"太湖县"},{"value":"340826","label":"宿松县"},{"value":"340827","label":"望江县"},{"value":"340828","label":"岳西县"},{"value":"340881","label":"桐城市"}]},{"value":"341000","label":"黄山市","children":[{"value":"341002","label":"屯溪区"},{"value":"341003","label":"黄山区"},{"value":"341004","label":"徽州区"},{"value":"341021","label":"歙县"},{"value":"341022","label":"休宁县"},{"value":"341023","label":"黟县"},{"value":"341024","label":"祁门县"}]},{"value":"341100","label":"滁州市","children":[{"value":"341102","label":"琅琊区"},{"value":"341103","label":"南谯区"},{"value":"341122","label":"来安县"},{"value":"341124","label":"全椒县"},{"value":"341125","label":"定远县"},{"value":"341126","label":"凤阳县"},{"value":"341181","label":"天长市"},{"value":"341182","label":"明光市"}]},{"value":"341200","label":"阜阳市","children":[{"value":"341202","label":"颍州区"},{"value":"341203","label":"颍东区"},{"value":"341204","label":"颍泉区"},{"value":"341221","label":"临泉县"},{"value":"341222","label":"太和县"},{"value":"341225","label":"阜南县"},{"value":"341226","label":"颍上县"},{"value":"341282","label":"界首市"}]},{"value":"341300","label":"宿州市","children":[{"value":"341302","label":"埇桥区"},{"value":"341321","label":"砀山县"},{"value":"341322","label":"萧县"},{"value":"341323","label":"灵璧县"},{"value":"341324","label":"泗县"}]},{"value":"341500","label":"六安市","children":[{"value":"341502","label":"金安区"},{"value":"341503","label":"裕安区"},{"value":"341521","label":"寿县"},{"value":"341522","label":"霍邱县"},{"value":"341523","label":"舒城县"},{"value":"341524","label":"金寨县"},{"value":"341525","label":"霍山县"}]},{"value":"341600","label":"亳州市","children":[{"value":"341602","label":"谯城区"},{"value":"341621","label":"涡阳县"},{"value":"341622","label":"蒙城县"},{"value":"341623","label":"利辛县"}]},{"value":"341700","label":"池州市","children":[{"value":"341702","label":"贵池区"},{"value":"341721","label":"东至县"},{"value":"341722","label":"石台县"},{"value":"341723","label":"青阳县"}]},{"value":"341800","label":"宣城市","children":[{"value":"341802","label":"宣州区"},{"value":"341821","label":"郎溪县"},{"value":"341822","label":"广德县"},{"value":"341823","label":"泾县"},{"value":"341824","label":"绩溪县"},{"value":"341825","label":"旌德县"},{"value":"341881","label":"宁国市"}]}]},{"label":"福建省","value":"350000","children":[{"value":"350100","label":"福州市","children":[{"value":"350102","label":"鼓楼区"},{"value":"350103","label":"台江区"},{"value":"350104","label":"仓山区"},{"value":"350105","label":"马尾区"},{"value":"350111","label":"晋安区"},{"value":"350121","label":"闽侯县"},{"value":"350122","label":"连江县"},{"value":"350123","label":"罗源县"},{"value":"350124","label":"闽清县"},{"value":"350125","label":"永泰县"},{"value":"350128","label":"平潭县"},{"value":"350181","label":"福清市"},{"value":"350182","label":"长乐市"}]},{"value":"350200","label":"厦门市","children":[{"value":"350203","label":"思明区"},{"value":"350205","label":"海沧区"},{"value":"350206","label":"湖里区"},{"value":"350211","label":"集美区"},{"value":"350212","label":"同安区"},{"value":"350213","label":"翔安区"}]},{"value":"350300","label":"莆田市","children":[{"value":"350302","label":"城厢区"},{"value":"350303","label":"涵江区"},{"value":"350304","label":"荔城区"},{"value":"350305","label":"秀屿区"},{"value":"350322","label":"仙游县"}]},{"value":"350400","label":"三明市","children":[{"value":"350402","label":"梅列区"},{"value":"350403","label":"三元区"},{"value":"350421","label":"明溪县"},{"value":"350423","label":"清流县"},{"value":"350424","label":"宁化县"},{"value":"350425","label":"大田县"},{"value":"350426","label":"尤溪县"},{"value":"350427","label":"沙县"},{"value":"350428","label":"将乐县"},{"value":"350429","label":"泰宁县"},{"value":"350430","label":"建宁县"},{"value":"350481","label":"永安市"}]},{"value":"350500","label":"泉州市","children":[{"value":"350502","label":"鲤城区"},{"value":"350503","label":"丰泽区"},{"value":"350504","label":"洛江区"},{"value":"350505","label":"泉港区"},{"value":"350521","label":"惠安县"},{"value":"350524","label":"安溪县"},{"value":"350525","label":"永春县"},{"value":"350526","label":"德化县"},{"value":"350527","label":"金门县"},{"value":"350581","label":"石狮市"},{"value":"350582","label":"晋江市"},{"value":"350583","label":"南安市"}]},{"value":"350600","label":"漳州市","children":[{"value":"350602","label":"芗城区"},{"value":"350603","label":"龙文区"},{"value":"350622","label":"云霄县"},{"value":"350623","label":"漳浦县"},{"value":"350624","label":"诏安县"},{"value":"350625","label":"长泰县"},{"value":"350626","label":"东山县"},{"value":"350627","label":"南靖县"},{"value":"350628","label":"平和县"},{"value":"350629","label":"华安县"},{"value":"350681","label":"龙海市"}]},{"value":"350700","label":"南平市","children":[{"value":"350702","label":"延平区"},{"value":"350721","label":"顺昌县"},{"value":"350722","label":"浦城县"},{"value":"350723","label":"光泽县"},{"value":"350724","label":"松溪县"},{"value":"350725","label":"政和县"},{"value":"350781","label":"邵武市"},{"value":"350782","label":"武夷山市"},{"value":"350783","label":"建瓯市"},{"value":"350784","label":"建阳市"}]},{"value":"350800","label":"龙岩市","children":[{"value":"350802","label":"新罗区"},{"value":"350821","label":"长汀县"},{"value":"350822","label":"永定县"},{"value":"350823","label":"上杭县"},{"value":"350824","label":"武平县"},{"value":"350825","label":"连城县"},{"value":"350881","label":"漳平市"}]},{"value":"350900","label":"宁德市","children":[{"value":"350902","label":"蕉城区"},{"value":"350921","label":"霞浦县"},{"value":"350922","label":"古田县"},{"value":"350923","label":"屏南县"},{"value":"350924","label":"寿宁县"},{"value":"350925","label":"周宁县"},{"value":"350926","label":"柘荣县"},{"value":"350981","label":"福安市"},{"value":"350982","label":"福鼎市"}]}]},{"label":"江西省","value":"360000","children":[{"value":"360100","label":"南昌市","children":[{"value":"360102","label":"东湖区"},{"value":"360103","label":"西湖区"},{"value":"360104","label":"青云谱区"},{"value":"360105","label":"湾里区"},{"value":"360111","label":"青山湖区"},{"value":"360121","label":"南昌县"},{"value":"360122","label":"新建县"},{"value":"360123","label":"安义县"},{"value":"360124","label":"进贤县"},{"value":"360125","label":"红谷滩新区"},{"value":"360126","label":"经济技术开发区"},{"value":"360127","label":"昌北区"}]},{"value":"360200","label":"景德镇市","children":[{"value":"360202","label":"昌江区"},{"value":"360203","label":"珠山区"},{"value":"360222","label":"浮梁县"},{"value":"360281","label":"乐平市"}]},{"value":"360300","label":"萍乡市","children":[{"value":"360302","label":"安源区"},{"value":"360313","label":"湘东区"},{"value":"360321","label":"莲花县"},{"value":"360322","label":"上栗县"},{"value":"360323","label":"芦溪县"}]},{"value":"360400","label":"九江市","children":[{"value":"360402","label":"庐山区"},{"value":"360403","label":"浔阳区"},{"value":"360421","label":"九江县"},{"value":"360423","label":"武宁县"},{"value":"360424","label":"修水县"},{"value":"360425","label":"永修县"},{"value":"360426","label":"德安县"},{"value":"360427","label":"星子县"},{"value":"360428","label":"都昌县"},{"value":"360429","label":"湖口县"},{"value":"360430","label":"彭泽县"},{"value":"360481","label":"瑞昌市"}]},{"value":"360500","label":"新余市","children":[{"value":"360502","label":"渝水区"},{"value":"360521","label":"分宜县"}]},{"value":"360600","label":"鹰潭市","children":[{"value":"360602","label":"月湖区"},{"value":"360622","label":"余江县"},{"value":"360681","label":"贵溪市"}]},{"value":"360700","label":"赣州市","children":[{"value":"360702","label":"章贡区"},{"value":"360721","label":"赣县"},{"value":"360722","label":"信丰县"},{"value":"360723","label":"大余县"},{"value":"360724","label":"上犹县"},{"value":"360725","label":"崇义县"},{"value":"360726","label":"安远县"},{"value":"360727","label":"龙南县"},{"value":"360728","label":"定南县"},{"value":"360729","label":"全南县"},{"value":"360730","label":"宁都县"},{"value":"360731","label":"于都县"},{"value":"360732","label":"兴国县"},{"value":"360733","label":"会昌县"},{"value":"360734","label":"寻乌县"},{"value":"360735","label":"石城县"},{"value":"360751","label":"黄金区"},{"value":"360781","label":"瑞金市"},{"value":"360782","label":"南康市"}]},{"value":"360800","label":"吉安市","children":[{"value":"360802","label":"吉州区"},{"value":"360803","label":"青原区"},{"value":"360821","label":"吉安县"},{"value":"360822","label":"吉水县"},{"value":"360823","label":"峡江县"},{"value":"360824","label":"新干县"},{"value":"360825","label":"永丰县"},{"value":"360826","label":"泰和县"},{"value":"360827","label":"遂川县"},{"value":"360828","label":"万安县"},{"value":"360829","label":"安福县"},{"value":"360830","label":"永新县"},{"value":"360881","label":"井冈山市"}]},{"value":"360900","label":"宜春市","children":[{"value":"360902","label":"袁州区"},{"value":"360921","label":"奉新县"},{"value":"360922","label":"万载县"},{"value":"360923","label":"上高县"},{"value":"360924","label":"宜丰县"},{"value":"360925","label":"靖安县"},{"value":"360926","label":"铜鼓县"},{"value":"360981","label":"丰城市"},{"value":"360982","label":"樟树市"},{"value":"360983","label":"高安市"}]},{"value":"361000","label":"抚州市","children":[{"value":"361002","label":"临川区"},{"value":"361021","label":"南城县"},{"value":"361022","label":"黎川县"},{"value":"361023","label":"南丰县"},{"value":"361024","label":"崇仁县"},{"value":"361025","label":"乐安县"},{"value":"361026","label":"宜黄县"},{"value":"361027","label":"金溪县"},{"value":"361028","label":"资溪县"},{"value":"361029","label":"东乡县"},{"value":"361030","label":"广昌县"}]},{"value":"361100","label":"上饶市","children":[{"value":"361102","label":"信州区"},{"value":"361121","label":"上饶县"},{"value":"361122","label":"广丰县"},{"value":"361123","label":"玉山县"},{"value":"361124","label":"铅山县"},{"value":"361125","label":"横峰县"},{"value":"361126","label":"弋阳县"},{"value":"361127","label":"余干县"},{"value":"361128","label":"鄱阳县"},{"value":"361129","label":"万年县"},{"value":"361130","label":"婺源县"},{"value":"361181","label":"德兴市"}]}]},{"label":"山东省","value":"370000","children":[{"value":"370100","label":"济南市","children":[{"value":"370102","label":"历下区"},{"value":"370103","label":"市中区"},{"value":"370104","label":"槐荫区"},{"value":"370105","label":"天桥区"},{"value":"370112","label":"历城区"},{"value":"370113","label":"长清区"},{"value":"370124","label":"平阴县"},{"value":"370125","label":"济阳县"},{"value":"370126","label":"商河县"},{"value":"370181","label":"章丘市"}]},{"value":"370200","label":"青岛市","children":[{"value":"370202","label":"市南区"},{"value":"370203","label":"市北区"},{"value":"370205","label":"四方区"},{"value":"370211","label":"黄岛区"},{"value":"370212","label":"崂山区"},{"value":"370213","label":"李沧区"},{"value":"370214","label":"城阳区"},{"value":"370251","label":"开发区"},{"value":"370281","label":"胶州市"},{"value":"370282","label":"即墨市"},{"value":"370283","label":"平度市"},{"value":"370284","label":"胶南市"},{"value":"370285","label":"莱西市"}]},{"value":"370300","label":"淄博市","children":[{"value":"370302","label":"淄川区"},{"value":"370303","label":"张店区"},{"value":"370304","label":"博山区"},{"value":"370305","label":"临淄区"},{"value":"370306","label":"周村区"},{"value":"370321","label":"桓台县"},{"value":"370322","label":"高青县"},{"value":"370323","label":"沂源县"}]},{"value":"370400","label":"枣庄市","children":[{"value":"370402","label":"市中区"},{"value":"370403","label":"薛城区"},{"value":"370404","label":"峄城区"},{"value":"370405","label":"台儿庄区"},{"value":"370406","label":"山亭区"},{"value":"370481","label":"滕州市"}]},{"value":"370500","label":"东营市","children":[{"value":"370502","label":"东营区"},{"value":"370503","label":"河口区"},{"value":"370521","label":"垦利县"},{"value":"370522","label":"利津县"},{"value":"370523","label":"广饶县"},{"value":"370589","label":"西城区"},{"value":"370590","label":"东城区"}]},{"value":"370600","label":"烟台市","children":[{"value":"370602","label":"芝罘区"},{"value":"370611","label":"福山区"},{"value":"370612","label":"牟平区"},{"value":"370613","label":"莱山区"},{"value":"370634","label":"长岛县"},{"value":"370681","label":"龙口市"},{"value":"370682","label":"莱阳市"},{"value":"370683","label":"莱州市"},{"value":"370684","label":"蓬莱市"},{"value":"370685","label":"招远市"},{"value":"370686","label":"栖霞市"},{"value":"370687","label":"海阳市"}]},{"value":"370700","label":"潍坊市","children":[{"value":"370702","label":"潍城区"},{"value":"370703","label":"寒亭区"},{"value":"370704","label":"坊子区"},{"value":"370705","label":"奎文区"},{"value":"370724","label":"临朐县"},{"value":"370725","label":"昌乐县"},{"value":"370751","label":"开发区"},{"value":"370781","label":"青州市"},{"value":"370782","label":"诸城市"},{"value":"370783","label":"寿光市"},{"value":"370784","label":"安丘市"},{"value":"370785","label":"高密市"},{"value":"370786","label":"昌邑市"}]},{"value":"370800","label":"济宁市","children":[{"value":"370802","label":"市中区"},{"value":"370811","label":"任城区"},{"value":"370826","label":"微山县"},{"value":"370827","label":"鱼台县"},{"value":"370828","label":"金乡县"},{"value":"370829","label":"嘉祥县"},{"value":"370830","label":"汶上县"},{"value":"370831","label":"泗水县"},{"value":"370832","label":"梁山县"},{"value":"370881","label":"曲阜市"},{"value":"370882","label":"兖州市"},{"value":"370883","label":"邹城市"}]},{"value":"370900","label":"泰安市","children":[{"value":"370902","label":"泰山区"},{"value":"370903","label":"岱岳区"},{"value":"370921","label":"宁阳县"},{"value":"370923","label":"东平县"},{"value":"370982","label":"新泰市"},{"value":"370983","label":"肥城市"}]},{"value":"371000","label":"威海市","children":[{"value":"371002","label":"环翠区"},{"value":"371081","label":"文登市"},{"value":"371082","label":"荣成市"},{"value":"371083","label":"乳山市"}]},{"value":"371100","label":"日照市","children":[{"value":"371102","label":"东港区"},{"value":"371103","label":"岚山区"},{"value":"371121","label":"五莲县"},{"value":"371122","label":"莒县"}]},{"value":"371200","label":"莱芜市","children":[{"value":"371202","label":"莱城区"},{"value":"371203","label":"钢城区"}]},{"value":"371300","label":"临沂市","children":[{"value":"371302","label":"兰山区"},{"value":"371311","label":"罗庄区"},{"value":"371312","label":"河东区"},{"value":"371321","label":"沂南县"},{"value":"371322","label":"郯城县"},{"value":"371323","label":"沂水县"},{"value":"371324","label":"苍山县"},{"value":"371325","label":"费县"},{"value":"371326","label":"平邑县"},{"value":"371327","label":"莒南县"},{"value":"371328","label":"蒙阴县"},{"value":"371329","label":"临沭县"}]},{"value":"371400","label":"德州市","children":[{"value":"371402","label":"德城区"},{"value":"371421","label":"陵县"},{"value":"371422","label":"宁津县"},{"value":"371423","label":"庆云县"},{"value":"371424","label":"临邑县"},{"value":"371425","label":"齐河县"},{"value":"371426","label":"平原县"},{"value":"371427","label":"夏津县"},{"value":"371428","label":"武城县"},{"value":"371451","label":"开发区"},{"value":"371481","label":"乐陵市"},{"value":"371482","label":"禹城市"}]},{"value":"371500","label":"聊城市","children":[{"value":"371502","label":"东昌府区"},{"value":"371521","label":"阳谷县"},{"value":"371522","label":"莘县"},{"value":"371523","label":"茌平县"},{"value":"371524","label":"东阿县"},{"value":"371525","label":"冠县"},{"value":"371526","label":"高唐县"},{"value":"371581","label":"临清市"}]},{"value":"371600","label":"滨州市","children":[{"value":"371602","label":"滨城区"},{"value":"371621","label":"惠民县"},{"value":"371622","label":"阳信县"},{"value":"371623","label":"无棣县"},{"value":"371624","label":"沾化县"},{"value":"371625","label":"博兴县"},{"value":"371626","label":"邹平县"}]},{"value":"371700","label":"菏泽市","children":[{"value":"371702","label":"牡丹区"},{"value":"371721","label":"曹县"},{"value":"371722","label":"单县"},{"value":"371723","label":"成武县"},{"value":"371724","label":"巨野县"},{"value":"371725","label":"郓城县"},{"value":"371726","label":"鄄城县"},{"value":"371727","label":"定陶县"},{"value":"371728","label":"东明县"}]}]},{"label":"河南省","value":"410000","children":[{"value":"410100","label":"郑州市","children":[{"value":"410102","label":"中原区"},{"value":"410103","label":"二七区"},{"value":"410104","label":"管城回族区"},{"value":"410105","label":"金水区"},{"value":"410106","label":"上街区"},{"value":"410108","label":"惠济区"},{"value":"410122","label":"中牟县"},{"value":"410181","label":"巩义市"},{"value":"410182","label":"荥阳市"},{"value":"410183","label":"新密市"},{"value":"410184","label":"新郑市"},{"value":"410185","label":"登封市"},{"value":"410186","label":"郑东新区"},{"value":"410187","label":"高新区"}]},{"value":"410200","label":"开封市","children":[{"value":"410202","label":"龙亭区"},{"value":"410203","label":"顺河回族区"},{"value":"410204","label":"鼓楼区"},{"value":"410205","label":"禹王台区"},{"value":"410211","label":"金明区"},{"value":"410221","label":"杞县"},{"value":"410222","label":"通许县"},{"value":"410223","label":"尉氏县"},{"value":"410224","label":"开封县"},{"value":"410225","label":"兰考县"}]},{"value":"410300","label":"洛阳市","children":[{"value":"410302","label":"老城区"},{"value":"410303","label":"西工区"},{"value":"410304","label":"廛河回族区"},{"value":"410305","label":"涧西区"},{"value":"410306","label":"吉利区"},{"value":"410307","label":"洛龙区"},{"value":"410322","label":"孟津县"},{"value":"410323","label":"新安县"},{"value":"410324","label":"栾川县"},{"value":"410325","label":"嵩县"},{"value":"410326","label":"汝阳县"},{"value":"410327","label":"宜阳县"},{"value":"410328","label":"洛宁县"},{"value":"410329","label":"伊川县"},{"value":"410381","label":"偃师市"},{"value":"471004","label":"高新区"}]},{"value":"410400","label":"平顶山市","children":[{"value":"410402","label":"新华区"},{"value":"410403","label":"卫东区"},{"value":"410404","label":"石龙区"},{"value":"410411","label":"湛河区"},{"value":"410421","label":"宝丰县"},{"value":"410422","label":"叶县"},{"value":"410423","label":"鲁山县"},{"value":"410425","label":"郏县"},{"value":"410481","label":"舞钢市"},{"value":"410482","label":"汝州市"}]},{"value":"410500","label":"安阳市","children":[{"value":"410502","label":"文峰区"},{"value":"410503","label":"北关区"},{"value":"410505","label":"殷都区"},{"value":"410506","label":"龙安区"},{"value":"410522","label":"安阳县"},{"value":"410523","label":"汤阴县"},{"value":"410526","label":"滑县"},{"value":"410527","label":"内黄县"},{"value":"410581","label":"林州市"}]},{"value":"410600","label":"鹤壁市","children":[{"value":"410602","label":"鹤山区"},{"value":"410603","label":"山城区"},{"value":"410611","label":"淇滨区"},{"value":"410621","label":"浚县"},{"value":"410622","label":"淇县"}]},{"value":"410700","label":"新乡市","children":[{"value":"410702","label":"红旗区"},{"value":"410703","label":"卫滨区"},{"value":"410704","label":"凤泉区"},{"value":"410711","label":"牧野区"},{"value":"410721","label":"新乡县"},{"value":"410724","label":"获嘉县"},{"value":"410725","label":"原阳县"},{"value":"410726","label":"延津县"},{"value":"410727","label":"封丘县"},{"value":"410728","label":"长垣县"},{"value":"410781","label":"卫辉市"},{"value":"410782","label":"辉县市"}]},{"value":"410800","label":"焦作市","children":[{"value":"410802","label":"解放区"},{"value":"410803","label":"中站区"},{"value":"410804","label":"马村区"},{"value":"410811","label":"山阳区"},{"value":"410821","label":"修武县"},{"value":"410822","label":"博爱县"},{"value":"410823","label":"武陟县"},{"value":"410825","label":"温县"},{"value":"410882","label":"沁阳市"},{"value":"410883","label":"孟州市"}]},{"value":"410881","label":"济源市"},{"value":"410900","label":"濮阳市","children":[{"value":"410902","label":"华龙区"},{"value":"410922","label":"清丰县"},{"value":"410923","label":"南乐县"},{"value":"410926","label":"范县"},{"value":"410927","label":"台前县"},{"value":"410928","label":"濮阳县"}]},{"value":"411000","label":"许昌市","children":[{"value":"411002","label":"魏都区"},{"value":"411023","label":"许昌县"},{"value":"411024","label":"鄢陵县"},{"value":"411025","label":"襄城县"},{"value":"411081","label":"禹州市"},{"value":"411082","label":"长葛市"}]},{"value":"411100","label":"漯河市","children":[{"value":"411102","label":"源汇区"},{"value":"411103","label":"郾城区"},{"value":"411104","label":"召陵区"},{"value":"411121","label":"舞阳县"},{"value":"411122","label":"临颍县"}]},{"value":"411200","label":"三门峡市","children":[{"value":"411202","label":"湖滨区"},{"value":"411221","label":"渑池县"},{"value":"411222","label":"陕县"},{"value":"411224","label":"卢氏县"},{"value":"411281","label":"义马市"},{"value":"411282","label":"灵宝市"}]},{"value":"411300","label":"南阳市","children":[{"value":"411302","label":"宛城区"},{"value":"411303","label":"卧龙区"},{"value":"411321","label":"南召县"},{"value":"411322","label":"方城县"},{"value":"411323","label":"西峡县"},{"value":"411324","label":"镇平县"},{"value":"411325","label":"内乡县"},{"value":"411326","label":"淅川县"},{"value":"411327","label":"社旗县"},{"value":"411328","label":"唐河县"},{"value":"411329","label":"新野县"},{"value":"411330","label":"桐柏县"},{"value":"411381","label":"邓州市"}]},{"value":"411400","label":"商丘市","children":[{"value":"411402","label":"梁园区"},{"value":"411403","label":"睢阳区"},{"value":"411421","label":"民权县"},{"value":"411422","label":"睢县"},{"value":"411423","label":"宁陵县"},{"value":"411424","label":"柘城县"},{"value":"411425","label":"虞城县"},{"value":"411426","label":"夏邑县"},{"value":"411481","label":"永城市"}]},{"value":"411500","label":"信阳市","children":[{"value":"411502","label":"浉河区"},{"value":"411503","label":"平桥区"},{"value":"411521","label":"罗山县"},{"value":"411522","label":"光山县"},{"value":"411523","label":"新县"},{"value":"411524","label":"商城县"},{"value":"411525","label":"固始县"},{"value":"411526","label":"潢川县"},{"value":"411527","label":"淮滨县"},{"value":"411528","label":"息县"}]},{"value":"411600","label":"周口市","children":[{"value":"411602","label":"川汇区"},{"value":"411621","label":"扶沟县"},{"value":"411622","label":"西华县"},{"value":"411623","label":"商水县"},{"value":"411624","label":"沈丘县"},{"value":"411625","label":"郸城县"},{"value":"411626","label":"淮阳县"},{"value":"411627","label":"太康县"},{"value":"411628","label":"鹿邑县"},{"value":"411681","label":"项城市"}]},{"value":"411700","label":"驻马店市","children":[{"value":"411702","label":"驿城区"},{"value":"411721","label":"西平县"},{"value":"411722","label":"上蔡县"},{"value":"411723","label":"平舆县"},{"value":"411724","label":"正阳县"},{"value":"411725","label":"确山县"},{"value":"411726","label":"泌阳县"},{"value":"411727","label":"汝南县"},{"value":"411728","label":"遂平县"},{"value":"411729","label":"新蔡县"}]}]},{"label":"湖北省","value":"420000","children":[{"value":"420100","label":"武汉市","children":[{"value":"420102","label":"江岸区"},{"value":"420103","label":"江汉区"},{"value":"420104","label":"硚口区"},{"value":"420105","label":"汉阳区"},{"value":"420106","label":"武昌区"},{"value":"420107","label":"青山区"},{"value":"420111","label":"洪山区"},{"value":"420112","label":"东西湖区"},{"value":"420113","label":"汉南区"},{"value":"420114","label":"蔡甸区"},{"value":"420115","label":"江夏区"},{"value":"420116","label":"黄陂区"},{"value":"420117","label":"新洲区"}]},{"value":"420200","label":"黄石市","children":[{"value":"420202","label":"黄石港区"},{"value":"420203","label":"西塞山区"},{"value":"420204","label":"下陆区"},{"value":"420205","label":"铁山区"},{"value":"420222","label":"阳新县"},{"value":"420281","label":"大冶市"}]},{"value":"420300","label":"十堰市","children":[{"value":"420302","label":"茅箭区"},{"value":"420303","label":"张湾区"},{"value":"420321","label":"郧县"},{"value":"420322","label":"郧西县"},{"value":"420323","label":"竹山县"},{"value":"420324","label":"竹溪县"},{"value":"420325","label":"房县"},{"value":"420381","label":"丹江口市"},{"value":"420382","label":"城区"}]},{"value":"420500","label":"宜昌市","children":[{"value":"420502","label":"西陵区"},{"value":"420503","label":"伍家岗区"},{"value":"420504","label":"点军区"},{"value":"420505","label":"猇亭区"},{"value":"420506","label":"夷陵区"},{"value":"420525","label":"远安县"},{"value":"420526","label":"兴山县"},{"value":"420527","label":"秭归县"},{"value":"420528","label":"长阳土家族自治县"},{"value":"420529","label":"五峰土家族自治县"},{"value":"420551","label":"葛洲坝区"},{"value":"420552","label":"开发区"},{"value":"420581","label":"宜都市"},{"value":"420582","label":"当阳市"},{"value":"420583","label":"枝江市"}]},{"value":"420600","label":"襄阳市","children":[{"value":"420602","label":"襄城区"},{"value":"420606","label":"樊城区"},{"value":"420607","label":"襄州区"},{"value":"420624","label":"南漳县"},{"value":"420625","label":"谷城县"},{"value":"420626","label":"保康县"},{"value":"420682","label":"老河口市"},{"value":"420683","label":"枣阳市"},{"value":"420684","label":"宜城市"}]},{"value":"420700","label":"鄂州市","children":[{"value":"420702","label":"梁子湖区"},{"value":"420703","label":"华容区"},{"value":"420704","label":"鄂城区"}]},{"value":"420800","label":"荆门市","children":[{"value":"420802","label":"东宝区"},{"value":"420804","label":"掇刀区"},{"value":"420821","label":"京山县"},{"value":"420822","label":"沙洋县"},{"value":"420881","label":"钟祥市"}]},{"value":"420900","label":"孝感市","children":[{"value":"420902","label":"孝南区"},{"value":"420921","label":"孝昌县"},{"value":"420922","label":"大悟县"},{"value":"420923","label":"云梦县"},{"value":"420981","label":"应城市"},{"value":"420982","label":"安陆市"},{"value":"420984","label":"汉川市"}]},{"value":"421000","label":"荆州市","children":[{"value":"421002","label":"沙市区"},{"value":"421003","label":"荆州区"},{"value":"421022","label":"公安县"},{"value":"421023","label":"监利县"},{"value":"421024","label":"江陵县"},{"value":"421081","label":"石首市"},{"value":"421083","label":"洪湖市"},{"value":"421087","label":"松滋市"}]},{"value":"421100","label":"黄冈市","children":[{"value":"421102","label":"黄州区"},{"value":"421121","label":"团风县"},{"value":"421122","label":"红安县"},{"value":"421123","label":"罗田县"},{"value":"421124","label":"英山县"},{"value":"421125","label":"浠水县"},{"value":"421126","label":"蕲春县"},{"value":"421127","label":"黄梅县"},{"value":"421181","label":"麻城市"},{"value":"421182","label":"武穴市"}]},{"value":"421200","label":"咸宁市","children":[{"value":"421202","label":"咸安区"},{"value":"421221","label":"嘉鱼县"},{"value":"421222","label":"通城县"},{"value":"421223","label":"崇阳县"},{"value":"421224","label":"通山县"},{"value":"421281","label":"赤壁市"},{"value":"421282","label":"温泉城区"}]},{"value":"421300","label":"随州市","children":[{"value":"421302","label":"曾都区"},{"value":"421321","label":"随县"},{"value":"421381","label":"广水市"}]},{"value":"422800","label":"恩施土家族苗族自治州","children":[{"value":"422801","label":"恩施市"},{"value":"422802","label":"利川市"},{"value":"422822","label":"建始县"},{"value":"422823","label":"巴东县"},{"value":"422825","label":"宣恩县"},{"value":"422826","label":"咸丰县"},{"value":"422827","label":"来凤县"},{"value":"422828","label":"鹤峰县"}]},{"value":"429004","label":"仙桃市"},{"value":"429005","label":"潜江市"},{"value":"429006","label":"天门市"},{"value":"429021","label":"神农架林区"}]},{"label":"湖南省","value":"430000","children":[{"value":"430100","label":"长沙市","children":[{"value":"430102","label":"芙蓉区"},{"value":"430103","label":"天心区"},{"value":"430104","label":"岳麓区"},{"value":"430105","label":"开福区"},{"value":"430111","label":"雨花区"},{"value":"430121","label":"长沙县"},{"value":"430122","label":"望城县"},{"value":"430124","label":"宁乡县"},{"value":"430181","label":"浏阳市"}]},{"value":"430200","label":"株洲市","children":[{"value":"430202","label":"荷塘区"},{"value":"430203","label":"芦淞区"},{"value":"430204","label":"石峰区"},{"value":"430211","label":"天元区"},{"value":"430221","label":"株洲县"},{"value":"430223","label":"攸县"},{"value":"430224","label":"茶陵县"},{"value":"430225","label":"炎陵县"},{"value":"430281","label":"醴陵市"}]},{"value":"430300","label":"湘潭市","children":[{"value":"430302","label":"雨湖区"},{"value":"430304","label":"岳塘区"},{"value":"430321","label":"湘潭县"},{"value":"430381","label":"湘乡市"},{"value":"430382","label":"韶山市"}]},{"value":"430400","label":"衡阳市","children":[{"value":"430405","label":"珠晖区"},{"value":"430406","label":"雁峰区"},{"value":"430407","label":"石鼓区"},{"value":"430408","label":"蒸湘区"},{"value":"430412","label":"南岳区"},{"value":"430421","label":"衡阳县"},{"value":"430422","label":"衡南县"},{"value":"430423","label":"衡山县"},{"value":"430424","label":"衡东县"},{"value":"430426","label":"祁东县"},{"value":"430481","label":"耒阳市"},{"value":"430482","label":"常宁市"}]},{"value":"430500","label":"邵阳市","children":[{"value":"430502","label":"双清区"},{"value":"430503","label":"大祥区"},{"value":"430511","label":"北塔区"},{"value":"430521","label":"邵东县"},{"value":"430522","label":"新邵县"},{"value":"430523","label":"邵阳县"},{"value":"430524","label":"隆回县"},{"value":"430525","label":"洞口县"},{"value":"430527","label":"绥宁县"},{"value":"430528","label":"新宁县"},{"value":"430529","label":"城步苗族自治县"},{"value":"430581","label":"武冈市"}]},{"value":"430600","label":"岳阳市","children":[{"value":"430602","label":"岳阳楼区"},{"value":"430603","label":"云溪区"},{"value":"430611","label":"君山区"},{"value":"430621","label":"岳阳县"},{"value":"430623","label":"华容县"},{"value":"430624","label":"湘阴县"},{"value":"430626","label":"平江县"},{"value":"430681","label":"汨罗市"},{"value":"430682","label":"临湘市"}]},{"value":"430700","label":"常德市","children":[{"value":"430702","label":"武陵区"},{"value":"430703","label":"鼎城区"},{"value":"430721","label":"安乡县"},{"value":"430722","label":"汉寿县"},{"value":"430723","label":"澧县"},{"value":"430724","label":"临澧县"},{"value":"430725","label":"桃源县"},{"value":"430726","label":"石门县"},{"value":"430781","label":"津市市"}]},{"value":"430800","label":"张家界市","children":[{"value":"430802","label":"永定区"},{"value":"430811","label":"武陵源区"},{"value":"430821","label":"慈利县"},{"value":"430822","label":"桑植县"}]},{"value":"430900","label":"益阳市","children":[{"value":"430902","label":"资阳区"},{"value":"430903","label":"赫山区"},{"value":"430921","label":"南县"},{"value":"430922","label":"桃江县"},{"value":"430923","label":"安化县"},{"value":"430981","label":"沅江市"}]},{"value":"431000","label":"郴州市","children":[{"value":"431002","label":"北湖区"},{"value":"431003","label":"苏仙区"},{"value":"431021","label":"桂阳县"},{"value":"431022","label":"宜章县"},{"value":"431023","label":"永兴县"},{"value":"431024","label":"嘉禾县"},{"value":"431025","label":"临武县"},{"value":"431026","label":"汝城县"},{"value":"431027","label":"桂东县"},{"value":"431028","label":"安仁县"},{"value":"431081","label":"资兴市"}]},{"value":"431100","label":"永州市","children":[{"value":"431102","label":"零陵区"},{"value":"431103","label":"冷水滩区"},{"value":"431121","label":"祁阳县"},{"value":"431122","label":"东安县"},{"value":"431123","label":"双牌县"},{"value":"431124","label":"道县"},{"value":"431125","label":"江永县"},{"value":"431126","label":"宁远县"},{"value":"431127","label":"蓝山县"},{"value":"431128","label":"新田县"},{"value":"431129","label":"江华瑶族自治县"}]},{"value":"431200","label":"怀化市","children":[{"value":"431202","label":"鹤城区"},{"value":"431221","label":"中方县"},{"value":"431222","label":"沅陵县"},{"value":"431223","label":"辰溪县"},{"value":"431224","label":"溆浦县"},{"value":"431225","label":"会同县"},{"value":"431226","label":"麻阳苗族自治县"},{"value":"431227","label":"新晃侗族自治县"},{"value":"431228","label":"芷江侗族自治县"},{"value":"431229","label":"靖州苗族侗族自治县"},{"value":"431230","label":"通道侗族自治县"},{"value":"431281","label":"洪江市"}]},{"value":"431300","label":"娄底市","children":[{"value":"431302","label":"娄星区"},{"value":"431321","label":"双峰县"},{"value":"431322","label":"新化县"},{"value":"431381","label":"冷水江市"},{"value":"431382","label":"涟源市"}]},{"value":"433100","label":"湘西土家族苗族自治州","children":[{"value":"433101","label":"吉首市"},{"value":"433122","label":"泸溪县"},{"value":"433123","label":"凤凰县"},{"value":"433124","label":"花垣县"},{"value":"433125","label":"保靖县"},{"value":"433126","label":"古丈县"},{"value":"433127","label":"永顺县"},{"value":"433130","label":"龙山县"}]}]},{"label":"广东省","value":"440000","children":[{"value":"440100","label":"广州市","children":[{"value":"440103","label":"荔湾区"},{"value":"440104","label":"越秀区"},{"value":"440105","label":"海珠区"},{"value":"440106","label":"天河区"},{"value":"440111","label":"白云区"},{"value":"440112","label":"黄埔区"},{"value":"440113","label":"番禺区"},{"value":"440114","label":"花都区"},{"value":"440115","label":"南沙区"},{"value":"440116","label":"萝岗区"},{"value":"440183","label":"增城市"},{"value":"440184","label":"从化市"},{"value":"440188","label":"东山区"}]},{"value":"440200","label":"韶关市","children":[{"value":"440203","label":"武江区"},{"value":"440204","label":"浈江区"},{"value":"440205","label":"曲江区"},{"value":"440222","label":"始兴县"},{"value":"440224","label":"仁化县"},{"value":"440229","label":"翁源县"},{"value":"440232","label":"乳源瑶族自治县"},{"value":"440233","label":"新丰县"},{"value":"440281","label":"乐昌市"},{"value":"440282","label":"南雄市"}]},{"value":"440300","label":"深圳市","children":[{"value":"440303","label":"罗湖区"},{"value":"440304","label":"福田区"},{"value":"440305","label":"南山区"},{"value":"440306","label":"宝安区"},{"value":"440307","label":"龙岗区"},{"value":"440308","label":"盐田区"},{"value":"1032697","label":"光明新区"},{"value":"1032698","label":"坪山新区"},{"value":"1032699","label":"大鹏新区"},{"value":"1032700","label":"龙华新区"}]},{"value":"440400","label":"珠海市","children":[{"value":"440402","label":"香洲区"},{"value":"440403","label":"斗门区"},{"value":"440404","label":"金湾区"},{"value":"440486","label":"金唐区"},{"value":"440487","label":"南湾区"}]},{"value":"440500","label":"汕头市","children":[{"value":"440507","label":"龙湖区"},{"value":"440511","label":"金平区"},{"value":"440512","label":"濠江区"},{"value":"440513","label":"潮阳区"},{"value":"440514","label":"潮南区"},{"value":"440515","label":"澄海区"},{"value":"440523","label":"南澳县"}]},{"value":"440600","label":"佛山市","children":[{"value":"440604","label":"禅城区"},{"value":"440605","label":"南海区"},{"value":"440606","label":"顺德区"},{"value":"440607","label":"三水区"},{"value":"440608","label":"高明区"}]},{"value":"440700","label":"江门市","children":[{"value":"440703","label":"蓬江区"},{"value":"440704","label":"江海区"},{"value":"440705","label":"新会区"},{"value":"440781","label":"台山市"},{"value":"440783","label":"开平市"},{"value":"440784","label":"鹤山市"},{"value":"440785","label":"恩平市"}]},{"value":"440800","label":"湛江市","children":[{"value":"440802","label":"赤坎区"},{"value":"440803","label":"霞山区"},{"value":"440804","label":"坡头区"},{"value":"440811","label":"麻章区"},{"value":"440823","label":"遂溪县"},{"value":"440825","label":"徐闻县"},{"value":"440881","label":"廉江市"},{"value":"440882","label":"雷州市"},{"value":"440883","label":"吴川市"}]},{"value":"440900","label":"茂名市","children":[{"value":"440902","label":"茂南区"},{"value":"440903","label":"茂港区"},{"value":"440923","label":"电白县"},{"value":"440981","label":"高州市"},{"value":"440982","label":"化州市"},{"value":"440983","label":"信宜市"}]},{"value":"441200","label":"肇庆市","children":[{"value":"441202","label":"端州区"},{"value":"441203","label":"鼎湖区"},{"value":"441223","label":"广宁县"},{"value":"441224","label":"怀集县"},{"value":"441225","label":"封开县"},{"value":"441226","label":"德庆县"},{"value":"441283","label":"高要市"},{"value":"441284","label":"四会市"}]},{"value":"441300","label":"惠州市","children":[{"value":"441302","label":"惠城区"},{"value":"441303","label":"惠阳区"},{"value":"441322","label":"博罗县"},{"value":"441323","label":"惠东县"},{"value":"441324","label":"龙门县"}]},{"value":"441400","label":"梅州市","children":[{"value":"441402","label":"梅江区"},{"value":"441421","label":"梅县"},{"value":"441422","label":"大埔县"},{"value":"441423","label":"丰顺县"},{"value":"441424","label":"五华县"},{"value":"441426","label":"平远县"},{"value":"441427","label":"蕉岭县"},{"value":"441481","label":"兴宁市"}]},{"value":"441500","label":"汕尾市","children":[{"value":"441502","label":"城区"},{"value":"441521","label":"海丰县"},{"value":"441523","label":"陆河县"},{"value":"441581","label":"陆丰市"}]},{"value":"441600","label":"河源市","children":[{"value":"441602","label":"源城区"},{"value":"441621","label":"紫金县"},{"value":"441622","label":"龙川县"},{"value":"441623","label":"连平县"},{"value":"441624","label":"和平县"},{"value":"441625","label":"东源县"}]},{"value":"441700","label":"阳江市","children":[{"value":"441702","label":"江城区"},{"value":"441721","label":"阳西县"},{"value":"441723","label":"阳东县"},{"value":"441781","label":"阳春市"}]},{"value":"441800","label":"清远市","children":[{"value":"441802","label":"清城区"},{"value":"441821","label":"佛冈县"},{"value":"441823","label":"阳山县"},{"value":"441825","label":"连山壮族瑶族自治县"},{"value":"441826","label":"连南瑶族自治县"},{"value":"441827","label":"清新县"},{"value":"441881","label":"英德市"},{"value":"441882","label":"连州市"}]},{"value":"441900","label":"东莞市"},{"value":"442000","label":"中山市"},{"value":"445100","label":"潮州市","children":[{"value":"445102","label":"湘桥区"},{"value":"445121","label":"潮安县"},{"value":"445122","label":"饶平县"},{"value":"445185","label":"枫溪区"}]},{"value":"445200","label":"揭阳市","children":[{"value":"445202","label":"榕城区"},{"value":"445221","label":"揭东县"},{"value":"445222","label":"揭西县"},{"value":"445224","label":"惠来县"},{"value":"445281","label":"普宁市"},{"value":"445284","label":"东山区"}]},{"value":"445300","label":"云浮市","children":[{"value":"445302","label":"云城区"},{"value":"445321","label":"新兴县"},{"value":"445322","label":"郁南县"},{"value":"445323","label":"云安县"},{"value":"445381","label":"罗定市"}]}]},{"label":"广西壮族自治区","value":"450000","children":[{"value":"450100","label":"南宁市","children":[{"value":"450102","label":"兴宁区"},{"value":"450103","label":"青秀区"},{"value":"450105","label":"江南区"},{"value":"450107","label":"西乡塘区"},{"value":"450108","label":"良庆区"},{"value":"450109","label":"邕宁区"},{"value":"450122","label":"武鸣县"},{"value":"450123","label":"隆安县"},{"value":"450124","label":"马山县"},{"value":"450125","label":"上林县"},{"value":"450126","label":"宾阳县"},{"value":"450127","label":"横县"}]},{"value":"450200","label":"柳州市","children":[{"value":"450202","label":"城中区"},{"value":"450203","label":"鱼峰区"},{"value":"450204","label":"柳南区"},{"value":"450205","label":"柳北区"},{"value":"450221","label":"柳江县"},{"value":"450222","label":"柳城县"},{"value":"450223","label":"鹿寨县"},{"value":"450224","label":"融安县"},{"value":"450225","label":"融水苗族自治县"},{"value":"450226","label":"三江侗族自治县"}]},{"value":"450300","label":"桂林市","children":[{"value":"450302","label":"秀峰区"},{"value":"450303","label":"叠彩区"},{"value":"450304","label":"象山区"},{"value":"450305","label":"七星区"},{"value":"450311","label":"雁山区"},{"value":"450321","label":"阳朔县"},{"value":"450322","label":"临桂县"},{"value":"450323","label":"灵川县"},{"value":"450324","label":"全州县"},{"value":"450325","label":"兴安县"},{"value":"450326","label":"永福县"},{"value":"450327","label":"灌阳县"},{"value":"450328","label":"龙胜各族自治县"},{"value":"450329","label":"资源县"},{"value":"450330","label":"平乐县"},{"value":"450331","label":"荔浦县"},{"value":"450332","label":"恭城瑶族自治县"}]},{"value":"450400","label":"梧州市","children":[{"value":"450403","label":"万秀区"},{"value":"450404","label":"蝶山区"},{"value":"450405","label":"长洲区"},{"value":"450421","label":"苍梧县"},{"value":"450422","label":"藤县"},{"value":"450423","label":"蒙山县"},{"value":"450481","label":"岑溪市"}]},{"value":"450500","label":"北海市","children":[{"value":"450502","label":"海城区"},{"value":"450503","label":"银海区"},{"value":"450512","label":"铁山港区"},{"value":"450521","label":"合浦县"}]},{"value":"450600","label":"防城港市","children":[{"value":"450602","label":"港口区"},{"value":"450603","label":"防城区"},{"value":"450621","label":"上思县"},{"value":"450681","label":"东兴市"}]},{"value":"450700","label":"钦州市","children":[{"value":"450702","label":"钦南区"},{"value":"450703","label":"钦北区"},{"value":"450721","label":"灵山县"},{"value":"450722","label":"浦北县"}]},{"value":"450800","label":"贵港市","children":[{"value":"450802","label":"港北区"},{"value":"450803","label":"港南区"},{"value":"450804","label":"覃塘区"},{"value":"450821","label":"平南县"},{"value":"450881","label":"桂平市"}]},{"value":"450900","label":"玉林市","children":[{"value":"450902","label":"玉州区"},{"value":"450921","label":"容县"},{"value":"450922","label":"陆川县"},{"value":"450923","label":"博白县"},{"value":"450924","label":"兴业县"},{"value":"450981","label":"北流市"}]},{"value":"451000","label":"百色市","children":[{"value":"451002","label":"右江区"},{"value":"451021","label":"田阳县"},{"value":"451022","label":"田东县"},{"value":"451023","label":"平果县"},{"value":"451024","label":"德保县"},{"value":"451025","label":"靖西县"},{"value":"451026","label":"那坡县"},{"value":"451027","label":"凌云县"},{"value":"451028","label":"乐业县"},{"value":"451029","label":"田林县"},{"value":"451030","label":"西林县"},{"value":"451031","label":"隆林各族自治县"}]},{"value":"451100","label":"贺州市","children":[{"value":"451102","label":"八步区"},{"value":"451121","label":"昭平县"},{"value":"451122","label":"钟山县"},{"value":"451123","label":"富川瑶族自治县"}]},{"value":"451200","label":"河池市","children":[{"value":"451202","label":"金城江区"},{"value":"451221","label":"南丹县"},{"value":"451222","label":"天峨县"},{"value":"451223","label":"凤山县"},{"value":"451224","label":"东兰县"},{"value":"451225","label":"罗城仫佬族自治县"},{"value":"451226","label":"环江毛南族自治县"},{"value":"451227","label":"巴马瑶族自治县"},{"value":"451228","label":"都安瑶族自治县"},{"value":"451229","label":"大化瑶族自治县"},{"value":"451281","label":"宜州市"}]},{"value":"451300","label":"来宾市","children":[{"value":"451302","label":"兴宾区"},{"value":"451321","label":"忻城县"},{"value":"451322","label":"象州县"},{"value":"451323","label":"武宣县"},{"value":"451324","label":"金秀瑶族自治县"},{"value":"451381","label":"合山市"}]},{"value":"451400","label":"崇左市","children":[{"value":"451402","label":"江洲区"},{"value":"451421","label":"扶绥县"},{"value":"451422","label":"宁明县"},{"value":"451423","label":"龙州县"},{"value":"451424","label":"大新县"},{"value":"451425","label":"天等县"},{"value":"451481","label":"凭祥市"}]}]},{"label":"海南省","value":"460000","children":[{"value":"460100","label":"海口市","children":[{"value":"460105","label":"秀英区"},{"value":"460106","label":"龙华区"},{"value":"460107","label":"琼山区"},{"value":"460108","label":"美兰区"}]},{"value":"460200","label":"三亚市"},{"value":"469001","label":"五指山市"},{"value":"469002","label":"琼海市"},{"value":"469003","label":"儋州市"},{"value":"469005","label":"文昌市"},{"value":"469006","label":"万宁市"},{"value":"469007","label":"东方市"},{"value":"469025","label":"定安县"},{"value":"469026","label":"屯昌县"},{"value":"469027","label":"澄迈县"},{"value":"469028","label":"临高县"},{"value":"469030","label":"白沙黎族自治县"},{"value":"469031","label":"昌江黎族自治县"},{"value":"469033","label":"乐东黎族自治县"},{"value":"469034","label":"陵水黎族自治县"},{"value":"469035","label":"保亭黎族苗族自治县"},{"value":"469036","label":"琼中黎族苗族自治县"},{"value":"469037","label":"西沙群岛"},{"value":"469038","label":"南沙群岛"},{"value":"469039","label":"中沙群岛的岛礁及其海域"}]},{"label":"重庆","value":"500000","children":[{"value":"500100","label":"重庆市","children":[{"value":"500101","label":"万州区"},{"value":"500102","label":"涪陵区"},{"value":"500103","label":"渝中区"},{"value":"500104","label":"大渡口区"},{"value":"500105","label":"江北区"},{"value":"500106","label":"沙坪坝区"},{"value":"500107","label":"九龙坡区"},{"value":"500108","label":"南岸区"},{"value":"500109","label":"北碚区"},{"value":"500110","label":"万盛区"},{"value":"500111","label":"双桥区"},{"value":"500112","label":"渝北区"},{"value":"500113","label":"巴南区"},{"value":"500114","label":"黔江区"},{"value":"500115","label":"长寿区"},{"value":"500222","label":"綦江县"},{"value":"500223","label":"潼南县"},{"value":"500224","label":"铜梁县"},{"value":"500225","label":"大足县"},{"value":"500226","label":"荣昌县"},{"value":"500227","label":"璧山县"},{"value":"500228","label":"梁平县"},{"value":"500229","label":"城口县"},{"value":"500230","label":"丰都县"},{"value":"500231","label":"垫江县"},{"value":"500232","label":"武隆县"},{"value":"500233","label":"忠县"},{"value":"500234","label":"开县"},{"value":"500235","label":"云阳县"},{"value":"500236","label":"奉节县"},{"value":"500237","label":"巫山县"},{"value":"500238","label":"巫溪县"},{"value":"500240","label":"石柱土家族自治县"},{"value":"500241","label":"秀山土家族苗族自治县"},{"value":"500242","label":"酉阳土家族苗族自治县"},{"value":"500243","label":"彭水苗族土家族自治县"},{"value":"500381","label":"江津区"},{"value":"500382","label":"合川区"},{"value":"500383","label":"永川区"},{"value":"500384","label":"南川区"}]}]},{"label":"四川省","value":"510000","children":[{"value":"510100","label":"成都市","children":[{"value":"510104","label":"锦江区"},{"value":"510105","label":"青羊区"},{"value":"510106","label":"金牛区"},{"value":"510107","label":"武侯区"},{"value":"510108","label":"成华区"},{"value":"510112","label":"龙泉驿区"},{"value":"510113","label":"青白江区"},{"value":"510114","label":"新都区"},{"value":"510115","label":"温江区"},{"value":"510121","label":"金堂县"},{"value":"510122","label":"双流县"},{"value":"510124","label":"郫县"},{"value":"510129","label":"大邑县"},{"value":"510131","label":"蒲江县"},{"value":"510132","label":"新津县"},{"value":"510181","label":"都江堰市"},{"value":"510182","label":"彭州市"},{"value":"510183","label":"邛崃市"},{"value":"510184","label":"崇州市"}]},{"value":"510300","label":"自贡市","children":[{"value":"510302","label":"自流井区"},{"value":"510303","label":"贡井区"},{"value":"510304","label":"大安区"},{"value":"510311","label":"沿滩区"},{"value":"510321","label":"荣县"},{"value":"510322","label":"富顺县"}]},{"value":"510400","label":"攀枝花市","children":[{"value":"510402","label":"东区"},{"value":"510403","label":"西区"},{"value":"510411","label":"仁和区"},{"value":"510421","label":"米易县"},{"value":"510422","label":"盐边县"}]},{"value":"510500","label":"泸州市","children":[{"value":"510502","label":"江阳区"},{"value":"510503","label":"纳溪区"},{"value":"510504","label":"龙马潭区"},{"value":"510521","label":"泸县"},{"value":"510522","label":"合江县"},{"value":"510524","label":"叙永县"},{"value":"510525","label":"古蔺县"}]},{"value":"510600","label":"德阳市","children":[{"value":"510603","label":"旌阳区"},{"value":"510623","label":"中江县"},{"value":"510626","label":"罗江县"},{"value":"510681","label":"广汉市"},{"value":"510682","label":"什邡市"},{"value":"510683","label":"绵竹市"}]},{"value":"510700","label":"绵阳市","children":[{"value":"510703","label":"涪城区"},{"value":"510704","label":"游仙区"},{"value":"510722","label":"三台县"},{"value":"510723","label":"盐亭县"},{"value":"510724","label":"安县"},{"value":"510725","label":"梓潼县"},{"value":"510726","label":"北川羌族自治县"},{"value":"510727","label":"平武县"},{"value":"510751","label":"高新区"},{"value":"510781","label":"江油市"}]},{"value":"510800","label":"广元市","children":[{"value":"510802","label":"利州区"},{"value":"510811","label":"元坝区"},{"value":"510812","label":"朝天区"},{"value":"510821","label":"旺苍县"},{"value":"510822","label":"青川县"},{"value":"510823","label":"剑阁县"},{"value":"510824","label":"苍溪县"}]},{"value":"510900","label":"遂宁市","children":[{"value":"510903","label":"船山区"},{"value":"510904","label":"安居区"},{"value":"510921","label":"蓬溪县"},{"value":"510922","label":"射洪县"},{"value":"510923","label":"大英县"}]},{"value":"511000","label":"内江市","children":[{"value":"511002","label":"市中区"},{"value":"511011","label":"东兴区"},{"value":"511024","label":"威远县"},{"value":"511025","label":"资中县"},{"value":"511028","label":"隆昌县"}]},{"value":"511100","label":"乐山市","children":[{"value":"511102","label":"市中区"},{"value":"511111","label":"沙湾区"},{"value":"511112","label":"五通桥区"},{"value":"511113","label":"金口河区"},{"value":"511123","label":"犍为县"},{"value":"511124","label":"井研县"},{"value":"511126","label":"夹江县"},{"value":"511129","label":"沐川县"},{"value":"511132","label":"峨边彝族自治县"},{"value":"511133","label":"马边彝族自治县"},{"value":"511181","label":"峨眉山市"}]},{"value":"511300","label":"南充市","children":[{"value":"511302","label":"顺庆区"},{"value":"511303","label":"高坪区"},{"value":"511304","label":"嘉陵区"},{"value":"511321","label":"南部县"},{"value":"511322","label":"营山县"},{"value":"511323","label":"蓬安县"},{"value":"511324","label":"仪陇县"},{"value":"511325","label":"西充县"},{"value":"511381","label":"阆中市"}]},{"value":"511400","label":"眉山市","children":[{"value":"511402","label":"东坡区"},{"value":"511421","label":"仁寿县"},{"value":"511422","label":"彭山县"},{"value":"511423","label":"洪雅县"},{"value":"511424","label":"丹棱县"},{"value":"511425","label":"青神县"}]},{"value":"511500","label":"宜宾市","children":[{"value":"511502","label":"翠屏区"},{"value":"511521","label":"宜宾县"},{"value":"511522","label":"南溪县"},{"value":"511523","label":"江安县"},{"value":"511524","label":"长宁县"},{"value":"511525","label":"高县"},{"value":"511526","label":"珙县"},{"value":"511527","label":"筠连县"},{"value":"511528","label":"兴文县"},{"value":"511529","label":"屏山县"}]},{"value":"511600","label":"广安市","children":[{"value":"511602","label":"广安区"},{"value":"511621","label":"岳池县"},{"value":"511622","label":"武胜县"},{"value":"511623","label":"邻水县"},{"value":"511681","label":"华蓥市"},{"value":"511682","label":"市辖区"}]},{"value":"511700","label":"达州市","children":[{"value":"511702","label":"通川区"},{"value":"511721","label":"达县"},{"value":"511722","label":"宣汉县"},{"value":"511723","label":"开江县"},{"value":"511724","label":"大竹县"},{"value":"511725","label":"渠县"},{"value":"511781","label":"万源市"}]},{"value":"511800","label":"雅安市","children":[{"value":"511802","label":"雨城区"},{"value":"511821","label":"名山县"},{"value":"511822","label":"荥经县"},{"value":"511823","label":"汉源县"},{"value":"511824","label":"石棉县"},{"value":"511825","label":"天全县"},{"value":"511826","label":"芦山县"},{"value":"511827","label":"宝兴县"}]},{"value":"511900","label":"巴中市","children":[{"value":"511902","label":"巴州区"},{"value":"511921","label":"通江县"},{"value":"511922","label":"南江县"},{"value":"511923","label":"平昌县"}]},{"value":"512000","label":"资阳市","children":[{"value":"512002","label":"雁江区"},{"value":"512021","label":"安岳县"},{"value":"512022","label":"乐至县"},{"value":"512081","label":"简阳市"}]},{"value":"513200","label":"阿坝藏族羌族自治州","children":[{"value":"513221","label":"汶川县"},{"value":"513222","label":"理县"},{"value":"513223","label":"茂县"},{"value":"513224","label":"松潘县"},{"value":"513225","label":"九寨沟县"},{"value":"513226","label":"金川县"},{"value":"513227","label":"小金县"},{"value":"513228","label":"黑水县"},{"value":"513229","label":"马尔康县"},{"value":"513230","label":"壤塘县"},{"value":"513231","label":"阿坝县"},{"value":"513232","label":"若尔盖县"},{"value":"513233","label":"红原县"}]},{"value":"513300","label":"甘孜藏族自治州","children":[{"value":"513321","label":"康定县"},{"value":"513322","label":"泸定县"},{"value":"513323","label":"丹巴县"},{"value":"513324","label":"九龙县"},{"value":"513325","label":"雅江县"},{"value":"513326","label":"道孚县"},{"value":"513327","label":"炉霍县"},{"value":"513328","label":"甘孜县"},{"value":"513329","label":"新龙县"},{"value":"513330","label":"德格县"},{"value":"513331","label":"白玉县"},{"value":"513332","label":"石渠县"},{"value":"513333","label":"色达县"},{"value":"513334","label":"理塘县"},{"value":"513335","label":"巴塘县"},{"value":"513336","label":"乡城县"},{"value":"513337","label":"稻城县"},{"value":"513338","label":"得荣县"}]},{"value":"513400","label":"凉山彝族自治州","children":[{"value":"513401","label":"西昌市"},{"value":"513422","label":"木里藏族自治县"},{"value":"513423","label":"盐源县"},{"value":"513424","label":"德昌县"},{"value":"513425","label":"会理县"},{"value":"513426","label":"会东县"},{"value":"513427","label":"宁南县"},{"value":"513428","label":"普格县"},{"value":"513429","label":"布拖县"},{"value":"513430","label":"金阳县"},{"value":"513431","label":"昭觉县"},{"value":"513432","label":"喜德县"},{"value":"513433","label":"冕宁县"},{"value":"513434","label":"越西县"},{"value":"513435","label":"甘洛县"},{"value":"513436","label":"美姑县"},{"value":"513437","label":"雷波县"}]}]},{"label":"贵州省","value":"520000","children":[{"value":"520100","label":"贵阳市","children":[{"value":"520102","label":"南明区"},{"value":"520103","label":"云岩区"},{"value":"520111","label":"花溪区"},{"value":"520112","label":"乌当区"},{"value":"520113","label":"白云区"},{"value":"520114","label":"小河区"},{"value":"520121","label":"开阳县"},{"value":"520122","label":"息烽县"},{"value":"520123","label":"修文县"},{"value":"520151","label":"金阳开发区"},{"value":"520181","label":"清镇市"}]},{"value":"520200","label":"六盘水市","children":[{"value":"520201","label":"钟山区"},{"value":"520203","label":"六枝特区"},{"value":"520221","label":"水城县"},{"value":"520222","label":"盘县"}]},{"value":"520300","label":"遵义市","children":[{"value":"520302","label":"红花岗区"},{"value":"520303","label":"汇川区"},{"value":"520321","label":"遵义县"},{"value":"520322","label":"桐梓县"},{"value":"520323","label":"绥阳县"},{"value":"520324","label":"正安县"},{"value":"520325","label":"道真仡佬族苗族自治县"},{"value":"520326","label":"务川仡佬族苗族自治县"},{"value":"520327","label":"凤冈县"},{"value":"520328","label":"湄潭县"},{"value":"520329","label":"余庆县"},{"value":"520330","label":"习水县"},{"value":"520381","label":"赤水市"},{"value":"520382","label":"仁怀市"}]},{"value":"520400","label":"安顺市","children":[{"value":"520402","label":"西秀区"},{"value":"520421","label":"平坝县"},{"value":"520422","label":"普定县"},{"value":"520423","label":"镇宁布依族苗族自治县"},{"value":"520424","label":"关岭布依族苗族自治县"},{"value":"520425","label":"紫云苗族布依族自治县"}]},{"value":"522200","label":"铜仁地区","children":[{"value":"522201","label":"铜仁市"},{"value":"522222","label":"江口县"},{"value":"522223","label":"玉屏侗族自治县"},{"value":"522224","label":"石阡县"},{"value":"522225","label":"思南县"},{"value":"522226","label":"印江土家族苗族自治县"},{"value":"522227","label":"德江县"},{"value":"522228","label":"沿河土家族自治县"},{"value":"522229","label":"松桃苗族自治县"},{"value":"522230","label":"万山特区"}]},{"value":"522300","label":"黔西南布依族苗族自治州","children":[{"value":"522301","label":"兴义市"},{"value":"522322","label":"兴仁县"},{"value":"522323","label":"普安县"},{"value":"522324","label":"晴隆县"},{"value":"522325","label":"贞丰县"},{"value":"522326","label":"望谟县"},{"value":"522327","label":"册亨县"},{"value":"522328","label":"安龙县"}]},{"value":"522400","label":"毕节地区","children":[{"value":"522401","label":"毕节市"},{"value":"522422","label":"大方县"},{"value":"522423","label":"黔西县"},{"value":"522424","label":"金沙县"},{"value":"522425","label":"织金县"},{"value":"522426","label":"纳雍县"},{"value":"522427","label":"威宁彝族回族苗族自治县"},{"value":"522428","label":"赫章县"}]},{"value":"522600","label":"黔东南苗族侗族自治州","children":[{"value":"522601","label":"凯里市"},{"value":"522622","label":"黄平县"},{"value":"522623","label":"施秉县"},{"value":"522624","label":"三穗县"},{"value":"522625","label":"镇远县"},{"value":"522626","label":"岑巩县"},{"value":"522627","label":"天柱县"},{"value":"522628","label":"锦屏县"},{"value":"522629","label":"剑河县"},{"value":"522630","label":"台江县"},{"value":"522631","label":"黎平县"},{"value":"522632","label":"榕江县"},{"value":"522633","label":"从江县"},{"value":"522634","label":"雷山县"},{"value":"522635","label":"麻江县"},{"value":"522636","label":"丹寨县"}]},{"value":"522700","label":"黔南布依族苗族自治州","children":[{"value":"522701","label":"都匀市"},{"value":"522702","label":"福泉市"},{"value":"522722","label":"荔波县"},{"value":"522723","label":"贵定县"},{"value":"522725","label":"瓮安县"},{"value":"522726","label":"独山县"},{"value":"522727","label":"平塘县"},{"value":"522728","label":"罗甸县"},{"value":"522729","label":"长顺县"},{"value":"522730","label":"龙里县"},{"value":"522731","label":"惠水县"},{"value":"522732","label":"三都水族自治县"}]}]},{"label":"云南省","value":"530000","children":[{"value":"530100","label":"昆明市","children":[{"value":"530102","label":"五华区"},{"value":"530103","label":"盘龙区"},{"value":"530111","label":"官渡区"},{"value":"530112","label":"西山区"},{"value":"530113","label":"东川区"},{"value":"530121","label":"呈贡县"},{"value":"530122","label":"晋宁县"},{"value":"530124","label":"富民县"},{"value":"530125","label":"宜良县"},{"value":"530126","label":"石林彝族自治县"},{"value":"530127","label":"嵩明县"},{"value":"530128","label":"禄劝彝族苗族自治县"},{"value":"530129","label":"寻甸回族彝族自治县"},{"value":"530181","label":"安宁市"}]},{"value":"530300","label":"曲靖市","children":[{"value":"530302","label":"麒麟区"},{"value":"530321","label":"马龙县"},{"value":"530322","label":"陆良县"},{"value":"530323","label":"师宗县"},{"value":"530324","label":"罗平县"},{"value":"530325","label":"富源县"},{"value":"530326","label":"会泽县"},{"value":"530328","label":"沾益县"},{"value":"530381","label":"宣威市"}]},{"value":"530400","label":"玉溪市","children":[{"value":"530402","label":"红塔区"},{"value":"530421","label":"江川县"},{"value":"530422","label":"澄江县"},{"value":"530423","label":"通海县"},{"value":"530424","label":"华宁县"},{"value":"530425","label":"易门县"},{"value":"530426","label":"峨山彝族自治县"},{"value":"530427","label":"新平彝族傣族自治县"},{"value":"530428","label":"元江哈尼族彝族傣族自治县"}]},{"value":"530500","label":"保山市","children":[{"value":"530502","label":"隆阳区"},{"value":"530521","label":"施甸县"},{"value":"530522","label":"腾冲县"},{"value":"530523","label":"龙陵县"},{"value":"530524","label":"昌宁县"}]},{"value":"530600","label":"昭通市","children":[{"value":"530602","label":"昭阳区"},{"value":"530621","label":"鲁甸县"},{"value":"530622","label":"巧家县"},{"value":"530623","label":"盐津县"},{"value":"530624","label":"大关县"},{"value":"530625","label":"永善县"},{"value":"530626","label":"绥江县"},{"value":"530627","label":"镇雄县"},{"value":"530628","label":"彝良县"},{"value":"530629","label":"威信县"},{"value":"530630","label":"水富县"}]},{"value":"530700","label":"丽江市","children":[{"value":"530702","label":"古城区"},{"value":"530721","label":"玉龙纳西族自治县"},{"value":"530722","label":"永胜县"},{"value":"530723","label":"华坪县"},{"value":"530724","label":"宁蒗彝族自治县"}]},{"value":"530800","label":"普洱市","children":[{"value":"530802","label":"思茅区"},{"value":"530821","label":"宁洱哈尼族彝族自治县"},{"value":"530822","label":"墨江哈尼族自治县"},{"value":"530823","label":"景东彝族自治县"},{"value":"530824","label":"景谷傣族彝族自治县"},{"value":"530825","label":"镇沅彝族哈尼族拉祜族自治县"},{"value":"530826","label":"江城哈尼族彝族自治县"},{"value":"530827","label":"孟连傣族拉祜族佤族自治县"},{"value":"530828","label":"澜沧拉祜族自治县"},{"value":"530829","label":"西盟佤族自治县"}]},{"value":"530900","label":"临沧市","children":[{"value":"530902","label":"临翔区"},{"value":"530921","label":"凤庆县"},{"value":"530922","label":"云县"},{"value":"530923","label":"永德县"},{"value":"530924","label":"镇康县"},{"value":"530925","label":"双江拉祜族佤族布朗族傣族自治县"},{"value":"530926","label":"耿马傣族佤族自治县"},{"value":"530927","label":"沧源佤族自治县"}]},{"value":"532300","label":"楚雄彝族自治州","children":[{"value":"532301","label":"楚雄市"},{"value":"532322","label":"双柏县"},{"value":"532323","label":"牟定县"},{"value":"532324","label":"南华县"},{"value":"532325","label":"姚安县"},{"value":"532326","label":"大姚县"},{"value":"532327","label":"永仁县"},{"value":"532328","label":"元谋县"},{"value":"532329","label":"武定县"},{"value":"532331","label":"禄丰县"}]},{"value":"532500","label":"红河哈尼族彝族自治州","children":[{"value":"532501","label":"个旧市"},{"value":"532502","label":"开远市"},{"value":"532522","label":"蒙自县"},{"value":"532523","label":"屏边苗族自治县"},{"value":"532524","label":"建水县"},{"value":"532525","label":"石屏县"},{"value":"532526","label":"弥勒县"},{"value":"532527","label":"泸西县"},{"value":"532528","label":"元阳县"},{"value":"532529","label":"红河县"},{"value":"532530","label":"金平苗族瑶族傣族自治县"},{"value":"532531","label":"绿春县"},{"value":"532532","label":"河口瑶族自治县"}]},{"value":"532600","label":"文山壮族苗族自治州","children":[{"value":"532621","label":"文山县"},{"value":"532622","label":"砚山县"},{"value":"532623","label":"西畴县"},{"value":"532624","label":"麻栗坡县"},{"value":"532625","label":"马关县"},{"value":"532626","label":"丘北县"},{"value":"532627","label":"广南县"},{"value":"532628","label":"富宁县"}]},{"value":"532800","label":"西双版纳傣族自治州","children":[{"value":"532801","label":"景洪市"},{"value":"532822","label":"勐海县"},{"value":"532823","label":"勐腊县"}]},{"value":"532900","label":"大理白族自治州","children":[{"value":"532901","label":"大理市"},{"value":"532922","label":"漾濞彝族自治县"},{"value":"532923","label":"祥云县"},{"value":"532924","label":"宾川县"},{"value":"532925","label":"弥渡县"},{"value":"532926","label":"南涧彝族自治县"},{"value":"532927","label":"巍山彝族回族自治县"},{"value":"532928","label":"永平县"},{"value":"532929","label":"云龙县"},{"value":"532930","label":"洱源县"},{"value":"532931","label":"剑川县"},{"value":"532932","label":"鹤庆县"}]},{"value":"533100","label":"德宏傣族景颇族自治州","children":[{"value":"533102","label":"瑞丽市"},{"value":"533103","label":"潞西市"},{"value":"533122","label":"梁河县"},{"value":"533123","label":"盈江县"},{"value":"533124","label":"陇川县"}]},{"value":"533300","label":"怒江傈僳族自治州","children":[{"value":"533321","label":"泸水县"},{"value":"533323","label":"福贡县"},{"value":"533324","label":"贡山独龙族怒族自治县"},{"value":"533325","label":"兰坪白族普米族自治县"}]},{"value":"533400","label":"迪庆藏族自治州","children":[{"value":"533421","label":"香格里拉县"},{"value":"533422","label":"德钦县"},{"value":"533423","label":"维西傈僳族自治县"}]}]},{"label":"西藏自治区","value":"540000","children":[{"value":"540100","label":"拉萨市","children":[{"value":"540102","label":"城关区"},{"value":"540121","label":"林周县"},{"value":"540122","label":"当雄县"},{"value":"540123","label":"尼木县"},{"value":"540124","label":"曲水县"},{"value":"540125","label":"堆龙德庆县"},{"value":"540126","label":"达孜县"},{"value":"540127","label":"墨竹工卡县"}]},{"value":"542100","label":"昌都地区","children":[{"value":"542121","label":"昌都县"},{"value":"542122","label":"江达县"},{"value":"542123","label":"贡觉县"},{"value":"542124","label":"类乌齐县"},{"value":"542125","label":"丁青县"},{"value":"542126","label":"察雅县"},{"value":"542127","label":"八宿县"},{"value":"542128","label":"左贡县"},{"value":"542129","label":"芒康县"},{"value":"542132","label":"洛隆县"},{"value":"542133","label":"边坝县"}]},{"value":"542200","label":"山南地区","children":[{"value":"542221","label":"乃东县"},{"value":"542222","label":"扎囊县"},{"value":"542223","label":"贡嘎县"},{"value":"542224","label":"桑日县"},{"value":"542225","label":"琼结县"},{"value":"542226","label":"曲松县"},{"value":"542227","label":"措美县"},{"value":"542228","label":"洛扎县"},{"value":"542229","label":"加查县"},{"value":"542231","label":"隆子县"},{"value":"542232","label":"错那县"},{"value":"542233","label":"浪卡子县"}]},{"value":"542300","label":"日喀则地区","children":[{"value":"542301","label":"日喀则市"},{"value":"542322","label":"南木林县"},{"value":"542323","label":"江孜县"},{"value":"542324","label":"定日县"},{"value":"542325","label":"萨迦县"},{"value":"542326","label":"拉孜县"},{"value":"542327","label":"昂仁县"},{"value":"542328","label":"谢通门县"},{"value":"542329","label":"白朗县"},{"value":"542330","label":"仁布县"},{"value":"542331","label":"康马县"},{"value":"542332","label":"定结县"},{"value":"542333","label":"仲巴县"},{"value":"542334","label":"亚东县"},{"value":"542335","label":"吉隆县"},{"value":"542336","label":"聂拉木县"},{"value":"542337","label":"萨嘎县"},{"value":"542338","label":"岗巴县"}]},{"value":"542400","label":"那曲地区","children":[{"value":"542421","label":"那曲县"},{"value":"542422","label":"嘉黎县"},{"value":"542423","label":"比如县"},{"value":"542424","label":"聂荣县"},{"value":"542425","label":"安多县"},{"value":"542426","label":"申扎县"},{"value":"542427","label":"索县"},{"value":"542428","label":"班戈县"},{"value":"542429","label":"巴青县"},{"value":"542430","label":"尼玛县"}]},{"value":"542500","label":"阿里地区","children":[{"value":"542521","label":"普兰县"},{"value":"542522","label":"札达县"},{"value":"542523","label":"噶尔县"},{"value":"542524","label":"日土县"},{"value":"542525","label":"革吉县"},{"value":"542526","label":"改则县"},{"value":"542527","label":"措勤县"}]},{"value":"542600","label":"林芝地区","children":[{"value":"542621","label":"林芝县"},{"value":"542622","label":"工布江达县"},{"value":"542623","label":"米林县"},{"value":"542624","label":"墨脱县"},{"value":"542625","label":"波密县"},{"value":"542626","label":"察隅县"},{"value":"542627","label":"朗县"}]}]},{"label":"陕西省","value":"610000","children":[{"value":"610100","label":"西安市","children":[{"value":"610102","label":"新城区"},{"value":"610103","label":"碑林区"},{"value":"610104","label":"莲湖区"},{"value":"610111","label":"灞桥区"},{"value":"610112","label":"未央区"},{"value":"610113","label":"雁塔区"},{"value":"610114","label":"阎良区"},{"value":"610115","label":"临潼区"},{"value":"610116","label":"长安区"},{"value":"610122","label":"蓝田县"},{"value":"610124","label":"周至县"},{"value":"610125","label":"户县"},{"value":"610126","label":"高陵县"}]},{"value":"610200","label":"铜川市","children":[{"value":"610202","label":"王益区"},{"value":"610203","label":"印台区"},{"value":"610204","label":"耀州区"},{"value":"610222","label":"宜君县"}]},{"value":"610300","label":"宝鸡市","children":[{"value":"610302","label":"渭滨区"},{"value":"610303","label":"金台区"},{"value":"610304","label":"陈仓区"},{"value":"610322","label":"凤翔县"},{"value":"610323","label":"岐山县"},{"value":"610324","label":"扶风县"},{"value":"610326","label":"眉县"},{"value":"610327","label":"陇县"},{"value":"610328","label":"千阳县"},{"value":"610329","label":"麟游县"},{"value":"610330","label":"凤县"},{"value":"610331","label":"太白县"}]},{"value":"610400","label":"咸阳市","children":[{"value":"610402","label":"秦都区"},{"value":"610403","label":"杨陵区"},{"value":"610404","label":"渭城区"},{"value":"610422","label":"三原县"},{"value":"610423","label":"泾阳县"},{"value":"610424","label":"乾县"},{"value":"610425","label":"礼泉县"},{"value":"610426","label":"永寿县"},{"value":"610427","label":"彬县"},{"value":"610428","label":"长武县"},{"value":"610429","label":"旬邑县"},{"value":"610430","label":"淳化县"},{"value":"610431","label":"武功县"},{"value":"610481","label":"兴平市"}]},{"value":"610500","label":"渭南市","children":[{"value":"610502","label":"临渭区"},{"value":"610521","label":"华县"},{"value":"610522","label":"潼关县"},{"value":"610523","label":"大荔县"},{"value":"610524","label":"合阳县"},{"value":"610525","label":"澄城县"},{"value":"610526","label":"蒲城县"},{"value":"610527","label":"白水县"},{"value":"610528","label":"富平县"},{"value":"610581","label":"韩城市"},{"value":"610582","label":"华阴市"}]},{"value":"610600","label":"延安市","children":[{"value":"610602","label":"宝塔区"},{"value":"610621","label":"延长县"},{"value":"610622","label":"延川县"},{"value":"610623","label":"子长县"},{"value":"610624","label":"安塞县"},{"value":"610625","label":"志丹县"},{"value":"610626","label":"吴起县"},{"value":"610627","label":"甘泉县"},{"value":"610628","label":"富县"},{"value":"610629","label":"洛川县"},{"value":"610630","label":"宜川县"},{"value":"610631","label":"黄龙县"},{"value":"610632","label":"黄陵县"}]},{"value":"610700","label":"汉中市","children":[{"value":"610702","label":"汉台区"},{"value":"610721","label":"南郑县"},{"value":"610722","label":"城固县"},{"value":"610723","label":"洋县"},{"value":"610724","label":"西乡县"},{"value":"610725","label":"勉县"},{"value":"610726","label":"宁强县"},{"value":"610727","label":"略阳县"},{"value":"610728","label":"镇巴县"},{"value":"610729","label":"留坝县"},{"value":"610730","label":"佛坪县"}]},{"value":"610800","label":"榆林市","children":[{"value":"610802","label":"榆阳区"},{"value":"610821","label":"神木县"},{"value":"610822","label":"府谷县"},{"value":"610823","label":"横山县"},{"value":"610824","label":"靖边县"},{"value":"610825","label":"定边县"},{"value":"610826","label":"绥德县"},{"value":"610827","label":"米脂县"},{"value":"610828","label":"佳县"},{"value":"610829","label":"吴堡县"},{"value":"610830","label":"清涧县"},{"value":"610831","label":"子洲县"}]},{"value":"610900","label":"安康市","children":[{"value":"610902","label":"汉滨区"},{"value":"610921","label":"汉阴县"},{"value":"610922","label":"石泉县"},{"value":"610923","label":"宁陕县"},{"value":"610924","label":"紫阳县"},{"value":"610925","label":"岚皋县"},{"value":"610926","label":"平利县"},{"value":"610927","label":"镇坪县"},{"value":"610928","label":"旬阳县"},{"value":"610929","label":"白河县"}]},{"value":"611000","label":"商洛市","children":[{"value":"611002","label":"商州区"},{"value":"611021","label":"洛南县"},{"value":"611022","label":"丹凤县"},{"value":"611023","label":"商南县"},{"value":"611024","label":"山阳县"},{"value":"611025","label":"镇安县"},{"value":"611026","label":"柞水县"}]}]},{"label":"甘肃省","value":"620000","children":[{"value":"620100","label":"兰州市","children":[{"value":"620102","label":"城关区"},{"value":"620103","label":"七里河区"},{"value":"620104","label":"西固区"},{"value":"620105","label":"安宁区"},{"value":"620111","label":"红古区"},{"value":"620121","label":"永登县"},{"value":"620122","label":"皋兰县"},{"value":"620123","label":"榆中县"}]},{"value":"620200","label":"嘉峪关市"},{"value":"620300","label":"金昌市","children":[{"value":"620302","label":"金川区"},{"value":"620321","label":"永昌县"}]},{"value":"620400","label":"白银市","children":[{"value":"620402","label":"白银区"},{"value":"620403","label":"平川区"},{"value":"620421","label":"靖远县"},{"value":"620422","label":"会宁县"},{"value":"620423","label":"景泰县"}]},{"value":"620500","label":"天水市","children":[{"value":"620502","label":"秦州区"},{"value":"620503","label":"麦积区"},{"value":"620521","label":"清水县"},{"value":"620522","label":"秦安县"},{"value":"620523","label":"甘谷县"},{"value":"620524","label":"武山县"},{"value":"620525","label":"张家川回族自治县"}]},{"value":"620600","label":"武威市","children":[{"value":"620602","label":"凉州区"},{"value":"620621","label":"民勤县"},{"value":"620622","label":"古浪县"},{"value":"620623","label":"天祝藏族自治县"}]},{"value":"620700","label":"张掖市","children":[{"value":"620702","label":"甘州区"},{"value":"620721","label":"肃南裕固族自治县"},{"value":"620722","label":"民乐县"},{"value":"620723","label":"临泽县"},{"value":"620724","label":"高台县"},{"value":"620725","label":"山丹县"}]},{"value":"620800","label":"平凉市","children":[{"value":"620802","label":"崆峒区"},{"value":"620821","label":"泾川县"},{"value":"620822","label":"灵台县"},{"value":"620823","label":"崇信县"},{"value":"620824","label":"华亭县"},{"value":"620825","label":"庄浪县"},{"value":"620826","label":"静宁县"}]},{"value":"620900","label":"酒泉市","children":[{"value":"620902","label":"肃州区"},{"value":"620921","label":"金塔县"},{"value":"620922","label":"安西县"},{"value":"620923","label":"肃北蒙古族自治县"},{"value":"620924","label":"阿克塞哈萨克族自治县"},{"value":"620981","label":"玉门市"},{"value":"620982","label":"敦煌市"}]},{"value":"621000","label":"庆阳市","children":[{"value":"621002","label":"西峰区"},{"value":"621021","label":"庆城县"},{"value":"621022","label":"环县"},{"value":"621023","label":"华池县"},{"value":"621024","label":"合水县"},{"value":"621025","label":"正宁县"},{"value":"621026","label":"宁县"},{"value":"621027","label":"镇原县"}]},{"value":"621100","label":"定西市","children":[{"value":"621102","label":"安定区"},{"value":"621121","label":"通渭县"},{"value":"621122","label":"陇西县"},{"value":"621123","label":"渭源县"},{"value":"621124","label":"临洮县"},{"value":"621125","label":"漳县"},{"value":"621126","label":"岷县"}]},{"value":"621200","label":"陇南市","children":[{"value":"621202","label":"武都区"},{"value":"621221","label":"成县"},{"value":"621222","label":"文县"},{"value":"621223","label":"宕昌县"},{"value":"621224","label":"康县"},{"value":"621225","label":"西和县"},{"value":"621226","label":"礼县"},{"value":"621227","label":"徽县"},{"value":"621228","label":"两当县"}]},{"value":"622900","label":"临夏回族自治州","children":[{"value":"622901","label":"临夏市"},{"value":"622921","label":"临夏县"},{"value":"622922","label":"康乐县"},{"value":"622923","label":"永靖县"},{"value":"622924","label":"广河县"},{"value":"622925","label":"和政县"},{"value":"622926","label":"东乡族自治县"},{"value":"622927","label":"积石山保安族东乡族撒拉族自治县"}]},{"value":"623000","label":"甘南藏族自治州","children":[{"value":"623001","label":"合作市"},{"value":"623021","label":"临潭县"},{"value":"623022","label":"卓尼县"},{"value":"623023","label":"舟曲县"},{"value":"623024","label":"迭部县"},{"value":"623025","label":"玛曲县"},{"value":"623026","label":"碌曲县"},{"value":"623027","label":"夏河县"}]}]},{"label":"青海省","value":"630000","children":[{"value":"630100","label":"西宁市","children":[{"value":"630102","label":"城东区"},{"value":"630103","label":"城中区"},{"value":"630104","label":"城西区"},{"value":"630105","label":"城北区"},{"value":"630121","label":"大通回族土族自治县"},{"value":"630122","label":"湟中县"},{"value":"630123","label":"湟源县"}]},{"value":"632100","label":"海东地区","children":[{"value":"632121","label":"平安县"},{"value":"632122","label":"民和回族土族自治县"},{"value":"632123","label":"乐都县"},{"value":"632126","label":"互助土族自治县"},{"value":"632127","label":"化隆回族自治县"},{"value":"632128","label":"循化撒拉族自治县"}]},{"value":"632200","label":"海北藏族自治州","children":[{"value":"632221","label":"门源回族自治县"},{"value":"632222","label":"祁连县"},{"value":"632223","label":"海晏县"},{"value":"632224","label":"刚察县"}]},{"value":"632300","label":"黄南藏族自治州","children":[{"value":"632321","label":"同仁县"},{"value":"632322","label":"尖扎县"},{"value":"632323","label":"泽库县"},{"value":"632324","label":"河南蒙古族自治县"}]},{"value":"632500","label":"海南藏族自治州","children":[{"value":"632521","label":"共和县"},{"value":"632522","label":"同德县"},{"value":"632523","label":"贵德县"},{"value":"632524","label":"兴海县"},{"value":"632525","label":"贵南县"}]},{"value":"632600","label":"果洛藏族自治州","children":[{"value":"632621","label":"玛沁县"},{"value":"632622","label":"班玛县"},{"value":"632623","label":"甘德县"},{"value":"632624","label":"达日县"},{"value":"632625","label":"久治县"},{"value":"632626","label":"玛多县"}]},{"value":"632700","label":"玉树藏族自治州","children":[{"value":"632721","label":"玉树县"},{"value":"632722","label":"杂多县"},{"value":"632723","label":"称多县"},{"value":"632724","label":"治多县"},{"value":"632725","label":"囊谦县"},{"value":"632726","label":"曲麻莱县"}]},{"value":"632800","label":"海西蒙古族藏族自治州","children":[{"value":"632801","label":"格尔木市"},{"value":"632802","label":"德令哈市"},{"value":"632821","label":"乌兰县"},{"value":"632822","label":"都兰县"},{"value":"632823","label":"天峻县"}]}]},{"label":"宁夏回族自治区","value":"640000","children":[{"value":"640100","label":"银川市","children":[{"value":"640104","label":"兴庆区"},{"value":"640105","label":"西夏区"},{"value":"640106","label":"金凤区"},{"value":"640121","label":"永宁县"},{"value":"640122","label":"贺兰县"},{"value":"640181","label":"灵武市"}]},{"value":"640200","label":"石嘴山市","children":[{"value":"640202","label":"大武口区"},{"value":"640205","label":"惠农区"},{"value":"640221","label":"平罗县"}]},{"value":"640300","label":"吴忠市","children":[{"value":"640302","label":"利通区"},{"value":"640303","label":"红寺堡区"},{"value":"640323","label":"盐池县"},{"value":"640324","label":"同心县"},{"value":"640381","label":"青铜峡市"}]},{"value":"640400","label":"固原市","children":[{"value":"640402","label":"原州区"},{"value":"640422","label":"西吉县"},{"value":"640423","label":"隆德县"},{"value":"640424","label":"泾源县"},{"value":"640425","label":"彭阳县"}]},{"value":"640500","label":"中卫市","children":[{"value":"640502","label":"沙坡头区"},{"value":"640521","label":"中宁县"},{"value":"640522","label":"海原县"}]}]},{"label":"新疆维吾尔自治区","value":"650000","children":[{"value":"650100","label":"乌鲁木齐市","children":[{"value":"650102","label":"天山区"},{"value":"650103","label":"沙依巴克区"},{"value":"650104","label":"新市区"},{"value":"650105","label":"水磨沟区"},{"value":"650106","label":"头屯河区"},{"value":"650107","label":"达坂城区"},{"value":"650108","label":"东山区"},{"value":"650109","label":"米东区"},{"value":"650121","label":"乌鲁木齐县"}]},{"value":"650200","label":"克拉玛依市","children":[{"value":"650202","label":"独山子区"},{"value":"650203","label":"克拉玛依区"},{"value":"650204","label":"白碱滩区"},{"value":"650205","label":"乌尔禾区"}]},{"value":"652100","label":"吐鲁番地区","children":[{"value":"652101","label":"吐鲁番市"},{"value":"652122","label":"鄯善县"},{"value":"652123","label":"托克逊县"}]},{"value":"652200","label":"哈密地区","children":[{"value":"652201","label":"哈密市"},{"value":"652222","label":"巴里坤哈萨克自治县"},{"value":"652223","label":"伊吾县"}]},{"value":"652300","label":"昌吉回族自治州","children":[{"value":"652301","label":"昌吉市"},{"value":"652302","label":"阜康市"},{"value":"652303","label":"米泉市"},{"value":"652323","label":"呼图壁县"},{"value":"652324","label":"玛纳斯县"},{"value":"652325","label":"奇台县"},{"value":"652327","label":"吉木萨尔县"},{"value":"652328","label":"木垒哈萨克自治县"}]},{"value":"652700","label":"博尔塔拉蒙古自治州","children":[{"value":"652701","label":"博乐市"},{"value":"652722","label":"精河县"},{"value":"652723","label":"温泉县"}]},{"value":"652800","label":"巴音郭楞蒙古自治州","children":[{"value":"652801","label":"库尔勒市"},{"value":"652822","label":"轮台县"},{"value":"652823","label":"尉犁县"},{"value":"652824","label":"若羌县"},{"value":"652825","label":"且末县"},{"value":"652826","label":"焉耆回族自治县"},{"value":"652827","label":"和静县"},{"value":"652828","label":"和硕县"},{"value":"652829","label":"博湖县"}]},{"value":"652900","label":"阿克苏地区","children":[{"value":"652901","label":"阿克苏市"},{"value":"652922","label":"温宿县"},{"value":"652923","label":"库车县"},{"value":"652924","label":"沙雅县"},{"value":"652925","label":"新和县"},{"value":"652926","label":"拜城县"},{"value":"652927","label":"乌什县"},{"value":"652928","label":"阿瓦提县"},{"value":"652929","label":"柯坪县"}]},{"value":"653000","label":"克孜勒苏柯尔克孜自治州","children":[{"value":"653001","label":"阿图什市"},{"value":"653022","label":"阿克陶县"},{"value":"653023","label":"阿合奇县"},{"value":"653024","label":"乌恰县"}]},{"value":"653100","label":"喀什地区","children":[{"value":"653101","label":"喀什市"},{"value":"653121","label":"疏附县"},{"value":"653122","label":"疏勒县"},{"value":"653123","label":"英吉沙县"},{"value":"653124","label":"泽普县"},{"value":"653125","label":"莎车县"},{"value":"653126","label":"叶城县"},{"value":"653127","label":"麦盖提县"},{"value":"653128","label":"岳普湖县"},{"value":"653129","label":"伽师县"},{"value":"653130","label":"巴楚县"},{"value":"653131","label":"塔什库尔干塔吉克自治县"}]},{"value":"653200","label":"和田地区","children":[{"value":"653201","label":"和田市"},{"value":"653221","label":"和田县"},{"value":"653222","label":"墨玉县"},{"value":"653223","label":"皮山县"},{"value":"653224","label":"洛浦县"},{"value":"653225","label":"策勒县"},{"value":"653226","label":"于田县"},{"value":"653227","label":"民丰县"}]},{"value":"654000","label":"伊犁哈萨克自治州","children":[{"value":"654002","label":"伊宁市"},{"value":"654003","label":"奎屯市"},{"value":"654021","label":"伊宁县"},{"value":"654022","label":"察布查尔锡伯自治县"},{"value":"654023","label":"霍城县"},{"value":"654024","label":"巩留县"},{"value":"654025","label":"新源县"},{"value":"654026","label":"昭苏县"},{"value":"654027","label":"特克斯县"},{"value":"654028","label":"尼勒克县"}]},{"value":"654200","label":"塔城地区","children":[{"value":"654201","label":"塔城市"},{"value":"654202","label":"乌苏市"},{"value":"654221","label":"额敏县"},{"value":"654223","label":"沙湾县"},{"value":"654224","label":"托里县"},{"value":"654225","label":"裕民县"},{"value":"654226","label":"和布克赛尔蒙古自治县"}]},{"value":"654300","label":"阿勒泰地区","children":[{"value":"654301","label":"阿勒泰市"},{"value":"654321","label":"布尔津县"},{"value":"654322","label":"富蕴县"},{"value":"654323","label":"福海县"},{"value":"654324","label":"哈巴河县"},{"value":"654325","label":"青河县"},{"value":"654326","label":"吉木乃县"}]},{"value":"659001","label":"石河子市"},{"value":"659002","label":"阿拉尔市"},{"value":"659003","label":"图木舒克市"},{"value":"659004","label":"五家渠市"}]},{"label":"台湾省","value":"710000","children":[{"value":"710100","label":"台北市","children":[{"value":"710101","label":"中正区"},{"value":"710102","label":"大同区"},{"value":"710103","label":"中山区"},{"value":"710104","label":"松山区"},{"value":"710105","label":"大安区"},{"value":"710106","label":"万华区"},{"value":"710107","label":"信义区"},{"value":"710108","label":"士林区"},{"value":"710109","label":"北投区"},{"value":"710110","label":"内湖区"},{"value":"710111","label":"南港区"},{"value":"710112","label":"文山区"}]},{"value":"710200","label":"高雄市","children":[{"value":"710201","label":"新兴区"},{"value":"710202","label":"前金区"},{"value":"710203","label":"芩雅区"},{"value":"710204","label":"盐埕区"},{"value":"710205","label":"鼓山区"},{"value":"710206","label":"旗津区"},{"value":"710207","label":"前镇区"},{"value":"710208","label":"三民区"},{"value":"710209","label":"左营区"},{"value":"710210","label":"楠梓区"},{"value":"710211","label":"小港区"}]},{"value":"710300","label":"台南市","children":[{"value":"710301","label":"中西区"},{"value":"710302","label":"东区"},{"value":"710303","label":"南区"},{"value":"710304","label":"北区"},{"value":"710305","label":"安平区"},{"value":"710306","label":"安南区"}]},{"value":"710400","label":"台中市","children":[{"value":"710401","label":"中区"},{"value":"710402","label":"东区"},{"value":"710403","label":"南区"},{"value":"710404","label":"西区"},{"value":"710405","label":"北区"},{"value":"710406","label":"北屯区"},{"value":"710407","label":"西屯区"},{"value":"710408","label":"南屯区"}]},{"value":"710500","label":"金门县"},{"value":"710600","label":"南投县"},{"value":"710700","label":"基隆市","children":[{"value":"710701","label":"仁爱区"},{"value":"710702","label":"信义区"},{"value":"710703","label":"中正区"},{"value":"710704","label":"中山区"},{"value":"710705","label":"安乐区"},{"value":"710706","label":"暖暖区"},{"value":"710707","label":"七堵区"}]},{"value":"710800","label":"新竹市","children":[{"value":"710801","label":"东区"},{"value":"710802","label":"北区"},{"value":"710803","label":"香山区"}]},{"value":"710900","label":"嘉义市","children":[{"value":"710901","label":"东区"},{"value":"710902","label":"西区"}]},{"value":"711100","label":"新北市"},{"value":"711200","label":"宜兰县"},{"value":"711300","label":"新竹县"},{"value":"711400","label":"桃园县"},{"value":"711500","label":"苗栗县"},{"value":"711700","label":"彰化县"},{"value":"711900","label":"嘉义县"},{"value":"712100","label":"云林县"},{"value":"712400","label":"屏东县"},{"value":"712500","label":"台东县"},{"value":"712600","label":"花莲县"},{"value":"712700","label":"澎湖县"}]},{"label":"香港特别行政区","value":"810000","children":[{"value":"810100","label":"香港岛","children":[{"value":"810101","label":"中西区"},{"value":"810102","label":"湾仔"},{"value":"810103","label":"东区"},{"value":"810104","label":"南区"}]},{"value":"810200","label":"九龙","children":[{"value":"810201","label":"九龙城区"},{"value":"810202","label":"油尖旺区"},{"value":"810203","label":"深水埗区"},{"value":"810204","label":"黄大仙区"},{"value":"810205","label":"观塘区"}]},{"value":"810300","label":"新界","children":[{"value":"810301","label":"北区"},{"value":"810302","label":"大埔区"},{"value":"810303","label":"沙田区"},{"value":"810304","label":"西贡区"},{"value":"810305","label":"元朗区"},{"value":"810306","label":"屯门区"},{"value":"810307","label":"荃湾区"},{"value":"810308","label":"葵青区"},{"value":"810309","label":"离岛区"}]}]},{"label":"澳门特别行政区","value":"820000","children":[{"value":"820100","label":"澳门半岛"},{"value":"820200","label":"离岛"}]},{"label":"海外","value":"990000","children":[{"value":"990100","label":"海外"}]}] diff --git a/public/tinymce/langs/zh_CN.js b/public/tinymce/langs/zh_CN.js new file mode 100644 index 0000000..2a784f5 --- /dev/null +++ b/public/tinymce/langs/zh_CN.js @@ -0,0 +1,462 @@ +tinymce.addI18n('zh_CN',{ +"Redo": "\u91cd\u505a", +"Undo": "\u64a4\u9500", +"Cut": "\u526a\u5207", +"Copy": "\u590d\u5236", +"Paste": "\u7c98\u8d34", +"Select all": "\u5168\u9009", +"New document": "\u65b0\u6587\u4ef6", +"Ok": "\u786e\u5b9a", +"Cancel": "\u53d6\u6d88", +"Visual aids": "\u7f51\u683c\u7ebf", +"Bold": "\u7c97\u4f53", +"Italic": "\u659c\u4f53", +"Underline": "\u4e0b\u5212\u7ebf", +"Strikethrough": "\u5220\u9664\u7ebf", +"Superscript": "\u4e0a\u6807", +"Subscript": "\u4e0b\u6807", +"Clear formatting": "\u6e05\u9664\u683c\u5f0f", +"Align left": "\u5de6\u8fb9\u5bf9\u9f50", +"Align center": "\u4e2d\u95f4\u5bf9\u9f50", +"Align right": "\u53f3\u8fb9\u5bf9\u9f50", +"Justify": "\u4e24\u7aef\u5bf9\u9f50", +"Bullet list": "\u9879\u76ee\u7b26\u53f7", +"Numbered list": "\u7f16\u53f7\u5217\u8868", +"Decrease indent": "\u51cf\u5c11\u7f29\u8fdb", +"Increase indent": "\u589e\u52a0\u7f29\u8fdb", +"Close": "\u5173\u95ed", +"Formats": "\u683c\u5f0f", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u6253\u5f00\u526a\u8d34\u677f\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u7b49\u5feb\u6377\u952e\u3002", +"Headers": "\u6807\u9898", +"Header 1": "\u6807\u98981", +"Header 2": "\u6807\u98982", +"Header 3": "\u6807\u98983", +"Header 4": "\u6807\u98984", +"Header 5": "\u6807\u98985", +"Header 6": "\u6807\u98986", +"Headings": "\u6807\u9898", +"Heading 1": "\u6807\u98981", +"Heading 2": "\u6807\u98982", +"Heading 3": "\u6807\u98983", +"Heading 4": "\u6807\u98984", +"Heading 5": "\u6807\u98985", +"Heading 6": "\u6807\u98986", +"Preformatted": "\u9884\u5148\u683c\u5f0f\u5316\u7684", +"Div": "Div", +"Pre": "Pre", +"Code": "\u4ee3\u7801", +"Paragraph": "\u6bb5\u843d", +"Blockquote": "\u5f15\u6587\u533a\u5757", +"Inline": "\u6587\u672c", +"Blocks": "\u57fa\u5757", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002", +"Fonts": "\u5b57\u4f53", +"Font Sizes": "\u5b57\u53f7", +"Class": "\u7c7b\u578b", +"Browse for an image": "\u6d4f\u89c8\u56fe\u50cf", +"OR": "\u6216", +"Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64", +"Upload": "\u4e0a\u4f20", +"Block": "\u5757", +"Align": "\u5bf9\u9f50", +"Default": "\u9ed8\u8ba4", +"Circle": "\u7a7a\u5fc3\u5706", +"Disc": "\u5b9e\u5fc3\u5706", +"Square": "\u65b9\u5757", +"Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd", +"Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd", +"Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd", +"Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd", +"Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd", +"Anchor...": "\u951a\u70b9...", +"Name": "\u540d\u79f0", +"Id": "\u6807\u8bc6\u7b26", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002", +"You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f", +"Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f", +"Special character...": "\u7279\u6b8a\u5b57\u7b26...", +"Source code": "\u6e90\u4ee3\u7801", +"Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b", +"Language": "\u8bed\u8a00", +"Code sample...": "\u793a\u4f8b\u4ee3\u7801...", +"Color Picker": "\u9009\u8272\u5668", +"R": "R", +"G": "G", +"B": "B", +"Left to right": "\u4ece\u5de6\u5230\u53f3", +"Right to left": "\u4ece\u53f3\u5230\u5de6", +"Emoticons": "\u8868\u60c5", +"Emoticons...": "\u8868\u60c5\u7b26\u53f7...", +"Metadata and Document Properties": "\u5143\u6570\u636e\u548c\u6587\u6863\u5c5e\u6027", +"Title": "\u6807\u9898", +"Keywords": "\u5173\u952e\u8bcd", +"Description": "\u63cf\u8ff0", +"Robots": "\u673a\u5668\u4eba", +"Author": "\u4f5c\u8005", +"Encoding": "\u7f16\u7801", +"Fullscreen": "\u5168\u5c4f", +"Action": "\u64cd\u4f5c", +"Shortcut": "\u5feb\u6377\u952e", +"Help": "\u5e2e\u52a9", +"Address": "\u5730\u5740", +"Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f", +"Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f", +"Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84", +"Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355", +"Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)", +"Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)", +"Find (if searchreplace plugin activated)": "\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)", +"Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):", +"Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a", +"Learn more...": "\u4e86\u89e3\u66f4\u591a...", +"You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}", +"Plugins": "\u63d2\u4ef6", +"Handy Shortcuts": "\u5feb\u6377\u952e", +"Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf", +"Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247", +"Alternative description": "\u66ff\u4ee3\u63cf\u8ff0", +"Accessibility": "\u8f85\u52a9\u529f\u80fd", +"Image is decorative": "\u56fe\u50cf\u662f\u88c5\u9970\u6027\u7684", +"Source": "\u5730\u5740", +"Dimensions": "\u5927\u5c0f", +"Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4", +"General": "\u666e\u901a", +"Advanced": "\u9ad8\u7ea7", +"Style": "\u6837\u5f0f", +"Vertical space": "\u5782\u76f4\u8fb9\u8ddd", +"Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd", +"Border": "\u8fb9\u6846", +"Insert image": "\u63d2\u5165\u56fe\u7247", +"Image...": "\u56fe\u7247...", +"Image list": "\u56fe\u7247\u5217\u8868", +"Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c", +"Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c", +"Flip vertically": "\u5782\u76f4\u7ffb\u8f6c", +"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c", +"Edit image": "\u7f16\u8f91\u56fe\u7247", +"Image options": "\u56fe\u7247\u9009\u9879", +"Zoom in": "\u653e\u5927", +"Zoom out": "\u7f29\u5c0f", +"Crop": "\u88c1\u526a", +"Resize": "\u8c03\u6574\u5927\u5c0f", +"Orientation": "\u65b9\u5411", +"Brightness": "\u4eae\u5ea6", +"Sharpen": "\u9510\u5316", +"Contrast": "\u5bf9\u6bd4\u5ea6", +"Color levels": "\u989c\u8272\u5c42\u6b21", +"Gamma": "\u4f3d\u9a6c\u503c", +"Invert": "\u53cd\u8f6c", +"Apply": "\u5e94\u7528", +"Back": "\u540e\u9000", +"Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4", +"Date\/time": "\u65e5\u671f\/\u65f6\u95f4", +"Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5", +"Text to display": "\u663e\u793a\u6587\u5b57", +"Url": "\u5730\u5740", +"Open link in...": "\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...", +"Current window": "\u5f53\u524d\u7a97\u53e3", +"None": "\u65e0", +"New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00", +"Open link": "\u6253\u5f00\u94fe\u63a5", +"Remove link": "\u5220\u9664\u94fe\u63a5", +"Anchors": "\u951a\u70b9", +"Link...": "\u94fe\u63a5...", +"Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f", +"The URL you entered seems to be an external link. Do you want to add the required https:\/\/ prefix?": "\u60a8\u8f93\u5165\u7684 URL \u4f3c\u4e4e\u662f\u4e00\u4e2a\u5916\u90e8\u94fe\u63a5\u3002\u60a8\u60f3\u6dfb\u52a0\u6240\u9700\u7684 https:\/\/ \u524d\u7f00\u5417\uff1f", +"Link list": "\u94fe\u63a5\u5217\u8868", +"Insert video": "\u63d2\u5165\u89c6\u9891", +"Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891", +"Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53", +"Alternative source": "\u955c\u50cf", +"Alternative source URL": "\u66ff\u4ee3\u6765\u6e90\u7f51\u5740", +"Media poster (Image URL)": "\u5c01\u9762(\u56fe\u7247\u5730\u5740)", +"Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:", +"Embed": "\u5185\u5d4c", +"Media...": "\u591a\u5a92\u4f53...", +"Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c", +"Page break": "\u5206\u9875\u7b26", +"Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c", +"Preview": "\u9884\u89c8", +"Print...": "\u6253\u5370...", +"Save": "\u4fdd\u5b58", +"Find": "\u67e5\u627e", +"Replace with": "\u66ff\u6362\u4e3a", +"Replace": "\u66ff\u6362", +"Replace all": "\u5168\u90e8\u66ff\u6362", +"Previous": "\u4e0a\u4e00\u4e2a", +"Next": "\u4e0b\u4e00\u4e2a", +"Find and Replace": "\u67e5\u627e\u548c\u66ff\u6362", +"Find and replace...": "\u67e5\u627e\u5e76\u66ff\u6362...", +"Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.", +"Match case": "\u533a\u5206\u5927\u5c0f\u5199", +"Find whole words only": "\u5168\u5b57\u5339\u914d", +"Find in selection": "\u5728\u9009\u533a\u4e2d\u67e5\u627e", +"Spellcheck": "\u62fc\u5199\u68c0\u67e5", +"Spellcheck Language": "\u62fc\u5199\u68c0\u67e5\u8bed\u8a00", +"No misspellings found.": "\u6ca1\u6709\u53d1\u73b0\u62fc\u5199\u9519\u8bef", +"Ignore": "\u5ffd\u7565", +"Ignore all": "\u5168\u90e8\u5ffd\u7565", +"Finish": "\u5b8c\u6210", +"Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178", +"Insert table": "\u63d2\u5165\u8868\u683c", +"Table properties": "\u8868\u683c\u5c5e\u6027", +"Delete table": "\u5220\u9664\u8868\u683c", +"Cell": "\u5355\u5143\u683c", +"Row": "\u884c", +"Column": "\u5217", +"Cell properties": "\u5355\u5143\u683c\u5c5e\u6027", +"Merge cells": "\u5408\u5e76\u5355\u5143\u683c", +"Split cell": "\u62c6\u5206\u5355\u5143\u683c", +"Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165", +"Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165", +"Delete row": "\u5220\u9664\u884c", +"Row properties": "\u884c\u5c5e\u6027", +"Cut row": "\u526a\u5207\u884c", +"Copy row": "\u590d\u5236\u884c", +"Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9", +"Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9", +"Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165", +"Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165", +"Delete column": "\u5220\u9664\u5217", +"Cols": "\u5217", +"Rows": "\u884c", +"Width": "\u5bbd", +"Height": "\u9ad8", +"Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd", +"Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd", +"Caption": "\u6807\u9898", +"Show caption": "\u663e\u793a\u6807\u9898", +"Left": "\u5de6\u5bf9\u9f50", +"Center": "\u5c45\u4e2d", +"Right": "\u53f3\u5bf9\u9f50", +"Cell type": "\u5355\u5143\u683c\u7c7b\u578b", +"Scope": "\u8303\u56f4", +"Alignment": "\u5bf9\u9f50\u65b9\u5f0f", +"H Align": "\u6c34\u5e73\u5bf9\u9f50", +"V Align": "\u5782\u76f4\u5bf9\u9f50", +"Top": "\u9876\u90e8\u5bf9\u9f50", +"Middle": "\u5782\u76f4\u5c45\u4e2d", +"Bottom": "\u5e95\u90e8\u5bf9\u9f50", +"Header cell": "\u8868\u5934\u5355\u5143\u683c", +"Row group": "\u884c\u7ec4", +"Column group": "\u5217\u7ec4", +"Row type": "\u884c\u7c7b\u578b", +"Header": "\u8868\u5934", +"Body": "\u8868\u4f53", +"Footer": "\u8868\u5c3e", +"Border color": "\u8fb9\u6846\u989c\u8272", +"Insert template...": "\u63d2\u5165\u6a21\u677f...", +"Templates": "\u6a21\u677f", +"Template": "\u6a21\u677f", +"Text color": "\u6587\u5b57\u989c\u8272", +"Background color": "\u80cc\u666f\u8272", +"Custom...": "\u81ea\u5b9a\u4e49...", +"Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272", +"No color": "\u65e0", +"Remove color": "\u79fb\u9664\u989c\u8272", +"Table of Contents": "\u5185\u5bb9\u5217\u8868", +"Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846", +"Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26", +"Word count": "\u5b57\u6570", +"Count": "\u8ba1\u6570", +"Document": "\u6587\u6863", +"Selection": "\u9009\u62e9", +"Words": "\u5355\u8bcd", +"Words: {0}": "\u5b57\u6570\uff1a{0}", +"{0} words": "{0} \u5b57", +"File": "\u6587\u4ef6", +"Edit": "\u7f16\u8f91", +"Insert": "\u63d2\u5165", +"View": "\u89c6\u56fe", +"Format": "\u683c\u5f0f", +"Table": "\u8868\u683c", +"Tools": "\u5de5\u5177", +"Powered by {0}": "\u7531{0}\u9a71\u52a8", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9", +"Image title": "\u56fe\u7247\u6807\u9898", +"Border width": "\u8fb9\u6846\u5bbd\u5ea6", +"Border style": "\u8fb9\u6846\u6837\u5f0f", +"Error": "\u9519\u8bef", +"Warn": "\u8b66\u544a", +"Valid": "\u6709\u6548", +"To open the popup, press Shift+Enter": "\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846", +"Rich Text Area. Press ALT-0 for help.": "\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002", +"System Font": "\u7cfb\u7edf\u5b57\u4f53", +"Failed to upload image: {0}": "\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}", +"Failed to load plugin: {0} from url {1}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}", +"Failed to load plugin url: {0}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}", +"Failed to initialize plugin: {0}": "\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}", +"example": "\u793a\u4f8b", +"Search": "\u641c\u7d22", +"All": "\u5168\u90e8", +"Currency": "\u8d27\u5e01", +"Text": "\u6587\u5b57", +"Quotations": "\u5f15\u7528", +"Mathematical": "\u6570\u5b66", +"Extended Latin": "\u62c9\u4e01\u8bed\u6269\u5145", +"Symbols": "\u7b26\u53f7", +"Arrows": "\u7bad\u5934", +"User Defined": "\u81ea\u5b9a\u4e49", +"dollar sign": "\u7f8e\u5143\u7b26\u53f7", +"currency sign": "\u8d27\u5e01\u7b26\u53f7", +"euro-currency sign": "\u6b27\u5143\u7b26\u53f7", +"colon sign": "\u5192\u53f7", +"cruzeiro sign": "\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7", +"french franc sign": "\u6cd5\u90ce\u7b26\u53f7", +"lira sign": "\u91cc\u62c9\u7b26\u53f7", +"mill sign": "\u5bc6\u5c14\u7b26\u53f7", +"naira sign": "\u5948\u62c9\u7b26\u53f7", +"peseta sign": "\u6bd4\u585e\u5854\u7b26\u53f7", +"rupee sign": "\u5362\u6bd4\u7b26\u53f7", +"won sign": "\u97e9\u5143\u7b26\u53f7", +"new sheqel sign": "\u65b0\u8c22\u514b\u5c14\u7b26\u53f7", +"dong sign": "\u8d8a\u5357\u76fe\u7b26\u53f7", +"kip sign": "\u8001\u631d\u57fa\u666e\u7b26\u53f7", +"tugrik sign": "\u56fe\u683c\u91cc\u514b\u7b26\u53f7", +"drachma sign": "\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7", +"german penny symbol": "\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7", +"peso sign": "\u6bd4\u7d22\u7b26\u53f7", +"guarani sign": "\u74dc\u62c9\u5c3c\u7b26\u53f7", +"austral sign": "\u6fb3\u5143\u7b26\u53f7", +"hryvnia sign": "\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7", +"cedi sign": "\u585e\u5730\u7b26\u53f7", +"livre tournois sign": "\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7", +"spesmilo sign": "spesmilo\u7b26\u53f7", +"tenge sign": "\u575a\u6208\u7b26\u53f7", +"indian rupee sign": "\u5370\u5ea6\u5362\u6bd4", +"turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9", +"nordic mark sign": "\u5317\u6b27\u9a6c\u514b", +"manat sign": "\u9a6c\u7eb3\u7279\u7b26\u53f7", +"ruble sign": "\u5362\u5e03\u7b26\u53f7", +"yen character": "\u65e5\u5143\u5b57\u6837", +"yuan character": "\u4eba\u6c11\u5e01\u5143\u5b57\u6837", +"yuan character, in hong kong and taiwan": "\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09", +"yen\/yuan character variant one": "\u5143\u5b57\u6837\uff08\u5927\u5199\uff09", +"Loading emoticons...": "\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7...", +"Could not load emoticons": "\u4e0d\u80fd\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7", +"People": "\u4eba\u7c7b", +"Animals and Nature": "\u52a8\u7269\u548c\u81ea\u7136", +"Food and Drink": "\u98df\u7269\u548c\u996e\u54c1", +"Activity": "\u6d3b\u52a8", +"Travel and Places": "\u65c5\u6e38\u548c\u5730\u70b9", +"Objects": "\u7269\u4ef6", +"Flags": "\u65d7\u5e1c", +"Characters": "\u5b57\u7b26", +"Characters (no spaces)": "\u5b57\u7b26(\u65e0\u7a7a\u683c)", +"{0} characters": "{0} \u4e2a\u5b57\u7b26", +"Error: Form submit field collision.": "\u9519\u8bef: \u8868\u5355\u63d0\u4ea4\u5b57\u6bb5\u51b2\u7a81\u3002", +"Error: No form element found.": "\u9519\u8bef: \u6ca1\u6709\u8868\u5355\u63a7\u4ef6\u3002", +"Update": "\u66f4\u65b0", +"Color swatch": "\u989c\u8272\u6837\u672c", +"Turquoise": "\u9752\u7eff\u8272", +"Green": "\u7eff\u8272", +"Blue": "\u84dd\u8272", +"Purple": "\u7d2b\u8272", +"Navy Blue": "\u6d77\u519b\u84dd", +"Dark Turquoise": "\u6df1\u84dd\u7eff\u8272", +"Dark Green": "\u6df1\u7eff\u8272", +"Medium Blue": "\u4e2d\u84dd\u8272", +"Medium Purple": "\u4e2d\u7d2b\u8272", +"Midnight Blue": "\u6df1\u84dd\u8272", +"Yellow": "\u9ec4\u8272", +"Orange": "\u6a59\u8272", +"Red": "\u7ea2\u8272", +"Light Gray": "\u6d45\u7070\u8272", +"Gray": "\u7070\u8272", +"Dark Yellow": "\u6697\u9ec4\u8272", +"Dark Orange": "\u6df1\u6a59\u8272", +"Dark Red": "\u6df1\u7ea2\u8272", +"Medium Gray": "\u4e2d\u7070\u8272", +"Dark Gray": "\u6df1\u7070\u8272", +"Light Green": "\u6d45\u7eff\u8272", +"Light Yellow": "\u6d45\u9ec4\u8272", +"Light Red": "\u6d45\u7ea2\u8272", +"Light Purple": "\u6d45\u7d2b\u8272", +"Light Blue": "\u6d45\u84dd\u8272", +"Dark Purple": "\u6df1\u7d2b\u8272", +"Dark Blue": "\u6df1\u84dd\u8272", +"Black": "\u9ed1\u8272", +"White": "\u767d\u8272", +"Switch to or from fullscreen mode": "\u5207\u6362\u5168\u5c4f\u6a21\u5f0f", +"Open help dialog": "\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846", +"history": "\u5386\u53f2", +"styles": "\u6837\u5f0f", +"formatting": "\u683c\u5f0f\u5316", +"alignment": "\u5bf9\u9f50", +"indentation": "\u7f29\u8fdb", +"Font": "\u5b57\u4f53", +"Size": "\u5b57\u53f7", +"More...": "\u66f4\u591a...", +"Select...": "\u9009\u62e9...", +"Preferences": "\u9996\u9009\u9879", +"Yes": "\u662f", +"No": "\u5426", +"Keyboard Navigation": "\u952e\u76d8\u6307\u5f15", +"Version": "\u7248\u672c", +"Code view": "\u4ee3\u7801\u89c6\u56fe", +"Open popup menu for split buttons": "\u6253\u5f00\u5f39\u51fa\u5f0f\u83dc\u5355\uff0c\u7528\u4e8e\u62c6\u5206\u6309\u94ae", +"List Properties": "\u5217\u8868\u5c5e\u6027", +"List properties...": "\u6807\u9898\u5b57\u4f53\u5c5e\u6027", +"Start list at number": "\u4ee5\u6570\u5b57\u5f00\u59cb\u5217\u8868", +"Line height": "\u884c\u9ad8", +"comments": "\u5907\u6ce8", +"Format Painter": "\u683c\u5f0f\u5237", +"Insert\/edit iframe": "\u63d2\u5165\/\u7f16\u8f91\u6846\u67b6", +"Capitalization": "\u5927\u5199", +"lowercase": "\u5c0f\u5199", +"UPPERCASE": "\u5927\u5199", +"Title Case": "\u9996\u5b57\u6bcd\u5927\u5199", +"permanent pen": "\u8bb0\u53f7\u7b14", +"Permanent Pen Properties": "\u6c38\u4e45\u7b14\u5c5e\u6027", +"Permanent pen properties...": "\u6c38\u4e45\u7b14\u5c5e\u6027...", +"case change": "\u6848\u4f8b\u66f4\u6539", +"page embed": "\u9875\u9762\u5d4c\u5165", +"Advanced sort...": "\u9ad8\u7ea7\u6392\u5e8f...", +"Advanced Sort": "\u9ad8\u7ea7\u6392\u5e8f", +"Sort table by column ascending": "\u6309\u5217\u5347\u5e8f\u8868", +"Sort table by column descending": "\u6309\u5217\u964d\u5e8f\u8868", +"Sort": "\u6392\u5e8f", +"Order": "\u6392\u5e8f", +"Sort by": "\u6392\u5e8f\u65b9\u5f0f", +"Ascending": "\u5347\u5e8f", +"Descending": "\u964d\u5e8f", +"Column {0}": "\u5217{0}", +"Row {0}": "\u884c{0}", +"Spellcheck...": "\u62fc\u5199\u68c0\u67e5...", +"Misspelled word": "\u62fc\u5199\u9519\u8bef\u7684\u5355\u8bcd", +"Suggestions": "\u5efa\u8bae", +"Change": "\u66f4\u6539", +"Finding word suggestions": "\u67e5\u627e\u5355\u8bcd\u5efa\u8bae", +"Success": "\u6210\u529f", +"Repair": "\u4fee\u590d", +"Issue {0} of {1}": "\u5171\u8ba1{1}\u95ee\u9898{0}", +"Images must be marked as decorative or have an alternative text description": "\u56fe\u50cf\u5fc5\u987b\u6807\u8bb0\u4e3a\u88c5\u9970\u6027\u6216\u5177\u6709\u66ff\u4ee3\u6587\u672c\u63cf\u8ff0", +"Images must have an alternative text description. Decorative images are not allowed.": "\u56fe\u50cf\u5fc5\u987b\u5177\u6709\u66ff\u4ee3\u6587\u672c\u63cf\u8ff0\u3002\u4e0d\u5141\u8bb8\u4f7f\u7528\u88c5\u9970\u56fe\u50cf\u3002", +"Or provide alternative text:": "\u6216\u63d0\u4f9b\u5907\u9009\u6587\u672c\uff1a", +"Make image decorative:": "\u4f7f\u56fe\u50cf\u88c5\u9970\uff1a", +"ID attribute must be unique": "ID \u5c5e\u6027\u5fc5\u987b\u662f\u552f\u4e00\u7684", +"Make ID unique": "\u4f7f ID \u72ec\u4e00\u65e0\u4e8c", +"Keep this ID and remove all others": "\u4fdd\u7559\u6b64 ID \u5e76\u5220\u9664\u6240\u6709\u5176\u4ed6", +"Remove this ID": "\u5220\u9664\u6b64 ID", +"Remove all IDs": "\u6e05\u9664\u5168\u90e8IDs", +"Checklist": "\u6e05\u5355", +"Anchor": "\u951a\u70b9", +"Special character": "\u7279\u6b8a\u7b26\u53f7", +"Code sample": "\u4ee3\u7801\u793a\u4f8b", +"Color": "\u989c\u8272", +"Document properties": "\u6587\u6863\u5c5e\u6027", +"Image description": "\u56fe\u7247\u63cf\u8ff0", +"Image": "\u56fe\u7247", +"Insert link": "\u63d2\u5165\u94fe\u63a5", +"Target": "\u6253\u5f00\u65b9\u5f0f", +"Link": "\u94fe\u63a5", +"Poster": "\u5c01\u9762", +"Media": "\u5a92\u4f53", +"Print": "\u6253\u5370", +"Prev": "\u4e0a\u4e00\u4e2a", +"Find and replace": "\u67e5\u627e\u548c\u66ff\u6362", +"Whole words": "\u5168\u5b57\u5339\u914d", +"Insert template": "\u63d2\u5165\u6a21\u677f" +}); \ No newline at end of file diff --git a/public/tinymce/langs/zh_TW.js b/public/tinymce/langs/zh_TW.js new file mode 100644 index 0000000..1987486 --- /dev/null +++ b/public/tinymce/langs/zh_TW.js @@ -0,0 +1,419 @@ +tinymce.addI18n('zh_TW',{ +"Redo": "\u91cd\u505a", +"Undo": "\u64a4\u92b7", +"Cut": "\u526a\u4e0b", +"Copy": "\u8907\u88fd", +"Paste": "\u8cbc\u4e0a", +"Select all": "\u5168\u9078", +"New document": "\u65b0\u6587\u4ef6", +"Ok": "\u78ba\u5b9a", +"Cancel": "\u53d6\u6d88", +"Visual aids": "\u5c0f\u5e6b\u624b", +"Bold": "\u7c97\u9ad4", +"Italic": "\u659c\u9ad4", +"Underline": "\u4e0b\u5283\u7dda", +"Strikethrough": "\u522a\u9664\u7dda", +"Superscript": "\u4e0a\u6a19", +"Subscript": "\u4e0b\u6a19", +"Clear formatting": "\u6e05\u9664\u683c\u5f0f", +"Align left": "\u5de6\u908a\u5c0d\u9f4a", +"Align center": "\u4e2d\u9593\u5c0d\u9f4a", +"Align right": "\u53f3\u908a\u5c0d\u9f4a", +"Justify": "\u5de6\u53f3\u5c0d\u9f4a", +"Bullet list": "\u9805\u76ee\u6e05\u55ae", +"Numbered list": "\u6578\u5b57\u6e05\u55ae", +"Decrease indent": "\u6e1b\u5c11\u7e2e\u6392", +"Increase indent": "\u589e\u52a0\u7e2e\u6392", +"Close": "\u95dc\u9589", +"Formats": "\u683c\u5f0f", +"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u60a8\u7684\u700f\u89bd\u5668\u4e0d\u652f\u63f4\u5b58\u53d6\u526a\u8cbc\u7c3f\uff0c\u53ef\u4ee5\u4f7f\u7528\u5feb\u901f\u9375 Ctrl + X\/C\/V \u4ee3\u66ff\u526a\u4e0b\u3001\u8907\u88fd\u8207\u8cbc\u4e0a\u3002", +"Headers": "\u6a19\u984c", +"Header 1": "\u6a19\u984c 1", +"Header 2": "\u6a19\u984c 2", +"Header 3": "\u6a19\u984c 3", +"Header 4": "\u6a19\u984c 4", +"Header 5": "\u6a19\u984c 5", +"Header 6": "\u6a19\u984c 6", +"Headings": "\u6a19\u984c", +"Heading 1": "\u6a19\u984c1", +"Heading 2": "\u6a19\u984c2", +"Heading 3": "\u6a19\u984c3", +"Heading 4": "\u6a19\u984c4", +"Heading 5": "\u6a19\u984c5", +"Heading 6": "\u6a19\u984c6", +"Preformatted": "\u9810\u5148\u683c\u5f0f\u5316\u7684", +"Div": "Div", +"Pre": "Pre", +"Code": "\u4ee3\u78bc", +"Paragraph": "\u6bb5\u843d", +"Blockquote": "\u5f15\u6587\u5340\u584a", +"Inline": "\u5167\u806f", +"Blocks": "\u57fa\u584a", +"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u76ee\u524d\u5c07\u4ee5\u7d14\u6587\u5b57\u7684\u6a21\u5f0f\u8cbc\u4e0a\uff0c\u60a8\u53ef\u4ee5\u518d\u9ede\u9078\u4e00\u6b21\u53d6\u6d88\u3002", +"Fonts": "\u5b57\u578b", +"Font Sizes": "\u5b57\u578b\u5927\u5c0f", +"Class": "\u985e\u578b", +"Browse for an image": "\u5f9e\u5716\u7247\u4e2d\u700f\u89bd", +"OR": "\u6216", +"Drop an image here": "\u62d6\u66f3\u5716\u7247\u81f3\u6b64", +"Upload": "\u4e0a\u50b3", +"Block": "\u5340\u584a", +"Align": "\u5c0d\u9f4a", +"Default": "\u9810\u8a2d", +"Circle": "\u7a7a\u5fc3\u5713", +"Disc": "\u5be6\u5fc3\u5713", +"Square": "\u6b63\u65b9\u5f62", +"Lower Alpha": "\u5c0f\u5beb\u82f1\u6587\u5b57\u6bcd", +"Lower Greek": "\u5e0c\u81d8\u5b57\u6bcd", +"Lower Roman": "\u5c0f\u5beb\u7f85\u99ac\u6578\u5b57", +"Upper Alpha": "\u5927\u5beb\u82f1\u6587\u5b57\u6bcd", +"Upper Roman": "\u5927\u5beb\u7f85\u99ac\u6578\u5b57", +"Anchor...": "\u9328\u9ede...", +"Name": "\u540d\u7a31", +"Id": "Id", +"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "Id\u61c9\u4ee5\u5b57\u6bcd\u958b\u982d\uff0c\u5f8c\u9762\u63a5\u8457\u5b57\u6bcd\uff0c\u6578\u5b57\uff0c\u7834\u6298\u865f\uff0c\u9ede\u6578\uff0c\u5192\u865f\u6216\u4e0b\u5283\u7dda\u3002", +"You have unsaved changes are you sure you want to navigate away?": "\u7de8\u8f2f\u5c1a\u672a\u88ab\u5132\u5b58\uff0c\u4f60\u78ba\u5b9a\u8981\u96e2\u958b\uff1f", +"Restore last draft": "\u8f09\u5165\u4e0a\u4e00\u6b21\u7de8\u8f2f\u7684\u8349\u7a3f", +"Special character...": "\u7279\u6b8a\u5b57\u5143......", +"Source code": "\u539f\u59cb\u78bc", +"Insert\/Edit code sample": "\u63d2\u5165\/\u7de8\u8f2f \u7a0b\u5f0f\u78bc\u7bc4\u4f8b", +"Language": "\u8a9e\u8a00", +"Code sample...": "\u7a0b\u5f0f\u78bc\u7bc4\u4f8b...", +"Color Picker": "\u9078\u8272\u5668", +"R": "\u7d05", +"G": "\u7da0", +"B": "\u85cd", +"Left to right": "\u5f9e\u5de6\u5230\u53f3", +"Right to left": "\u5f9e\u53f3\u5230\u5de6", +"Emoticons...": "\u8868\u60c5\u7b26\u865f\u2026", +"Metadata and Document Properties": "\u5f8c\u8a2d\u8cc7\u6599\u8207\u6587\u4ef6\u5c6c\u6027", +"Title": "\u6a19\u984c", +"Keywords": "\u95dc\u9375\u5b57", +"Description": "\u63cf\u8ff0", +"Robots": "\u6a5f\u5668\u4eba", +"Author": "\u4f5c\u8005", +"Encoding": "\u7de8\u78bc", +"Fullscreen": "\u5168\u87a2\u5e55", +"Action": "\u52d5\u4f5c", +"Shortcut": "\u5feb\u901f\u9375", +"Help": "\u5e6b\u52a9", +"Address": "\u5730\u5740", +"Focus to menubar": "\u8df3\u81f3\u9078\u55ae\u5217", +"Focus to toolbar": "\u8df3\u81f3\u5de5\u5177\u5217", +"Focus to element path": "\u8df3\u81f3HTML\u5143\u7d20\u5217", +"Focus to contextual toolbar": "\u8df3\u81f3\u5feb\u6377\u9078\u55ae", +"Insert link (if link plugin activated)": "\u65b0\u589e\u6377\u5f91 (\u6377\u5f91\u5916\u639b\u555f\u7528\u6642)", +"Save (if save plugin activated)": "\u5132\u5b58 (\u5132\u5b58\u5916\u639b\u555f\u7528\u6642)", +"Find (if searchreplace plugin activated)": "\u5c0b\u627e (\u5c0b\u627e\u53d6\u4ee3\u5916\u639b\u555f\u7528\u6642)", +"Plugins installed ({0}):": "({0}) \u500b\u5916\u639b\u5df2\u5b89\u88dd\uff1a", +"Premium plugins:": "\u52a0\u503c\u5916\u639b\uff1a", +"Learn more...": "\u4e86\u89e3\u66f4\u591a...", +"You are using {0}": "\u60a8\u6b63\u5728\u4f7f\u7528 {0}", +"Plugins": "\u5916\u639b", +"Handy Shortcuts": "\u5feb\u901f\u9375", +"Horizontal line": "\u6c34\u5e73\u7dda", +"Insert\/edit image": "\u63d2\u5165\/\u7de8\u8f2f \u5716\u7247", +"Image description": "\u5716\u7247\u63cf\u8ff0", +"Source": "\u5716\u7247\u7db2\u5740", +"Dimensions": "\u5c3a\u5bf8", +"Constrain proportions": "\u7b49\u6bd4\u4f8b\u7e2e\u653e", +"General": "\u4e00\u822c", +"Advanced": "\u9032\u968e", +"Style": "\u6a23\u5f0f", +"Vertical space": "\u9ad8\u5ea6", +"Horizontal space": "\u5bec\u5ea6", +"Border": "\u908a\u6846", +"Insert image": "\u63d2\u5165\u5716\u7247", +"Image...": "\u5716\u7247......", +"Image list": "\u5716\u7247\u6e05\u55ae", +"Rotate counterclockwise": "\u9006\u6642\u91dd\u65cb\u8f49", +"Rotate clockwise": "\u9806\u6642\u91dd\u65cb\u8f49", +"Flip vertically": "\u5782\u76f4\u7ffb\u8f49", +"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f49", +"Edit image": "\u7de8\u8f2f\u5716\u7247", +"Image options": "\u5716\u7247\u9078\u9805", +"Zoom in": "\u653e\u5927", +"Zoom out": "\u7e2e\u5c0f", +"Crop": "\u88c1\u526a", +"Resize": "\u8abf\u6574\u5927\u5c0f", +"Orientation": "\u65b9\u5411", +"Brightness": "\u4eae\u5ea6", +"Sharpen": "\u92b3\u5316", +"Contrast": "\u5c0d\u6bd4", +"Color levels": "\u984f\u8272\u5c64\u6b21", +"Gamma": "\u4f3d\u99ac\u503c", +"Invert": "\u53cd\u8f49", +"Apply": "\u61c9\u7528", +"Back": "\u5f8c\u9000", +"Insert date\/time": "\u63d2\u5165 \u65e5\u671f\/\u6642\u9593", +"Date\/time": "\u65e5\u671f\/\u6642\u9593", +"Insert\/Edit Link": "\u63d2\u5165\/\u7de8\u8f2f\u9023\u7d50", +"Insert\/edit link": "\u63d2\u5165\/\u7de8\u8f2f\u9023\u7d50", +"Text to display": "\u986f\u793a\u6587\u5b57", +"Url": "\u7db2\u5740", +"Open link in...": "\u958b\u555f\u9023\u7d50\u65bc...", +"Current window": "\u76ee\u524d\u8996\u7a97", +"None": "\u7121", +"New window": "\u53e6\u958b\u8996\u7a97", +"Remove link": "\u79fb\u9664\u9023\u7d50", +"Anchors": "\u52a0\u5165\u9328\u9ede", +"Link...": "\u9023\u7d50...", +"Paste or type a link": "\u8cbc\u4e0a\u6216\u8f38\u5165\u9023\u7d50", +"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5beb\u7684URL\u70ba\u96fb\u5b50\u90f5\u4ef6\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7db4\u55ce\uff1f", +"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5beb\u7684URL\u5c6c\u65bc\u5916\u90e8\u93c8\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7db4\u55ce\uff1f", +"Link list": "\u9023\u7d50\u6e05\u55ae", +"Insert video": "\u63d2\u5165\u5f71\u97f3", +"Insert\/edit video": "\u63d2\u4ef6\/\u7de8\u8f2f \u5f71\u97f3", +"Insert\/edit media": "\u63d2\u5165\/\u7de8\u8f2f \u5a92\u9ad4", +"Alternative source": "\u66ff\u4ee3\u5f71\u97f3", +"Alternative source URL": "\u66ff\u4ee3\u4f86\u6e90URL", +"Media poster (Image URL)": "\u5a92\u9ad4\u6d77\u5831\uff08\u5f71\u50cfImage URL\uff09", +"Paste your embed code below:": "\u8acb\u5c07\u60a8\u7684\u5d4c\u5165\u5f0f\u7a0b\u5f0f\u78bc\u8cbc\u5728\u4e0b\u9762:", +"Embed": "\u5d4c\u5165\u78bc", +"Media...": "\u5a92\u9ad4...", +"Nonbreaking space": "\u4e0d\u5206\u884c\u7684\u7a7a\u683c", +"Page break": "\u5206\u9801", +"Paste as text": "\u4ee5\u7d14\u6587\u5b57\u8cbc\u4e0a", +"Preview": "\u9810\u89bd", +"Print...": "\u5217\u5370...", +"Save": "\u5132\u5b58", +"Find": "\u641c\u5c0b", +"Replace with": "\u66f4\u63db", +"Replace": "\u66ff\u63db", +"Replace all": "\u66ff\u63db\u5168\u90e8", +"Previous": "\u4e0a\u4e00\u500b", +"Next": "\u4e0b\u4e00\u500b", +"Find and replace...": "\u5c0b\u627e\u53ca\u53d6\u4ee3...", +"Could not find the specified string.": "\u7121\u6cd5\u67e5\u8a62\u5230\u6b64\u7279\u5b9a\u5b57\u4e32", +"Match case": "\u76f8\u5339\u914d\u6848\u4ef6", +"Find whole words only": "\u50c5\u627e\u51fa\u5b8c\u6574\u5b57\u532f", +"Spell check": "\u62fc\u5beb\u6aa2\u67e5", +"Ignore": "\u5ffd\u7565", +"Ignore all": "\u5ffd\u7565\u6240\u6709", +"Finish": "\u5b8c\u6210", +"Add to Dictionary": "\u52a0\u5165\u5b57\u5178\u4e2d", +"Insert table": "\u63d2\u5165\u8868\u683c", +"Table properties": "\u8868\u683c\u5c6c\u6027", +"Delete table": "\u522a\u9664\u8868\u683c", +"Cell": "\u5132\u5b58\u683c", +"Row": "\u5217", +"Column": "\u884c", +"Cell properties": "\u5132\u5b58\u683c\u5c6c\u6027", +"Merge cells": "\u5408\u4f75\u5132\u5b58\u683c", +"Split cell": "\u5206\u5272\u5132\u5b58\u683c", +"Insert row before": "\u63d2\u5165\u5217\u5728...\u4e4b\u524d", +"Insert row after": "\u63d2\u5165\u5217\u5728...\u4e4b\u5f8c", +"Delete row": "\u522a\u9664\u5217", +"Row properties": "\u5217\u5c6c\u6027", +"Cut row": "\u526a\u4e0b\u5217", +"Copy row": "\u8907\u88fd\u5217", +"Paste row before": "\u8cbc\u4e0a\u5217\u5728...\u4e4b\u524d", +"Paste row after": "\u8cbc\u4e0a\u5217\u5728...\u4e4b\u5f8c", +"Insert column before": "\u63d2\u5165\u6b04\u4f4d\u5728...\u4e4b\u524d", +"Insert column after": "\u63d2\u5165\u6b04\u4f4d\u5728...\u4e4b\u5f8c", +"Delete column": "\u522a\u9664\u884c", +"Cols": "\u6b04\u4f4d\u6bb5", +"Rows": "\u5217", +"Width": "\u5bec\u5ea6", +"Height": "\u9ad8\u5ea6", +"Cell spacing": "\u5132\u5b58\u683c\u5f97\u9593\u8ddd", +"Cell padding": "\u5132\u5b58\u683c\u7684\u908a\u8ddd", +"Show caption": "\u986f\u793a\u6a19\u984c", +"Left": "\u5de6\u908a", +"Center": "\u4e2d\u9593", +"Right": "\u53f3\u908a", +"Cell type": "\u5132\u5b58\u683c\u7684\u985e\u578b", +"Scope": "\u7bc4\u570d", +"Alignment": "\u5c0d\u9f4a", +"H Align": "\u6c34\u5e73\u4f4d\u7f6e", +"V Align": "\u5782\u76f4\u4f4d\u7f6e", +"Top": "\u7f6e\u9802", +"Middle": "\u7f6e\u4e2d", +"Bottom": "\u7f6e\u5e95", +"Header cell": "\u6a19\u982d\u5132\u5b58\u683c", +"Row group": "\u5217\u7fa4\u7d44", +"Column group": "\u6b04\u4f4d\u7fa4\u7d44", +"Row type": "\u884c\u7684\u985e\u578b", +"Header": "\u6a19\u982d", +"Body": "\u4e3b\u9ad4", +"Footer": "\u9801\u5c3e", +"Border color": "\u908a\u6846\u984f\u8272", +"Insert template...": "\u63d2\u5165\u6a23\u7248...", +"Templates": "\u6a23\u7248", +"Template": "\u6a23\u677f", +"Text color": "\u6587\u5b57\u984f\u8272", +"Background color": "\u80cc\u666f\u984f\u8272", +"Custom...": "\u81ea\u8a02", +"Custom color": "\u81ea\u8a02\u984f\u8272", +"No color": "No color", +"Remove color": "\u79fb\u9664\u984f\u8272", +"Table of Contents": "\u76ee\u9304", +"Show blocks": "\u986f\u793a\u5340\u584a\u8cc7\u8a0a", +"Show invisible characters": "\u986f\u793a\u96b1\u85cf\u5b57\u5143", +"Word count": "\u8a08\u7b97\u5b57\u6578", +"Count": "\u8a08\u7b97", +"Document": "\u6587\u4ef6", +"Selection": "\u9078\u9805", +"Words": "\u5b57\u6578", +"Words: {0}": "\u5b57\u6578\uff1a{0}", +"{0} words": "{0} \u5b57\u5143", +"File": "\u6a94\u6848", +"Edit": "\u7de8\u8f2f", +"Insert": "\u63d2\u5165", +"View": "\u6aa2\u8996", +"Format": "\u683c\u5f0f", +"Table": "\u8868\u683c", +"Tools": "\u5de5\u5177", +"Powered by {0}": "\u7531 {0} \u63d0\u4f9b", +"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u8c50\u5bcc\u7684\u6587\u672c\u5340\u57df\u3002\u6309ALT-F9\u524d\u5f80\u4e3b\u9078\u55ae\u3002\u6309ALT-F10\u547c\u53eb\u5de5\u5177\u6b04\u3002\u6309ALT-0\u5c0b\u6c42\u5e6b\u52a9", +"Image title": "\u5716\u7247\u6a19\u984c", +"Border width": "\u6846\u7dda\u5bec\u5ea6", +"Border style": "\u6846\u7dda\u6a23\u5f0f", +"Error": "\u932f\u8aa4", +"Warn": "\u8b66\u544a", +"Valid": "\u6709\u6548", +"To open the popup, press Shift+Enter": "\u8981\u958b\u555f\u5f48\u51fa\u8996\u7a97\uff0c\u8acb\u6309Shift+Enter", +"Rich Text Area. Press ALT-0 for help.": "\u5bcc\u6587\u672c\u5340\u57df\u3002\u8acb\u6309ALT-0\u5c0b\u6c42\u5354\u52a9\u3002", +"System Font": "\u7cfb\u7d71\u5b57\u578b", +"Failed to upload image: {0}": "\u7121\u6cd5\u4e0a\u50b3\u5f71\u50cf\uff1a{0}", +"Failed to load plugin: {0} from url {1}": "\u7121\u6cd5\u4e0a\u50b3\u63d2\u4ef6\uff1a{0}\u81eaurl{1}", +"Failed to load plugin url: {0}": "\u7121\u6cd5\u4e0a\u50b3\u63d2\u4ef6\uff1a{0}", +"Failed to initialize plugin: {0}": "\u7121\u6cd5\u555f\u52d5\u63d2\u4ef6\uff1a{0}", +"example": "\u7bc4\u4f8b", +"Search": "\u641c\u7d22", +"All": "\u5168\u90e8", +"Currency": "\u8ca8\u5e63", +"Text": "\u6587\u672c", +"Quotations": "\u5f15\u7528", +"Mathematical": "\u6578\u5b78", +"Extended Latin": "\u62c9\u4e01\u5b57\u6bcd\u64f4\u5145", +"Symbols": "\u7b26\u865f", +"Arrows": "\u7bad\u982d", +"User Defined": "\u4f7f\u7528\u8005\u5df2\u5b9a\u7fa9", +"dollar sign": "\u7f8e\u5143\u7b26\u865f", +"currency sign": "\u8ca8\u5e63\u7b26\u865f", +"euro-currency sign": "\u6b50\u5143\u7b26\u865f", +"colon sign": "\u79d1\u6717\u7b26\u865f", +"cruzeiro sign": "\u514b\u9b6f\u8cfd\u7f85\u7b26\u865f", +"french franc sign": "\u6cd5\u6717\u7b26\u865f", +"lira sign": "\u91cc\u62c9\u7b26\u865f", +"mill sign": "\u6587\u7b26\u865f", +"naira sign": "\u5948\u62c9\u7b26\u865f", +"peseta sign": "\u6bd4\u585e\u5854\u7b26\u865f", +"rupee sign": "\u76e7\u6bd4\u7b26\u865f", +"won sign": "\u97d3\u571c\u7b26\u865f", +"new sheqel sign": "\u65b0\u8b1d\u514b\u723e\u7b26\u865f", +"dong sign": "\u8d8a\u5357\u76fe\u7b26\u865f", +"kip sign": "\u8001\u64be\u5e63\u7b26\u865f", +"tugrik sign": "\u8499\u53e4\u5e63\u7b26\u865f", +"drachma sign": "\u5fb7\u514b\u62c9\u99ac\u7b26\u865f", +"german penny symbol": "\u5fb7\u570b\u5206\u7b26\u865f", +"peso sign": "\u62ab\u7d22\u7b26\u865f", +"guarani sign": "\u5df4\u62c9\u572d\u5e63\u7b26\u865f", +"austral sign": "\u963f\u6839\u5ef7\u5e63\u7b26\u865f", +"hryvnia sign": "\u70cf\u514b\u862d\u5e63\u7b26\u865f", +"cedi sign": "\u8fe6\u7d0d\u5e63\u7b26\u865f", +"livre tournois sign": "\u91cc\u5f17\u723e\u7b26\u865f", +"spesmilo sign": "\u570b\u969b\u5e63\u7b26\u865f", +"tenge sign": "\u54c8\u85a9\u514b\u5e63\u7b26\u865f", +"indian rupee sign": "\u5370\u5ea6\u76e7\u6bd4\u7b26\u865f", +"turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9\u7b26\u865f", +"nordic mark sign": "\u5317\u6b50\u99ac\u514b\u7b26\u865f", +"manat sign": "\u4e9e\u585e\u62dc\u7136\u5e63\u7b26\u865f", +"ruble sign": "\u76e7\u5e03\u7b26\u865f", +"yen character": "\u65e5\u5713\u7b26\u865f", +"yuan character": "\u4eba\u6c11\u5e63\u7b26\u865f", +"yuan character, in hong kong and taiwan": "\u6e2f\u5143\u8207\u53f0\u5e63\u7b26\u865f", +"yen\/yuan character variant one": "\u65e5\u5713\/\u4eba\u6c11\u5e63\u7b26\u865f\u8b8a\u5316\u578b", +"Loading emoticons...": "\u8f09\u5165\u8868\u60c5\u7b26\u865f\u2026", +"Could not load emoticons": "\u7121\u6cd5\u8f09\u5165\u8868\u60c5\u7b26\u865f", +"People": "\u4eba", +"Animals and Nature": "\u52d5\u7269\u8207\u81ea\u7136", +"Food and Drink": "\u98f2\u98df", +"Activity": "\u6d3b\u52d5", +"Travel and Places": "\u65c5\u884c\u8207\u5730\u9ede", +"Objects": "\u7269\u4ef6", +"Flags": "\u65d7\u6a19", +"Characters": "\u5b57\u5143", +"Characters (no spaces)": "\u5b57\u5143\uff08\u7121\u7a7a\u683c\uff09", +"{0} characters": "{0}\u5b57\u5143", +"Error: Form submit field collision.": "\u932f\u8aa4\uff1a\u8868\u683c\u905e\u4ea4\u6b04\u4f4d\u885d\u7a81\u3002", +"Error: No form element found.": "\u932f\u8aa4\uff1a\u627e\u4e0d\u5230\u8868\u683c\u5143\u7d20\u3002", +"Update": "\u66f4\u65b0", +"Color swatch": "\u8272\u5f69\u6a23\u672c", +"Turquoise": "\u571f\u8033\u5176\u85cd", +"Green": "\u7da0\u8272", +"Blue": "\u85cd\u8272", +"Purple": "\u7d2b\u8272", +"Navy Blue": "\u6df1\u85cd\u8272", +"Dark Turquoise": "\u6df1\u571f\u8033\u5176\u85cd", +"Dark Green": "\u6df1\u7da0\u8272", +"Medium Blue": "\u4e2d\u85cd\u8272", +"Medium Purple": "\u4e2d\u7d2b\u8272", +"Midnight Blue": "\u9ed1\u85cd\u8272", +"Yellow": "\u9ec3\u8272", +"Orange": "\u6a59\u8272", +"Red": "\u7d05\u8272", +"Light Gray": "\u6dfa\u7070\u8272", +"Gray": "\u7070\u8272", +"Dark Yellow": "\u6df1\u9ec3\u8272", +"Dark Orange": "\u6df1\u6a59\u8272", +"Dark Red": "\u6697\u7d05\u8272", +"Medium Gray": "\u4e2d\u7070\u8272", +"Dark Gray": "\u6df1\u7070\u8272", +"Light Green": "\u6de1\u7da0\u8272", +"Light Yellow": "\u6dfa\u9ec3\u8272", +"Light Red": "\u6dfa\u7d05\u8272", +"Light Purple": "\u6dfa\u7d2b\u8272", +"Light Blue": "\u6dfa\u85cd\u8272", +"Dark Purple": "\u6df1\u7d2b\u8272", +"Dark Blue": "\u6df1\u85cd\u8272", +"Black": "\u9ed1\u8272", +"White": "\u767d\u8272", +"Switch to or from fullscreen mode": "\u8f49\u63db\u81ea\/\u81f3\u5168\u87a2\u5e55\u6a21\u5f0f", +"Open help dialog": "\u958b\u555f\u5354\u52a9\u5c0d\u8a71", +"history": "\u6b77\u53f2", +"styles": "\u6a23\u5f0f", +"formatting": "\u683c\u5f0f", +"alignment": "\u5c0d\u9f4a", +"indentation": "\u7e2e\u6392", +"permanent pen": "\u6c38\u4e45\u6027\u7b46", +"comments": "\u8a3b\u89e3", +"Format Painter": "\u8907\u88fd\u683c\u5f0f", +"Insert\/edit iframe": "\u63d2\u5165\/\u7de8\u8f2fiframe", +"Capitalization": "\u5927\u5beb", +"lowercase": "\u5c0f\u5beb", +"UPPERCASE": "\u5927\u5beb", +"Title Case": "\u5b57\u9996\u5927\u5beb", +"Permanent Pen Properties": "\u6c38\u4e45\u6a19\u8a18\u5c6c\u6027", +"Permanent pen properties...": "\u6c38\u4e45\u6a19\u8a18\u5c6c\u6027......", +"Font": "\u5b57\u578b", +"Size": "\u5b57\u5f62\u5927\u5c0f", +"More...": "\u66f4\u591a\u8cc7\u8a0a......", +"Spellcheck Language": "\u62fc\u5beb\u8a9e\u8a00", +"Select...": "\u9078\u64c7......", +"Preferences": "\u9996\u9078\u9805", +"Yes": "\u662f", +"No": "\u5426", +"Keyboard Navigation": "\u9375\u76e4\u5c0e\u822a", +"Version": "\u7248\u672c", +"Anchor": "\u52a0\u5165\u9328\u9ede", +"Special character": "\u7279\u6b8a\u5b57\u5143", +"Code sample": "\u7a0b\u5f0f\u78bc\u7bc4\u4f8b", +"Color": "\u984f\u8272", +"Emoticons": "\u8868\u60c5", +"Document properties": "\u6587\u4ef6\u7684\u5c6c\u6027", +"Image": "\u5716\u7247", +"Insert link": "\u63d2\u5165\u9023\u7d50", +"Target": "\u958b\u555f\u65b9\u5f0f", +"Link": "\u9023\u7d50", +"Poster": "\u9810\u89bd\u5716\u7247", +"Media": "\u5a92\u9ad4", +"Print": "\u5217\u5370", +"Prev": "\u4e0a\u4e00\u500b", +"Find and replace": "\u5c0b\u627e\u53ca\u53d6\u4ee3", +"Whole words": "\u6574\u500b\u55ae\u5b57", +"Spellcheck": "\u62fc\u5b57\u6aa2\u67e5", +"Caption": "\u8868\u683c\u6a19\u984c", +"Insert template": "\u63d2\u5165\u6a23\u7248" +}); \ No newline at end of file diff --git a/public/tinymce/skins/content/dark/content.css b/public/tinymce/skins/content/dark/content.css new file mode 100644 index 0000000..bae7923 --- /dev/null +++ b/public/tinymce/skins/content/dark/content.css @@ -0,0 +1,72 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +body { + background-color: #2f3742; + color: #dfe0e4; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + line-height: 1.4; + margin: 1rem; +} +a { + color: #4099ff; +} +table { + border-collapse: collapse; +} +/* Apply a default padding if legacy cellpadding attribute is missing */ +table:not([cellpadding]) th, +table:not([cellpadding]) td { + padding: 0.4rem; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-width"]) th, +table[border]:not([border="0"]):not([style*="border-width"]) td { + border-width: 1px; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-style"]) th, +table[border]:not([border="0"]):not([style*="border-style"]) td { + border-style: solid; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-color"]) th, +table[border]:not([border="0"]):not([style*="border-color"]) td { + border-color: #6d737b; +} +figure { + display: table; + margin: 1rem auto; +} +figure figcaption { + color: #8a8f97; + display: block; + margin-top: 0.25rem; + text-align: center; +} +hr { + border-color: #6d737b; + border-style: solid; + border-width: 1px 0 0 0; +} +code { + background-color: #6d737b; + border-radius: 3px; + padding: 0.1rem 0.2rem; +} +.mce-content-body:not([dir=rtl]) blockquote { + border-left: 2px solid #6d737b; + margin-left: 1.5rem; + padding-left: 1rem; +} +.mce-content-body[dir=rtl] blockquote { + border-right: 2px solid #6d737b; + margin-right: 1.5rem; + padding-right: 1rem; +} diff --git a/public/tinymce/skins/content/dark/content.min.css b/public/tinymce/skins/content/dark/content.min.css new file mode 100644 index 0000000..07d40c2 --- /dev/null +++ b/public/tinymce/skins/content/dark/content.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +body{background-color:#2f3742;color:#dfe0e4;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem} diff --git a/public/tinymce/skins/content/default/content.css b/public/tinymce/skins/content/default/content.css new file mode 100644 index 0000000..dd6a5c1 --- /dev/null +++ b/public/tinymce/skins/content/default/content.css @@ -0,0 +1,67 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + line-height: 1.4; + margin: 1rem; +} +table { + border-collapse: collapse; +} +/* Apply a default padding if legacy cellpadding attribute is missing */ +table:not([cellpadding]) th, +table:not([cellpadding]) td { + padding: 0.4rem; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-width"]) th, +table[border]:not([border="0"]):not([style*="border-width"]) td { + border-width: 1px; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-style"]) th, +table[border]:not([border="0"]):not([style*="border-style"]) td { + border-style: solid; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-color"]) th, +table[border]:not([border="0"]):not([style*="border-color"]) td { + border-color: #ccc; +} +figure { + display: table; + margin: 1rem auto; +} +figure figcaption { + color: #999; + display: block; + margin-top: 0.25rem; + text-align: center; +} +hr { + border-color: #ccc; + border-style: solid; + border-width: 1px 0 0 0; +} +code { + background-color: #e8e8e8; + border-radius: 3px; + padding: 0.1rem 0.2rem; +} +.mce-content-body:not([dir=rtl]) blockquote { + border-left: 2px solid #ccc; + margin-left: 1.5rem; + padding-left: 1rem; +} +.mce-content-body[dir=rtl] blockquote { + border-right: 2px solid #ccc; + margin-right: 1.5rem; + padding-right: 1rem; +} diff --git a/public/tinymce/skins/content/default/content.min.css b/public/tinymce/skins/content/default/content.min.css new file mode 100644 index 0000000..29cd987 --- /dev/null +++ b/public/tinymce/skins/content/default/content.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} diff --git a/public/tinymce/skins/content/document/content.css b/public/tinymce/skins/content/document/content.css new file mode 100644 index 0000000..75f637a --- /dev/null +++ b/public/tinymce/skins/content/document/content.css @@ -0,0 +1,72 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +@media screen { + html { + background: #f4f4f4; + min-height: 100%; + } +} +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; +} +@media screen { + body { + background-color: #fff; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.15); + box-sizing: border-box; + margin: 1rem auto 0; + max-width: 820px; + min-height: calc(100vh - 1rem); + padding: 4rem 6rem 6rem 6rem; + } +} +table { + border-collapse: collapse; +} +/* Apply a default padding if legacy cellpadding attribute is missing */ +table:not([cellpadding]) th, +table:not([cellpadding]) td { + padding: 0.4rem; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-width"]) th, +table[border]:not([border="0"]):not([style*="border-width"]) td { + border-width: 1px; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-style"]) th, +table[border]:not([border="0"]):not([style*="border-style"]) td { + border-style: solid; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-color"]) th, +table[border]:not([border="0"]):not([style*="border-color"]) td { + border-color: #ccc; +} +figure figcaption { + color: #999; + margin-top: 0.25rem; + text-align: center; +} +hr { + border-color: #ccc; + border-style: solid; + border-width: 1px 0 0 0; +} +.mce-content-body:not([dir=rtl]) blockquote { + border-left: 2px solid #ccc; + margin-left: 1.5rem; + padding-left: 1rem; +} +.mce-content-body[dir=rtl] blockquote { + border-right: 2px solid #ccc; + margin-right: 1.5rem; + padding-right: 1rem; +} diff --git a/public/tinymce/skins/content/document/content.min.css b/public/tinymce/skins/content/document/content.min.css new file mode 100644 index 0000000..a1feef4 --- /dev/null +++ b/public/tinymce/skins/content/document/content.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +@media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} diff --git a/public/tinymce/skins/content/writer/content.css b/public/tinymce/skins/content/writer/content.css new file mode 100644 index 0000000..ceee359 --- /dev/null +++ b/public/tinymce/skins/content/writer/content.css @@ -0,0 +1,68 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + line-height: 1.4; + margin: 1rem auto; + max-width: 900px; +} +table { + border-collapse: collapse; +} +/* Apply a default padding if legacy cellpadding attribute is missing */ +table:not([cellpadding]) th, +table:not([cellpadding]) td { + padding: 0.4rem; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-width"]) th, +table[border]:not([border="0"]):not([style*="border-width"]) td { + border-width: 1px; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-style"]) th, +table[border]:not([border="0"]):not([style*="border-style"]) td { + border-style: solid; +} +/* Set default table styles if a table has a positive border attribute + and no inline css */ +table[border]:not([border="0"]):not([style*="border-color"]) th, +table[border]:not([border="0"]):not([style*="border-color"]) td { + border-color: #ccc; +} +figure { + display: table; + margin: 1rem auto; +} +figure figcaption { + color: #999; + display: block; + margin-top: 0.25rem; + text-align: center; +} +hr { + border-color: #ccc; + border-style: solid; + border-width: 1px 0 0 0; +} +code { + background-color: #e8e8e8; + border-radius: 3px; + padding: 0.1rem 0.2rem; +} +.mce-content-body:not([dir=rtl]) blockquote { + border-left: 2px solid #ccc; + margin-left: 1.5rem; + padding-left: 1rem; +} +.mce-content-body[dir=rtl] blockquote { + border-right: 2px solid #ccc; + margin-right: 1.5rem; + padding-right: 1rem; +} diff --git a/public/tinymce/skins/content/writer/content.min.css b/public/tinymce/skins/content/writer/content.min.css new file mode 100644 index 0000000..0d8f5d3 --- /dev/null +++ b/public/tinymce/skins/content/writer/content.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border="0"]):not([style*=border-width]) td,table[border]:not([border="0"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border="0"]):not([style*=border-style]) td,table[border]:not([border="0"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border="0"]):not([style*=border-color]) td,table[border]:not([border="0"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem} diff --git a/public/tinymce/skins/ui/oxide-dark/content.css b/public/tinymce/skins/ui/oxide-dark/content.css new file mode 100644 index 0000000..9c0e3a8 --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/content.css @@ -0,0 +1,714 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + cursor: default; + display: inline-block; + height: 12px !important; + padding: 0 2px; + -webkit-user-modify: read-only; + -moz-user-modify: read-only; + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; + width: 8px !important; +} +.mce-content-body .mce-item-anchor[data-mce-selected] { + outline-offset: 1px; +} +.tox-comments-visible .tox-comment { + background-color: #fff0b7; +} +.tox-comments-visible .tox-comment--active { + background-color: #ffe168; +} +.tox-checklist > li:not(.tox-checklist--hidden) { + list-style: none; + margin: 0.25em 0; +} +.tox-checklist > li:not(.tox-checklist--hidden)::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%236d737b%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); + cursor: pointer; + height: 1em; + margin-left: -1.5em; + margin-top: 0.125em; + position: absolute; + width: 1em; +} +.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); +} +[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before { + margin-left: 0; + margin-right: -1.5em; +} +/* stylelint-disable */ +/* http://prismjs.com/ */ +/** + * Dracula Theme originally by Zeno Rocha [@zenorocha] + * https://draculatheme.com/ + * + * Ported for PrismJS by Albert Vallverdu [@byverdu] + */ +code[class*="language-"], +pre[class*="language-"] { + color: #f8f8f2; + background: none; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + 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; + tab-size: 4; + -webkit-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; + border-radius: 0.3em; +} +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #282a36; +} +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #6272a4; +} +.token.punctuation { + color: #f8f8f2; +} +.namespace { + opacity: 0.7; +} +.token.property, +.token.tag, +.token.constant, +.token.symbol, +.token.deleted { + color: #ff79c6; +} +.token.boolean, +.token.number { + color: #bd93f9; +} +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #50fa7b; +} +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: #f8f8f2; +} +.token.atrule, +.token.attr-value, +.token.function, +.token.class-name { + color: #f1fa8c; +} +.token.keyword { + color: #8be9fd; +} +.token.regex, +.token.important { + color: #ffb86c; +} +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} +.token.entity { + cursor: help; +} +/* stylelint-enable */ +.mce-content-body { + overflow-wrap: break-word; + word-wrap: break-word; +} +.mce-content-body .mce-visual-caret { + background-color: black; + background-color: currentColor; + position: absolute; +} +.mce-content-body .mce-visual-caret-hidden { + display: none; +} +.mce-content-body *[data-mce-caret] { + left: -1000px; + margin: 0; + padding: 0; + position: absolute; + right: auto; + top: 0; +} +.mce-content-body .mce-offscreen-selection { + left: -2000000px; + max-width: 1000000px; + position: absolute; +} +.mce-content-body *[contentEditable=false] { + cursor: default; +} +.mce-content-body *[contentEditable=true] { + cursor: text; +} +.tox-cursor-format-painter { + cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default; +} +.mce-content-body figure.align-left { + float: left; +} +.mce-content-body figure.align-right { + float: right; +} +.mce-content-body figure.image.align-center { + display: table; + margin-left: auto; + margin-right: auto; +} +.mce-preview-object { + border: 1px solid gray; + display: inline-block; + line-height: 0; + margin: 0 2px 0 2px; + position: relative; +} +.mce-preview-object .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-preview-object[data-mce-selected="2"] .mce-shim { + display: none; +} +.mce-object { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + border: 1px dashed #aaa; +} +.mce-pagebreak { + border: 1px dashed #aaa; + cursor: default; + display: block; + height: 5px; + margin-top: 15px; + page-break-before: always; + width: 100%; +} +@media print { + .mce-pagebreak { + border: 0; + } +} +.tiny-pageembed .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.tiny-pageembed[data-mce-selected="2"] .mce-shim { + display: none; +} +.tiny-pageembed { + display: inline-block; + position: relative; +} +.tiny-pageembed--21by9, +.tiny-pageembed--16by9, +.tiny-pageembed--4by3, +.tiny-pageembed--1by1 { + display: block; + overflow: hidden; + padding: 0; + position: relative; + width: 100%; +} +.tiny-pageembed--21by9 { + padding-top: 42.857143%; +} +.tiny-pageembed--16by9 { + padding-top: 56.25%; +} +.tiny-pageembed--4by3 { + padding-top: 75%; +} +.tiny-pageembed--1by1 { + padding-top: 100%; +} +.tiny-pageembed--21by9 iframe, +.tiny-pageembed--16by9 iframe, +.tiny-pageembed--4by3 iframe, +.tiny-pageembed--1by1 iframe { + border: 0; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-content-body[data-mce-placeholder] { + position: relative; +} +.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before { + color: rgba(34, 47, 62, 0.7); + content: attr(data-mce-placeholder); + position: absolute; +} +.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before { + left: 1px; +} +.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before { + right: 1px; +} +.mce-content-body div.mce-resizehandle { + background-color: #4099ff; + border-color: #4099ff; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + height: 10px; + position: absolute; + width: 10px; + z-index: 1298; +} +.mce-content-body div.mce-resizehandle:hover { + background-color: #4099ff; +} +.mce-content-body div.mce-resizehandle:nth-of-type(1) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(2) { + cursor: nesw-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(3) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(4) { + cursor: nesw-resize; +} +.mce-content-body .mce-resize-backdrop { + z-index: 10000; +} +.mce-content-body .mce-clonedresizable { + cursor: default; + opacity: 0.5; + outline: 1px dashed black; + position: absolute; + z-index: 10001; +} +.mce-content-body .mce-clonedresizable.mce-resizetable-columns th, +.mce-content-body .mce-clonedresizable.mce-resizetable-columns td { + border: 0; +} +.mce-content-body .mce-resize-helper { + background: #555; + background: rgba(0, 0, 0, 0.75); + border: 1px; + border-radius: 3px; + color: white; + display: none; + font-family: sans-serif; + font-size: 12px; + line-height: 14px; + margin: 5px 10px; + padding: 5px; + position: absolute; + white-space: nowrap; + z-index: 10002; +} +.tox-rtc-user-selection { + position: relative; +} +.tox-rtc-user-cursor { + bottom: 0; + cursor: default; + position: absolute; + top: 0; + width: 2px; +} +.tox-rtc-user-cursor::before { + background-color: inherit; + border-radius: 50%; + content: ''; + display: block; + height: 8px; + position: absolute; + right: -3px; + top: -3px; + width: 8px; +} +.tox-rtc-user-cursor:hover::after { + background-color: inherit; + border-radius: 100px; + box-sizing: border-box; + color: #fff; + content: attr(data-user); + display: block; + font-size: 12px; + font-weight: bold; + left: -5px; + min-height: 8px; + min-width: 8px; + padding: 0 12px; + position: absolute; + top: -11px; + white-space: nowrap; + z-index: 1000; +} +.tox-rtc-user-selection--1 .tox-rtc-user-cursor { + background-color: #2dc26b; +} +.tox-rtc-user-selection--2 .tox-rtc-user-cursor { + background-color: #e03e2d; +} +.tox-rtc-user-selection--3 .tox-rtc-user-cursor { + background-color: #f1c40f; +} +.tox-rtc-user-selection--4 .tox-rtc-user-cursor { + background-color: #3598db; +} +.tox-rtc-user-selection--5 .tox-rtc-user-cursor { + background-color: #b96ad9; +} +.tox-rtc-user-selection--6 .tox-rtc-user-cursor { + background-color: #e67e23; +} +.tox-rtc-user-selection--7 .tox-rtc-user-cursor { + background-color: #aaa69d; +} +.tox-rtc-user-selection--8 .tox-rtc-user-cursor { + background-color: #f368e0; +} +.tox-rtc-remote-image { + background: #eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center; + border: 1px solid #ccc; + min-height: 240px; + min-width: 320px; +} +.mce-match-marker { + background: #aaa; + color: #fff; +} +.mce-match-marker-selected { + background: #39f; + color: #fff; +} +.mce-match-marker-selected::-moz-selection { + background: #39f; + color: #fff; +} +.mce-match-marker-selected::selection { + background: #39f; + color: #fff; +} +.mce-content-body img[data-mce-selected], +.mce-content-body video[data-mce-selected], +.mce-content-body audio[data-mce-selected], +.mce-content-body object[data-mce-selected], +.mce-content-body embed[data-mce-selected], +.mce-content-body table[data-mce-selected] { + outline: 3px solid #4099ff; +} +.mce-content-body hr[data-mce-selected] { + outline: 3px solid #4099ff; + outline-offset: 1px; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus { + outline: 3px solid #4099ff; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover { + outline: 3px solid #4099ff; +} +.mce-content-body *[contentEditable=false][data-mce-selected] { + cursor: not-allowed; + outline: 3px solid #4099ff; +} +.mce-content-body.mce-content-readonly *[contentEditable=true]:focus, +.mce-content-body.mce-content-readonly *[contentEditable=true]:hover { + outline: none; +} +.mce-content-body *[data-mce-selected="inline-boundary"] { + background-color: #4099ff; +} +.mce-content-body .mce-edit-focus { + outline: 3px solid #4099ff; +} +.mce-content-body td[data-mce-selected], +.mce-content-body th[data-mce-selected] { + position: relative; +} +.mce-content-body td[data-mce-selected]::-moz-selection, +.mce-content-body th[data-mce-selected]::-moz-selection { + background: none; +} +.mce-content-body td[data-mce-selected]::selection, +.mce-content-body th[data-mce-selected]::selection { + background: none; +} +.mce-content-body td[data-mce-selected] *, +.mce-content-body th[data-mce-selected] * { + outline: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mce-content-body td[data-mce-selected]::after, +.mce-content-body th[data-mce-selected]::after { + background-color: rgba(180, 215, 255, 0.7); + border: 1px solid transparent; + bottom: -1px; + content: ''; + left: -1px; + mix-blend-mode: lighten; + position: absolute; + right: -1px; + top: -1px; +} +@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + .mce-content-body td[data-mce-selected]::after, + .mce-content-body th[data-mce-selected]::after { + border-color: rgba(0, 84, 180, 0.7); + } +} +.mce-content-body img::-moz-selection { + background: none; +} +.mce-content-body img::selection { + background: none; +} +.ephox-snooker-resizer-bar { + background-color: #4099ff; + opacity: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.ephox-snooker-resizer-cols { + cursor: col-resize; +} +.ephox-snooker-resizer-rows { + cursor: row-resize; +} +.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging { + opacity: 1; +} +.mce-spellchecker-word { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; + height: 2rem; +} +.mce-spellchecker-grammar { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; +} +.mce-toc { + border: 1px solid gray; +} +.mce-toc h2 { + margin: 4px; +} +.mce-toc li { + list-style-type: none; +} +table[style*="border-width: 0px"], +.mce-item-table:not([border]), +.mce-item-table[border="0"], +table[style*="border-width: 0px"] td, +.mce-item-table:not([border]) td, +.mce-item-table[border="0"] td, +table[style*="border-width: 0px"] th, +.mce-item-table:not([border]) th, +.mce-item-table[border="0"] th, +table[style*="border-width: 0px"] caption, +.mce-item-table:not([border]) caption, +.mce-item-table[border="0"] caption { + border: 1px dashed #bbb; +} +.mce-visualblocks p, +.mce-visualblocks h1, +.mce-visualblocks h2, +.mce-visualblocks h3, +.mce-visualblocks h4, +.mce-visualblocks h5, +.mce-visualblocks h6, +.mce-visualblocks div:not([data-mce-bogus]), +.mce-visualblocks section, +.mce-visualblocks article, +.mce-visualblocks blockquote, +.mce-visualblocks address, +.mce-visualblocks pre, +.mce-visualblocks figure, +.mce-visualblocks figcaption, +.mce-visualblocks hgroup, +.mce-visualblocks aside, +.mce-visualblocks ul, +.mce-visualblocks ol, +.mce-visualblocks dl { + background-repeat: no-repeat; + border: 1px dashed #bbb; + margin-left: 3px; + padding-top: 10px; +} +.mce-visualblocks p { + background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7); +} +.mce-visualblocks h1 { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==); +} +.mce-visualblocks h2 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==); +} +.mce-visualblocks h3 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7); +} +.mce-visualblocks h4 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==); +} +.mce-visualblocks h5 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==); +} +.mce-visualblocks h6 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==); +} +.mce-visualblocks div:not([data-mce-bogus]) { + background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7); +} +.mce-visualblocks section { + background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=); +} +.mce-visualblocks article { + background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7); +} +.mce-visualblocks blockquote { + background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7); +} +.mce-visualblocks address { + background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=); +} +.mce-visualblocks pre { + background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==); +} +.mce-visualblocks figure { + background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7); +} +.mce-visualblocks figcaption { + border: 1px dashed #bbb; +} +.mce-visualblocks hgroup { + background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7); +} +.mce-visualblocks aside { + background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=); +} +.mce-visualblocks ul { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==); +} +.mce-visualblocks ol { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==); +} +.mce-visualblocks dl { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==); +} +.mce-visualblocks:not([dir=rtl]) p, +.mce-visualblocks:not([dir=rtl]) h1, +.mce-visualblocks:not([dir=rtl]) h2, +.mce-visualblocks:not([dir=rtl]) h3, +.mce-visualblocks:not([dir=rtl]) h4, +.mce-visualblocks:not([dir=rtl]) h5, +.mce-visualblocks:not([dir=rtl]) h6, +.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]), +.mce-visualblocks:not([dir=rtl]) section, +.mce-visualblocks:not([dir=rtl]) article, +.mce-visualblocks:not([dir=rtl]) blockquote, +.mce-visualblocks:not([dir=rtl]) address, +.mce-visualblocks:not([dir=rtl]) pre, +.mce-visualblocks:not([dir=rtl]) figure, +.mce-visualblocks:not([dir=rtl]) figcaption, +.mce-visualblocks:not([dir=rtl]) hgroup, +.mce-visualblocks:not([dir=rtl]) aside, +.mce-visualblocks:not([dir=rtl]) ul, +.mce-visualblocks:not([dir=rtl]) ol, +.mce-visualblocks:not([dir=rtl]) dl { + margin-left: 3px; +} +.mce-visualblocks[dir=rtl] p, +.mce-visualblocks[dir=rtl] h1, +.mce-visualblocks[dir=rtl] h2, +.mce-visualblocks[dir=rtl] h3, +.mce-visualblocks[dir=rtl] h4, +.mce-visualblocks[dir=rtl] h5, +.mce-visualblocks[dir=rtl] h6, +.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]), +.mce-visualblocks[dir=rtl] section, +.mce-visualblocks[dir=rtl] article, +.mce-visualblocks[dir=rtl] blockquote, +.mce-visualblocks[dir=rtl] address, +.mce-visualblocks[dir=rtl] pre, +.mce-visualblocks[dir=rtl] figure, +.mce-visualblocks[dir=rtl] figcaption, +.mce-visualblocks[dir=rtl] hgroup, +.mce-visualblocks[dir=rtl] aside, +.mce-visualblocks[dir=rtl] ul, +.mce-visualblocks[dir=rtl] ol, +.mce-visualblocks[dir=rtl] dl { + background-position-x: right; + margin-right: 3px; +} +.mce-nbsp, +.mce-shy { + background: #aaa; +} +.mce-shy::after { + content: '-'; +} +body { + font-family: sans-serif; +} +table { + border-collapse: collapse; +} diff --git a/public/tinymce/skins/ui/oxide-dark/content.inline.css b/public/tinymce/skins/ui/oxide-dark/content.inline.css new file mode 100644 index 0000000..8e7521d --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/content.inline.css @@ -0,0 +1,726 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + cursor: default; + display: inline-block; + height: 12px !important; + padding: 0 2px; + -webkit-user-modify: read-only; + -moz-user-modify: read-only; + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; + width: 8px !important; +} +.mce-content-body .mce-item-anchor[data-mce-selected] { + outline-offset: 1px; +} +.tox-comments-visible .tox-comment { + background-color: #fff0b7; +} +.tox-comments-visible .tox-comment--active { + background-color: #ffe168; +} +.tox-checklist > li:not(.tox-checklist--hidden) { + list-style: none; + margin: 0.25em 0; +} +.tox-checklist > li:not(.tox-checklist--hidden)::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); + cursor: pointer; + height: 1em; + margin-left: -1.5em; + margin-top: 0.125em; + position: absolute; + width: 1em; +} +.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); +} +[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before { + margin-left: 0; + margin-right: -1.5em; +} +/* stylelint-disable */ +/* http://prismjs.com/ */ +/** + * 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; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + tab-size: 4; + -webkit-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: 0.5em 0; + overflow: auto; +} +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} +.token.punctuation { + color: #999; +} +.namespace { + opacity: 0.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%, 0.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; +} +/* stylelint-enable */ +.mce-content-body { + overflow-wrap: break-word; + word-wrap: break-word; +} +.mce-content-body .mce-visual-caret { + background-color: black; + background-color: currentColor; + position: absolute; +} +.mce-content-body .mce-visual-caret-hidden { + display: none; +} +.mce-content-body *[data-mce-caret] { + left: -1000px; + margin: 0; + padding: 0; + position: absolute; + right: auto; + top: 0; +} +.mce-content-body .mce-offscreen-selection { + left: -2000000px; + max-width: 1000000px; + position: absolute; +} +.mce-content-body *[contentEditable=false] { + cursor: default; +} +.mce-content-body *[contentEditable=true] { + cursor: text; +} +.tox-cursor-format-painter { + cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default; +} +.mce-content-body figure.align-left { + float: left; +} +.mce-content-body figure.align-right { + float: right; +} +.mce-content-body figure.image.align-center { + display: table; + margin-left: auto; + margin-right: auto; +} +.mce-preview-object { + border: 1px solid gray; + display: inline-block; + line-height: 0; + margin: 0 2px 0 2px; + position: relative; +} +.mce-preview-object .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-preview-object[data-mce-selected="2"] .mce-shim { + display: none; +} +.mce-object { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + border: 1px dashed #aaa; +} +.mce-pagebreak { + border: 1px dashed #aaa; + cursor: default; + display: block; + height: 5px; + margin-top: 15px; + page-break-before: always; + width: 100%; +} +@media print { + .mce-pagebreak { + border: 0; + } +} +.tiny-pageembed .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.tiny-pageembed[data-mce-selected="2"] .mce-shim { + display: none; +} +.tiny-pageembed { + display: inline-block; + position: relative; +} +.tiny-pageembed--21by9, +.tiny-pageembed--16by9, +.tiny-pageembed--4by3, +.tiny-pageembed--1by1 { + display: block; + overflow: hidden; + padding: 0; + position: relative; + width: 100%; +} +.tiny-pageembed--21by9 { + padding-top: 42.857143%; +} +.tiny-pageembed--16by9 { + padding-top: 56.25%; +} +.tiny-pageembed--4by3 { + padding-top: 75%; +} +.tiny-pageembed--1by1 { + padding-top: 100%; +} +.tiny-pageembed--21by9 iframe, +.tiny-pageembed--16by9 iframe, +.tiny-pageembed--4by3 iframe, +.tiny-pageembed--1by1 iframe { + border: 0; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-content-body[data-mce-placeholder] { + position: relative; +} +.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before { + color: rgba(34, 47, 62, 0.7); + content: attr(data-mce-placeholder); + position: absolute; +} +.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before { + left: 1px; +} +.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before { + right: 1px; +} +.mce-content-body div.mce-resizehandle { + background-color: #4099ff; + border-color: #4099ff; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + height: 10px; + position: absolute; + width: 10px; + z-index: 1298; +} +.mce-content-body div.mce-resizehandle:hover { + background-color: #4099ff; +} +.mce-content-body div.mce-resizehandle:nth-of-type(1) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(2) { + cursor: nesw-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(3) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(4) { + cursor: nesw-resize; +} +.mce-content-body .mce-resize-backdrop { + z-index: 10000; +} +.mce-content-body .mce-clonedresizable { + cursor: default; + opacity: 0.5; + outline: 1px dashed black; + position: absolute; + z-index: 10001; +} +.mce-content-body .mce-clonedresizable.mce-resizetable-columns th, +.mce-content-body .mce-clonedresizable.mce-resizetable-columns td { + border: 0; +} +.mce-content-body .mce-resize-helper { + background: #555; + background: rgba(0, 0, 0, 0.75); + border: 1px; + border-radius: 3px; + color: white; + display: none; + font-family: sans-serif; + font-size: 12px; + line-height: 14px; + margin: 5px 10px; + padding: 5px; + position: absolute; + white-space: nowrap; + z-index: 10002; +} +.tox-rtc-user-selection { + position: relative; +} +.tox-rtc-user-cursor { + bottom: 0; + cursor: default; + position: absolute; + top: 0; + width: 2px; +} +.tox-rtc-user-cursor::before { + background-color: inherit; + border-radius: 50%; + content: ''; + display: block; + height: 8px; + position: absolute; + right: -3px; + top: -3px; + width: 8px; +} +.tox-rtc-user-cursor:hover::after { + background-color: inherit; + border-radius: 100px; + box-sizing: border-box; + color: #fff; + content: attr(data-user); + display: block; + font-size: 12px; + font-weight: bold; + left: -5px; + min-height: 8px; + min-width: 8px; + padding: 0 12px; + position: absolute; + top: -11px; + white-space: nowrap; + z-index: 1000; +} +.tox-rtc-user-selection--1 .tox-rtc-user-cursor { + background-color: #2dc26b; +} +.tox-rtc-user-selection--2 .tox-rtc-user-cursor { + background-color: #e03e2d; +} +.tox-rtc-user-selection--3 .tox-rtc-user-cursor { + background-color: #f1c40f; +} +.tox-rtc-user-selection--4 .tox-rtc-user-cursor { + background-color: #3598db; +} +.tox-rtc-user-selection--5 .tox-rtc-user-cursor { + background-color: #b96ad9; +} +.tox-rtc-user-selection--6 .tox-rtc-user-cursor { + background-color: #e67e23; +} +.tox-rtc-user-selection--7 .tox-rtc-user-cursor { + background-color: #aaa69d; +} +.tox-rtc-user-selection--8 .tox-rtc-user-cursor { + background-color: #f368e0; +} +.tox-rtc-remote-image { + background: #eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center; + border: 1px solid #ccc; + min-height: 240px; + min-width: 320px; +} +.mce-match-marker { + background: #aaa; + color: #fff; +} +.mce-match-marker-selected { + background: #39f; + color: #fff; +} +.mce-match-marker-selected::-moz-selection { + background: #39f; + color: #fff; +} +.mce-match-marker-selected::selection { + background: #39f; + color: #fff; +} +.mce-content-body img[data-mce-selected], +.mce-content-body video[data-mce-selected], +.mce-content-body audio[data-mce-selected], +.mce-content-body object[data-mce-selected], +.mce-content-body embed[data-mce-selected], +.mce-content-body table[data-mce-selected] { + outline: 3px solid #b4d7ff; +} +.mce-content-body hr[data-mce-selected] { + outline: 3px solid #b4d7ff; + outline-offset: 1px; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus { + outline: 3px solid #b4d7ff; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover { + outline: 3px solid #b4d7ff; +} +.mce-content-body *[contentEditable=false][data-mce-selected] { + cursor: not-allowed; + outline: 3px solid #b4d7ff; +} +.mce-content-body.mce-content-readonly *[contentEditable=true]:focus, +.mce-content-body.mce-content-readonly *[contentEditable=true]:hover { + outline: none; +} +.mce-content-body *[data-mce-selected="inline-boundary"] { + background-color: #b4d7ff; +} +.mce-content-body .mce-edit-focus { + outline: 3px solid #b4d7ff; +} +.mce-content-body td[data-mce-selected], +.mce-content-body th[data-mce-selected] { + position: relative; +} +.mce-content-body td[data-mce-selected]::-moz-selection, +.mce-content-body th[data-mce-selected]::-moz-selection { + background: none; +} +.mce-content-body td[data-mce-selected]::selection, +.mce-content-body th[data-mce-selected]::selection { + background: none; +} +.mce-content-body td[data-mce-selected] *, +.mce-content-body th[data-mce-selected] * { + outline: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mce-content-body td[data-mce-selected]::after, +.mce-content-body th[data-mce-selected]::after { + background-color: rgba(180, 215, 255, 0.7); + border: 1px solid rgba(180, 215, 255, 0.7); + bottom: -1px; + content: ''; + left: -1px; + mix-blend-mode: multiply; + position: absolute; + right: -1px; + top: -1px; +} +@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + .mce-content-body td[data-mce-selected]::after, + .mce-content-body th[data-mce-selected]::after { + border-color: rgba(0, 84, 180, 0.7); + } +} +.mce-content-body img::-moz-selection { + background: none; +} +.mce-content-body img::selection { + background: none; +} +.ephox-snooker-resizer-bar { + background-color: #b4d7ff; + opacity: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.ephox-snooker-resizer-cols { + cursor: col-resize; +} +.ephox-snooker-resizer-rows { + cursor: row-resize; +} +.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging { + opacity: 1; +} +.mce-spellchecker-word { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; + height: 2rem; +} +.mce-spellchecker-grammar { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; +} +.mce-toc { + border: 1px solid gray; +} +.mce-toc h2 { + margin: 4px; +} +.mce-toc li { + list-style-type: none; +} +table[style*="border-width: 0px"], +.mce-item-table:not([border]), +.mce-item-table[border="0"], +table[style*="border-width: 0px"] td, +.mce-item-table:not([border]) td, +.mce-item-table[border="0"] td, +table[style*="border-width: 0px"] th, +.mce-item-table:not([border]) th, +.mce-item-table[border="0"] th, +table[style*="border-width: 0px"] caption, +.mce-item-table:not([border]) caption, +.mce-item-table[border="0"] caption { + border: 1px dashed #bbb; +} +.mce-visualblocks p, +.mce-visualblocks h1, +.mce-visualblocks h2, +.mce-visualblocks h3, +.mce-visualblocks h4, +.mce-visualblocks h5, +.mce-visualblocks h6, +.mce-visualblocks div:not([data-mce-bogus]), +.mce-visualblocks section, +.mce-visualblocks article, +.mce-visualblocks blockquote, +.mce-visualblocks address, +.mce-visualblocks pre, +.mce-visualblocks figure, +.mce-visualblocks figcaption, +.mce-visualblocks hgroup, +.mce-visualblocks aside, +.mce-visualblocks ul, +.mce-visualblocks ol, +.mce-visualblocks dl { + background-repeat: no-repeat; + border: 1px dashed #bbb; + margin-left: 3px; + padding-top: 10px; +} +.mce-visualblocks p { + background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7); +} +.mce-visualblocks h1 { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==); +} +.mce-visualblocks h2 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==); +} +.mce-visualblocks h3 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7); +} +.mce-visualblocks h4 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==); +} +.mce-visualblocks h5 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==); +} +.mce-visualblocks h6 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==); +} +.mce-visualblocks div:not([data-mce-bogus]) { + background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7); +} +.mce-visualblocks section { + background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=); +} +.mce-visualblocks article { + background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7); +} +.mce-visualblocks blockquote { + background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7); +} +.mce-visualblocks address { + background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=); +} +.mce-visualblocks pre { + background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==); +} +.mce-visualblocks figure { + background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7); +} +.mce-visualblocks figcaption { + border: 1px dashed #bbb; +} +.mce-visualblocks hgroup { + background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7); +} +.mce-visualblocks aside { + background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=); +} +.mce-visualblocks ul { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==); +} +.mce-visualblocks ol { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==); +} +.mce-visualblocks dl { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==); +} +.mce-visualblocks:not([dir=rtl]) p, +.mce-visualblocks:not([dir=rtl]) h1, +.mce-visualblocks:not([dir=rtl]) h2, +.mce-visualblocks:not([dir=rtl]) h3, +.mce-visualblocks:not([dir=rtl]) h4, +.mce-visualblocks:not([dir=rtl]) h5, +.mce-visualblocks:not([dir=rtl]) h6, +.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]), +.mce-visualblocks:not([dir=rtl]) section, +.mce-visualblocks:not([dir=rtl]) article, +.mce-visualblocks:not([dir=rtl]) blockquote, +.mce-visualblocks:not([dir=rtl]) address, +.mce-visualblocks:not([dir=rtl]) pre, +.mce-visualblocks:not([dir=rtl]) figure, +.mce-visualblocks:not([dir=rtl]) figcaption, +.mce-visualblocks:not([dir=rtl]) hgroup, +.mce-visualblocks:not([dir=rtl]) aside, +.mce-visualblocks:not([dir=rtl]) ul, +.mce-visualblocks:not([dir=rtl]) ol, +.mce-visualblocks:not([dir=rtl]) dl { + margin-left: 3px; +} +.mce-visualblocks[dir=rtl] p, +.mce-visualblocks[dir=rtl] h1, +.mce-visualblocks[dir=rtl] h2, +.mce-visualblocks[dir=rtl] h3, +.mce-visualblocks[dir=rtl] h4, +.mce-visualblocks[dir=rtl] h5, +.mce-visualblocks[dir=rtl] h6, +.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]), +.mce-visualblocks[dir=rtl] section, +.mce-visualblocks[dir=rtl] article, +.mce-visualblocks[dir=rtl] blockquote, +.mce-visualblocks[dir=rtl] address, +.mce-visualblocks[dir=rtl] pre, +.mce-visualblocks[dir=rtl] figure, +.mce-visualblocks[dir=rtl] figcaption, +.mce-visualblocks[dir=rtl] hgroup, +.mce-visualblocks[dir=rtl] aside, +.mce-visualblocks[dir=rtl] ul, +.mce-visualblocks[dir=rtl] ol, +.mce-visualblocks[dir=rtl] dl { + background-position-x: right; + margin-right: 3px; +} +.mce-nbsp, +.mce-shy { + background: #aaa; +} +.mce-shy::after { + content: '-'; +} diff --git a/public/tinymce/skins/ui/oxide-dark/content.inline.min.css b/public/tinymce/skins/ui/oxide-dark/content.inline.min.css new file mode 100644 index 0000000..b4ab9a3 --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/content.inline.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;-ms-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment{background-color:#fff0b7}.tox-comments-visible .tox-comment--active{background-color:#ffe168}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"),default}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-object{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected="2"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #b4d7ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #b4d7ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#b4d7ff}.mce-content-body .mce-edit-focus{outline:3px solid #b4d7ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid rgba(180,215,255,.7);bottom:-1px;content:'';left:-1px;mix-blend-mode:multiply;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img::-moz-selection{background:0 0}.mce-content-body img::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#b4d7ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc li{list-style-type:none}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border="0"],.mce-item-table[border="0"] caption,.mce-item-table[border="0"] td,.mce-item-table[border="0"] th,table[style*="border-width: 0px"],table[style*="border-width: 0px"] caption,table[style*="border-width: 0px"] td,table[style*="border-width: 0px"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'} diff --git a/public/tinymce/skins/ui/oxide-dark/content.min.css b/public/tinymce/skins/ui/oxide-dark/content.min.css new file mode 100644 index 0000000..e27b8a0 --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/content.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;-ms-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment{background-color:#fff0b7}.tox-comments-visible .tox-comment--active{background-color:#ffe168}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%236d737b%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);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;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#282a36}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#6272a4}.token.punctuation{color:#f8f8f2}.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#ff79c6}.token.boolean,.token.number{color:#bd93f9}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#50fa7b}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#f1fa8c}.token.keyword{color:#8be9fd}.token.important,.token.regex{color:#ffb86c}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"),default}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-object{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected="2"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #4099ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #4099ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #4099ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #4099ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #4099ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#4099ff}.mce-content-body .mce-edit-focus{outline:3px solid #4099ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid transparent;bottom:-1px;content:'';left:-1px;mix-blend-mode:lighten;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img::-moz-selection{background:0 0}.mce-content-body img::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#4099ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc li{list-style-type:none}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border="0"],.mce-item-table[border="0"] caption,.mce-item-table[border="0"] td,.mce-item-table[border="0"] th,table[style*="border-width: 0px"],table[style*="border-width: 0px"] caption,table[style*="border-width: 0px"] td,table[style*="border-width: 0px"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}body{font-family:sans-serif}table{border-collapse:collapse} diff --git a/public/tinymce/skins/ui/oxide-dark/content.mobile.css b/public/tinymce/skins/ui/oxide-dark/content.mobile.css new file mode 100644 index 0000000..4bdb8ba --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/content.mobile.css @@ -0,0 +1,29 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection { + /* Note: this file is used inside the content, so isn't part of theming */ + background-color: green; + display: inline-block; + opacity: 0.5; + position: absolute; +} +body { + -webkit-text-size-adjust: none; +} +body img { + /* this is related to the content margin */ + max-width: 96vw; +} +body table img { + max-width: 95%; +} +body { + font-family: sans-serif; +} +table { + border-collapse: collapse; +} diff --git a/public/tinymce/skins/ui/oxide-dark/content.mobile.min.css b/public/tinymce/skins/ui/oxide-dark/content.mobile.min.css new file mode 100644 index 0000000..35f7dc0 --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/content.mobile.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse} diff --git a/public/tinymce/skins/ui/oxide-dark/fonts/tinymce-mobile.woff b/public/tinymce/skins/ui/oxide-dark/fonts/tinymce-mobile.woff new file mode 100644 index 0000000..1e3be03 Binary files /dev/null and b/public/tinymce/skins/ui/oxide-dark/fonts/tinymce-mobile.woff differ diff --git a/public/tinymce/skins/ui/oxide-dark/skin.css b/public/tinymce/skins/ui/oxide-dark/skin.css new file mode 100644 index 0000000..d34b9c1 --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/skin.css @@ -0,0 +1,3047 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tox { + box-shadow: none; + box-sizing: content-box; + color: #2A3746; + cursor: auto; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 16px; + font-style: normal; + font-weight: normal; + line-height: normal; + -webkit-tap-highlight-color: transparent; + text-decoration: none; + text-shadow: none; + text-transform: none; + vertical-align: initial; + white-space: normal; +} +.tox *:not(svg):not(rect) { + box-sizing: inherit; + color: inherit; + cursor: inherit; + direction: inherit; + font-family: inherit; + font-size: inherit; + font-style: inherit; + font-weight: inherit; + line-height: inherit; + -webkit-tap-highlight-color: inherit; + text-align: inherit; + text-decoration: inherit; + text-shadow: inherit; + text-transform: inherit; + vertical-align: inherit; + white-space: inherit; +} +.tox *:not(svg):not(rect) { + /* stylelint-disable-line no-duplicate-selectors */ + background: transparent; + border: 0; + box-shadow: none; + float: none; + height: auto; + margin: 0; + max-width: none; + outline: 0; + padding: 0; + position: static; + width: auto; +} +.tox:not([dir=rtl]) { + direction: ltr; + text-align: left; +} +.tox[dir=rtl] { + direction: rtl; + text-align: right; +} +.tox-tinymce { + border: 1px solid #000000; + border-radius: 0; + box-shadow: none; + box-sizing: border-box; + display: flex; + flex-direction: column; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + overflow: hidden; + position: relative; + visibility: inherit !important; +} +.tox-tinymce-inline { + border: none; + box-shadow: none; +} +.tox-tinymce-inline .tox-editor-header { + background-color: transparent; + border: 1px solid #000000; + border-radius: 0; + box-shadow: none; +} +.tox-tinymce-aux { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + z-index: 1300; +} +.tox-tinymce *:focus, +.tox-tinymce-aux *:focus { + outline: none; +} +button::-moz-focus-inner { + border: 0; +} +.tox[dir=rtl] .tox-icon--flip svg { + transform: rotateY(180deg); +} +.tox .accessibility-issue__header { + align-items: center; + display: flex; + margin-bottom: 4px; +} +.tox .accessibility-issue__description { + align-items: stretch; + border: 1px solid #000000; + border-radius: 3px; + display: flex; + justify-content: space-between; +} +.tox .accessibility-issue__description > div { + padding-bottom: 4px; +} +.tox .accessibility-issue__description > div > div { + align-items: center; + display: flex; + margin-bottom: 4px; +} +.tox .accessibility-issue__description > *:last-child:not(:only-child) { + border-color: #000000; + border-style: solid; +} +.tox .accessibility-issue__repair { + margin-top: 16px; +} +.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description { + background-color: rgba(32, 122, 183, 0.5); + border-color: #207ab7; + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description > *:last-child { + border-color: #207ab7; +} +.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2 { + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg { + fill: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--info a .tox-icon { + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description { + background-color: rgba(255, 165, 0, 0.5); + border-color: rgba(255, 165, 0, 0.8); + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description > *:last-child { + border-color: rgba(255, 165, 0, 0.8); +} +.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2 { + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg { + fill: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--warn a .tox-icon { + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description { + background-color: rgba(204, 0, 0, 0.5); + border-color: rgba(204, 0, 0, 0.8); + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description > *:last-child { + border-color: rgba(204, 0, 0, 0.8); +} +.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2 { + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg { + fill: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--error a .tox-icon { + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description { + background-color: rgba(120, 171, 70, 0.5); + border-color: rgba(120, 171, 70, 0.8); + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description > *:last-child { + border-color: rgba(120, 171, 70, 0.8); +} +.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2 { + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg { + fill: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue--success a .tox-icon { + color: #fff; +} +.tox .tox-dialog__body-content .accessibility-issue__header h1, +.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2 { + margin-top: 0; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button { + margin-left: 4px; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) { + margin-left: auto; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description { + padding: 4px 4px 4px 8px; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description > *:last-child { + border-left-width: 1px; + padding-left: 4px; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button { + margin-right: 4px; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) { + margin-right: auto; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description { + padding: 4px 8px 4px 4px; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description > *:last-child { + border-right-width: 1px; + padding-right: 4px; +} +.tox .tox-anchorbar { + display: flex; + flex: 0 0 auto; +} +.tox .tox-bar { + display: flex; + flex: 0 0 auto; +} +.tox .tox-button { + background-color: #207ab7; + background-image: none; + background-position: 0 0; + background-repeat: repeat; + border-color: #207ab7; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + color: #fff; + cursor: pointer; + display: inline-block; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 14px; + font-style: normal; + font-weight: bold; + letter-spacing: normal; + line-height: 24px; + margin: 0; + outline: none; + padding: 4px 16px; + text-align: center; + text-decoration: none; + text-transform: none; + white-space: nowrap; +} +.tox .tox-button[disabled] { + background-color: #207ab7; + background-image: none; + border-color: #207ab7; + box-shadow: none; + color: rgba(255, 255, 255, 0.5); + cursor: not-allowed; +} +.tox .tox-button:focus:not(:disabled) { + background-color: #1c6ca1; + background-image: none; + border-color: #1c6ca1; + box-shadow: none; + color: #fff; +} +.tox .tox-button:hover:not(:disabled) { + background-color: #1c6ca1; + background-image: none; + border-color: #1c6ca1; + box-shadow: none; + color: #fff; +} +.tox .tox-button:active:not(:disabled) { + background-color: #185d8c; + background-image: none; + border-color: #185d8c; + box-shadow: none; + color: #fff; +} +.tox .tox-button--secondary { + background-color: #3d546f; + background-image: none; + background-position: 0 0; + background-repeat: repeat; + border-color: #3d546f; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + color: #fff; + font-size: 14px; + font-style: normal; + font-weight: bold; + letter-spacing: normal; + outline: none; + padding: 4px 16px; + text-decoration: none; + text-transform: none; +} +.tox .tox-button--secondary[disabled] { + background-color: #3d546f; + background-image: none; + border-color: #3d546f; + box-shadow: none; + color: rgba(255, 255, 255, 0.5); +} +.tox .tox-button--secondary:focus:not(:disabled) { + background-color: #34485f; + background-image: none; + border-color: #34485f; + box-shadow: none; + color: #fff; +} +.tox .tox-button--secondary:hover:not(:disabled) { + background-color: #34485f; + background-image: none; + border-color: #34485f; + box-shadow: none; + color: #fff; +} +.tox .tox-button--secondary:active:not(:disabled) { + background-color: #2b3b4e; + background-image: none; + border-color: #2b3b4e; + box-shadow: none; + color: #fff; +} +.tox .tox-button--icon, +.tox .tox-button.tox-button--icon, +.tox .tox-button.tox-button--secondary.tox-button--icon { + padding: 4px; +} +.tox .tox-button--icon .tox-icon svg, +.tox .tox-button.tox-button--icon .tox-icon svg, +.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg { + display: block; + fill: currentColor; +} +.tox .tox-button-link { + background: 0; + border: none; + box-sizing: border-box; + cursor: pointer; + display: inline-block; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 16px; + font-weight: normal; + line-height: 1.3; + margin: 0; + padding: 0; + white-space: nowrap; +} +.tox .tox-button-link--sm { + font-size: 14px; +} +.tox .tox-button--naked { + background-color: transparent; + border-color: transparent; + box-shadow: unset; + color: #fff; +} +.tox .tox-button--naked[disabled] { + background-color: #3d546f; + border-color: #3d546f; + box-shadow: none; + color: rgba(255, 255, 255, 0.5); +} +.tox .tox-button--naked:hover:not(:disabled) { + background-color: #34485f; + border-color: #34485f; + box-shadow: none; + color: #fff; +} +.tox .tox-button--naked:focus:not(:disabled) { + background-color: #34485f; + border-color: #34485f; + box-shadow: none; + color: #fff; +} +.tox .tox-button--naked:active:not(:disabled) { + background-color: #2b3b4e; + border-color: #2b3b4e; + box-shadow: none; + color: #fff; +} +.tox .tox-button--naked .tox-icon svg { + fill: currentColor; +} +.tox .tox-button--naked.tox-button--icon:hover:not(:disabled) { + color: #fff; +} +.tox .tox-checkbox { + align-items: center; + border-radius: 3px; + cursor: pointer; + display: flex; + height: 36px; + min-width: 36px; +} +.tox .tox-checkbox__input { + /* Hide from view but visible to screen readers */ + height: 1px; + overflow: hidden; + position: absolute; + top: auto; + width: 1px; +} +.tox .tox-checkbox__icons { + align-items: center; + border-radius: 3px; + box-shadow: 0 0 0 2px transparent; + box-sizing: content-box; + display: flex; + height: 24px; + justify-content: center; + padding: calc(4px - 1px); + width: 24px; +} +.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + display: block; + fill: rgba(255, 255, 255, 0.2); +} +.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg { + display: none; + fill: #207ab7; +} +.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg { + display: none; + fill: #207ab7; +} +.tox .tox-checkbox--disabled { + color: rgba(255, 255, 255, 0.5); + cursor: not-allowed; +} +.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg { + fill: rgba(255, 255, 255, 0.5); +} +.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + fill: rgba(255, 255, 255, 0.5); +} +.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg { + fill: rgba(255, 255, 255, 0.5); +} +.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + display: none; +} +.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__checked svg { + display: block; +} +.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + display: none; +} +.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg { + display: block; +} +.tox input.tox-checkbox__input:focus + .tox-checkbox__icons { + border-radius: 3px; + box-shadow: inset 0 0 0 1px #207ab7; + padding: calc(4px - 1px); +} +.tox:not([dir=rtl]) .tox-checkbox__label { + margin-left: 4px; +} +.tox:not([dir=rtl]) .tox-checkbox__input { + left: -10000px; +} +.tox:not([dir=rtl]) .tox-bar .tox-checkbox { + margin-left: 4px; +} +.tox[dir=rtl] .tox-checkbox__label { + margin-right: 4px; +} +.tox[dir=rtl] .tox-checkbox__input { + right: -10000px; +} +.tox[dir=rtl] .tox-bar .tox-checkbox { + margin-right: 4px; +} +.tox { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox .tox-collection--toolbar .tox-collection__group { + display: flex; + padding: 0; +} +.tox .tox-collection--grid .tox-collection__group { + display: flex; + flex-wrap: wrap; + max-height: 208px; + overflow-x: hidden; + overflow-y: auto; + padding: 0; +} +.tox .tox-collection--list .tox-collection__group { + border-bottom-width: 0; + border-color: #1a1a1a; + border-left-width: 0; + border-right-width: 0; + border-style: solid; + border-top-width: 1px; + padding: 4px 0; +} +.tox .tox-collection--list .tox-collection__group:first-child { + border-top-width: 0; +} +.tox .tox-collection__group-heading { + background-color: #333333; + color: #fff; + cursor: default; + font-size: 12px; + font-style: normal; + font-weight: normal; + margin-bottom: 4px; + margin-top: -4px; + padding: 4px 8px; + text-transform: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.tox .tox-collection__item { + align-items: center; + color: #fff; + cursor: pointer; + display: flex; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.tox .tox-collection--list .tox-collection__item { + padding: 4px 8px; +} +.tox .tox-collection--toolbar .tox-collection__item { + border-radius: 3px; + padding: 4px; +} +.tox .tox-collection--grid .tox-collection__item { + border-radius: 3px; + padding: 4px; +} +.tox .tox-collection--list .tox-collection__item--enabled { + background-color: #2b3b4e; + color: #fff; +} +.tox .tox-collection--list .tox-collection__item--active { + background-color: #4a5562; +} +.tox .tox-collection--toolbar .tox-collection__item--enabled { + background-color: #757d87; + color: #fff; +} +.tox .tox-collection--toolbar .tox-collection__item--active { + background-color: #4a5562; +} +.tox .tox-collection--grid .tox-collection__item--enabled { + background-color: #757d87; + color: #fff; +} +.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled) { + background-color: #4a5562; + color: #fff; +} +.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled) { + color: #fff; +} +.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled) { + color: #fff; +} +.tox .tox-collection__item-icon, +.tox .tox-collection__item-checkmark { + align-items: center; + display: flex; + height: 24px; + justify-content: center; + width: 24px; +} +.tox .tox-collection__item-icon svg, +.tox .tox-collection__item-checkmark svg { + fill: currentColor; +} +.tox .tox-collection--toolbar-lg .tox-collection__item-icon { + height: 48px; + width: 48px; +} +.tox .tox-collection__item-label { + color: currentColor; + display: inline-block; + flex: 1; + -ms-flex-preferred-size: auto; + font-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 24px; + text-transform: none; + word-break: break-all; +} +.tox .tox-collection__item-accessory { + color: rgba(255, 255, 255, 0.5); + display: inline-block; + font-size: 14px; + height: 24px; + line-height: 24px; + text-transform: none; +} +.tox .tox-collection__item-caret { + align-items: center; + display: flex; + min-height: 24px; +} +.tox .tox-collection__item-caret::after { + content: ''; + font-size: 0; + min-height: inherit; +} +.tox .tox-collection__item-caret svg { + fill: #fff; +} +.tox .tox-collection__item--state-disabled { + background-color: transparent; + color: rgba(255, 255, 255, 0.5); + cursor: not-allowed; +} +.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg { + fill: rgba(255, 255, 255, 0.5); +} +.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg { + display: none; +} +.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory + .tox-collection__item-checkmark { + display: none; +} +.tox .tox-collection--horizontal { + background-color: #2b3b4e; + border: 1px solid #1a1a1a; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); + display: flex; + flex: 0 0 auto; + flex-shrink: 0; + flex-wrap: nowrap; + margin-bottom: 0; + overflow-x: auto; + padding: 0; +} +.tox .tox-collection--horizontal .tox-collection__group { + align-items: center; + display: flex; + flex-wrap: nowrap; + margin: 0; + padding: 0 4px; +} +.tox .tox-collection--horizontal .tox-collection__item { + height: 34px; + margin: 2px 0 3px 0; + padding: 0 4px; +} +.tox .tox-collection--horizontal .tox-collection__item-label { + white-space: nowrap; +} +.tox .tox-collection--horizontal .tox-collection__item-caret { + margin-left: 4px; +} +.tox .tox-collection__item-container { + display: flex; +} +.tox .tox-collection__item-container--row { + align-items: center; + flex: 1 1 auto; + flex-direction: row; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--align-left { + margin-right: auto; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--align-right { + justify-content: flex-end; + margin-left: auto; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top { + align-items: flex-start; + margin-bottom: auto; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle { + align-items: center; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom { + align-items: flex-end; + margin-top: auto; +} +.tox .tox-collection__item-container--column { + -ms-grid-row-align: center; + align-self: center; + flex: 1 1 auto; + flex-direction: column; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--align-left { + align-items: flex-start; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--align-right { + align-items: flex-end; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top { + align-self: flex-start; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle { + -ms-grid-row-align: center; + align-self: center; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom { + align-self: flex-end; +} +.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type) { + border-right: 1px solid #000000; +} +.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > *:not(:first-child) { + margin-left: 8px; +} +.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child { + margin-left: 4px; +} +.tox:not([dir=rtl]) .tox-collection__item-accessory { + margin-left: 16px; + text-align: right; +} +.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret { + margin-left: 16px; +} +.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type) { + border-left: 1px solid #000000; +} +.tox[dir=rtl] .tox-collection--list .tox-collection__item > *:not(:first-child) { + margin-right: 8px; +} +.tox[dir=rtl] .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child { + margin-right: 4px; +} +.tox[dir=rtl] .tox-collection__item-accessory { + margin-right: 16px; + text-align: left; +} +.tox[dir=rtl] .tox-collection .tox-collection__item-caret { + margin-right: 16px; + transform: rotateY(180deg); +} +.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret { + margin-right: 4px; +} +.tox .tox-color-picker-container { + display: flex; + flex-direction: row; + height: 225px; + margin: 0; +} +.tox .tox-sv-palette { + box-sizing: border-box; + display: flex; + height: 100%; +} +.tox .tox-sv-palette-spectrum { + height: 100%; +} +.tox .tox-sv-palette, +.tox .tox-sv-palette-spectrum { + width: 225px; +} +.tox .tox-sv-palette-thumb { + background: none; + border: 1px solid black; + border-radius: 50%; + box-sizing: content-box; + height: 12px; + position: absolute; + width: 12px; +} +.tox .tox-sv-palette-inner-thumb { + border: 1px solid white; + border-radius: 50%; + height: 10px; + position: absolute; + width: 10px; +} +.tox .tox-hue-slider { + box-sizing: border-box; + height: 100%; + width: 25px; +} +.tox .tox-hue-slider-spectrum { + background: linear-gradient(to bottom, #f00, #ff0080, #f0f, #8000ff, #00f, #0080ff, #0ff, #00ff80, #0f0, #80ff00, #ff0, #ff8000, #f00); + height: 100%; + width: 100%; +} +.tox .tox-hue-slider, +.tox .tox-hue-slider-spectrum { + width: 20px; +} +.tox .tox-hue-slider-thumb { + background: white; + border: 1px solid black; + box-sizing: content-box; + height: 4px; + width: 100%; +} +.tox .tox-rgb-form { + display: flex; + flex-direction: column; + justify-content: space-between; +} +.tox .tox-rgb-form div { + align-items: center; + display: flex; + justify-content: space-between; + margin-bottom: 5px; + width: inherit; +} +.tox .tox-rgb-form input { + width: 6em; +} +.tox .tox-rgb-form input.tox-invalid { + /* Need !important to override Chrome's focus styling unfortunately */ + border: 1px solid red !important; +} +.tox .tox-rgb-form .tox-rgba-preview { + border: 1px solid black; + flex-grow: 2; + margin-bottom: 0; +} +.tox:not([dir=rtl]) .tox-sv-palette { + margin-right: 15px; +} +.tox:not([dir=rtl]) .tox-hue-slider { + margin-right: 15px; +} +.tox:not([dir=rtl]) .tox-hue-slider-thumb { + margin-left: -1px; +} +.tox:not([dir=rtl]) .tox-rgb-form label { + margin-right: 0.5em; +} +.tox[dir=rtl] .tox-sv-palette { + margin-left: 15px; +} +.tox[dir=rtl] .tox-hue-slider { + margin-left: 15px; +} +.tox[dir=rtl] .tox-hue-slider-thumb { + margin-right: -1px; +} +.tox[dir=rtl] .tox-rgb-form label { + margin-left: 0.5em; +} +.tox .tox-toolbar .tox-swatches, +.tox .tox-toolbar__primary .tox-swatches, +.tox .tox-toolbar__overflow .tox-swatches { + margin: 2px 0 3px 4px; +} +.tox .tox-collection--list .tox-collection__group .tox-swatches-menu { + border: 0; + margin: -4px 0; +} +.tox .tox-swatches__row { + display: flex; +} +.tox .tox-swatch { + height: 30px; + transition: transform 0.15s, box-shadow 0.15s; + width: 30px; +} +.tox .tox-swatch:hover, +.tox .tox-swatch:focus { + box-shadow: 0 0 0 1px rgba(127, 127, 127, 0.3) inset; + transform: scale(0.8); +} +.tox .tox-swatch--remove { + align-items: center; + display: flex; + justify-content: center; +} +.tox .tox-swatch--remove svg path { + stroke: #e74c3c; +} +.tox .tox-swatches__picker-btn { + align-items: center; + background-color: transparent; + border: 0; + cursor: pointer; + display: flex; + height: 30px; + justify-content: center; + outline: none; + padding: 0; + width: 30px; +} +.tox .tox-swatches__picker-btn svg { + height: 24px; + width: 24px; +} +.tox .tox-swatches__picker-btn:hover { + background: #4a5562; +} +.tox:not([dir=rtl]) .tox-swatches__picker-btn { + margin-left: auto; +} +.tox[dir=rtl] .tox-swatches__picker-btn { + margin-right: auto; +} +.tox .tox-comment-thread { + background: #2b3b4e; + position: relative; +} +.tox .tox-comment-thread > *:not(:first-child) { + margin-top: 8px; +} +.tox .tox-comment { + background: #2b3b4e; + border: 1px solid #000000; + border-radius: 3px; + box-shadow: 0 4px 8px 0 rgba(42, 55, 70, 0.1); + padding: 8px 8px 16px 8px; + position: relative; +} +.tox .tox-comment__header { + align-items: center; + color: #fff; + display: flex; + justify-content: space-between; +} +.tox .tox-comment__date { + color: rgba(255, 255, 255, 0.5); + font-size: 12px; +} +.tox .tox-comment__body { + color: #fff; + font-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + margin-top: 8px; + position: relative; + text-transform: initial; +} +.tox .tox-comment__body textarea { + resize: none; + white-space: normal; + width: 100%; +} +.tox .tox-comment__expander { + padding-top: 8px; +} +.tox .tox-comment__expander p { + color: rgba(255, 255, 255, 0.5); + font-size: 14px; + font-style: normal; +} +.tox .tox-comment__body p { + margin: 0; +} +.tox .tox-comment__buttonspacing { + padding-top: 16px; + text-align: center; +} +.tox .tox-comment-thread__overlay::after { + background: #2b3b4e; + bottom: 0; + content: ""; + display: flex; + left: 0; + opacity: 0.9; + position: absolute; + right: 0; + top: 0; + z-index: 5; +} +.tox .tox-comment__reply { + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + justify-content: flex-end; + margin-top: 8px; +} +.tox .tox-comment__reply > *:first-child { + margin-bottom: 8px; + width: 100%; +} +.tox .tox-comment__edit { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + margin-top: 16px; +} +.tox .tox-comment__gradient::after { + background: linear-gradient(rgba(43, 59, 78, 0), #2b3b4e); + bottom: 0; + content: ""; + display: block; + height: 5em; + margin-top: -40px; + position: absolute; + width: 100%; +} +.tox .tox-comment__overlay { + background: #2b3b4e; + bottom: 0; + display: flex; + flex-direction: column; + flex-grow: 1; + left: 0; + opacity: 0.9; + position: absolute; + right: 0; + text-align: center; + top: 0; + z-index: 5; +} +.tox .tox-comment__loading-text { + align-items: center; + color: #fff; + display: flex; + flex-direction: column; + position: relative; +} +.tox .tox-comment__loading-text > div { + padding-bottom: 16px; +} +.tox .tox-comment__overlaytext { + bottom: 0; + flex-direction: column; + font-size: 14px; + left: 0; + padding: 1em; + position: absolute; + right: 0; + top: 0; + z-index: 10; +} +.tox .tox-comment__overlaytext p { + background-color: #2b3b4e; + box-shadow: 0 0 8px 8px #2b3b4e; + color: #fff; + text-align: center; +} +.tox .tox-comment__overlaytext div:nth-of-type(2) { + font-size: 0.8em; +} +.tox .tox-comment__busy-spinner { + align-items: center; + background-color: #2b3b4e; + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 20; +} +.tox .tox-comment__scroll { + display: flex; + flex-direction: column; + flex-shrink: 1; + overflow: auto; +} +.tox .tox-conversations { + margin: 8px; +} +.tox:not([dir=rtl]) .tox-comment__edit { + margin-left: 8px; +} +.tox:not([dir=rtl]) .tox-comment__buttonspacing > *:last-child, +.tox:not([dir=rtl]) .tox-comment__edit > *:last-child, +.tox:not([dir=rtl]) .tox-comment__reply > *:last-child { + margin-left: 8px; +} +.tox[dir=rtl] .tox-comment__edit { + margin-right: 8px; +} +.tox[dir=rtl] .tox-comment__buttonspacing > *:last-child, +.tox[dir=rtl] .tox-comment__edit > *:last-child, +.tox[dir=rtl] .tox-comment__reply > *:last-child { + margin-right: 8px; +} +.tox .tox-user { + align-items: center; + display: flex; +} +.tox .tox-user__avatar svg { + fill: rgba(255, 255, 255, 0.5); +} +.tox .tox-user__name { + color: rgba(255, 255, 255, 0.5); + font-size: 12px; + font-style: normal; + font-weight: bold; + text-transform: uppercase; +} +.tox:not([dir=rtl]) .tox-user__avatar svg { + margin-right: 8px; +} +.tox:not([dir=rtl]) .tox-user__avatar + .tox-user__name { + margin-left: 8px; +} +.tox[dir=rtl] .tox-user__avatar svg { + margin-left: 8px; +} +.tox[dir=rtl] .tox-user__avatar + .tox-user__name { + margin-right: 8px; +} +.tox .tox-dialog-wrap { + align-items: center; + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: fixed; + right: 0; + top: 0; + z-index: 1100; +} +.tox .tox-dialog-wrap__backdrop { + background-color: rgba(34, 47, 62, 0.75); + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 1; +} +.tox .tox-dialog-wrap__backdrop--opaque { + background-color: #222f3e; +} +.tox .tox-dialog { + background-color: #2b3b4e; + border-color: #000000; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: 0 16px 16px -10px rgba(42, 55, 70, 0.15), 0 0 40px 1px rgba(42, 55, 70, 0.15); + display: flex; + flex-direction: column; + max-height: 100%; + max-width: 480px; + overflow: hidden; + position: relative; + width: 95vw; + z-index: 2; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox .tox-dialog { + align-self: flex-start; + margin: 8px auto; + width: calc(100vw - 16px); + } +} +.tox .tox-dialog-inline { + z-index: 1100; +} +.tox .tox-dialog__header { + align-items: center; + background-color: #2b3b4e; + border-bottom: none; + color: #fff; + display: flex; + font-size: 16px; + justify-content: space-between; + padding: 8px 16px 0 16px; + position: relative; +} +.tox .tox-dialog__header .tox-button { + z-index: 1; +} +.tox .tox-dialog__draghandle { + cursor: grab; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.tox .tox-dialog__draghandle:active { + cursor: grabbing; +} +.tox .tox-dialog__dismiss { + margin-left: auto; +} +.tox .tox-dialog__title { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 20px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + margin: 0; + text-transform: none; +} +.tox .tox-dialog__body { + color: #fff; + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; + font-size: 16px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + min-width: 0; + text-align: left; + text-transform: none; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox .tox-dialog__body { + flex-direction: column; + } +} +.tox .tox-dialog__body-nav { + align-items: flex-start; + display: flex; + flex-direction: column; + padding: 16px 16px; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox .tox-dialog__body-nav { + flex-direction: row; + -webkit-overflow-scrolling: touch; + overflow-x: auto; + padding-bottom: 0; + } +} +.tox .tox-dialog__body-nav-item { + border-bottom: 2px solid transparent; + color: rgba(255, 255, 255, 0.5); + display: inline-block; + font-size: 14px; + line-height: 1.3; + margin-bottom: 8px; + text-decoration: none; + white-space: nowrap; +} +.tox .tox-dialog__body-nav-item:focus { + background-color: rgba(32, 122, 183, 0.1); +} +.tox .tox-dialog__body-nav-item--active { + border-bottom: 2px solid #207ab7; + color: #207ab7; +} +.tox .tox-dialog__body-content { + box-sizing: border-box; + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; + max-height: 650px; + overflow: auto; + -webkit-overflow-scrolling: touch; + padding: 16px 16px; +} +.tox .tox-dialog__body-content > * { + margin-bottom: 0; + margin-top: 16px; +} +.tox .tox-dialog__body-content > *:first-child { + margin-top: 0; +} +.tox .tox-dialog__body-content > *:last-child { + margin-bottom: 0; +} +.tox .tox-dialog__body-content > *:only-child { + margin-bottom: 0; + margin-top: 0; +} +.tox .tox-dialog__body-content a { + color: #207ab7; + cursor: pointer; + text-decoration: none; +} +.tox .tox-dialog__body-content a:hover, +.tox .tox-dialog__body-content a:focus { + color: #185d8c; + text-decoration: none; +} +.tox .tox-dialog__body-content a:active { + color: #185d8c; + text-decoration: none; +} +.tox .tox-dialog__body-content svg { + fill: #fff; +} +.tox .tox-dialog__body-content ul { + display: block; + list-style-type: disc; + margin-bottom: 16px; + -webkit-margin-end: 0; + margin-inline-end: 0; + -webkit-margin-start: 0; + margin-inline-start: 0; + -webkit-padding-start: 2.5rem; + padding-inline-start: 2.5rem; +} +.tox .tox-dialog__body-content .tox-form__group h1 { + color: #fff; + font-size: 20px; + font-style: normal; + font-weight: bold; + letter-spacing: normal; + margin-bottom: 16px; + margin-top: 2rem; + text-transform: none; +} +.tox .tox-dialog__body-content .tox-form__group h2 { + color: #fff; + font-size: 16px; + font-style: normal; + font-weight: bold; + letter-spacing: normal; + margin-bottom: 16px; + margin-top: 2rem; + text-transform: none; +} +.tox .tox-dialog__body-content .tox-form__group p { + margin-bottom: 16px; +} +.tox .tox-dialog__body-content .tox-form__group h1:first-child, +.tox .tox-dialog__body-content .tox-form__group h2:first-child, +.tox .tox-dialog__body-content .tox-form__group p:first-child { + margin-top: 0; +} +.tox .tox-dialog__body-content .tox-form__group h1:last-child, +.tox .tox-dialog__body-content .tox-form__group h2:last-child, +.tox .tox-dialog__body-content .tox-form__group p:last-child { + margin-bottom: 0; +} +.tox .tox-dialog__body-content .tox-form__group h1:only-child, +.tox .tox-dialog__body-content .tox-form__group h2:only-child, +.tox .tox-dialog__body-content .tox-form__group p:only-child { + margin-bottom: 0; + margin-top: 0; +} +.tox .tox-dialog--width-lg { + height: 650px; + max-width: 1200px; +} +.tox .tox-dialog--width-md { + max-width: 800px; +} +.tox .tox-dialog--width-md .tox-dialog__body-content { + overflow: auto; +} +.tox .tox-dialog__body-content--centered { + text-align: center; +} +.tox .tox-dialog__footer { + align-items: center; + background-color: #2b3b4e; + border-top: 1px solid #000000; + display: flex; + justify-content: space-between; + padding: 8px 16px; +} +.tox .tox-dialog__footer-start, +.tox .tox-dialog__footer-end { + display: flex; +} +.tox .tox-dialog__busy-spinner { + align-items: center; + background-color: rgba(34, 47, 62, 0.75); + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 3; +} +.tox .tox-dialog__table { + border-collapse: collapse; + width: 100%; +} +.tox .tox-dialog__table thead th { + font-weight: bold; + padding-bottom: 8px; +} +.tox .tox-dialog__table tbody tr { + border-bottom: 1px solid #000000; +} +.tox .tox-dialog__table tbody tr:last-child { + border-bottom: none; +} +.tox .tox-dialog__table td { + padding-bottom: 8px; + padding-top: 8px; +} +.tox .tox-dialog__popups { + position: absolute; + width: 100%; + z-index: 1100; +} +.tox .tox-dialog__body-iframe { + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; +} +.tox .tox-dialog__body-iframe .tox-navobj { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2) { + flex: 1; + -ms-flex-preferred-size: auto; + height: 100%; +} +.tox .tox-dialog-dock-fadeout { + opacity: 0; + visibility: hidden; +} +.tox .tox-dialog-dock-fadein { + opacity: 1; + visibility: visible; +} +.tox .tox-dialog-dock-transition { + transition: visibility 0s linear 0.3s, opacity 0.3s ease; +} +.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein { + transition-delay: 0s; +} +.tox.tox-platform-ie { + /* IE11 CSS styles go here */ +} +.tox.tox-platform-ie .tox-dialog-wrap { + position: -ms-device-fixed; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav { + margin-right: 0; + } +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child) { + margin-left: 8px; + } +} +.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start > *, +.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end > * { + margin-left: 8px; +} +.tox[dir=rtl] .tox-dialog__body { + text-align: right; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav { + margin-left: 0; + } +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child) { + margin-right: 8px; + } +} +.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start > *, +.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end > * { + margin-right: 8px; +} +body.tox-dialog__disable-scroll { + overflow: hidden; +} +.tox .tox-dropzone-container { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-dropzone { + align-items: center; + background: #fff; + border: 2px dashed #000000; + box-sizing: border-box; + display: flex; + flex-direction: column; + flex-grow: 1; + justify-content: center; + min-height: 100px; + padding: 10px; +} +.tox .tox-dropzone p { + color: rgba(255, 255, 255, 0.5); + margin: 0 0 16px 0; +} +.tox .tox-edit-area { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; + overflow: hidden; + position: relative; +} +.tox .tox-edit-area__iframe { + background-color: #fff; + border: 0; + box-sizing: border-box; + flex: 1; + -ms-flex-preferred-size: auto; + height: 100%; + position: absolute; + width: 100%; +} +.tox.tox-inline-edit-area { + border: 1px dotted #000000; +} +.tox .tox-editor-container { + display: flex; + flex: 1 1 auto; + flex-direction: column; + overflow: hidden; +} +.tox .tox-editor-header { + z-index: 1; +} +.tox:not(.tox-tinymce-inline) .tox-editor-header { + box-shadow: none; + transition: box-shadow 0.5s; +} +.tox.tox-tinymce--toolbar-bottom .tox-editor-header, +.tox.tox-tinymce-inline .tox-editor-header { + margin-bottom: -1px; +} +.tox.tox-tinymce--toolbar-sticky-on .tox-editor-header { + background-color: transparent; + box-shadow: 0 4px 4px -3px rgba(0, 0, 0, 0.25); +} +.tox-editor-dock-fadeout { + opacity: 0; + visibility: hidden; +} +.tox-editor-dock-fadein { + opacity: 1; + visibility: visible; +} +.tox-editor-dock-transition { + transition: visibility 0s linear 0.25s, opacity 0.25s ease; +} +.tox-editor-dock-transition.tox-editor-dock-fadein { + transition-delay: 0s; +} +.tox .tox-control-wrap { + flex: 1; + position: relative; +} +.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid, +.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown, +.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid { + display: none; +} +.tox .tox-control-wrap svg { + display: block; +} +.tox .tox-control-wrap__status-icon-wrap { + position: absolute; + top: 50%; + transform: translateY(-50%); +} +.tox .tox-control-wrap__status-icon-invalid svg { + fill: #c00; +} +.tox .tox-control-wrap__status-icon-unknown svg { + fill: orange; +} +.tox .tox-control-wrap__status-icon-valid svg { + fill: green; +} +.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield, +.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield, +.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield { + padding-right: 32px; +} +.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap { + right: 4px; +} +.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield, +.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield, +.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield { + padding-left: 32px; +} +.tox[dir=rtl] .tox-control-wrap__status-icon-wrap { + left: 4px; +} +.tox .tox-autocompleter { + max-width: 25em; +} +.tox .tox-autocompleter .tox-menu { + max-width: 25em; +} +.tox .tox-autocompleter .tox-autocompleter-highlight { + font-weight: bold; +} +.tox .tox-color-input { + display: flex; + position: relative; + z-index: 1; +} +.tox .tox-color-input .tox-textfield { + z-index: -1; +} +.tox .tox-color-input span { + border-color: rgba(42, 55, 70, 0.2); + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + height: 24px; + position: absolute; + top: 6px; + width: 24px; +} +.tox .tox-color-input span:hover:not([aria-disabled=true]), +.tox .tox-color-input span:focus:not([aria-disabled=true]) { + border-color: #207ab7; + cursor: pointer; +} +.tox .tox-color-input span::before { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.25) 25%, transparent 25%), linear-gradient(-45deg, rgba(255, 255, 255, 0.25) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.25) 75%), linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.25) 75%); + background-position: 0 0, 0 6px, 6px -6px, -6px 0; + background-size: 12px 12px; + border: 1px solid #2b3b4e; + border-radius: 3px; + box-sizing: border-box; + content: ''; + height: 24px; + left: -1px; + position: absolute; + top: -1px; + width: 24px; + z-index: -1; +} +.tox .tox-color-input span[aria-disabled=true] { + cursor: not-allowed; +} +.tox:not([dir=rtl]) .tox-color-input { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox:not([dir=rtl]) .tox-color-input .tox-textfield { + padding-left: 36px; +} +.tox:not([dir=rtl]) .tox-color-input span { + left: 6px; +} +.tox[dir="rtl"] .tox-color-input { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox[dir="rtl"] .tox-color-input .tox-textfield { + padding-right: 36px; +} +.tox[dir="rtl"] .tox-color-input span { + right: 6px; +} +.tox .tox-label, +.tox .tox-toolbar-label { + color: rgba(255, 255, 255, 0.5); + display: block; + font-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + padding: 0 8px 0 0; + text-transform: none; + white-space: nowrap; +} +.tox .tox-toolbar-label { + padding: 0 8px; +} +.tox[dir=rtl] .tox-label { + padding: 0 0 0 8px; +} +.tox .tox-form { + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group { + box-sizing: border-box; + margin-bottom: 4px; +} +.tox .tox-form-group--maximize { + flex: 1; +} +.tox .tox-form__group--error { + color: #c00; +} +.tox .tox-form__group--collection { + display: flex; +} +.tox .tox-form__grid { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; +} +.tox .tox-form__grid--2col > .tox-form__group { + width: calc(50% - (8px / 2)); +} +.tox .tox-form__grid--3col > .tox-form__group { + width: calc(100% / 3 - (8px / 2)); +} +.tox .tox-form__grid--4col > .tox-form__group { + width: calc(25% - (8px / 2)); +} +.tox .tox-form__controls-h-stack { + align-items: center; + display: flex; +} +.tox .tox-form__group--inline { + align-items: center; + display: flex; +} +.tox .tox-form__group--stretched { + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group--stretched .tox-textarea { + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group--stretched .tox-navobj { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group--stretched .tox-navobj :nth-child(2) { + flex: 1; + -ms-flex-preferred-size: auto; + height: 100%; +} +.tox:not([dir=rtl]) .tox-form__controls-h-stack > *:not(:first-child) { + margin-left: 4px; +} +.tox[dir=rtl] .tox-form__controls-h-stack > *:not(:first-child) { + margin-right: 4px; +} +.tox .tox-lock.tox-locked .tox-lock-icon__unlock, +.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock { + display: none; +} +.tox .tox-textfield, +.tox .tox-toolbar-textfield, +.tox .tox-listboxfield .tox-listbox--select, +.tox .tox-textarea { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #2b3b4e; + border-color: #000000; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + color: #fff; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 16px; + line-height: 24px; + margin: 0; + min-height: 34px; + outline: none; + padding: 5px 4.75px; + resize: none; + width: 100%; +} +.tox .tox-textfield[disabled], +.tox .tox-textarea[disabled] { + background-color: #222f3e; + color: rgba(255, 255, 255, 0.85); + cursor: not-allowed; +} +.tox .tox-textfield:focus, +.tox .tox-listboxfield .tox-listbox--select:focus, +.tox .tox-textarea:focus { + background-color: #2b3b4e; + border-color: #207ab7; + box-shadow: none; + outline: none; +} +.tox .tox-toolbar-textfield { + border-width: 0; + margin-bottom: 3px; + margin-top: 2px; + max-width: 250px; +} +.tox .tox-naked-btn { + background-color: transparent; + border: 0; + border-color: transparent; + box-shadow: unset; + color: #207ab7; + cursor: pointer; + display: block; + margin: 0; + padding: 0; +} +.tox .tox-naked-btn svg { + display: block; + fill: #fff; +} +.tox:not([dir=rtl]) .tox-toolbar-textfield + * { + margin-left: 4px; +} +.tox[dir=rtl] .tox-toolbar-textfield + * { + margin-right: 4px; +} +.tox .tox-listboxfield { + cursor: pointer; + position: relative; +} +.tox .tox-listboxfield .tox-listbox--select[disabled] { + background-color: #19232e; + color: rgba(255, 255, 255, 0.85); + cursor: not-allowed; +} +.tox .tox-listbox__select-label { + cursor: default; + flex: 1; + margin: 0 4px; +} +.tox .tox-listbox__select-chevron { + align-items: center; + display: flex; + justify-content: center; + width: 16px; +} +.tox .tox-listbox__select-chevron svg { + fill: #fff; +} +.tox .tox-listboxfield .tox-listbox--select { + align-items: center; + display: flex; +} +.tox:not([dir=rtl]) .tox-listboxfield svg { + right: 8px; +} +.tox[dir=rtl] .tox-listboxfield svg { + left: 8px; +} +.tox .tox-selectfield { + cursor: pointer; + position: relative; +} +.tox .tox-selectfield select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #2b3b4e; + border-color: #000000; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + color: #fff; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 16px; + line-height: 24px; + margin: 0; + min-height: 34px; + outline: none; + padding: 5px 4.75px; + resize: none; + width: 100%; +} +.tox .tox-selectfield select[disabled] { + background-color: #19232e; + color: rgba(255, 255, 255, 0.85); + cursor: not-allowed; +} +.tox .tox-selectfield select::-ms-expand { + display: none; +} +.tox .tox-selectfield select:focus { + background-color: #2b3b4e; + border-color: #207ab7; + box-shadow: none; + outline: none; +} +.tox .tox-selectfield svg { + pointer-events: none; + position: absolute; + top: 50%; + transform: translateY(-50%); +} +.tox:not([dir=rtl]) .tox-selectfield select[size="0"], +.tox:not([dir=rtl]) .tox-selectfield select[size="1"] { + padding-right: 24px; +} +.tox:not([dir=rtl]) .tox-selectfield svg { + right: 8px; +} +.tox[dir=rtl] .tox-selectfield select[size="0"], +.tox[dir=rtl] .tox-selectfield select[size="1"] { + padding-left: 24px; +} +.tox[dir=rtl] .tox-selectfield svg { + left: 8px; +} +.tox .tox-textarea { + -webkit-appearance: textarea; + -moz-appearance: textarea; + appearance: textarea; + white-space: pre-wrap; +} +.tox-fullscreen { + border: 0; + height: 100%; + margin: 0; + overflow: hidden; + -ms-scroll-chaining: none; + overscroll-behavior: none; + padding: 0; + touch-action: pinch-zoom; + width: 100%; +} +.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { + display: none; +} +.tox.tox-tinymce.tox-fullscreen, +.tox-shadowhost.tox-fullscreen { + left: 0; + position: fixed; + top: 0; + z-index: 1200; +} +.tox.tox-tinymce.tox-fullscreen { + background-color: transparent; +} +.tox-fullscreen .tox.tox-tinymce-aux, +.tox-fullscreen ~ .tox.tox-tinymce-aux { + z-index: 1201; +} +.tox .tox-help__more-link { + list-style: none; + margin-top: 1em; +} +.tox .tox-image-tools { + width: 100%; +} +.tox .tox-image-tools__toolbar { + align-items: center; + display: flex; + justify-content: center; +} +.tox .tox-image-tools__image { + background-color: #666; + height: 380px; + overflow: auto; + position: relative; + width: 100%; +} +.tox .tox-image-tools__image, +.tox .tox-image-tools__image + .tox-image-tools__toolbar { + margin-top: 8px; +} +.tox .tox-image-tools__image-bg { + background: url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==); +} +.tox .tox-image-tools__toolbar > .tox-spacer { + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-croprect-block { + background: black; + filter: alpha(opacity=50); + opacity: 0.5; + position: absolute; + zoom: 1; +} +.tox .tox-croprect-handle { + border: 2px solid white; + height: 20px; + left: 0; + position: absolute; + top: 0; + width: 20px; +} +.tox .tox-croprect-handle-move { + border: 0; + cursor: move; + position: absolute; +} +.tox .tox-croprect-handle-nw { + border-width: 2px 0 0 2px; + cursor: nw-resize; + left: 100px; + margin: -2px 0 0 -2px; + top: 100px; +} +.tox .tox-croprect-handle-ne { + border-width: 2px 2px 0 0; + cursor: ne-resize; + left: 200px; + margin: -2px 0 0 -20px; + top: 100px; +} +.tox .tox-croprect-handle-sw { + border-width: 0 0 2px 2px; + cursor: sw-resize; + left: 100px; + margin: -20px 2px 0 -2px; + top: 200px; +} +.tox .tox-croprect-handle-se { + border-width: 0 2px 2px 0; + cursor: se-resize; + left: 200px; + margin: -20px 0 0 -20px; + top: 200px; +} +.tox:not([dir=rtl]) .tox-image-tools__toolbar > .tox-slider:not(:first-of-type) { + margin-left: 8px; +} +.tox:not([dir=rtl]) .tox-image-tools__toolbar > .tox-button + .tox-slider { + margin-left: 32px; +} +.tox:not([dir=rtl]) .tox-image-tools__toolbar > .tox-slider + .tox-button { + margin-left: 32px; +} +.tox[dir=rtl] .tox-image-tools__toolbar > .tox-slider:not(:first-of-type) { + margin-right: 8px; +} +.tox[dir=rtl] .tox-image-tools__toolbar > .tox-button + .tox-slider { + margin-right: 32px; +} +.tox[dir=rtl] .tox-image-tools__toolbar > .tox-slider + .tox-button { + margin-right: 32px; +} +.tox .tox-insert-table-picker { + display: flex; + flex-wrap: wrap; + width: 170px; +} +.tox .tox-insert-table-picker > div { + border-color: #000000; + border-style: solid; + border-width: 0 1px 1px 0; + box-sizing: border-box; + height: 17px; + width: 17px; +} +.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker { + margin: -4px 0; +} +.tox .tox-insert-table-picker .tox-insert-table-picker__selected { + background-color: rgba(32, 122, 183, 0.5); + border-color: rgba(32, 122, 183, 0.5); +} +.tox .tox-insert-table-picker__label { + color: #fff; + display: block; + font-size: 14px; + padding: 4px; + text-align: center; + width: 100%; +} +.tox:not([dir=rtl]) { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox:not([dir=rtl]) .tox-insert-table-picker > div:nth-child(10n) { + border-right: 0; +} +.tox[dir=rtl] { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox[dir=rtl] .tox-insert-table-picker > div:nth-child(10n+1) { + border-right: 0; +} +.tox { + /* stylelint-disable */ + /* stylelint-enable */ +} +.tox .tox-menu { + background-color: #2b3b4e; + border: 1px solid #000000; + border-radius: 3px; + box-shadow: 0 4px 8px 0 rgba(42, 55, 70, 0.1); + display: inline-block; + overflow: hidden; + vertical-align: top; + z-index: 1150; +} +.tox .tox-menu.tox-collection.tox-collection--list { + padding: 0; +} +.tox .tox-menu.tox-collection.tox-collection--toolbar { + padding: 4px; +} +.tox .tox-menu.tox-collection.tox-collection--grid { + padding: 4px; +} +.tox .tox-menu__label h1, +.tox .tox-menu__label h2, +.tox .tox-menu__label h3, +.tox .tox-menu__label h4, +.tox .tox-menu__label h5, +.tox .tox-menu__label h6, +.tox .tox-menu__label p, +.tox .tox-menu__label blockquote, +.tox .tox-menu__label code { + margin: 0; +} +.tox .tox-menubar { + background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E") left 0 top 0 #222f3e; + background-color: #222f3e; + display: flex; + flex: 0 0 auto; + flex-shrink: 0; + flex-wrap: wrap; + padding: 0 4px 0 4px; +} +.tox.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar { + border-top: 1px solid #000000; +} +/* Deprecated. Remove in next major release */ +.tox .tox-mbtn { + align-items: center; + background: transparent; + border: 0; + border-radius: 3px; + box-shadow: none; + color: #fff; + display: flex; + flex: 0 0 auto; + font-size: 14px; + font-style: normal; + font-weight: normal; + height: 34px; + justify-content: center; + margin: 2px 0 3px 0; + outline: none; + overflow: hidden; + padding: 0 4px; + text-transform: none; + width: auto; +} +.tox .tox-mbtn[disabled] { + background-color: transparent; + border: 0; + box-shadow: none; + color: rgba(255, 255, 255, 0.5); + cursor: not-allowed; +} +.tox .tox-mbtn:focus:not(:disabled) { + background: #4a5562; + border: 0; + box-shadow: none; + color: #fff; +} +.tox .tox-mbtn--active { + background: #757d87; + border: 0; + box-shadow: none; + color: #fff; +} +.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active) { + background: #4a5562; + border: 0; + box-shadow: none; + color: #fff; +} +.tox .tox-mbtn__select-label { + cursor: default; + font-weight: normal; + margin: 0 4px; +} +.tox .tox-mbtn[disabled] .tox-mbtn__select-label { + cursor: not-allowed; +} +.tox .tox-mbtn__select-chevron { + align-items: center; + display: flex; + justify-content: center; + width: 16px; + display: none; +} +.tox .tox-notification { + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + display: -ms-grid; + display: grid; + font-size: 14px; + font-weight: normal; + -ms-grid-columns: minmax(40px, 1fr) auto minmax(40px, 1fr); + grid-template-columns: minmax(40px, 1fr) auto minmax(40px, 1fr); + margin-top: 4px; + opacity: 0; + padding: 4px; + transition: transform 100ms ease-in, opacity 150ms ease-in; +} +.tox .tox-notification p { + font-size: 14px; + font-weight: normal; +} +.tox .tox-notification a { + cursor: pointer; + text-decoration: underline; +} +.tox .tox-notification--in { + opacity: 1; +} +.tox .tox-notification--success { + background-color: #e4eeda; + border-color: #d7e6c8; + color: #fff; +} +.tox .tox-notification--success p { + color: #fff; +} +.tox .tox-notification--success a { + color: #547831; +} +.tox .tox-notification--success svg { + fill: #fff; +} +.tox .tox-notification--error { + background-color: #f8dede; + border-color: #f2bfbf; + color: #fff; +} +.tox .tox-notification--error p { + color: #fff; +} +.tox .tox-notification--error a { + color: #c00; +} +.tox .tox-notification--error svg { + fill: #fff; +} +.tox .tox-notification--warn, +.tox .tox-notification--warning { + background-color: #fffaea; + border-color: #ffe89d; + color: #fff; +} +.tox .tox-notification--warn p, +.tox .tox-notification--warning p { + color: #fff; +} +.tox .tox-notification--warn a, +.tox .tox-notification--warning a { + color: #fff; +} +.tox .tox-notification--warn svg, +.tox .tox-notification--warning svg { + fill: #fff; +} +.tox .tox-notification--info { + background-color: #d9edf7; + border-color: #779ecb; + color: #fff; +} +.tox .tox-notification--info p { + color: #fff; +} +.tox .tox-notification--info a { + color: #fff; +} +.tox .tox-notification--info svg { + fill: #fff; +} +.tox .tox-notification__body { + -ms-grid-row-align: center; + align-self: center; + color: #fff; + font-size: 14px; + -ms-grid-column-span: 1; + grid-column-end: 3; + -ms-grid-column: 2; + grid-column-start: 2; + -ms-grid-row-span: 1; + grid-row-end: 2; + -ms-grid-row: 1; + grid-row-start: 1; + text-align: center; + white-space: normal; + word-break: break-all; + word-break: break-word; +} +.tox .tox-notification__body > * { + margin: 0; +} +.tox .tox-notification__body > * + * { + margin-top: 1rem; +} +.tox .tox-notification__icon { + -ms-grid-row-align: center; + align-self: center; + -ms-grid-column-span: 1; + grid-column-end: 2; + -ms-grid-column: 1; + grid-column-start: 1; + -ms-grid-row-span: 1; + grid-row-end: 2; + -ms-grid-row: 1; + grid-row-start: 1; + -ms-grid-column-align: end; + justify-self: end; +} +.tox .tox-notification__icon svg { + display: block; +} +.tox .tox-notification__dismiss { + -ms-grid-row-align: start; + align-self: start; + -ms-grid-column-span: 1; + grid-column-end: 4; + -ms-grid-column: 3; + grid-column-start: 3; + -ms-grid-row-span: 1; + grid-row-end: 2; + -ms-grid-row: 1; + grid-row-start: 1; + -ms-grid-column-align: end; + justify-self: end; +} +.tox .tox-notification .tox-progress-bar { + -ms-grid-column-span: 3; + grid-column-end: 4; + -ms-grid-column: 1; + grid-column-start: 1; + -ms-grid-row-span: 1; + grid-row-end: 3; + -ms-grid-row: 2; + grid-row-start: 2; + -ms-grid-column-align: center; + justify-self: center; +} +.tox .tox-pop { + display: inline-block; + position: relative; +} +.tox .tox-pop--resizing { + transition: width 0.1s ease; +} +.tox .tox-pop--resizing .tox-toolbar, +.tox .tox-pop--resizing .tox-toolbar__group { + flex-wrap: nowrap; +} +.tox .tox-pop--transition { + transition: 0.15s ease; + transition-property: left, right, top, bottom; +} +.tox .tox-pop--transition::before, +.tox .tox-pop--transition::after { + transition: all 0.15s, visibility 0s, opacity 0.075s ease 0.075s; +} +.tox .tox-pop__dialog { + background-color: #222f3e; + border: 1px solid #000000; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); + min-width: 0; + overflow: hidden; +} +.tox .tox-pop__dialog > *:not(.tox-toolbar) { + margin: 4px 4px 4px 8px; +} +.tox .tox-pop__dialog .tox-toolbar { + background-color: transparent; + margin-bottom: -1px; +} +.tox .tox-pop::before, +.tox .tox-pop::after { + border-style: solid; + content: ''; + display: block; + height: 0; + opacity: 1; + position: absolute; + width: 0; +} +.tox .tox-pop.tox-pop--inset::before, +.tox .tox-pop.tox-pop--inset::after { + opacity: 0; + transition: all 0s 0.15s, visibility 0s, opacity 0.075s ease; +} +.tox .tox-pop.tox-pop--bottom::before, +.tox .tox-pop.tox-pop--bottom::after { + left: 50%; + top: 100%; +} +.tox .tox-pop.tox-pop--bottom::after { + border-color: #222f3e transparent transparent transparent; + border-width: 8px; + margin-left: -8px; + margin-top: -1px; +} +.tox .tox-pop.tox-pop--bottom::before { + border-color: #000000 transparent transparent transparent; + border-width: 9px; + margin-left: -9px; +} +.tox .tox-pop.tox-pop--top::before, +.tox .tox-pop.tox-pop--top::after { + left: 50%; + top: 0; + transform: translateY(-100%); +} +.tox .tox-pop.tox-pop--top::after { + border-color: transparent transparent #222f3e transparent; + border-width: 8px; + margin-left: -8px; + margin-top: 1px; +} +.tox .tox-pop.tox-pop--top::before { + border-color: transparent transparent #000000 transparent; + border-width: 9px; + margin-left: -9px; +} +.tox .tox-pop.tox-pop--left::before, +.tox .tox-pop.tox-pop--left::after { + left: 0; + top: calc(50% - 1px); + transform: translateY(-50%); +} +.tox .tox-pop.tox-pop--left::after { + border-color: transparent #222f3e transparent transparent; + border-width: 8px; + margin-left: -15px; +} +.tox .tox-pop.tox-pop--left::before { + border-color: transparent #000000 transparent transparent; + border-width: 10px; + margin-left: -19px; +} +.tox .tox-pop.tox-pop--right::before, +.tox .tox-pop.tox-pop--right::after { + left: 100%; + top: calc(50% + 1px); + transform: translateY(-50%); +} +.tox .tox-pop.tox-pop--right::after { + border-color: transparent transparent transparent #222f3e; + border-width: 8px; + margin-left: -1px; +} +.tox .tox-pop.tox-pop--right::before { + border-color: transparent transparent transparent #000000; + border-width: 10px; + margin-left: -1px; +} +.tox .tox-pop.tox-pop--align-left::before, +.tox .tox-pop.tox-pop--align-left::after { + left: 20px; +} +.tox .tox-pop.tox-pop--align-right::before, +.tox .tox-pop.tox-pop--align-right::after { + left: calc(100% - 20px); +} +.tox .tox-sidebar-wrap { + display: flex; + flex-direction: row; + flex-grow: 1; + -ms-flex-preferred-size: 0; + min-height: 0; +} +.tox .tox-sidebar { + background-color: #222f3e; + display: flex; + flex-direction: row; + justify-content: flex-end; +} +.tox .tox-sidebar__slider { + display: flex; + overflow: hidden; +} +.tox .tox-sidebar__pane-container { + display: flex; +} +.tox .tox-sidebar__pane { + display: flex; +} +.tox .tox-sidebar--sliding-closed { + opacity: 0; +} +.tox .tox-sidebar--sliding-open { + opacity: 1; +} +.tox .tox-sidebar--sliding-growing, +.tox .tox-sidebar--sliding-shrinking { + transition: width 0.5s ease, opacity 0.5s ease; +} +.tox .tox-selector { + background-color: #4099ff; + border-color: #4099ff; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + display: inline-block; + height: 10px; + position: absolute; + width: 10px; +} +.tox.tox-platform-touch .tox-selector { + height: 12px; + width: 12px; +} +.tox .tox-slider { + align-items: center; + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; + height: 24px; + justify-content: center; + position: relative; +} +.tox .tox-slider__rail { + background-color: transparent; + border: 1px solid #000000; + border-radius: 3px; + height: 10px; + min-width: 120px; + width: 100%; +} +.tox .tox-slider__handle { + background-color: #207ab7; + border: 2px solid #185d8c; + border-radius: 3px; + box-shadow: none; + height: 24px; + left: 50%; + position: absolute; + top: 50%; + transform: translateX(-50%) translateY(-50%); + width: 14px; +} +.tox .tox-source-code { + overflow: auto; +} +.tox .tox-spinner { + display: flex; +} +.tox .tox-spinner > div { + animation: tam-bouncing-dots 1.5s ease-in-out 0s infinite both; + background-color: rgba(255, 255, 255, 0.5); + border-radius: 100%; + height: 8px; + width: 8px; +} +.tox .tox-spinner > div:nth-child(1) { + animation-delay: -0.32s; +} +.tox .tox-spinner > div:nth-child(2) { + animation-delay: -0.16s; +} +@keyframes tam-bouncing-dots { + 0%, + 80%, + 100% { + transform: scale(0); + } + 40% { + transform: scale(1); + } +} +.tox:not([dir=rtl]) .tox-spinner > div:not(:first-child) { + margin-left: 4px; +} +.tox[dir=rtl] .tox-spinner > div:not(:first-child) { + margin-right: 4px; +} +.tox .tox-statusbar { + align-items: center; + background-color: #222f3e; + border-top: 1px solid #000000; + color: #fff; + display: flex; + flex: 0 0 auto; + font-size: 12px; + font-weight: normal; + height: 18px; + overflow: hidden; + padding: 0 8px; + position: relative; + text-transform: uppercase; +} +.tox .tox-statusbar__text-container { + display: flex; + flex: 1 1 auto; + justify-content: flex-end; + overflow: hidden; +} +.tox .tox-statusbar__path { + display: flex; + flex: 1 1 auto; + margin-right: auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.tox .tox-statusbar__path > * { + display: inline; + white-space: nowrap; +} +.tox .tox-statusbar__wordcount { + flex: 0 0 auto; + margin-left: 1ch; +} +.tox .tox-statusbar a, +.tox .tox-statusbar__path-item, +.tox .tox-statusbar__wordcount { + color: #fff; + text-decoration: none; +} +.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]) { + cursor: pointer; + text-decoration: underline; +} +.tox .tox-statusbar__resize-handle { + align-items: flex-end; + align-self: stretch; + cursor: nwse-resize; + display: flex; + flex: 0 0 auto; + justify-content: flex-end; + margin-left: auto; + margin-right: -8px; + padding-left: 1ch; +} +.tox .tox-statusbar__resize-handle svg { + display: block; + fill: #fff; +} +.tox .tox-statusbar__resize-handle:focus svg { + background-color: #4a5562; + border-radius: 1px; + box-shadow: 0 0 0 2px #4a5562; +} +.tox:not([dir=rtl]) .tox-statusbar__path > * { + margin-right: 4px; +} +.tox:not([dir=rtl]) .tox-statusbar__branding { + margin-left: 1ch; +} +.tox[dir=rtl] .tox-statusbar { + flex-direction: row-reverse; +} +.tox[dir=rtl] .tox-statusbar__path > * { + margin-left: 4px; +} +.tox .tox-throbber { + z-index: 1299; +} +.tox .tox-throbber__busy-spinner { + align-items: center; + background-color: rgba(34, 47, 62, 0.6); + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; +} +.tox .tox-tbtn { + align-items: center; + background: transparent; + border: 0; + border-radius: 3px; + box-shadow: none; + color: #fff; + display: flex; + flex: 0 0 auto; + font-size: 14px; + font-style: normal; + font-weight: normal; + height: 34px; + justify-content: center; + margin: 2px 0 3px 0; + outline: none; + overflow: hidden; + padding: 0; + text-transform: none; + width: 34px; +} +.tox .tox-tbtn svg { + display: block; + fill: #fff; +} +.tox .tox-tbtn.tox-tbtn-more { + padding-left: 5px; + padding-right: 5px; + width: inherit; +} +.tox .tox-tbtn:focus { + background: #4a5562; + border: 0; + box-shadow: none; +} +.tox .tox-tbtn:hover { + background: #4a5562; + border: 0; + box-shadow: none; + color: #fff; +} +.tox .tox-tbtn:hover svg { + fill: #fff; +} +.tox .tox-tbtn:active { + background: #757d87; + border: 0; + box-shadow: none; + color: #fff; +} +.tox .tox-tbtn:active svg { + fill: #fff; +} +.tox .tox-tbtn--disabled, +.tox .tox-tbtn--disabled:hover, +.tox .tox-tbtn:disabled, +.tox .tox-tbtn:disabled:hover { + background: transparent; + border: 0; + box-shadow: none; + color: rgba(255, 255, 255, 0.5); + cursor: not-allowed; +} +.tox .tox-tbtn--disabled svg, +.tox .tox-tbtn--disabled:hover svg, +.tox .tox-tbtn:disabled svg, +.tox .tox-tbtn:disabled:hover svg { + /* stylelint-disable-line no-descending-specificity */ + fill: rgba(255, 255, 255, 0.5); +} +.tox .tox-tbtn--enabled, +.tox .tox-tbtn--enabled:hover { + background: #757d87; + border: 0; + box-shadow: none; + color: #fff; +} +.tox .tox-tbtn--enabled > *, +.tox .tox-tbtn--enabled:hover > * { + transform: none; +} +.tox .tox-tbtn--enabled svg, +.tox .tox-tbtn--enabled:hover svg { + /* stylelint-disable-line no-descending-specificity */ + fill: #fff; +} +.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) { + color: #fff; +} +.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg { + fill: #fff; +} +.tox .tox-tbtn:active > * { + transform: none; +} +.tox .tox-tbtn--md { + height: 51px; + width: 51px; +} +.tox .tox-tbtn--lg { + flex-direction: column; + height: 68px; + width: 68px; +} +.tox .tox-tbtn--return { + -ms-grid-row-align: stretch; + align-self: stretch; + height: unset; + width: 16px; +} +.tox .tox-tbtn--labeled { + padding: 0 4px; + width: unset; +} +.tox .tox-tbtn__vlabel { + display: block; + font-size: 10px; + font-weight: normal; + letter-spacing: -0.025em; + margin-bottom: 4px; + white-space: nowrap; +} +.tox .tox-tbtn--select { + margin: 2px 0 3px 0; + padding: 0 4px; + width: auto; +} +.tox .tox-tbtn__select-label { + cursor: default; + font-weight: normal; + margin: 0 4px; +} +.tox .tox-tbtn__select-chevron { + align-items: center; + display: flex; + justify-content: center; + width: 16px; +} +.tox .tox-tbtn__select-chevron svg { + fill: rgba(255, 255, 255, 0.5); +} +.tox .tox-tbtn--bespoke .tox-tbtn__select-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 7em; +} +.tox .tox-split-button { + border: 0; + border-radius: 3px; + box-sizing: border-box; + display: flex; + margin: 2px 0 3px 0; + overflow: hidden; +} +.tox .tox-split-button:hover { + box-shadow: 0 0 0 1px #4a5562 inset; +} +.tox .tox-split-button:focus { + background: #4a5562; + box-shadow: none; + color: #fff; +} +.tox .tox-split-button > * { + border-radius: 0; +} +.tox .tox-split-button__chevron { + width: 16px; +} +.tox .tox-split-button__chevron svg { + fill: rgba(255, 255, 255, 0.5); +} +.tox .tox-split-button .tox-tbtn { + margin: 0; +} +.tox.tox-platform-touch .tox-split-button .tox-tbtn:first-child { + width: 30px; +} +.tox.tox-platform-touch .tox-split-button__chevron { + width: 20px; +} +.tox .tox-split-button.tox-tbtn--disabled:hover, +.tox .tox-split-button.tox-tbtn--disabled:focus, +.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover, +.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus { + background: transparent; + box-shadow: none; + color: rgba(255, 255, 255, 0.5); +} +.tox .tox-toolbar-overlord { + background-color: #222f3e; +} +.tox .tox-toolbar, +.tox .tox-toolbar__primary, +.tox .tox-toolbar__overflow { + background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E") left 0 top 0 #222f3e; + background-color: #222f3e; + display: flex; + flex: 0 0 auto; + flex-shrink: 0; + flex-wrap: wrap; + padding: 0 0; +} +.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed { + height: 0; + opacity: 0; + padding-bottom: 0; + padding-top: 0; + visibility: hidden; +} +.tox .tox-toolbar__overflow--growing { + transition: height 0.3s ease, opacity 0.2s linear 0.1s; +} +.tox .tox-toolbar__overflow--shrinking { + transition: opacity 0.3s ease, height 0.2s linear 0.1s, visibility 0s linear 0.3s; +} +.tox .tox-menubar + .tox-toolbar, +.tox .tox-menubar + .tox-toolbar-overlord .tox-toolbar__primary { + border-top: 1px solid #000000; + margin-top: -1px; +} +.tox .tox-toolbar--scrolling { + flex-wrap: nowrap; + overflow-x: auto; +} +.tox .tox-pop .tox-toolbar { + border-width: 0; +} +.tox .tox-toolbar--no-divider { + background-image: none; +} +.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child, +.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary { + border-top: 1px solid #000000; +} +.tox.tox-tinymce-aux .tox-toolbar__overflow { + background-color: #222f3e; + border: 1px solid #000000; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); +} +.tox .tox-toolbar__group { + align-items: center; + display: flex; + flex-wrap: wrap; + margin: 0 0; + padding: 0 4px 0 4px; +} +.tox .tox-toolbar__group--pull-right { + margin-left: auto; +} +.tox .tox-toolbar--scrolling .tox-toolbar__group { + flex-shrink: 0; + flex-wrap: nowrap; +} +.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type) { + border-right: 1px solid #000000; +} +.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type) { + border-left: 1px solid #000000; +} +.tox .tox-tooltip { + display: inline-block; + padding: 8px; + position: relative; +} +.tox .tox-tooltip__body { + background-color: #3d546f; + border-radius: 3px; + box-shadow: 0 2px 4px rgba(42, 55, 70, 0.3); + color: rgba(255, 255, 255, 0.75); + font-size: 14px; + font-style: normal; + font-weight: normal; + padding: 4px 8px; + text-transform: none; +} +.tox .tox-tooltip__arrow { + position: absolute; +} +.tox .tox-tooltip--down .tox-tooltip__arrow { + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid #3d546f; + bottom: 0; + left: 50%; + position: absolute; + transform: translateX(-50%); +} +.tox .tox-tooltip--up .tox-tooltip__arrow { + border-bottom: 8px solid #3d546f; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + left: 50%; + position: absolute; + top: 0; + transform: translateX(-50%); +} +.tox .tox-tooltip--right .tox-tooltip__arrow { + border-bottom: 8px solid transparent; + border-left: 8px solid #3d546f; + border-top: 8px solid transparent; + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); +} +.tox .tox-tooltip--left .tox-tooltip__arrow { + border-bottom: 8px solid transparent; + border-right: 8px solid #3d546f; + border-top: 8px solid transparent; + left: 0; + position: absolute; + top: 50%; + transform: translateY(-50%); +} +.tox .tox-well { + border: 1px solid #000000; + border-radius: 3px; + padding: 8px; + width: 100%; +} +.tox .tox-well > *:first-child { + margin-top: 0; +} +.tox .tox-well > *:last-child { + margin-bottom: 0; +} +.tox .tox-well > *:only-child { + margin: 0; +} +.tox .tox-custom-editor { + border: 1px solid #000000; + border-radius: 3px; + display: flex; + flex: 1; + position: relative; +} +/* stylelint-disable */ +.tox { + /* stylelint-enable */ +} +.tox .tox-dialog-loading::before { + background-color: rgba(0, 0, 0, 0.5); + content: ""; + height: 100%; + position: absolute; + width: 100%; + z-index: 1000; +} +.tox .tox-tab { + cursor: pointer; +} +.tox .tox-dialog__content-js { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-dialog__body-content .tox-collection { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-image-tools-edit-panel { + height: 60px; +} +.tox .tox-image-tools__sidebar { + height: 60px; +} diff --git a/public/tinymce/skins/ui/oxide-dark/skin.min.css b/public/tinymce/skins/ui/oxide-dark/skin.min.css new file mode 100644 index 0000000..e71f6f0 --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/skin.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tox{box-shadow:none;box-sizing:content-box;color:#2a3746;cursor:auto;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;font-style:normal;font-weight:400;line-height:normal;-webkit-tap-highlight-color:transparent;text-decoration:none;text-shadow:none;text-transform:none;vertical-align:initial;white-space:normal}.tox :not(svg):not(rect){box-sizing:inherit;color:inherit;cursor:inherit;direction:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;line-height:inherit;-webkit-tap-highlight-color:inherit;text-align:inherit;text-decoration:inherit;text-shadow:inherit;text-transform:inherit;vertical-align:inherit;white-space:inherit}.tox :not(svg):not(rect){background:0 0;border:0;box-shadow:none;float:none;height:auto;margin:0;max-width:none;outline:0;padding:0;position:static;width:auto}.tox:not([dir=rtl]){direction:ltr;text-align:left}.tox[dir=rtl]{direction:rtl;text-align:right}.tox-tinymce{border:1px solid #000;border-radius:0;box-shadow:none;box-sizing:border-box;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;overflow:hidden;position:relative;visibility:inherit!important}.tox-tinymce-inline{border:none;box-shadow:none}.tox-tinymce-inline .tox-editor-header{background-color:transparent;border:1px solid #000;border-radius:0;box-shadow:none}.tox-tinymce-aux{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;z-index:1300}.tox-tinymce :focus,.tox-tinymce-aux :focus{outline:0}button::-moz-focus-inner{border:0}.tox[dir=rtl] .tox-icon--flip svg{transform:rotateY(180deg)}.tox .accessibility-issue__header{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description{align-items:stretch;border:1px solid #000;border-radius:3px;display:flex;justify-content:space-between}.tox .accessibility-issue__description>div{padding-bottom:4px}.tox .accessibility-issue__description>div>div{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description>:last-child:not(:only-child){border-color:#000;border-style:solid}.tox .accessibility-issue__repair{margin-top:16px}.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description{background-color:rgba(32,122,183,.5);border-color:#207ab7;color:#fff}.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description>:last-child{border-color:#207ab7}.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue--info a .tox-icon{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description{background-color:rgba(255,165,0,.5);border-color:rgba(255,165,0,.8);color:#fff}.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description>:last-child{border-color:rgba(255,165,0,.8)}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue--warn a .tox-icon{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description{background-color:rgba(204,0,0,.5);border-color:rgba(204,0,0,.8);color:#fff}.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description>:last-child{border-color:rgba(204,0,0,.8)}.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue--error a .tox-icon{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description{background-color:rgba(120,171,70,.5);border-color:rgba(120,171,70,.8);color:#fff}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description>:last-child{border-color:rgba(120,171,70,.8)}.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue--success a .tox-icon{color:#fff}.tox .tox-dialog__body-content .accessibility-issue__header h1,.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2{margin-top:0}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-left:4px}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-left:auto}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description{padding:4px 4px 4px 8px}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description>:last-child{border-left-width:1px;padding-left:4px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-right:4px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-right:auto}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description{padding:4px 8px 4px 4px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description>:last-child{border-right-width:1px;padding-right:4px}.tox .tox-anchorbar{display:flex;flex:0 0 auto}.tox .tox-bar{display:flex;flex:0 0 auto}.tox .tox-button{background-color:#207ab7;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#207ab7;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;line-height:24px;margin:0;outline:0;padding:4px 16px;text-align:center;text-decoration:none;text-transform:none;white-space:nowrap}.tox .tox-button[disabled]{background-color:#207ab7;background-image:none;border-color:#207ab7;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-button:focus:not(:disabled){background-color:#1c6ca1;background-image:none;border-color:#1c6ca1;box-shadow:none;color:#fff}.tox .tox-button:hover:not(:disabled){background-color:#1c6ca1;background-image:none;border-color:#1c6ca1;box-shadow:none;color:#fff}.tox .tox-button:active:not(:disabled){background-color:#185d8c;background-image:none;border-color:#185d8c;box-shadow:none;color:#fff}.tox .tox-button--secondary{background-color:#3d546f;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#3d546f;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;color:#fff;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;outline:0;padding:4px 16px;text-decoration:none;text-transform:none}.tox .tox-button--secondary[disabled]{background-color:#3d546f;background-image:none;border-color:#3d546f;box-shadow:none;color:rgba(255,255,255,.5)}.tox .tox-button--secondary:focus:not(:disabled){background-color:#34485f;background-image:none;border-color:#34485f;box-shadow:none;color:#fff}.tox .tox-button--secondary:hover:not(:disabled){background-color:#34485f;background-image:none;border-color:#34485f;box-shadow:none;color:#fff}.tox .tox-button--secondary:active:not(:disabled){background-color:#2b3b4e;background-image:none;border-color:#2b3b4e;box-shadow:none;color:#fff}.tox .tox-button--icon,.tox .tox-button.tox-button--icon,.tox .tox-button.tox-button--secondary.tox-button--icon{padding:4px}.tox .tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg{display:block;fill:currentColor}.tox .tox-button-link{background:0;border:none;box-sizing:border-box;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;white-space:nowrap}.tox .tox-button-link--sm{font-size:14px}.tox .tox-button--naked{background-color:transparent;border-color:transparent;box-shadow:unset;color:#fff}.tox .tox-button--naked[disabled]{background-color:#3d546f;border-color:#3d546f;box-shadow:none;color:rgba(255,255,255,.5)}.tox .tox-button--naked:hover:not(:disabled){background-color:#34485f;border-color:#34485f;box-shadow:none;color:#fff}.tox .tox-button--naked:focus:not(:disabled){background-color:#34485f;border-color:#34485f;box-shadow:none;color:#fff}.tox .tox-button--naked:active:not(:disabled){background-color:#2b3b4e;border-color:#2b3b4e;box-shadow:none;color:#fff}.tox .tox-button--naked .tox-icon svg{fill:currentColor}.tox .tox-button--naked.tox-button--icon:hover:not(:disabled){color:#fff}.tox .tox-checkbox{align-items:center;border-radius:3px;cursor:pointer;display:flex;height:36px;min-width:36px}.tox .tox-checkbox__input{height:1px;overflow:hidden;position:absolute;top:auto;width:1px}.tox .tox-checkbox__icons{align-items:center;border-radius:3px;box-shadow:0 0 0 2px transparent;box-sizing:content-box;display:flex;height:24px;justify-content:center;padding:calc(4px - 1px);width:24px}.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:block;fill:rgba(255,255,255,.2)}.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:none;fill:#207ab7}.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg{display:none;fill:#207ab7}.tox .tox-checkbox--disabled{color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg{fill:rgba(255,255,255,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{fill:rgba(255,255,255,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{fill:rgba(255,255,255,.5)}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__checked svg{display:block}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:block}.tox input.tox-checkbox__input:focus+.tox-checkbox__icons{border-radius:3px;box-shadow:inset 0 0 0 1px #207ab7;padding:calc(4px - 1px)}.tox:not([dir=rtl]) .tox-checkbox__label{margin-left:4px}.tox:not([dir=rtl]) .tox-checkbox__input{left:-10000px}.tox:not([dir=rtl]) .tox-bar .tox-checkbox{margin-left:4px}.tox[dir=rtl] .tox-checkbox__label{margin-right:4px}.tox[dir=rtl] .tox-checkbox__input{right:-10000px}.tox[dir=rtl] .tox-bar .tox-checkbox{margin-right:4px}.tox .tox-collection--toolbar .tox-collection__group{display:flex;padding:0}.tox .tox-collection--grid .tox-collection__group{display:flex;flex-wrap:wrap;max-height:208px;overflow-x:hidden;overflow-y:auto;padding:0}.tox .tox-collection--list .tox-collection__group{border-bottom-width:0;border-color:#1a1a1a;border-left-width:0;border-right-width:0;border-style:solid;border-top-width:1px;padding:4px 0}.tox .tox-collection--list .tox-collection__group:first-child{border-top-width:0}.tox .tox-collection__group-heading{background-color:#333;color:#fff;cursor:default;font-size:12px;font-style:normal;font-weight:400;margin-bottom:4px;margin-top:-4px;padding:4px 8px;text-transform:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tox .tox-collection__item{align-items:center;color:#fff;cursor:pointer;display:flex;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tox .tox-collection--list .tox-collection__item{padding:4px 8px}.tox .tox-collection--toolbar .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--grid .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--list .tox-collection__item--enabled{background-color:#2b3b4e;color:#fff}.tox .tox-collection--list .tox-collection__item--active{background-color:#4a5562}.tox .tox-collection--toolbar .tox-collection__item--enabled{background-color:#757d87;color:#fff}.tox .tox-collection--toolbar .tox-collection__item--active{background-color:#4a5562}.tox .tox-collection--grid .tox-collection__item--enabled{background-color:#757d87;color:#fff}.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled){background-color:#4a5562;color:#fff}.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#fff}.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#fff}.tox .tox-collection__item-checkmark,.tox .tox-collection__item-icon{align-items:center;display:flex;height:24px;justify-content:center;width:24px}.tox .tox-collection__item-checkmark svg,.tox .tox-collection__item-icon svg{fill:currentColor}.tox .tox-collection--toolbar-lg .tox-collection__item-icon{height:48px;width:48px}.tox .tox-collection__item-label{color:currentColor;display:inline-block;flex:1;-ms-flex-preferred-size:auto;font-size:14px;font-style:normal;font-weight:400;line-height:24px;text-transform:none;word-break:break-all}.tox .tox-collection__item-accessory{color:rgba(255,255,255,.5);display:inline-block;font-size:14px;height:24px;line-height:24px;text-transform:none}.tox .tox-collection__item-caret{align-items:center;display:flex;min-height:24px}.tox .tox-collection__item-caret::after{content:'';font-size:0;min-height:inherit}.tox .tox-collection__item-caret svg{fill:#fff}.tox .tox-collection__item--state-disabled{background-color:transparent;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg{fill:rgba(255,255,255,.5)}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg{display:none}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory+.tox-collection__item-checkmark{display:none}.tox .tox-collection--horizontal{background-color:#2b3b4e;border:1px solid #1a1a1a;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15);display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:nowrap;margin-bottom:0;overflow-x:auto;padding:0}.tox .tox-collection--horizontal .tox-collection__group{align-items:center;display:flex;flex-wrap:nowrap;margin:0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item{height:34px;margin:2px 0 3px 0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item-label{white-space:nowrap}.tox .tox-collection--horizontal .tox-collection__item-caret{margin-left:4px}.tox .tox-collection__item-container{display:flex}.tox .tox-collection__item-container--row{align-items:center;flex:1 1 auto;flex-direction:row}.tox .tox-collection__item-container--row.tox-collection__item-container--align-left{margin-right:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--align-right{justify-content:flex-end;margin-left:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top{align-items:flex-start;margin-bottom:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle{align-items:center}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom{align-items:flex-end;margin-top:auto}.tox .tox-collection__item-container--column{-ms-grid-row-align:center;align-self:center;flex:1 1 auto;flex-direction:column}.tox .tox-collection__item-container--column.tox-collection__item-container--align-left{align-items:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--align-right{align-items:flex-end}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top{align-self:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle{-ms-grid-row-align:center;align-self:center}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom{align-self:flex-end}.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-right:1px solid #000}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>:not(:first-child){margin-left:8px}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-left:4px}.tox:not([dir=rtl]) .tox-collection__item-accessory{margin-left:16px;text-align:right}.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret{margin-left:16px}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-left:1px solid #000}.tox[dir=rtl] .tox-collection--list .tox-collection__item>:not(:first-child){margin-right:8px}.tox[dir=rtl] .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-right:4px}.tox[dir=rtl] .tox-collection__item-accessory{margin-right:16px;text-align:left}.tox[dir=rtl] .tox-collection .tox-collection__item-caret{margin-right:16px;transform:rotateY(180deg)}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret{margin-right:4px}.tox .tox-color-picker-container{display:flex;flex-direction:row;height:225px;margin:0}.tox .tox-sv-palette{box-sizing:border-box;display:flex;height:100%}.tox .tox-sv-palette-spectrum{height:100%}.tox .tox-sv-palette,.tox .tox-sv-palette-spectrum{width:225px}.tox .tox-sv-palette-thumb{background:0 0;border:1px solid #000;border-radius:50%;box-sizing:content-box;height:12px;position:absolute;width:12px}.tox .tox-sv-palette-inner-thumb{border:1px solid #fff;border-radius:50%;height:10px;position:absolute;width:10px}.tox .tox-hue-slider{box-sizing:border-box;height:100%;width:25px}.tox .tox-hue-slider-spectrum{background:linear-gradient(to bottom,red,#ff0080,#f0f,#8000ff,#00f,#0080ff,#0ff,#00ff80,#0f0,#80ff00,#ff0,#ff8000,red);height:100%;width:100%}.tox .tox-hue-slider,.tox .tox-hue-slider-spectrum{width:20px}.tox .tox-hue-slider-thumb{background:#fff;border:1px solid #000;box-sizing:content-box;height:4px;width:100%}.tox .tox-rgb-form{display:flex;flex-direction:column;justify-content:space-between}.tox .tox-rgb-form div{align-items:center;display:flex;justify-content:space-between;margin-bottom:5px;width:inherit}.tox .tox-rgb-form input{width:6em}.tox .tox-rgb-form input.tox-invalid{border:1px solid red!important}.tox .tox-rgb-form .tox-rgba-preview{border:1px solid #000;flex-grow:2;margin-bottom:0}.tox:not([dir=rtl]) .tox-sv-palette{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider-thumb{margin-left:-1px}.tox:not([dir=rtl]) .tox-rgb-form label{margin-right:.5em}.tox[dir=rtl] .tox-sv-palette{margin-left:15px}.tox[dir=rtl] .tox-hue-slider{margin-left:15px}.tox[dir=rtl] .tox-hue-slider-thumb{margin-right:-1px}.tox[dir=rtl] .tox-rgb-form label{margin-left:.5em}.tox .tox-toolbar .tox-swatches,.tox .tox-toolbar__overflow .tox-swatches,.tox .tox-toolbar__primary .tox-swatches{margin:2px 0 3px 4px}.tox .tox-collection--list .tox-collection__group .tox-swatches-menu{border:0;margin:-4px 0}.tox .tox-swatches__row{display:flex}.tox .tox-swatch{height:30px;transition:transform .15s,box-shadow .15s;width:30px}.tox .tox-swatch:focus,.tox .tox-swatch:hover{box-shadow:0 0 0 1px rgba(127,127,127,.3) inset;transform:scale(.8)}.tox .tox-swatch--remove{align-items:center;display:flex;justify-content:center}.tox .tox-swatch--remove svg path{stroke:#e74c3c}.tox .tox-swatches__picker-btn{align-items:center;background-color:transparent;border:0;cursor:pointer;display:flex;height:30px;justify-content:center;outline:0;padding:0;width:30px}.tox .tox-swatches__picker-btn svg{height:24px;width:24px}.tox .tox-swatches__picker-btn:hover{background:#4a5562}.tox:not([dir=rtl]) .tox-swatches__picker-btn{margin-left:auto}.tox[dir=rtl] .tox-swatches__picker-btn{margin-right:auto}.tox .tox-comment-thread{background:#2b3b4e;position:relative}.tox .tox-comment-thread>:not(:first-child){margin-top:8px}.tox .tox-comment{background:#2b3b4e;border:1px solid #000;border-radius:3px;box-shadow:0 4px 8px 0 rgba(42,55,70,.1);padding:8px 8px 16px 8px;position:relative}.tox .tox-comment__header{align-items:center;color:#fff;display:flex;justify-content:space-between}.tox .tox-comment__date{color:rgba(255,255,255,.5);font-size:12px}.tox .tox-comment__body{color:#fff;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;margin-top:8px;position:relative;text-transform:initial}.tox .tox-comment__body textarea{resize:none;white-space:normal;width:100%}.tox .tox-comment__expander{padding-top:8px}.tox .tox-comment__expander p{color:rgba(255,255,255,.5);font-size:14px;font-style:normal}.tox .tox-comment__body p{margin:0}.tox .tox-comment__buttonspacing{padding-top:16px;text-align:center}.tox .tox-comment-thread__overlay::after{background:#2b3b4e;bottom:0;content:"";display:flex;left:0;opacity:.9;position:absolute;right:0;top:0;z-index:5}.tox .tox-comment__reply{display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;margin-top:8px}.tox .tox-comment__reply>:first-child{margin-bottom:8px;width:100%}.tox .tox-comment__edit{display:flex;flex-wrap:wrap;justify-content:flex-end;margin-top:16px}.tox .tox-comment__gradient::after{background:linear-gradient(rgba(43,59,78,0),#2b3b4e);bottom:0;content:"";display:block;height:5em;margin-top:-40px;position:absolute;width:100%}.tox .tox-comment__overlay{background:#2b3b4e;bottom:0;display:flex;flex-direction:column;flex-grow:1;left:0;opacity:.9;position:absolute;right:0;text-align:center;top:0;z-index:5}.tox .tox-comment__loading-text{align-items:center;color:#fff;display:flex;flex-direction:column;position:relative}.tox .tox-comment__loading-text>div{padding-bottom:16px}.tox .tox-comment__overlaytext{bottom:0;flex-direction:column;font-size:14px;left:0;padding:1em;position:absolute;right:0;top:0;z-index:10}.tox .tox-comment__overlaytext p{background-color:#2b3b4e;box-shadow:0 0 8px 8px #2b3b4e;color:#fff;text-align:center}.tox .tox-comment__overlaytext div:nth-of-type(2){font-size:.8em}.tox .tox-comment__busy-spinner{align-items:center;background-color:#2b3b4e;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:20}.tox .tox-comment__scroll{display:flex;flex-direction:column;flex-shrink:1;overflow:auto}.tox .tox-conversations{margin:8px}.tox:not([dir=rtl]) .tox-comment__edit{margin-left:8px}.tox:not([dir=rtl]) .tox-comment__buttonspacing>:last-child,.tox:not([dir=rtl]) .tox-comment__edit>:last-child,.tox:not([dir=rtl]) .tox-comment__reply>:last-child{margin-left:8px}.tox[dir=rtl] .tox-comment__edit{margin-right:8px}.tox[dir=rtl] .tox-comment__buttonspacing>:last-child,.tox[dir=rtl] .tox-comment__edit>:last-child,.tox[dir=rtl] .tox-comment__reply>:last-child{margin-right:8px}.tox .tox-user{align-items:center;display:flex}.tox .tox-user__avatar svg{fill:rgba(255,255,255,.5)}.tox .tox-user__name{color:rgba(255,255,255,.5);font-size:12px;font-style:normal;font-weight:700;text-transform:uppercase}.tox:not([dir=rtl]) .tox-user__avatar svg{margin-right:8px}.tox:not([dir=rtl]) .tox-user__avatar+.tox-user__name{margin-left:8px}.tox[dir=rtl] .tox-user__avatar svg{margin-left:8px}.tox[dir=rtl] .tox-user__avatar+.tox-user__name{margin-right:8px}.tox .tox-dialog-wrap{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0;z-index:1100}.tox .tox-dialog-wrap__backdrop{background-color:rgba(34,47,62,.75);bottom:0;left:0;position:absolute;right:0;top:0;z-index:1}.tox .tox-dialog-wrap__backdrop--opaque{background-color:#222f3e}.tox .tox-dialog{background-color:#2b3b4e;border-color:#000;border-radius:3px;border-style:solid;border-width:1px;box-shadow:0 16px 16px -10px rgba(42,55,70,.15),0 0 40px 1px rgba(42,55,70,.15);display:flex;flex-direction:column;max-height:100%;max-width:480px;overflow:hidden;position:relative;width:95vw;z-index:2}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog{align-self:flex-start;margin:8px auto;width:calc(100vw - 16px)}}.tox .tox-dialog-inline{z-index:1100}.tox .tox-dialog__header{align-items:center;background-color:#2b3b4e;border-bottom:none;color:#fff;display:flex;font-size:16px;justify-content:space-between;padding:8px 16px 0 16px;position:relative}.tox .tox-dialog__header .tox-button{z-index:1}.tox .tox-dialog__draghandle{cursor:grab;height:100%;left:0;position:absolute;top:0;width:100%}.tox .tox-dialog__draghandle:active{cursor:grabbing}.tox .tox-dialog__dismiss{margin-left:auto}.tox .tox-dialog__title{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:20px;font-style:normal;font-weight:400;line-height:1.3;margin:0;text-transform:none}.tox .tox-dialog__body{color:#fff;display:flex;flex:1;-ms-flex-preferred-size:auto;font-size:16px;font-style:normal;font-weight:400;line-height:1.3;min-width:0;text-align:left;text-transform:none}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body{flex-direction:column}}.tox .tox-dialog__body-nav{align-items:flex-start;display:flex;flex-direction:column;padding:16px 16px}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body-nav{flex-direction:row;-webkit-overflow-scrolling:touch;overflow-x:auto;padding-bottom:0}}.tox .tox-dialog__body-nav-item{border-bottom:2px solid transparent;color:rgba(255,255,255,.5);display:inline-block;font-size:14px;line-height:1.3;margin-bottom:8px;text-decoration:none;white-space:nowrap}.tox .tox-dialog__body-nav-item:focus{background-color:rgba(32,122,183,.1)}.tox .tox-dialog__body-nav-item--active{border-bottom:2px solid #207ab7;color:#207ab7}.tox .tox-dialog__body-content{box-sizing:border-box;display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto;max-height:650px;overflow:auto;-webkit-overflow-scrolling:touch;padding:16px 16px}.tox .tox-dialog__body-content>*{margin-bottom:0;margin-top:16px}.tox .tox-dialog__body-content>:first-child{margin-top:0}.tox .tox-dialog__body-content>:last-child{margin-bottom:0}.tox .tox-dialog__body-content>:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog__body-content a{color:#207ab7;cursor:pointer;text-decoration:none}.tox .tox-dialog__body-content a:focus,.tox .tox-dialog__body-content a:hover{color:#185d8c;text-decoration:none}.tox .tox-dialog__body-content a:active{color:#185d8c;text-decoration:none}.tox .tox-dialog__body-content svg{fill:#fff}.tox .tox-dialog__body-content ul{display:block;list-style-type:disc;margin-bottom:16px;-webkit-margin-end:0;margin-inline-end:0;-webkit-margin-start:0;margin-inline-start:0;-webkit-padding-start:2.5rem;padding-inline-start:2.5rem}.tox .tox-dialog__body-content .tox-form__group h1{color:#fff;font-size:20px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group h2{color:#fff;font-size:16px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group p{margin-bottom:16px}.tox .tox-dialog__body-content .tox-form__group h1:first-child,.tox .tox-dialog__body-content .tox-form__group h2:first-child,.tox .tox-dialog__body-content .tox-form__group p:first-child{margin-top:0}.tox .tox-dialog__body-content .tox-form__group h1:last-child,.tox .tox-dialog__body-content .tox-form__group h2:last-child,.tox .tox-dialog__body-content .tox-form__group p:last-child{margin-bottom:0}.tox .tox-dialog__body-content .tox-form__group h1:only-child,.tox .tox-dialog__body-content .tox-form__group h2:only-child,.tox .tox-dialog__body-content .tox-form__group p:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog--width-lg{height:650px;max-width:1200px}.tox .tox-dialog--width-md{max-width:800px}.tox .tox-dialog--width-md .tox-dialog__body-content{overflow:auto}.tox .tox-dialog__body-content--centered{text-align:center}.tox .tox-dialog__footer{align-items:center;background-color:#2b3b4e;border-top:1px solid #000;display:flex;justify-content:space-between;padding:8px 16px}.tox .tox-dialog__footer-end,.tox .tox-dialog__footer-start{display:flex}.tox .tox-dialog__busy-spinner{align-items:center;background-color:rgba(34,47,62,.75);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:3}.tox .tox-dialog__table{border-collapse:collapse;width:100%}.tox .tox-dialog__table thead th{font-weight:700;padding-bottom:8px}.tox .tox-dialog__table tbody tr{border-bottom:1px solid #000}.tox .tox-dialog__table tbody tr:last-child{border-bottom:none}.tox .tox-dialog__table td{padding-bottom:8px;padding-top:8px}.tox .tox-dialog__popups{position:absolute;width:100%;z-index:1100}.tox .tox-dialog__body-iframe{display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto}.tox .tox-dialog__body-iframe .tox-navobj{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2){flex:1;-ms-flex-preferred-size:auto;height:100%}.tox .tox-dialog-dock-fadeout{opacity:0;visibility:hidden}.tox .tox-dialog-dock-fadein{opacity:1;visibility:visible}.tox .tox-dialog-dock-transition{transition:visibility 0s linear .3s,opacity .3s ease}.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein{transition-delay:0s}.tox.tox-platform-ie .tox-dialog-wrap{position:-ms-device-fixed}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav{margin-right:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child){margin-left:8px}}.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end>*,.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start>*{margin-left:8px}.tox[dir=rtl] .tox-dialog__body{text-align:right}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav{margin-left:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child){margin-right:8px}}.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end>*,.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start>*{margin-right:8px}body.tox-dialog__disable-scroll{overflow:hidden}.tox .tox-dropzone-container{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-dropzone{align-items:center;background:#fff;border:2px dashed #000;box-sizing:border-box;display:flex;flex-direction:column;flex-grow:1;justify-content:center;min-height:100px;padding:10px}.tox .tox-dropzone p{color:rgba(255,255,255,.5);margin:0 0 16px 0}.tox .tox-edit-area{display:flex;flex:1;-ms-flex-preferred-size:auto;overflow:hidden;position:relative}.tox .tox-edit-area__iframe{background-color:#fff;border:0;box-sizing:border-box;flex:1;-ms-flex-preferred-size:auto;height:100%;position:absolute;width:100%}.tox.tox-inline-edit-area{border:1px dotted #000}.tox .tox-editor-container{display:flex;flex:1 1 auto;flex-direction:column;overflow:hidden}.tox .tox-editor-header{z-index:1}.tox:not(.tox-tinymce-inline) .tox-editor-header{box-shadow:none;transition:box-shadow .5s}.tox.tox-tinymce--toolbar-bottom .tox-editor-header,.tox.tox-tinymce-inline .tox-editor-header{margin-bottom:-1px}.tox.tox-tinymce--toolbar-sticky-on .tox-editor-header{background-color:transparent;box-shadow:0 4px 4px -3px rgba(0,0,0,.25)}.tox-editor-dock-fadeout{opacity:0;visibility:hidden}.tox-editor-dock-fadein{opacity:1;visibility:visible}.tox-editor-dock-transition{transition:visibility 0s linear .25s,opacity .25s ease}.tox-editor-dock-transition.tox-editor-dock-fadein{transition-delay:0s}.tox .tox-control-wrap{flex:1;position:relative}.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid{display:none}.tox .tox-control-wrap svg{display:block}.tox .tox-control-wrap__status-icon-wrap{position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-control-wrap__status-icon-invalid svg{fill:#c00}.tox .tox-control-wrap__status-icon-unknown svg{fill:orange}.tox .tox-control-wrap__status-icon-valid svg{fill:green}.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield{padding-right:32px}.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap{right:4px}.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield{padding-left:32px}.tox[dir=rtl] .tox-control-wrap__status-icon-wrap{left:4px}.tox .tox-autocompleter{max-width:25em}.tox .tox-autocompleter .tox-menu{max-width:25em}.tox .tox-autocompleter .tox-autocompleter-highlight{font-weight:700}.tox .tox-color-input{display:flex;position:relative;z-index:1}.tox .tox-color-input .tox-textfield{z-index:-1}.tox .tox-color-input span{border-color:rgba(42,55,70,.2);border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;height:24px;position:absolute;top:6px;width:24px}.tox .tox-color-input span:focus:not([aria-disabled=true]),.tox .tox-color-input span:hover:not([aria-disabled=true]){border-color:#207ab7;cursor:pointer}.tox .tox-color-input span::before{background-image:linear-gradient(45deg,rgba(255,255,255,.25) 25%,transparent 25%),linear-gradient(-45deg,rgba(255,255,255,.25) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(255,255,255,.25) 75%),linear-gradient(-45deg,transparent 75%,rgba(255,255,255,.25) 75%);background-position:0 0,0 6px,6px -6px,-6px 0;background-size:12px 12px;border:1px solid #2b3b4e;border-radius:3px;box-sizing:border-box;content:'';height:24px;left:-1px;position:absolute;top:-1px;width:24px;z-index:-1}.tox .tox-color-input span[aria-disabled=true]{cursor:not-allowed}.tox:not([dir=rtl]) .tox-color-input .tox-textfield{padding-left:36px}.tox:not([dir=rtl]) .tox-color-input span{left:6px}.tox[dir=rtl] .tox-color-input .tox-textfield{padding-right:36px}.tox[dir=rtl] .tox-color-input span{right:6px}.tox .tox-label,.tox .tox-toolbar-label{color:rgba(255,255,255,.5);display:block;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;padding:0 8px 0 0;text-transform:none;white-space:nowrap}.tox .tox-toolbar-label{padding:0 8px}.tox[dir=rtl] .tox-label{padding:0 0 0 8px}.tox .tox-form{display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto}.tox .tox-form__group{box-sizing:border-box;margin-bottom:4px}.tox .tox-form-group--maximize{flex:1}.tox .tox-form__group--error{color:#c00}.tox .tox-form__group--collection{display:flex}.tox .tox-form__grid{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between}.tox .tox-form__grid--2col>.tox-form__group{width:calc(50% - (8px / 2))}.tox .tox-form__grid--3col>.tox-form__group{width:calc(100% / 3 - (8px / 2))}.tox .tox-form__grid--4col>.tox-form__group{width:calc(25% - (8px / 2))}.tox .tox-form__controls-h-stack{align-items:center;display:flex}.tox .tox-form__group--inline{align-items:center;display:flex}.tox .tox-form__group--stretched{display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto}.tox .tox-form__group--stretched .tox-textarea{flex:1;-ms-flex-preferred-size:auto}.tox .tox-form__group--stretched .tox-navobj{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-form__group--stretched .tox-navobj :nth-child(2){flex:1;-ms-flex-preferred-size:auto;height:100%}.tox:not([dir=rtl]) .tox-form__controls-h-stack>:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-form__controls-h-stack>:not(:first-child){margin-right:4px}.tox .tox-lock.tox-locked .tox-lock-icon__unlock,.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock{display:none}.tox .tox-listboxfield .tox-listbox--select,.tox .tox-textarea,.tox .tox-textfield,.tox .tox-toolbar-textfield{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#2b3b4e;border-color:#000;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 4.75px;resize:none;width:100%}.tox .tox-textarea[disabled],.tox .tox-textfield[disabled]{background-color:#222f3e;color:rgba(255,255,255,.85);cursor:not-allowed}.tox .tox-listboxfield .tox-listbox--select:focus,.tox .tox-textarea:focus,.tox .tox-textfield:focus{background-color:#2b3b4e;border-color:#207ab7;box-shadow:none;outline:0}.tox .tox-toolbar-textfield{border-width:0;margin-bottom:3px;margin-top:2px;max-width:250px}.tox .tox-naked-btn{background-color:transparent;border:0;border-color:transparent;box-shadow:unset;color:#207ab7;cursor:pointer;display:block;margin:0;padding:0}.tox .tox-naked-btn svg{display:block;fill:#fff}.tox:not([dir=rtl]) .tox-toolbar-textfield+*{margin-left:4px}.tox[dir=rtl] .tox-toolbar-textfield+*{margin-right:4px}.tox .tox-listboxfield{cursor:pointer;position:relative}.tox .tox-listboxfield .tox-listbox--select[disabled]{background-color:#19232e;color:rgba(255,255,255,.85);cursor:not-allowed}.tox .tox-listbox__select-label{cursor:default;flex:1;margin:0 4px}.tox .tox-listbox__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-listbox__select-chevron svg{fill:#fff}.tox .tox-listboxfield .tox-listbox--select{align-items:center;display:flex}.tox:not([dir=rtl]) .tox-listboxfield svg{right:8px}.tox[dir=rtl] .tox-listboxfield svg{left:8px}.tox .tox-selectfield{cursor:pointer;position:relative}.tox .tox-selectfield select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#2b3b4e;border-color:#000;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 4.75px;resize:none;width:100%}.tox .tox-selectfield select[disabled]{background-color:#19232e;color:rgba(255,255,255,.85);cursor:not-allowed}.tox .tox-selectfield select::-ms-expand{display:none}.tox .tox-selectfield select:focus{background-color:#2b3b4e;border-color:#207ab7;box-shadow:none;outline:0}.tox .tox-selectfield svg{pointer-events:none;position:absolute;top:50%;transform:translateY(-50%)}.tox:not([dir=rtl]) .tox-selectfield select[size="0"],.tox:not([dir=rtl]) .tox-selectfield select[size="1"]{padding-right:24px}.tox:not([dir=rtl]) .tox-selectfield svg{right:8px}.tox[dir=rtl] .tox-selectfield select[size="0"],.tox[dir=rtl] .tox-selectfield select[size="1"]{padding-left:24px}.tox[dir=rtl] .tox-selectfield svg{left:8px}.tox .tox-textarea{-webkit-appearance:textarea;-moz-appearance:textarea;appearance:textarea;white-space:pre-wrap}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}.tox .tox-help__more-link{list-style:none;margin-top:1em}.tox .tox-image-tools{width:100%}.tox .tox-image-tools__toolbar{align-items:center;display:flex;justify-content:center}.tox .tox-image-tools__image{background-color:#666;height:380px;overflow:auto;position:relative;width:100%}.tox .tox-image-tools__image,.tox .tox-image-tools__image+.tox-image-tools__toolbar{margin-top:8px}.tox .tox-image-tools__image-bg{background:url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==)}.tox .tox-image-tools__toolbar>.tox-spacer{flex:1;-ms-flex-preferred-size:auto}.tox .tox-croprect-block{background:#000;opacity:.5;position:absolute;zoom:1}.tox .tox-croprect-handle{border:2px solid #fff;height:20px;left:0;position:absolute;top:0;width:20px}.tox .tox-croprect-handle-move{border:0;cursor:move;position:absolute}.tox .tox-croprect-handle-nw{border-width:2px 0 0 2px;cursor:nw-resize;left:100px;margin:-2px 0 0 -2px;top:100px}.tox .tox-croprect-handle-ne{border-width:2px 2px 0 0;cursor:ne-resize;left:200px;margin:-2px 0 0 -20px;top:100px}.tox .tox-croprect-handle-sw{border-width:0 0 2px 2px;cursor:sw-resize;left:100px;margin:-20px 2px 0 -2px;top:200px}.tox .tox-croprect-handle-se{border-width:0 2px 2px 0;cursor:se-resize;left:200px;margin:-20px 0 0 -20px;top:200px}.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-slider:not(:first-of-type){margin-left:8px}.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-button+.tox-slider{margin-left:32px}.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-slider+.tox-button{margin-left:32px}.tox[dir=rtl] .tox-image-tools__toolbar>.tox-slider:not(:first-of-type){margin-right:8px}.tox[dir=rtl] .tox-image-tools__toolbar>.tox-button+.tox-slider{margin-right:32px}.tox[dir=rtl] .tox-image-tools__toolbar>.tox-slider+.tox-button{margin-right:32px}.tox .tox-insert-table-picker{display:flex;flex-wrap:wrap;width:170px}.tox .tox-insert-table-picker>div{border-color:#000;border-style:solid;border-width:0 1px 1px 0;box-sizing:border-box;height:17px;width:17px}.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker{margin:-4px 0}.tox .tox-insert-table-picker .tox-insert-table-picker__selected{background-color:rgba(32,122,183,.5);border-color:rgba(32,122,183,.5)}.tox .tox-insert-table-picker__label{color:#fff;display:block;font-size:14px;padding:4px;text-align:center;width:100%}.tox:not([dir=rtl]) .tox-insert-table-picker>div:nth-child(10n){border-right:0}.tox[dir=rtl] .tox-insert-table-picker>div:nth-child(10n+1){border-right:0}.tox .tox-menu{background-color:#2b3b4e;border:1px solid #000;border-radius:3px;box-shadow:0 4px 8px 0 rgba(42,55,70,.1);display:inline-block;overflow:hidden;vertical-align:top;z-index:1150}.tox .tox-menu.tox-collection.tox-collection--list{padding:0}.tox .tox-menu.tox-collection.tox-collection--toolbar{padding:4px}.tox .tox-menu.tox-collection.tox-collection--grid{padding:4px}.tox .tox-menu__label blockquote,.tox .tox-menu__label code,.tox .tox-menu__label h1,.tox .tox-menu__label h2,.tox .tox-menu__label h3,.tox .tox-menu__label h4,.tox .tox-menu__label h5,.tox .tox-menu__label h6,.tox .tox-menu__label p{margin:0}.tox .tox-menubar{background:url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E") left 0 top 0 #222f3e;background-color:#222f3e;display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;padding:0 4px 0 4px}.tox.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar{border-top:1px solid #000}.tox .tox-mbtn{align-items:center;background:0 0;border:0;border-radius:3px;box-shadow:none;color:#fff;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:34px;justify-content:center;margin:2px 0 3px 0;outline:0;overflow:hidden;padding:0 4px;text-transform:none;width:auto}.tox .tox-mbtn[disabled]{background-color:transparent;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-mbtn:focus:not(:disabled){background:#4a5562;border:0;box-shadow:none;color:#fff}.tox .tox-mbtn--active{background:#757d87;border:0;box-shadow:none;color:#fff}.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active){background:#4a5562;border:0;box-shadow:none;color:#fff}.tox .tox-mbtn__select-label{cursor:default;font-weight:400;margin:0 4px}.tox .tox-mbtn[disabled] .tox-mbtn__select-label{cursor:not-allowed}.tox .tox-mbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px;display:none}.tox .tox-notification{border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;display:-ms-grid;display:grid;font-size:14px;font-weight:400;-ms-grid-columns:minmax(40px,1fr) auto minmax(40px,1fr);grid-template-columns:minmax(40px,1fr) auto minmax(40px,1fr);margin-top:4px;opacity:0;padding:4px;transition:transform .1s ease-in,opacity 150ms ease-in}.tox .tox-notification p{font-size:14px;font-weight:400}.tox .tox-notification a{cursor:pointer;text-decoration:underline}.tox .tox-notification--in{opacity:1}.tox .tox-notification--success{background-color:#e4eeda;border-color:#d7e6c8;color:#fff}.tox .tox-notification--success p{color:#fff}.tox .tox-notification--success a{color:#547831}.tox .tox-notification--success svg{fill:#fff}.tox .tox-notification--error{background-color:#f8dede;border-color:#f2bfbf;color:#fff}.tox .tox-notification--error p{color:#fff}.tox .tox-notification--error a{color:#c00}.tox .tox-notification--error svg{fill:#fff}.tox .tox-notification--warn,.tox .tox-notification--warning{background-color:#fffaea;border-color:#ffe89d;color:#fff}.tox .tox-notification--warn p,.tox .tox-notification--warning p{color:#fff}.tox .tox-notification--warn a,.tox .tox-notification--warning a{color:#fff}.tox .tox-notification--warn svg,.tox .tox-notification--warning svg{fill:#fff}.tox .tox-notification--info{background-color:#d9edf7;border-color:#779ecb;color:#fff}.tox .tox-notification--info p{color:#fff}.tox .tox-notification--info a{color:#fff}.tox .tox-notification--info svg{fill:#fff}.tox .tox-notification__body{-ms-grid-row-align:center;align-self:center;color:#fff;font-size:14px;-ms-grid-column-span:1;grid-column-end:3;-ms-grid-column:2;grid-column-start:2;-ms-grid-row-span:1;grid-row-end:2;-ms-grid-row:1;grid-row-start:1;text-align:center;white-space:normal;word-break:break-all;word-break:break-word}.tox .tox-notification__body>*{margin:0}.tox .tox-notification__body>*+*{margin-top:1rem}.tox .tox-notification__icon{-ms-grid-row-align:center;align-self:center;-ms-grid-column-span:1;grid-column-end:2;-ms-grid-column:1;grid-column-start:1;-ms-grid-row-span:1;grid-row-end:2;-ms-grid-row:1;grid-row-start:1;-ms-grid-column-align:end;justify-self:end}.tox .tox-notification__icon svg{display:block}.tox .tox-notification__dismiss{-ms-grid-row-align:start;align-self:start;-ms-grid-column-span:1;grid-column-end:4;-ms-grid-column:3;grid-column-start:3;-ms-grid-row-span:1;grid-row-end:2;-ms-grid-row:1;grid-row-start:1;-ms-grid-column-align:end;justify-self:end}.tox .tox-notification .tox-progress-bar{-ms-grid-column-span:3;grid-column-end:4;-ms-grid-column:1;grid-column-start:1;-ms-grid-row-span:1;grid-row-end:3;-ms-grid-row:2;grid-row-start:2;-ms-grid-column-align:center;justify-self:center}.tox .tox-pop{display:inline-block;position:relative}.tox .tox-pop--resizing{transition:width .1s ease}.tox .tox-pop--resizing .tox-toolbar,.tox .tox-pop--resizing .tox-toolbar__group{flex-wrap:nowrap}.tox .tox-pop--transition{transition:.15s ease;transition-property:left,right,top,bottom}.tox .tox-pop--transition::after,.tox .tox-pop--transition::before{transition:all .15s,visibility 0s,opacity 75ms ease 75ms}.tox .tox-pop__dialog{background-color:#222f3e;border:1px solid #000;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15);min-width:0;overflow:hidden}.tox .tox-pop__dialog>:not(.tox-toolbar){margin:4px 4px 4px 8px}.tox .tox-pop__dialog .tox-toolbar{background-color:transparent;margin-bottom:-1px}.tox .tox-pop::after,.tox .tox-pop::before{border-style:solid;content:'';display:block;height:0;opacity:1;position:absolute;width:0}.tox .tox-pop.tox-pop--inset::after,.tox .tox-pop.tox-pop--inset::before{opacity:0;transition:all 0s .15s,visibility 0s,opacity 75ms ease}.tox .tox-pop.tox-pop--bottom::after,.tox .tox-pop.tox-pop--bottom::before{left:50%;top:100%}.tox .tox-pop.tox-pop--bottom::after{border-color:#222f3e transparent transparent transparent;border-width:8px;margin-left:-8px;margin-top:-1px}.tox .tox-pop.tox-pop--bottom::before{border-color:#000 transparent transparent transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--top::after,.tox .tox-pop.tox-pop--top::before{left:50%;top:0;transform:translateY(-100%)}.tox .tox-pop.tox-pop--top::after{border-color:transparent transparent #222f3e transparent;border-width:8px;margin-left:-8px;margin-top:1px}.tox .tox-pop.tox-pop--top::before{border-color:transparent transparent #000 transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--left::after,.tox .tox-pop.tox-pop--left::before{left:0;top:calc(50% - 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--left::after{border-color:transparent #222f3e transparent transparent;border-width:8px;margin-left:-15px}.tox .tox-pop.tox-pop--left::before{border-color:transparent #000 transparent transparent;border-width:10px;margin-left:-19px}.tox .tox-pop.tox-pop--right::after,.tox .tox-pop.tox-pop--right::before{left:100%;top:calc(50% + 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--right::after{border-color:transparent transparent transparent #222f3e;border-width:8px;margin-left:-1px}.tox .tox-pop.tox-pop--right::before{border-color:transparent transparent transparent #000;border-width:10px;margin-left:-1px}.tox .tox-pop.tox-pop--align-left::after,.tox .tox-pop.tox-pop--align-left::before{left:20px}.tox .tox-pop.tox-pop--align-right::after,.tox .tox-pop.tox-pop--align-right::before{left:calc(100% - 20px)}.tox .tox-sidebar-wrap{display:flex;flex-direction:row;flex-grow:1;-ms-flex-preferred-size:0;min-height:0}.tox .tox-sidebar{background-color:#222f3e;display:flex;flex-direction:row;justify-content:flex-end}.tox .tox-sidebar__slider{display:flex;overflow:hidden}.tox .tox-sidebar__pane-container{display:flex}.tox .tox-sidebar__pane{display:flex}.tox .tox-sidebar--sliding-closed{opacity:0}.tox .tox-sidebar--sliding-open{opacity:1}.tox .tox-sidebar--sliding-growing,.tox .tox-sidebar--sliding-shrinking{transition:width .5s ease,opacity .5s ease}.tox .tox-selector{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;display:inline-block;height:10px;position:absolute;width:10px}.tox.tox-platform-touch .tox-selector{height:12px;width:12px}.tox .tox-slider{align-items:center;display:flex;flex:1;-ms-flex-preferred-size:auto;height:24px;justify-content:center;position:relative}.tox .tox-slider__rail{background-color:transparent;border:1px solid #000;border-radius:3px;height:10px;min-width:120px;width:100%}.tox .tox-slider__handle{background-color:#207ab7;border:2px solid #185d8c;border-radius:3px;box-shadow:none;height:24px;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:14px}.tox .tox-source-code{overflow:auto}.tox .tox-spinner{display:flex}.tox .tox-spinner>div{animation:tam-bouncing-dots 1.5s ease-in-out 0s infinite both;background-color:rgba(255,255,255,.5);border-radius:100%;height:8px;width:8px}.tox .tox-spinner>div:nth-child(1){animation-delay:-.32s}.tox .tox-spinner>div:nth-child(2){animation-delay:-.16s}@keyframes tam-bouncing-dots{0%,100%,80%{transform:scale(0)}40%{transform:scale(1)}}.tox:not([dir=rtl]) .tox-spinner>div:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-spinner>div:not(:first-child){margin-right:4px}.tox .tox-statusbar{align-items:center;background-color:#222f3e;border-top:1px solid #000;color:#fff;display:flex;flex:0 0 auto;font-size:12px;font-weight:400;height:18px;overflow:hidden;padding:0 8px;position:relative;text-transform:uppercase}.tox .tox-statusbar__text-container{display:flex;flex:1 1 auto;justify-content:flex-end;overflow:hidden}.tox .tox-statusbar__path{display:flex;flex:1 1 auto;margin-right:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tox .tox-statusbar__path>*{display:inline;white-space:nowrap}.tox .tox-statusbar__wordcount{flex:0 0 auto;margin-left:1ch}.tox .tox-statusbar a,.tox .tox-statusbar__path-item,.tox .tox-statusbar__wordcount{color:#fff;text-decoration:none}.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]){cursor:pointer;text-decoration:underline}.tox .tox-statusbar__resize-handle{align-items:flex-end;align-self:stretch;cursor:nwse-resize;display:flex;flex:0 0 auto;justify-content:flex-end;margin-left:auto;margin-right:-8px;padding-left:1ch}.tox .tox-statusbar__resize-handle svg{display:block;fill:#fff}.tox .tox-statusbar__resize-handle:focus svg{background-color:#4a5562;border-radius:1px;box-shadow:0 0 0 2px #4a5562}.tox:not([dir=rtl]) .tox-statusbar__path>*{margin-right:4px}.tox:not([dir=rtl]) .tox-statusbar__branding{margin-left:1ch}.tox[dir=rtl] .tox-statusbar{flex-direction:row-reverse}.tox[dir=rtl] .tox-statusbar__path>*{margin-left:4px}.tox .tox-throbber{z-index:1299}.tox .tox-throbber__busy-spinner{align-items:center;background-color:rgba(34,47,62,.6);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.tox .tox-tbtn{align-items:center;background:0 0;border:0;border-radius:3px;box-shadow:none;color:#fff;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:34px;justify-content:center;margin:2px 0 3px 0;outline:0;overflow:hidden;padding:0;text-transform:none;width:34px}.tox .tox-tbtn svg{display:block;fill:#fff}.tox .tox-tbtn.tox-tbtn-more{padding-left:5px;padding-right:5px;width:inherit}.tox .tox-tbtn:focus{background:#4a5562;border:0;box-shadow:none}.tox .tox-tbtn:hover{background:#4a5562;border:0;box-shadow:none;color:#fff}.tox .tox-tbtn:hover svg{fill:#fff}.tox .tox-tbtn:active{background:#757d87;border:0;box-shadow:none;color:#fff}.tox .tox-tbtn:active svg{fill:#fff}.tox .tox-tbtn--disabled,.tox .tox-tbtn--disabled:hover,.tox .tox-tbtn:disabled,.tox .tox-tbtn:disabled:hover{background:0 0;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-tbtn--disabled svg,.tox .tox-tbtn--disabled:hover svg,.tox .tox-tbtn:disabled svg,.tox .tox-tbtn:disabled:hover svg{fill:rgba(255,255,255,.5)}.tox .tox-tbtn--enabled,.tox .tox-tbtn--enabled:hover{background:#757d87;border:0;box-shadow:none;color:#fff}.tox .tox-tbtn--enabled:hover>*,.tox .tox-tbtn--enabled>*{transform:none}.tox .tox-tbtn--enabled svg,.tox .tox-tbtn--enabled:hover svg{fill:#fff}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled){color:#fff}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg{fill:#fff}.tox .tox-tbtn:active>*{transform:none}.tox .tox-tbtn--md{height:51px;width:51px}.tox .tox-tbtn--lg{flex-direction:column;height:68px;width:68px}.tox .tox-tbtn--return{-ms-grid-row-align:stretch;align-self:stretch;height:unset;width:16px}.tox .tox-tbtn--labeled{padding:0 4px;width:unset}.tox .tox-tbtn__vlabel{display:block;font-size:10px;font-weight:400;letter-spacing:-.025em;margin-bottom:4px;white-space:nowrap}.tox .tox-tbtn--select{margin:2px 0 3px 0;padding:0 4px;width:auto}.tox .tox-tbtn__select-label{cursor:default;font-weight:400;margin:0 4px}.tox .tox-tbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-tbtn__select-chevron svg{fill:rgba(255,255,255,.5)}.tox .tox-tbtn--bespoke .tox-tbtn__select-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:7em}.tox .tox-split-button{border:0;border-radius:3px;box-sizing:border-box;display:flex;margin:2px 0 3px 0;overflow:hidden}.tox .tox-split-button:hover{box-shadow:0 0 0 1px #4a5562 inset}.tox .tox-split-button:focus{background:#4a5562;box-shadow:none;color:#fff}.tox .tox-split-button>*{border-radius:0}.tox .tox-split-button__chevron{width:16px}.tox .tox-split-button__chevron svg{fill:rgba(255,255,255,.5)}.tox .tox-split-button .tox-tbtn{margin:0}.tox.tox-platform-touch .tox-split-button .tox-tbtn:first-child{width:30px}.tox.tox-platform-touch .tox-split-button__chevron{width:20px}.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus,.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,.tox .tox-split-button.tox-tbtn--disabled:focus,.tox .tox-split-button.tox-tbtn--disabled:hover{background:0 0;box-shadow:none;color:rgba(255,255,255,.5)}.tox .tox-toolbar-overlord{background-color:#222f3e}.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary{background:url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E") left 0 top 0 #222f3e;background-color:#222f3e;display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;padding:0 0}.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed{height:0;opacity:0;padding-bottom:0;padding-top:0;visibility:hidden}.tox .tox-toolbar__overflow--growing{transition:height .3s ease,opacity .2s linear .1s}.tox .tox-toolbar__overflow--shrinking{transition:opacity .3s ease,height .2s linear .1s,visibility 0s linear .3s}.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord .tox-toolbar__primary{border-top:1px solid #000;margin-top:-1px}.tox .tox-toolbar--scrolling{flex-wrap:nowrap;overflow-x:auto}.tox .tox-pop .tox-toolbar{border-width:0}.tox .tox-toolbar--no-divider{background-image:none}.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary,.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child{border-top:1px solid #000}.tox.tox-tinymce-aux .tox-toolbar__overflow{background-color:#222f3e;border:1px solid #000;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15)}.tox .tox-toolbar__group{align-items:center;display:flex;flex-wrap:wrap;margin:0 0;padding:0 4px 0 4px}.tox .tox-toolbar__group--pull-right{margin-left:auto}.tox .tox-toolbar--scrolling .tox-toolbar__group{flex-shrink:0;flex-wrap:nowrap}.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type){border-right:1px solid #000}.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type){border-left:1px solid #000}.tox .tox-tooltip{display:inline-block;padding:8px;position:relative}.tox .tox-tooltip__body{background-color:#3d546f;border-radius:3px;box-shadow:0 2px 4px rgba(42,55,70,.3);color:rgba(255,255,255,.75);font-size:14px;font-style:normal;font-weight:400;padding:4px 8px;text-transform:none}.tox .tox-tooltip__arrow{position:absolute}.tox .tox-tooltip--down .tox-tooltip__arrow{border-left:8px solid transparent;border-right:8px solid transparent;border-top:8px solid #3d546f;bottom:0;left:50%;position:absolute;transform:translateX(-50%)}.tox .tox-tooltip--up .tox-tooltip__arrow{border-bottom:8px solid #3d546f;border-left:8px solid transparent;border-right:8px solid transparent;left:50%;position:absolute;top:0;transform:translateX(-50%)}.tox .tox-tooltip--right .tox-tooltip__arrow{border-bottom:8px solid transparent;border-left:8px solid #3d546f;border-top:8px solid transparent;position:absolute;right:0;top:50%;transform:translateY(-50%)}.tox .tox-tooltip--left .tox-tooltip__arrow{border-bottom:8px solid transparent;border-right:8px solid #3d546f;border-top:8px solid transparent;left:0;position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-well{border:1px solid #000;border-radius:3px;padding:8px;width:100%}.tox .tox-well>:first-child{margin-top:0}.tox .tox-well>:last-child{margin-bottom:0}.tox .tox-well>:only-child{margin:0}.tox .tox-custom-editor{border:1px solid #000;border-radius:3px;display:flex;flex:1;position:relative}.tox .tox-dialog-loading::before{background-color:rgba(0,0,0,.5);content:"";height:100%;position:absolute;width:100%;z-index:1000}.tox .tox-tab{cursor:pointer}.tox .tox-dialog__content-js{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-dialog__body-content .tox-collection{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-image-tools-edit-panel{height:60px}.tox .tox-image-tools__sidebar{height:60px} diff --git a/public/tinymce/skins/ui/oxide-dark/skin.mobile.css b/public/tinymce/skins/ui/oxide-dark/skin.mobile.css new file mode 100644 index 0000000..875721a --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/skin.mobile.css @@ -0,0 +1,673 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +/* RESET all the things! */ +.tinymce-mobile-outer-container { + all: initial; + display: block; +} +.tinymce-mobile-outer-container * { + border: 0; + box-sizing: initial; + cursor: inherit; + float: none; + line-height: 1; + margin: 0; + outline: 0; + padding: 0; + -webkit-tap-highlight-color: transparent; + /* TBIO-3691, stop the gray flicker on touch. */ + text-shadow: none; + white-space: nowrap; +} +.tinymce-mobile-icon-arrow-back::before { + content: "\e5cd"; +} +.tinymce-mobile-icon-image::before { + content: "\e412"; +} +.tinymce-mobile-icon-cancel-circle::before { + content: "\e5c9"; +} +.tinymce-mobile-icon-full-dot::before { + content: "\e061"; +} +.tinymce-mobile-icon-align-center::before { + content: "\e234"; +} +.tinymce-mobile-icon-align-left::before { + content: "\e236"; +} +.tinymce-mobile-icon-align-right::before { + content: "\e237"; +} +.tinymce-mobile-icon-bold::before { + content: "\e238"; +} +.tinymce-mobile-icon-italic::before { + content: "\e23f"; +} +.tinymce-mobile-icon-unordered-list::before { + content: "\e241"; +} +.tinymce-mobile-icon-ordered-list::before { + content: "\e242"; +} +.tinymce-mobile-icon-font-size::before { + content: "\e245"; +} +.tinymce-mobile-icon-underline::before { + content: "\e249"; +} +.tinymce-mobile-icon-link::before { + content: "\e157"; +} +.tinymce-mobile-icon-unlink::before { + content: "\eca2"; +} +.tinymce-mobile-icon-color::before { + content: "\e891"; +} +.tinymce-mobile-icon-previous::before { + content: "\e314"; +} +.tinymce-mobile-icon-next::before { + content: "\e315"; +} +.tinymce-mobile-icon-large-font::before, +.tinymce-mobile-icon-style-formats::before { + content: "\e264"; +} +.tinymce-mobile-icon-undo::before { + content: "\e166"; +} +.tinymce-mobile-icon-redo::before { + content: "\e15a"; +} +.tinymce-mobile-icon-removeformat::before { + content: "\e239"; +} +.tinymce-mobile-icon-small-font::before { + content: "\e906"; +} +.tinymce-mobile-icon-readonly-back::before, +.tinymce-mobile-format-matches::after { + content: "\e5ca"; +} +.tinymce-mobile-icon-small-heading::before { + content: "small"; +} +.tinymce-mobile-icon-large-heading::before { + content: "large"; +} +.tinymce-mobile-icon-small-heading::before, +.tinymce-mobile-icon-large-heading::before { + font-family: sans-serif; + font-size: 80%; +} +.tinymce-mobile-mask-edit-icon::before { + content: "\e254"; +} +.tinymce-mobile-icon-back::before { + content: "\e5c4"; +} +.tinymce-mobile-icon-heading::before { + /* TODO: Translate */ + content: "Headings"; + font-family: sans-serif; + font-size: 80%; + font-weight: bold; +} +.tinymce-mobile-icon-h1::before { + content: "H1"; + font-weight: bold; +} +.tinymce-mobile-icon-h2::before { + content: "H2"; + font-weight: bold; +} +.tinymce-mobile-icon-h3::before { + content: "H3"; + font-weight: bold; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask { + align-items: center; + display: flex; + justify-content: center; + background: rgba(51, 51, 51, 0.5); + height: 100%; + position: absolute; + top: 0; + width: 100%; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container { + align-items: center; + border-radius: 50%; + display: flex; + flex-direction: column; + font-family: sans-serif; + font-size: 1em; + justify-content: space-between; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .mixin-menu-item { + align-items: center; + display: flex; + justify-content: center; + border-radius: 50%; + height: 2.1em; + width: 2.1em; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section { + align-items: center; + display: flex; + justify-content: center; + flex-direction: column; + font-size: 1em; +} +@media only screen and (min-device-width:700px) { + .tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section { + font-size: 1.2em; + } +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon { + align-items: center; + display: flex; + justify-content: center; + border-radius: 50%; + height: 2.1em; + width: 2.1em; + background-color: white; + color: #207ab7; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon::before { + content: "\e900"; + font-family: 'tinymce-mobile', sans-serif; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section:not(.tinymce-mobile-mask-tap-icon-selected) .tinymce-mobile-mask-tap-icon { + z-index: 2; +} +.tinymce-mobile-android-container.tinymce-mobile-android-maximized { + background: #ffffff; + border: none; + bottom: 0; + display: flex; + flex-direction: column; + left: 0; + position: fixed; + right: 0; + top: 0; +} +.tinymce-mobile-android-container:not(.tinymce-mobile-android-maximized) { + position: relative; +} +.tinymce-mobile-android-container .tinymce-mobile-editor-socket { + display: flex; + flex-grow: 1; +} +.tinymce-mobile-android-container .tinymce-mobile-editor-socket iframe { + display: flex !important; + flex-grow: 1; + height: auto !important; +} +.tinymce-mobile-android-scroll-reload { + overflow: hidden; +} +:not(.tinymce-mobile-readonly-mode) > .tinymce-mobile-android-selection-context-toolbar { + margin-top: 23px; +} +.tinymce-mobile-toolstrip { + background: #fff; + display: flex; + flex: 0 0 auto; + z-index: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar { + align-items: center; + background-color: #fff; + border-bottom: 1px solid #cccccc; + display: flex; + flex: 1; + height: 2.5em; + width: 100%; + /* Make it no larger than the toolstrip, so that it needs to scroll */ +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group { + align-items: center; + display: flex; + height: 100%; + flex-shrink: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group > div { + align-items: center; + display: flex; + height: 100%; + flex: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-exit-container { + background: #f44336; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-toolbar-scrollable-group { + flex-grow: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item { + padding-left: 0.5em; + padding-right: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button { + align-items: center; + display: flex; + height: 80%; + margin-left: 2px; + margin-right: 2px; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected { + background: #c8cbcf; + color: #cccccc; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:first-of-type, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:last-of-type { + background: #207ab7; + color: #eceff1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar { + /* Note, this file is imported inside .tinymce-mobile-context-toolbar, so that prefix is on everything here. */ +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group { + align-items: center; + display: flex; + height: 100%; + flex: 1; + padding-bottom: 0.4em; + padding-top: 0.4em; + /* Make any buttons appearing on the left and right display in the centre (e.g. color edges) */ + /* For widgets like the colour picker, use the whole height */ +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog { + display: flex; + min-height: 1.5em; + overflow: hidden; + padding-left: 0; + padding-right: 0; + position: relative; + width: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain { + display: flex; + height: 100%; + transition: left cubic-bezier(0.4, 0, 1, 1) 0.15s; + width: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen { + display: flex; + flex: 0 0 auto; + justify-content: space-between; + width: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen input { + font-family: Sans-serif; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container { + display: flex; + flex-grow: 1; + position: relative; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container .tinymce-mobile-input-container-x { + -ms-grid-row-align: center; + align-self: center; + background: inherit; + border: none; + border-radius: 50%; + color: #888; + font-size: 0.6em; + font-weight: bold; + height: 100%; + padding-right: 2px; + position: absolute; + right: 0; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container.tinymce-mobile-input-container-empty .tinymce-mobile-input-container-x { + display: none; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next { + align-items: center; + display: flex; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next::before { + align-items: center; + display: flex; + font-weight: bold; + height: 100%; + padding-left: 0.5em; + padding-right: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous.tinymce-mobile-toolbar-navigation-disabled::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next.tinymce-mobile-toolbar-navigation-disabled::before { + visibility: hidden; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item { + color: #cccccc; + font-size: 10px; + line-height: 10px; + margin: 0 2px; + padding-top: 3px; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item.tinymce-mobile-dot-active { + color: #c8cbcf; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-font::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-heading::before { + margin-left: 0.5em; + margin-right: 0.9em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-font::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-heading::before { + margin-left: 0.9em; + margin-right: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider { + display: flex; + flex: 1; + margin-left: 0; + margin-right: 0; + padding: 0.28em 0; + position: relative; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container { + align-items: center; + display: flex; + flex-grow: 1; + height: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container .tinymce-mobile-slider-size-line { + background: #cccccc; + display: flex; + flex: 1; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container { + padding-left: 2em; + padding-right: 2em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container { + align-items: center; + display: flex; + flex-grow: 1; + height: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container .tinymce-mobile-slider-gradient { + background: linear-gradient(to right, hsl(0, 100%, 50%) 0%, hsl(60, 100%, 50%) 17%, hsl(120, 100%, 50%) 33%, hsl(180, 100%, 50%) 50%, hsl(240, 100%, 50%) 67%, hsl(300, 100%, 50%) 83%, hsl(0, 100%, 50%) 100%); + display: flex; + flex: 1; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-black { + /* Not part of theming */ + background: black; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; + width: 1.2em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-white { + /* Not part of theming */ + background: white; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; + width: 1.2em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb { + /* vertically centering trick (margin: auto, top: 0, bottom: 0). On iOS and Safari, if you leave + * out these values, then it shows the thumb at the top of the spectrum. This is probably because it is + * absolutely positioned with only a left value, and not a top. Note, on Chrome it seems to be fine without + * this approach. + */ + align-items: center; + background-clip: padding-box; + background-color: #455a64; + border: 0.5em solid rgba(136, 136, 136, 0); + border-radius: 3em; + bottom: 0; + color: #fff; + display: flex; + height: 0.5em; + justify-content: center; + left: -10px; + margin: auto; + position: absolute; + top: 0; + transition: border 120ms cubic-bezier(0.39, 0.58, 0.57, 1); + width: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb.tinymce-mobile-thumb-active { + border: 0.5em solid rgba(136, 136, 136, 0.39); +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group > div { + align-items: center; + display: flex; + height: 100%; + flex: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper { + flex-direction: column; + justify-content: center; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item { + align-items: center; + display: flex; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item:not(.tinymce-mobile-serialised-dialog) { + height: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-container { + display: flex; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input { + background: #ffffff; + border: none; + border-radius: 0; + color: #455a64; + flex-grow: 1; + font-size: 0.85em; + padding-bottom: 0.1em; + padding-left: 5px; + padding-top: 0.1em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::-webkit-input-placeholder { + /* WebKit, Blink, Edge */ + color: #888; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::placeholder { + /* WebKit, Blink, Edge */ + color: #888; +} +/* dropup */ +.tinymce-mobile-dropup { + background: white; + display: flex; + overflow: hidden; + width: 100%; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-shrinking { + transition: height 0.3s ease-out; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-growing { + transition: height 0.3s ease-in; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-closed { + flex-grow: 0; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-open:not(.tinymce-mobile-dropup-growing) { + flex-grow: 1; +} +/* TODO min-height for device size and orientation */ +.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) { + min-height: 200px; +} +@media only screen and (orientation: landscape) { + .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) { + min-height: 200px; + } +} +@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) { + .tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) { + min-height: 150px; + } +} +/* styles menu */ +.tinymce-mobile-styles-menu { + font-family: sans-serif; + outline: 4px solid black; + overflow: hidden; + position: relative; + width: 100%; +} +.tinymce-mobile-styles-menu [role="menu"] { + display: flex; + flex-direction: column; + height: 100%; + position: absolute; + width: 100%; +} +.tinymce-mobile-styles-menu [role="menu"].transitioning { + transition: transform 0.5s ease-in-out; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item { + border-bottom: 1px solid #ddd; + color: #455a64; + cursor: pointer; + display: flex; + padding: 1em 1em; + position: relative; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser .tinymce-mobile-styles-collapse-icon::before { + color: #455a64; + content: "\e314"; + font-family: 'tinymce-mobile', sans-serif; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-styles-item-is-menu::after { + color: #455a64; + content: "\e315"; + font-family: 'tinymce-mobile', sans-serif; + padding-left: 1em; + padding-right: 1em; + position: absolute; + right: 0; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-format-matches::after { + font-family: 'tinymce-mobile', sans-serif; + padding-left: 1em; + padding-right: 1em; + position: absolute; + right: 0; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-separator, +.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser { + align-items: center; + background: #fff; + border-top: #455a64; + color: #455a64; + display: flex; + min-height: 2.5em; + padding-left: 1em; + padding-right: 1em; +} +.tinymce-mobile-styles-menu [data-transitioning-destination="before"][data-transitioning-state], +.tinymce-mobile-styles-menu [data-transitioning-state="before"] { + transform: translate(-100%); +} +.tinymce-mobile-styles-menu [data-transitioning-destination="current"][data-transitioning-state], +.tinymce-mobile-styles-menu [data-transitioning-state="current"] { + transform: translate(0%); +} +.tinymce-mobile-styles-menu [data-transitioning-destination="after"][data-transitioning-state], +.tinymce-mobile-styles-menu [data-transitioning-state="after"] { + transform: translate(100%); +} +@font-face { + font-family: 'tinymce-mobile'; + font-style: normal; + font-weight: normal; + src: url('fonts/tinymce-mobile.woff?8x92w3') format('woff'); +} +@media (min-device-width: 700px) { + .tinymce-mobile-outer-container, + .tinymce-mobile-outer-container input { + font-size: 25px; + } +} +@media (max-device-width: 700px) { + .tinymce-mobile-outer-container, + .tinymce-mobile-outer-container input { + font-size: 18px; + } +} +.tinymce-mobile-icon { + font-family: 'tinymce-mobile', sans-serif; +} +.mixin-flex-and-centre { + align-items: center; + display: flex; + justify-content: center; +} +.mixin-flex-bar { + align-items: center; + display: flex; + height: 100%; +} +.tinymce-mobile-outer-container .tinymce-mobile-editor-socket iframe { + background-color: #fff; + width: 100%; +} +.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon { + /* Note, on the iPod touch in landscape, this isn't visible when the navbar appears */ + background-color: #207ab7; + border-radius: 50%; + bottom: 1em; + color: white; + font-size: 1em; + height: 2.1em; + position: fixed; + right: 2em; + width: 2.1em; + align-items: center; + display: flex; + justify-content: center; +} +@media only screen and (min-device-width:700px) { + .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon { + font-size: 1.2em; + } +} +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket { + height: 300px; + overflow: hidden; +} +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket iframe { + height: 100%; +} +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-toolstrip { + display: none; +} +/* + Note, that if you don't include this (::-webkit-file-upload-button), the toolbar width gets + increased and the whole body becomes scrollable. It's important! + */ +input[type="file"]::-webkit-file-upload-button { + display: none; +} +@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) { + .tinymce-mobile-ios-container .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon { + bottom: 50%; + } +} diff --git a/public/tinymce/skins/ui/oxide-dark/skin.mobile.min.css b/public/tinymce/skins/ui/oxide-dark/skin.mobile.min.css new file mode 100644 index 0000000..3a45cac --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/skin.mobile.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tinymce-mobile-outer-container{all:initial;display:block}.tinymce-mobile-outer-container *{border:0;box-sizing:initial;cursor:inherit;float:none;line-height:1;margin:0;outline:0;padding:0;-webkit-tap-highlight-color:transparent;text-shadow:none;white-space:nowrap}.tinymce-mobile-icon-arrow-back::before{content:"\e5cd"}.tinymce-mobile-icon-image::before{content:"\e412"}.tinymce-mobile-icon-cancel-circle::before{content:"\e5c9"}.tinymce-mobile-icon-full-dot::before{content:"\e061"}.tinymce-mobile-icon-align-center::before{content:"\e234"}.tinymce-mobile-icon-align-left::before{content:"\e236"}.tinymce-mobile-icon-align-right::before{content:"\e237"}.tinymce-mobile-icon-bold::before{content:"\e238"}.tinymce-mobile-icon-italic::before{content:"\e23f"}.tinymce-mobile-icon-unordered-list::before{content:"\e241"}.tinymce-mobile-icon-ordered-list::before{content:"\e242"}.tinymce-mobile-icon-font-size::before{content:"\e245"}.tinymce-mobile-icon-underline::before{content:"\e249"}.tinymce-mobile-icon-link::before{content:"\e157"}.tinymce-mobile-icon-unlink::before{content:"\eca2"}.tinymce-mobile-icon-color::before{content:"\e891"}.tinymce-mobile-icon-previous::before{content:"\e314"}.tinymce-mobile-icon-next::before{content:"\e315"}.tinymce-mobile-icon-large-font::before,.tinymce-mobile-icon-style-formats::before{content:"\e264"}.tinymce-mobile-icon-undo::before{content:"\e166"}.tinymce-mobile-icon-redo::before{content:"\e15a"}.tinymce-mobile-icon-removeformat::before{content:"\e239"}.tinymce-mobile-icon-small-font::before{content:"\e906"}.tinymce-mobile-format-matches::after,.tinymce-mobile-icon-readonly-back::before{content:"\e5ca"}.tinymce-mobile-icon-small-heading::before{content:"small"}.tinymce-mobile-icon-large-heading::before{content:"large"}.tinymce-mobile-icon-large-heading::before,.tinymce-mobile-icon-small-heading::before{font-family:sans-serif;font-size:80%}.tinymce-mobile-mask-edit-icon::before{content:"\e254"}.tinymce-mobile-icon-back::before{content:"\e5c4"}.tinymce-mobile-icon-heading::before{content:"Headings";font-family:sans-serif;font-size:80%;font-weight:700}.tinymce-mobile-icon-h1::before{content:"H1";font-weight:700}.tinymce-mobile-icon-h2::before{content:"H2";font-weight:700}.tinymce-mobile-icon-h3::before{content:"H3";font-weight:700}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask{align-items:center;display:flex;justify-content:center;background:rgba(51,51,51,.5);height:100%;position:absolute;top:0;width:100%}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container{align-items:center;border-radius:50%;display:flex;flex-direction:column;font-family:sans-serif;font-size:1em;justify-content:space-between}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .mixin-menu-item{align-items:center;display:flex;justify-content:center;border-radius:50%;height:2.1em;width:2.1em}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section{align-items:center;display:flex;justify-content:center;flex-direction:column;font-size:1em}@media only screen and (min-device-width:700px){.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section{font-size:1.2em}}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon{align-items:center;display:flex;justify-content:center;border-radius:50%;height:2.1em;width:2.1em;background-color:#fff;color:#207ab7}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon::before{content:"\e900";font-family:tinymce-mobile,sans-serif}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section:not(.tinymce-mobile-mask-tap-icon-selected) .tinymce-mobile-mask-tap-icon{z-index:2}.tinymce-mobile-android-container.tinymce-mobile-android-maximized{background:#fff;border:none;bottom:0;display:flex;flex-direction:column;left:0;position:fixed;right:0;top:0}.tinymce-mobile-android-container:not(.tinymce-mobile-android-maximized){position:relative}.tinymce-mobile-android-container .tinymce-mobile-editor-socket{display:flex;flex-grow:1}.tinymce-mobile-android-container .tinymce-mobile-editor-socket iframe{display:flex!important;flex-grow:1;height:auto!important}.tinymce-mobile-android-scroll-reload{overflow:hidden}:not(.tinymce-mobile-readonly-mode)>.tinymce-mobile-android-selection-context-toolbar{margin-top:23px}.tinymce-mobile-toolstrip{background:#fff;display:flex;flex:0 0 auto;z-index:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar{align-items:center;background-color:#fff;border-bottom:1px solid #ccc;display:flex;flex:1;height:2.5em;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group{align-items:center;display:flex;height:100%;flex-shrink:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group>div{align-items:center;display:flex;height:100%;flex:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-exit-container{background:#f44336}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-toolbar-scrollable-group{flex-grow:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item{padding-left:.5em;padding-right:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button{align-items:center;display:flex;height:80%;margin-left:2px;margin-right:2px}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected{background:#c8cbcf;color:#ccc}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:first-of-type,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:last-of-type{background:#207ab7;color:#eceff1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group{align-items:center;display:flex;height:100%;flex:1;padding-bottom:.4em;padding-top:.4em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog{display:flex;min-height:1.5em;overflow:hidden;padding-left:0;padding-right:0;position:relative;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain{display:flex;height:100%;transition:left cubic-bezier(.4,0,1,1) .15s;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen{display:flex;flex:0 0 auto;justify-content:space-between;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen input{font-family:Sans-serif}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container{display:flex;flex-grow:1;position:relative}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container .tinymce-mobile-input-container-x{-ms-grid-row-align:center;align-self:center;background:inherit;border:none;border-radius:50%;color:#888;font-size:.6em;font-weight:700;height:100%;padding-right:2px;position:absolute;right:0}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container.tinymce-mobile-input-container-empty .tinymce-mobile-input-container-x{display:none}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous{align-items:center;display:flex}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous::before{align-items:center;display:flex;font-weight:700;height:100%;padding-left:.5em;padding-right:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next.tinymce-mobile-toolbar-navigation-disabled::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous.tinymce-mobile-toolbar-navigation-disabled::before{visibility:hidden}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item{color:#ccc;font-size:10px;line-height:10px;margin:0 2px;padding-top:3px}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item.tinymce-mobile-dot-active{color:#c8cbcf}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-font::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-heading::before{margin-left:.5em;margin-right:.9em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-font::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-heading::before{margin-left:.9em;margin-right:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider{display:flex;flex:1;margin-left:0;margin-right:0;padding:.28em 0;position:relative}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container{align-items:center;display:flex;flex-grow:1;height:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container .tinymce-mobile-slider-size-line{background:#ccc;display:flex;flex:1;height:.2em;margin-bottom:.3em;margin-top:.3em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container{padding-left:2em;padding-right:2em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container{align-items:center;display:flex;flex-grow:1;height:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container .tinymce-mobile-slider-gradient{background:linear-gradient(to right,red 0,#feff00 17%,#0f0 33%,#00feff 50%,#00f 67%,#ff00fe 83%,red 100%);display:flex;flex:1;height:.2em;margin-bottom:.3em;margin-top:.3em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-black{background:#000;height:.2em;margin-bottom:.3em;margin-top:.3em;width:1.2em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-white{background:#fff;height:.2em;margin-bottom:.3em;margin-top:.3em;width:1.2em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb{align-items:center;background-clip:padding-box;background-color:#455a64;border:.5em solid rgba(136,136,136,0);border-radius:3em;bottom:0;color:#fff;display:flex;height:.5em;justify-content:center;left:-10px;margin:auto;position:absolute;top:0;transition:border 120ms cubic-bezier(.39,.58,.57,1);width:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb.tinymce-mobile-thumb-active{border:.5em solid rgba(136,136,136,.39)}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group>div{align-items:center;display:flex;height:100%;flex:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper{flex-direction:column;justify-content:center}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item{align-items:center;display:flex}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item:not(.tinymce-mobile-serialised-dialog){height:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-container{display:flex}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input{background:#fff;border:none;border-radius:0;color:#455a64;flex-grow:1;font-size:.85em;padding-bottom:.1em;padding-left:5px;padding-top:.1em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::-webkit-input-placeholder{color:#888}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::placeholder{color:#888}.tinymce-mobile-dropup{background:#fff;display:flex;overflow:hidden;width:100%}.tinymce-mobile-dropup.tinymce-mobile-dropup-shrinking{transition:height .3s ease-out}.tinymce-mobile-dropup.tinymce-mobile-dropup-growing{transition:height .3s ease-in}.tinymce-mobile-dropup.tinymce-mobile-dropup-closed{flex-grow:0}.tinymce-mobile-dropup.tinymce-mobile-dropup-open:not(.tinymce-mobile-dropup-growing){flex-grow:1}.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height:200px}@media only screen and (orientation:landscape){.tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height:200px}}@media only screen and (min-device-width :320px) and (max-device-width :568px) and (orientation :landscape){.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height:150px}}.tinymce-mobile-styles-menu{font-family:sans-serif;outline:4px solid #000;overflow:hidden;position:relative;width:100%}.tinymce-mobile-styles-menu [role=menu]{display:flex;flex-direction:column;height:100%;position:absolute;width:100%}.tinymce-mobile-styles-menu [role=menu].transitioning{transition:transform .5s ease-in-out}.tinymce-mobile-styles-menu .tinymce-mobile-styles-item{border-bottom:1px solid #ddd;color:#455a64;cursor:pointer;display:flex;padding:1em 1em;position:relative}.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser .tinymce-mobile-styles-collapse-icon::before{color:#455a64;content:"\e314";font-family:tinymce-mobile,sans-serif}.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-styles-item-is-menu::after{color:#455a64;content:"\e315";font-family:tinymce-mobile,sans-serif;padding-left:1em;padding-right:1em;position:absolute;right:0}.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-format-matches::after{font-family:tinymce-mobile,sans-serif;padding-left:1em;padding-right:1em;position:absolute;right:0}.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser,.tinymce-mobile-styles-menu .tinymce-mobile-styles-separator{align-items:center;background:#fff;border-top:#455a64;color:#455a64;display:flex;min-height:2.5em;padding-left:1em;padding-right:1em}.tinymce-mobile-styles-menu [data-transitioning-destination=before][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=before]{transform:translate(-100%)}.tinymce-mobile-styles-menu [data-transitioning-destination=current][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=current]{transform:translate(0)}.tinymce-mobile-styles-menu [data-transitioning-destination=after][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=after]{transform:translate(100%)}@font-face{font-family:tinymce-mobile;font-style:normal;font-weight:400;src:url(fonts/tinymce-mobile.woff?8x92w3) format('woff')}@media (min-device-width:700px){.tinymce-mobile-outer-container,.tinymce-mobile-outer-container input{font-size:25px}}@media (max-device-width:700px){.tinymce-mobile-outer-container,.tinymce-mobile-outer-container input{font-size:18px}}.tinymce-mobile-icon{font-family:tinymce-mobile,sans-serif}.mixin-flex-and-centre{align-items:center;display:flex;justify-content:center}.mixin-flex-bar{align-items:center;display:flex;height:100%}.tinymce-mobile-outer-container .tinymce-mobile-editor-socket iframe{background-color:#fff;width:100%}.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{background-color:#207ab7;border-radius:50%;bottom:1em;color:#fff;font-size:1em;height:2.1em;position:fixed;right:2em;width:2.1em;align-items:center;display:flex;justify-content:center}@media only screen and (min-device-width:700px){.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{font-size:1.2em}}.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket{height:300px;overflow:hidden}.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket iframe{height:100%}.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-toolstrip{display:none}input[type=file]::-webkit-file-upload-button{display:none}@media only screen and (min-device-width :320px) and (max-device-width :568px) and (orientation :landscape){.tinymce-mobile-ios-container .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{bottom:50%}} diff --git a/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.css b/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.css new file mode 100644 index 0000000..d2adc4d --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.css @@ -0,0 +1,37 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +body.tox-dialog__disable-scroll { + overflow: hidden; +} +.tox-fullscreen { + border: 0; + height: 100%; + margin: 0; + overflow: hidden; + -ms-scroll-chaining: none; + overscroll-behavior: none; + padding: 0; + touch-action: pinch-zoom; + width: 100%; +} +.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { + display: none; +} +.tox.tox-tinymce.tox-fullscreen, +.tox-shadowhost.tox-fullscreen { + left: 0; + position: fixed; + top: 0; + z-index: 1200; +} +.tox.tox-tinymce.tox-fullscreen { + background-color: transparent; +} +.tox-fullscreen .tox.tox-tinymce-aux, +.tox-fullscreen ~ .tox.tox-tinymce-aux { + z-index: 1201; +} diff --git a/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css b/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css new file mode 100644 index 0000000..a0893b9 --- /dev/null +++ b/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} diff --git a/public/tinymce/skins/ui/oxide/content.css b/public/tinymce/skins/ui/oxide/content.css new file mode 100644 index 0000000..2ac0cca --- /dev/null +++ b/public/tinymce/skins/ui/oxide/content.css @@ -0,0 +1,732 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + cursor: default; + display: inline-block; + height: 12px !important; + padding: 0 2px; + -webkit-user-modify: read-only; + -moz-user-modify: read-only; + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; + width: 8px !important; +} +.mce-content-body .mce-item-anchor[data-mce-selected] { + outline-offset: 1px; +} +.tox-comments-visible .tox-comment { + background-color: #fff0b7; +} +.tox-comments-visible .tox-comment--active { + background-color: #ffe168; +} +.tox-checklist > li:not(.tox-checklist--hidden) { + list-style: none; + margin: 0.25em 0; +} +.tox-checklist > li:not(.tox-checklist--hidden)::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); + cursor: pointer; + height: 1em; + margin-left: -1.5em; + margin-top: 0.125em; + position: absolute; + width: 1em; +} +.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); +} +[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before { + margin-left: 0; + margin-right: -1.5em; +} +/* stylelint-disable */ +/* http://prismjs.com/ */ +/** + * 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; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + tab-size: 4; + -webkit-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: 0.5em 0; + overflow: auto; +} +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} +.token.punctuation { + color: #999; +} +.namespace { + opacity: 0.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%, 0.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; +} +/* stylelint-enable */ +.mce-content-body { + overflow-wrap: break-word; + word-wrap: break-word; +} +.mce-content-body .mce-visual-caret { + background-color: black; + background-color: currentColor; + position: absolute; +} +.mce-content-body .mce-visual-caret-hidden { + display: none; +} +.mce-content-body *[data-mce-caret] { + left: -1000px; + margin: 0; + padding: 0; + position: absolute; + right: auto; + top: 0; +} +.mce-content-body .mce-offscreen-selection { + left: -2000000px; + max-width: 1000000px; + position: absolute; +} +.mce-content-body *[contentEditable=false] { + cursor: default; +} +.mce-content-body *[contentEditable=true] { + cursor: text; +} +.tox-cursor-format-painter { + cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default; +} +.mce-content-body figure.align-left { + float: left; +} +.mce-content-body figure.align-right { + float: right; +} +.mce-content-body figure.image.align-center { + display: table; + margin-left: auto; + margin-right: auto; +} +.mce-preview-object { + border: 1px solid gray; + display: inline-block; + line-height: 0; + margin: 0 2px 0 2px; + position: relative; +} +.mce-preview-object .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-preview-object[data-mce-selected="2"] .mce-shim { + display: none; +} +.mce-object { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + border: 1px dashed #aaa; +} +.mce-pagebreak { + border: 1px dashed #aaa; + cursor: default; + display: block; + height: 5px; + margin-top: 15px; + page-break-before: always; + width: 100%; +} +@media print { + .mce-pagebreak { + border: 0; + } +} +.tiny-pageembed .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.tiny-pageembed[data-mce-selected="2"] .mce-shim { + display: none; +} +.tiny-pageembed { + display: inline-block; + position: relative; +} +.tiny-pageembed--21by9, +.tiny-pageembed--16by9, +.tiny-pageembed--4by3, +.tiny-pageembed--1by1 { + display: block; + overflow: hidden; + padding: 0; + position: relative; + width: 100%; +} +.tiny-pageembed--21by9 { + padding-top: 42.857143%; +} +.tiny-pageembed--16by9 { + padding-top: 56.25%; +} +.tiny-pageembed--4by3 { + padding-top: 75%; +} +.tiny-pageembed--1by1 { + padding-top: 100%; +} +.tiny-pageembed--21by9 iframe, +.tiny-pageembed--16by9 iframe, +.tiny-pageembed--4by3 iframe, +.tiny-pageembed--1by1 iframe { + border: 0; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-content-body[data-mce-placeholder] { + position: relative; +} +.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before { + color: rgba(34, 47, 62, 0.7); + content: attr(data-mce-placeholder); + position: absolute; +} +.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before { + left: 1px; +} +.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before { + right: 1px; +} +.mce-content-body div.mce-resizehandle { + background-color: #4099ff; + border-color: #4099ff; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + height: 10px; + position: absolute; + width: 10px; + z-index: 1298; +} +.mce-content-body div.mce-resizehandle:hover { + background-color: #4099ff; +} +.mce-content-body div.mce-resizehandle:nth-of-type(1) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(2) { + cursor: nesw-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(3) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(4) { + cursor: nesw-resize; +} +.mce-content-body .mce-resize-backdrop { + z-index: 10000; +} +.mce-content-body .mce-clonedresizable { + cursor: default; + opacity: 0.5; + outline: 1px dashed black; + position: absolute; + z-index: 10001; +} +.mce-content-body .mce-clonedresizable.mce-resizetable-columns th, +.mce-content-body .mce-clonedresizable.mce-resizetable-columns td { + border: 0; +} +.mce-content-body .mce-resize-helper { + background: #555; + background: rgba(0, 0, 0, 0.75); + border: 1px; + border-radius: 3px; + color: white; + display: none; + font-family: sans-serif; + font-size: 12px; + line-height: 14px; + margin: 5px 10px; + padding: 5px; + position: absolute; + white-space: nowrap; + z-index: 10002; +} +.tox-rtc-user-selection { + position: relative; +} +.tox-rtc-user-cursor { + bottom: 0; + cursor: default; + position: absolute; + top: 0; + width: 2px; +} +.tox-rtc-user-cursor::before { + background-color: inherit; + border-radius: 50%; + content: ''; + display: block; + height: 8px; + position: absolute; + right: -3px; + top: -3px; + width: 8px; +} +.tox-rtc-user-cursor:hover::after { + background-color: inherit; + border-radius: 100px; + box-sizing: border-box; + color: #fff; + content: attr(data-user); + display: block; + font-size: 12px; + font-weight: bold; + left: -5px; + min-height: 8px; + min-width: 8px; + padding: 0 12px; + position: absolute; + top: -11px; + white-space: nowrap; + z-index: 1000; +} +.tox-rtc-user-selection--1 .tox-rtc-user-cursor { + background-color: #2dc26b; +} +.tox-rtc-user-selection--2 .tox-rtc-user-cursor { + background-color: #e03e2d; +} +.tox-rtc-user-selection--3 .tox-rtc-user-cursor { + background-color: #f1c40f; +} +.tox-rtc-user-selection--4 .tox-rtc-user-cursor { + background-color: #3598db; +} +.tox-rtc-user-selection--5 .tox-rtc-user-cursor { + background-color: #b96ad9; +} +.tox-rtc-user-selection--6 .tox-rtc-user-cursor { + background-color: #e67e23; +} +.tox-rtc-user-selection--7 .tox-rtc-user-cursor { + background-color: #aaa69d; +} +.tox-rtc-user-selection--8 .tox-rtc-user-cursor { + background-color: #f368e0; +} +.tox-rtc-remote-image { + background: #eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center; + border: 1px solid #ccc; + min-height: 240px; + min-width: 320px; +} +.mce-match-marker { + background: #aaa; + color: #fff; +} +.mce-match-marker-selected { + background: #39f; + color: #fff; +} +.mce-match-marker-selected::-moz-selection { + background: #39f; + color: #fff; +} +.mce-match-marker-selected::selection { + background: #39f; + color: #fff; +} +.mce-content-body img[data-mce-selected], +.mce-content-body video[data-mce-selected], +.mce-content-body audio[data-mce-selected], +.mce-content-body object[data-mce-selected], +.mce-content-body embed[data-mce-selected], +.mce-content-body table[data-mce-selected] { + outline: 3px solid #b4d7ff; +} +.mce-content-body hr[data-mce-selected] { + outline: 3px solid #b4d7ff; + outline-offset: 1px; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus { + outline: 3px solid #b4d7ff; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover { + outline: 3px solid #b4d7ff; +} +.mce-content-body *[contentEditable=false][data-mce-selected] { + cursor: not-allowed; + outline: 3px solid #b4d7ff; +} +.mce-content-body.mce-content-readonly *[contentEditable=true]:focus, +.mce-content-body.mce-content-readonly *[contentEditable=true]:hover { + outline: none; +} +.mce-content-body *[data-mce-selected="inline-boundary"] { + background-color: #b4d7ff; +} +.mce-content-body .mce-edit-focus { + outline: 3px solid #b4d7ff; +} +.mce-content-body td[data-mce-selected], +.mce-content-body th[data-mce-selected] { + position: relative; +} +.mce-content-body td[data-mce-selected]::-moz-selection, +.mce-content-body th[data-mce-selected]::-moz-selection { + background: none; +} +.mce-content-body td[data-mce-selected]::selection, +.mce-content-body th[data-mce-selected]::selection { + background: none; +} +.mce-content-body td[data-mce-selected] *, +.mce-content-body th[data-mce-selected] * { + outline: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mce-content-body td[data-mce-selected]::after, +.mce-content-body th[data-mce-selected]::after { + background-color: rgba(180, 215, 255, 0.7); + border: 1px solid rgba(180, 215, 255, 0.7); + bottom: -1px; + content: ''; + left: -1px; + mix-blend-mode: multiply; + position: absolute; + right: -1px; + top: -1px; +} +@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + .mce-content-body td[data-mce-selected]::after, + .mce-content-body th[data-mce-selected]::after { + border-color: rgba(0, 84, 180, 0.7); + } +} +.mce-content-body img::-moz-selection { + background: none; +} +.mce-content-body img::selection { + background: none; +} +.ephox-snooker-resizer-bar { + background-color: #b4d7ff; + opacity: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.ephox-snooker-resizer-cols { + cursor: col-resize; +} +.ephox-snooker-resizer-rows { + cursor: row-resize; +} +.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging { + opacity: 1; +} +.mce-spellchecker-word { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; + height: 2rem; +} +.mce-spellchecker-grammar { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; +} +.mce-toc { + border: 1px solid gray; +} +.mce-toc h2 { + margin: 4px; +} +.mce-toc li { + list-style-type: none; +} +table[style*="border-width: 0px"], +.mce-item-table:not([border]), +.mce-item-table[border="0"], +table[style*="border-width: 0px"] td, +.mce-item-table:not([border]) td, +.mce-item-table[border="0"] td, +table[style*="border-width: 0px"] th, +.mce-item-table:not([border]) th, +.mce-item-table[border="0"] th, +table[style*="border-width: 0px"] caption, +.mce-item-table:not([border]) caption, +.mce-item-table[border="0"] caption { + border: 1px dashed #bbb; +} +.mce-visualblocks p, +.mce-visualblocks h1, +.mce-visualblocks h2, +.mce-visualblocks h3, +.mce-visualblocks h4, +.mce-visualblocks h5, +.mce-visualblocks h6, +.mce-visualblocks div:not([data-mce-bogus]), +.mce-visualblocks section, +.mce-visualblocks article, +.mce-visualblocks blockquote, +.mce-visualblocks address, +.mce-visualblocks pre, +.mce-visualblocks figure, +.mce-visualblocks figcaption, +.mce-visualblocks hgroup, +.mce-visualblocks aside, +.mce-visualblocks ul, +.mce-visualblocks ol, +.mce-visualblocks dl { + background-repeat: no-repeat; + border: 1px dashed #bbb; + margin-left: 3px; + padding-top: 10px; +} +.mce-visualblocks p { + background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7); +} +.mce-visualblocks h1 { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==); +} +.mce-visualblocks h2 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==); +} +.mce-visualblocks h3 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7); +} +.mce-visualblocks h4 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==); +} +.mce-visualblocks h5 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==); +} +.mce-visualblocks h6 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==); +} +.mce-visualblocks div:not([data-mce-bogus]) { + background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7); +} +.mce-visualblocks section { + background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=); +} +.mce-visualblocks article { + background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7); +} +.mce-visualblocks blockquote { + background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7); +} +.mce-visualblocks address { + background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=); +} +.mce-visualblocks pre { + background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==); +} +.mce-visualblocks figure { + background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7); +} +.mce-visualblocks figcaption { + border: 1px dashed #bbb; +} +.mce-visualblocks hgroup { + background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7); +} +.mce-visualblocks aside { + background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=); +} +.mce-visualblocks ul { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==); +} +.mce-visualblocks ol { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==); +} +.mce-visualblocks dl { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==); +} +.mce-visualblocks:not([dir=rtl]) p, +.mce-visualblocks:not([dir=rtl]) h1, +.mce-visualblocks:not([dir=rtl]) h2, +.mce-visualblocks:not([dir=rtl]) h3, +.mce-visualblocks:not([dir=rtl]) h4, +.mce-visualblocks:not([dir=rtl]) h5, +.mce-visualblocks:not([dir=rtl]) h6, +.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]), +.mce-visualblocks:not([dir=rtl]) section, +.mce-visualblocks:not([dir=rtl]) article, +.mce-visualblocks:not([dir=rtl]) blockquote, +.mce-visualblocks:not([dir=rtl]) address, +.mce-visualblocks:not([dir=rtl]) pre, +.mce-visualblocks:not([dir=rtl]) figure, +.mce-visualblocks:not([dir=rtl]) figcaption, +.mce-visualblocks:not([dir=rtl]) hgroup, +.mce-visualblocks:not([dir=rtl]) aside, +.mce-visualblocks:not([dir=rtl]) ul, +.mce-visualblocks:not([dir=rtl]) ol, +.mce-visualblocks:not([dir=rtl]) dl { + margin-left: 3px; +} +.mce-visualblocks[dir=rtl] p, +.mce-visualblocks[dir=rtl] h1, +.mce-visualblocks[dir=rtl] h2, +.mce-visualblocks[dir=rtl] h3, +.mce-visualblocks[dir=rtl] h4, +.mce-visualblocks[dir=rtl] h5, +.mce-visualblocks[dir=rtl] h6, +.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]), +.mce-visualblocks[dir=rtl] section, +.mce-visualblocks[dir=rtl] article, +.mce-visualblocks[dir=rtl] blockquote, +.mce-visualblocks[dir=rtl] address, +.mce-visualblocks[dir=rtl] pre, +.mce-visualblocks[dir=rtl] figure, +.mce-visualblocks[dir=rtl] figcaption, +.mce-visualblocks[dir=rtl] hgroup, +.mce-visualblocks[dir=rtl] aside, +.mce-visualblocks[dir=rtl] ul, +.mce-visualblocks[dir=rtl] ol, +.mce-visualblocks[dir=rtl] dl { + background-position-x: right; + margin-right: 3px; +} +.mce-nbsp, +.mce-shy { + background: #aaa; +} +.mce-shy::after { + content: '-'; +} +body { + font-family: sans-serif; +} +table { + border-collapse: collapse; +} diff --git a/public/tinymce/skins/ui/oxide/content.inline.css b/public/tinymce/skins/ui/oxide/content.inline.css new file mode 100644 index 0000000..8e7521d --- /dev/null +++ b/public/tinymce/skins/ui/oxide/content.inline.css @@ -0,0 +1,726 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + cursor: default; + display: inline-block; + height: 12px !important; + padding: 0 2px; + -webkit-user-modify: read-only; + -moz-user-modify: read-only; + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; + width: 8px !important; +} +.mce-content-body .mce-item-anchor[data-mce-selected] { + outline-offset: 1px; +} +.tox-comments-visible .tox-comment { + background-color: #fff0b7; +} +.tox-comments-visible .tox-comment--active { + background-color: #ffe168; +} +.tox-checklist > li:not(.tox-checklist--hidden) { + list-style: none; + margin: 0.25em 0; +} +.tox-checklist > li:not(.tox-checklist--hidden)::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); + cursor: pointer; + height: 1em; + margin-left: -1.5em; + margin-top: 0.125em; + position: absolute; + width: 1em; +} +.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before { + content: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A"); +} +[dir=rtl] .tox-checklist > li:not(.tox-checklist--hidden)::before { + margin-left: 0; + margin-right: -1.5em; +} +/* stylelint-disable */ +/* http://prismjs.com/ */ +/** + * 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; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + tab-size: 4; + -webkit-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: 0.5em 0; + overflow: auto; +} +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} +.token.punctuation { + color: #999; +} +.namespace { + opacity: 0.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%, 0.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; +} +/* stylelint-enable */ +.mce-content-body { + overflow-wrap: break-word; + word-wrap: break-word; +} +.mce-content-body .mce-visual-caret { + background-color: black; + background-color: currentColor; + position: absolute; +} +.mce-content-body .mce-visual-caret-hidden { + display: none; +} +.mce-content-body *[data-mce-caret] { + left: -1000px; + margin: 0; + padding: 0; + position: absolute; + right: auto; + top: 0; +} +.mce-content-body .mce-offscreen-selection { + left: -2000000px; + max-width: 1000000px; + position: absolute; +} +.mce-content-body *[contentEditable=false] { + cursor: default; +} +.mce-content-body *[contentEditable=true] { + cursor: text; +} +.tox-cursor-format-painter { + cursor: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"), default; +} +.mce-content-body figure.align-left { + float: left; +} +.mce-content-body figure.align-right { + float: right; +} +.mce-content-body figure.image.align-center { + display: table; + margin-left: auto; + margin-right: auto; +} +.mce-preview-object { + border: 1px solid gray; + display: inline-block; + line-height: 0; + margin: 0 2px 0 2px; + position: relative; +} +.mce-preview-object .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-preview-object[data-mce-selected="2"] .mce-shim { + display: none; +} +.mce-object { + background: transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center; + border: 1px dashed #aaa; +} +.mce-pagebreak { + border: 1px dashed #aaa; + cursor: default; + display: block; + height: 5px; + margin-top: 15px; + page-break-before: always; + width: 100%; +} +@media print { + .mce-pagebreak { + border: 0; + } +} +.tiny-pageembed .mce-shim { + background: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7); + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.tiny-pageembed[data-mce-selected="2"] .mce-shim { + display: none; +} +.tiny-pageembed { + display: inline-block; + position: relative; +} +.tiny-pageembed--21by9, +.tiny-pageembed--16by9, +.tiny-pageembed--4by3, +.tiny-pageembed--1by1 { + display: block; + overflow: hidden; + padding: 0; + position: relative; + width: 100%; +} +.tiny-pageembed--21by9 { + padding-top: 42.857143%; +} +.tiny-pageembed--16by9 { + padding-top: 56.25%; +} +.tiny-pageembed--4by3 { + padding-top: 75%; +} +.tiny-pageembed--1by1 { + padding-top: 100%; +} +.tiny-pageembed--21by9 iframe, +.tiny-pageembed--16by9 iframe, +.tiny-pageembed--4by3 iframe, +.tiny-pageembed--1by1 iframe { + border: 0; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.mce-content-body[data-mce-placeholder] { + position: relative; +} +.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before { + color: rgba(34, 47, 62, 0.7); + content: attr(data-mce-placeholder); + position: absolute; +} +.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before { + left: 1px; +} +.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before { + right: 1px; +} +.mce-content-body div.mce-resizehandle { + background-color: #4099ff; + border-color: #4099ff; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + height: 10px; + position: absolute; + width: 10px; + z-index: 1298; +} +.mce-content-body div.mce-resizehandle:hover { + background-color: #4099ff; +} +.mce-content-body div.mce-resizehandle:nth-of-type(1) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(2) { + cursor: nesw-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(3) { + cursor: nwse-resize; +} +.mce-content-body div.mce-resizehandle:nth-of-type(4) { + cursor: nesw-resize; +} +.mce-content-body .mce-resize-backdrop { + z-index: 10000; +} +.mce-content-body .mce-clonedresizable { + cursor: default; + opacity: 0.5; + outline: 1px dashed black; + position: absolute; + z-index: 10001; +} +.mce-content-body .mce-clonedresizable.mce-resizetable-columns th, +.mce-content-body .mce-clonedresizable.mce-resizetable-columns td { + border: 0; +} +.mce-content-body .mce-resize-helper { + background: #555; + background: rgba(0, 0, 0, 0.75); + border: 1px; + border-radius: 3px; + color: white; + display: none; + font-family: sans-serif; + font-size: 12px; + line-height: 14px; + margin: 5px 10px; + padding: 5px; + position: absolute; + white-space: nowrap; + z-index: 10002; +} +.tox-rtc-user-selection { + position: relative; +} +.tox-rtc-user-cursor { + bottom: 0; + cursor: default; + position: absolute; + top: 0; + width: 2px; +} +.tox-rtc-user-cursor::before { + background-color: inherit; + border-radius: 50%; + content: ''; + display: block; + height: 8px; + position: absolute; + right: -3px; + top: -3px; + width: 8px; +} +.tox-rtc-user-cursor:hover::after { + background-color: inherit; + border-radius: 100px; + box-sizing: border-box; + color: #fff; + content: attr(data-user); + display: block; + font-size: 12px; + font-weight: bold; + left: -5px; + min-height: 8px; + min-width: 8px; + padding: 0 12px; + position: absolute; + top: -11px; + white-space: nowrap; + z-index: 1000; +} +.tox-rtc-user-selection--1 .tox-rtc-user-cursor { + background-color: #2dc26b; +} +.tox-rtc-user-selection--2 .tox-rtc-user-cursor { + background-color: #e03e2d; +} +.tox-rtc-user-selection--3 .tox-rtc-user-cursor { + background-color: #f1c40f; +} +.tox-rtc-user-selection--4 .tox-rtc-user-cursor { + background-color: #3598db; +} +.tox-rtc-user-selection--5 .tox-rtc-user-cursor { + background-color: #b96ad9; +} +.tox-rtc-user-selection--6 .tox-rtc-user-cursor { + background-color: #e67e23; +} +.tox-rtc-user-selection--7 .tox-rtc-user-cursor { + background-color: #aaa69d; +} +.tox-rtc-user-selection--8 .tox-rtc-user-cursor { + background-color: #f368e0; +} +.tox-rtc-remote-image { + background: #eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center; + border: 1px solid #ccc; + min-height: 240px; + min-width: 320px; +} +.mce-match-marker { + background: #aaa; + color: #fff; +} +.mce-match-marker-selected { + background: #39f; + color: #fff; +} +.mce-match-marker-selected::-moz-selection { + background: #39f; + color: #fff; +} +.mce-match-marker-selected::selection { + background: #39f; + color: #fff; +} +.mce-content-body img[data-mce-selected], +.mce-content-body video[data-mce-selected], +.mce-content-body audio[data-mce-selected], +.mce-content-body object[data-mce-selected], +.mce-content-body embed[data-mce-selected], +.mce-content-body table[data-mce-selected] { + outline: 3px solid #b4d7ff; +} +.mce-content-body hr[data-mce-selected] { + outline: 3px solid #b4d7ff; + outline-offset: 1px; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus { + outline: 3px solid #b4d7ff; +} +.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover { + outline: 3px solid #b4d7ff; +} +.mce-content-body *[contentEditable=false][data-mce-selected] { + cursor: not-allowed; + outline: 3px solid #b4d7ff; +} +.mce-content-body.mce-content-readonly *[contentEditable=true]:focus, +.mce-content-body.mce-content-readonly *[contentEditable=true]:hover { + outline: none; +} +.mce-content-body *[data-mce-selected="inline-boundary"] { + background-color: #b4d7ff; +} +.mce-content-body .mce-edit-focus { + outline: 3px solid #b4d7ff; +} +.mce-content-body td[data-mce-selected], +.mce-content-body th[data-mce-selected] { + position: relative; +} +.mce-content-body td[data-mce-selected]::-moz-selection, +.mce-content-body th[data-mce-selected]::-moz-selection { + background: none; +} +.mce-content-body td[data-mce-selected]::selection, +.mce-content-body th[data-mce-selected]::selection { + background: none; +} +.mce-content-body td[data-mce-selected] *, +.mce-content-body th[data-mce-selected] * { + outline: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.mce-content-body td[data-mce-selected]::after, +.mce-content-body th[data-mce-selected]::after { + background-color: rgba(180, 215, 255, 0.7); + border: 1px solid rgba(180, 215, 255, 0.7); + bottom: -1px; + content: ''; + left: -1px; + mix-blend-mode: multiply; + position: absolute; + right: -1px; + top: -1px; +} +@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + .mce-content-body td[data-mce-selected]::after, + .mce-content-body th[data-mce-selected]::after { + border-color: rgba(0, 84, 180, 0.7); + } +} +.mce-content-body img::-moz-selection { + background: none; +} +.mce-content-body img::selection { + background: none; +} +.ephox-snooker-resizer-bar { + background-color: #b4d7ff; + opacity: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.ephox-snooker-resizer-cols { + cursor: col-resize; +} +.ephox-snooker-resizer-rows { + cursor: row-resize; +} +.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging { + opacity: 1; +} +.mce-spellchecker-word { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; + height: 2rem; +} +.mce-spellchecker-grammar { + background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A"); + background-position: 0 calc(100% + 1px); + background-repeat: repeat-x; + background-size: auto 6px; + cursor: default; +} +.mce-toc { + border: 1px solid gray; +} +.mce-toc h2 { + margin: 4px; +} +.mce-toc li { + list-style-type: none; +} +table[style*="border-width: 0px"], +.mce-item-table:not([border]), +.mce-item-table[border="0"], +table[style*="border-width: 0px"] td, +.mce-item-table:not([border]) td, +.mce-item-table[border="0"] td, +table[style*="border-width: 0px"] th, +.mce-item-table:not([border]) th, +.mce-item-table[border="0"] th, +table[style*="border-width: 0px"] caption, +.mce-item-table:not([border]) caption, +.mce-item-table[border="0"] caption { + border: 1px dashed #bbb; +} +.mce-visualblocks p, +.mce-visualblocks h1, +.mce-visualblocks h2, +.mce-visualblocks h3, +.mce-visualblocks h4, +.mce-visualblocks h5, +.mce-visualblocks h6, +.mce-visualblocks div:not([data-mce-bogus]), +.mce-visualblocks section, +.mce-visualblocks article, +.mce-visualblocks blockquote, +.mce-visualblocks address, +.mce-visualblocks pre, +.mce-visualblocks figure, +.mce-visualblocks figcaption, +.mce-visualblocks hgroup, +.mce-visualblocks aside, +.mce-visualblocks ul, +.mce-visualblocks ol, +.mce-visualblocks dl { + background-repeat: no-repeat; + border: 1px dashed #bbb; + margin-left: 3px; + padding-top: 10px; +} +.mce-visualblocks p { + background-image: url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7); +} +.mce-visualblocks h1 { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==); +} +.mce-visualblocks h2 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==); +} +.mce-visualblocks h3 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7); +} +.mce-visualblocks h4 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==); +} +.mce-visualblocks h5 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==); +} +.mce-visualblocks h6 { + background-image: url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==); +} +.mce-visualblocks div:not([data-mce-bogus]) { + background-image: url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7); +} +.mce-visualblocks section { + background-image: url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=); +} +.mce-visualblocks article { + background-image: url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7); +} +.mce-visualblocks blockquote { + background-image: url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7); +} +.mce-visualblocks address { + background-image: url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=); +} +.mce-visualblocks pre { + background-image: url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==); +} +.mce-visualblocks figure { + background-image: url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7); +} +.mce-visualblocks figcaption { + border: 1px dashed #bbb; +} +.mce-visualblocks hgroup { + background-image: url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7); +} +.mce-visualblocks aside { + background-image: url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=); +} +.mce-visualblocks ul { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==); +} +.mce-visualblocks ol { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==); +} +.mce-visualblocks dl { + background-image: url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==); +} +.mce-visualblocks:not([dir=rtl]) p, +.mce-visualblocks:not([dir=rtl]) h1, +.mce-visualblocks:not([dir=rtl]) h2, +.mce-visualblocks:not([dir=rtl]) h3, +.mce-visualblocks:not([dir=rtl]) h4, +.mce-visualblocks:not([dir=rtl]) h5, +.mce-visualblocks:not([dir=rtl]) h6, +.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]), +.mce-visualblocks:not([dir=rtl]) section, +.mce-visualblocks:not([dir=rtl]) article, +.mce-visualblocks:not([dir=rtl]) blockquote, +.mce-visualblocks:not([dir=rtl]) address, +.mce-visualblocks:not([dir=rtl]) pre, +.mce-visualblocks:not([dir=rtl]) figure, +.mce-visualblocks:not([dir=rtl]) figcaption, +.mce-visualblocks:not([dir=rtl]) hgroup, +.mce-visualblocks:not([dir=rtl]) aside, +.mce-visualblocks:not([dir=rtl]) ul, +.mce-visualblocks:not([dir=rtl]) ol, +.mce-visualblocks:not([dir=rtl]) dl { + margin-left: 3px; +} +.mce-visualblocks[dir=rtl] p, +.mce-visualblocks[dir=rtl] h1, +.mce-visualblocks[dir=rtl] h2, +.mce-visualblocks[dir=rtl] h3, +.mce-visualblocks[dir=rtl] h4, +.mce-visualblocks[dir=rtl] h5, +.mce-visualblocks[dir=rtl] h6, +.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]), +.mce-visualblocks[dir=rtl] section, +.mce-visualblocks[dir=rtl] article, +.mce-visualblocks[dir=rtl] blockquote, +.mce-visualblocks[dir=rtl] address, +.mce-visualblocks[dir=rtl] pre, +.mce-visualblocks[dir=rtl] figure, +.mce-visualblocks[dir=rtl] figcaption, +.mce-visualblocks[dir=rtl] hgroup, +.mce-visualblocks[dir=rtl] aside, +.mce-visualblocks[dir=rtl] ul, +.mce-visualblocks[dir=rtl] ol, +.mce-visualblocks[dir=rtl] dl { + background-position-x: right; + margin-right: 3px; +} +.mce-nbsp, +.mce-shy { + background: #aaa; +} +.mce-shy::after { + content: '-'; +} diff --git a/public/tinymce/skins/ui/oxide/content.inline.min.css b/public/tinymce/skins/ui/oxide/content.inline.min.css new file mode 100644 index 0000000..b4ab9a3 --- /dev/null +++ b/public/tinymce/skins/ui/oxide/content.inline.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;-ms-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment{background-color:#fff0b7}.tox-comments-visible .tox-comment--active{background-color:#ffe168}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"),default}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-object{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected="2"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #b4d7ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #b4d7ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#b4d7ff}.mce-content-body .mce-edit-focus{outline:3px solid #b4d7ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid rgba(180,215,255,.7);bottom:-1px;content:'';left:-1px;mix-blend-mode:multiply;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img::-moz-selection{background:0 0}.mce-content-body img::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#b4d7ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc li{list-style-type:none}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border="0"],.mce-item-table[border="0"] caption,.mce-item-table[border="0"] td,.mce-item-table[border="0"] th,table[style*="border-width: 0px"],table[style*="border-width: 0px"] caption,table[style*="border-width: 0px"] td,table[style*="border-width: 0px"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'} diff --git a/public/tinymce/skins/ui/oxide/content.min.css b/public/tinymce/skins/ui/oxide/content.min.css new file mode 100644 index 0000000..844858d --- /dev/null +++ b/public/tinymce/skins/ui/oxide/content.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.mce-content-body .mce-item-anchor{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;-ms-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment{background-color:#fff0b7}.tox-comments-visible .tox-comment--active{background-color:#ffe168}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A"),default}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-object{background:transparent url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected="2"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #b4d7ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #b4d7ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#b4d7ff}.mce-content-body .mce-edit-focus{outline:3px solid #b4d7ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid rgba(180,215,255,.7);bottom:-1px;content:'';left:-1px;mix-blend-mode:multiply;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img::-moz-selection{background:0 0}.mce-content-body img::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#b4d7ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc li{list-style-type:none}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border="0"],.mce-item-table[border="0"] caption,.mce-item-table[border="0"] td,.mce-item-table[border="0"] th,table[style*="border-width: 0px"],table[style*="border-width: 0px"] caption,table[style*="border-width: 0px"] td,table[style*="border-width: 0px"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}body{font-family:sans-serif}table{border-collapse:collapse} diff --git a/public/tinymce/skins/ui/oxide/content.mobile.css b/public/tinymce/skins/ui/oxide/content.mobile.css new file mode 100644 index 0000000..4bdb8ba --- /dev/null +++ b/public/tinymce/skins/ui/oxide/content.mobile.css @@ -0,0 +1,29 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection { + /* Note: this file is used inside the content, so isn't part of theming */ + background-color: green; + display: inline-block; + opacity: 0.5; + position: absolute; +} +body { + -webkit-text-size-adjust: none; +} +body img { + /* this is related to the content margin */ + max-width: 96vw; +} +body table img { + max-width: 95%; +} +body { + font-family: sans-serif; +} +table { + border-collapse: collapse; +} diff --git a/public/tinymce/skins/ui/oxide/content.mobile.min.css b/public/tinymce/skins/ui/oxide/content.mobile.min.css new file mode 100644 index 0000000..35f7dc0 --- /dev/null +++ b/public/tinymce/skins/ui/oxide/content.mobile.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse} diff --git a/public/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff b/public/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff new file mode 100644 index 0000000..1e3be03 Binary files /dev/null and b/public/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff differ diff --git a/public/tinymce/skins/ui/oxide/skin.css b/public/tinymce/skins/ui/oxide/skin.css new file mode 100644 index 0000000..49a82fa --- /dev/null +++ b/public/tinymce/skins/ui/oxide/skin.css @@ -0,0 +1,3047 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tox { + box-shadow: none; + box-sizing: content-box; + color: #222f3e; + cursor: auto; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 16px; + font-style: normal; + font-weight: normal; + line-height: normal; + -webkit-tap-highlight-color: transparent; + text-decoration: none; + text-shadow: none; + text-transform: none; + vertical-align: initial; + white-space: normal; +} +.tox *:not(svg):not(rect) { + box-sizing: inherit; + color: inherit; + cursor: inherit; + direction: inherit; + font-family: inherit; + font-size: inherit; + font-style: inherit; + font-weight: inherit; + line-height: inherit; + -webkit-tap-highlight-color: inherit; + text-align: inherit; + text-decoration: inherit; + text-shadow: inherit; + text-transform: inherit; + vertical-align: inherit; + white-space: inherit; +} +.tox *:not(svg):not(rect) { + /* stylelint-disable-line no-duplicate-selectors */ + background: transparent; + border: 0; + box-shadow: none; + float: none; + height: auto; + margin: 0; + max-width: none; + outline: 0; + padding: 0; + position: static; + width: auto; +} +.tox:not([dir=rtl]) { + direction: ltr; + text-align: left; +} +.tox[dir=rtl] { + direction: rtl; + text-align: right; +} +.tox-tinymce { + border: 1px solid #cccccc; + border-radius: 0; + box-shadow: none; + box-sizing: border-box; + display: flex; + flex-direction: column; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + overflow: hidden; + position: relative; + visibility: inherit !important; +} +.tox-tinymce-inline { + border: none; + box-shadow: none; +} +.tox-tinymce-inline .tox-editor-header { + background-color: transparent; + border: 1px solid #cccccc; + border-radius: 0; + box-shadow: none; +} +.tox-tinymce-aux { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + z-index: 1300; +} +.tox-tinymce *:focus, +.tox-tinymce-aux *:focus { + outline: none; +} +button::-moz-focus-inner { + border: 0; +} +.tox[dir=rtl] .tox-icon--flip svg { + transform: rotateY(180deg); +} +.tox .accessibility-issue__header { + align-items: center; + display: flex; + margin-bottom: 4px; +} +.tox .accessibility-issue__description { + align-items: stretch; + border: 1px solid #cccccc; + border-radius: 3px; + display: flex; + justify-content: space-between; +} +.tox .accessibility-issue__description > div { + padding-bottom: 4px; +} +.tox .accessibility-issue__description > div > div { + align-items: center; + display: flex; + margin-bottom: 4px; +} +.tox .accessibility-issue__description > *:last-child:not(:only-child) { + border-color: #cccccc; + border-style: solid; +} +.tox .accessibility-issue__repair { + margin-top: 16px; +} +.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description { + background-color: rgba(32, 122, 183, 0.1); + border-color: rgba(32, 122, 183, 0.4); + color: #222f3e; +} +.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description > *:last-child { + border-color: rgba(32, 122, 183, 0.4); +} +.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2 { + color: #207ab7; +} +.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg { + fill: #207ab7; +} +.tox .tox-dialog__body-content .accessibility-issue--info a .tox-icon { + color: #207ab7; +} +.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description { + background-color: rgba(255, 165, 0, 0.1); + border-color: rgba(255, 165, 0, 0.5); + color: #222f3e; +} +.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description > *:last-child { + border-color: rgba(255, 165, 0, 0.5); +} +.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2 { + color: #cc8500; +} +.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg { + fill: #cc8500; +} +.tox .tox-dialog__body-content .accessibility-issue--warn a .tox-icon { + color: #cc8500; +} +.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description { + background-color: rgba(204, 0, 0, 0.1); + border-color: rgba(204, 0, 0, 0.4); + color: #222f3e; +} +.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description > *:last-child { + border-color: rgba(204, 0, 0, 0.4); +} +.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2 { + color: #c00; +} +.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg { + fill: #c00; +} +.tox .tox-dialog__body-content .accessibility-issue--error a .tox-icon { + color: #c00; +} +.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description { + background-color: rgba(120, 171, 70, 0.1); + border-color: rgba(120, 171, 70, 0.4); + color: #222f3e; +} +.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description > *:last-child { + border-color: rgba(120, 171, 70, 0.4); +} +.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2 { + color: #78AB46; +} +.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg { + fill: #78AB46; +} +.tox .tox-dialog__body-content .accessibility-issue--success a .tox-icon { + color: #78AB46; +} +.tox .tox-dialog__body-content .accessibility-issue__header h1, +.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2 { + margin-top: 0; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button { + margin-left: 4px; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) { + margin-left: auto; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description { + padding: 4px 4px 4px 8px; +} +.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description > *:last-child { + border-left-width: 1px; + padding-left: 4px; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button { + margin-right: 4px; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header > *:nth-last-child(2) { + margin-right: auto; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description { + padding: 4px 8px 4px 4px; +} +.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description > *:last-child { + border-right-width: 1px; + padding-right: 4px; +} +.tox .tox-anchorbar { + display: flex; + flex: 0 0 auto; +} +.tox .tox-bar { + display: flex; + flex: 0 0 auto; +} +.tox .tox-button { + background-color: #207ab7; + background-image: none; + background-position: 0 0; + background-repeat: repeat; + border-color: #207ab7; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + color: #fff; + cursor: pointer; + display: inline-block; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 14px; + font-style: normal; + font-weight: bold; + letter-spacing: normal; + line-height: 24px; + margin: 0; + outline: none; + padding: 4px 16px; + text-align: center; + text-decoration: none; + text-transform: none; + white-space: nowrap; +} +.tox .tox-button[disabled] { + background-color: #207ab7; + background-image: none; + border-color: #207ab7; + box-shadow: none; + color: rgba(255, 255, 255, 0.5); + cursor: not-allowed; +} +.tox .tox-button:focus:not(:disabled) { + background-color: #1c6ca1; + background-image: none; + border-color: #1c6ca1; + box-shadow: none; + color: #fff; +} +.tox .tox-button:hover:not(:disabled) { + background-color: #1c6ca1; + background-image: none; + border-color: #1c6ca1; + box-shadow: none; + color: #fff; +} +.tox .tox-button:active:not(:disabled) { + background-color: #185d8c; + background-image: none; + border-color: #185d8c; + box-shadow: none; + color: #fff; +} +.tox .tox-button--secondary { + background-color: #f0f0f0; + background-image: none; + background-position: 0 0; + background-repeat: repeat; + border-color: #f0f0f0; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + color: #222f3e; + font-size: 14px; + font-style: normal; + font-weight: bold; + letter-spacing: normal; + outline: none; + padding: 4px 16px; + text-decoration: none; + text-transform: none; +} +.tox .tox-button--secondary[disabled] { + background-color: #f0f0f0; + background-image: none; + border-color: #f0f0f0; + box-shadow: none; + color: rgba(34, 47, 62, 0.5); +} +.tox .tox-button--secondary:focus:not(:disabled) { + background-color: #e3e3e3; + background-image: none; + border-color: #e3e3e3; + box-shadow: none; + color: #222f3e; +} +.tox .tox-button--secondary:hover:not(:disabled) { + background-color: #e3e3e3; + background-image: none; + border-color: #e3e3e3; + box-shadow: none; + color: #222f3e; +} +.tox .tox-button--secondary:active:not(:disabled) { + background-color: #d6d6d6; + background-image: none; + border-color: #d6d6d6; + box-shadow: none; + color: #222f3e; +} +.tox .tox-button--icon, +.tox .tox-button.tox-button--icon, +.tox .tox-button.tox-button--secondary.tox-button--icon { + padding: 4px; +} +.tox .tox-button--icon .tox-icon svg, +.tox .tox-button.tox-button--icon .tox-icon svg, +.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg { + display: block; + fill: currentColor; +} +.tox .tox-button-link { + background: 0; + border: none; + box-sizing: border-box; + cursor: pointer; + display: inline-block; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 16px; + font-weight: normal; + line-height: 1.3; + margin: 0; + padding: 0; + white-space: nowrap; +} +.tox .tox-button-link--sm { + font-size: 14px; +} +.tox .tox-button--naked { + background-color: transparent; + border-color: transparent; + box-shadow: unset; + color: #222f3e; +} +.tox .tox-button--naked[disabled] { + background-color: #f0f0f0; + border-color: #f0f0f0; + box-shadow: none; + color: rgba(34, 47, 62, 0.5); +} +.tox .tox-button--naked:hover:not(:disabled) { + background-color: #e3e3e3; + border-color: #e3e3e3; + box-shadow: none; + color: #222f3e; +} +.tox .tox-button--naked:focus:not(:disabled) { + background-color: #e3e3e3; + border-color: #e3e3e3; + box-shadow: none; + color: #222f3e; +} +.tox .tox-button--naked:active:not(:disabled) { + background-color: #d6d6d6; + border-color: #d6d6d6; + box-shadow: none; + color: #222f3e; +} +.tox .tox-button--naked .tox-icon svg { + fill: currentColor; +} +.tox .tox-button--naked.tox-button--icon:hover:not(:disabled) { + color: #222f3e; +} +.tox .tox-checkbox { + align-items: center; + border-radius: 3px; + cursor: pointer; + display: flex; + height: 36px; + min-width: 36px; +} +.tox .tox-checkbox__input { + /* Hide from view but visible to screen readers */ + height: 1px; + overflow: hidden; + position: absolute; + top: auto; + width: 1px; +} +.tox .tox-checkbox__icons { + align-items: center; + border-radius: 3px; + box-shadow: 0 0 0 2px transparent; + box-sizing: content-box; + display: flex; + height: 24px; + justify-content: center; + padding: calc(4px - 1px); + width: 24px; +} +.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + display: block; + fill: rgba(34, 47, 62, 0.3); +} +.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg { + display: none; + fill: #207ab7; +} +.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg { + display: none; + fill: #207ab7; +} +.tox .tox-checkbox--disabled { + color: rgba(34, 47, 62, 0.5); + cursor: not-allowed; +} +.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg { + fill: rgba(34, 47, 62, 0.5); +} +.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + fill: rgba(34, 47, 62, 0.5); +} +.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg { + fill: rgba(34, 47, 62, 0.5); +} +.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + display: none; +} +.tox input.tox-checkbox__input:checked + .tox-checkbox__icons .tox-checkbox-icon__checked svg { + display: block; +} +.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__unchecked svg { + display: none; +} +.tox input.tox-checkbox__input:indeterminate + .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg { + display: block; +} +.tox input.tox-checkbox__input:focus + .tox-checkbox__icons { + border-radius: 3px; + box-shadow: inset 0 0 0 1px #207ab7; + padding: calc(4px - 1px); +} +.tox:not([dir=rtl]) .tox-checkbox__label { + margin-left: 4px; +} +.tox:not([dir=rtl]) .tox-checkbox__input { + left: -10000px; +} +.tox:not([dir=rtl]) .tox-bar .tox-checkbox { + margin-left: 4px; +} +.tox[dir=rtl] .tox-checkbox__label { + margin-right: 4px; +} +.tox[dir=rtl] .tox-checkbox__input { + right: -10000px; +} +.tox[dir=rtl] .tox-bar .tox-checkbox { + margin-right: 4px; +} +.tox { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox .tox-collection--toolbar .tox-collection__group { + display: flex; + padding: 0; +} +.tox .tox-collection--grid .tox-collection__group { + display: flex; + flex-wrap: wrap; + max-height: 208px; + overflow-x: hidden; + overflow-y: auto; + padding: 0; +} +.tox .tox-collection--list .tox-collection__group { + border-bottom-width: 0; + border-color: #cccccc; + border-left-width: 0; + border-right-width: 0; + border-style: solid; + border-top-width: 1px; + padding: 4px 0; +} +.tox .tox-collection--list .tox-collection__group:first-child { + border-top-width: 0; +} +.tox .tox-collection__group-heading { + background-color: #e6e6e6; + color: rgba(34, 47, 62, 0.7); + cursor: default; + font-size: 12px; + font-style: normal; + font-weight: normal; + margin-bottom: 4px; + margin-top: -4px; + padding: 4px 8px; + text-transform: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.tox .tox-collection__item { + align-items: center; + color: #222f3e; + cursor: pointer; + display: flex; + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.tox .tox-collection--list .tox-collection__item { + padding: 4px 8px; +} +.tox .tox-collection--toolbar .tox-collection__item { + border-radius: 3px; + padding: 4px; +} +.tox .tox-collection--grid .tox-collection__item { + border-radius: 3px; + padding: 4px; +} +.tox .tox-collection--list .tox-collection__item--enabled { + background-color: #fff; + color: #222f3e; +} +.tox .tox-collection--list .tox-collection__item--active { + background-color: #dee0e2; +} +.tox .tox-collection--toolbar .tox-collection__item--enabled { + background-color: #c8cbcf; + color: #222f3e; +} +.tox .tox-collection--toolbar .tox-collection__item--active { + background-color: #dee0e2; +} +.tox .tox-collection--grid .tox-collection__item--enabled { + background-color: #c8cbcf; + color: #222f3e; +} +.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled) { + background-color: #dee0e2; + color: #222f3e; +} +.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled) { + color: #222f3e; +} +.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled) { + color: #222f3e; +} +.tox .tox-collection__item-icon, +.tox .tox-collection__item-checkmark { + align-items: center; + display: flex; + height: 24px; + justify-content: center; + width: 24px; +} +.tox .tox-collection__item-icon svg, +.tox .tox-collection__item-checkmark svg { + fill: currentColor; +} +.tox .tox-collection--toolbar-lg .tox-collection__item-icon { + height: 48px; + width: 48px; +} +.tox .tox-collection__item-label { + color: currentColor; + display: inline-block; + flex: 1; + -ms-flex-preferred-size: auto; + font-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 24px; + text-transform: none; + word-break: break-all; +} +.tox .tox-collection__item-accessory { + color: rgba(34, 47, 62, 0.7); + display: inline-block; + font-size: 14px; + height: 24px; + line-height: 24px; + text-transform: none; +} +.tox .tox-collection__item-caret { + align-items: center; + display: flex; + min-height: 24px; +} +.tox .tox-collection__item-caret::after { + content: ''; + font-size: 0; + min-height: inherit; +} +.tox .tox-collection__item-caret svg { + fill: #222f3e; +} +.tox .tox-collection__item--state-disabled { + background-color: transparent; + color: rgba(34, 47, 62, 0.5); + cursor: not-allowed; +} +.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg { + fill: rgba(34, 47, 62, 0.5); +} +.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg { + display: none; +} +.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory + .tox-collection__item-checkmark { + display: none; +} +.tox .tox-collection--horizontal { + background-color: #fff; + border: 1px solid #cccccc; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); + display: flex; + flex: 0 0 auto; + flex-shrink: 0; + flex-wrap: nowrap; + margin-bottom: 0; + overflow-x: auto; + padding: 0; +} +.tox .tox-collection--horizontal .tox-collection__group { + align-items: center; + display: flex; + flex-wrap: nowrap; + margin: 0; + padding: 0 4px; +} +.tox .tox-collection--horizontal .tox-collection__item { + height: 34px; + margin: 2px 0 3px 0; + padding: 0 4px; +} +.tox .tox-collection--horizontal .tox-collection__item-label { + white-space: nowrap; +} +.tox .tox-collection--horizontal .tox-collection__item-caret { + margin-left: 4px; +} +.tox .tox-collection__item-container { + display: flex; +} +.tox .tox-collection__item-container--row { + align-items: center; + flex: 1 1 auto; + flex-direction: row; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--align-left { + margin-right: auto; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--align-right { + justify-content: flex-end; + margin-left: auto; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top { + align-items: flex-start; + margin-bottom: auto; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle { + align-items: center; +} +.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom { + align-items: flex-end; + margin-top: auto; +} +.tox .tox-collection__item-container--column { + -ms-grid-row-align: center; + align-self: center; + flex: 1 1 auto; + flex-direction: column; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--align-left { + align-items: flex-start; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--align-right { + align-items: flex-end; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top { + align-self: flex-start; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle { + -ms-grid-row-align: center; + align-self: center; +} +.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom { + align-self: flex-end; +} +.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type) { + border-right: 1px solid #cccccc; +} +.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > *:not(:first-child) { + margin-left: 8px; +} +.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child { + margin-left: 4px; +} +.tox:not([dir=rtl]) .tox-collection__item-accessory { + margin-left: 16px; + text-align: right; +} +.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret { + margin-left: 16px; +} +.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type) { + border-left: 1px solid #cccccc; +} +.tox[dir=rtl] .tox-collection--list .tox-collection__item > *:not(:first-child) { + margin-right: 8px; +} +.tox[dir=rtl] .tox-collection--list .tox-collection__item > .tox-collection__item-label:first-child { + margin-right: 4px; +} +.tox[dir=rtl] .tox-collection__item-accessory { + margin-right: 16px; + text-align: left; +} +.tox[dir=rtl] .tox-collection .tox-collection__item-caret { + margin-right: 16px; + transform: rotateY(180deg); +} +.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret { + margin-right: 4px; +} +.tox .tox-color-picker-container { + display: flex; + flex-direction: row; + height: 225px; + margin: 0; +} +.tox .tox-sv-palette { + box-sizing: border-box; + display: flex; + height: 100%; +} +.tox .tox-sv-palette-spectrum { + height: 100%; +} +.tox .tox-sv-palette, +.tox .tox-sv-palette-spectrum { + width: 225px; +} +.tox .tox-sv-palette-thumb { + background: none; + border: 1px solid black; + border-radius: 50%; + box-sizing: content-box; + height: 12px; + position: absolute; + width: 12px; +} +.tox .tox-sv-palette-inner-thumb { + border: 1px solid white; + border-radius: 50%; + height: 10px; + position: absolute; + width: 10px; +} +.tox .tox-hue-slider { + box-sizing: border-box; + height: 100%; + width: 25px; +} +.tox .tox-hue-slider-spectrum { + background: linear-gradient(to bottom, #f00, #ff0080, #f0f, #8000ff, #00f, #0080ff, #0ff, #00ff80, #0f0, #80ff00, #ff0, #ff8000, #f00); + height: 100%; + width: 100%; +} +.tox .tox-hue-slider, +.tox .tox-hue-slider-spectrum { + width: 20px; +} +.tox .tox-hue-slider-thumb { + background: white; + border: 1px solid black; + box-sizing: content-box; + height: 4px; + width: 100%; +} +.tox .tox-rgb-form { + display: flex; + flex-direction: column; + justify-content: space-between; +} +.tox .tox-rgb-form div { + align-items: center; + display: flex; + justify-content: space-between; + margin-bottom: 5px; + width: inherit; +} +.tox .tox-rgb-form input { + width: 6em; +} +.tox .tox-rgb-form input.tox-invalid { + /* Need !important to override Chrome's focus styling unfortunately */ + border: 1px solid red !important; +} +.tox .tox-rgb-form .tox-rgba-preview { + border: 1px solid black; + flex-grow: 2; + margin-bottom: 0; +} +.tox:not([dir=rtl]) .tox-sv-palette { + margin-right: 15px; +} +.tox:not([dir=rtl]) .tox-hue-slider { + margin-right: 15px; +} +.tox:not([dir=rtl]) .tox-hue-slider-thumb { + margin-left: -1px; +} +.tox:not([dir=rtl]) .tox-rgb-form label { + margin-right: 0.5em; +} +.tox[dir=rtl] .tox-sv-palette { + margin-left: 15px; +} +.tox[dir=rtl] .tox-hue-slider { + margin-left: 15px; +} +.tox[dir=rtl] .tox-hue-slider-thumb { + margin-right: -1px; +} +.tox[dir=rtl] .tox-rgb-form label { + margin-left: 0.5em; +} +.tox .tox-toolbar .tox-swatches, +.tox .tox-toolbar__primary .tox-swatches, +.tox .tox-toolbar__overflow .tox-swatches { + margin: 2px 0 3px 4px; +} +.tox .tox-collection--list .tox-collection__group .tox-swatches-menu { + border: 0; + margin: -4px 0; +} +.tox .tox-swatches__row { + display: flex; +} +.tox .tox-swatch { + height: 30px; + transition: transform 0.15s, box-shadow 0.15s; + width: 30px; +} +.tox .tox-swatch:hover, +.tox .tox-swatch:focus { + box-shadow: 0 0 0 1px rgba(127, 127, 127, 0.3) inset; + transform: scale(0.8); +} +.tox .tox-swatch--remove { + align-items: center; + display: flex; + justify-content: center; +} +.tox .tox-swatch--remove svg path { + stroke: #e74c3c; +} +.tox .tox-swatches__picker-btn { + align-items: center; + background-color: transparent; + border: 0; + cursor: pointer; + display: flex; + height: 30px; + justify-content: center; + outline: none; + padding: 0; + width: 30px; +} +.tox .tox-swatches__picker-btn svg { + height: 24px; + width: 24px; +} +.tox .tox-swatches__picker-btn:hover { + background: #dee0e2; +} +.tox:not([dir=rtl]) .tox-swatches__picker-btn { + margin-left: auto; +} +.tox[dir=rtl] .tox-swatches__picker-btn { + margin-right: auto; +} +.tox .tox-comment-thread { + background: #fff; + position: relative; +} +.tox .tox-comment-thread > *:not(:first-child) { + margin-top: 8px; +} +.tox .tox-comment { + background: #fff; + border: 1px solid #cccccc; + border-radius: 3px; + box-shadow: 0 4px 8px 0 rgba(34, 47, 62, 0.1); + padding: 8px 8px 16px 8px; + position: relative; +} +.tox .tox-comment__header { + align-items: center; + color: #222f3e; + display: flex; + justify-content: space-between; +} +.tox .tox-comment__date { + color: rgba(34, 47, 62, 0.7); + font-size: 12px; +} +.tox .tox-comment__body { + color: #222f3e; + font-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + margin-top: 8px; + position: relative; + text-transform: initial; +} +.tox .tox-comment__body textarea { + resize: none; + white-space: normal; + width: 100%; +} +.tox .tox-comment__expander { + padding-top: 8px; +} +.tox .tox-comment__expander p { + color: rgba(34, 47, 62, 0.7); + font-size: 14px; + font-style: normal; +} +.tox .tox-comment__body p { + margin: 0; +} +.tox .tox-comment__buttonspacing { + padding-top: 16px; + text-align: center; +} +.tox .tox-comment-thread__overlay::after { + background: #fff; + bottom: 0; + content: ""; + display: flex; + left: 0; + opacity: 0.9; + position: absolute; + right: 0; + top: 0; + z-index: 5; +} +.tox .tox-comment__reply { + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + justify-content: flex-end; + margin-top: 8px; +} +.tox .tox-comment__reply > *:first-child { + margin-bottom: 8px; + width: 100%; +} +.tox .tox-comment__edit { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + margin-top: 16px; +} +.tox .tox-comment__gradient::after { + background: linear-gradient(rgba(255, 255, 255, 0), #fff); + bottom: 0; + content: ""; + display: block; + height: 5em; + margin-top: -40px; + position: absolute; + width: 100%; +} +.tox .tox-comment__overlay { + background: #fff; + bottom: 0; + display: flex; + flex-direction: column; + flex-grow: 1; + left: 0; + opacity: 0.9; + position: absolute; + right: 0; + text-align: center; + top: 0; + z-index: 5; +} +.tox .tox-comment__loading-text { + align-items: center; + color: #222f3e; + display: flex; + flex-direction: column; + position: relative; +} +.tox .tox-comment__loading-text > div { + padding-bottom: 16px; +} +.tox .tox-comment__overlaytext { + bottom: 0; + flex-direction: column; + font-size: 14px; + left: 0; + padding: 1em; + position: absolute; + right: 0; + top: 0; + z-index: 10; +} +.tox .tox-comment__overlaytext p { + background-color: #fff; + box-shadow: 0 0 8px 8px #fff; + color: #222f3e; + text-align: center; +} +.tox .tox-comment__overlaytext div:nth-of-type(2) { + font-size: 0.8em; +} +.tox .tox-comment__busy-spinner { + align-items: center; + background-color: #fff; + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 20; +} +.tox .tox-comment__scroll { + display: flex; + flex-direction: column; + flex-shrink: 1; + overflow: auto; +} +.tox .tox-conversations { + margin: 8px; +} +.tox:not([dir=rtl]) .tox-comment__edit { + margin-left: 8px; +} +.tox:not([dir=rtl]) .tox-comment__buttonspacing > *:last-child, +.tox:not([dir=rtl]) .tox-comment__edit > *:last-child, +.tox:not([dir=rtl]) .tox-comment__reply > *:last-child { + margin-left: 8px; +} +.tox[dir=rtl] .tox-comment__edit { + margin-right: 8px; +} +.tox[dir=rtl] .tox-comment__buttonspacing > *:last-child, +.tox[dir=rtl] .tox-comment__edit > *:last-child, +.tox[dir=rtl] .tox-comment__reply > *:last-child { + margin-right: 8px; +} +.tox .tox-user { + align-items: center; + display: flex; +} +.tox .tox-user__avatar svg { + fill: rgba(34, 47, 62, 0.7); +} +.tox .tox-user__name { + color: rgba(34, 47, 62, 0.7); + font-size: 12px; + font-style: normal; + font-weight: bold; + text-transform: uppercase; +} +.tox:not([dir=rtl]) .tox-user__avatar svg { + margin-right: 8px; +} +.tox:not([dir=rtl]) .tox-user__avatar + .tox-user__name { + margin-left: 8px; +} +.tox[dir=rtl] .tox-user__avatar svg { + margin-left: 8px; +} +.tox[dir=rtl] .tox-user__avatar + .tox-user__name { + margin-right: 8px; +} +.tox .tox-dialog-wrap { + align-items: center; + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: fixed; + right: 0; + top: 0; + z-index: 1100; +} +.tox .tox-dialog-wrap__backdrop { + background-color: rgba(255, 255, 255, 0.75); + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 1; +} +.tox .tox-dialog-wrap__backdrop--opaque { + background-color: #fff; +} +.tox .tox-dialog { + background-color: #fff; + border-color: #cccccc; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: 0 16px 16px -10px rgba(34, 47, 62, 0.15), 0 0 40px 1px rgba(34, 47, 62, 0.15); + display: flex; + flex-direction: column; + max-height: 100%; + max-width: 480px; + overflow: hidden; + position: relative; + width: 95vw; + z-index: 2; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox .tox-dialog { + align-self: flex-start; + margin: 8px auto; + width: calc(100vw - 16px); + } +} +.tox .tox-dialog-inline { + z-index: 1100; +} +.tox .tox-dialog__header { + align-items: center; + background-color: #fff; + border-bottom: none; + color: #222f3e; + display: flex; + font-size: 16px; + justify-content: space-between; + padding: 8px 16px 0 16px; + position: relative; +} +.tox .tox-dialog__header .tox-button { + z-index: 1; +} +.tox .tox-dialog__draghandle { + cursor: grab; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} +.tox .tox-dialog__draghandle:active { + cursor: grabbing; +} +.tox .tox-dialog__dismiss { + margin-left: auto; +} +.tox .tox-dialog__title { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 20px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + margin: 0; + text-transform: none; +} +.tox .tox-dialog__body { + color: #222f3e; + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; + font-size: 16px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + min-width: 0; + text-align: left; + text-transform: none; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox .tox-dialog__body { + flex-direction: column; + } +} +.tox .tox-dialog__body-nav { + align-items: flex-start; + display: flex; + flex-direction: column; + padding: 16px 16px; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox .tox-dialog__body-nav { + flex-direction: row; + -webkit-overflow-scrolling: touch; + overflow-x: auto; + padding-bottom: 0; + } +} +.tox .tox-dialog__body-nav-item { + border-bottom: 2px solid transparent; + color: rgba(34, 47, 62, 0.7); + display: inline-block; + font-size: 14px; + line-height: 1.3; + margin-bottom: 8px; + text-decoration: none; + white-space: nowrap; +} +.tox .tox-dialog__body-nav-item:focus { + background-color: rgba(32, 122, 183, 0.1); +} +.tox .tox-dialog__body-nav-item--active { + border-bottom: 2px solid #207ab7; + color: #207ab7; +} +.tox .tox-dialog__body-content { + box-sizing: border-box; + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; + max-height: 650px; + overflow: auto; + -webkit-overflow-scrolling: touch; + padding: 16px 16px; +} +.tox .tox-dialog__body-content > * { + margin-bottom: 0; + margin-top: 16px; +} +.tox .tox-dialog__body-content > *:first-child { + margin-top: 0; +} +.tox .tox-dialog__body-content > *:last-child { + margin-bottom: 0; +} +.tox .tox-dialog__body-content > *:only-child { + margin-bottom: 0; + margin-top: 0; +} +.tox .tox-dialog__body-content a { + color: #207ab7; + cursor: pointer; + text-decoration: none; +} +.tox .tox-dialog__body-content a:hover, +.tox .tox-dialog__body-content a:focus { + color: #185d8c; + text-decoration: none; +} +.tox .tox-dialog__body-content a:active { + color: #185d8c; + text-decoration: none; +} +.tox .tox-dialog__body-content svg { + fill: #222f3e; +} +.tox .tox-dialog__body-content ul { + display: block; + list-style-type: disc; + margin-bottom: 16px; + -webkit-margin-end: 0; + margin-inline-end: 0; + -webkit-margin-start: 0; + margin-inline-start: 0; + -webkit-padding-start: 2.5rem; + padding-inline-start: 2.5rem; +} +.tox .tox-dialog__body-content .tox-form__group h1 { + color: #222f3e; + font-size: 20px; + font-style: normal; + font-weight: bold; + letter-spacing: normal; + margin-bottom: 16px; + margin-top: 2rem; + text-transform: none; +} +.tox .tox-dialog__body-content .tox-form__group h2 { + color: #222f3e; + font-size: 16px; + font-style: normal; + font-weight: bold; + letter-spacing: normal; + margin-bottom: 16px; + margin-top: 2rem; + text-transform: none; +} +.tox .tox-dialog__body-content .tox-form__group p { + margin-bottom: 16px; +} +.tox .tox-dialog__body-content .tox-form__group h1:first-child, +.tox .tox-dialog__body-content .tox-form__group h2:first-child, +.tox .tox-dialog__body-content .tox-form__group p:first-child { + margin-top: 0; +} +.tox .tox-dialog__body-content .tox-form__group h1:last-child, +.tox .tox-dialog__body-content .tox-form__group h2:last-child, +.tox .tox-dialog__body-content .tox-form__group p:last-child { + margin-bottom: 0; +} +.tox .tox-dialog__body-content .tox-form__group h1:only-child, +.tox .tox-dialog__body-content .tox-form__group h2:only-child, +.tox .tox-dialog__body-content .tox-form__group p:only-child { + margin-bottom: 0; + margin-top: 0; +} +.tox .tox-dialog--width-lg { + height: 650px; + max-width: 1200px; +} +.tox .tox-dialog--width-md { + max-width: 800px; +} +.tox .tox-dialog--width-md .tox-dialog__body-content { + overflow: auto; +} +.tox .tox-dialog__body-content--centered { + text-align: center; +} +.tox .tox-dialog__footer { + align-items: center; + background-color: #fff; + border-top: 1px solid #cccccc; + display: flex; + justify-content: space-between; + padding: 8px 16px; +} +.tox .tox-dialog__footer-start, +.tox .tox-dialog__footer-end { + display: flex; +} +.tox .tox-dialog__busy-spinner { + align-items: center; + background-color: rgba(255, 255, 255, 0.75); + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 3; +} +.tox .tox-dialog__table { + border-collapse: collapse; + width: 100%; +} +.tox .tox-dialog__table thead th { + font-weight: bold; + padding-bottom: 8px; +} +.tox .tox-dialog__table tbody tr { + border-bottom: 1px solid #cccccc; +} +.tox .tox-dialog__table tbody tr:last-child { + border-bottom: none; +} +.tox .tox-dialog__table td { + padding-bottom: 8px; + padding-top: 8px; +} +.tox .tox-dialog__popups { + position: absolute; + width: 100%; + z-index: 1100; +} +.tox .tox-dialog__body-iframe { + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; +} +.tox .tox-dialog__body-iframe .tox-navobj { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2) { + flex: 1; + -ms-flex-preferred-size: auto; + height: 100%; +} +.tox .tox-dialog-dock-fadeout { + opacity: 0; + visibility: hidden; +} +.tox .tox-dialog-dock-fadein { + opacity: 1; + visibility: visible; +} +.tox .tox-dialog-dock-transition { + transition: visibility 0s linear 0.3s, opacity 0.3s ease; +} +.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein { + transition-delay: 0s; +} +.tox.tox-platform-ie { + /* IE11 CSS styles go here */ +} +.tox.tox-platform-ie .tox-dialog-wrap { + position: -ms-device-fixed; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav { + margin-right: 0; + } +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child) { + margin-left: 8px; + } +} +.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start > *, +.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end > * { + margin-left: 8px; +} +.tox[dir=rtl] .tox-dialog__body { + text-align: right; +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav { + margin-left: 0; + } +} +@media only screen and (max-width:767px) { + body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child) { + margin-right: 8px; + } +} +.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start > *, +.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end > * { + margin-right: 8px; +} +body.tox-dialog__disable-scroll { + overflow: hidden; +} +.tox .tox-dropzone-container { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-dropzone { + align-items: center; + background: #fff; + border: 2px dashed #cccccc; + box-sizing: border-box; + display: flex; + flex-direction: column; + flex-grow: 1; + justify-content: center; + min-height: 100px; + padding: 10px; +} +.tox .tox-dropzone p { + color: rgba(34, 47, 62, 0.7); + margin: 0 0 16px 0; +} +.tox .tox-edit-area { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; + overflow: hidden; + position: relative; +} +.tox .tox-edit-area__iframe { + background-color: #fff; + border: 0; + box-sizing: border-box; + flex: 1; + -ms-flex-preferred-size: auto; + height: 100%; + position: absolute; + width: 100%; +} +.tox.tox-inline-edit-area { + border: 1px dotted #cccccc; +} +.tox .tox-editor-container { + display: flex; + flex: 1 1 auto; + flex-direction: column; + overflow: hidden; +} +.tox .tox-editor-header { + z-index: 1; +} +.tox:not(.tox-tinymce-inline) .tox-editor-header { + box-shadow: none; + transition: box-shadow 0.5s; +} +.tox.tox-tinymce--toolbar-bottom .tox-editor-header, +.tox.tox-tinymce-inline .tox-editor-header { + margin-bottom: -1px; +} +.tox.tox-tinymce--toolbar-sticky-on .tox-editor-header { + background-color: transparent; + box-shadow: 0 4px 4px -3px rgba(0, 0, 0, 0.25); +} +.tox-editor-dock-fadeout { + opacity: 0; + visibility: hidden; +} +.tox-editor-dock-fadein { + opacity: 1; + visibility: visible; +} +.tox-editor-dock-transition { + transition: visibility 0s linear 0.25s, opacity 0.25s ease; +} +.tox-editor-dock-transition.tox-editor-dock-fadein { + transition-delay: 0s; +} +.tox .tox-control-wrap { + flex: 1; + position: relative; +} +.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid, +.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown, +.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid { + display: none; +} +.tox .tox-control-wrap svg { + display: block; +} +.tox .tox-control-wrap__status-icon-wrap { + position: absolute; + top: 50%; + transform: translateY(-50%); +} +.tox .tox-control-wrap__status-icon-invalid svg { + fill: #c00; +} +.tox .tox-control-wrap__status-icon-unknown svg { + fill: orange; +} +.tox .tox-control-wrap__status-icon-valid svg { + fill: green; +} +.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield, +.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield, +.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield { + padding-right: 32px; +} +.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap { + right: 4px; +} +.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield, +.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield, +.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield { + padding-left: 32px; +} +.tox[dir=rtl] .tox-control-wrap__status-icon-wrap { + left: 4px; +} +.tox .tox-autocompleter { + max-width: 25em; +} +.tox .tox-autocompleter .tox-menu { + max-width: 25em; +} +.tox .tox-autocompleter .tox-autocompleter-highlight { + font-weight: bold; +} +.tox .tox-color-input { + display: flex; + position: relative; + z-index: 1; +} +.tox .tox-color-input .tox-textfield { + z-index: -1; +} +.tox .tox-color-input span { + border-color: rgba(34, 47, 62, 0.2); + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + height: 24px; + position: absolute; + top: 6px; + width: 24px; +} +.tox .tox-color-input span:hover:not([aria-disabled=true]), +.tox .tox-color-input span:focus:not([aria-disabled=true]) { + border-color: #207ab7; + cursor: pointer; +} +.tox .tox-color-input span::before { + background-image: linear-gradient(45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%), linear-gradient(-45deg, rgba(0, 0, 0, 0.25) 25%, transparent 25%), linear-gradient(45deg, transparent 75%, rgba(0, 0, 0, 0.25) 75%), linear-gradient(-45deg, transparent 75%, rgba(0, 0, 0, 0.25) 75%); + background-position: 0 0, 0 6px, 6px -6px, -6px 0; + background-size: 12px 12px; + border: 1px solid #fff; + border-radius: 3px; + box-sizing: border-box; + content: ''; + height: 24px; + left: -1px; + position: absolute; + top: -1px; + width: 24px; + z-index: -1; +} +.tox .tox-color-input span[aria-disabled=true] { + cursor: not-allowed; +} +.tox:not([dir=rtl]) .tox-color-input { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox:not([dir=rtl]) .tox-color-input .tox-textfield { + padding-left: 36px; +} +.tox:not([dir=rtl]) .tox-color-input span { + left: 6px; +} +.tox[dir="rtl"] .tox-color-input { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox[dir="rtl"] .tox-color-input .tox-textfield { + padding-right: 36px; +} +.tox[dir="rtl"] .tox-color-input span { + right: 6px; +} +.tox .tox-label, +.tox .tox-toolbar-label { + color: rgba(34, 47, 62, 0.7); + display: block; + font-size: 14px; + font-style: normal; + font-weight: normal; + line-height: 1.3; + padding: 0 8px 0 0; + text-transform: none; + white-space: nowrap; +} +.tox .tox-toolbar-label { + padding: 0 8px; +} +.tox[dir=rtl] .tox-label { + padding: 0 0 0 8px; +} +.tox .tox-form { + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group { + box-sizing: border-box; + margin-bottom: 4px; +} +.tox .tox-form-group--maximize { + flex: 1; +} +.tox .tox-form__group--error { + color: #c00; +} +.tox .tox-form__group--collection { + display: flex; +} +.tox .tox-form__grid { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; +} +.tox .tox-form__grid--2col > .tox-form__group { + width: calc(50% - (8px / 2)); +} +.tox .tox-form__grid--3col > .tox-form__group { + width: calc(100% / 3 - (8px / 2)); +} +.tox .tox-form__grid--4col > .tox-form__group { + width: calc(25% - (8px / 2)); +} +.tox .tox-form__controls-h-stack { + align-items: center; + display: flex; +} +.tox .tox-form__group--inline { + align-items: center; + display: flex; +} +.tox .tox-form__group--stretched { + display: flex; + flex: 1; + flex-direction: column; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group--stretched .tox-textarea { + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group--stretched .tox-navobj { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-form__group--stretched .tox-navobj :nth-child(2) { + flex: 1; + -ms-flex-preferred-size: auto; + height: 100%; +} +.tox:not([dir=rtl]) .tox-form__controls-h-stack > *:not(:first-child) { + margin-left: 4px; +} +.tox[dir=rtl] .tox-form__controls-h-stack > *:not(:first-child) { + margin-right: 4px; +} +.tox .tox-lock.tox-locked .tox-lock-icon__unlock, +.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock { + display: none; +} +.tox .tox-textfield, +.tox .tox-toolbar-textfield, +.tox .tox-listboxfield .tox-listbox--select, +.tox .tox-textarea { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-color: #cccccc; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + color: #222f3e; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 16px; + line-height: 24px; + margin: 0; + min-height: 34px; + outline: none; + padding: 5px 4.75px; + resize: none; + width: 100%; +} +.tox .tox-textfield[disabled], +.tox .tox-textarea[disabled] { + background-color: #f2f2f2; + color: rgba(34, 47, 62, 0.85); + cursor: not-allowed; +} +.tox .tox-textfield:focus, +.tox .tox-listboxfield .tox-listbox--select:focus, +.tox .tox-textarea:focus { + background-color: #fff; + border-color: #207ab7; + box-shadow: none; + outline: none; +} +.tox .tox-toolbar-textfield { + border-width: 0; + margin-bottom: 3px; + margin-top: 2px; + max-width: 250px; +} +.tox .tox-naked-btn { + background-color: transparent; + border: 0; + border-color: transparent; + box-shadow: unset; + color: #207ab7; + cursor: pointer; + display: block; + margin: 0; + padding: 0; +} +.tox .tox-naked-btn svg { + display: block; + fill: #222f3e; +} +.tox:not([dir=rtl]) .tox-toolbar-textfield + * { + margin-left: 4px; +} +.tox[dir=rtl] .tox-toolbar-textfield + * { + margin-right: 4px; +} +.tox .tox-listboxfield { + cursor: pointer; + position: relative; +} +.tox .tox-listboxfield .tox-listbox--select[disabled] { + background-color: #f2f2f2; + color: rgba(34, 47, 62, 0.85); + cursor: not-allowed; +} +.tox .tox-listbox__select-label { + cursor: default; + flex: 1; + margin: 0 4px; +} +.tox .tox-listbox__select-chevron { + align-items: center; + display: flex; + justify-content: center; + width: 16px; +} +.tox .tox-listbox__select-chevron svg { + fill: #222f3e; +} +.tox .tox-listboxfield .tox-listbox--select { + align-items: center; + display: flex; +} +.tox:not([dir=rtl]) .tox-listboxfield svg { + right: 8px; +} +.tox[dir=rtl] .tox-listboxfield svg { + left: 8px; +} +.tox .tox-selectfield { + cursor: pointer; + position: relative; +} +.tox .tox-selectfield select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-color: #cccccc; + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + color: #222f3e; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + font-size: 16px; + line-height: 24px; + margin: 0; + min-height: 34px; + outline: none; + padding: 5px 4.75px; + resize: none; + width: 100%; +} +.tox .tox-selectfield select[disabled] { + background-color: #f2f2f2; + color: rgba(34, 47, 62, 0.85); + cursor: not-allowed; +} +.tox .tox-selectfield select::-ms-expand { + display: none; +} +.tox .tox-selectfield select:focus { + background-color: #fff; + border-color: #207ab7; + box-shadow: none; + outline: none; +} +.tox .tox-selectfield svg { + pointer-events: none; + position: absolute; + top: 50%; + transform: translateY(-50%); +} +.tox:not([dir=rtl]) .tox-selectfield select[size="0"], +.tox:not([dir=rtl]) .tox-selectfield select[size="1"] { + padding-right: 24px; +} +.tox:not([dir=rtl]) .tox-selectfield svg { + right: 8px; +} +.tox[dir=rtl] .tox-selectfield select[size="0"], +.tox[dir=rtl] .tox-selectfield select[size="1"] { + padding-left: 24px; +} +.tox[dir=rtl] .tox-selectfield svg { + left: 8px; +} +.tox .tox-textarea { + -webkit-appearance: textarea; + -moz-appearance: textarea; + appearance: textarea; + white-space: pre-wrap; +} +.tox-fullscreen { + border: 0; + height: 100%; + margin: 0; + overflow: hidden; + -ms-scroll-chaining: none; + overscroll-behavior: none; + padding: 0; + touch-action: pinch-zoom; + width: 100%; +} +.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { + display: none; +} +.tox.tox-tinymce.tox-fullscreen, +.tox-shadowhost.tox-fullscreen { + left: 0; + position: fixed; + top: 0; + z-index: 1200; +} +.tox.tox-tinymce.tox-fullscreen { + background-color: transparent; +} +.tox-fullscreen .tox.tox-tinymce-aux, +.tox-fullscreen ~ .tox.tox-tinymce-aux { + z-index: 1201; +} +.tox .tox-help__more-link { + list-style: none; + margin-top: 1em; +} +.tox .tox-image-tools { + width: 100%; +} +.tox .tox-image-tools__toolbar { + align-items: center; + display: flex; + justify-content: center; +} +.tox .tox-image-tools__image { + background-color: #666; + height: 380px; + overflow: auto; + position: relative; + width: 100%; +} +.tox .tox-image-tools__image, +.tox .tox-image-tools__image + .tox-image-tools__toolbar { + margin-top: 8px; +} +.tox .tox-image-tools__image-bg { + background: url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==); +} +.tox .tox-image-tools__toolbar > .tox-spacer { + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-croprect-block { + background: black; + filter: alpha(opacity=50); + opacity: 0.5; + position: absolute; + zoom: 1; +} +.tox .tox-croprect-handle { + border: 2px solid white; + height: 20px; + left: 0; + position: absolute; + top: 0; + width: 20px; +} +.tox .tox-croprect-handle-move { + border: 0; + cursor: move; + position: absolute; +} +.tox .tox-croprect-handle-nw { + border-width: 2px 0 0 2px; + cursor: nw-resize; + left: 100px; + margin: -2px 0 0 -2px; + top: 100px; +} +.tox .tox-croprect-handle-ne { + border-width: 2px 2px 0 0; + cursor: ne-resize; + left: 200px; + margin: -2px 0 0 -20px; + top: 100px; +} +.tox .tox-croprect-handle-sw { + border-width: 0 0 2px 2px; + cursor: sw-resize; + left: 100px; + margin: -20px 2px 0 -2px; + top: 200px; +} +.tox .tox-croprect-handle-se { + border-width: 0 2px 2px 0; + cursor: se-resize; + left: 200px; + margin: -20px 0 0 -20px; + top: 200px; +} +.tox:not([dir=rtl]) .tox-image-tools__toolbar > .tox-slider:not(:first-of-type) { + margin-left: 8px; +} +.tox:not([dir=rtl]) .tox-image-tools__toolbar > .tox-button + .tox-slider { + margin-left: 32px; +} +.tox:not([dir=rtl]) .tox-image-tools__toolbar > .tox-slider + .tox-button { + margin-left: 32px; +} +.tox[dir=rtl] .tox-image-tools__toolbar > .tox-slider:not(:first-of-type) { + margin-right: 8px; +} +.tox[dir=rtl] .tox-image-tools__toolbar > .tox-button + .tox-slider { + margin-right: 32px; +} +.tox[dir=rtl] .tox-image-tools__toolbar > .tox-slider + .tox-button { + margin-right: 32px; +} +.tox .tox-insert-table-picker { + display: flex; + flex-wrap: wrap; + width: 170px; +} +.tox .tox-insert-table-picker > div { + border-color: #cccccc; + border-style: solid; + border-width: 0 1px 1px 0; + box-sizing: border-box; + height: 17px; + width: 17px; +} +.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker { + margin: -4px 0; +} +.tox .tox-insert-table-picker .tox-insert-table-picker__selected { + background-color: rgba(32, 122, 183, 0.5); + border-color: rgba(32, 122, 183, 0.5); +} +.tox .tox-insert-table-picker__label { + color: rgba(34, 47, 62, 0.7); + display: block; + font-size: 14px; + padding: 4px; + text-align: center; + width: 100%; +} +.tox:not([dir=rtl]) { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox:not([dir=rtl]) .tox-insert-table-picker > div:nth-child(10n) { + border-right: 0; +} +.tox[dir=rtl] { + /* stylelint-disable-next-line no-descending-specificity */ +} +.tox[dir=rtl] .tox-insert-table-picker > div:nth-child(10n+1) { + border-right: 0; +} +.tox { + /* stylelint-disable */ + /* stylelint-enable */ +} +.tox .tox-menu { + background-color: #fff; + border: 1px solid #cccccc; + border-radius: 3px; + box-shadow: 0 4px 8px 0 rgba(34, 47, 62, 0.1); + display: inline-block; + overflow: hidden; + vertical-align: top; + z-index: 1150; +} +.tox .tox-menu.tox-collection.tox-collection--list { + padding: 0; +} +.tox .tox-menu.tox-collection.tox-collection--toolbar { + padding: 4px; +} +.tox .tox-menu.tox-collection.tox-collection--grid { + padding: 4px; +} +.tox .tox-menu__label h1, +.tox .tox-menu__label h2, +.tox .tox-menu__label h3, +.tox .tox-menu__label h4, +.tox .tox-menu__label h5, +.tox .tox-menu__label h6, +.tox .tox-menu__label p, +.tox .tox-menu__label blockquote, +.tox .tox-menu__label code { + margin: 0; +} +.tox .tox-menubar { + background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E") left 0 top 0 #fff; + background-color: #fff; + display: flex; + flex: 0 0 auto; + flex-shrink: 0; + flex-wrap: wrap; + padding: 0 4px 0 4px; +} +.tox.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar { + border-top: 1px solid #cccccc; +} +/* Deprecated. Remove in next major release */ +.tox .tox-mbtn { + align-items: center; + background: transparent; + border: 0; + border-radius: 3px; + box-shadow: none; + color: #222f3e; + display: flex; + flex: 0 0 auto; + font-size: 14px; + font-style: normal; + font-weight: normal; + height: 34px; + justify-content: center; + margin: 2px 0 3px 0; + outline: none; + overflow: hidden; + padding: 0 4px; + text-transform: none; + width: auto; +} +.tox .tox-mbtn[disabled] { + background-color: transparent; + border: 0; + box-shadow: none; + color: rgba(34, 47, 62, 0.5); + cursor: not-allowed; +} +.tox .tox-mbtn:focus:not(:disabled) { + background: #dee0e2; + border: 0; + box-shadow: none; + color: #222f3e; +} +.tox .tox-mbtn--active { + background: #c8cbcf; + border: 0; + box-shadow: none; + color: #222f3e; +} +.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active) { + background: #dee0e2; + border: 0; + box-shadow: none; + color: #222f3e; +} +.tox .tox-mbtn__select-label { + cursor: default; + font-weight: normal; + margin: 0 4px; +} +.tox .tox-mbtn[disabled] .tox-mbtn__select-label { + cursor: not-allowed; +} +.tox .tox-mbtn__select-chevron { + align-items: center; + display: flex; + justify-content: center; + width: 16px; + display: none; +} +.tox .tox-notification { + border-radius: 3px; + border-style: solid; + border-width: 1px; + box-shadow: none; + box-sizing: border-box; + display: -ms-grid; + display: grid; + font-size: 14px; + font-weight: normal; + -ms-grid-columns: minmax(40px, 1fr) auto minmax(40px, 1fr); + grid-template-columns: minmax(40px, 1fr) auto minmax(40px, 1fr); + margin-top: 4px; + opacity: 0; + padding: 4px; + transition: transform 100ms ease-in, opacity 150ms ease-in; +} +.tox .tox-notification p { + font-size: 14px; + font-weight: normal; +} +.tox .tox-notification a { + cursor: pointer; + text-decoration: underline; +} +.tox .tox-notification--in { + opacity: 1; +} +.tox .tox-notification--success { + background-color: #e4eeda; + border-color: #d7e6c8; + color: #222f3e; +} +.tox .tox-notification--success p { + color: #222f3e; +} +.tox .tox-notification--success a { + color: #547831; +} +.tox .tox-notification--success svg { + fill: #222f3e; +} +.tox .tox-notification--error { + background-color: #f8dede; + border-color: #f2bfbf; + color: #222f3e; +} +.tox .tox-notification--error p { + color: #222f3e; +} +.tox .tox-notification--error a { + color: #c00; +} +.tox .tox-notification--error svg { + fill: #222f3e; +} +.tox .tox-notification--warn, +.tox .tox-notification--warning { + background-color: #fffaea; + border-color: #ffe89d; + color: #222f3e; +} +.tox .tox-notification--warn p, +.tox .tox-notification--warning p { + color: #222f3e; +} +.tox .tox-notification--warn a, +.tox .tox-notification--warning a { + color: #222f3e; +} +.tox .tox-notification--warn svg, +.tox .tox-notification--warning svg { + fill: #222f3e; +} +.tox .tox-notification--info { + background-color: #d9edf7; + border-color: #779ecb; + color: #222f3e; +} +.tox .tox-notification--info p { + color: #222f3e; +} +.tox .tox-notification--info a { + color: #222f3e; +} +.tox .tox-notification--info svg { + fill: #222f3e; +} +.tox .tox-notification__body { + -ms-grid-row-align: center; + align-self: center; + color: #222f3e; + font-size: 14px; + -ms-grid-column-span: 1; + grid-column-end: 3; + -ms-grid-column: 2; + grid-column-start: 2; + -ms-grid-row-span: 1; + grid-row-end: 2; + -ms-grid-row: 1; + grid-row-start: 1; + text-align: center; + white-space: normal; + word-break: break-all; + word-break: break-word; +} +.tox .tox-notification__body > * { + margin: 0; +} +.tox .tox-notification__body > * + * { + margin-top: 1rem; +} +.tox .tox-notification__icon { + -ms-grid-row-align: center; + align-self: center; + -ms-grid-column-span: 1; + grid-column-end: 2; + -ms-grid-column: 1; + grid-column-start: 1; + -ms-grid-row-span: 1; + grid-row-end: 2; + -ms-grid-row: 1; + grid-row-start: 1; + -ms-grid-column-align: end; + justify-self: end; +} +.tox .tox-notification__icon svg { + display: block; +} +.tox .tox-notification__dismiss { + -ms-grid-row-align: start; + align-self: start; + -ms-grid-column-span: 1; + grid-column-end: 4; + -ms-grid-column: 3; + grid-column-start: 3; + -ms-grid-row-span: 1; + grid-row-end: 2; + -ms-grid-row: 1; + grid-row-start: 1; + -ms-grid-column-align: end; + justify-self: end; +} +.tox .tox-notification .tox-progress-bar { + -ms-grid-column-span: 3; + grid-column-end: 4; + -ms-grid-column: 1; + grid-column-start: 1; + -ms-grid-row-span: 1; + grid-row-end: 3; + -ms-grid-row: 2; + grid-row-start: 2; + -ms-grid-column-align: center; + justify-self: center; +} +.tox .tox-pop { + display: inline-block; + position: relative; +} +.tox .tox-pop--resizing { + transition: width 0.1s ease; +} +.tox .tox-pop--resizing .tox-toolbar, +.tox .tox-pop--resizing .tox-toolbar__group { + flex-wrap: nowrap; +} +.tox .tox-pop--transition { + transition: 0.15s ease; + transition-property: left, right, top, bottom; +} +.tox .tox-pop--transition::before, +.tox .tox-pop--transition::after { + transition: all 0.15s, visibility 0s, opacity 0.075s ease 0.075s; +} +.tox .tox-pop__dialog { + background-color: #fff; + border: 1px solid #cccccc; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); + min-width: 0; + overflow: hidden; +} +.tox .tox-pop__dialog > *:not(.tox-toolbar) { + margin: 4px 4px 4px 8px; +} +.tox .tox-pop__dialog .tox-toolbar { + background-color: transparent; + margin-bottom: -1px; +} +.tox .tox-pop::before, +.tox .tox-pop::after { + border-style: solid; + content: ''; + display: block; + height: 0; + opacity: 1; + position: absolute; + width: 0; +} +.tox .tox-pop.tox-pop--inset::before, +.tox .tox-pop.tox-pop--inset::after { + opacity: 0; + transition: all 0s 0.15s, visibility 0s, opacity 0.075s ease; +} +.tox .tox-pop.tox-pop--bottom::before, +.tox .tox-pop.tox-pop--bottom::after { + left: 50%; + top: 100%; +} +.tox .tox-pop.tox-pop--bottom::after { + border-color: #fff transparent transparent transparent; + border-width: 8px; + margin-left: -8px; + margin-top: -1px; +} +.tox .tox-pop.tox-pop--bottom::before { + border-color: #cccccc transparent transparent transparent; + border-width: 9px; + margin-left: -9px; +} +.tox .tox-pop.tox-pop--top::before, +.tox .tox-pop.tox-pop--top::after { + left: 50%; + top: 0; + transform: translateY(-100%); +} +.tox .tox-pop.tox-pop--top::after { + border-color: transparent transparent #fff transparent; + border-width: 8px; + margin-left: -8px; + margin-top: 1px; +} +.tox .tox-pop.tox-pop--top::before { + border-color: transparent transparent #cccccc transparent; + border-width: 9px; + margin-left: -9px; +} +.tox .tox-pop.tox-pop--left::before, +.tox .tox-pop.tox-pop--left::after { + left: 0; + top: calc(50% - 1px); + transform: translateY(-50%); +} +.tox .tox-pop.tox-pop--left::after { + border-color: transparent #fff transparent transparent; + border-width: 8px; + margin-left: -15px; +} +.tox .tox-pop.tox-pop--left::before { + border-color: transparent #cccccc transparent transparent; + border-width: 10px; + margin-left: -19px; +} +.tox .tox-pop.tox-pop--right::before, +.tox .tox-pop.tox-pop--right::after { + left: 100%; + top: calc(50% + 1px); + transform: translateY(-50%); +} +.tox .tox-pop.tox-pop--right::after { + border-color: transparent transparent transparent #fff; + border-width: 8px; + margin-left: -1px; +} +.tox .tox-pop.tox-pop--right::before { + border-color: transparent transparent transparent #cccccc; + border-width: 10px; + margin-left: -1px; +} +.tox .tox-pop.tox-pop--align-left::before, +.tox .tox-pop.tox-pop--align-left::after { + left: 20px; +} +.tox .tox-pop.tox-pop--align-right::before, +.tox .tox-pop.tox-pop--align-right::after { + left: calc(100% - 20px); +} +.tox .tox-sidebar-wrap { + display: flex; + flex-direction: row; + flex-grow: 1; + -ms-flex-preferred-size: 0; + min-height: 0; +} +.tox .tox-sidebar { + background-color: #fff; + display: flex; + flex-direction: row; + justify-content: flex-end; +} +.tox .tox-sidebar__slider { + display: flex; + overflow: hidden; +} +.tox .tox-sidebar__pane-container { + display: flex; +} +.tox .tox-sidebar__pane { + display: flex; +} +.tox .tox-sidebar--sliding-closed { + opacity: 0; +} +.tox .tox-sidebar--sliding-open { + opacity: 1; +} +.tox .tox-sidebar--sliding-growing, +.tox .tox-sidebar--sliding-shrinking { + transition: width 0.5s ease, opacity 0.5s ease; +} +.tox .tox-selector { + background-color: #4099ff; + border-color: #4099ff; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + display: inline-block; + height: 10px; + position: absolute; + width: 10px; +} +.tox.tox-platform-touch .tox-selector { + height: 12px; + width: 12px; +} +.tox .tox-slider { + align-items: center; + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; + height: 24px; + justify-content: center; + position: relative; +} +.tox .tox-slider__rail { + background-color: transparent; + border: 1px solid #cccccc; + border-radius: 3px; + height: 10px; + min-width: 120px; + width: 100%; +} +.tox .tox-slider__handle { + background-color: #207ab7; + border: 2px solid #185d8c; + border-radius: 3px; + box-shadow: none; + height: 24px; + left: 50%; + position: absolute; + top: 50%; + transform: translateX(-50%) translateY(-50%); + width: 14px; +} +.tox .tox-source-code { + overflow: auto; +} +.tox .tox-spinner { + display: flex; +} +.tox .tox-spinner > div { + animation: tam-bouncing-dots 1.5s ease-in-out 0s infinite both; + background-color: rgba(34, 47, 62, 0.7); + border-radius: 100%; + height: 8px; + width: 8px; +} +.tox .tox-spinner > div:nth-child(1) { + animation-delay: -0.32s; +} +.tox .tox-spinner > div:nth-child(2) { + animation-delay: -0.16s; +} +@keyframes tam-bouncing-dots { + 0%, + 80%, + 100% { + transform: scale(0); + } + 40% { + transform: scale(1); + } +} +.tox:not([dir=rtl]) .tox-spinner > div:not(:first-child) { + margin-left: 4px; +} +.tox[dir=rtl] .tox-spinner > div:not(:first-child) { + margin-right: 4px; +} +.tox .tox-statusbar { + align-items: center; + background-color: #fff; + border-top: 1px solid #cccccc; + color: rgba(34, 47, 62, 0.7); + display: flex; + flex: 0 0 auto; + font-size: 12px; + font-weight: normal; + height: 18px; + overflow: hidden; + padding: 0 8px; + position: relative; + text-transform: uppercase; +} +.tox .tox-statusbar__text-container { + display: flex; + flex: 1 1 auto; + justify-content: flex-end; + overflow: hidden; +} +.tox .tox-statusbar__path { + display: flex; + flex: 1 1 auto; + margin-right: auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.tox .tox-statusbar__path > * { + display: inline; + white-space: nowrap; +} +.tox .tox-statusbar__wordcount { + flex: 0 0 auto; + margin-left: 1ch; +} +.tox .tox-statusbar a, +.tox .tox-statusbar__path-item, +.tox .tox-statusbar__wordcount { + color: rgba(34, 47, 62, 0.7); + text-decoration: none; +} +.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]), +.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]) { + cursor: pointer; + text-decoration: underline; +} +.tox .tox-statusbar__resize-handle { + align-items: flex-end; + align-self: stretch; + cursor: nwse-resize; + display: flex; + flex: 0 0 auto; + justify-content: flex-end; + margin-left: auto; + margin-right: -8px; + padding-left: 1ch; +} +.tox .tox-statusbar__resize-handle svg { + display: block; + fill: rgba(34, 47, 62, 0.7); +} +.tox .tox-statusbar__resize-handle:focus svg { + background-color: #dee0e2; + border-radius: 1px; + box-shadow: 0 0 0 2px #dee0e2; +} +.tox:not([dir=rtl]) .tox-statusbar__path > * { + margin-right: 4px; +} +.tox:not([dir=rtl]) .tox-statusbar__branding { + margin-left: 1ch; +} +.tox[dir=rtl] .tox-statusbar { + flex-direction: row-reverse; +} +.tox[dir=rtl] .tox-statusbar__path > * { + margin-left: 4px; +} +.tox .tox-throbber { + z-index: 1299; +} +.tox .tox-throbber__busy-spinner { + align-items: center; + background-color: rgba(255, 255, 255, 0.6); + bottom: 0; + display: flex; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; +} +.tox .tox-tbtn { + align-items: center; + background: transparent; + border: 0; + border-radius: 3px; + box-shadow: none; + color: #222f3e; + display: flex; + flex: 0 0 auto; + font-size: 14px; + font-style: normal; + font-weight: normal; + height: 34px; + justify-content: center; + margin: 2px 0 3px 0; + outline: none; + overflow: hidden; + padding: 0; + text-transform: none; + width: 34px; +} +.tox .tox-tbtn svg { + display: block; + fill: #222f3e; +} +.tox .tox-tbtn.tox-tbtn-more { + padding-left: 5px; + padding-right: 5px; + width: inherit; +} +.tox .tox-tbtn:focus { + background: #dee0e2; + border: 0; + box-shadow: none; +} +.tox .tox-tbtn:hover { + background: #dee0e2; + border: 0; + box-shadow: none; + color: #222f3e; +} +.tox .tox-tbtn:hover svg { + fill: #222f3e; +} +.tox .tox-tbtn:active { + background: #c8cbcf; + border: 0; + box-shadow: none; + color: #222f3e; +} +.tox .tox-tbtn:active svg { + fill: #222f3e; +} +.tox .tox-tbtn--disabled, +.tox .tox-tbtn--disabled:hover, +.tox .tox-tbtn:disabled, +.tox .tox-tbtn:disabled:hover { + background: transparent; + border: 0; + box-shadow: none; + color: rgba(34, 47, 62, 0.5); + cursor: not-allowed; +} +.tox .tox-tbtn--disabled svg, +.tox .tox-tbtn--disabled:hover svg, +.tox .tox-tbtn:disabled svg, +.tox .tox-tbtn:disabled:hover svg { + /* stylelint-disable-line no-descending-specificity */ + fill: rgba(34, 47, 62, 0.5); +} +.tox .tox-tbtn--enabled, +.tox .tox-tbtn--enabled:hover { + background: #c8cbcf; + border: 0; + box-shadow: none; + color: #222f3e; +} +.tox .tox-tbtn--enabled > *, +.tox .tox-tbtn--enabled:hover > * { + transform: none; +} +.tox .tox-tbtn--enabled svg, +.tox .tox-tbtn--enabled:hover svg { + /* stylelint-disable-line no-descending-specificity */ + fill: #222f3e; +} +.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) { + color: #222f3e; +} +.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg { + fill: #222f3e; +} +.tox .tox-tbtn:active > * { + transform: none; +} +.tox .tox-tbtn--md { + height: 51px; + width: 51px; +} +.tox .tox-tbtn--lg { + flex-direction: column; + height: 68px; + width: 68px; +} +.tox .tox-tbtn--return { + -ms-grid-row-align: stretch; + align-self: stretch; + height: unset; + width: 16px; +} +.tox .tox-tbtn--labeled { + padding: 0 4px; + width: unset; +} +.tox .tox-tbtn__vlabel { + display: block; + font-size: 10px; + font-weight: normal; + letter-spacing: -0.025em; + margin-bottom: 4px; + white-space: nowrap; +} +.tox .tox-tbtn--select { + margin: 2px 0 3px 0; + padding: 0 4px; + width: auto; +} +.tox .tox-tbtn__select-label { + cursor: default; + font-weight: normal; + margin: 0 4px; +} +.tox .tox-tbtn__select-chevron { + align-items: center; + display: flex; + justify-content: center; + width: 16px; +} +.tox .tox-tbtn__select-chevron svg { + fill: rgba(34, 47, 62, 0.5); +} +.tox .tox-tbtn--bespoke .tox-tbtn__select-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 7em; +} +.tox .tox-split-button { + border: 0; + border-radius: 3px; + box-sizing: border-box; + display: flex; + margin: 2px 0 3px 0; + overflow: hidden; +} +.tox .tox-split-button:hover { + box-shadow: 0 0 0 1px #dee0e2 inset; +} +.tox .tox-split-button:focus { + background: #dee0e2; + box-shadow: none; + color: #222f3e; +} +.tox .tox-split-button > * { + border-radius: 0; +} +.tox .tox-split-button__chevron { + width: 16px; +} +.tox .tox-split-button__chevron svg { + fill: rgba(34, 47, 62, 0.5); +} +.tox .tox-split-button .tox-tbtn { + margin: 0; +} +.tox.tox-platform-touch .tox-split-button .tox-tbtn:first-child { + width: 30px; +} +.tox.tox-platform-touch .tox-split-button__chevron { + width: 20px; +} +.tox .tox-split-button.tox-tbtn--disabled:hover, +.tox .tox-split-button.tox-tbtn--disabled:focus, +.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover, +.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus { + background: transparent; + box-shadow: none; + color: rgba(34, 47, 62, 0.5); +} +.tox .tox-toolbar-overlord { + background-color: #fff; +} +.tox .tox-toolbar, +.tox .tox-toolbar__primary, +.tox .tox-toolbar__overflow { + background: url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E") left 0 top 0 #fff; + background-color: #fff; + display: flex; + flex: 0 0 auto; + flex-shrink: 0; + flex-wrap: wrap; + padding: 0 0; +} +.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed { + height: 0; + opacity: 0; + padding-bottom: 0; + padding-top: 0; + visibility: hidden; +} +.tox .tox-toolbar__overflow--growing { + transition: height 0.3s ease, opacity 0.2s linear 0.1s; +} +.tox .tox-toolbar__overflow--shrinking { + transition: opacity 0.3s ease, height 0.2s linear 0.1s, visibility 0s linear 0.3s; +} +.tox .tox-menubar + .tox-toolbar, +.tox .tox-menubar + .tox-toolbar-overlord .tox-toolbar__primary { + border-top: 1px solid #cccccc; + margin-top: -1px; +} +.tox .tox-toolbar--scrolling { + flex-wrap: nowrap; + overflow-x: auto; +} +.tox .tox-pop .tox-toolbar { + border-width: 0; +} +.tox .tox-toolbar--no-divider { + background-image: none; +} +.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child, +.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary { + border-top: 1px solid #cccccc; +} +.tox.tox-tinymce-aux .tox-toolbar__overflow { + background-color: #fff; + border: 1px solid #cccccc; + border-radius: 3px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); +} +.tox .tox-toolbar__group { + align-items: center; + display: flex; + flex-wrap: wrap; + margin: 0 0; + padding: 0 4px 0 4px; +} +.tox .tox-toolbar__group--pull-right { + margin-left: auto; +} +.tox .tox-toolbar--scrolling .tox-toolbar__group { + flex-shrink: 0; + flex-wrap: nowrap; +} +.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type) { + border-right: 1px solid #cccccc; +} +.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type) { + border-left: 1px solid #cccccc; +} +.tox .tox-tooltip { + display: inline-block; + padding: 8px; + position: relative; +} +.tox .tox-tooltip__body { + background-color: #222f3e; + border-radius: 3px; + box-shadow: 0 2px 4px rgba(34, 47, 62, 0.3); + color: rgba(255, 255, 255, 0.75); + font-size: 14px; + font-style: normal; + font-weight: normal; + padding: 4px 8px; + text-transform: none; +} +.tox .tox-tooltip__arrow { + position: absolute; +} +.tox .tox-tooltip--down .tox-tooltip__arrow { + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid #222f3e; + bottom: 0; + left: 50%; + position: absolute; + transform: translateX(-50%); +} +.tox .tox-tooltip--up .tox-tooltip__arrow { + border-bottom: 8px solid #222f3e; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + left: 50%; + position: absolute; + top: 0; + transform: translateX(-50%); +} +.tox .tox-tooltip--right .tox-tooltip__arrow { + border-bottom: 8px solid transparent; + border-left: 8px solid #222f3e; + border-top: 8px solid transparent; + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); +} +.tox .tox-tooltip--left .tox-tooltip__arrow { + border-bottom: 8px solid transparent; + border-right: 8px solid #222f3e; + border-top: 8px solid transparent; + left: 0; + position: absolute; + top: 50%; + transform: translateY(-50%); +} +.tox .tox-well { + border: 1px solid #cccccc; + border-radius: 3px; + padding: 8px; + width: 100%; +} +.tox .tox-well > *:first-child { + margin-top: 0; +} +.tox .tox-well > *:last-child { + margin-bottom: 0; +} +.tox .tox-well > *:only-child { + margin: 0; +} +.tox .tox-custom-editor { + border: 1px solid #cccccc; + border-radius: 3px; + display: flex; + flex: 1; + position: relative; +} +/* stylelint-disable */ +.tox { + /* stylelint-enable */ +} +.tox .tox-dialog-loading::before { + background-color: rgba(0, 0, 0, 0.5); + content: ""; + height: 100%; + position: absolute; + width: 100%; + z-index: 1000; +} +.tox .tox-tab { + cursor: pointer; +} +.tox .tox-dialog__content-js { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-dialog__body-content .tox-collection { + display: flex; + flex: 1; + -ms-flex-preferred-size: auto; +} +.tox .tox-image-tools-edit-panel { + height: 60px; +} +.tox .tox-image-tools__sidebar { + height: 60px; +} diff --git a/public/tinymce/skins/ui/oxide/skin.min.css b/public/tinymce/skins/ui/oxide/skin.min.css new file mode 100644 index 0000000..f570b8e --- /dev/null +++ b/public/tinymce/skins/ui/oxide/skin.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tox{box-shadow:none;box-sizing:content-box;color:#222f3e;cursor:auto;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;font-style:normal;font-weight:400;line-height:normal;-webkit-tap-highlight-color:transparent;text-decoration:none;text-shadow:none;text-transform:none;vertical-align:initial;white-space:normal}.tox :not(svg):not(rect){box-sizing:inherit;color:inherit;cursor:inherit;direction:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;line-height:inherit;-webkit-tap-highlight-color:inherit;text-align:inherit;text-decoration:inherit;text-shadow:inherit;text-transform:inherit;vertical-align:inherit;white-space:inherit}.tox :not(svg):not(rect){background:0 0;border:0;box-shadow:none;float:none;height:auto;margin:0;max-width:none;outline:0;padding:0;position:static;width:auto}.tox:not([dir=rtl]){direction:ltr;text-align:left}.tox[dir=rtl]{direction:rtl;text-align:right}.tox-tinymce{border:1px solid #ccc;border-radius:0;box-shadow:none;box-sizing:border-box;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;overflow:hidden;position:relative;visibility:inherit!important}.tox-tinymce-inline{border:none;box-shadow:none}.tox-tinymce-inline .tox-editor-header{background-color:transparent;border:1px solid #ccc;border-radius:0;box-shadow:none}.tox-tinymce-aux{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;z-index:1300}.tox-tinymce :focus,.tox-tinymce-aux :focus{outline:0}button::-moz-focus-inner{border:0}.tox[dir=rtl] .tox-icon--flip svg{transform:rotateY(180deg)}.tox .accessibility-issue__header{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description{align-items:stretch;border:1px solid #ccc;border-radius:3px;display:flex;justify-content:space-between}.tox .accessibility-issue__description>div{padding-bottom:4px}.tox .accessibility-issue__description>div>div{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description>:last-child:not(:only-child){border-color:#ccc;border-style:solid}.tox .accessibility-issue__repair{margin-top:16px}.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description{background-color:rgba(32,122,183,.1);border-color:rgba(32,122,183,.4);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description>:last-child{border-color:rgba(32,122,183,.4)}.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2{color:#207ab7}.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg{fill:#207ab7}.tox .tox-dialog__body-content .accessibility-issue--info a .tox-icon{color:#207ab7}.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description{background-color:rgba(255,165,0,.1);border-color:rgba(255,165,0,.5);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description>:last-child{border-color:rgba(255,165,0,.5)}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2{color:#cc8500}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg{fill:#cc8500}.tox .tox-dialog__body-content .accessibility-issue--warn a .tox-icon{color:#cc8500}.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description{background-color:rgba(204,0,0,.1);border-color:rgba(204,0,0,.4);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description>:last-child{border-color:rgba(204,0,0,.4)}.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2{color:#c00}.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg{fill:#c00}.tox .tox-dialog__body-content .accessibility-issue--error a .tox-icon{color:#c00}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description{background-color:rgba(120,171,70,.1);border-color:rgba(120,171,70,.4);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description>:last-child{border-color:rgba(120,171,70,.4)}.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2{color:#78ab46}.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg{fill:#78ab46}.tox .tox-dialog__body-content .accessibility-issue--success a .tox-icon{color:#78ab46}.tox .tox-dialog__body-content .accessibility-issue__header h1,.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2{margin-top:0}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-left:4px}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-left:auto}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description{padding:4px 4px 4px 8px}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description>:last-child{border-left-width:1px;padding-left:4px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-right:4px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-right:auto}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description{padding:4px 8px 4px 4px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description>:last-child{border-right-width:1px;padding-right:4px}.tox .tox-anchorbar{display:flex;flex:0 0 auto}.tox .tox-bar{display:flex;flex:0 0 auto}.tox .tox-button{background-color:#207ab7;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#207ab7;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;line-height:24px;margin:0;outline:0;padding:4px 16px;text-align:center;text-decoration:none;text-transform:none;white-space:nowrap}.tox .tox-button[disabled]{background-color:#207ab7;background-image:none;border-color:#207ab7;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-button:focus:not(:disabled){background-color:#1c6ca1;background-image:none;border-color:#1c6ca1;box-shadow:none;color:#fff}.tox .tox-button:hover:not(:disabled){background-color:#1c6ca1;background-image:none;border-color:#1c6ca1;box-shadow:none;color:#fff}.tox .tox-button:active:not(:disabled){background-color:#185d8c;background-image:none;border-color:#185d8c;box-shadow:none;color:#fff}.tox .tox-button--secondary{background-color:#f0f0f0;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#f0f0f0;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;color:#222f3e;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;outline:0;padding:4px 16px;text-decoration:none;text-transform:none}.tox .tox-button--secondary[disabled]{background-color:#f0f0f0;background-image:none;border-color:#f0f0f0;box-shadow:none;color:rgba(34,47,62,.5)}.tox .tox-button--secondary:focus:not(:disabled){background-color:#e3e3e3;background-image:none;border-color:#e3e3e3;box-shadow:none;color:#222f3e}.tox .tox-button--secondary:hover:not(:disabled){background-color:#e3e3e3;background-image:none;border-color:#e3e3e3;box-shadow:none;color:#222f3e}.tox .tox-button--secondary:active:not(:disabled){background-color:#d6d6d6;background-image:none;border-color:#d6d6d6;box-shadow:none;color:#222f3e}.tox .tox-button--icon,.tox .tox-button.tox-button--icon,.tox .tox-button.tox-button--secondary.tox-button--icon{padding:4px}.tox .tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg{display:block;fill:currentColor}.tox .tox-button-link{background:0;border:none;box-sizing:border-box;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;white-space:nowrap}.tox .tox-button-link--sm{font-size:14px}.tox .tox-button--naked{background-color:transparent;border-color:transparent;box-shadow:unset;color:#222f3e}.tox .tox-button--naked[disabled]{background-color:#f0f0f0;border-color:#f0f0f0;box-shadow:none;color:rgba(34,47,62,.5)}.tox .tox-button--naked:hover:not(:disabled){background-color:#e3e3e3;border-color:#e3e3e3;box-shadow:none;color:#222f3e}.tox .tox-button--naked:focus:not(:disabled){background-color:#e3e3e3;border-color:#e3e3e3;box-shadow:none;color:#222f3e}.tox .tox-button--naked:active:not(:disabled){background-color:#d6d6d6;border-color:#d6d6d6;box-shadow:none;color:#222f3e}.tox .tox-button--naked .tox-icon svg{fill:currentColor}.tox .tox-button--naked.tox-button--icon:hover:not(:disabled){color:#222f3e}.tox .tox-checkbox{align-items:center;border-radius:3px;cursor:pointer;display:flex;height:36px;min-width:36px}.tox .tox-checkbox__input{height:1px;overflow:hidden;position:absolute;top:auto;width:1px}.tox .tox-checkbox__icons{align-items:center;border-radius:3px;box-shadow:0 0 0 2px transparent;box-sizing:content-box;display:flex;height:24px;justify-content:center;padding:calc(4px - 1px);width:24px}.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:block;fill:rgba(34,47,62,.3)}.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:none;fill:#207ab7}.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg{display:none;fill:#207ab7}.tox .tox-checkbox--disabled{color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg{fill:rgba(34,47,62,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{fill:rgba(34,47,62,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{fill:rgba(34,47,62,.5)}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__checked svg{display:block}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:block}.tox input.tox-checkbox__input:focus+.tox-checkbox__icons{border-radius:3px;box-shadow:inset 0 0 0 1px #207ab7;padding:calc(4px - 1px)}.tox:not([dir=rtl]) .tox-checkbox__label{margin-left:4px}.tox:not([dir=rtl]) .tox-checkbox__input{left:-10000px}.tox:not([dir=rtl]) .tox-bar .tox-checkbox{margin-left:4px}.tox[dir=rtl] .tox-checkbox__label{margin-right:4px}.tox[dir=rtl] .tox-checkbox__input{right:-10000px}.tox[dir=rtl] .tox-bar .tox-checkbox{margin-right:4px}.tox .tox-collection--toolbar .tox-collection__group{display:flex;padding:0}.tox .tox-collection--grid .tox-collection__group{display:flex;flex-wrap:wrap;max-height:208px;overflow-x:hidden;overflow-y:auto;padding:0}.tox .tox-collection--list .tox-collection__group{border-bottom-width:0;border-color:#ccc;border-left-width:0;border-right-width:0;border-style:solid;border-top-width:1px;padding:4px 0}.tox .tox-collection--list .tox-collection__group:first-child{border-top-width:0}.tox .tox-collection__group-heading{background-color:#e6e6e6;color:rgba(34,47,62,.7);cursor:default;font-size:12px;font-style:normal;font-weight:400;margin-bottom:4px;margin-top:-4px;padding:4px 8px;text-transform:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tox .tox-collection__item{align-items:center;color:#222f3e;cursor:pointer;display:flex;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.tox .tox-collection--list .tox-collection__item{padding:4px 8px}.tox .tox-collection--toolbar .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--grid .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--list .tox-collection__item--enabled{background-color:#fff;color:#222f3e}.tox .tox-collection--list .tox-collection__item--active{background-color:#dee0e2}.tox .tox-collection--toolbar .tox-collection__item--enabled{background-color:#c8cbcf;color:#222f3e}.tox .tox-collection--toolbar .tox-collection__item--active{background-color:#dee0e2}.tox .tox-collection--grid .tox-collection__item--enabled{background-color:#c8cbcf;color:#222f3e}.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled){background-color:#dee0e2;color:#222f3e}.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#222f3e}.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#222f3e}.tox .tox-collection__item-checkmark,.tox .tox-collection__item-icon{align-items:center;display:flex;height:24px;justify-content:center;width:24px}.tox .tox-collection__item-checkmark svg,.tox .tox-collection__item-icon svg{fill:currentColor}.tox .tox-collection--toolbar-lg .tox-collection__item-icon{height:48px;width:48px}.tox .tox-collection__item-label{color:currentColor;display:inline-block;flex:1;-ms-flex-preferred-size:auto;font-size:14px;font-style:normal;font-weight:400;line-height:24px;text-transform:none;word-break:break-all}.tox .tox-collection__item-accessory{color:rgba(34,47,62,.7);display:inline-block;font-size:14px;height:24px;line-height:24px;text-transform:none}.tox .tox-collection__item-caret{align-items:center;display:flex;min-height:24px}.tox .tox-collection__item-caret::after{content:'';font-size:0;min-height:inherit}.tox .tox-collection__item-caret svg{fill:#222f3e}.tox .tox-collection__item--state-disabled{background-color:transparent;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg{fill:rgba(34,47,62,.5)}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg{display:none}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory+.tox-collection__item-checkmark{display:none}.tox .tox-collection--horizontal{background-color:#fff;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15);display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:nowrap;margin-bottom:0;overflow-x:auto;padding:0}.tox .tox-collection--horizontal .tox-collection__group{align-items:center;display:flex;flex-wrap:nowrap;margin:0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item{height:34px;margin:2px 0 3px 0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item-label{white-space:nowrap}.tox .tox-collection--horizontal .tox-collection__item-caret{margin-left:4px}.tox .tox-collection__item-container{display:flex}.tox .tox-collection__item-container--row{align-items:center;flex:1 1 auto;flex-direction:row}.tox .tox-collection__item-container--row.tox-collection__item-container--align-left{margin-right:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--align-right{justify-content:flex-end;margin-left:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top{align-items:flex-start;margin-bottom:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle{align-items:center}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom{align-items:flex-end;margin-top:auto}.tox .tox-collection__item-container--column{-ms-grid-row-align:center;align-self:center;flex:1 1 auto;flex-direction:column}.tox .tox-collection__item-container--column.tox-collection__item-container--align-left{align-items:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--align-right{align-items:flex-end}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top{align-self:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle{-ms-grid-row-align:center;align-self:center}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom{align-self:flex-end}.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-right:1px solid #ccc}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>:not(:first-child){margin-left:8px}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-left:4px}.tox:not([dir=rtl]) .tox-collection__item-accessory{margin-left:16px;text-align:right}.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret{margin-left:16px}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-left:1px solid #ccc}.tox[dir=rtl] .tox-collection--list .tox-collection__item>:not(:first-child){margin-right:8px}.tox[dir=rtl] .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-right:4px}.tox[dir=rtl] .tox-collection__item-accessory{margin-right:16px;text-align:left}.tox[dir=rtl] .tox-collection .tox-collection__item-caret{margin-right:16px;transform:rotateY(180deg)}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret{margin-right:4px}.tox .tox-color-picker-container{display:flex;flex-direction:row;height:225px;margin:0}.tox .tox-sv-palette{box-sizing:border-box;display:flex;height:100%}.tox .tox-sv-palette-spectrum{height:100%}.tox .tox-sv-palette,.tox .tox-sv-palette-spectrum{width:225px}.tox .tox-sv-palette-thumb{background:0 0;border:1px solid #000;border-radius:50%;box-sizing:content-box;height:12px;position:absolute;width:12px}.tox .tox-sv-palette-inner-thumb{border:1px solid #fff;border-radius:50%;height:10px;position:absolute;width:10px}.tox .tox-hue-slider{box-sizing:border-box;height:100%;width:25px}.tox .tox-hue-slider-spectrum{background:linear-gradient(to bottom,red,#ff0080,#f0f,#8000ff,#00f,#0080ff,#0ff,#00ff80,#0f0,#80ff00,#ff0,#ff8000,red);height:100%;width:100%}.tox .tox-hue-slider,.tox .tox-hue-slider-spectrum{width:20px}.tox .tox-hue-slider-thumb{background:#fff;border:1px solid #000;box-sizing:content-box;height:4px;width:100%}.tox .tox-rgb-form{display:flex;flex-direction:column;justify-content:space-between}.tox .tox-rgb-form div{align-items:center;display:flex;justify-content:space-between;margin-bottom:5px;width:inherit}.tox .tox-rgb-form input{width:6em}.tox .tox-rgb-form input.tox-invalid{border:1px solid red!important}.tox .tox-rgb-form .tox-rgba-preview{border:1px solid #000;flex-grow:2;margin-bottom:0}.tox:not([dir=rtl]) .tox-sv-palette{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider-thumb{margin-left:-1px}.tox:not([dir=rtl]) .tox-rgb-form label{margin-right:.5em}.tox[dir=rtl] .tox-sv-palette{margin-left:15px}.tox[dir=rtl] .tox-hue-slider{margin-left:15px}.tox[dir=rtl] .tox-hue-slider-thumb{margin-right:-1px}.tox[dir=rtl] .tox-rgb-form label{margin-left:.5em}.tox .tox-toolbar .tox-swatches,.tox .tox-toolbar__overflow .tox-swatches,.tox .tox-toolbar__primary .tox-swatches{margin:2px 0 3px 4px}.tox .tox-collection--list .tox-collection__group .tox-swatches-menu{border:0;margin:-4px 0}.tox .tox-swatches__row{display:flex}.tox .tox-swatch{height:30px;transition:transform .15s,box-shadow .15s;width:30px}.tox .tox-swatch:focus,.tox .tox-swatch:hover{box-shadow:0 0 0 1px rgba(127,127,127,.3) inset;transform:scale(.8)}.tox .tox-swatch--remove{align-items:center;display:flex;justify-content:center}.tox .tox-swatch--remove svg path{stroke:#e74c3c}.tox .tox-swatches__picker-btn{align-items:center;background-color:transparent;border:0;cursor:pointer;display:flex;height:30px;justify-content:center;outline:0;padding:0;width:30px}.tox .tox-swatches__picker-btn svg{height:24px;width:24px}.tox .tox-swatches__picker-btn:hover{background:#dee0e2}.tox:not([dir=rtl]) .tox-swatches__picker-btn{margin-left:auto}.tox[dir=rtl] .tox-swatches__picker-btn{margin-right:auto}.tox .tox-comment-thread{background:#fff;position:relative}.tox .tox-comment-thread>:not(:first-child){margin-top:8px}.tox .tox-comment{background:#fff;border:1px solid #ccc;border-radius:3px;box-shadow:0 4px 8px 0 rgba(34,47,62,.1);padding:8px 8px 16px 8px;position:relative}.tox .tox-comment__header{align-items:center;color:#222f3e;display:flex;justify-content:space-between}.tox .tox-comment__date{color:rgba(34,47,62,.7);font-size:12px}.tox .tox-comment__body{color:#222f3e;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;margin-top:8px;position:relative;text-transform:initial}.tox .tox-comment__body textarea{resize:none;white-space:normal;width:100%}.tox .tox-comment__expander{padding-top:8px}.tox .tox-comment__expander p{color:rgba(34,47,62,.7);font-size:14px;font-style:normal}.tox .tox-comment__body p{margin:0}.tox .tox-comment__buttonspacing{padding-top:16px;text-align:center}.tox .tox-comment-thread__overlay::after{background:#fff;bottom:0;content:"";display:flex;left:0;opacity:.9;position:absolute;right:0;top:0;z-index:5}.tox .tox-comment__reply{display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;margin-top:8px}.tox .tox-comment__reply>:first-child{margin-bottom:8px;width:100%}.tox .tox-comment__edit{display:flex;flex-wrap:wrap;justify-content:flex-end;margin-top:16px}.tox .tox-comment__gradient::after{background:linear-gradient(rgba(255,255,255,0),#fff);bottom:0;content:"";display:block;height:5em;margin-top:-40px;position:absolute;width:100%}.tox .tox-comment__overlay{background:#fff;bottom:0;display:flex;flex-direction:column;flex-grow:1;left:0;opacity:.9;position:absolute;right:0;text-align:center;top:0;z-index:5}.tox .tox-comment__loading-text{align-items:center;color:#222f3e;display:flex;flex-direction:column;position:relative}.tox .tox-comment__loading-text>div{padding-bottom:16px}.tox .tox-comment__overlaytext{bottom:0;flex-direction:column;font-size:14px;left:0;padding:1em;position:absolute;right:0;top:0;z-index:10}.tox .tox-comment__overlaytext p{background-color:#fff;box-shadow:0 0 8px 8px #fff;color:#222f3e;text-align:center}.tox .tox-comment__overlaytext div:nth-of-type(2){font-size:.8em}.tox .tox-comment__busy-spinner{align-items:center;background-color:#fff;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:20}.tox .tox-comment__scroll{display:flex;flex-direction:column;flex-shrink:1;overflow:auto}.tox .tox-conversations{margin:8px}.tox:not([dir=rtl]) .tox-comment__edit{margin-left:8px}.tox:not([dir=rtl]) .tox-comment__buttonspacing>:last-child,.tox:not([dir=rtl]) .tox-comment__edit>:last-child,.tox:not([dir=rtl]) .tox-comment__reply>:last-child{margin-left:8px}.tox[dir=rtl] .tox-comment__edit{margin-right:8px}.tox[dir=rtl] .tox-comment__buttonspacing>:last-child,.tox[dir=rtl] .tox-comment__edit>:last-child,.tox[dir=rtl] .tox-comment__reply>:last-child{margin-right:8px}.tox .tox-user{align-items:center;display:flex}.tox .tox-user__avatar svg{fill:rgba(34,47,62,.7)}.tox .tox-user__name{color:rgba(34,47,62,.7);font-size:12px;font-style:normal;font-weight:700;text-transform:uppercase}.tox:not([dir=rtl]) .tox-user__avatar svg{margin-right:8px}.tox:not([dir=rtl]) .tox-user__avatar+.tox-user__name{margin-left:8px}.tox[dir=rtl] .tox-user__avatar svg{margin-left:8px}.tox[dir=rtl] .tox-user__avatar+.tox-user__name{margin-right:8px}.tox .tox-dialog-wrap{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0;z-index:1100}.tox .tox-dialog-wrap__backdrop{background-color:rgba(255,255,255,.75);bottom:0;left:0;position:absolute;right:0;top:0;z-index:1}.tox .tox-dialog-wrap__backdrop--opaque{background-color:#fff}.tox .tox-dialog{background-color:#fff;border-color:#ccc;border-radius:3px;border-style:solid;border-width:1px;box-shadow:0 16px 16px -10px rgba(34,47,62,.15),0 0 40px 1px rgba(34,47,62,.15);display:flex;flex-direction:column;max-height:100%;max-width:480px;overflow:hidden;position:relative;width:95vw;z-index:2}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog{align-self:flex-start;margin:8px auto;width:calc(100vw - 16px)}}.tox .tox-dialog-inline{z-index:1100}.tox .tox-dialog__header{align-items:center;background-color:#fff;border-bottom:none;color:#222f3e;display:flex;font-size:16px;justify-content:space-between;padding:8px 16px 0 16px;position:relative}.tox .tox-dialog__header .tox-button{z-index:1}.tox .tox-dialog__draghandle{cursor:grab;height:100%;left:0;position:absolute;top:0;width:100%}.tox .tox-dialog__draghandle:active{cursor:grabbing}.tox .tox-dialog__dismiss{margin-left:auto}.tox .tox-dialog__title{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:20px;font-style:normal;font-weight:400;line-height:1.3;margin:0;text-transform:none}.tox .tox-dialog__body{color:#222f3e;display:flex;flex:1;-ms-flex-preferred-size:auto;font-size:16px;font-style:normal;font-weight:400;line-height:1.3;min-width:0;text-align:left;text-transform:none}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body{flex-direction:column}}.tox .tox-dialog__body-nav{align-items:flex-start;display:flex;flex-direction:column;padding:16px 16px}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body-nav{flex-direction:row;-webkit-overflow-scrolling:touch;overflow-x:auto;padding-bottom:0}}.tox .tox-dialog__body-nav-item{border-bottom:2px solid transparent;color:rgba(34,47,62,.7);display:inline-block;font-size:14px;line-height:1.3;margin-bottom:8px;text-decoration:none;white-space:nowrap}.tox .tox-dialog__body-nav-item:focus{background-color:rgba(32,122,183,.1)}.tox .tox-dialog__body-nav-item--active{border-bottom:2px solid #207ab7;color:#207ab7}.tox .tox-dialog__body-content{box-sizing:border-box;display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto;max-height:650px;overflow:auto;-webkit-overflow-scrolling:touch;padding:16px 16px}.tox .tox-dialog__body-content>*{margin-bottom:0;margin-top:16px}.tox .tox-dialog__body-content>:first-child{margin-top:0}.tox .tox-dialog__body-content>:last-child{margin-bottom:0}.tox .tox-dialog__body-content>:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog__body-content a{color:#207ab7;cursor:pointer;text-decoration:none}.tox .tox-dialog__body-content a:focus,.tox .tox-dialog__body-content a:hover{color:#185d8c;text-decoration:none}.tox .tox-dialog__body-content a:active{color:#185d8c;text-decoration:none}.tox .tox-dialog__body-content svg{fill:#222f3e}.tox .tox-dialog__body-content ul{display:block;list-style-type:disc;margin-bottom:16px;-webkit-margin-end:0;margin-inline-end:0;-webkit-margin-start:0;margin-inline-start:0;-webkit-padding-start:2.5rem;padding-inline-start:2.5rem}.tox .tox-dialog__body-content .tox-form__group h1{color:#222f3e;font-size:20px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group h2{color:#222f3e;font-size:16px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group p{margin-bottom:16px}.tox .tox-dialog__body-content .tox-form__group h1:first-child,.tox .tox-dialog__body-content .tox-form__group h2:first-child,.tox .tox-dialog__body-content .tox-form__group p:first-child{margin-top:0}.tox .tox-dialog__body-content .tox-form__group h1:last-child,.tox .tox-dialog__body-content .tox-form__group h2:last-child,.tox .tox-dialog__body-content .tox-form__group p:last-child{margin-bottom:0}.tox .tox-dialog__body-content .tox-form__group h1:only-child,.tox .tox-dialog__body-content .tox-form__group h2:only-child,.tox .tox-dialog__body-content .tox-form__group p:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog--width-lg{height:650px;max-width:1200px}.tox .tox-dialog--width-md{max-width:800px}.tox .tox-dialog--width-md .tox-dialog__body-content{overflow:auto}.tox .tox-dialog__body-content--centered{text-align:center}.tox .tox-dialog__footer{align-items:center;background-color:#fff;border-top:1px solid #ccc;display:flex;justify-content:space-between;padding:8px 16px}.tox .tox-dialog__footer-end,.tox .tox-dialog__footer-start{display:flex}.tox .tox-dialog__busy-spinner{align-items:center;background-color:rgba(255,255,255,.75);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:3}.tox .tox-dialog__table{border-collapse:collapse;width:100%}.tox .tox-dialog__table thead th{font-weight:700;padding-bottom:8px}.tox .tox-dialog__table tbody tr{border-bottom:1px solid #ccc}.tox .tox-dialog__table tbody tr:last-child{border-bottom:none}.tox .tox-dialog__table td{padding-bottom:8px;padding-top:8px}.tox .tox-dialog__popups{position:absolute;width:100%;z-index:1100}.tox .tox-dialog__body-iframe{display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto}.tox .tox-dialog__body-iframe .tox-navobj{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2){flex:1;-ms-flex-preferred-size:auto;height:100%}.tox .tox-dialog-dock-fadeout{opacity:0;visibility:hidden}.tox .tox-dialog-dock-fadein{opacity:1;visibility:visible}.tox .tox-dialog-dock-transition{transition:visibility 0s linear .3s,opacity .3s ease}.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein{transition-delay:0s}.tox.tox-platform-ie .tox-dialog-wrap{position:-ms-device-fixed}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav{margin-right:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child){margin-left:8px}}.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end>*,.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start>*{margin-left:8px}.tox[dir=rtl] .tox-dialog__body{text-align:right}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav{margin-left:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child){margin-right:8px}}.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end>*,.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start>*{margin-right:8px}body.tox-dialog__disable-scroll{overflow:hidden}.tox .tox-dropzone-container{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-dropzone{align-items:center;background:#fff;border:2px dashed #ccc;box-sizing:border-box;display:flex;flex-direction:column;flex-grow:1;justify-content:center;min-height:100px;padding:10px}.tox .tox-dropzone p{color:rgba(34,47,62,.7);margin:0 0 16px 0}.tox .tox-edit-area{display:flex;flex:1;-ms-flex-preferred-size:auto;overflow:hidden;position:relative}.tox .tox-edit-area__iframe{background-color:#fff;border:0;box-sizing:border-box;flex:1;-ms-flex-preferred-size:auto;height:100%;position:absolute;width:100%}.tox.tox-inline-edit-area{border:1px dotted #ccc}.tox .tox-editor-container{display:flex;flex:1 1 auto;flex-direction:column;overflow:hidden}.tox .tox-editor-header{z-index:1}.tox:not(.tox-tinymce-inline) .tox-editor-header{box-shadow:none;transition:box-shadow .5s}.tox.tox-tinymce--toolbar-bottom .tox-editor-header,.tox.tox-tinymce-inline .tox-editor-header{margin-bottom:-1px}.tox.tox-tinymce--toolbar-sticky-on .tox-editor-header{background-color:transparent;box-shadow:0 4px 4px -3px rgba(0,0,0,.25)}.tox-editor-dock-fadeout{opacity:0;visibility:hidden}.tox-editor-dock-fadein{opacity:1;visibility:visible}.tox-editor-dock-transition{transition:visibility 0s linear .25s,opacity .25s ease}.tox-editor-dock-transition.tox-editor-dock-fadein{transition-delay:0s}.tox .tox-control-wrap{flex:1;position:relative}.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid{display:none}.tox .tox-control-wrap svg{display:block}.tox .tox-control-wrap__status-icon-wrap{position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-control-wrap__status-icon-invalid svg{fill:#c00}.tox .tox-control-wrap__status-icon-unknown svg{fill:orange}.tox .tox-control-wrap__status-icon-valid svg{fill:green}.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield{padding-right:32px}.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap{right:4px}.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield{padding-left:32px}.tox[dir=rtl] .tox-control-wrap__status-icon-wrap{left:4px}.tox .tox-autocompleter{max-width:25em}.tox .tox-autocompleter .tox-menu{max-width:25em}.tox .tox-autocompleter .tox-autocompleter-highlight{font-weight:700}.tox .tox-color-input{display:flex;position:relative;z-index:1}.tox .tox-color-input .tox-textfield{z-index:-1}.tox .tox-color-input span{border-color:rgba(34,47,62,.2);border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;height:24px;position:absolute;top:6px;width:24px}.tox .tox-color-input span:focus:not([aria-disabled=true]),.tox .tox-color-input span:hover:not([aria-disabled=true]){border-color:#207ab7;cursor:pointer}.tox .tox-color-input span::before{background-image:linear-gradient(45deg,rgba(0,0,0,.25) 25%,transparent 25%),linear-gradient(-45deg,rgba(0,0,0,.25) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(0,0,0,.25) 75%),linear-gradient(-45deg,transparent 75%,rgba(0,0,0,.25) 75%);background-position:0 0,0 6px,6px -6px,-6px 0;background-size:12px 12px;border:1px solid #fff;border-radius:3px;box-sizing:border-box;content:'';height:24px;left:-1px;position:absolute;top:-1px;width:24px;z-index:-1}.tox .tox-color-input span[aria-disabled=true]{cursor:not-allowed}.tox:not([dir=rtl]) .tox-color-input .tox-textfield{padding-left:36px}.tox:not([dir=rtl]) .tox-color-input span{left:6px}.tox[dir=rtl] .tox-color-input .tox-textfield{padding-right:36px}.tox[dir=rtl] .tox-color-input span{right:6px}.tox .tox-label,.tox .tox-toolbar-label{color:rgba(34,47,62,.7);display:block;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;padding:0 8px 0 0;text-transform:none;white-space:nowrap}.tox .tox-toolbar-label{padding:0 8px}.tox[dir=rtl] .tox-label{padding:0 0 0 8px}.tox .tox-form{display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto}.tox .tox-form__group{box-sizing:border-box;margin-bottom:4px}.tox .tox-form-group--maximize{flex:1}.tox .tox-form__group--error{color:#c00}.tox .tox-form__group--collection{display:flex}.tox .tox-form__grid{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between}.tox .tox-form__grid--2col>.tox-form__group{width:calc(50% - (8px / 2))}.tox .tox-form__grid--3col>.tox-form__group{width:calc(100% / 3 - (8px / 2))}.tox .tox-form__grid--4col>.tox-form__group{width:calc(25% - (8px / 2))}.tox .tox-form__controls-h-stack{align-items:center;display:flex}.tox .tox-form__group--inline{align-items:center;display:flex}.tox .tox-form__group--stretched{display:flex;flex:1;flex-direction:column;-ms-flex-preferred-size:auto}.tox .tox-form__group--stretched .tox-textarea{flex:1;-ms-flex-preferred-size:auto}.tox .tox-form__group--stretched .tox-navobj{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-form__group--stretched .tox-navobj :nth-child(2){flex:1;-ms-flex-preferred-size:auto;height:100%}.tox:not([dir=rtl]) .tox-form__controls-h-stack>:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-form__controls-h-stack>:not(:first-child){margin-right:4px}.tox .tox-lock.tox-locked .tox-lock-icon__unlock,.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock{display:none}.tox .tox-listboxfield .tox-listbox--select,.tox .tox-textarea,.tox .tox-textfield,.tox .tox-toolbar-textfield{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#ccc;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#222f3e;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 4.75px;resize:none;width:100%}.tox .tox-textarea[disabled],.tox .tox-textfield[disabled]{background-color:#f2f2f2;color:rgba(34,47,62,.85);cursor:not-allowed}.tox .tox-listboxfield .tox-listbox--select:focus,.tox .tox-textarea:focus,.tox .tox-textfield:focus{background-color:#fff;border-color:#207ab7;box-shadow:none;outline:0}.tox .tox-toolbar-textfield{border-width:0;margin-bottom:3px;margin-top:2px;max-width:250px}.tox .tox-naked-btn{background-color:transparent;border:0;border-color:transparent;box-shadow:unset;color:#207ab7;cursor:pointer;display:block;margin:0;padding:0}.tox .tox-naked-btn svg{display:block;fill:#222f3e}.tox:not([dir=rtl]) .tox-toolbar-textfield+*{margin-left:4px}.tox[dir=rtl] .tox-toolbar-textfield+*{margin-right:4px}.tox .tox-listboxfield{cursor:pointer;position:relative}.tox .tox-listboxfield .tox-listbox--select[disabled]{background-color:#f2f2f2;color:rgba(34,47,62,.85);cursor:not-allowed}.tox .tox-listbox__select-label{cursor:default;flex:1;margin:0 4px}.tox .tox-listbox__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-listbox__select-chevron svg{fill:#222f3e}.tox .tox-listboxfield .tox-listbox--select{align-items:center;display:flex}.tox:not([dir=rtl]) .tox-listboxfield svg{right:8px}.tox[dir=rtl] .tox-listboxfield svg{left:8px}.tox .tox-selectfield{cursor:pointer;position:relative}.tox .tox-selectfield select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#ccc;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#222f3e;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 4.75px;resize:none;width:100%}.tox .tox-selectfield select[disabled]{background-color:#f2f2f2;color:rgba(34,47,62,.85);cursor:not-allowed}.tox .tox-selectfield select::-ms-expand{display:none}.tox .tox-selectfield select:focus{background-color:#fff;border-color:#207ab7;box-shadow:none;outline:0}.tox .tox-selectfield svg{pointer-events:none;position:absolute;top:50%;transform:translateY(-50%)}.tox:not([dir=rtl]) .tox-selectfield select[size="0"],.tox:not([dir=rtl]) .tox-selectfield select[size="1"]{padding-right:24px}.tox:not([dir=rtl]) .tox-selectfield svg{right:8px}.tox[dir=rtl] .tox-selectfield select[size="0"],.tox[dir=rtl] .tox-selectfield select[size="1"]{padding-left:24px}.tox[dir=rtl] .tox-selectfield svg{left:8px}.tox .tox-textarea{-webkit-appearance:textarea;-moz-appearance:textarea;appearance:textarea;white-space:pre-wrap}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}.tox .tox-help__more-link{list-style:none;margin-top:1em}.tox .tox-image-tools{width:100%}.tox .tox-image-tools__toolbar{align-items:center;display:flex;justify-content:center}.tox .tox-image-tools__image{background-color:#666;height:380px;overflow:auto;position:relative;width:100%}.tox .tox-image-tools__image,.tox .tox-image-tools__image+.tox-image-tools__toolbar{margin-top:8px}.tox .tox-image-tools__image-bg{background:url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==)}.tox .tox-image-tools__toolbar>.tox-spacer{flex:1;-ms-flex-preferred-size:auto}.tox .tox-croprect-block{background:#000;opacity:.5;position:absolute;zoom:1}.tox .tox-croprect-handle{border:2px solid #fff;height:20px;left:0;position:absolute;top:0;width:20px}.tox .tox-croprect-handle-move{border:0;cursor:move;position:absolute}.tox .tox-croprect-handle-nw{border-width:2px 0 0 2px;cursor:nw-resize;left:100px;margin:-2px 0 0 -2px;top:100px}.tox .tox-croprect-handle-ne{border-width:2px 2px 0 0;cursor:ne-resize;left:200px;margin:-2px 0 0 -20px;top:100px}.tox .tox-croprect-handle-sw{border-width:0 0 2px 2px;cursor:sw-resize;left:100px;margin:-20px 2px 0 -2px;top:200px}.tox .tox-croprect-handle-se{border-width:0 2px 2px 0;cursor:se-resize;left:200px;margin:-20px 0 0 -20px;top:200px}.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-slider:not(:first-of-type){margin-left:8px}.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-button+.tox-slider{margin-left:32px}.tox:not([dir=rtl]) .tox-image-tools__toolbar>.tox-slider+.tox-button{margin-left:32px}.tox[dir=rtl] .tox-image-tools__toolbar>.tox-slider:not(:first-of-type){margin-right:8px}.tox[dir=rtl] .tox-image-tools__toolbar>.tox-button+.tox-slider{margin-right:32px}.tox[dir=rtl] .tox-image-tools__toolbar>.tox-slider+.tox-button{margin-right:32px}.tox .tox-insert-table-picker{display:flex;flex-wrap:wrap;width:170px}.tox .tox-insert-table-picker>div{border-color:#ccc;border-style:solid;border-width:0 1px 1px 0;box-sizing:border-box;height:17px;width:17px}.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker{margin:-4px 0}.tox .tox-insert-table-picker .tox-insert-table-picker__selected{background-color:rgba(32,122,183,.5);border-color:rgba(32,122,183,.5)}.tox .tox-insert-table-picker__label{color:rgba(34,47,62,.7);display:block;font-size:14px;padding:4px;text-align:center;width:100%}.tox:not([dir=rtl]) .tox-insert-table-picker>div:nth-child(10n){border-right:0}.tox[dir=rtl] .tox-insert-table-picker>div:nth-child(10n+1){border-right:0}.tox .tox-menu{background-color:#fff;border:1px solid #ccc;border-radius:3px;box-shadow:0 4px 8px 0 rgba(34,47,62,.1);display:inline-block;overflow:hidden;vertical-align:top;z-index:1150}.tox .tox-menu.tox-collection.tox-collection--list{padding:0}.tox .tox-menu.tox-collection.tox-collection--toolbar{padding:4px}.tox .tox-menu.tox-collection.tox-collection--grid{padding:4px}.tox .tox-menu__label blockquote,.tox .tox-menu__label code,.tox .tox-menu__label h1,.tox .tox-menu__label h2,.tox .tox-menu__label h3,.tox .tox-menu__label h4,.tox .tox-menu__label h5,.tox .tox-menu__label h6,.tox .tox-menu__label p{margin:0}.tox .tox-menubar{background:url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E") left 0 top 0 #fff;background-color:#fff;display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;padding:0 4px 0 4px}.tox.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar{border-top:1px solid #ccc}.tox .tox-mbtn{align-items:center;background:0 0;border:0;border-radius:3px;box-shadow:none;color:#222f3e;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:34px;justify-content:center;margin:2px 0 3px 0;outline:0;overflow:hidden;padding:0 4px;text-transform:none;width:auto}.tox .tox-mbtn[disabled]{background-color:transparent;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-mbtn:focus:not(:disabled){background:#dee0e2;border:0;box-shadow:none;color:#222f3e}.tox .tox-mbtn--active{background:#c8cbcf;border:0;box-shadow:none;color:#222f3e}.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active){background:#dee0e2;border:0;box-shadow:none;color:#222f3e}.tox .tox-mbtn__select-label{cursor:default;font-weight:400;margin:0 4px}.tox .tox-mbtn[disabled] .tox-mbtn__select-label{cursor:not-allowed}.tox .tox-mbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px;display:none}.tox .tox-notification{border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;display:-ms-grid;display:grid;font-size:14px;font-weight:400;-ms-grid-columns:minmax(40px,1fr) auto minmax(40px,1fr);grid-template-columns:minmax(40px,1fr) auto minmax(40px,1fr);margin-top:4px;opacity:0;padding:4px;transition:transform .1s ease-in,opacity 150ms ease-in}.tox .tox-notification p{font-size:14px;font-weight:400}.tox .tox-notification a{cursor:pointer;text-decoration:underline}.tox .tox-notification--in{opacity:1}.tox .tox-notification--success{background-color:#e4eeda;border-color:#d7e6c8;color:#222f3e}.tox .tox-notification--success p{color:#222f3e}.tox .tox-notification--success a{color:#547831}.tox .tox-notification--success svg{fill:#222f3e}.tox .tox-notification--error{background-color:#f8dede;border-color:#f2bfbf;color:#222f3e}.tox .tox-notification--error p{color:#222f3e}.tox .tox-notification--error a{color:#c00}.tox .tox-notification--error svg{fill:#222f3e}.tox .tox-notification--warn,.tox .tox-notification--warning{background-color:#fffaea;border-color:#ffe89d;color:#222f3e}.tox .tox-notification--warn p,.tox .tox-notification--warning p{color:#222f3e}.tox .tox-notification--warn a,.tox .tox-notification--warning a{color:#222f3e}.tox .tox-notification--warn svg,.tox .tox-notification--warning svg{fill:#222f3e}.tox .tox-notification--info{background-color:#d9edf7;border-color:#779ecb;color:#222f3e}.tox .tox-notification--info p{color:#222f3e}.tox .tox-notification--info a{color:#222f3e}.tox .tox-notification--info svg{fill:#222f3e}.tox .tox-notification__body{-ms-grid-row-align:center;align-self:center;color:#222f3e;font-size:14px;-ms-grid-column-span:1;grid-column-end:3;-ms-grid-column:2;grid-column-start:2;-ms-grid-row-span:1;grid-row-end:2;-ms-grid-row:1;grid-row-start:1;text-align:center;white-space:normal;word-break:break-all;word-break:break-word}.tox .tox-notification__body>*{margin:0}.tox .tox-notification__body>*+*{margin-top:1rem}.tox .tox-notification__icon{-ms-grid-row-align:center;align-self:center;-ms-grid-column-span:1;grid-column-end:2;-ms-grid-column:1;grid-column-start:1;-ms-grid-row-span:1;grid-row-end:2;-ms-grid-row:1;grid-row-start:1;-ms-grid-column-align:end;justify-self:end}.tox .tox-notification__icon svg{display:block}.tox .tox-notification__dismiss{-ms-grid-row-align:start;align-self:start;-ms-grid-column-span:1;grid-column-end:4;-ms-grid-column:3;grid-column-start:3;-ms-grid-row-span:1;grid-row-end:2;-ms-grid-row:1;grid-row-start:1;-ms-grid-column-align:end;justify-self:end}.tox .tox-notification .tox-progress-bar{-ms-grid-column-span:3;grid-column-end:4;-ms-grid-column:1;grid-column-start:1;-ms-grid-row-span:1;grid-row-end:3;-ms-grid-row:2;grid-row-start:2;-ms-grid-column-align:center;justify-self:center}.tox .tox-pop{display:inline-block;position:relative}.tox .tox-pop--resizing{transition:width .1s ease}.tox .tox-pop--resizing .tox-toolbar,.tox .tox-pop--resizing .tox-toolbar__group{flex-wrap:nowrap}.tox .tox-pop--transition{transition:.15s ease;transition-property:left,right,top,bottom}.tox .tox-pop--transition::after,.tox .tox-pop--transition::before{transition:all .15s,visibility 0s,opacity 75ms ease 75ms}.tox .tox-pop__dialog{background-color:#fff;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15);min-width:0;overflow:hidden}.tox .tox-pop__dialog>:not(.tox-toolbar){margin:4px 4px 4px 8px}.tox .tox-pop__dialog .tox-toolbar{background-color:transparent;margin-bottom:-1px}.tox .tox-pop::after,.tox .tox-pop::before{border-style:solid;content:'';display:block;height:0;opacity:1;position:absolute;width:0}.tox .tox-pop.tox-pop--inset::after,.tox .tox-pop.tox-pop--inset::before{opacity:0;transition:all 0s .15s,visibility 0s,opacity 75ms ease}.tox .tox-pop.tox-pop--bottom::after,.tox .tox-pop.tox-pop--bottom::before{left:50%;top:100%}.tox .tox-pop.tox-pop--bottom::after{border-color:#fff transparent transparent transparent;border-width:8px;margin-left:-8px;margin-top:-1px}.tox .tox-pop.tox-pop--bottom::before{border-color:#ccc transparent transparent transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--top::after,.tox .tox-pop.tox-pop--top::before{left:50%;top:0;transform:translateY(-100%)}.tox .tox-pop.tox-pop--top::after{border-color:transparent transparent #fff transparent;border-width:8px;margin-left:-8px;margin-top:1px}.tox .tox-pop.tox-pop--top::before{border-color:transparent transparent #ccc transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--left::after,.tox .tox-pop.tox-pop--left::before{left:0;top:calc(50% - 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--left::after{border-color:transparent #fff transparent transparent;border-width:8px;margin-left:-15px}.tox .tox-pop.tox-pop--left::before{border-color:transparent #ccc transparent transparent;border-width:10px;margin-left:-19px}.tox .tox-pop.tox-pop--right::after,.tox .tox-pop.tox-pop--right::before{left:100%;top:calc(50% + 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--right::after{border-color:transparent transparent transparent #fff;border-width:8px;margin-left:-1px}.tox .tox-pop.tox-pop--right::before{border-color:transparent transparent transparent #ccc;border-width:10px;margin-left:-1px}.tox .tox-pop.tox-pop--align-left::after,.tox .tox-pop.tox-pop--align-left::before{left:20px}.tox .tox-pop.tox-pop--align-right::after,.tox .tox-pop.tox-pop--align-right::before{left:calc(100% - 20px)}.tox .tox-sidebar-wrap{display:flex;flex-direction:row;flex-grow:1;-ms-flex-preferred-size:0;min-height:0}.tox .tox-sidebar{background-color:#fff;display:flex;flex-direction:row;justify-content:flex-end}.tox .tox-sidebar__slider{display:flex;overflow:hidden}.tox .tox-sidebar__pane-container{display:flex}.tox .tox-sidebar__pane{display:flex}.tox .tox-sidebar--sliding-closed{opacity:0}.tox .tox-sidebar--sliding-open{opacity:1}.tox .tox-sidebar--sliding-growing,.tox .tox-sidebar--sliding-shrinking{transition:width .5s ease,opacity .5s ease}.tox .tox-selector{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;display:inline-block;height:10px;position:absolute;width:10px}.tox.tox-platform-touch .tox-selector{height:12px;width:12px}.tox .tox-slider{align-items:center;display:flex;flex:1;-ms-flex-preferred-size:auto;height:24px;justify-content:center;position:relative}.tox .tox-slider__rail{background-color:transparent;border:1px solid #ccc;border-radius:3px;height:10px;min-width:120px;width:100%}.tox .tox-slider__handle{background-color:#207ab7;border:2px solid #185d8c;border-radius:3px;box-shadow:none;height:24px;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:14px}.tox .tox-source-code{overflow:auto}.tox .tox-spinner{display:flex}.tox .tox-spinner>div{animation:tam-bouncing-dots 1.5s ease-in-out 0s infinite both;background-color:rgba(34,47,62,.7);border-radius:100%;height:8px;width:8px}.tox .tox-spinner>div:nth-child(1){animation-delay:-.32s}.tox .tox-spinner>div:nth-child(2){animation-delay:-.16s}@keyframes tam-bouncing-dots{0%,100%,80%{transform:scale(0)}40%{transform:scale(1)}}.tox:not([dir=rtl]) .tox-spinner>div:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-spinner>div:not(:first-child){margin-right:4px}.tox .tox-statusbar{align-items:center;background-color:#fff;border-top:1px solid #ccc;color:rgba(34,47,62,.7);display:flex;flex:0 0 auto;font-size:12px;font-weight:400;height:18px;overflow:hidden;padding:0 8px;position:relative;text-transform:uppercase}.tox .tox-statusbar__text-container{display:flex;flex:1 1 auto;justify-content:flex-end;overflow:hidden}.tox .tox-statusbar__path{display:flex;flex:1 1 auto;margin-right:auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tox .tox-statusbar__path>*{display:inline;white-space:nowrap}.tox .tox-statusbar__wordcount{flex:0 0 auto;margin-left:1ch}.tox .tox-statusbar a,.tox .tox-statusbar__path-item,.tox .tox-statusbar__wordcount{color:rgba(34,47,62,.7);text-decoration:none}.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]){cursor:pointer;text-decoration:underline}.tox .tox-statusbar__resize-handle{align-items:flex-end;align-self:stretch;cursor:nwse-resize;display:flex;flex:0 0 auto;justify-content:flex-end;margin-left:auto;margin-right:-8px;padding-left:1ch}.tox .tox-statusbar__resize-handle svg{display:block;fill:rgba(34,47,62,.7)}.tox .tox-statusbar__resize-handle:focus svg{background-color:#dee0e2;border-radius:1px;box-shadow:0 0 0 2px #dee0e2}.tox:not([dir=rtl]) .tox-statusbar__path>*{margin-right:4px}.tox:not([dir=rtl]) .tox-statusbar__branding{margin-left:1ch}.tox[dir=rtl] .tox-statusbar{flex-direction:row-reverse}.tox[dir=rtl] .tox-statusbar__path>*{margin-left:4px}.tox .tox-throbber{z-index:1299}.tox .tox-throbber__busy-spinner{align-items:center;background-color:rgba(255,255,255,.6);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.tox .tox-tbtn{align-items:center;background:0 0;border:0;border-radius:3px;box-shadow:none;color:#222f3e;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:34px;justify-content:center;margin:2px 0 3px 0;outline:0;overflow:hidden;padding:0;text-transform:none;width:34px}.tox .tox-tbtn svg{display:block;fill:#222f3e}.tox .tox-tbtn.tox-tbtn-more{padding-left:5px;padding-right:5px;width:inherit}.tox .tox-tbtn:focus{background:#dee0e2;border:0;box-shadow:none}.tox .tox-tbtn:hover{background:#dee0e2;border:0;box-shadow:none;color:#222f3e}.tox .tox-tbtn:hover svg{fill:#222f3e}.tox .tox-tbtn:active{background:#c8cbcf;border:0;box-shadow:none;color:#222f3e}.tox .tox-tbtn:active svg{fill:#222f3e}.tox .tox-tbtn--disabled,.tox .tox-tbtn--disabled:hover,.tox .tox-tbtn:disabled,.tox .tox-tbtn:disabled:hover{background:0 0;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-tbtn--disabled svg,.tox .tox-tbtn--disabled:hover svg,.tox .tox-tbtn:disabled svg,.tox .tox-tbtn:disabled:hover svg{fill:rgba(34,47,62,.5)}.tox .tox-tbtn--enabled,.tox .tox-tbtn--enabled:hover{background:#c8cbcf;border:0;box-shadow:none;color:#222f3e}.tox .tox-tbtn--enabled:hover>*,.tox .tox-tbtn--enabled>*{transform:none}.tox .tox-tbtn--enabled svg,.tox .tox-tbtn--enabled:hover svg{fill:#222f3e}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled){color:#222f3e}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg{fill:#222f3e}.tox .tox-tbtn:active>*{transform:none}.tox .tox-tbtn--md{height:51px;width:51px}.tox .tox-tbtn--lg{flex-direction:column;height:68px;width:68px}.tox .tox-tbtn--return{-ms-grid-row-align:stretch;align-self:stretch;height:unset;width:16px}.tox .tox-tbtn--labeled{padding:0 4px;width:unset}.tox .tox-tbtn__vlabel{display:block;font-size:10px;font-weight:400;letter-spacing:-.025em;margin-bottom:4px;white-space:nowrap}.tox .tox-tbtn--select{margin:2px 0 3px 0;padding:0 4px;width:auto}.tox .tox-tbtn__select-label{cursor:default;font-weight:400;margin:0 4px}.tox .tox-tbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-tbtn__select-chevron svg{fill:rgba(34,47,62,.5)}.tox .tox-tbtn--bespoke .tox-tbtn__select-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:7em}.tox .tox-split-button{border:0;border-radius:3px;box-sizing:border-box;display:flex;margin:2px 0 3px 0;overflow:hidden}.tox .tox-split-button:hover{box-shadow:0 0 0 1px #dee0e2 inset}.tox .tox-split-button:focus{background:#dee0e2;box-shadow:none;color:#222f3e}.tox .tox-split-button>*{border-radius:0}.tox .tox-split-button__chevron{width:16px}.tox .tox-split-button__chevron svg{fill:rgba(34,47,62,.5)}.tox .tox-split-button .tox-tbtn{margin:0}.tox.tox-platform-touch .tox-split-button .tox-tbtn:first-child{width:30px}.tox.tox-platform-touch .tox-split-button__chevron{width:20px}.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus,.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,.tox .tox-split-button.tox-tbtn--disabled:focus,.tox .tox-split-button.tox-tbtn--disabled:hover{background:0 0;box-shadow:none;color:rgba(34,47,62,.5)}.tox .tox-toolbar-overlord{background-color:#fff}.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary{background:url("data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E") left 0 top 0 #fff;background-color:#fff;display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;padding:0 0}.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed{height:0;opacity:0;padding-bottom:0;padding-top:0;visibility:hidden}.tox .tox-toolbar__overflow--growing{transition:height .3s ease,opacity .2s linear .1s}.tox .tox-toolbar__overflow--shrinking{transition:opacity .3s ease,height .2s linear .1s,visibility 0s linear .3s}.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord .tox-toolbar__primary{border-top:1px solid #ccc;margin-top:-1px}.tox .tox-toolbar--scrolling{flex-wrap:nowrap;overflow-x:auto}.tox .tox-pop .tox-toolbar{border-width:0}.tox .tox-toolbar--no-divider{background-image:none}.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary,.tox-tinymce:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child{border-top:1px solid #ccc}.tox.tox-tinymce-aux .tox-toolbar__overflow{background-color:#fff;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,.15)}.tox .tox-toolbar__group{align-items:center;display:flex;flex-wrap:wrap;margin:0 0;padding:0 4px 0 4px}.tox .tox-toolbar__group--pull-right{margin-left:auto}.tox .tox-toolbar--scrolling .tox-toolbar__group{flex-shrink:0;flex-wrap:nowrap}.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type){border-right:1px solid #ccc}.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type){border-left:1px solid #ccc}.tox .tox-tooltip{display:inline-block;padding:8px;position:relative}.tox .tox-tooltip__body{background-color:#222f3e;border-radius:3px;box-shadow:0 2px 4px rgba(34,47,62,.3);color:rgba(255,255,255,.75);font-size:14px;font-style:normal;font-weight:400;padding:4px 8px;text-transform:none}.tox .tox-tooltip__arrow{position:absolute}.tox .tox-tooltip--down .tox-tooltip__arrow{border-left:8px solid transparent;border-right:8px solid transparent;border-top:8px solid #222f3e;bottom:0;left:50%;position:absolute;transform:translateX(-50%)}.tox .tox-tooltip--up .tox-tooltip__arrow{border-bottom:8px solid #222f3e;border-left:8px solid transparent;border-right:8px solid transparent;left:50%;position:absolute;top:0;transform:translateX(-50%)}.tox .tox-tooltip--right .tox-tooltip__arrow{border-bottom:8px solid transparent;border-left:8px solid #222f3e;border-top:8px solid transparent;position:absolute;right:0;top:50%;transform:translateY(-50%)}.tox .tox-tooltip--left .tox-tooltip__arrow{border-bottom:8px solid transparent;border-right:8px solid #222f3e;border-top:8px solid transparent;left:0;position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-well{border:1px solid #ccc;border-radius:3px;padding:8px;width:100%}.tox .tox-well>:first-child{margin-top:0}.tox .tox-well>:last-child{margin-bottom:0}.tox .tox-well>:only-child{margin:0}.tox .tox-custom-editor{border:1px solid #ccc;border-radius:3px;display:flex;flex:1;position:relative}.tox .tox-dialog-loading::before{background-color:rgba(0,0,0,.5);content:"";height:100%;position:absolute;width:100%;z-index:1000}.tox .tox-tab{cursor:pointer}.tox .tox-dialog__content-js{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-dialog__body-content .tox-collection{display:flex;flex:1;-ms-flex-preferred-size:auto}.tox .tox-image-tools-edit-panel{height:60px}.tox .tox-image-tools__sidebar{height:60px} diff --git a/public/tinymce/skins/ui/oxide/skin.mobile.css b/public/tinymce/skins/ui/oxide/skin.mobile.css new file mode 100644 index 0000000..875721a --- /dev/null +++ b/public/tinymce/skins/ui/oxide/skin.mobile.css @@ -0,0 +1,673 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +/* RESET all the things! */ +.tinymce-mobile-outer-container { + all: initial; + display: block; +} +.tinymce-mobile-outer-container * { + border: 0; + box-sizing: initial; + cursor: inherit; + float: none; + line-height: 1; + margin: 0; + outline: 0; + padding: 0; + -webkit-tap-highlight-color: transparent; + /* TBIO-3691, stop the gray flicker on touch. */ + text-shadow: none; + white-space: nowrap; +} +.tinymce-mobile-icon-arrow-back::before { + content: "\e5cd"; +} +.tinymce-mobile-icon-image::before { + content: "\e412"; +} +.tinymce-mobile-icon-cancel-circle::before { + content: "\e5c9"; +} +.tinymce-mobile-icon-full-dot::before { + content: "\e061"; +} +.tinymce-mobile-icon-align-center::before { + content: "\e234"; +} +.tinymce-mobile-icon-align-left::before { + content: "\e236"; +} +.tinymce-mobile-icon-align-right::before { + content: "\e237"; +} +.tinymce-mobile-icon-bold::before { + content: "\e238"; +} +.tinymce-mobile-icon-italic::before { + content: "\e23f"; +} +.tinymce-mobile-icon-unordered-list::before { + content: "\e241"; +} +.tinymce-mobile-icon-ordered-list::before { + content: "\e242"; +} +.tinymce-mobile-icon-font-size::before { + content: "\e245"; +} +.tinymce-mobile-icon-underline::before { + content: "\e249"; +} +.tinymce-mobile-icon-link::before { + content: "\e157"; +} +.tinymce-mobile-icon-unlink::before { + content: "\eca2"; +} +.tinymce-mobile-icon-color::before { + content: "\e891"; +} +.tinymce-mobile-icon-previous::before { + content: "\e314"; +} +.tinymce-mobile-icon-next::before { + content: "\e315"; +} +.tinymce-mobile-icon-large-font::before, +.tinymce-mobile-icon-style-formats::before { + content: "\e264"; +} +.tinymce-mobile-icon-undo::before { + content: "\e166"; +} +.tinymce-mobile-icon-redo::before { + content: "\e15a"; +} +.tinymce-mobile-icon-removeformat::before { + content: "\e239"; +} +.tinymce-mobile-icon-small-font::before { + content: "\e906"; +} +.tinymce-mobile-icon-readonly-back::before, +.tinymce-mobile-format-matches::after { + content: "\e5ca"; +} +.tinymce-mobile-icon-small-heading::before { + content: "small"; +} +.tinymce-mobile-icon-large-heading::before { + content: "large"; +} +.tinymce-mobile-icon-small-heading::before, +.tinymce-mobile-icon-large-heading::before { + font-family: sans-serif; + font-size: 80%; +} +.tinymce-mobile-mask-edit-icon::before { + content: "\e254"; +} +.tinymce-mobile-icon-back::before { + content: "\e5c4"; +} +.tinymce-mobile-icon-heading::before { + /* TODO: Translate */ + content: "Headings"; + font-family: sans-serif; + font-size: 80%; + font-weight: bold; +} +.tinymce-mobile-icon-h1::before { + content: "H1"; + font-weight: bold; +} +.tinymce-mobile-icon-h2::before { + content: "H2"; + font-weight: bold; +} +.tinymce-mobile-icon-h3::before { + content: "H3"; + font-weight: bold; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask { + align-items: center; + display: flex; + justify-content: center; + background: rgba(51, 51, 51, 0.5); + height: 100%; + position: absolute; + top: 0; + width: 100%; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container { + align-items: center; + border-radius: 50%; + display: flex; + flex-direction: column; + font-family: sans-serif; + font-size: 1em; + justify-content: space-between; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .mixin-menu-item { + align-items: center; + display: flex; + justify-content: center; + border-radius: 50%; + height: 2.1em; + width: 2.1em; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section { + align-items: center; + display: flex; + justify-content: center; + flex-direction: column; + font-size: 1em; +} +@media only screen and (min-device-width:700px) { + .tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section { + font-size: 1.2em; + } +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon { + align-items: center; + display: flex; + justify-content: center; + border-radius: 50%; + height: 2.1em; + width: 2.1em; + background-color: white; + color: #207ab7; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon::before { + content: "\e900"; + font-family: 'tinymce-mobile', sans-serif; +} +.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section:not(.tinymce-mobile-mask-tap-icon-selected) .tinymce-mobile-mask-tap-icon { + z-index: 2; +} +.tinymce-mobile-android-container.tinymce-mobile-android-maximized { + background: #ffffff; + border: none; + bottom: 0; + display: flex; + flex-direction: column; + left: 0; + position: fixed; + right: 0; + top: 0; +} +.tinymce-mobile-android-container:not(.tinymce-mobile-android-maximized) { + position: relative; +} +.tinymce-mobile-android-container .tinymce-mobile-editor-socket { + display: flex; + flex-grow: 1; +} +.tinymce-mobile-android-container .tinymce-mobile-editor-socket iframe { + display: flex !important; + flex-grow: 1; + height: auto !important; +} +.tinymce-mobile-android-scroll-reload { + overflow: hidden; +} +:not(.tinymce-mobile-readonly-mode) > .tinymce-mobile-android-selection-context-toolbar { + margin-top: 23px; +} +.tinymce-mobile-toolstrip { + background: #fff; + display: flex; + flex: 0 0 auto; + z-index: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar { + align-items: center; + background-color: #fff; + border-bottom: 1px solid #cccccc; + display: flex; + flex: 1; + height: 2.5em; + width: 100%; + /* Make it no larger than the toolstrip, so that it needs to scroll */ +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group { + align-items: center; + display: flex; + height: 100%; + flex-shrink: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group > div { + align-items: center; + display: flex; + height: 100%; + flex: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-exit-container { + background: #f44336; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-toolbar-scrollable-group { + flex-grow: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item { + padding-left: 0.5em; + padding-right: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button { + align-items: center; + display: flex; + height: 80%; + margin-left: 2px; + margin-right: 2px; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected { + background: #c8cbcf; + color: #cccccc; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:first-of-type, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:last-of-type { + background: #207ab7; + color: #eceff1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar { + /* Note, this file is imported inside .tinymce-mobile-context-toolbar, so that prefix is on everything here. */ +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group { + align-items: center; + display: flex; + height: 100%; + flex: 1; + padding-bottom: 0.4em; + padding-top: 0.4em; + /* Make any buttons appearing on the left and right display in the centre (e.g. color edges) */ + /* For widgets like the colour picker, use the whole height */ +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog { + display: flex; + min-height: 1.5em; + overflow: hidden; + padding-left: 0; + padding-right: 0; + position: relative; + width: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain { + display: flex; + height: 100%; + transition: left cubic-bezier(0.4, 0, 1, 1) 0.15s; + width: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen { + display: flex; + flex: 0 0 auto; + justify-content: space-between; + width: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen input { + font-family: Sans-serif; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container { + display: flex; + flex-grow: 1; + position: relative; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container .tinymce-mobile-input-container-x { + -ms-grid-row-align: center; + align-self: center; + background: inherit; + border: none; + border-radius: 50%; + color: #888; + font-size: 0.6em; + font-weight: bold; + height: 100%; + padding-right: 2px; + position: absolute; + right: 0; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container.tinymce-mobile-input-container-empty .tinymce-mobile-input-container-x { + display: none; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next { + align-items: center; + display: flex; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next::before { + align-items: center; + display: flex; + font-weight: bold; + height: 100%; + padding-left: 0.5em; + padding-right: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous.tinymce-mobile-toolbar-navigation-disabled::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next.tinymce-mobile-toolbar-navigation-disabled::before { + visibility: hidden; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item { + color: #cccccc; + font-size: 10px; + line-height: 10px; + margin: 0 2px; + padding-top: 3px; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item.tinymce-mobile-dot-active { + color: #c8cbcf; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-font::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-heading::before { + margin-left: 0.5em; + margin-right: 0.9em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-font::before, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-heading::before { + margin-left: 0.9em; + margin-right: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider { + display: flex; + flex: 1; + margin-left: 0; + margin-right: 0; + padding: 0.28em 0; + position: relative; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container { + align-items: center; + display: flex; + flex-grow: 1; + height: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container .tinymce-mobile-slider-size-line { + background: #cccccc; + display: flex; + flex: 1; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container { + padding-left: 2em; + padding-right: 2em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container { + align-items: center; + display: flex; + flex-grow: 1; + height: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container .tinymce-mobile-slider-gradient { + background: linear-gradient(to right, hsl(0, 100%, 50%) 0%, hsl(60, 100%, 50%) 17%, hsl(120, 100%, 50%) 33%, hsl(180, 100%, 50%) 50%, hsl(240, 100%, 50%) 67%, hsl(300, 100%, 50%) 83%, hsl(0, 100%, 50%) 100%); + display: flex; + flex: 1; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-black { + /* Not part of theming */ + background: black; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; + width: 1.2em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-white { + /* Not part of theming */ + background: white; + height: 0.2em; + margin-bottom: 0.3em; + margin-top: 0.3em; + width: 1.2em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb { + /* vertically centering trick (margin: auto, top: 0, bottom: 0). On iOS and Safari, if you leave + * out these values, then it shows the thumb at the top of the spectrum. This is probably because it is + * absolutely positioned with only a left value, and not a top. Note, on Chrome it seems to be fine without + * this approach. + */ + align-items: center; + background-clip: padding-box; + background-color: #455a64; + border: 0.5em solid rgba(136, 136, 136, 0); + border-radius: 3em; + bottom: 0; + color: #fff; + display: flex; + height: 0.5em; + justify-content: center; + left: -10px; + margin: auto; + position: absolute; + top: 0; + transition: border 120ms cubic-bezier(0.39, 0.58, 0.57, 1); + width: 0.5em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb.tinymce-mobile-thumb-active { + border: 0.5em solid rgba(136, 136, 136, 0.39); +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper, +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group > div { + align-items: center; + display: flex; + height: 100%; + flex: 1; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper { + flex-direction: column; + justify-content: center; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item { + align-items: center; + display: flex; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item:not(.tinymce-mobile-serialised-dialog) { + height: 100%; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-container { + display: flex; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input { + background: #ffffff; + border: none; + border-radius: 0; + color: #455a64; + flex-grow: 1; + font-size: 0.85em; + padding-bottom: 0.1em; + padding-left: 5px; + padding-top: 0.1em; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::-webkit-input-placeholder { + /* WebKit, Blink, Edge */ + color: #888; +} +.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::placeholder { + /* WebKit, Blink, Edge */ + color: #888; +} +/* dropup */ +.tinymce-mobile-dropup { + background: white; + display: flex; + overflow: hidden; + width: 100%; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-shrinking { + transition: height 0.3s ease-out; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-growing { + transition: height 0.3s ease-in; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-closed { + flex-grow: 0; +} +.tinymce-mobile-dropup.tinymce-mobile-dropup-open:not(.tinymce-mobile-dropup-growing) { + flex-grow: 1; +} +/* TODO min-height for device size and orientation */ +.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) { + min-height: 200px; +} +@media only screen and (orientation: landscape) { + .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) { + min-height: 200px; + } +} +@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) { + .tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) { + min-height: 150px; + } +} +/* styles menu */ +.tinymce-mobile-styles-menu { + font-family: sans-serif; + outline: 4px solid black; + overflow: hidden; + position: relative; + width: 100%; +} +.tinymce-mobile-styles-menu [role="menu"] { + display: flex; + flex-direction: column; + height: 100%; + position: absolute; + width: 100%; +} +.tinymce-mobile-styles-menu [role="menu"].transitioning { + transition: transform 0.5s ease-in-out; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item { + border-bottom: 1px solid #ddd; + color: #455a64; + cursor: pointer; + display: flex; + padding: 1em 1em; + position: relative; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser .tinymce-mobile-styles-collapse-icon::before { + color: #455a64; + content: "\e314"; + font-family: 'tinymce-mobile', sans-serif; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-styles-item-is-menu::after { + color: #455a64; + content: "\e315"; + font-family: 'tinymce-mobile', sans-serif; + padding-left: 1em; + padding-right: 1em; + position: absolute; + right: 0; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-format-matches::after { + font-family: 'tinymce-mobile', sans-serif; + padding-left: 1em; + padding-right: 1em; + position: absolute; + right: 0; +} +.tinymce-mobile-styles-menu .tinymce-mobile-styles-separator, +.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser { + align-items: center; + background: #fff; + border-top: #455a64; + color: #455a64; + display: flex; + min-height: 2.5em; + padding-left: 1em; + padding-right: 1em; +} +.tinymce-mobile-styles-menu [data-transitioning-destination="before"][data-transitioning-state], +.tinymce-mobile-styles-menu [data-transitioning-state="before"] { + transform: translate(-100%); +} +.tinymce-mobile-styles-menu [data-transitioning-destination="current"][data-transitioning-state], +.tinymce-mobile-styles-menu [data-transitioning-state="current"] { + transform: translate(0%); +} +.tinymce-mobile-styles-menu [data-transitioning-destination="after"][data-transitioning-state], +.tinymce-mobile-styles-menu [data-transitioning-state="after"] { + transform: translate(100%); +} +@font-face { + font-family: 'tinymce-mobile'; + font-style: normal; + font-weight: normal; + src: url('fonts/tinymce-mobile.woff?8x92w3') format('woff'); +} +@media (min-device-width: 700px) { + .tinymce-mobile-outer-container, + .tinymce-mobile-outer-container input { + font-size: 25px; + } +} +@media (max-device-width: 700px) { + .tinymce-mobile-outer-container, + .tinymce-mobile-outer-container input { + font-size: 18px; + } +} +.tinymce-mobile-icon { + font-family: 'tinymce-mobile', sans-serif; +} +.mixin-flex-and-centre { + align-items: center; + display: flex; + justify-content: center; +} +.mixin-flex-bar { + align-items: center; + display: flex; + height: 100%; +} +.tinymce-mobile-outer-container .tinymce-mobile-editor-socket iframe { + background-color: #fff; + width: 100%; +} +.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon { + /* Note, on the iPod touch in landscape, this isn't visible when the navbar appears */ + background-color: #207ab7; + border-radius: 50%; + bottom: 1em; + color: white; + font-size: 1em; + height: 2.1em; + position: fixed; + right: 2em; + width: 2.1em; + align-items: center; + display: flex; + justify-content: center; +} +@media only screen and (min-device-width:700px) { + .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon { + font-size: 1.2em; + } +} +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket { + height: 300px; + overflow: hidden; +} +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket iframe { + height: 100%; +} +.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-toolstrip { + display: none; +} +/* + Note, that if you don't include this (::-webkit-file-upload-button), the toolbar width gets + increased and the whole body becomes scrollable. It's important! + */ +input[type="file"]::-webkit-file-upload-button { + display: none; +} +@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) { + .tinymce-mobile-ios-container .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon { + bottom: 50%; + } +} diff --git a/public/tinymce/skins/ui/oxide/skin.mobile.min.css b/public/tinymce/skins/ui/oxide/skin.mobile.min.css new file mode 100644 index 0000000..3a45cac --- /dev/null +++ b/public/tinymce/skins/ui/oxide/skin.mobile.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +.tinymce-mobile-outer-container{all:initial;display:block}.tinymce-mobile-outer-container *{border:0;box-sizing:initial;cursor:inherit;float:none;line-height:1;margin:0;outline:0;padding:0;-webkit-tap-highlight-color:transparent;text-shadow:none;white-space:nowrap}.tinymce-mobile-icon-arrow-back::before{content:"\e5cd"}.tinymce-mobile-icon-image::before{content:"\e412"}.tinymce-mobile-icon-cancel-circle::before{content:"\e5c9"}.tinymce-mobile-icon-full-dot::before{content:"\e061"}.tinymce-mobile-icon-align-center::before{content:"\e234"}.tinymce-mobile-icon-align-left::before{content:"\e236"}.tinymce-mobile-icon-align-right::before{content:"\e237"}.tinymce-mobile-icon-bold::before{content:"\e238"}.tinymce-mobile-icon-italic::before{content:"\e23f"}.tinymce-mobile-icon-unordered-list::before{content:"\e241"}.tinymce-mobile-icon-ordered-list::before{content:"\e242"}.tinymce-mobile-icon-font-size::before{content:"\e245"}.tinymce-mobile-icon-underline::before{content:"\e249"}.tinymce-mobile-icon-link::before{content:"\e157"}.tinymce-mobile-icon-unlink::before{content:"\eca2"}.tinymce-mobile-icon-color::before{content:"\e891"}.tinymce-mobile-icon-previous::before{content:"\e314"}.tinymce-mobile-icon-next::before{content:"\e315"}.tinymce-mobile-icon-large-font::before,.tinymce-mobile-icon-style-formats::before{content:"\e264"}.tinymce-mobile-icon-undo::before{content:"\e166"}.tinymce-mobile-icon-redo::before{content:"\e15a"}.tinymce-mobile-icon-removeformat::before{content:"\e239"}.tinymce-mobile-icon-small-font::before{content:"\e906"}.tinymce-mobile-format-matches::after,.tinymce-mobile-icon-readonly-back::before{content:"\e5ca"}.tinymce-mobile-icon-small-heading::before{content:"small"}.tinymce-mobile-icon-large-heading::before{content:"large"}.tinymce-mobile-icon-large-heading::before,.tinymce-mobile-icon-small-heading::before{font-family:sans-serif;font-size:80%}.tinymce-mobile-mask-edit-icon::before{content:"\e254"}.tinymce-mobile-icon-back::before{content:"\e5c4"}.tinymce-mobile-icon-heading::before{content:"Headings";font-family:sans-serif;font-size:80%;font-weight:700}.tinymce-mobile-icon-h1::before{content:"H1";font-weight:700}.tinymce-mobile-icon-h2::before{content:"H2";font-weight:700}.tinymce-mobile-icon-h3::before{content:"H3";font-weight:700}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask{align-items:center;display:flex;justify-content:center;background:rgba(51,51,51,.5);height:100%;position:absolute;top:0;width:100%}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container{align-items:center;border-radius:50%;display:flex;flex-direction:column;font-family:sans-serif;font-size:1em;justify-content:space-between}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .mixin-menu-item{align-items:center;display:flex;justify-content:center;border-radius:50%;height:2.1em;width:2.1em}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section{align-items:center;display:flex;justify-content:center;flex-direction:column;font-size:1em}@media only screen and (min-device-width:700px){.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section{font-size:1.2em}}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon{align-items:center;display:flex;justify-content:center;border-radius:50%;height:2.1em;width:2.1em;background-color:#fff;color:#207ab7}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon::before{content:"\e900";font-family:tinymce-mobile,sans-serif}.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section:not(.tinymce-mobile-mask-tap-icon-selected) .tinymce-mobile-mask-tap-icon{z-index:2}.tinymce-mobile-android-container.tinymce-mobile-android-maximized{background:#fff;border:none;bottom:0;display:flex;flex-direction:column;left:0;position:fixed;right:0;top:0}.tinymce-mobile-android-container:not(.tinymce-mobile-android-maximized){position:relative}.tinymce-mobile-android-container .tinymce-mobile-editor-socket{display:flex;flex-grow:1}.tinymce-mobile-android-container .tinymce-mobile-editor-socket iframe{display:flex!important;flex-grow:1;height:auto!important}.tinymce-mobile-android-scroll-reload{overflow:hidden}:not(.tinymce-mobile-readonly-mode)>.tinymce-mobile-android-selection-context-toolbar{margin-top:23px}.tinymce-mobile-toolstrip{background:#fff;display:flex;flex:0 0 auto;z-index:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar{align-items:center;background-color:#fff;border-bottom:1px solid #ccc;display:flex;flex:1;height:2.5em;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group{align-items:center;display:flex;height:100%;flex-shrink:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group>div{align-items:center;display:flex;height:100%;flex:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-exit-container{background:#f44336}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-toolbar-scrollable-group{flex-grow:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item{padding-left:.5em;padding-right:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button{align-items:center;display:flex;height:80%;margin-left:2px;margin-right:2px}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected{background:#c8cbcf;color:#ccc}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:first-of-type,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:last-of-type{background:#207ab7;color:#eceff1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group{align-items:center;display:flex;height:100%;flex:1;padding-bottom:.4em;padding-top:.4em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog{display:flex;min-height:1.5em;overflow:hidden;padding-left:0;padding-right:0;position:relative;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain{display:flex;height:100%;transition:left cubic-bezier(.4,0,1,1) .15s;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen{display:flex;flex:0 0 auto;justify-content:space-between;width:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen input{font-family:Sans-serif}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container{display:flex;flex-grow:1;position:relative}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container .tinymce-mobile-input-container-x{-ms-grid-row-align:center;align-self:center;background:inherit;border:none;border-radius:50%;color:#888;font-size:.6em;font-weight:700;height:100%;padding-right:2px;position:absolute;right:0}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container.tinymce-mobile-input-container-empty .tinymce-mobile-input-container-x{display:none}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous{align-items:center;display:flex}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous::before{align-items:center;display:flex;font-weight:700;height:100%;padding-left:.5em;padding-right:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next.tinymce-mobile-toolbar-navigation-disabled::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous.tinymce-mobile-toolbar-navigation-disabled::before{visibility:hidden}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item{color:#ccc;font-size:10px;line-height:10px;margin:0 2px;padding-top:3px}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item.tinymce-mobile-dot-active{color:#c8cbcf}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-font::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-heading::before{margin-left:.5em;margin-right:.9em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-font::before,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-heading::before{margin-left:.9em;margin-right:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider{display:flex;flex:1;margin-left:0;margin-right:0;padding:.28em 0;position:relative}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container{align-items:center;display:flex;flex-grow:1;height:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container .tinymce-mobile-slider-size-line{background:#ccc;display:flex;flex:1;height:.2em;margin-bottom:.3em;margin-top:.3em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container{padding-left:2em;padding-right:2em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container{align-items:center;display:flex;flex-grow:1;height:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container .tinymce-mobile-slider-gradient{background:linear-gradient(to right,red 0,#feff00 17%,#0f0 33%,#00feff 50%,#00f 67%,#ff00fe 83%,red 100%);display:flex;flex:1;height:.2em;margin-bottom:.3em;margin-top:.3em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-black{background:#000;height:.2em;margin-bottom:.3em;margin-top:.3em;width:1.2em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-white{background:#fff;height:.2em;margin-bottom:.3em;margin-top:.3em;width:1.2em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb{align-items:center;background-clip:padding-box;background-color:#455a64;border:.5em solid rgba(136,136,136,0);border-radius:3em;bottom:0;color:#fff;display:flex;height:.5em;justify-content:center;left:-10px;margin:auto;position:absolute;top:0;transition:border 120ms cubic-bezier(.39,.58,.57,1);width:.5em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb.tinymce-mobile-thumb-active{border:.5em solid rgba(136,136,136,.39)}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper,.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group>div{align-items:center;display:flex;height:100%;flex:1}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper{flex-direction:column;justify-content:center}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item{align-items:center;display:flex}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item:not(.tinymce-mobile-serialised-dialog){height:100%}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-container{display:flex}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input{background:#fff;border:none;border-radius:0;color:#455a64;flex-grow:1;font-size:.85em;padding-bottom:.1em;padding-left:5px;padding-top:.1em}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::-webkit-input-placeholder{color:#888}.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::placeholder{color:#888}.tinymce-mobile-dropup{background:#fff;display:flex;overflow:hidden;width:100%}.tinymce-mobile-dropup.tinymce-mobile-dropup-shrinking{transition:height .3s ease-out}.tinymce-mobile-dropup.tinymce-mobile-dropup-growing{transition:height .3s ease-in}.tinymce-mobile-dropup.tinymce-mobile-dropup-closed{flex-grow:0}.tinymce-mobile-dropup.tinymce-mobile-dropup-open:not(.tinymce-mobile-dropup-growing){flex-grow:1}.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height:200px}@media only screen and (orientation:landscape){.tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height:200px}}@media only screen and (min-device-width :320px) and (max-device-width :568px) and (orientation :landscape){.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed){min-height:150px}}.tinymce-mobile-styles-menu{font-family:sans-serif;outline:4px solid #000;overflow:hidden;position:relative;width:100%}.tinymce-mobile-styles-menu [role=menu]{display:flex;flex-direction:column;height:100%;position:absolute;width:100%}.tinymce-mobile-styles-menu [role=menu].transitioning{transition:transform .5s ease-in-out}.tinymce-mobile-styles-menu .tinymce-mobile-styles-item{border-bottom:1px solid #ddd;color:#455a64;cursor:pointer;display:flex;padding:1em 1em;position:relative}.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser .tinymce-mobile-styles-collapse-icon::before{color:#455a64;content:"\e314";font-family:tinymce-mobile,sans-serif}.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-styles-item-is-menu::after{color:#455a64;content:"\e315";font-family:tinymce-mobile,sans-serif;padding-left:1em;padding-right:1em;position:absolute;right:0}.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-format-matches::after{font-family:tinymce-mobile,sans-serif;padding-left:1em;padding-right:1em;position:absolute;right:0}.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser,.tinymce-mobile-styles-menu .tinymce-mobile-styles-separator{align-items:center;background:#fff;border-top:#455a64;color:#455a64;display:flex;min-height:2.5em;padding-left:1em;padding-right:1em}.tinymce-mobile-styles-menu [data-transitioning-destination=before][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=before]{transform:translate(-100%)}.tinymce-mobile-styles-menu [data-transitioning-destination=current][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=current]{transform:translate(0)}.tinymce-mobile-styles-menu [data-transitioning-destination=after][data-transitioning-state],.tinymce-mobile-styles-menu [data-transitioning-state=after]{transform:translate(100%)}@font-face{font-family:tinymce-mobile;font-style:normal;font-weight:400;src:url(fonts/tinymce-mobile.woff?8x92w3) format('woff')}@media (min-device-width:700px){.tinymce-mobile-outer-container,.tinymce-mobile-outer-container input{font-size:25px}}@media (max-device-width:700px){.tinymce-mobile-outer-container,.tinymce-mobile-outer-container input{font-size:18px}}.tinymce-mobile-icon{font-family:tinymce-mobile,sans-serif}.mixin-flex-and-centre{align-items:center;display:flex;justify-content:center}.mixin-flex-bar{align-items:center;display:flex;height:100%}.tinymce-mobile-outer-container .tinymce-mobile-editor-socket iframe{background-color:#fff;width:100%}.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{background-color:#207ab7;border-radius:50%;bottom:1em;color:#fff;font-size:1em;height:2.1em;position:fixed;right:2em;width:2.1em;align-items:center;display:flex;justify-content:center}@media only screen and (min-device-width:700px){.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{font-size:1.2em}}.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket{height:300px;overflow:hidden}.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket iframe{height:100%}.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-toolstrip{display:none}input[type=file]::-webkit-file-upload-button{display:none}@media only screen and (min-device-width :320px) and (max-device-width :568px) and (orientation :landscape){.tinymce-mobile-ios-container .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon{bottom:50%}} diff --git a/public/tinymce/skins/ui/oxide/skin.shadowdom.css b/public/tinymce/skins/ui/oxide/skin.shadowdom.css new file mode 100644 index 0000000..d2adc4d --- /dev/null +++ b/public/tinymce/skins/ui/oxide/skin.shadowdom.css @@ -0,0 +1,37 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +body.tox-dialog__disable-scroll { + overflow: hidden; +} +.tox-fullscreen { + border: 0; + height: 100%; + margin: 0; + overflow: hidden; + -ms-scroll-chaining: none; + overscroll-behavior: none; + padding: 0; + touch-action: pinch-zoom; + width: 100%; +} +.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { + display: none; +} +.tox.tox-tinymce.tox-fullscreen, +.tox-shadowhost.tox-fullscreen { + left: 0; + position: fixed; + top: 0; + z-index: 1200; +} +.tox.tox-tinymce.tox-fullscreen { + background-color: transparent; +} +.tox-fullscreen .tox.tox-tinymce-aux, +.tox-fullscreen ~ .tox.tox-tinymce-aux { + z-index: 1201; +} diff --git a/public/tinymce/skins/ui/oxide/skin.shadowdom.min.css b/public/tinymce/skins/ui/oxide/skin.shadowdom.min.css new file mode 100644 index 0000000..a0893b9 --- /dev/null +++ b/public/tinymce/skins/ui/oxide/skin.shadowdom.min.css @@ -0,0 +1,7 @@ +/** + * Copyright (c) Tiny Technologies, Inc. All rights reserved. + * Licensed under the LGPL or a commercial license. + * For LGPL see License.txt in the project root for license information. + * For commercial licenses see https://www.tiny.cloud/ + */ +body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;-ms-scroll-chaining:none;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..3beb7f1 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,36 @@ +<template> + <ele-config-provider + :map-key="MAP_KEY" + :locale="eleLocale" + :keep-alive="keepAlive" + :license="LICENSE_CODE" + > + <a-config-provider :locale="antLocale"> + <router-view /> + </a-config-provider> + </ele-config-provider> +</template> + +<script lang="ts" setup> + import { unref, computed } from 'vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { MAP_KEY, LICENSE_CODE, TAB_KEEP_ALIVE } from '@/config/setting'; + import { useSetDocumentTitle } from '@/utils/document-title-util'; + import { useLocale } from '@/i18n/use-locale'; + + const themeStore = useThemeStore(); + const { showTabs } = storeToRefs(themeStore); + + // 恢复主题 + themeStore.recoverTheme(); + + // 切换路由自动更新浏览器页签标题 + useSetDocumentTitle(); + + // 国际化配置 + const { antLocale, eleLocale } = useLocale(); + + // 用于内链 iframe 组件获取 KeepAlive + const keepAlive = computed(() => TAB_KEEP_ALIVE && unref(showTabs)); +</script> diff --git a/src/api/content/article/index.ts b/src/api/content/article/index.ts new file mode 100644 index 0000000..419f091 --- /dev/null +++ b/src/api/content/article/index.ts @@ -0,0 +1,66 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { Article, ArticleParam } from './model'; + +/** + * 分页查询 + */ +export async function pageArticles(params: ArticleParam) { + const res = await request.get<ApiResult<PageResult<Article>>>( + '/content/article/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加 + */ +export async function addArticle(data: Article) { + const res = await request.post<ApiResult<unknown>>('/content/article', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改 + */ +export async function updateArticle(data: Article) { + const res = await request.put<ApiResult<unknown>>('/content/article', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除 + */ +export async function removeArticle(id?: number) { + const res = await request.delete<ApiResult<unknown>>( + '/content/article/' + id + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改状态 + */ +export async function updateArticleStatus(id?: number, status?: number) { + const res = await request.put<ApiResult<unknown>>('/content/article/status', { + id, + status + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/content/article/model/index.ts b/src/api/content/article/model/index.ts new file mode 100644 index 0000000..966aa5e --- /dev/null +++ b/src/api/content/article/model/index.ts @@ -0,0 +1,51 @@ +import type { PageParam } from '@/api'; + +/** + * 文章 + */ +export interface Article { + // id + id?: number; + // 栏目ID + cateId?: number; + // 发布者 + user?: string; + // 标题 + title?: string; + // 来源 + origin?: string; + // 类型 + type?: number; + // 外链地址 + link?: string; + //模板路径 + template?: string; + // 图片 + image?: string; + // 关键字 + keywords?: string; + // 摘要 + summary?: string; + // 内容 + content?: string; + // 点击量 + clickNum?: number; + // 添加量 + addNum?: number; + // 状态 + status?: number; + // 删除 + deleted?: number; + // 创建时间 + createTime: string; +} + +/** + * 搜索条件 + */ +export interface ArticleParam extends PageParam { + title?: string; + user?: string; + status?: number; + cateId?: number; +} diff --git a/src/api/content/category/index.ts b/src/api/content/category/index.ts new file mode 100644 index 0000000..0b952a3 --- /dev/null +++ b/src/api/content/category/index.ts @@ -0,0 +1,80 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { Category, CategoryParam } from './model'; + +/** + * 分页查询分类 + */ +export async function pageCategories(params: CategoryParam) { + const res = await request.get<ApiResult<PageResult<Category>>>( + '/content/category/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * Id查询 + * @param params + * @returns + */ +export async function getCategory(id: number) { + const res = await request.get<ApiResult<Category[]>>( + '/content/category/' + id + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询列表 + */ +export async function listCategories(params?: CategoryParam) { + const res = await request.get<ApiResult<Category[]>>('/content/category', { + params + }); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加 + */ +export async function addCategory(data: Category) { + const res = await request.post<ApiResult<unknown>>('/content/category', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改 + */ +export async function updateCategory(data: Category) { + const res = await request.put<ApiResult<unknown>>('/content/category', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除 + */ +export async function removeCategory(id?: number) { + const res = await request.delete<ApiResult<unknown>>( + '/content/category/' + id + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/content/category/model/index.ts b/src/api/content/category/model/index.ts new file mode 100644 index 0000000..d34dd82 --- /dev/null +++ b/src/api/content/category/model/index.ts @@ -0,0 +1,44 @@ +import { PageParam } from '@/api'; + +/** + * 分类 + */ +export interface Category { + // 分类id + cateId?: number; + // 上级id, 0是顶级 + parentId?: number; + // 分类名称 + cateName?: string; + //栏目类型 + menuType?: number; + //外链 + url?: string; + //模板 + template?: string; + //color + color?: string; + //图片 + image?: string; + // 排序号 + sortNumber?: number; + // 备注 + introduction?: string; + // 创建时间 + created_at?: string; + // + key?: number; + // + value?: number; + // + title?: string; + + disabled?: boolean | number; +} + +/** + * 搜索条件 + */ +export interface CategoryParam extends PageParam { + cateName?: string; +} diff --git a/src/api/dashboard/analysis/index.ts b/src/api/dashboard/analysis/index.ts new file mode 100644 index 0000000..19354b6 --- /dev/null +++ b/src/api/dashboard/analysis/index.ts @@ -0,0 +1,56 @@ +import request from '@/utils/request'; +import type { ApiResult } from '@/api'; +import type { PayNumData, SaleroomResult, VisitData, CloudData } from './model'; + +/** + * 获取支付笔数数据 + */ +export async function getPayNumList() { + const res = await request.get<ApiResult<PayNumData[]>>( + 'https://cdn.eleadmin.com/20200610/analysis-pay-num.json' + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 获取销售量数据 + */ +export async function getSaleroomList() { + const res = await request.get<ApiResult<SaleroomResult>>( + 'https://cdn.eleadmin.com/20200610/analysis-saleroom.json' + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 获取最近 1 小时访问情况数据 + * @returns {Promise<Object>} + */ +export async function getVisitHourList() { + const res = await request.get<ApiResult<VisitData[]>>( + 'https://cdn.eleadmin.com/20200610/analysis-visits.json' + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 获取词云数据 + */ +export async function getWordCloudList() { + const res = await request.get<ApiResult<CloudData[]>>( + 'https://cdn.eleadmin.com/20200610/analysis-hot-search.json' + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/dashboard/analysis/model/index.ts b/src/api/dashboard/analysis/model/index.ts new file mode 100644 index 0000000..026ecbd --- /dev/null +++ b/src/api/dashboard/analysis/model/index.ts @@ -0,0 +1,46 @@ +/** + * 支付笔数数据格式 + */ +export interface PayNumData { + // 日期 + date?: string; + // 支付笔数 + value?: number; +} + +/** + * 销售量数据格式 + */ +export interface SaleroomData { + // 月份 + month?: string; + // 销售量 + value?: number; +} + +export interface SaleroomResult { + list1: SaleroomData[]; + list2: SaleroomData[]; +} + +/** + * 访问情况数据格式 + */ +export interface VisitData { + // 时间 + time?: string; + // 访问量 + visits?: number; + // 浏览量 + views?: number; +} + +/** + * 词云数据格式 + */ +export interface CloudData { + // 标题 + name: string; + // 数量 + value: number; +} diff --git a/src/api/dashboard/monitor/index.ts b/src/api/dashboard/monitor/index.ts new file mode 100644 index 0000000..37af9f4 --- /dev/null +++ b/src/api/dashboard/monitor/index.ts @@ -0,0 +1,44 @@ +import request from '@/utils/request'; +import type { ApiResult } from '@/api'; +import type { UserCount, BrowserCount } from './model'; +const BASE_URL = import.meta.env.BASE_URL; + +/** + * 获取中国地图geo数据 + */ +export async function getChinaMapData() { + const res = await request.get<any>( + BASE_URL + 'json/china-provinces.geo.json', + { baseURL: '' } + ); + if (res.data) { + return res.data; + } + return Promise.reject(new Error('获取地图数据失败')); +} + +/** + * 获取用户分布数据 + */ +export async function getUserCountList() { + const res = await request.get<ApiResult<UserCount[]>>( + 'https://cdn.eleadmin.com/20200610/monitor-user-count.json' + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 获取用户浏览器分布数据 + */ +export async function getBrowserCountList() { + const res = await request.get<ApiResult<BrowserCount[]>>( + 'https://cdn.eleadmin.com/20200610/monitor-browser-count.json' + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/dashboard/monitor/model/index.ts b/src/api/dashboard/monitor/model/index.ts new file mode 100644 index 0000000..e7d21a8 --- /dev/null +++ b/src/api/dashboard/monitor/model/index.ts @@ -0,0 +1,21 @@ +/** + * 用户分布数据格式 + */ +export interface UserCount { + // 省份 + name: string; + // 用户数量 + value: number; + // 百分比 + percent?: number; +} + +/** + * 浏览器分布数据格式 + */ +export interface BrowserCount { + // 浏览器 + name: string; + // 用户数量 + value: number; +} diff --git a/src/api/employ/category/index.ts b/src/api/employ/category/index.ts new file mode 100644 index 0000000..1d508d1 --- /dev/null +++ b/src/api/employ/category/index.ts @@ -0,0 +1,81 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { Category, CategoryParam } from './model'; + +/** + * 分页查询 + */ +export async function pageCategory(params: CategoryParam) { + const res = await request.get<ApiResult<PageResult<Category>>>( + '/employ/category/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询列表 + */ +export async function listCategory(params?: CategoryParam) { + const res = await request.get<ApiResult<Category[]>>('/employ/category', { + params + }); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加 + */ +export async function addCategory(data: Category) { + const res = await request.post<ApiResult<unknown>>('/employ/category', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改 + */ +export async function updateCategory(data: Category) { + const res = await request.put<ApiResult<unknown>>('/employ/category', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除 + */ +export async function removeCategory(id?: number) { + const res = await request.delete<ApiResult<unknown>>( + '/employ/category/' + id + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 批量删除 + */ +export async function removeCategoryBatch(data: (number | undefined)[]) { + const res = await request.delete<ApiResult<unknown>>( + '/employ/category/batch', + { + data + } + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/employ/category/model/index.ts b/src/api/employ/category/model/index.ts new file mode 100644 index 0000000..6522b14 --- /dev/null +++ b/src/api/employ/category/model/index.ts @@ -0,0 +1,23 @@ +import type { PageParam } from '@/api'; + +/** + * 职位分类 + */ +export interface Category { + // id + categoryId?: number; + // 名称 + name?: string; + // 备注 + comments?: string; + // 创建时间 + createTime?: string; +} + +/** + * 分类搜索条件 + */ +export interface CategoryParam extends PageParam { + name?: string; + comments?: string; +} diff --git a/src/api/employ/company/index.ts b/src/api/employ/company/index.ts new file mode 100644 index 0000000..3988d5c --- /dev/null +++ b/src/api/employ/company/index.ts @@ -0,0 +1,124 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { Company, CompanyParam } from './model'; + +/** + * 分页查询公司 + */ +export async function pageCompany(params: CompanyParam) { + const res = await request.get<ApiResult<PageResult<Company>>>( + '/employ/company/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询公司列表 + */ +export async function listCompany(params?: CompanyParam) { + const res = await request.get<ApiResult<Company[]>>('/employ/company', { + params + }); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 根据id查询公司 + */ +export async function getCompany(id: number) { + const res = await request.get<ApiResult<Company>>('/employ/company' + id); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加公司 + */ +export async function addCompany(data: Company) { + const res = await request.post<ApiResult<unknown>>('/employ/company', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改公司 + */ +export async function updateCompany(data: Company) { + const res = await request.put<ApiResult<unknown>>('/employ/company', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除公司 + */ +export async function removeCompany(id?: number) { + const res = await request.delete<ApiResult<unknown>>('/employ/company/' + id); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 批量删除公司 + */ +export async function removeCompanyBatch(data: (number | undefined)[]) { + const res = await request.delete<ApiResult<unknown>>( + '/employ/company/batch', + { + data + } + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改公司状态 + */ +export async function updateCompanyStatus(companyId?: number, status?: number) { + const res = await request.put<ApiResult<unknown>>('/employ/company/status', { + companyId, + status + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 检查公司是否存在 + */ +export async function checkExistence( + field: string, + value: string, + id?: number +) { + const res = await request.get<ApiResult<unknown>>( + '/employ/company/existence', + { + params: { field, value, id } + } + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/employ/company/model/index.ts b/src/api/employ/company/model/index.ts new file mode 100644 index 0000000..1416517 --- /dev/null +++ b/src/api/employ/company/model/index.ts @@ -0,0 +1,40 @@ +import type { PageParam } from '@/api'; + +/** + * 公司 + */ +export interface Company { + // 公司id + companyId?: number; + // 名称 + name?: string; + // logo + logo?: string; + //联系人 + hr?: string; + // 联系电话 + phone?: string; + // 邮箱 + email?: string; + // 联系地址 + address?: string; + // 地图导航 + location?: string; + // 公司简介 + comment?: string; + // 状态, 0正常, 1冻结 + status?: number; + //是否删除 + deleted?: number; + // 创建时间 + createTime?: string; +} + +/** + * 企业搜索条件 + */ +export interface CompanyParam extends PageParam { + name?: string; + address?: string; + status?: string; +} diff --git a/src/api/employ/jobs/index.ts b/src/api/employ/jobs/index.ts new file mode 100644 index 0000000..f3c2dc3 --- /dev/null +++ b/src/api/employ/jobs/index.ts @@ -0,0 +1,79 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { Job, JobParam } from './model'; + +/** + * 分页查询 + */ +export async function pageJobs(params: JobParam) { + const res = await request.get<ApiResult<PageResult<Job>>>( + '/employ/jobs/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加 + */ +export async function addJob(data: Job) { + const res = await request.post<ApiResult<unknown>>('/employ/jobs', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改 + */ +export async function updateJob(data: Job) { + const res = await request.put<ApiResult<unknown>>('/employ/jobs', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除 + */ +export async function removeJob(id?: number) { + const res = await request.delete<ApiResult<unknown>>('/employ/jobs/' + id); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 批量删除 + */ +export async function removeJobBatch(data: (number | undefined)[]) { + const res = await request.delete<ApiResult<unknown>>( + '/employ/jobs/batch', + { + data + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改状态 + */ +export async function updateJobStatus(jobId?: number, status?: number) { + const res = await request.put<ApiResult<unknown>>('/employ/jobs/status', { + jobId, + status + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/employ/jobs/model/index.ts b/src/api/employ/jobs/model/index.ts new file mode 100644 index 0000000..9e205d4 --- /dev/null +++ b/src/api/employ/jobs/model/index.ts @@ -0,0 +1,39 @@ +import type { PageParam } from '@/api'; +import { Company } from '@/api/employ/company/model'; +import { Category } from '@/api/employ/category/model'; + +/** + * 职位 + */ +export interface Job { + // id + jobId?: number; + seniority?: string; + companyId?: string; + salary?: string; + company?: Company[]; + categoryId?: number; + degree?: string; + category?: Category[]; + needs?: string; + tags?: []; + // 名称 + title?: string; + // 职位简介 + description?: string; + // 状态, 0正常, 1冻结 + status?: number; + //是否删除 + deleted?: number; + // 创建时间 + createTime?: string; +} + +/** + * 搜索条件 + */ +export interface JobParam extends PageParam { + title?: string; + company?: string; + status?: string; +} diff --git a/src/api/employ/tags/index.ts b/src/api/employ/tags/index.ts new file mode 100644 index 0000000..e4b5cf5 --- /dev/null +++ b/src/api/employ/tags/index.ts @@ -0,0 +1,76 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import { Tag, TagParam } from '@/api/employ/tags/model'; + +/** + * 分页查询 + */ +export async function pageTags(params: TagParam) { + const res = await request.get<ApiResult<PageResult<Tag>>>( + '/employ/tags/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询列表 + */ +export async function listTags(params?: TagParam) { + const res = await request.get<ApiResult<Tag[]>>('/employ/tags', { + params + }); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加 + */ +export async function addTag(data: Tag) { + const res = await request.post<ApiResult<unknown>>('/employ/tags', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改 + */ +export async function updateTag(data: Tag) { + const res = await request.put<ApiResult<unknown>>('/employ/tags', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除 + */ +export async function removeTag(id?: number) { + const res = await request.delete<ApiResult<unknown>>('/employ/tags/' + id); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 批量删除 + */ +export async function removeTagBatch(data: (number | undefined)[]) { + const res = await request.delete<ApiResult<unknown>>('/employ/tags/batch', { + data + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/employ/tags/model/index.ts b/src/api/employ/tags/model/index.ts new file mode 100644 index 0000000..25eb85a --- /dev/null +++ b/src/api/employ/tags/model/index.ts @@ -0,0 +1,23 @@ +import type { PageParam } from '@/api'; + +/** + * 职位标签 + */ +export interface Tag { + // id + tagId?: number; + // 名称 + name?: string; + // 备注 + comments?: string; + // 创建时间 + createTime?: string; +} + +/** + * 分类搜索条件 + */ +export interface TagParam extends PageParam { + name?: string; + comments?: string; +} diff --git a/src/api/example/choose/index.ts b/src/api/example/choose/index.ts new file mode 100644 index 0000000..3033438 --- /dev/null +++ b/src/api/example/choose/index.ts @@ -0,0 +1,17 @@ +import request from '@/utils/request'; +import type { ApiResult } from '@/api'; +import type { Classes, ClassesParam } from './model'; + +/** + * 获取全部的班级数据 + */ +export async function getAllClasses(params?: ClassesParam) { + const res = await request.get<ApiResult<Classes[]>>( + 'https://cdn.eleadmin.com/20200610/classes.json', + { params } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/example/choose/model/index.ts b/src/api/example/choose/model/index.ts new file mode 100644 index 0000000..8fb0520 --- /dev/null +++ b/src/api/example/choose/model/index.ts @@ -0,0 +1,21 @@ +/** + * 班级 + */ +export interface Classes { + // 班级id + classesId?: number; + // 班级名称 + classesName?: string; + // 学院名称 + college?: string; + // 专业名称 + major?: string; +} + +/** + * 班级查询参数 + */ +export interface ClassesParam { + classesId?: number; + classesName?: string; +} diff --git a/src/api/example/document/index.ts b/src/api/example/document/index.ts new file mode 100644 index 0000000..fa00964 --- /dev/null +++ b/src/api/example/document/index.ts @@ -0,0 +1,31 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { Piece, PieceParam, Archive, ArchiveParam } from './model'; + +/** + * 获取案卷列表 + */ +export async function getPieceList(params: PieceParam) { + const res = await request.get<ApiResult<PageResult<Piece>>>( + 'https://cdn.eleadmin.com/20200610/document.json', + { params } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 获取卷内文件列表 + */ +export async function getArchiveList(params: ArchiveParam) { + const res = await request.get<ApiResult<Archive[]>>( + 'https://cdn.eleadmin.com/20200610/archive.json', + { params } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/example/document/model/index.ts b/src/api/example/document/model/index.ts new file mode 100644 index 0000000..f9847b5 --- /dev/null +++ b/src/api/example/document/model/index.ts @@ -0,0 +1,71 @@ +import { PageParam } from '@/api'; + +/** + * 案卷 + */ +export interface Piece { + // 案卷id + id?: number; + // 案卷题名 + title?: string; + // 案卷档号 + piece_no?: string; + // 密级 + secret?: string; + // 存放位置 + location?: string; + // 案卷类型 + type?: string; + // 保管期限 + retention?: string; + // 载体类型 + carrier?: string; + // 归档年度 + year?: string; + // 件数 + amount?: number; +} + +/** + * 案卷查询参数 + */ +export interface PieceParam extends PageParam { + title?: string; + piece_no?: string; +} + +/** + * 文档 + */ +export interface Archive { + // 文件题名 + title?: string; + // 案卷档号 + piece_no?: string; + // 文件档号 + archive_no?: string; + // 密级 + secret?: string; + // 存放位置 + location?: string; + // 文件类型 + type?: string; + // 保管期限 + retention?: string; + // 载体类型 + carrier?: string; + // 归档年度 + year?: string; + // 排序号 + sort_number?: number; +} + +/** + * 文档查询参数 + */ +export interface ArchiveParam { + title?: string; + piece_no?: string; + archive_no?: string; + piece_no_in?: (string | undefined)[]; +} diff --git a/src/api/example/table/index.ts b/src/api/example/table/index.ts new file mode 100644 index 0000000..cead949 --- /dev/null +++ b/src/api/example/table/index.ts @@ -0,0 +1,13 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { UserScore } from './model'; + +export async function pageUserScores() { + const res = await request.get<ApiResult<PageResult<UserScore>>>( + 'https://cdn.eleadmin.com/20200610/example-table-merge.json' + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/example/table/model/index.ts b/src/api/example/table/model/index.ts new file mode 100644 index 0000000..c497242 --- /dev/null +++ b/src/api/example/table/model/index.ts @@ -0,0 +1,7 @@ +export interface UserScore { + id: number; + userName: string; + courseName: string; + score: number; + userNameRowSpan: number; +} diff --git a/src/api/form/advanced/index.ts b/src/api/form/advanced/index.ts new file mode 100644 index 0000000..138c89f --- /dev/null +++ b/src/api/form/advanced/index.ts @@ -0,0 +1,28 @@ +import type { UserItem } from './model'; + +/** + * 获取数据 + */ +export async function queryList() { + const data: UserItem[] = [ + { + key: '1', + number: '00001', + name: 'John Brown', + department: '研发部' + }, + { + key: '2', + number: '00002', + name: 'Jim Green', + department: '产品部' + }, + { + key: '3', + number: '00003', + name: 'Joe Black', + department: '产品部' + } + ]; + return data; +} diff --git a/src/api/form/advanced/model/index.ts b/src/api/form/advanced/model/index.ts new file mode 100644 index 0000000..fcfabad --- /dev/null +++ b/src/api/form/advanced/model/index.ts @@ -0,0 +1,7 @@ +export interface UserItem { + key: string; + isEdit?: boolean; + number?: string; + name?: string; + department?: string; +} diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..9cbda77 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,35 @@ +/** + * 接口统一返回结果 + */ +export interface ApiResult<T> { + // 状态码 + code: number; + // 状态信息 + message?: string; + // 返回数据 + data?: T; +} + +/** + * 分页查询统一结果 + */ +export interface PageResult<T> { + // 返回数据 + list: T[]; + // 总数量 + count: number; +} + +/** + * 分页查询基本参数 + */ +export interface PageParam { + // 第几页 + page?: number; + // 每页多少条 + limit?: number; + // 排序字段 + sort?: string; + // 排序方式, asc升序, desc降序 + order?: string; +} diff --git a/src/api/layout/index.ts b/src/api/layout/index.ts new file mode 100644 index 0000000..024a145 --- /dev/null +++ b/src/api/layout/index.ts @@ -0,0 +1,120 @@ +import request from '@/utils/request'; +import type { ApiResult } from '@/api'; +import type { User } from '@/api/system/user/model'; +import type { UpdatePasswordParam, NoticeResult } from './model'; + +/** + * 获取当前登录的用户信息、菜单、权限、角色 + */ +export async function getUserInfo(): Promise<User> { + const res = await request.get<ApiResult<User>>('/auth/user'); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改当前登录的用户密码 + */ +export async function updatePassword( + data: UpdatePasswordParam +): Promise<string> { + const res = await request.put<ApiResult<unknown>>('/auth/password', data); + if (res.data.code === 0) { + return res.data.message ?? '修改成功'; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询未读通知 + */ +export async function getUnreadNotice(): Promise<NoticeResult> { + return { + notice: [ + { + color: '#60B2FC', + icon: 'NotificationFilled', + title: '你收到了一封14份新周报', + time: '2020-07-27 18:30:18' + }, + { + color: '#F5686F', + icon: 'PushpinFilled', + title: '许经理同意了你的请假申请', + time: '2020-07-27 09:08:36' + }, + { + color: '#7CD734', + icon: 'VideoCameraFilled', + title: '陈总邀请你参加视频会议', + time: '2020-07-26 18:30:01' + }, + { + color: '#FAAD14', + icon: 'CarryOutFilled', + title: '你推荐的刘诗雨已通过第三轮面试', + time: '2020-07-25 16:38:46' + }, + { + color: '#2BCACD', + icon: 'BellFilled', + title: '你的6月加班奖金已发放', + time: '2020-07-25 11:03:31' + } + ], + letter: [ + { + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg', + title: 'SunSmile 评论了你的日志', + content: '写的不错, 以后多多向你学习~', + time: '2020-07-27 18:30:18' + }, + { + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg', + title: '刘诗雨 点赞了你的日志', + content: '写的不错, 以后多多向你学习~', + time: '2020-07-27 09:08:36' + }, + { + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg', + title: '酷酷的大叔 评论了你的周报', + content: '写的不错, 以后多多向你学习~', + time: '2020-07-26 18:30:01' + }, + { + avatar: + 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg', + title: 'Jasmine 点赞了你的周报', + content: '写的不错, 以后多多向你学习~', + time: '2020-07-25 11:03:31' + } + ], + todo: [ + { + status: 0, + title: '刘诗雨的请假审批', + description: '刘诗雨在 07-27 18:30 提交的请假申请' + }, + { + status: 1, + title: '第三方代码紧急变更', + description: '需要在 2020-07-27 之前完成' + }, + { + status: 2, + title: '信息安全考试', + description: '需要在 2020-07-26 18:30 前完成' + }, + { + status: 2, + title: 'EleAdmin发布新版本', + description: '需要在 2020-07-25 11:03 前完成' + } + ] + }; +} diff --git a/src/api/layout/model/index.ts b/src/api/layout/model/index.ts new file mode 100644 index 0000000..c0bdbf8 --- /dev/null +++ b/src/api/layout/model/index.ts @@ -0,0 +1,58 @@ +/** + * 修改密码参数 + */ +export interface UpdatePasswordParam { + // 新密码 + password: string; + // 原始密码 + oldPassword: string; +} + +/** + * 通知数据格式 + */ +export interface NoticeModel { + // 图标颜色 + color?: string; + // 图标 + icon?: string; + // 标题 + title?: string; + // 时间 + time?: string; +} + +/** + * 私信数据格式 + */ +export interface LetterModel { + // 头像 + avatar?: string; + // 标题 + title?: string; + // 内容 + content?: string; + // 时间 + time?: string; +} + +/** + * 代办数据格式 + */ +export interface TodoModel { + // 状态 + status?: number; + // 标题 + title?: string; + // 描述 + description?: string; +} + +/** + * 查询未读通知返回结果 + */ +export interface NoticeResult { + notice: NoticeModel[]; + letter: LetterModel[]; + todo: TodoModel[]; +} diff --git a/src/api/login/index.ts b/src/api/login/index.ts new file mode 100644 index 0000000..079a4f8 --- /dev/null +++ b/src/api/login/index.ts @@ -0,0 +1,28 @@ +import request from '@/utils/request'; +import { setToken } from '@/utils/token-util'; +import type { ApiResult } from '@/api'; +import type { LoginParam, LoginResult, CaptchaResult } from './model'; + +/** + * 登录 + */ +export async function login(data: LoginParam) { + data.tenantId = 2; // 租户id + const res = await request.post<ApiResult<LoginResult>>('/login', data); + if (res.data.code === 0) { + setToken(res.data.data?.access_token, data.remember); + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 获取验证码 + */ +export async function getCaptcha() { + const res = await request.get<ApiResult<CaptchaResult>>('/captcha'); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/login/model/index.ts b/src/api/login/model/index.ts new file mode 100644 index 0000000..521f562 --- /dev/null +++ b/src/api/login/model/index.ts @@ -0,0 +1,36 @@ +import type { User } from '../../system/user/model'; +/** + * 登录参数 + */ +export interface LoginParam { + // 账号 + username?: string; + // 密码 + password?: string; + // 租户id + tenantId?: number; + // 是否记住密码 + remember?: boolean; +} + +/** + * 登录返回结果 + */ +export interface LoginResult { + // token + access_token?: string; + // 用户信息 + user?: User; +} + +/** + * 图形验证码返回结果 + */ +export interface CaptchaResult { + // 图形验证码base64数据 + base64: string; + // 验证码文本 + text: string; + + key: string; +} diff --git a/src/api/meeting/index.ts b/src/api/meeting/index.ts new file mode 100644 index 0000000..425a247 --- /dev/null +++ b/src/api/meeting/index.ts @@ -0,0 +1,84 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { Meeting, User, MeetingParam, UserParam } from './model'; + +/** + * 分页查询角色 + */ +export async function pageMeeting(params: MeetingParam) { + const res = await request.get<ApiResult<PageResult<Meeting>>>( + '/sign/meeting/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询用户列表 + */ +export async function listUsers(params?: UserParam) { + const res = await request.get<ApiResult<User[]>>('/sign/users', { + params + }); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加 + */ +export async function addMeeting(data: Meeting) { + const res = await request.post<ApiResult<unknown>>('/sign/meeting', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改 + */ +export async function updateMeeting(data: Meeting) { + const res = await request.put<ApiResult<unknown>>('/sign/meeting', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除 + */ +export async function removeMeeting(id?: number) { + const res = await request.delete<ApiResult<unknown>>('/sign/meeting/' + id); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 批量删除角色 + */ +export async function removeMeetingBatch(data: (number | undefined)[]) { + const res = await request.delete<ApiResult<unknown>>('/sign/meeting/batch', { + data + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +export async function updateSignUser(data: User) { + const res = await request.put<ApiResult<unknown>>('/sign/userSeat', data); + if (res.data.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/meeting/model/index.ts b/src/api/meeting/model/index.ts new file mode 100644 index 0000000..1a0e3a8 --- /dev/null +++ b/src/api/meeting/model/index.ts @@ -0,0 +1,48 @@ +import type { PageParam } from '@/api'; + +/** + * 会议 + */ +export interface Meeting { + id?: number; + title?: string; + room?: string; + mode?: number; + image?: string; + location?: string; + meeting_time?: string; + entry_time?: [string, string]; + sign_time?: [string, string]; + status?: number; + content?: string; + // 创建时间 + createTime?: string; +} + +export interface User { + meetingId?: number; + name?: string; + company?: string; + position?: string; + phone?: string; + isSign?: string; + entry_time?: string; + sign_time?: string; + openid?: string; + row?: number; + column?: number; +} +/** + * 角色搜索条件 + */ +export interface MeetingParam extends PageParam { + title?: string; + room?: string; + content?: string; +} + +export interface UserParam extends PageParam { + meetingId?: string | number; + name?: string; + company?: string; +} diff --git a/src/api/setting/index.ts b/src/api/setting/index.ts new file mode 100644 index 0000000..b526ec5 --- /dev/null +++ b/src/api/setting/index.ts @@ -0,0 +1,51 @@ +import request from '@/utils/request'; +import { ApiResult } from '@/api'; + +/** + * 获取回显数据 + */ +export async function getConfig() { + const res = await request.get<ApiResult<any>>('/setting/get'); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 提交表单 + * @param data + */ +export async function submitForm(data: any) { + const res = await request.post<ApiResult<unknown>>('/setting/update', data); + if (res.data.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 上传资料 + * @param data + */ +export async function upload(data: any) { + const res = await request.post<ApiResult<any>>('/setting/upload', data); + if (res.data.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 操作 + * @param field + */ +export async function clearData(field) { + const res = await request.get<ApiResult<any>>('clear-cache', { + params: { field } + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/setting/model/index.ts b/src/api/setting/model/index.ts new file mode 100644 index 0000000..cdf1f01 --- /dev/null +++ b/src/api/setting/model/index.ts @@ -0,0 +1,25 @@ +export interface SiteForm { + type?: string; + urlPre?: string; + url?: string; + icon?: string; + logo?: string; + siteName?: string; + keywords?: string; + description?: string; + icp?: string; + beian?: string; + key?: string +} +export interface FileForm { + type?: string; + file_path?: string; + domain?: string; + bucket?: string; + access_key?: string; + secret_key?: string; +} +export interface WeChatForm { + appid?: string; + appsecret?: string; +} diff --git a/src/api/system/dictionary-data/index.ts b/src/api/system/dictionary-data/index.ts new file mode 100644 index 0000000..30a411c --- /dev/null +++ b/src/api/system/dictionary-data/index.ts @@ -0,0 +1,86 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { DictionaryData, DictionaryDataParam } from './model'; + +/** + * 分页查询字典数据 + */ +export async function pageDictionaryData(params: DictionaryDataParam) { + const res = await request.get<ApiResult<PageResult<DictionaryData>>>( + '/system/dictionary-data/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询字典数据列表 + */ +export async function listDictionaryData(params: DictionaryDataParam) { + const res = await request.get<ApiResult<DictionaryData[]>>( + '/system/dictionary-data', + { params } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加字典数据 + */ +export async function addDictionaryData(data: DictionaryData) { + const res = await request.post<ApiResult<unknown>>( + '/system/dictionary-data', + data + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改字典数据 + */ +export async function updateDictionaryData(data: DictionaryData) { + const res = await request.put<ApiResult<unknown>>( + '/system/dictionary-data', + data + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除字典数据 + */ +export async function removeDictionaryData(id?: number) { + const res = await request.delete<ApiResult<unknown>>( + '/system/dictionary-data/' + id + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 批量删除字典数据 + */ +export async function removeDictionaryDataBatch(data: (number | undefined)[]) { + const res = await request.delete<ApiResult<unknown>>( + '/system/dictionary-data/batch', + { data } + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/dictionary-data/model/index.ts b/src/api/system/dictionary-data/model/index.ts new file mode 100644 index 0000000..e1301bd --- /dev/null +++ b/src/api/system/dictionary-data/model/index.ts @@ -0,0 +1,33 @@ +import { PageParam } from '@/api'; + +/** + * 字典数据 + */ +export interface DictionaryData { + // 字典数据id + dictDataId?: number; + // 字典id + dictId?: number; + // 字典数据标识 + dictDataCode?: string; + // 字典数据名称 + dictDataName?: string; + // 排序号 + sortNumber?: string; + // 备注 + comments?: string; + // 创建时间 + createTime?: string; +} + +/** + * 字典数据搜索条件 + */ +export interface DictionaryDataParam extends PageParam { + // 关键字 + keywords?: string; + // 字典标识 + dictCode?: string; + // 字典id + dictId?: number; +} diff --git a/src/api/system/dictionary/index.ts b/src/api/system/dictionary/index.ts new file mode 100644 index 0000000..17daa8e --- /dev/null +++ b/src/api/system/dictionary/index.ts @@ -0,0 +1,54 @@ +import request from '@/utils/request'; +import type { ApiResult } from '@/api'; +import type { Dictionary, DictionaryParam } from './model'; + +/** + * 查询字典列表 + */ +export async function listDictionaries(params?: DictionaryParam) { + const res = await request.get<ApiResult<Dictionary[]>>('/system/dictionary', { + params + }); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加字典 + */ +export async function addDictionary(data: Dictionary) { + const res = await request.post<ApiResult<unknown>>( + '/system/dictionary', + data + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改字典 + */ +export async function updateDictionary(data: Dictionary) { + const res = await request.put<ApiResult<unknown>>('/system/dictionary', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除字典 + */ +export async function removeDictionary(id?: number) { + const res = await request.delete<ApiResult<unknown>>( + '/system/dictionary/' + id + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/dictionary/model/index.ts b/src/api/system/dictionary/model/index.ts new file mode 100644 index 0000000..2b30009 --- /dev/null +++ b/src/api/system/dictionary/model/index.ts @@ -0,0 +1,25 @@ +/** + * 字典 + */ +export interface Dictionary { + // 字典id + dictId?: number; + // 字典标识 + dictCode?: string; + // 字典名称 + dictName?: string; + // 排序号 + sortNumber?: number; + // 备注 + comments?: string; + // 创建时间 + createTime?: string; +} + +/** + * 字典搜索条件 + */ +export interface DictionaryParam { + dictCode?: string; + dictName?: string; +} diff --git a/src/api/system/file/index.ts b/src/api/system/file/index.ts new file mode 100644 index 0000000..d624cf9 --- /dev/null +++ b/src/api/system/file/index.ts @@ -0,0 +1,78 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { FileRecord, FileRecordParam } from './model'; + +/** + * 上传文件 + */ +export async function uploadFile(file: File) { + const formData = new FormData(); + formData.append('file', file); + const res = await request.post<ApiResult<FileRecord>>( + '/file/upload', + formData + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 上传 base64 文件 + * @param base64 文件数据 + * @param fileName 文件名称 + */ +export async function uploadBase64File(base64: string, fileName?: string) { + const formData = new FormData(); + formData.append('base64', base64); + if (fileName) { + formData.append('fileName', fileName); + } + const res = await request.post<ApiResult<FileRecord>>( + '/file/upload/base64', + formData + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 分页查询文件上传记录 + */ +export async function pageFiles(params: FileRecordParam) { + const res = await request.get<ApiResult<PageResult<FileRecord>>>( + '/file/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除文件 + */ +export async function removeFile(id?: number) { + const res = await request.delete<ApiResult<unknown>>('/file/remove/' + id); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 批量删除文件 + */ +export async function removeFiles(data: (number | undefined)[]) { + const res = await request.delete<ApiResult<unknown>>('/file/remove/batch', { + data + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/file/model/index.ts b/src/api/system/file/model/index.ts new file mode 100644 index 0000000..32f4eb0 --- /dev/null +++ b/src/api/system/file/model/index.ts @@ -0,0 +1,40 @@ +import { PageParam } from '@/api'; + +/** + * 文件上传记录 + */ +export interface FileRecord { + // id + id: number; + // 文件名称 + name?: string; + // 文件存储路径 + path?: string; + // 文件大小 + length?: number; + // 文件类型 + contentType?: string; + // 上传人id + createUserId?: number; + // 上传时间 + createTime?: string; + // 文件访问地址 + url?: string; + // 文件缩略图访问地址 + thumbnail?: string; + // 文件下载地址 + downloadUrl?: string; + // 上传人账号 + createUsername?: string; + // 上传人名称 + createNickname?: string; +} + +/** + * 文件上传记录查询参数 + */ +export interface FileRecordParam extends PageParam { + name?: string; + path?: string; + createNickname?: string; +} diff --git a/src/api/system/login-record/index.ts b/src/api/system/login-record/index.ts new file mode 100644 index 0000000..ca76fd7 --- /dev/null +++ b/src/api/system/login-record/index.ts @@ -0,0 +1,31 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { LoginRecord, LoginRecordParam } from './model'; + +/** + * 分页查询登录日志 + */ +export async function pageLoginRecords(params: LoginRecordParam) { + const res = await request.get<ApiResult<PageResult<LoginRecord>>>( + '/system/login-record/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询登录日志列表 + */ +export async function listLoginRecords(params?: LoginRecordParam) { + const res = await request.get<ApiResult<LoginRecord[]>>( + '/system/login-record', + { params } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/login-record/model/index.ts b/src/api/system/login-record/model/index.ts new file mode 100644 index 0000000..0836eb5 --- /dev/null +++ b/src/api/system/login-record/model/index.ts @@ -0,0 +1,38 @@ +import { PageParam } from '@/api'; + +/** + * 登录日志 + */ +export interface LoginRecord { + // 登录日志id + id: number; + // 用户账号 + username: string; + // 操作系统 + os: string; + // 设备名称 + device: string; + // 浏览器类型 + browser: string; + // ip地址 + ip: string; + // 操作类型, 0登录成功, 1登录失败, 2退出登录, 3续签token + loginType: number; + // 备注 + comments: string; + // 操作时间 + createTime: string; + // 用户昵称 + nickname: string; +} + +/** + * 登录日志搜索条件 + */ +export interface LoginRecordParam extends PageParam { + username?: string; + nickname?: string; + createTimeStart?: string; + createTimeEnd?: string; + loginType?: number; +} diff --git a/src/api/system/menu/index.ts b/src/api/system/menu/index.ts new file mode 100644 index 0000000..6772e15 --- /dev/null +++ b/src/api/system/menu/index.ts @@ -0,0 +1,49 @@ +import request from '@/utils/request'; +import type { ApiResult } from '@/api'; +import type { Menu, MenuParam } from './model'; + +/** + * 查询菜单列表 + */ +export async function listMenus(params: MenuParam) { + const res = await request.get<ApiResult<Menu[]>>('/system/menu', { + params + }); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加菜单 + */ +export async function addMenu(data: Menu) { + const res = await request.post<ApiResult<unknown>>('/system/menu', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改菜单 + */ +export async function updateMenu(data: Menu) { + const res = await request.put<ApiResult<unknown>>('/system/menu', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除菜单 + */ +export async function removeMenu(id?: number) { + const res = await request.delete<ApiResult<unknown>>('/system/menu/' + id); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/menu/model/index.ts b/src/api/system/menu/model/index.ts new file mode 100644 index 0000000..dc43164 --- /dev/null +++ b/src/api/system/menu/model/index.ts @@ -0,0 +1,51 @@ +/** + * 菜单 + */ +export interface Menu { + // 菜单id + menuId?: number; + // 上级id, 0是顶级 + parentId?: number; + // 菜单名称 + title: string; + // 菜单路由地址 + path: string; + // 菜单组件地址 + component: string; + // 菜单类型, 0菜单, 1按钮 + menuType?: number; + // 排序号 + sortNumber?: number; + // 权限标识 + authority?: string; + // 菜单图标 + icon?: string; + // 是否隐藏, 0否,1是(仅注册路由不显示左侧菜单) + hide?: number; + // 路由元信息 + meta?: string; + // 创建时间 + createTime?: string; + // 子菜单 + children?: Menu[]; + // 权限树回显选中状态, 0未选中, 1选中 + checked?: boolean; + // + key?: number; + // + value?: number; + // + parentIds?: number[]; + // + openType?: number; +} + +/** + * 菜单搜索参数 + */ +export interface MenuParam { + title?: string; + path?: string; + authority?: string; + parentId?: number; +} diff --git a/src/api/system/operation-record/index.ts b/src/api/system/operation-record/index.ts new file mode 100644 index 0000000..7fb288e --- /dev/null +++ b/src/api/system/operation-record/index.ts @@ -0,0 +1,31 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { OperationRecord, OperationRecordParam } from './model'; + +/** + * 分页查询操作日志 + */ +export async function pageOperationRecords(params: OperationRecordParam) { + const res = await request.get<ApiResult<PageResult<OperationRecord>>>( + '/system/operation-record/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询操作日志列表 + */ +export async function listOperationRecords(params?: OperationRecordParam) { + const res = await request.get<ApiResult<OperationRecord[]>>( + '/system/operation-record', + { params } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/operation-record/model/index.ts b/src/api/system/operation-record/model/index.ts new file mode 100644 index 0000000..fc1425e --- /dev/null +++ b/src/api/system/operation-record/model/index.ts @@ -0,0 +1,56 @@ +import { PageParam } from '@/api'; + +/** + * 操作日志 + */ +export interface OperationRecord { + // 操作日志id + id?: number; + // 用户id + userId?: number; + // 操作模块 + module: string; + // 操作功能 + description: string; + // 请求地址 + url: string; + // 请求方式 + requestMethod: string; + // 调用方法 + method: string; + // 请求参数 + params: string; + // 返回结果 + result: string; + // 异常信息 + error: string; + // 消耗时间, 单位毫秒 + spendTime: number; + // 操作系统 + os: string; + // 设备名称 + device: string; + // 浏览器类型 + browser: string; + // ip地址 + ip: string; + // 状态, 0成功, 1异常 + status: number; + // 操作时间 + createTime: string; + // 用户昵称 + nickname: string; + // 用户账号 + username: string; +} + +/** + * 操作日志搜索条件 + */ +export interface OperationRecordParam extends PageParam { + username?: string; + module?: string; + createTimeStart?: string; + createTimeEnd?: string; + status?: number; +} diff --git a/src/api/system/organization/index.ts b/src/api/system/organization/index.ts new file mode 100644 index 0000000..bf374b7 --- /dev/null +++ b/src/api/system/organization/index.ts @@ -0,0 +1,72 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { Organization, OrganizationParam } from './model'; + +/** + * 分页查询机构 + */ +export async function pageOrganizations(params: OrganizationParam) { + const res = await request.get<ApiResult<PageResult<Organization>>>( + '/system/organization/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询机构列表 + */ +export async function listOrganizations(params?: OrganizationParam) { + const res = await request.get<ApiResult<Organization[]>>( + '/system/organization', + { params } + ); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加机构 + */ +export async function addOrganization(data: Organization) { + const res = await request.post<ApiResult<unknown>>( + '/system/organization', + data + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改机构 + */ +export async function updateOrganization(data: Organization) { + const res = await request.put<ApiResult<unknown>>( + '/system/organization', + data + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除机构 + */ +export async function removeOrganization(id?: number) { + const res = await request.delete<ApiResult<unknown>>( + '/system/organization/' + id + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/organization/model/index.ts b/src/api/system/organization/model/index.ts new file mode 100644 index 0000000..8a5a72a --- /dev/null +++ b/src/api/system/organization/model/index.ts @@ -0,0 +1,40 @@ +import { PageParam } from '@/api'; + +/** + * 机构 + */ +export interface Organization { + // 机构id + organizationId?: number; + // 上级id, 0是顶级 + parentId?: number; + // 机构名称 + organizationName?: string; + // 机构全称 + organizationFullName?: string; + // 机构代码 + organizationCode?: string; + // 机构类型(字典) + organizationType?: string; + // 排序号 + sortNumber?: number; + // 备注 + comments?: string; + // 创建时间 + createTime?: string; + // 机构类型名称 + organizationTypeName?: string; + // + key?: number; + // + value?: number; + // + title?: string; +} + +/** + * 机构搜索条件 + */ +export interface OrganizationParam extends PageParam { + organizationName?: string; +} diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts new file mode 100644 index 0000000..9a89031 --- /dev/null +++ b/src/api/system/role/index.ts @@ -0,0 +1,104 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { Role, RoleParam } from './model'; +import type { Menu } from '../menu/model'; + +/** + * 分页查询角色 + */ +export async function pageRoles(params: RoleParam) { + const res = await request.get<ApiResult<PageResult<Role>>>( + '/system/role/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询角色列表 + */ +export async function listRoles(params?: RoleParam) { + const res = await request.get<ApiResult<Role[]>>('/system/role', { + params + }); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加角色 + */ +export async function addRole(data: Role) { + const res = await request.post<ApiResult<unknown>>('/system/role', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改角色 + */ +export async function updateRole(data: Role) { + const res = await request.put<ApiResult<unknown>>('/system/role', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除角色 + */ +export async function removeRole(id?: number) { + const res = await request.delete<ApiResult<unknown>>('/system/role/' + id); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 批量删除角色 + */ +export async function removeRoles(data: (number | undefined)[]) { + const res = await request.delete<ApiResult<unknown>>('/system/role/batch', { + data + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 获取角色分配的菜单 + */ +export async function listRoleMenus(roleId?: number) { + const res = await request.get<ApiResult<Menu[]>>( + '/system/role-menu/' + roleId + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改角色菜单 + */ +export async function updateRoleMenus(roleId?: number, data?: number[]) { + const res = await request.put<ApiResult<unknown>>( + '/system/role-menu/' + roleId, + data + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/role/model/index.ts b/src/api/system/role/model/index.ts new file mode 100644 index 0000000..1a81787 --- /dev/null +++ b/src/api/system/role/model/index.ts @@ -0,0 +1,26 @@ +import type { PageParam } from '@/api'; + +/** + * 角色 + */ +export interface Role { + // 角色id + roleId?: number; + // 角色标识 + roleCode?: string; + // 角色名称 + roleName?: string; + // 备注 + comments?: string; + // 创建时间 + createTime?: string; +} + +/** + * 角色搜索条件 + */ +export interface RoleParam extends PageParam { + roleName?: string; + roleCode?: string; + comments?: string; +} diff --git a/src/api/system/user-file/index.ts b/src/api/system/user-file/index.ts new file mode 100644 index 0000000..1187f92 --- /dev/null +++ b/src/api/system/user-file/index.ts @@ -0,0 +1,79 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { UserFile, UserFileParam } from './model'; + +/** + * 分页查询用户文件 + */ +export async function pageUserFiles(params: UserFileParam) { + const res = await request.get<ApiResult<PageResult<UserFile>>>( + '/system/user-file/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询用户文件列表 + */ +export async function listUserFiles(params: UserFileParam) { + const res = await request.get<ApiResult<UserFile[]>>('/system/user-file', { + params + }); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加用户文件 + */ +export async function addUserFile(data: UserFile) { + const res = await request.post<ApiResult<unknown>>('/system/user-file', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改用户文件 + */ +export async function updateUserFile(data: UserFile) { + const res = await request.put<ApiResult<unknown>>('/system/user-file', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除用户文件 + */ +export async function removeUserFile(id?: number) { + const res = await request.delete<ApiResult<unknown>>( + '/system/user-file/' + id + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 批量删除用户文件 + */ +export async function removeUserFiles(data: (number | undefined)[]) { + const res = await request.delete<ApiResult<unknown>>( + '/system/user-file/batch', + { data } + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/user-file/model/index.ts b/src/api/system/user-file/model/index.ts new file mode 100644 index 0000000..ca51256 --- /dev/null +++ b/src/api/system/user-file/model/index.ts @@ -0,0 +1,39 @@ +import { PageParam } from '@/api'; + +/** + * 用户文件 + */ +export interface UserFile { + // id + id?: number; + // 用户id + userId?: number; + // 文件名称 + name?: string; + // 是否是文件夹, 0否, 1是 + isDirectory?: number; + // 上级id + parentId?: number; + // 文件存储路径 + path?: string; + // 文件大小 + length?: number; + // 文件类型 + contentType?: string; + // 上传时间 + createTime?: string; + // 文件访问地址 + url?: string; + // 文件缩略图访问地址 + thumbnail?: string; + // 文件下载地址 + downloadUrl?: string; +} + +/** + * 用户文件查询参数 + */ +export interface UserFileParam extends PageParam { + name?: string; + parentId?: number; +} diff --git a/src/api/system/user/index.ts b/src/api/system/user/index.ts new file mode 100644 index 0000000..c5f594e --- /dev/null +++ b/src/api/system/user/index.ts @@ -0,0 +1,148 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api'; +import type { User, UserParam } from './model'; + +/** + * 分页查询用户 + */ +export async function pageUsers(params: UserParam) { + const res = await request.get<ApiResult<PageResult<User>>>( + '/system/user/page', + { params } + ); + if (res.data.code === 0) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 查询用户列表 + */ +export async function listUsers(params?: UserParam) { + const res = await request.get<ApiResult<User[]>>('/system/user', { + params + }); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 根据id查询用户 + */ +export async function getUser(id: number) { + const res = await request.get<ApiResult<User>>('/system/user/' + id); + if (res.data.code === 0 && res.data.data) { + return res.data.data; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 添加用户 + */ +export async function addUser(data: User) { + const res = await request.post<ApiResult<unknown>>('/system/user', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改用户 + */ +export async function updateUser(data: User) { + const res = await request.put<ApiResult<unknown>>('/system/user', data); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 删除用户 + */ +export async function removeUser(id?: number) { + const res = await request.delete<ApiResult<unknown>>('/system/user/' + id); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 批量删除用户 + */ +export async function removeUsers(data: (number | undefined)[]) { + const res = await request.delete<ApiResult<unknown>>('/system/user/batch', { + data + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 修改用户状态 + */ +export async function updateUserStatus(userId?: number, status?: number) { + const res = await request.put<ApiResult<unknown>>('/system/user/status', { + userId, + status + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 重置用户密码 + */ +export async function updateUserPassword(userId?: number, password = '123456') { + const res = await request.put<ApiResult<unknown>>('/system/user/password', { + userId, + password + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 导入用户 + */ +export async function importUsers(file: File) { + const formData = new FormData(); + formData.append('file', file); + const res = await request.post<ApiResult<unknown>>( + '/system/user/import', + formData + ); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} + +/** + * 检查用户是否存在 + */ +export async function checkExistence( + field: string, + value: string, + id?: number +) { + const res = await request.get<ApiResult<unknown>>('/system/user/existence', { + params: { field, value, id } + }); + if (res.data.code === 0) { + return res.data.message; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/system/user/model/index.ts b/src/api/system/user/model/index.ts new file mode 100644 index 0000000..b6058cd --- /dev/null +++ b/src/api/system/user/model/index.ts @@ -0,0 +1,56 @@ +import type { PageParam } from '@/api'; +import type { Role } from '../../role/model'; +import type { Menu } from '../../menu/model'; + +/** + * 用户 + */ +export interface User { + // 用户id + userId?: number; + // 账号 + username?: string; + // 密码 + password?: string; + // 昵称 + nickname?: string; + // 头像 + avatar?: string; + // 性别(字典) + sex?: string; + // 手机号 + phone?: string; + // 邮箱 + email?: string; + // 出生日期 + birthday?: string; + // 个人简介 + introduction?: string; + // 机构id + organizationId?: number; + // 状态, 0正常, 1冻结 + status?: number; + // 性别名称 + sexName?: string; + // 机构名称 + organizationName?: string; + // 角色列表 + roles?: Role[]; + // 权限列表 + authorities?: Menu[]; + // 创建时间 + createTime?: string; +} + +/** + * 用户搜索条件 + */ +export interface UserParam extends PageParam { + username?: string; + nickname?: string; + sex?: string; + phone?: string; + status?: number; + organizationId?: number; + sexName?: string; +} diff --git a/src/api/user/message/index.ts b/src/api/user/message/index.ts new file mode 100644 index 0000000..5a98ea5 --- /dev/null +++ b/src/api/user/message/index.ts @@ -0,0 +1,229 @@ +import type { PageResult } from '@/api'; +import type { Message } from './model'; + +/** + * 分页查询通知 + */ +export async function pageNotices(_params: any) { + const result: PageResult<Message> = { + count: 10, + list: [ + { + id: 21, + title: 'EleAdmin新版本发布,欢迎体验', + time: '2020-07-24 11:35', + status: 0 + }, + { + id: 22, + title: 'EleAdmin新版本发布,欢迎体验', + time: '2020-07-24 11:35', + status: 0 + }, + { + id: 23, + title: 'EleAdmin新版本发布,欢迎体验', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 24, + title: 'EleAdmin新版本发布,欢迎体验', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 25, + title: 'EleAdmin新版本发布,欢迎体验', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 26, + title: 'EleAdmin新版本发布,欢迎体验', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 27, + title: 'EleAdmin新版本发布,欢迎体验', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 28, + title: 'EleAdmin新版本发布,欢迎体验', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 29, + title: 'EleAdmin新版本发布,欢迎体验', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 30, + title: 'EleAdmin新版本发布,欢迎体验', + time: '2020-07-24 11:35', + status: 1 + } + ] + }; + return result; +} + +/** + * 分页查询私信 + */ +export async function pageLetters(_params: any) { + const result: PageResult<Message> = { + count: 10, + list: [ + { + id: 11, + title: 'Jasmine给你发来了一条私信', + time: '2020-07-24 11:35', + status: 0 + }, + { + id: 12, + title: 'Jasmine给你发来了一条私信', + time: '2020-07-24 11:35', + status: 0 + }, + { + id: 13, + title: 'Jasmine给你发来了一条私信', + time: '2020-07-24 11:35', + status: 0 + }, + { + id: 14, + title: 'Jasmine给你发来了一条私信', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 15, + title: 'Jasmine给你发来了一条私信', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 16, + title: 'Jasmine给你发来了一条私信', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 17, + title: 'Jasmine给你发来了一条私信', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 18, + title: 'Jasmine给你发来了一条私信', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 19, + title: 'Jasmine给你发来了一条私信', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 20, + title: 'Jasmine给你发来了一条私信', + time: '2020-07-24 11:35', + status: 1 + } + ] + }; + return result; +} + +/** + * 分页查询代办 + */ +export async function pageTodos(_params: any) { + const result: PageResult<Message> = { + count: 10, + list: [ + { + id: 1, + title: '你有两条任务待完成,不要忘了哦~', + time: '2020-07-24 11:35', + status: 0 + }, + { + id: 2, + title: '你有两条任务待完成,不要忘了哦~', + time: '2020-07-24 11:35', + status: 0 + }, + { + id: 3, + title: '你有两条任务待完成,不要忘了哦~', + time: '2020-07-24 11:35', + status: 0 + }, + { + id: 4, + title: '你有两条任务待完成,不要忘了哦~', + time: '2020-07-24 11:35', + status: 0 + }, + { + id: 5, + title: '你有两条任务待完成,不要忘了哦~', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 6, + title: '你有两条任务待完成,不要忘了哦~', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 7, + title: '你有两条任务待完成,不要忘了哦~', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 8, + title: '你有两条任务待完成,不要忘了哦~', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 9, + title: '你有两条任务待完成,不要忘了哦~', + time: '2020-07-24 11:35', + status: 1 + }, + { + id: 10, + title: '你有两条任务待完成,不要忘了哦~', + time: '2020-07-24 11:35', + status: 1 + } + ] + }; + return result; +} + +/** + * 查询未读数量 + */ +export async function getUnReadNum() { + return { + notice: 2, + letter: 3, + todo: 4 + }; +} diff --git a/src/api/user/message/model/index.ts b/src/api/user/message/model/index.ts new file mode 100644 index 0000000..1bed997 --- /dev/null +++ b/src/api/user/message/model/index.ts @@ -0,0 +1,13 @@ +/** + * 消息 + */ +export interface Message { + // 消息id + id?: number; + // 标题 + title?: string; + // 时间 + time?: string; + // 状态 + status?: number; +} diff --git a/src/api/user/profile/index.ts b/src/api/user/profile/index.ts new file mode 100644 index 0000000..71f84ae --- /dev/null +++ b/src/api/user/profile/index.ts @@ -0,0 +1,18 @@ +import request from '@/utils/request'; +import { ApiResult } from '@/api'; + +export async function uploadAvatar(data) { + const res = await request.post<ApiResult<unknown>>('/file/upload', data); + if (res.data.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.data.message)); +} + +export async function updateUser(data) { + const res = await request.put<ApiResult<unknown>>('/system/user', data); + if (res.data.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.data.message)); +} diff --git a/src/api/user/profile/model/index.ts b/src/api/user/profile/model/index.ts new file mode 100644 index 0000000..c1dc66f --- /dev/null +++ b/src/api/user/profile/model/index.ts @@ -0,0 +1,9 @@ +export interface ProfileForm { + userId?: number; + avatar?: string; + email?: string; + introduction?: string; + nickname?: string; + phone?: string; + sex?: number | string; +} diff --git a/src/api/wechat/index.ts b/src/api/wechat/index.ts new file mode 100644 index 0000000..5eb77f0 --- /dev/null +++ b/src/api/wechat/index.ts @@ -0,0 +1,46 @@ +import request from "@/utils/request"; + +/** + * 获取微信菜单 + */ +export async function getWxMenu() { + const res = await request.get<any>('wechat/getWxMenu'); + if (!res.data.errcode) { + return res.data; + } + return Promise.reject(new Error(res.data.errmsg)); +} + +/** + * 获取永久素材 + */ +export async function getWxMaterial(id) { + const res = await request.get<any>('wechat/getMaterial', id); + if (!res.data.errcode) { + return res.data; + } + return Promise.reject(new Error(res.data.errmsg)); +} + +/** + * 永久素材列表 + * @param params + */ +export async function getWxMaterialList(params) { + const res = await request.get<any>('wechat/getMaterialList', { params }); + if (!res.data.errcode) { + return res.data; + } + return Promise.reject(new Error(res.data.errmsg)); +} + +/** + * 创建自定义菜单 + */ +export async function addWxMenu(params) { + const res = await request.post<any>('wechat/addWxMenu', params); + if (!res.data.errcode) { + return res.data; + } + return Promise.reject(new Error(res.data.errmsg)); +} diff --git a/src/api/wechat/model/index.ts b/src/api/wechat/model/index.ts new file mode 100644 index 0000000..153c236 --- /dev/null +++ b/src/api/wechat/model/index.ts @@ -0,0 +1,11 @@ +export interface WxMenuForm { + button: any; + name?: string; + type?: string; + value?: string; + url?: string; + key?: string; + pagepath?: string; + sub_button?: WxMenuForm; +} + diff --git a/src/assets/base.png b/src/assets/base.png new file mode 100644 index 0000000..1aa53a0 Binary files /dev/null and b/src/assets/base.png differ diff --git a/src/assets/bg-login.jpg b/src/assets/bg-login.jpg new file mode 100644 index 0000000..26baa69 Binary files /dev/null and b/src/assets/bg-login.jpg differ diff --git a/src/assets/logo.svg b/src/assets/logo.svg new file mode 100644 index 0000000..3359d58 --- /dev/null +++ b/src/assets/logo.svg @@ -0,0 +1,24 @@ +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="280" height="280" viewBox="-18.75 0 280 280"> + <defs xmlns="http://www.w3.org/2000/svg"> + <linearGradient x1="20%" y1="0%" x2="100%" y2="80%" id="linearGradient-1"> + <stop stop-color="#4285EB" offset="0%"/> + <stop stop-color="#2EC7FF" offset="100%"/> + </linearGradient> + <linearGradient x1="60%" y1="0%" x2="50%" y2="120%" id="linearGradient-2"> + <stop stop-color="#29CDFF" offset="0%"/> + <stop stop-color="#148EFF" offset="60%"/> + <stop stop-color="#0A60FF" offset="100%"/> + </linearGradient> + <linearGradient x1="120%" y1="60%" x2="20%" y2="40%" id="linearGradient-3"> + <stop stop-color="#FA816E" offset="0%"/> + <stop stop-color="#F74A5C" offset="60%"/> + <stop stop-color="#F51D2C" offset="100%"/> + </linearGradient> + </defs> + <path d="M121.2435565 0 L242.4871131 70 L242.4871131 130 L121.2435565 200 L121.2435565 160 L207.8460969 110 L207.8460969 90 L121.2435565 40 Z" + fill="url(#linearGradient-1)"/> + <path d="M242.4871131 210 L121.2435565 280 L0 210 L0 70 L121.2435565 0 L181.8653348 35 Q 155.5544457 23.5 121.2435565 40 L34.64101615 90 L34.64101615 190 L121.2435565 240 L242.4871131 170 Z" + fill="url(#linearGradient-2)"/> + <path d="M173.2050808 170 L121.2435565 200 L69.2820323 170 L69.2820323 110 L121.2435565 80 L155.8845727 100 L103.9230485 130 L103.9230485 150 L121.2435565 160 L173.2050808 130 Z" + fill="url(#linearGradient-3)"/> +</svg> diff --git a/src/assets/menu_foot.png b/src/assets/menu_foot.png new file mode 100644 index 0000000..4a89d4b Binary files /dev/null and b/src/assets/menu_foot.png differ diff --git a/src/assets/menu_head.png b/src/assets/menu_head.png new file mode 100644 index 0000000..248cfb7 Binary files /dev/null and b/src/assets/menu_head.png differ diff --git a/src/assets/msg_tab.png b/src/assets/msg_tab.png new file mode 100644 index 0000000..6a18531 Binary files /dev/null and b/src/assets/msg_tab.png differ diff --git a/src/assets/noimage.png b/src/assets/noimage.png new file mode 100644 index 0000000..29c1c92 Binary files /dev/null and b/src/assets/noimage.png differ diff --git a/src/components/ByteMdEditor/index.vue b/src/components/ByteMdEditor/index.vue new file mode 100644 index 0000000..5193689 --- /dev/null +++ b/src/components/ByteMdEditor/index.vue @@ -0,0 +1,109 @@ +<!-- markdown 编辑器 --> +<template> + <div ref="rootRef" class="ele-bytemd-wrap"></div> +</template> + +<script lang="ts" setup> + import { onMounted, ref, watch } from 'vue'; + import { Editor } from 'bytemd'; + import type { BytemdPlugin, BytemdLocale, ViewerProps } from 'bytemd'; + import 'bytemd/dist/index.min.css'; + + const props = withDefaults( + defineProps<{ + value: string; + plugins?: BytemdPlugin[]; + sanitize?: (schema: any) => any; + mode?: 'split' | 'tab' | 'auto'; + previewDebounce?: number; + placeholder?: string; + editorConfig?: Record<string, any>; + locale?: Partial<BytemdLocale>; + uploadImages?: ( + files: File[] + ) => Promise<Pick<any, 'url' | 'alt' | 'title'>[]>; + overridePreview?: (el: HTMLElement, props: ViewerProps) => void; + maxLength?: number; + height?: string; + fullZIndex?: number; + }>(), + { + fullZIndex: 999 + } + ); + + const emit = defineEmits<{ + (e: 'update:value', value?: string): void; + (e: 'change', value?: string): void; + }>(); + + const rootRef = ref<HTMLElement | null>(null); + const editor = ref<InstanceType<typeof Editor> | null>(null); + + onMounted(() => { + editor.value = new Editor({ + target: rootRef.value as HTMLElement, + props + }); + editor.value.$on('change', (e: any) => { + emit('update:value', e.detail.value); + emit('change', e.detail.value); + }); + }); + + watch( + [ + () => props.value, + () => props.plugins, + () => props.sanitize, + () => props.mode, + () => props.previewDebounce, + () => props.placeholder, + () => props.editorConfig, + () => props.locale, + () => props.uploadImages, + () => props.maxLength + ], + () => { + const option = { ...props }; + for (let key in option) { + if (typeof option[key] === 'undefined') { + delete option[key]; + } + } + editor.value?.$set(option); + } + ); +</script> + +<style lang="less" scoped> + // 修改编辑器高度 + .ele-bytemd-wrap :deep(.bytemd) { + height: v-bind(height); + + // 修改全屏的 zIndex + &.bytemd-fullscreen { + z-index: v-bind(fullZIndex); + } + + // 去掉默认的最大宽度限制 + .CodeMirror .CodeMirror-lines { + max-width: 100%; + } + + pre.CodeMirror-line, + pre.CodeMirror-line-like { + padding: 0 24px; + } + + .markdown-body { + max-width: 100%; + padding: 16px 24px; + } + + // 去掉 github 图标 + .bytemd-toolbar-right > .bytemd-toolbar-icon:last-child { + display: none; + } + } +</style> diff --git a/src/components/ByteMdViewer/index.vue b/src/components/ByteMdViewer/index.vue new file mode 100644 index 0000000..22e95a3 --- /dev/null +++ b/src/components/ByteMdViewer/index.vue @@ -0,0 +1,93 @@ +<!-- markdown 解析 --> +<template> + <!-- eslint-disable vue/no-v-html --> + <div + ref="rootRef" + v-html="content" + class="markdown-body" + @click="handleClick" + > + </div> +</template> + +<script lang="ts" setup> + import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue'; + import type { BytemdPlugin } from 'bytemd'; + import { getProcessor } from 'bytemd'; + + const props = defineProps<{ + value: string; + plugins?: BytemdPlugin[]; + sanitize?: (schema: any) => any; + }>(); + + const rootRef = ref<HTMLElement | null>(null); + const content = ref<any | null>(null); + const cbs = ref<(void | (() => void))[]>([]); + + const on = () => { + if (props.plugins && rootRef.value && content.value) { + cbs.value = props.plugins.map(({ viewerEffect }) => { + return ( + viewerEffect && + viewerEffect({ + markdownBody: rootRef.value as HTMLElement, + file: content.value + }) + ); + }); + } + }; + + const off = () => { + if (cbs.value) { + cbs.value.forEach((cb) => cb && cb()); + } + }; + + const handleClick = (e: MouseEvent) => { + const $ = e.target as HTMLElement; + if ($.tagName !== 'A') { + return; + } + + const href = $.getAttribute('href'); + if (!href || !href.startsWith('#')) { + return; + } + + const dest = rootRef.value?.querySelector('#user-content-' + href.slice(1)); + if (dest) { + dest.scrollIntoView(); + } + }; + + watch( + [() => props.value, () => props.plugins, () => props.sanitize], + () => { + try { + content.value = getProcessor({ + plugins: props.plugins, + sanitize: props.sanitize + }).processSync(props.value); + } catch (e) { + console.error(e); + } + off(); + nextTick(() => { + on(); + }); + }, + { + immediate: true + } + ); + + onMounted(() => { + on(); + }); + + onBeforeUnmount(() => { + off(); + }); +</script> diff --git a/src/components/RedirectLayout/index.ts b/src/components/RedirectLayout/index.ts new file mode 100644 index 0000000..6c4def6 --- /dev/null +++ b/src/components/RedirectLayout/index.ts @@ -0,0 +1,21 @@ +/** 用于刷新的路由组件 */ +import { defineComponent, unref, h } from 'vue'; +import { useRouter } from 'vue-router'; +import { setRouteReload } from '@/utils/page-tab-util'; + +export default defineComponent({ + name: 'RedirectLayout', + setup() { + const { currentRoute, replace } = useRouter(); + const { params, query } = unref(currentRoute); + const from = Array.isArray(params.path) + ? params.path.join('/') + : params.path; + const path = '/' + from; + setTimeout(() => { + setRouteReload(null); + replace({ path, query }); + }, 100); + return () => h('div'); + } +}); diff --git a/src/components/RegionsSelect/index.vue b/src/components/RegionsSelect/index.vue new file mode 100644 index 0000000..47fe28e --- /dev/null +++ b/src/components/RegionsSelect/index.vue @@ -0,0 +1,127 @@ +<!-- 省市区级联选择器 --> +<template> + <a-cascader + :value="value" + :options="regionsData" + :show-search="showSearch" + :placeholder="placeholder" + dropdown-class-name="ele-pop-wrap-higher" + @update:value="updateValue" + /> +</template> + +<script lang="ts" setup> + import { ref, watch } from 'vue'; + import type { ValueType } from 'ant-design-vue/es/vc-cascader/Cascader'; + import type { RegionsData } from './types'; + import { getRegionsData } from './load-data'; + + const props = withDefaults( + defineProps<{ + value?: string[]; + placeholder?: string; + options?: RegionsData[]; + valueField?: 'label'; + type?: 'provinceCity' | 'province'; + showSearch?: boolean; + }>(), + { + showSearch: true + } + ); + + const emit = defineEmits<{ + (e: 'update:value', value?: string[]): void; + (e: 'load-data-done', value: RegionsData[]): void; + }>(); + + // 级联选择器数据 + const regionsData = ref<RegionsData[]>([]); + + /* 更新 value */ + const updateValue = (value: ValueType) => { + emit('update:value', value as string[]); + }; + + /* 级联选择器数据 value 处理 */ + const formatData = (data: RegionsData[]) => { + if (props.valueField === 'label') { + return data.map((d) => { + const item: RegionsData = { + label: d.label, + value: d.label + }; + if (d.children) { + item.children = d.children.map((c) => { + const cItem: RegionsData = { + label: c.label, + value: c.label + }; + if (c.children) { + cItem.children = c.children.map((cc) => { + return { + label: cc.label, + value: cc.label + }; + }); + } + return cItem; + }); + } + return item; + }); + } else { + return data; + } + }; + + /* 省市区数据筛选 */ + const filterData = (data: RegionsData[]) => { + if (props.type === 'provinceCity') { + return formatData( + data.map((d) => { + const item: RegionsData = { + label: d.label, + value: d.value + }; + if (d.children) { + item.children = d.children.map((c) => { + return { + label: c.label, + value: c.value + }; + }); + } + return item; + }) + ); + } else if (props.type === 'province') { + return formatData( + data.map((d) => { + return { + label: d.label, + value: d.value + }; + }) + ); + } else { + return formatData(data); + } + }; + + watch( + () => props.options, + (options) => { + regionsData.value = filterData(options ?? []); + if (!options) { + getRegionsData().then((data) => { + regionsData.value = filterData(data ?? []); + emit('load-data-done', data); + }); + } + }, + { + immediate: true + } + ); +</script> diff --git a/src/components/RegionsSelect/load-data.ts b/src/components/RegionsSelect/load-data.ts new file mode 100644 index 0000000..bc7d756 --- /dev/null +++ b/src/components/RegionsSelect/load-data.ts @@ -0,0 +1,25 @@ +import request from '@/utils/request'; +import type { RegionsData } from './types'; +const BASE_URL = import.meta.env.BASE_URL; +let reqPromise: Promise<RegionsData[]>; + +/** + * 获取省市区数据 + */ +export function getRegionsData() { + if (!reqPromise) { + reqPromise = new Promise<RegionsData[]>((resolve, reject) => { + request + .get<RegionsData[]>(BASE_URL + 'json/regions-data.json', { + baseURL: '' + }) + .then((res) => { + resolve(res.data ?? []); + }) + .catch((e) => { + reject(e); + }); + }); + } + return reqPromise; +} diff --git a/src/components/RegionsSelect/types/index.ts b/src/components/RegionsSelect/types/index.ts new file mode 100644 index 0000000..ebf2eca --- /dev/null +++ b/src/components/RegionsSelect/types/index.ts @@ -0,0 +1,15 @@ +/** + * 省市区数据类型 + */ +export interface RegionsData { + label: string; + value: string; + children?: { + value: string; + label: string; + children?: { + value: string; + label: string; + }[]; + }[]; +} diff --git a/src/components/RouterLayout/index.vue b/src/components/RouterLayout/index.vue new file mode 100644 index 0000000..e304b5c --- /dev/null +++ b/src/components/RouterLayout/index.vue @@ -0,0 +1,26 @@ +<!-- router-view 结合 keep-alive 组件 --> +<template> + <router-view v-slot="{ Component }"> + <transition :name="transitionName" mode="out-in" appear> + <keep-alive :include="keepAliveInclude"> + <component :is="Component" /> + </keep-alive> + </transition> + </router-view> +</template> + +<script lang="ts"> + import { defineComponent } from 'vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + export default defineComponent({ + name: 'RouterLayout', + setup() { + const themeStore = useThemeStore(); + const { keepAliveInclude, transitionName } = storeToRefs(themeStore); + + return { keepAliveInclude, transitionName }; + } + }); +</script> diff --git a/src/components/TinymceEditor/index.vue b/src/components/TinymceEditor/index.vue new file mode 100644 index 0000000..aad1e50 --- /dev/null +++ b/src/components/TinymceEditor/index.vue @@ -0,0 +1,242 @@ +<!-- 富文本编辑器 --> +<template> + <component v-if="inlineEditor" :is="tagName" :id="elementId" /> + <textarea v-else :id="elementId"></textarea> +</template> + +<script lang="ts" setup> + import { + watch, + onMounted, + onBeforeUnmount, + onActivated, + onDeactivated, + nextTick, + useAttrs + } from 'vue'; + import tinymce from 'tinymce/tinymce'; + import type { + Editor as TinyMCEEditor, + EditorEvent, + RawEditorSettings + } from 'tinymce'; + import 'tinymce/themes/silver'; + import 'tinymce/icons/default'; + import 'tinymce/plugins/code'; + import 'tinymce/plugins/preview'; + import 'tinymce/plugins/fullscreen'; + import 'tinymce/plugins/paste'; + import 'tinymce/plugins/searchreplace'; + import 'tinymce/plugins/save'; + import 'tinymce/plugins/autosave'; + import 'tinymce/plugins/link'; + import 'tinymce/plugins/autolink'; + import 'tinymce/plugins/image'; + import 'tinymce/plugins/media'; + import 'tinymce/plugins/table'; + import 'tinymce/plugins/codesample'; + import 'tinymce/plugins/lists'; + import 'tinymce/plugins/advlist'; + import 'tinymce/plugins/hr'; + import 'tinymce/plugins/charmap'; + import 'tinymce/plugins/emoticons'; + import 'tinymce/plugins/anchor'; + import 'tinymce/plugins/directionality'; + import 'tinymce/plugins/pagebreak'; + import 'tinymce/plugins/quickbars'; + import 'tinymce/plugins/nonbreaking'; + import 'tinymce/plugins/visualblocks'; + import 'tinymce/plugins/visualchars'; + import 'tinymce/plugins/wordcount'; + import 'tinymce/plugins/emoticons/js/emojis'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { + DEFAULT_CONFIG, + DARK_CONFIG, + uuid, + bindHandlers, + openAlert + } from './util'; + import type { AlertOption } from './util'; + + const props = withDefaults( + defineProps<{ + // 编辑器唯一 id + id?: string; + // v-model + value?: string; + // 编辑器配置 + init?: RawEditorSettings; + // 是否内联模式 + inline?: boolean; + // model events + modelEvents?: string; + // 内联模式标签名 + tagName?: string; + // 是否禁用 + disabled?: boolean; + // 是否跟随框架主题 + autoTheme?: boolean; + // 不跟随框架主题时是否使用暗黑主题 + darkTheme?: boolean; + }>(), + { + inline: false, + modelEvents: 'change input undo redo', + tagName: 'div', + autoTheme: true + } + ); + + const emit = defineEmits<{ + (e: 'update:value', value: string): void; + }>(); + + const attrs = useAttrs(); + const themeStore = useThemeStore(); + const { darkMode } = storeToRefs(themeStore); + + // 编辑器唯一 id + const elementId: string = props.id || uuid('tiny-vue'); + + // 编辑器实例 + let editorIns: TinyMCEEditor | null = null; + + // 是否内联模式 + const inlineEditor: boolean = props.init?.inline || props.inline; + + /* 更新 value */ + const updateValue = (value: string) => { + emit('update:value', value); + }; + + /* 修改内容 */ + const setContent = (value?: string) => { + if ( + editorIns && + typeof value === 'string' && + value !== editorIns.getContent() + ) { + editorIns.setContent(value); + } + }; + + /* 渲染编辑器 */ + const render = () => { + const isDark = props.autoTheme ? darkMode.value : props.darkTheme; + tinymce.init({ + ...DEFAULT_CONFIG, + ...(isDark ? DARK_CONFIG : {}), + ...props.init, + selector: `#${elementId}`, + readonly: props.disabled, + inline: inlineEditor, + setup: (editor: TinyMCEEditor) => { + editorIns = editor; + editor.on('init', (e: EditorEvent<any>) => { + // 回显初始值 + if (props.value) { + setContent(props.value); + } + // v-model + editor.on(props.modelEvents, () => { + updateValue(editor.getContent()); + }); + // valid events + bindHandlers(e, attrs, editor); + }); + if (typeof props.init?.setup === 'function') { + props.init.setup(editor); + } + } + }); + }; + + /* 销毁编辑器 */ + const destory = () => { + if (tinymce != null && editorIns != null) { + tinymce.remove(editorIns as any); + editorIns = null; + } + }; + + /* 弹出提示框 */ + const alert = (option?: AlertOption) => { + openAlert(editorIns, option); + }; + + defineExpose({ editorIns, alert }); + + watch( + () => props.value, + (val: string, prevVal: string) => { + if (val !== prevVal) { + setContent(val); + } + } + ); + + watch( + () => props.disabled, + (disable) => { + if (editorIns !== null) { + if (typeof editorIns.mode?.set === 'function') { + editorIns.mode.set(disable ? 'readonly' : 'design'); + } else { + editorIns.setMode(disable ? 'readonly' : 'design'); + } + } + } + ); + + watch( + () => props.tagName, + () => { + destory(); + nextTick(() => { + render(); + }); + } + ); + + watch(darkMode, () => { + if (props.autoTheme) { + destory(); + nextTick(() => { + render(); + }); + } + }); + + onMounted(() => { + render(); + }); + + onBeforeUnmount(() => { + destory(); + }); + + onActivated(() => { + render(); + }); + + onDeactivated(() => { + destory(); + }); +</script> + +<style> + body .tox-tinymce-aux { + z-index: 19990000; + } + + textarea[id^='tiny-vue'] { + width: 0; + height: 0; + margin: 0; + padding: 0; + opacity: 0; + box-sizing: border-box; + } +</style> diff --git a/src/components/TinymceEditor/util.ts b/src/components/TinymceEditor/util.ts new file mode 100644 index 0000000..c86373b --- /dev/null +++ b/src/components/TinymceEditor/util.ts @@ -0,0 +1,248 @@ +import type { + Editor as TinyMCEEditor, + EditorEvent, + RawEditorSettings +} from 'tinymce'; +const BASE_URL = import.meta.env.BASE_URL; + +// 默认加载插件 +const PLUGINS: string = [ + 'code', + 'preview', + 'fullscreen', + 'paste', + 'searchreplace', + 'save', + 'autosave', + 'link', + 'autolink', + 'image', + 'media', + 'table', + 'codesample', + 'lists', + 'advlist', + 'hr', + 'charmap', + 'emoticons', + 'anchor', + 'directionality', + 'pagebreak', + 'quickbars', + 'nonbreaking', + 'visualblocks', + 'visualchars', + 'wordcount' +].join(' '); + +// 默认工具栏布局 +const TOOLBAR: string = [ + 'fullscreen', + 'preview', + 'code', + '|', + 'undo', + 'redo', + '|', + 'forecolor', + 'backcolor', + '|', + 'bold', + 'italic', + 'underline', + 'strikethrough', + '|', + 'alignleft', + 'aligncenter', + 'alignright', + 'alignjustify', + '|', + 'outdent', + 'indent', + '|', + 'numlist', + 'bullist', + '|', + 'formatselect', + 'fontselect', + 'fontsizeselect', + '|', + 'link', + 'image', + 'media', + 'emoticons', + 'charmap', + 'anchor', + 'pagebreak', + 'codesample', + '|', + 'ltr', + 'rtl' +].join(' '); + +// 默认配置 +export const DEFAULT_CONFIG: RawEditorSettings = { + height: 300, + branding: false, + skin_url: BASE_URL + 'tinymce/skins/ui/oxide', + content_css: BASE_URL + 'tinymce/skins/content/default/content.min.css', + language_url: BASE_URL + 'tinymce/langs/zh_CN.js', + language: 'zh_CN', + plugins: PLUGINS, + toolbar: TOOLBAR, + draggable_modal: true, + toolbar_mode: 'sliding', + quickbars_insert_toolbar: '', + images_upload_handler: (blobInfo: any, success: any, error: any) => { + if (blobInfo.blob().size / 1024 > 400) { + error('大小不能超过 400KB'); + return; + } + success('data:image/jpeg;base64,' + blobInfo.base64()); + }, + file_picker_types: 'media', + file_picker_callback: () => {} +}; + +// 暗黑主题配置 +export const DARK_CONFIG: RawEditorSettings = { + skin_url: BASE_URL + 'tinymce/skins/ui/oxide-dark', + content_css: BASE_URL + 'tinymce/skins/content/dark/content.min.css' +}; + +// 支持监听的事件 +export const VALID_EVENTS = [ + 'onActivate', + 'onAddUndo', + 'onBeforeAddUndo', + 'onBeforeExecCommand', + 'onBeforeGetContent', + 'onBeforeRenderUI', + 'onBeforeSetContent', + 'onBeforePaste', + 'onBlur', + 'onChange', + 'onClearUndos', + 'onClick', + 'onContextMenu', + 'onCopy', + 'onCut', + 'onDblclick', + 'onDeactivate', + 'onDirty', + 'onDrag', + 'onDragDrop', + 'onDragEnd', + 'onDragGesture', + 'onDragOver', + 'onDrop', + 'onExecCommand', + 'onFocus', + 'onFocusIn', + 'onFocusOut', + 'onGetContent', + 'onHide', + 'onInit', + 'onKeyDown', + 'onKeyPress', + 'onKeyUp', + 'onLoadContent', + 'onMouseDown', + 'onMouseEnter', + 'onMouseLeave', + 'onMouseMove', + 'onMouseOut', + 'onMouseOver', + 'onMouseUp', + 'onNodeChange', + 'onObjectResizeStart', + 'onObjectResized', + 'onObjectSelected', + 'onPaste', + 'onPostProcess', + 'onPostRender', + 'onPreProcess', + 'onProgressState', + 'onRedo', + 'onRemove', + 'onReset', + 'onSaveContent', + 'onSelectionChange', + 'onSetAttrib', + 'onSetContent', + 'onShow', + 'onSubmit', + 'onUndo', + 'onVisualAid' +]; + +let unique = 0; + +/** + * 生成编辑器 id + */ +export function uuid(prefix: string): string { + const time = Date.now(); + const random = Math.floor(Math.random() * 1000000000); + unique++; + return prefix + '_' + random + unique + String(time); +} + +/** + * 绑定事件 + */ +export function bindHandlers( + initEvent: EditorEvent<any>, + listeners: Record<string, any>, + editor: TinyMCEEditor +): void { + const validEvents = VALID_EVENTS.map((event) => event.toLowerCase()); + Object.keys(listeners) + .filter((key: string) => validEvents.includes(key.toLowerCase())) + .forEach((key: string) => { + const handler = listeners[key]; + if (typeof handler === 'function') { + if (key === 'onInit') { + handler(initEvent, editor); + } else { + editor.on(key.substring(2), (e: EditorEvent<any>) => + handler(e, editor) + ); + } + } + }); +} + +/** + * 弹出提示框 + */ +export function openAlert( + editor: TinyMCEEditor | null, + option: AlertOption = {} +) { + editor?.windowManager?.open({ + title: option.title ?? '提示', + body: { + type: 'panel', + items: [ + { + type: 'htmlpanel', + html: `<p>${option.content ?? ''}</p>` + } + ] + }, + buttons: [ + { + type: 'cancel', + name: 'closeButton', + text: '确定', + primary: true + } + ] + }); +} + +export interface AlertOption { + title?: string; + content?: string; +} diff --git a/src/config/setting.ts b/src/config/setting.ts new file mode 100644 index 0000000..d61937d --- /dev/null +++ b/src/config/setting.ts @@ -0,0 +1,68 @@ +// 接口地址 +export const API_BASE_URL: string = import.meta.env.VITE_API_URL; + +//主页地址 +export const HOME_BASE_URL: string = import.meta.env.VITE_HOME_URL; + +// 项目名称 +export const PROJECT_NAME: string = import.meta.env.VITE_APP_NAME; + +// 不显示侧栏的路由 +export const HIDE_SIDEBARS: string[] = []; + +// 不显示页脚的路由 +export const HIDE_FOOTERS: string[] = [ + '/system/dictionary', + '/system/content/category', + '/form/advanced' +]; + +// 页签同路由不同参数可重复打开的路由 +export const REPEATABLE_TABS: string[] = []; + +// 不需要登录的路由 +export const WHITE_LIST: string[] = ['/login', '/forget']; + +// 开启 KeepAlive 后仍然不需要缓存的路由地址 +export const KEEP_ALIVE_EXCLUDES: string[] = []; + +// 直接指定菜单数据 +export const USER_MENUS: Array<any> | undefined = undefined; + +// 首页名称, 为空则取第一个菜单的名称 +export const HOME_TITLE: string | undefined = undefined; + +// 首页路径, 为空则取第一个菜单的地址 +export const HOME_PATH: string | undefined = undefined; + +// 外层布局的路由地址 +export const LAYOUT_PATH = '/'; + +// 刷新路由的路由地址 +export const REDIRECT_PATH = '/redirect'; + +// 开启页签栏是否缓存组件 +//export const TAB_KEEP_ALIVE = !import.meta.env.DEV; +export const TAB_KEEP_ALIVE = true; + +// token 传递的 header 名称 +export const TOKEN_HEADER_NAME = 'Authorization'; + +// token 存储的名称 +export const TOKEN_STORE_NAME = 'access_token'; + +// 主题配置存储的名称 +export const THEME_STORE_NAME = 'theme'; + +// i18n 缓存的名称 +export const I18N_CACHE_NAME = 'i18n-lang'; + +// 是否开启国际化功能 +export const I18N_ENABLE = false; + +// 高德地图 key , 自带的只能用于测试, 正式项目请自行到高德地图官网申请 key +export const MAP_KEY = '006d995d433058322319fa797f2876f5'; + +// EleAdminPro 授权码, 自带的只能用于演示, 正式项目请更换为自己的授权码 +export const LICENSE_CODE = + 'dk9mcwJyetRWQlxWRiojIzJCLi8mcQ5Wa3ojI0NWZqJWd6ICZpJCL0UjMYd2VEFjWwcjIvl2cyVmdiwiIiETMuEjI6IibQf0NW=='; diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 0000000..7af0dbd --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,20 @@ +/** + * 国际化配置 + */ +import { createI18n } from 'vue-i18n'; +import { I18N_CACHE_NAME } from '@/config/setting'; +import zh_CN from './lang/zh_CN'; +import zh_TW from './lang/zh_TW'; +import en from './lang/en'; + +const messages = { zh_CN, zh_TW, en }; + +const i18n = createI18n({ + messages, + legacy: false, + silentTranslationWarn: true, + // 默认语言 + locale: localStorage.getItem(I18N_CACHE_NAME) || 'zh_CN' +}); + +export default i18n; diff --git a/src/i18n/lang/en/index.ts b/src/i18n/lang/en/index.ts new file mode 100644 index 0000000..743cea1 --- /dev/null +++ b/src/i18n/lang/en/index.ts @@ -0,0 +1,14 @@ +/** + * 英语 + */ +import route from './route'; +import layout from './layout'; +import login from './login'; +import list from './list'; + +export default { + route, + layout, + login, + list +}; diff --git a/src/i18n/lang/en/layout.ts b/src/i18n/lang/en/layout.ts new file mode 100644 index 0000000..ce9467e --- /dev/null +++ b/src/i18n/lang/en/layout.ts @@ -0,0 +1,77 @@ +/* 主框架 */ +export default { + home: 'Home', + header: { + profile: 'Profile', + password: 'Password', + logout: 'SignOut' + }, + footer: { + website: 'Website', + document: 'Document', + authorization: 'Authorization', + copyright: 'Copyright © 2021 Wuhan EClouds Technology Co., Ltd' + }, + logout: { + title: 'Confirm', + message: 'Are you sure you want to log out?' + }, + setting: { + title: 'Theme Setting', + sideStyles: { + dark: 'Dark Sidebar', + light: 'Light Sidebar' + }, + headStyles: { + light: 'Light Header', + dark: 'Dark Header', + primary: 'Primary Header' + }, + layoutStyles: { + side: 'Side Menu Layout', + top: 'Top Menu Layout', + mix: 'Mix Menu Layout' + }, + colors: { + default: 'Daybreak Blue', + dust: 'Dust Blue', + sunset: 'Sunset Orange', + volcano: 'Volcano', + purple: 'Golden Purple', + cyan: 'Cyan', + green: 'Polar Green', + geekblue: 'Geek Blue' + }, + darkMode: 'Dark Mode', + layoutStyle: 'Navigation Mode', + sideMenuStyle: 'Sidebar Double Menu', + bodyFull: 'Body Fixed Width', + other: 'Other Setting', + fixedHeader: 'Fixed Header', + fixedSidebar: 'Fixed Sidebar', + fixedBody: 'Fixed Body', + logoAutoSize: 'Logo In Header', + styleResponsive: 'Responsive', + colorfulIcon: 'Colorful Icon', + sideUniqueOpen: 'Menu Unique Open', + weakMode: 'Weak Mode', + showFooter: 'Show Footer', + showTabs: 'Show Tabs', + tabStyle: 'Tab Style', + tabStyles: { + default: 'Default', + dot: 'Dot', + card: 'Card' + }, + transitionName: 'Transition', + transitions: { + slideRight: 'Slide Right', + slideBottom: 'Slide Bottom', + zoomIn: 'Zoom In', + zoomOut: 'Zoom Out', + fade: 'Fade' + }, + reset: 'Reset', + tips: 'It will remember your configuration the next time you open it.' + } +}; diff --git a/src/i18n/lang/en/list.ts b/src/i18n/lang/en/list.ts new file mode 100644 index 0000000..f809452 --- /dev/null +++ b/src/i18n/lang/en/list.ts @@ -0,0 +1,17 @@ +/* 列表页面 */ +export default { + // 基础列表 + basic: { + table: { + avatar: 'Avatar', + username: 'Username', + nickname: 'Nickname', + organizationName: 'Organization', + phone: 'Phone', + sexName: 'Sex', + createTime: 'CreateTime', + status: 'Status', + action: 'Action' + } + } +}; diff --git a/src/i18n/lang/en/login.ts b/src/i18n/lang/en/login.ts new file mode 100644 index 0000000..860f1a4 --- /dev/null +++ b/src/i18n/lang/en/login.ts @@ -0,0 +1,11 @@ +/* 登录界面 */ +export default { + title: 'User Login', + username: 'please input username', + password: 'please input password', + code: 'please input code', + remember: 'remember', + forget: 'forget', + login: 'login', + loading: 'loading' +}; diff --git a/src/i18n/lang/en/route.ts b/src/i18n/lang/en/route.ts new file mode 100644 index 0000000..07bbdd4 --- /dev/null +++ b/src/i18n/lang/en/route.ts @@ -0,0 +1,100 @@ +/* 菜单路由 */ +export default { + login: { _name: 'Login' }, + forget: { _name: 'Forget' }, + dashboard: { + _name: 'Dashboard', + workplace: { _name: 'Workplace' }, + analysis: { _name: 'Analysis' }, + monitor: { _name: 'Monitor' } + }, + system: { + _name: 'System', + user: { + _name: 'User', + details: { _name: '' } + }, + role: { _name: 'Role' }, + menu: { _name: 'Menu' }, + dictionary: { _name: 'Dictionary' }, + organization: { _name: 'Organization' }, + loginRecord: { _name: 'LoginRecord' }, + operationRecord: { _name: 'OperationRecord' }, + file: { _name: 'File' }, + userInfo: { _name: '' } + }, + form: { + _name: 'Form', + basic: { _name: 'Basic Form' }, + advanced: { _name: 'Advanced Form' }, + step: { _name: 'Step Form' } + }, + list: { + _name: 'List', + basic: { + _name: 'Basic List', + add: { _name: 'UserAdd' }, + edit: { _name: 'UserEdit' }, + details: { + ':id': { _name: '' } + } + }, + advanced: { _name: 'Advanced List' }, + card: { + _name: 'Card List', + project: { _name: 'Project' }, + application: { _name: 'Application' }, + article: { _name: 'Article' } + } + }, + result: { + _name: 'Result', + success: { _name: 'Success' }, + fail: { _name: 'Fail' } + }, + exception: { + _name: 'Exception', + '403': { _name: '403' }, + '404': { _name: '404' }, + '500': { _name: '500' } + }, + user: { + _name: 'User', + profile: { _name: 'Profile' }, + message: { _name: 'Message' } + }, + extension: { + _name: 'Extension', + tag: { _name: 'Tags' }, + dialog: { _name: 'DragDialog' }, + file: { _name: 'FileList' }, + upload: { _name: 'ImageUpload' }, + dragsort: { _name: 'DragSort' }, + colorPicker: { _name: 'ColorPicker' }, + regions: { _name: 'CitySelect' }, + printer: { _name: 'Printer' }, + excel: { _name: 'Excel' }, + countUp: { _name: 'CountUp' }, + tableSelect: { _name: 'TableSelect' }, + player: { _name: 'Player' }, + map: { _name: 'Map' }, + qrCode: { _name: 'QRCode' }, + barCode: { _name: 'BarCode' }, + editor: { _name: 'Editor' }, + markdown: { _name: 'Markdown' }, + dashboard: { _name: 'Dashboard' }, + tour: { _name: 'Tour' }, + watermark: { _name: 'Watermark' }, + split: { _name: 'Split' } + }, + example: { + _name: 'Example', + table: { _name: 'ProTable' }, + menuBadge: { _name: 'MenuBadge' }, + eleadmin: { _name: 'IFrame' }, + eleadminDoc: { _name: 'IFrame2' }, + document: { _name: 'Document' }, + choose: { _name: 'Choose' } + }, + 'https://eleadminCom/goods/9': { _name: 'Authorization' } +}; diff --git a/src/i18n/lang/zh_CN/index.ts b/src/i18n/lang/zh_CN/index.ts new file mode 100644 index 0000000..282e79e --- /dev/null +++ b/src/i18n/lang/zh_CN/index.ts @@ -0,0 +1,14 @@ +/** + * 简体中文 + */ +import route from './route'; +import layout from './layout'; +import login from './login'; +import list from './list'; + +export default { + route, + layout, + login, + list +}; diff --git a/src/i18n/lang/zh_CN/layout.ts b/src/i18n/lang/zh_CN/layout.ts new file mode 100644 index 0000000..44b450b --- /dev/null +++ b/src/i18n/lang/zh_CN/layout.ts @@ -0,0 +1,77 @@ +/* 主框架 */ +export default { + home: '主页', + header: { + profile: '个人中心', + password: '修改密码', + logout: '退出登录' + }, + footer: { + website: '官网', + document: '文档', + authorization: '授权', + copyright: 'Copyright © 2022 CQTLCM' + }, + logout: { + title: '提示', + message: '确定要退出登录吗?' + }, + setting: { + title: '整体风格设置', + sideStyles: { + dark: '暗色侧边栏', + light: '亮色侧边栏' + }, + headStyles: { + light: '亮色顶栏', + dark: '暗色顶栏', + primary: '主色顶栏' + }, + layoutStyles: { + side: '左侧菜单布局', + top: '顶部菜单布局', + mix: '混合菜单布局' + }, + colors: { + default: '拂晓蓝', + dust: '薄暮', + sunset: '日暮', + volcano: '火山', + purple: '酱紫', + cyan: '明青', + green: '极光绿', + geekblue: '极客蓝' + }, + darkMode: '开启暗黑模式', + layoutStyle: '导航模式', + sideMenuStyle: '侧栏双排菜单', + bodyFull: '内容区域定宽', + other: '其它配置', + fixedHeader: '固定顶栏区域', + fixedSidebar: '固定侧栏区域', + fixedBody: '固定主体区域', + logoAutoSize: 'Logo置于顶栏', + styleResponsive: '移动端响应式', + colorfulIcon: '侧栏彩色图标', + sideUniqueOpen: '侧栏排他展开', + weakMode: '开启色弱模式', + showFooter: '开启全局页脚', + showTabs: '开启多页签栏', + tabStyle: '页签显示风格', + tabStyles: { + default: '默认', + dot: '圆点', + card: '卡片' + }, + transitionName: '路由切换动画', + transitions: { + slideRight: '滑动消退', + slideBottom: '底部消退', + zoomIn: '放大渐变', + zoomOut: '缩小渐变', + fade: '淡入淡出' + }, + reset: '重置', + tips: '该功能可实时预览各种布局效果, 修改后会缓存在本地, 下次打开会记忆主题配置.' + } +}; diff --git a/src/i18n/lang/zh_CN/list.ts b/src/i18n/lang/zh_CN/list.ts new file mode 100644 index 0000000..4011464 --- /dev/null +++ b/src/i18n/lang/zh_CN/list.ts @@ -0,0 +1,17 @@ +/* 列表页面 */ +export default { + // 基础列表 + basic: { + table: { + avatar: '头像', + username: '用户账号', + nickname: '用户名', + organizationName: '组织机构', + phone: '手机号', + sexName: '性别', + createTime: '创建时间', + status: '状态', + action: '操作' + } + } +}; diff --git a/src/i18n/lang/zh_CN/login.ts b/src/i18n/lang/zh_CN/login.ts new file mode 100644 index 0000000..d7d54cf --- /dev/null +++ b/src/i18n/lang/zh_CN/login.ts @@ -0,0 +1,11 @@ +/* 登录界面 */ +export default { + title: '用户登录', + username: '请输入登录账号', + password: '请输入登录密码', + code: '请输入验证码', + remember: '记住密码', + forget: '忘记密码', + login: '登录', + loading: '登录中' +}; diff --git a/src/i18n/lang/zh_CN/route.ts b/src/i18n/lang/zh_CN/route.ts new file mode 100644 index 0000000..db312d9 --- /dev/null +++ b/src/i18n/lang/zh_CN/route.ts @@ -0,0 +1,100 @@ +/* 菜单路由 */ +export default { + login: { _name: '登录' }, + forget: { _name: '忘记密码' }, + dashboard: { + _name: 'Dashboard', + workplace: { _name: '工作台' }, + analysis: { _name: '分析页' }, + monitor: { _name: '监控页' } + }, + system: { + _name: '系统管理', + user: { + _name: '用户管理', + add: { _name: '添加用户' }, + edit: { _name: '修改用户' }, + details: { _name: '' } + }, + role: { _name: '角色管理' }, + menu: { _name: '菜单管理' }, + dictionary: { _name: '字典管理' }, + organization: { _name: '机构管理' }, + loginRecord: { _name: '登录日志' }, + operationRecord: { _name: '操作日志' }, + file: { _name: '文件管理' } + }, + form: { + _name: '表单页面', + basic: { _name: '基础表单' }, + advanced: { _name: '复杂表单' }, + step: { _name: '分步表单' } + }, + list: { + _name: '列表页面', + basic: { + _name: '基础列表', + add: { _name: '添加用户' }, + edit: { _name: '修改用户' }, + details: { + ':id': { _name: '' } + } + }, + advanced: { _name: '复杂列表' }, + card: { + _name: '卡片列表', + project: { _name: '项目列表' }, + application: { _name: '应用列表' }, + article: { _name: '文章列表' } + } + }, + result: { + _name: '结果页面', + success: { _name: '成功页' }, + fail: { _name: '失败页' } + }, + exception: { + _name: '异常页面', + '403': { _name: '403' }, + '404': { _name: '404' }, + '500': { _name: '500' } + }, + user: { + _name: '个人中心', + profile: { _name: '个人资料' }, + message: { _name: '我的消息' } + }, + extension: { + _name: '扩展组件', + tag: { _name: '标签组件' }, + dialog: { _name: '拖拽弹窗' }, + file: { _name: '文件列表' }, + upload: { _name: '图片上传' }, + dragsort: { _name: '拖拽排序' }, + colorPicker: { _name: '颜色选择' }, + regions: { _name: '城市选择' }, + printer: { _name: '打印插件' }, + excel: { _name: 'excel插件' }, + countUp: { _name: '滚动数字' }, + tableSelect: { _name: '表格下拉' }, + player: { _name: '视频播放' }, + map: { _name: '地图组件' }, + qrCode: { _name: '二维码' }, + barCode: { _name: '条形码' }, + editor: { _name: '富文本框' }, + markdown: { _name: 'markdown' }, + dashboard: { _name: '仪表盘' }, + tour: { _name: '引导组件' }, + watermark: { _name: '水印组件' }, + split: { _name: '分割面板' } + }, + example: { + _name: '常用实例', + table: { _name: '表格实例' }, + menuBadge: { _name: '菜单徽章' }, + eleadmin: { _name: '内嵌页面' }, + eleadminDoc: { _name: '内嵌文档' }, + document: { _name: '案卷调整' }, + choose: { _name: '批量选择' } + } +}; diff --git a/src/i18n/lang/zh_TW/index.ts b/src/i18n/lang/zh_TW/index.ts new file mode 100644 index 0000000..50057e3 --- /dev/null +++ b/src/i18n/lang/zh_TW/index.ts @@ -0,0 +1,14 @@ +/** + * 繁体中文 + */ +import route from './route'; +import layout from './layout'; +import login from './login'; +import list from './list'; + +export default { + route, + layout, + login, + list +}; diff --git a/src/i18n/lang/zh_TW/layout.ts b/src/i18n/lang/zh_TW/layout.ts new file mode 100644 index 0000000..cfd0bd5 --- /dev/null +++ b/src/i18n/lang/zh_TW/layout.ts @@ -0,0 +1,77 @@ +/* 主框架 */ +export default { + home: '主頁', + header: { + profile: '個人中心', + password: '修改密碼', + logout: '安全登出' + }, + footer: { + website: '官網', + document: '檔案', + authorization: '授權', + copyright: 'Copyright © 2022 武漢易雲智科技有限公司' + }, + logout: { + title: '詢問', + message: '確定要登出嗎?' + }, + setting: { + title: '整體風格設定', + sideStyles: { + dark: '暗色側邊欄', + light: '亮色側邊欄' + }, + headStyles: { + light: '亮色頂欄', + dark: '暗色頂欄', + primary: '主色頂欄' + }, + layoutStyles: { + side: '左側選單佈局', + top: '頂部選單佈局', + mix: '混合選單佈局' + }, + colors: { + default: '拂曉藍', + dust: '薄暮', + sunset: '日暮', + volcano: '火山', + purple: '醬紫', + cyan: '明青', + green: '極光綠', + geekblue: '極客藍' + }, + darkMode: '開啟暗黑模式', + layoutStyle: '導航模式', + sideMenuStyle: '側欄雙排選單', + bodyFull: '內容區域定寬', + other: '其它配寘', + fixedHeader: '固定頂欄區域', + fixedSidebar: '固定側欄區域', + fixedBody: '固定主體區域', + logoAutoSize: 'Logo置於頂欄', + styleResponsive: '移動端響應式', + colorfulIcon: '側欄彩色圖標', + sideUniqueOpen: '側欄排他展開', + weakMode: '開啟色弱模式', + showFooter: '開啟全域頁腳', + showTabs: '開啟多頁簽欄', + tabStyle: '頁簽顯示風格', + tabStyles: { + default: '默認', + dot: '圓點', + card: '卡片' + }, + transitionName: '路由切換動畫', + transitions: { + slideRight: '滑動消退', + slideBottom: '底部消退', + zoomIn: '放大漸變', + zoomOut: '縮小漸變', + fade: '淡入淡出' + }, + reset: '重置', + tips: '該功能可實时預覽各種佈局效果,修改後會緩存在本地,下次打開會記憶主題配寘.' + } +}; diff --git a/src/i18n/lang/zh_TW/list.ts b/src/i18n/lang/zh_TW/list.ts new file mode 100644 index 0000000..f24c25f --- /dev/null +++ b/src/i18n/lang/zh_TW/list.ts @@ -0,0 +1,17 @@ +/* 列表页面 */ +export default { + // 基础列表 + basic: { + table: { + avatar: '頭像', + username: '用戶賬號', + nickname: '用戶名', + organizationName: '組織機構', + phone: '手機號', + sexName: '性別', + createTime: '創建時間', + status: '狀態', + action: '操作' + } + } +}; diff --git a/src/i18n/lang/zh_TW/login.ts b/src/i18n/lang/zh_TW/login.ts new file mode 100644 index 0000000..5cd7b85 --- /dev/null +++ b/src/i18n/lang/zh_TW/login.ts @@ -0,0 +1,11 @@ +/* 登录界面 */ +export default { + title: '用戶登錄', + username: '請輸入登入帳號', + password: '請輸入登入密碼', + code: '請輸入驗證碼', + remember: '記住密碼', + forget: '忘記密碼', + login: '登入', + loading: '登入中' +}; diff --git a/src/i18n/lang/zh_TW/route.ts b/src/i18n/lang/zh_TW/route.ts new file mode 100644 index 0000000..73cf5d8 --- /dev/null +++ b/src/i18n/lang/zh_TW/route.ts @@ -0,0 +1,101 @@ +/* 菜单路由 */ +export default { + login: { _name: '登入' }, + forget: { _name: '忘記密碼' }, + dashboard: { + _name: 'Dashboard', + workplace: { _name: '工作臺' }, + analysis: { _name: '分析頁' }, + monitor: { _name: '監控頁' } + }, + system: { + _name: '系統管理', + user: { + _name: '用戶管理', + add: { _name: '添加用戶' }, + edit: { _name: '編輯用戶' }, + details: { _name: '' } + }, + role: { _name: '角色管理' }, + menu: { _name: '選單管理' }, + dictionary: { _name: '字典管理' }, + organization: { _name: '機构管理' }, + loginRecord: { _name: '登入日誌' }, + operationRecord: { _name: '操作日誌' }, + file: { _name: '檔案管理' } + }, + form: { + _name: '表單頁面', + basic: { _name: '基礎表單' }, + advanced: { _name: '複雜表單' }, + step: { _name: '分步表單' } + }, + list: { + _name: '清單頁面', + basic: { + _name: '基礎清單', + add: { _name: '添加用戶' }, + edit: { _name: '編輯用戶' }, + details: { + ':id': { _name: '' } + } + }, + advanced: { _name: '複雜清單' }, + card: { + _name: '卡片清單', + project: { _name: '項目清單' }, + application: { _name: '應用清單' }, + article: { _name: '文章清單' } + } + }, + result: { + _name: '結果頁面', + success: { _name: '成功頁' }, + fail: { _name: '失敗頁' } + }, + exception: { + _name: '异常頁面', + '403': { _name: '403' }, + '404': { _name: '404' }, + '500': { _name: '500' } + }, + user: { + _name: '個人中心', + profile: { _name: '個人資料' }, + message: { _name: '我的消息' } + }, + extension: { + _name: '擴展組件', + tag: { _name: '標籤組件' }, + dialog: { _name: '拖拽彈窗' }, + file: { _name: '檔案清單' }, + upload: { _name: '圖片上傳' }, + dragsort: { _name: '拖拽排序' }, + colorPicker: { _name: '顏色選擇' }, + regions: { _name: '城市選擇' }, + printer: { _name: '列印挿件' }, + excel: { _name: 'excel挿件' }, + countUp: { _name: '滾動數字' }, + tableSelect: { _name: '表格下拉' }, + player: { _name: '視頻播放' }, + map: { _name: '地圖組件' }, + qrCode: { _name: '二維碼' }, + barCode: { _name: '條形碼' }, + editor: { _name: '富文本框' }, + markdown: { _name: 'markdown' }, + dashboard: { _name: '儀錶盤' }, + tour: { _name: '引導組件' }, + watermark: { _name: '水印組件' }, + split: { _name: '分割面板' } + }, + example: { + _name: '常用實例', + table: { _name: '表格實例' }, + menuBadge: { _name: '選單徽章' }, + eleadmin: { _name: '內嵌頁面' }, + eleadminDoc: { _name: '内嵌文檔' }, + document: { _name: '案卷調整' }, + choose: { _name: '批量選擇' } + }, + 'https://eleadminCom/goods/9': { _name: '獲取授權' } +}; diff --git a/src/i18n/use-locale.ts b/src/i18n/use-locale.ts new file mode 100644 index 0000000..5c10eef --- /dev/null +++ b/src/i18n/use-locale.ts @@ -0,0 +1,38 @@ +/** + * AntDesignVue、EleAdminPro、Dayjs 国际化配置 + */ +import { ref, watch } from 'vue'; +import { useI18n } from 'vue-i18n'; +import type { Locale } from 'ant-design-vue/es/locale-provider'; +import type { EleLocale } from 'ele-admin-pro/es'; +// AntDesignVue +import zh_CN from 'ant-design-vue/es/locale/zh_CN'; +import zh_TW from 'ant-design-vue/es/locale/zh_TW'; +import en from 'ant-design-vue/es/locale/en_US'; +// EleAdminPro +import eleZh_CN from 'ele-admin-pro/es/lang/zh_CN'; +import eleZh_TW from 'ele-admin-pro/es/lang/zh_TW'; +import eleEn from 'ele-admin-pro/es/lang/en_US'; +// Dayjs +import dayjs from 'dayjs'; +import 'dayjs/locale/zh-cn'; +import 'dayjs/locale/zh-tw'; +const antLocales = { zh_CN, zh_TW, en }; +const eleLocales = { zh_CN: eleZh_CN, zh_TW: eleZh_TW, en: eleEn }; + +export function useLocale() { + const { locale } = useI18n(); + const antLocale = ref<Locale>(); + const eleLocale = ref<EleLocale>(); + + watch( + locale, + () => { + antLocale.value = antLocales[locale.value]; + eleLocale.value = eleLocales[locale.value]; + dayjs.locale(locale.value.toLowerCase().replace(/_/g, '-')); + }, + { immediate: true } + ); + return { antLocale, eleLocale }; +} diff --git a/src/layout/components/header-notice.vue b/src/layout/components/header-notice.vue new file mode 100644 index 0000000..1179183 --- /dev/null +++ b/src/layout/components/header-notice.vue @@ -0,0 +1,251 @@ +<!-- 顶栏消息通知 --> +<template> + <a-dropdown + v-model:visible="visible" + placement="bottom" + :trigger="['click']" + :overlay-style="{ padding: '0 10px' }" + > + <a-badge :count="unreadNum" class="ele-notice-trigger" :offset="[6, 4]"> + <bell-outlined style="padding: 8px 0" /> + </a-badge> + <template #overlay> + <div class="ant-dropdown-menu ele-notice-pop"> + <div @click.stop=""> + <a-tabs v-model:active-key="active" :centered="true"> + <a-tab-pane key="notice" :tab="noticeTitle"> + <a-list item-layout="horizontal" :data-source="notice"> + <template #renderItem="{ item }"> + <a-list-item> + <a-list-item-meta + :title="item.title" + :description="item.time" + > + <template #avatar> + <a-avatar :style="{ background: item.color }"> + <template #icon> + <component :is="item.icon" /> + </template> + </a-avatar> + </template> + </a-list-item-meta> + </a-list-item> + </template> + </a-list> + <div v-if="notice.length" class="ele-cell ele-notice-actions"> + <div class="ele-cell-content" @click="clearNotice"> + 清空通知 + </div> + <a-divider type="vertical" /> + <router-link + to="/user/message?type=notice" + class="ele-cell-content" + > + 查看更多 + </router-link> + </div> + </a-tab-pane> + <a-tab-pane key="letter" :tab="letterTitle"> + <a-list item-layout="horizontal" :data-source="letter"> + <template #renderItem="{ item }"> + <a-list-item> + <a-list-item-meta :title="item.title"> + <template #avatar> + <a-avatar :src="item.avatar" /> + </template> + <template #description> + <div>{{ item.content }}</div> + <div>{{ item.time }}</div> + </template> + </a-list-item-meta> + </a-list-item> + </template> + </a-list> + <div v-if="letter.length" class="ele-cell ele-notice-actions"> + <div class="ele-cell-content" @click="clearLetter"> + 清空私信 + </div> + <a-divider type="vertical" /> + <router-link + to="/user/message?type=letter" + class="ele-cell-content" + > + 查看更多 + </router-link> + </div> + </a-tab-pane> + <a-tab-pane key="todo" :tab="todoTitle"> + <a-list item-layout="horizontal" :data-source="todo"> + <template #renderItem="{ item }"> + <a-list-item> + <a-list-item-meta :description="item.description"> + <template #title> + <div class="ele-cell"> + <div class="ele-cell-content">{{ item.title }}</div> + <a-tag :color="[void 0, 'red', 'blue'][item.status]"> + {{ ['未开始', '即将到期', '进行中'][item.status] }} + </a-tag> + </div> + </template> + </a-list-item-meta> + </a-list-item> + </template> + </a-list> + <div v-if="todo.length" class="ele-cell ele-notice-actions"> + <div class="ele-cell-content" @click="clearTodo"> + 清空待办 + </div> + <a-divider type="vertical" /> + <router-link + to="/user/message?type=todo" + class="ele-cell-content" + > + 查看更多 + </router-link> + </div> + </a-tab-pane> + </a-tabs> + </div> + </div> + </template> + </a-dropdown> +</template> + +<script lang="ts" setup> + import { ref, computed } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { getUnreadNotice } from '@/api/layout'; + import type { NoticeModel, LetterModel, TodoModel } from '@/api/layout/model'; + + // 是否显示 + const visible = ref<boolean>(false); + // 选项卡选中 + const active = ref<string>('notice'); + // 通知数据 + const notice = ref<NoticeModel[]>([]); + // 私信数据 + const letter = ref<LetterModel[]>([]); + // 待办数据 + const todo = ref<TodoModel[]>([]); + + // 通知标题 + const noticeTitle = computed(() => { + return '通知' + (notice.value.length ? `(${notice.value.length})` : ''); + }); + + // 私信标题 + const letterTitle = computed(() => { + return '私信' + (letter.value.length ? `(${letter.value.length})` : ''); + }); + + // 待办标题 + const todoTitle = computed(() => { + return '待办' + (todo.value.length ? `(${todo.value.length})` : ''); + }); + + // 未读数量 + const unreadNum = computed(() => { + return notice.value.length + letter.value.length + todo.value.length; + }); + + /* 查询数据 */ + const query = () => { + getUnreadNotice() + .then((result) => { + notice.value = result.notice; + letter.value = result.letter; + todo.value = result.todo; + }) + .catch((e) => { + message.error(e.message); + }); + }; + + /* 清空通知 */ + const clearNotice = () => { + notice.value = []; + }; + + /* 清空通知 */ + const clearLetter = () => { + letter.value = []; + }; + + /* 清空通知 */ + const clearTodo = () => { + todo.value = []; + }; + + query(); +</script> + +<script lang="ts"> + import { + BellOutlined, + NotificationFilled, + PushpinFilled, + VideoCameraFilled, + CarryOutFilled, + BellFilled + } from '@ant-design/icons-vue'; + + export default { + name: 'HeaderNotice', + components: { + BellOutlined, + NotificationFilled, + PushpinFilled, + VideoCameraFilled, + CarryOutFilled, + BellFilled + } + }; +</script> + +<style lang="less"> + .ele-notice-trigger.ant-badge { + color: inherit; + } + + .ele-notice-pop { + &.ant-dropdown-menu { + padding: 0; + width: 336px; + max-width: 100%; + margin-top: 11px; + } + + // 内容 + .ant-list-item { + padding-left: 24px; + padding-right: 24px; + transition: background-color 0.3s; + cursor: pointer; + + &:hover { + background: hsla(0, 0%, 60%, 0.05); + } + } + + .ant-tag { + margin: 0; + } + + // 操作按钮 + .ele-notice-actions { + border-top: 1px solid hsla(0, 0%, 60%, 0.15); + + & > .ele-cell-content { + line-height: 46px; + text-align: center; + transition: background-color 0.3s; + cursor: pointer; + color: inherit; + + &:hover { + background: hsla(0, 0%, 60%, 0.05); + } + } + } + } +</style> diff --git a/src/layout/components/header-tools.vue b/src/layout/components/header-tools.vue new file mode 100644 index 0000000..af75064 --- /dev/null +++ b/src/layout/components/header-tools.vue @@ -0,0 +1,158 @@ +<!-- 顶栏右侧区域 --> +<template> + <div class="ele-admin-header-tool"> + <!-- 全屏切换 --> + <div + :class="[ + 'ele-admin-header-tool-item', + { 'hidden-sm-and-down': styleResponsive } + ]" + @click="toggleFullscreen" + > + <fullscreen-exit-outlined v-if="fullscreen" /> + <fullscreen-outlined v-else /> + </div> + <!-- 语言切换 --> + <div class="ele-admin-header-tool-item"> + <i18n-icon /> + </div> + <!-- 消息通知 --> + <div class="ele-admin-header-tool-item"> + <header-notice /> + </div> + <!-- 用户信息 --> + <div class="ele-admin-header-tool-item"> + <a-dropdown placement="bottom" :overlay-style="{ minWidth: '120px' }"> + <div class="ele-admin-header-avatar"> + <a-avatar :src="loginUser.avatar"> + <template v-if="!loginUser.avatar" #icon> + <user-outlined /> + </template> + </a-avatar> + <span :class="{ 'hidden-sm-and-down': styleResponsive }"> + {{ loginUser.nickname }} + </span> + <down-outlined style="margin-left: 6px" /> + </div> + <template #overlay> + <a-menu :selectable="false" @click="onUserDropClick"> + <a-menu-item key="profile"> + <div class="ele-cell"> + <user-outlined /> + <div class="ele-cell-content"> + {{ t('layout.header.profile') }} + </div> + </div> + </a-menu-item> + <a-menu-item key="password"> + <div class="ele-cell"> + <key-outlined /> + <div class="ele-cell-content"> + {{ t('layout.header.password') }} + </div> + </div> + </a-menu-item> + <a-menu-divider /> + <a-menu-item key="logout"> + <div class="ele-cell"> + <logout-outlined /> + <div class="ele-cell-content"> + {{ t('layout.header.logout') }} + </div> + </div> + </a-menu-item> + </a-menu> + </template> + </a-dropdown> + </div> + <!-- 主题设置 --> + <div class="ele-admin-header-tool-item" @click="openSetting"> + <more-outlined /> + </div> + </div> + <!-- 修改密码弹窗 --> + <password-modal v-model:visible="passwordVisible" /> + <!-- 主题设置抽屉 --> + <setting-drawer v-model:visible="settingVisible" /> +</template> + +<script lang="ts" setup> + import { computed, createVNode, ref } from 'vue'; + import { useRouter } from 'vue-router'; + import { useI18n } from 'vue-i18n'; + import { Modal } from 'ant-design-vue/es'; + import { + DownOutlined, + MoreOutlined, + UserOutlined, + KeyOutlined, + LogoutOutlined, + ExclamationCircleOutlined, + FullscreenOutlined, + FullscreenExitOutlined + } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import HeaderNotice from './header-notice.vue'; + import PasswordModal from './password-modal.vue'; + import SettingDrawer from './setting-drawer.vue'; + import I18nIcon from './i18n-icon.vue'; + import { useUserStore } from '@/store/modules/user'; + import { logout } from '@/utils/page-tab-util'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'fullscreen'): void; + }>(); + + defineProps<{ + // 是否是全屏 + fullscreen: boolean; + }>(); + + const { push } = useRouter(); + const { t } = useI18n(); + const userStore = useUserStore(); + + // 是否显示修改密码弹窗 + const passwordVisible = ref(false); + + // 是否显示主题设置抽屉 + const settingVisible = ref(false); + + // 当前用户信息 + const loginUser = computed(() => userStore.info ?? {}); + + /* 用户信息下拉点击 */ + const onUserDropClick = ({ key }) => { + if (key === 'password') { + passwordVisible.value = true; + } else if (key === 'profile') { + push('/user/profile'); + } else if (key === 'logout') { + // 退出登录 + Modal.confirm({ + title: t('layout.logout.title'), + content: t('layout.logout.message'), + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + logout(); + } + }); + } + }; + + /* 切换全屏 */ + const toggleFullscreen = () => { + emit('fullscreen'); + }; + + /* 打开主题设置抽屉 */ + const openSetting = () => { + settingVisible.value = true; + }; +</script> diff --git a/src/layout/components/i18n-icon.vue b/src/layout/components/i18n-icon.vue new file mode 100644 index 0000000..82984d6 --- /dev/null +++ b/src/layout/components/i18n-icon.vue @@ -0,0 +1,52 @@ +<!-- 国际化语言切换组件 --> +<template> + <a-dropdown + :placement="placement" + :overlay-style="{ minWidth: '120px', paddingTop: '17px' }" + > + <slot> + <global-outlined :style="style" /> + </slot> + <template #overlay> + <a-menu :selected-keys="language" @click="changeLanguage"> + <a-menu-item key="en">English</a-menu-item> + <a-menu-item key="zh_CN">简体中文</a-menu-item> + <a-menu-item key="zh_TW">繁體中文</a-menu-item> + </a-menu> + </template> + </a-dropdown> +</template> + +<script lang="ts" setup> + import type { CSSProperties } from 'vue'; + import { computed } from 'vue'; + import { useI18n } from 'vue-i18n'; + import { GlobalOutlined } from '@ant-design/icons-vue'; + import { I18N_CACHE_NAME } from '@/config/setting'; + + withDefaults( + defineProps<{ + // dropdown placement + placement?: any; + // 自定义样式 + style?: CSSProperties; + }>(), + { + placement: 'bottom', + style: () => { + return { transform: 'scale(1.08)' }; + } + } + ); + + const { locale } = useI18n(); + + // 当前显示语言 + const language = computed(() => [locale.value]); + + /* 切换语言 */ + const changeLanguage = ({ key }) => { + locale.value = key; + localStorage.setItem(I18N_CACHE_NAME, key); + }; +</script> diff --git a/src/layout/components/menu-title.vue b/src/layout/components/menu-title.vue new file mode 100644 index 0000000..168a697 --- /dev/null +++ b/src/layout/components/menu-title.vue @@ -0,0 +1,17 @@ +<template> + <span>{{ item.meta.title }}</span> + <div v-if="item.meta && item.meta.badge" class="ele-menu-badge"> + <a-badge + :count="item.meta.badge" + :number-style="{ background: item.meta.badgeColor as string }" + /> + </div> +</template> + +<script lang="ts" setup> + import type { MenuItemType } from 'ele-admin-pro/es'; + + defineProps<{ + item: MenuItemType; + }>(); +</script> diff --git a/src/layout/components/page-footer.vue b/src/layout/components/page-footer.vue new file mode 100644 index 0000000..8ef1f5c --- /dev/null +++ b/src/layout/components/page-footer.vue @@ -0,0 +1,33 @@ +<!-- 全局页脚 --> +<template> + <div class="ele-text-center" style="padding: 16px 0"> + <a-space size="large"> + <a class="ele-text-secondary" href="" target="_blank"> + {{ t('layout.footer.website') }} + </a> + <a + class="ele-text-secondary" + href="" + target="_blank" + > + {{ t('layout.footer.document') }} + </a> + <a + class="ele-text-secondary" + href="" + target="_blank" + > + {{ t('layout.footer.authorization') }} + </a> + </a-space> + <div class="ele-text-secondary" style="margin-top: 8px"> + {{ t('layout.footer.copyright') }} + </div> + </div> +</template> + +<script lang="ts" setup> + import { useI18n } from 'vue-i18n'; + + const { t } = useI18n(); +</script> diff --git a/src/layout/components/password-modal.vue b/src/layout/components/password-modal.vue new file mode 100644 index 0000000..2075b7a --- /dev/null +++ b/src/layout/components/password-modal.vue @@ -0,0 +1,146 @@ +<!-- 修改密码弹窗 --> +<template> + <ele-modal + :width="420" + title="修改密码" + :visible="visible" + :confirm-loading="loading" + :body-style="{ paddingBottom: '16px' }" + @update:visible="updateVisible" + @cancel="onCancel" + @ok="onOk" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { sm: 6 } : { flex: '90px' }" + :wrapper-col="styleResponsive ? { sm: 18 } : { flex: '1' }" + > + <a-form-item label="旧密码" name="oldPassword"> + <a-input-password + v-model:value="form.oldPassword" + placeholder="请输入旧密码" + /> + </a-form-item> + <a-form-item label="新密码" name="password"> + <a-input-password + v-model:value="form.password" + placeholder="请输入新密码" + /> + </a-form-item> + <a-form-item label="确认密码" name="password2"> + <a-input-password + v-model:value="form.password2" + placeholder="请再次输入新密码" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { updatePassword } from '@/api/layout'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'update:visible', value: boolean): void; + }>(); + + defineProps<{ + visible: boolean; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 提交loading + const loading = ref<boolean>(false); + + // 表单数据 + const { form, resetFields } = useFormData({ + oldPassword: '', + password: '', + password2: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + oldPassword: [ + { + required: true, + type: 'string', + message: '请输入旧密码', + trigger: 'blur' + } + ], + password: [ + { + required: true, + type: 'string', + message: '请输入新密码', + trigger: 'blur' + } + ], + password2: [ + { + required: true, + type: 'string', + validator: async (_rule: Rule, value: string) => { + if (!value) { + return Promise.reject('请再次输入新密码'); + } + if (value !== form.password) { + return Promise.reject('两次输入密码不一致'); + } + return Promise.resolve(); + }, + trigger: 'blur' + } + ] + }); + + /* 修改visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + /* 保存修改 */ + const onOk = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + updatePassword(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 关闭回调 */ + const onCancel = () => { + resetFields(); + formRef.value?.clearValidate(); + loading.value = false; + }; +</script> diff --git a/src/layout/components/setting-drawer.vue b/src/layout/components/setting-drawer.vue new file mode 100644 index 0000000..1c47d32 --- /dev/null +++ b/src/layout/components/setting-drawer.vue @@ -0,0 +1,745 @@ +<!-- 主题设置抽屉 --> +<template> + <a-drawer + :width="280" + :visible="visible" + :body-style="{ padding: 0 }" + :header-style="{ + position: 'absolute', + top: '16px', + right: 0, + padding: 0, + background: 'none' + }" + :z-index="1001" + @update:visible="updateVisible" + > + <div :class="['ele-setting-wrapper', { 'ele-setting-dark': darkMode }]"> + <div class="ele-setting-title">{{ t('layout.setting.title') }}</div> + <!-- 侧栏风格 --> + <div + v-if="layoutStyle !== 'top'" + class="ele-setting-theme ele-text-primary" + > + <a-tooltip :title="t('layout.setting.sideStyles.dark')"> + <div + class="ele-bg-base ele-side-dark" + @click="updateSideStyle('dark')" + > + <check-outlined v-if="sideStyle === 'dark'" /> + </div> + </a-tooltip> + <a-tooltip :title="t('layout.setting.sideStyles.light')"> + <div class="ele-bg-base" @click="updateSideStyle('light')"> + <check-outlined v-if="sideStyle === 'light'" /> + </div> + </a-tooltip> + </div> + <!-- 顶栏风格 --> + <div class="ele-setting-theme ele-text-primary"> + <a-tooltip :title="t('layout.setting.headStyles.light')"> + <div + class="ele-bg-base ele-head-light" + @click="updateHeadStyle('light')" + > + <check-outlined v-if="headStyle === 'light'" /> + </div> + </a-tooltip> + <a-tooltip :title="t('layout.setting.headStyles.dark')"> + <div + class="ele-bg-base ele-head-dark" + @click="updateHeadStyle('dark')" + > + <check-outlined v-if="headStyle === 'dark'" /> + </div> + </a-tooltip> + <a-tooltip :title="t('layout.setting.headStyles.primary')"> + <div + class="ele-bg-base ele-head-primary" + @click="updateHeadStyle('primary')" + > + <div class="ele-bg-primary"></div> + <check-outlined v-if="headStyle === 'primary'" /> + </div> + </a-tooltip> + </div> + <!-- 主题色 --> + <div class="ele-setting-colors"> + <div + v-for="item in themes" + :key="item.name" + :style="{ 'background-color': item.color || item.value }" + class="ele-setting-color-item" + @click="updateColor(item.value)" + > + <check-outlined v-if="item.value ? item.value === color : !color" /> + <a-tooltip :title="t('layout.setting.colors.' + item.name)"> + <div class="ele-setting-color-tooltip"></div> + </a-tooltip> + </div> + <!-- 颜色选择器 --> + <ele-color-picker + v-model:value="colorValue" + :predefine="predefineColors" + custom-class="ele-setting-color-picker" + @change="updateColor" + /> + </div> + <!-- 暗黑模式 --> + <div class="ele-setting-item"> + <div class="setting-item-title">{{ t('layout.setting.darkMode') }}</div> + <div class="setting-item-control"> + <a-switch size="small" :checked="darkMode" @change="updateDarkMode" /> + </div> + </div> + <a-divider /> + <!-- 导航布局 --> + <div + :class="[ + 'ele-setting-title ele-text-secondary', + { 'hidden-xs-only': styleResponsive } + ]" + > + {{ t('layout.setting.layoutStyle') }} + </div> + <div + :class="[ + 'ele-setting-theme ele-text-primary', + { 'hidden-xs-only': styleResponsive } + ]" + > + <a-tooltip :title="t('layout.setting.layoutStyles.side')"> + <div + class="ele-bg-base ele-side-dark" + @click="updateLayoutStyle('side')" + > + <check-outlined v-if="layoutStyle === 'side'" /> + </div> + </a-tooltip> + <a-tooltip :title="t('layout.setting.layoutStyles.top')"> + <div + class="ele-bg-base ele-head-dark ele-layout-top" + @click="updateLayoutStyle('top')" + > + <check-outlined v-if="layoutStyle === 'top'" /> + </div> + </a-tooltip> + <a-tooltip :title="t('layout.setting.layoutStyles.mix')"> + <div + class="ele-bg-base ele-layout-mix" + @click="updateLayoutStyle('mix')" + > + <check-outlined v-if="layoutStyle === 'mix'" /> + </div> + </a-tooltip> + </div> + <!-- 侧栏菜单布局 --> + <div + v-if="layoutStyle !== 'top'" + :class="['ele-setting-item', { 'hidden-xs-only': styleResponsive }]" + > + <div class="setting-item-title"> + {{ t('layout.setting.sideMenuStyle') }} + </div> + <div class="setting-item-control"> + <a-switch + size="small" + :checked="sideMenuStyle === 'mix'" + @change="updateSideMenuStyle" + /> + </div> + </div> + <!-- 内容区域定宽 --> + <div :class="['ele-setting-item', { 'hidden-xs-only': styleResponsive }]"> + <div class="setting-item-title"> + {{ t('layout.setting.bodyFull') }} + </div> + <div class="setting-item-control"> + <a-switch + size="small" + :checked="!bodyFull" + @change="updateBodyFull" + /> + </div> + </div> + <a-divider :class="{ 'hidden-xs-only': styleResponsive }" /> + <div class="ele-setting-title ele-text-secondary"> + {{ t('layout.setting.other') }} + </div> + <!-- 固定主体 --> + <div class="ele-setting-item"> + <div class="setting-item-title"> + {{ t('layout.setting.fixedBody') }} + </div> + <div class="setting-item-control"> + <a-switch + size="small" + :checked="fixedBody" + @change="updateFixedBody" + /> + </div> + </div> + <!-- 固定顶栏 --> + <div class="ele-setting-item"> + <div class="setting-item-title"> + {{ t('layout.setting.fixedHeader') }} + </div> + <div class="setting-item-control"> + <a-switch + size="small" + :disabled="fixedBody" + :checked="fixedHeader" + @change="updateFixedHeader" + /> + </div> + </div> + <!-- 固定侧栏 --> + <div + v-if="layoutStyle !== 'top'" + :class="['ele-setting-item', { 'hidden-xs-only': styleResponsive }]" + > + <div class="setting-item-title"> + {{ t('layout.setting.fixedSidebar') }} + </div> + <div class="setting-item-control"> + <a-switch + size="small" + :disabled="fixedBody" + :checked="fixedSidebar" + @change="updateFixedSidebar" + /> + </div> + </div> + <!-- logo 置于顶栏 --> + <div + v-if="layoutStyle !== 'top'" + :class="['ele-setting-item', { 'hidden-xs-only': styleResponsive }]" + > + <div class="setting-item-title"> + {{ t('layout.setting.logoAutoSize') }} + </div> + <div class="setting-item-control"> + <a-switch + size="small" + :checked="logoAutoSize" + @change="updateLogoAutoSize" + /> + </div> + </div> + <!-- 移动端响应式 --> + <div class="ele-setting-item"> + <div class="setting-item-title"> + {{ t('layout.setting.styleResponsive') }} + </div> + <div class="setting-item-control"> + <a-switch + size="small" + :checked="styleResponsive" + @change="updateStyleResponsive" + /> + </div> + </div> + <!-- 侧栏彩色图标 --> + <div v-if="layoutStyle !== 'top'" class="ele-setting-item"> + <div class="setting-item-title"> + {{ t('layout.setting.colorfulIcon') }} + </div> + <div class="setting-item-control"> + <a-switch + size="small" + :checked="colorfulIcon" + @change="updateColorfulIcon" + /> + </div> + </div> + <!-- 侧栏排他展开 --> + <div v-if="layoutStyle !== 'top'" class="ele-setting-item"> + <div class="setting-item-title"> + {{ t('layout.setting.sideUniqueOpen') }} + </div> + <div class="setting-item-control"> + <a-switch + size="small" + :checked="sideUniqueOpen" + @change="updateSideUniqueOpen" + /> + </div> + </div> + <!-- 全局页脚 --> + <div class="ele-setting-item"> + <div class="setting-item-title"> + {{ t('layout.setting.showFooter') }} + </div> + <div class="setting-item-control"> + <a-switch + size="small" + :checked="showFooter" + @change="updateShowFooter" + /> + </div> + </div> + <!-- 色弱模式 --> + <div class="ele-setting-item"> + <div class="setting-item-title">{{ t('layout.setting.weakMode') }}</div> + <div class="setting-item-control"> + <a-switch size="small" :checked="weakMode" @change="updateWeakMode" /> + </div> + </div> + <!-- 页签 --> + <div class="ele-setting-item"> + <div class="setting-item-title">{{ t('layout.setting.showTabs') }}</div> + <div class="setting-item-control"> + <a-switch size="small" :checked="showTabs" @change="updateShowTabs" /> + </div> + </div> + <!-- 页签风格 --> + <div v-if="showTabs" class="ele-setting-item"> + <div class="setting-item-title">{{ t('layout.setting.tabStyle') }}</div> + <div class="setting-item-control"> + <a-select + size="small" + :value="tabStyle" + style="width: 80px" + @change="updateTabStyle" + > + <a-select-option value="default"> + {{ t('layout.setting.tabStyles.default') }} + </a-select-option> + <a-select-option value="dot"> + {{ t('layout.setting.tabStyles.dot') }} + </a-select-option> + <a-select-option value="card"> + {{ t('layout.setting.tabStyles.card') }} + </a-select-option> + </a-select> + </div> + </div> + <!-- 切换动画 --> + <div class="ele-setting-item"> + <div class="setting-item-title"> + {{ t('layout.setting.transitionName') }} + </div> + <div class="setting-item-control"> + <a-select + size="small" + :value="transitionName" + style="width: 100px" + @change="updateTransitionName" + > + <a-select-option value="slide-right"> + {{ t('layout.setting.transitions.slideRight') }} + </a-select-option> + <a-select-option value="slide-bottom"> + {{ t('layout.setting.transitions.slideBottom') }} + </a-select-option> + <a-select-option value="zoom-in"> + {{ t('layout.setting.transitions.zoomIn') }} + </a-select-option> + <a-select-option value="zoom-out"> + {{ t('layout.setting.transitions.zoomOut') }} + </a-select-option> + <a-select-option value="fade"> + {{ t('layout.setting.transitions.fade') }} + </a-select-option> + </a-select> + </div> + </div> + <!-- 提示 --> + <a-divider /> + <a-alert show-icon type="warning" :message="t('layout.setting.tips')"> + <template #icon> + <sound-outlined /> + </template> + </a-alert> + <!-- 重置 --> + <a-button block type="dashed" @click="resetSetting"> + {{ t('layout.setting.reset') }} + </a-button> + </div> + </a-drawer> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { useI18n } from 'vue-i18n'; + import { storeToRefs } from 'pinia'; + import { message } from 'ant-design-vue/es'; + import { CheckOutlined, SoundOutlined } from '@ant-design/icons-vue'; + import { messageLoading } from 'ele-admin-pro/es'; + import type { + ThemeItem, + HeadStyleType, + SideStyleType, + LayoutStyleType, + TabStyleType + } from 'ele-admin-pro/es'; + import { useThemeStore } from '@/store/modules/theme'; + + defineProps<{ + // drawer 是否显示, v-model + visible: boolean; + }>(); + + const emit = defineEmits<{ + (e: 'update:visible', value: boolean): void; + }>(); + + const { t } = useI18n(); + const themeStore = useThemeStore(); + + const { + showTabs, + showFooter, + headStyle, + sideStyle, + layoutStyle, + sideMenuStyle, + tabStyle, + transitionName, + fixedHeader, + fixedSidebar, + fixedBody, + bodyFull, + logoAutoSize, + colorfulIcon, + sideUniqueOpen, + styleResponsive, + weakMode, + darkMode, + color + } = storeToRefs(themeStore); + + // 主题列表 + const themes = ref<ThemeItem[]>([ + { + name: 'default', + color: '#1890ff' + }, + { + name: 'dust', + value: '#5f80c7' + }, + { + name: 'sunset', + value: '#faad14' + }, + { + name: 'volcano', + value: '#f5686f' + }, + { + name: 'purple', + value: '#9266f9' + }, + { + name: 'green', + value: '#33cc99' + }, + { + name: 'geekblue', + value: '#32a2d4' + } + ]); + + // 颜色选择器预设颜色 + const predefineColors = ref<string[]>([ + '#f5222d', + '#fa541c', + '#fa8c16', + '#faad14', + '#a0d911', + '#52c41a', + '#13c2c2', + '#2f54eb', + '#722ed1', + '#eb2f96' + ]); + + // 颜色选择器选中颜色 + const colorValue = ref<string | undefined>(void 0); + + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + const updateShowTabs = (value: boolean) => { + themeStore.setShowTabs(value); + }; + + const updateShowFooter = (value: boolean) => { + themeStore.setShowFooter(value); + }; + + const updateHeadStyle = (value: HeadStyleType) => { + themeStore.setHeadStyle(value); + }; + + const updateSideStyle = (value: SideStyleType) => { + themeStore.setSideStyle(value); + }; + + const updateLayoutStyle = (value: LayoutStyleType) => { + themeStore.setLayoutStyle(value); + }; + + const updateSideMenuStyle = (value: boolean) => { + themeStore.setSideMenuStyle(value ? 'mix' : 'default'); + }; + + const updateTabStyle = (value: TabStyleType) => { + themeStore.setTabStyle(value); + }; + + const updateTransitionName = (value: string) => { + themeStore.setTransitionName(value); + }; + + const updateFixedHeader = (value: boolean) => { + themeStore.setFixedHeader(value); + }; + + const updateFixedSidebar = (value: boolean) => { + themeStore.setFixedSidebar(value); + }; + + const updateFixedBody = (value: boolean) => { + themeStore.setFixedBody(value); + }; + + const updateBodyFull = (value: boolean) => { + themeStore.setBodyFull(!value); + }; + + const updateLogoAutoSize = (value: boolean) => { + themeStore.setLogoAutoSize(value); + }; + + const updateStyleResponsive = (value: boolean) => { + themeStore.setStyleResponsive(value); + updateVisible(false); + }; + + const updateColorfulIcon = (value: boolean) => { + themeStore.setColorfulIcon(value); + }; + + const updateSideUniqueOpen = (value: boolean) => { + themeStore.setSideUniqueOpen(value); + }; + + const updateWeakMode = (value: boolean) => { + themeStore.setWeakMode(value); + }; + + const updateDarkMode = (value: boolean) => { + doWithLoading(() => themeStore.setDarkMode(value)); + }; + + const updateColor = (value?: string) => { + doWithLoading(() => themeStore.setColor(value)); + }; + + const resetSetting = () => { + doWithLoading(() => themeStore.resetSetting()); + }; + + const doWithLoading = (fun: () => Promise<void>) => { + const hide = messageLoading('正在加载主题..', 0); + setTimeout(() => { + fun() + .then(() => { + hide(); + initColorValue(); + }) + .catch((e) => { + hide(); + console.error(e); + message.error('主题加载失败'); + }); + }, 0); + }; + + const initColorValue = () => { + if (color?.value && !themes.value.some((t) => t.value === color.value)) { + colorValue.value = color.value; + } else { + colorValue.value = void 0; + } + }; + + initColorValue(); +</script> + +<style lang="less"> + .ele-setting-wrapper { + padding: 20px 18px; + + .ele-setting-title { + font-size: 13px; + margin-bottom: 15px; + } + + /* 主题风格 */ + .ele-setting-theme > div { + width: 52px; + height: 36px; + line-height: 1; + border-radius: 3px; + margin: 0 20px 30px 0; + padding: 16px 0 0 26px; + box-sizing: border-box; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15); + display: inline-block; + vertical-align: top; + position: relative; + overflow: hidden; + cursor: pointer; + transition: background-color 0.2s; + + &:before, + &:after, + & > .ele-bg-primary { + content: ''; + width: 100%; + height: 10px; + background: #fff; + position: absolute; + left: 0; + top: 0; + transition: background-color 0.2s; + } + + &:after { + width: 14px; + height: 100%; + } + + &.ele-side-dark:after, + &.ele-head-dark:before, + &.ele-layout-mix:before, + &.ele-layout-mix:after { + background: #001529; + } + + &.ele-head-light:before, + &.ele-head-dark:before, + & > .ele-bg-primary { + z-index: 1; + } + + &.ele-layout-top { + padding-left: 19px; + + &:after { + display: none; + } + } + } + + /* 主题色选择 */ + .ele-setting-colors { + color: #fff; + margin-bottom: 20px; + } + + .ele-setting-color-item { + width: 20px; + height: 20px; + line-height: 20px; + border-radius: 2px; + margin: 8px 8px 0 0; + display: inline-block; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15); + vertical-align: top; + position: relative; + text-align: center; + cursor: pointer; + + .ele-setting-color-tooltip { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + } + } + + /* 主题配置项 */ + .ele-setting-item { + display: flex; + align-items: center; + margin-bottom: 20px; + + .setting-item-title { + flex: 1; + line-height: 28px; + } + + .setting-item-control { + line-height: 1; + } + } + + .ant-divider { + margin-bottom: 20px; + } + + .ant-alert + .ant-btn { + margin-top: 12px; + } + + /* 暗黑模式 */ + &.ele-setting-dark .ele-setting-theme > div { + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.55); + + &:before, + &:after, + & > .ele-bg-primary { + background: #1f1f1f; + } + + &.ele-side-dark:after, + &.ele-head-dark:before, + &.ele-layout-mix:before, + &.ele-layout-mix:after { + background: #262626; + } + } + } + + /* 颜色选择器 */ + .ele-setting-color-picker.ele-color-picker-trigger { + padding: 0; + width: 20px; + height: 20px; + margin-top: 8px; + border: none !important; + background: none !important; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15); + + & > .ele-color-picker-trigger-inner { + background: none; + + &.is-empty { + background: conic-gradient( + from 90deg at 50% 50%, + rgb(255, 0, 0) -19.41deg, + rgb(255, 0, 0) 18.76deg, + rgb(255, 138, 0) 59.32deg, + rgb(255, 230, 0) 99.87deg, + rgb(20, 255, 0) 141.65deg, + rgb(0, 163, 255) 177.72deg, + rgb(5, 0, 255) 220.23deg, + rgb(173, 0, 255) 260.13deg, + rgb(255, 0, 199) 300.69deg, + rgb(255, 0, 0) 340.59deg, + rgb(255, 0, 0) 378.76deg + ); + + & + .ele-color-picker-trigger-arrow { + display: none; + } + } + } + } +</style> diff --git a/src/layout/index.vue b/src/layout/index.vue new file mode 100644 index 0000000..749ba16 --- /dev/null +++ b/src/layout/index.vue @@ -0,0 +1,343 @@ +<template> + <ele-pro-layout + :menus="menus" + :tabs="tabs" + :collapse="collapse" + :side-nav-collapse="sideNavCollapse" + :body-fullscreen="bodyFullscreen" + :show-tabs="showTabs" + :show-footer="showFooter" + :head-style="headStyle" + :side-style="sideStyle" + :layout-style="layoutStyle" + :side-menu-style="sideMenuStyle" + :tab-style="tabStyle" + :fixed-header="fixedHeader" + :fixed-sidebar="fixedSidebar" + :fixed-body="fixedBody" + :body-full="bodyFull" + :logo-auto-size="logoAutoSize" + :colorful-icon="colorfulIcon" + :side-unique-open="sideUniqueOpen" + :style-responsive="styleResponsive" + :project-name="projectName" + :hide-footers="HIDE_FOOTERS" + :hide-sidebars="HIDE_SIDEBARS" + :repeatable-tabs="REPEATABLE_TABS" + :home-title="HOME_TITLE || t('layout.home')" + :home-path="HOME_PATH" + :layout-path="LAYOUT_PATH" + :redirect-path="REDIRECT_PATH" + :tab-sortable="true" + :locale="locale" + :i18n="i18n" + @update:collapse="updateCollapse" + @update:side-nav-collapse="updateSideNavCollapse" + @update:body-fullscreen="updateBodyFullscreen" + @tab-add="addPageTab" + @tab-remove="removePageTab" + @tab-remove-all="removeAllPageTab" + @tab-remove-left="removeLeftPageTab" + @tab-remove-right="removeRightPageTab" + @tab-remove-other="removeOtherPageTab" + @tabSortChange="setPageTabs" + @reload-page="reloadPageTab" + @logo-click="onLogoClick" + @screen-size-change="screenSizeChange" + @set-home-components="setHomeComponents" + @tab-context-menu="onTabContextMenu" + > + <!-- 路由出口 --> + <router-layout /> + <!-- logo 图标 --> + <template #logo> + <img src="/src/assets/logo.svg" alt="logo" /> + </template> + <!-- 顶栏右侧区域 --> + <template #right> + <header-tools :fullscreen="fullscreen" @fullscreen="onFullscreen" /> + </template> + <!-- 全局页脚 --> + <template #footer> + <page-footer /> + </template> + <!-- 菜单图标 --> + <template #icon="{ icon }"> + <component :is="icon" class="ant-menu-item-icon" /> + </template> + <!-- 自定义菜单标题增加徽章、小红点 --> + <template #title="{ item }"> + <menu-title :item="item" /> + </template> + <template #top-title="{ item }"> + <menu-title :item="item" /> + </template> + <template #nav-title="{ item }"> + <menu-title :item="item" /> + </template> + </ele-pro-layout> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { useRouter } from 'vue-router'; + import { storeToRefs } from 'pinia'; + import { useI18n } from 'vue-i18n'; + import { message } from 'ant-design-vue/es'; + import { toggleFullscreen, isFullscreen } from 'ele-admin-pro/es'; + import { useUserStore } from '@/store/modules/user'; + import { useThemeStore } from '@/store/modules/theme'; + import RouterLayout from '@/components/RouterLayout/index.vue'; + import HeaderTools from './components/header-tools.vue'; + import PageFooter from './components/page-footer.vue'; + import MenuTitle from './components/menu-title.vue'; + import { + HIDE_SIDEBARS, + HIDE_FOOTERS, + REPEATABLE_TABS, + HOME_TITLE, + HOME_PATH, + LAYOUT_PATH, + REDIRECT_PATH, + I18N_ENABLE + } from '@/config/setting'; + import { + addPageTab, + removePageTab, + removeAllPageTab, + removeLeftPageTab, + removeRightPageTab, + removeOtherPageTab, + reloadPageTab, + setHomeComponents, + setPageTabs + } from '@/utils/page-tab-util'; + import type { TabCtxMenuOption } from 'ele-admin-pro/es/ele-pro-layout/types'; + + const { push } = useRouter(); + const { t, locale } = useI18n(); + const userStore = useUserStore(); + const themeStore = useThemeStore(); + + // 项目名 + const projectName = import.meta.env.VITE_APP_NAME as string; + + // 是否全屏 + const fullscreen = ref(false); + + // 菜单数据 + const { menus } = storeToRefs(userStore); + + // 布局风格 + const { + tabs, + collapse, + sideNavCollapse, + bodyFullscreen, + showTabs, + showFooter, + headStyle, + sideStyle, + layoutStyle, + sideMenuStyle, + tabStyle, + fixedHeader, + fixedSidebar, + fixedBody, + bodyFull, + logoAutoSize, + colorfulIcon, + sideUniqueOpen, + styleResponsive + } = storeToRefs(themeStore); + + /* 侧栏折叠切换 */ + const updateCollapse = (value: boolean) => { + themeStore.setCollapse(value); + }; + + /* 双侧栏一级折叠切换 */ + const updateSideNavCollapse = (value: boolean) => { + themeStore.setSideNavCollapse(value); + }; + + /* 内容区域全屏切换 */ + const updateBodyFullscreen = (value: boolean) => { + themeStore.setBodyFullscreen(value); + }; + + /* logo 点击事件 */ + const onLogoClick = (isHome: boolean) => { + isHome || push(LAYOUT_PATH); + }; + + /* 监听屏幕尺寸改变 */ + const screenSizeChange = () => { + themeStore.updateScreenSize(); + fullscreen.value = isFullscreen(); + }; + + /* 全屏切换 */ + const onFullscreen = () => { + try { + fullscreen.value = toggleFullscreen(); + } catch (e) { + message.error('您的浏览器不支持全屏模式'); + } + }; + + /* 页签右键菜单点击事件 */ + const onTabContextMenu = ({ + key, + tabKey, + item, + active + }: TabCtxMenuOption) => { + switch (key) { + case 'reload': // 刷新 + reloadPageTab({ + isHome: !item, + fullPath: item?.fullPath ?? tabKey + }); + break; + case 'close': // 关闭当前 + removePageTab({ + key: item?.fullPath ?? tabKey, + active + }); + break; + case 'left': // 关闭左侧 + removeLeftPageTab({ + key: tabKey, + active + }); + break; + case 'right': // 关闭右侧 + removeRightPageTab({ + key: tabKey, + active + }); + break; + case 'other': // 关闭其他 + removeOtherPageTab({ + key: tabKey, + active + }); + break; + } + }; + + /* 菜单标题国际化 */ + const i18n = (_path: string, key?: string) => { + if (!I18N_ENABLE || !key) { + return; + } + const k = 'route.' + key + '._name'; + const title = t(k); + if (title !== k) { + return title; + } + }; +</script> + +<script lang="ts"> + import * as MenuIcons from './menu-icons'; + + export default { + name: 'EleLayout', + components: MenuIcons + }; +</script> + +<style lang="less"> + // 侧栏菜单徽章样式,定位在右侧垂直居中并调小尺寸 + .ele-menu-badge { + position: absolute; + top: 50%; + right: 14px; + line-height: 1; + margin-top: -9px; + font-size: 0; + + .ant-badge-count { + height: 18px; + line-height: 18px; + border-radius: 9px; + box-shadow: none; + min-width: 18px; + padding: 0 4px; + } + + .ant-scroll-number-only { + height: 18px; + + & > p.ant-scroll-number-only-unit { + height: 18px; + } + } + } + + // 父级菜单标题中右侧多定位一点,避免与箭头重合 + .ant-menu-submenu-title > .ant-menu-title-content .ele-menu-badge { + right: 36px; + } + + // 折叠悬浮中样式调整 + .ant-menu-submenu-popup { + .ant-menu-submenu-title > .ant-menu-title-content .ele-menu-badge { + right: 30px; + } + } + + // 顶栏菜单标题中样式调整 + .ele-admin-header-nav > .ant-menu { + & > .ant-menu-item, + & > .ant-menu-submenu > .ant-menu-submenu-title { + & > .ant-menu-title-content .ele-menu-badge { + position: static; + right: auto; + top: auto; + display: inline-block; + vertical-align: 5px; + margin: 0 0 0 4px; + } + } + } + + // 双侧栏时一级侧栏菜单中样式调整,定位在右上角 + .ele-admin-sidebar-nav-menu > .ant-menu { + & > .ant-menu-item, + & > .ant-menu-submenu > .ant-menu-submenu-title { + & > .ant-menu-title-content .ele-menu-badge { + top: 0; + right: 0; + margin: 0; + } + } + } + + // 双侧栏时一级侧栏菜单折叠后样式调整 + .ele-admin-nav-collapse .ele-admin-sidebar-nav-menu > .ant-menu { + & > .ant-menu-item, + & > .ant-menu-submenu > .ant-menu-submenu-title { + & > .ant-menu-title-content .ele-menu-badge { + top: 0; + right: 0; + } + } + } + + // 菜单折叠后在 tooltip 中不显示徽章 + .ant-tooltip-inner .ele-menu-badge { + display: none; + } + + // 分组菜单标题 + .ant-menu-item-group-title { + position: relative; + font-size: 12px !important; + background: rgba(0, 0, 0, 0.02); + padding-top: 4px !important; + padding-bottom: 4px !important; + } +</style> diff --git a/src/layout/menu-icons.ts b/src/layout/menu-icons.ts new file mode 100644 index 0000000..c3a45e4 --- /dev/null +++ b/src/layout/menu-icons.ts @@ -0,0 +1,48 @@ +/** 菜单用到的图标 */ +export { + HomeOutlined, + SettingOutlined, + TeamOutlined, + DesktopOutlined, + FileTextOutlined, + TableOutlined, + AppstoreOutlined, + CheckCircleOutlined, + ExclamationCircleOutlined, + UserOutlined, + TagOutlined, + IdcardOutlined, + BarChartOutlined, + AuditOutlined, + PicLeftOutlined, + CloseCircleOutlined, + QuestionCircleOutlined, + SoundOutlined, + ApartmentOutlined, + DashboardOutlined, + OneToOneOutlined, + DragOutlined, + InteractionOutlined, + BankOutlined, + BlockOutlined, + CheckSquareOutlined, + ProfileOutlined, + WarningOutlined, + FolderOutlined, + YoutubeOutlined, + ControlOutlined, + EllipsisOutlined, + CalendarOutlined, + AppstoreAddOutlined, + FileSearchOutlined, + EnvironmentOutlined, + CompassOutlined, + FontSizeOutlined, + SketchOutlined, + BgColorsOutlined, + PrinterOutlined, + QrcodeOutlined, + BarcodeOutlined, + PictureOutlined, + LinkOutlined +} from '@ant-design/icons-vue'; diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..876546c --- /dev/null +++ b/src/main.ts @@ -0,0 +1,16 @@ +import { createApp } from 'vue'; +import App from './App.vue'; +import store from './store'; +import router from './router'; +import permission from './utils/permission'; +import i18n from './i18n'; +import './styles/index.less'; + +const app = createApp(App); + +app.use(store); +app.use(router); +app.use(permission); +app.use(i18n); + +app.mount('#app'); diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..7bb8228 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,63 @@ +/** + * 路由配置 + */ +import NProgress from 'nprogress'; +import type { _RouteLocationBase } from 'vue-router'; +import { createRouter, createWebHistory } from 'vue-router'; +import { WHITE_LIST, REDIRECT_PATH, LAYOUT_PATH } from '@/config/setting'; +import { useUserStore } from '@/store/modules/user'; +import { getToken } from '@/utils/token-util'; +import { routes, getMenuRoutes } from './routes'; + +NProgress.configure({ + speed: 200, + minimum: 0.02, + trickleSpeed: 200, + showSpinner: false +}); + +const router = createRouter({ + routes, + history: createWebHistory(), + scrollBehavior() { + return { top: 0 }; + } +}); + +/** + * 路由守卫 + */ +router.beforeEach(async (to, from) => { + if (!from.path.includes(REDIRECT_PATH)) { + NProgress.start(); + } + if (!getToken()) { + // 未登录跳转登录界面 + if (!WHITE_LIST.includes(to.path)) { + return { + path: '/login', + query: to.path === LAYOUT_PATH ? {} : { from: to.path } + }; + } + return; + } + // 注册动态路由 + const userStore = useUserStore(); + if (!userStore.menus) { + const { menus, homePath } = await userStore.fetchUserInfo(); + if (menus) { + router.addRoute(getMenuRoutes(menus, homePath)); + return { ...to, replace: true }; + } + } +}); + +router.afterEach((to) => { + if (!to.path.includes(REDIRECT_PATH) && NProgress.isStarted()) { + setTimeout(() => { + NProgress.done(true); + }, 200); + } +}); + +export default router; diff --git a/src/router/routes.ts b/src/router/routes.ts new file mode 100644 index 0000000..9b3a8d7 --- /dev/null +++ b/src/router/routes.ts @@ -0,0 +1,68 @@ +import type { RouteRecordRaw } from 'vue-router'; +import type { MenuItemType } from 'ele-admin-pro/es'; +import { menuToRoutes, eachTreeData } from 'ele-admin-pro/es'; +import { HOME_PATH, LAYOUT_PATH, REDIRECT_PATH } from '@/config/setting'; +import EleLayout from '@/layout/index.vue'; +import RedirectLayout from '@/components/RedirectLayout'; +const modules = import.meta.glob('/src/views/**/index.vue'); + +/** + * 静态路由 + */ +export const routes = [ + { + path: '/login', + component: () => import('@/views/login/index.vue'), + meta: { title: '登录' } + }, + { + path: '/forget', + component: () => import('@/views/forget/index.vue'), + meta: { title: '忘记密码' } + }, + // 404 + { + path: '/:path(.*)*', + component: () => import('@/views/exception/404/index.vue') + } +]; + +/** + * 动态路由 + * @param menus 菜单数据 + * @param homePath 主页地址 + */ +export function getMenuRoutes(menus?: MenuItemType[], homePath?: string) { + const routes: RouteRecordRaw[] = [ + // 用于刷新的路由 + { + path: REDIRECT_PATH + '/:path(.*)', + component: RedirectLayout, + meta: { hideFooter: true } + } + ]; + // 路由铺平处理 + eachTreeData(menuToRoutes(menus, getComponent), (route) => { + routes.push(Object.assign({}, route, { children: void 0 })); + }); + return { + path: LAYOUT_PATH, + component: EleLayout, + redirect: HOME_PATH ?? homePath, + children: routes + }; +} + +/** + * 解析路由组件 + * @param component 组件名称 + */ +function getComponent(component?: string) { + if (component) { + const module = modules[`/src/views/${component}.vue`]; + if (!module) { + return modules[`/src/views/${component}/index.vue`]; + } + return module; + } +} diff --git a/src/shims-vue.d.ts b/src/shims-vue.d.ts new file mode 100644 index 0000000..fe7917e --- /dev/null +++ b/src/shims-vue.d.ts @@ -0,0 +1,6 @@ +declare module '*.vue' { + import { DefineComponent } from 'vue'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any>; + export default component; +} diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..bf43b30 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,6 @@ +/** + * pinia + */ +import { createPinia } from 'pinia'; + +export default createPinia(); diff --git a/src/store/modules/theme.ts b/src/store/modules/theme.ts new file mode 100644 index 0000000..8674cdb --- /dev/null +++ b/src/store/modules/theme.ts @@ -0,0 +1,690 @@ +/** + * 主题状态管理 + */ +import { defineStore } from 'pinia'; +import { + changeColor, + screenWidth, + screenHeight, + contentWidth, + contentHeight, + WEAK_CLASS, + BODY_LIMIT_CLASS, + DISABLES_CLASS +} from 'ele-admin-pro/es'; +import type { + TabItem, + HeadStyleType, + SideStyleType, + LayoutStyleType, + SideMenuStyleType, + TabStyleType, + TabRemoveOption +} from 'ele-admin-pro/es'; +import { + TAB_KEEP_ALIVE, + KEEP_ALIVE_EXCLUDES, + THEME_STORE_NAME +} from '@/config/setting'; + +/** + * state 默认值 + */ +const DEFAULT_STATE: ThemeState = Object.freeze({ + // 页签数据 + tabs: [], + // 是否折叠侧栏 + collapse: false, + // 是否折叠一级侧栏 + sideNavCollapse: false, + // 内容区域是否全屏 + bodyFullscreen: false, + // 是否开启页签栏 + showTabs: true, + // 是否开启页脚 + showFooter: true, + // 顶栏风格: light(亮色), dark(暗色), primary(主色) + headStyle: 'light', + // 侧栏风格: light(亮色), dark(暗色) + sideStyle: 'dark', + // 布局风格: side(默认), top(顶栏导航), mix(混合导航) + layoutStyle: 'side', + // 侧栏菜单风格: default(默认), mix(双排侧栏) + sideMenuStyle: 'default', + // 页签风格: default(默认), dot(圆点), card(卡片) + tabStyle: 'default', + // 路由切换动画 + transitionName: 'slide-right', + // 是否固定顶栏 + fixedHeader: false, + // 是否固定侧栏 + fixedSidebar: true, + // 是否固定主体 + fixedBody: true, + // 内容区域宽度铺满 + bodyFull: true, + // logo 是否自适应宽度 + logoAutoSize: false, + // 侧栏是否彩色图标 + colorfulIcon: false, + // 侧栏是否只保持一个子菜单展开 + sideUniqueOpen: true, + // 是否是色弱模式 + weakMode: false, + // 是否是暗黑模式 + darkMode: false, + // 主题色 + color: null, + // 主页的组件名称 + homeComponents: [], + // 刷新路由时的参数 + routeReload: null, + // 屏幕宽度 + screenWidth: screenWidth(), + // 屏幕高度 + screenHeight: screenHeight(), + // 内容区域宽度 + contentWidth: contentWidth(), + // 内容区域高度 + contentHeight: contentHeight(), + // 是否开启响应式 + styleResponsive: true +}); +// 延时操作定时器 +let disableTransitionTimer: number, updateContentSizeTimer: number; + +/** + * 读取缓存配置 + */ +function getCacheSetting(): any { + try { + const value = localStorage.getItem(THEME_STORE_NAME); + if (value) { + const cache = JSON.parse(value); + if (typeof cache === 'object') { + return cache; + } + } + } catch (e) { + console.error(e); + } + return {}; +} + +/** + * 缓存配置 + */ +function cacheSetting(key: string, value: any) { + const cache = getCacheSetting(); + if (cache[key] !== value) { + cache[key] = value; + localStorage.setItem(THEME_STORE_NAME, JSON.stringify(cache)); + } +} + +/** + * 开关响应式布局 + */ +function changeStyleResponsive(styleResponsive: boolean) { + if (styleResponsive) { + document.body.classList.remove(BODY_LIMIT_CLASS); + } else { + document.body.classList.add(BODY_LIMIT_CLASS); + } +} + +/** + * 切换色弱模式 + */ +function changeWeakMode(weakMode: boolean) { + if (weakMode) { + document.body.classList.add(WEAK_CLASS); + } else { + document.body.classList.remove(WEAK_CLASS); + } +} + +/** + * 切换主题 + */ +function changeTheme(value?: string | null, dark?: boolean) { + return new Promise<void>((resolve, reject) => { + try { + changeColor(value, dark); + resolve(); + } catch (e) { + reject(e); + } + }); +} + +/** + * 切换布局时禁用过渡动画 + */ +function disableTransition() { + disableTransitionTimer && clearTimeout(disableTransitionTimer); + document.body.classList.add(DISABLES_CLASS); + disableTransitionTimer = setTimeout(() => { + document.body.classList.remove(DISABLES_CLASS); + }, 100) as unknown as number; +} + +export const useThemeStore = defineStore({ + id: 'theme', + state: (): ThemeState => { + const state = { ...DEFAULT_STATE }; + // 读取本地缓存 + const cache = getCacheSetting(); + Object.keys(state).forEach((key) => { + if (typeof cache[key] !== 'undefined') { + state[key] = cache[key]; + } + }); + return state; + }, + getters: { + // 需要 keep-alive 的组件 + keepAliveInclude(): string[] { + if (!TAB_KEEP_ALIVE || !this.showTabs) { + return []; + } + const components = new Set<string>(); + const { reloadPath, reloadHome } = this.routeReload || {}; + this.tabs?.forEach((t) => { + const isAlive = t.meta?.keepAlive !== false; + const isExclude = KEEP_ALIVE_EXCLUDES.includes(t.path as string); + const isReload = reloadPath && reloadPath === t.fullPath; + if (isAlive && !isExclude && !isReload && t.components) { + t.components.forEach((c) => { + if (typeof c === 'string' && c) { + components.add(c); + } + }); + } + }); + if (!reloadHome) { + this.homeComponents?.forEach((c) => { + if (typeof c === 'string' && c) { + components.add(c); + } + }); + } + return Array.from(components); + } + }, + actions: { + setTabs(value: TabItem[]) { + this.tabs = value; + //cacheSetting('tabs', value); + }, + setCollapse(value: boolean) { + this.collapse = value; + this.delayUpdateContentSize(800); + }, + setSideNavCollapse(value: boolean) { + this.sideNavCollapse = value; + this.delayUpdateContentSize(800); + }, + setBodyFullscreen(value: boolean) { + disableTransition(); + this.bodyFullscreen = value; + this.delayUpdateContentSize(800); + }, + setShowTabs(value: boolean) { + this.showTabs = value; + cacheSetting('showTabs', value); + this.delayUpdateContentSize(); + }, + setShowFooter(value: boolean) { + this.showFooter = value; + cacheSetting('showFooter', value); + this.delayUpdateContentSize(); + }, + setHeadStyle(value: HeadStyleType) { + this.headStyle = value; + cacheSetting('headStyle', value); + }, + setSideStyle(value: SideStyleType) { + this.sideStyle = value; + cacheSetting('sideStyle', value); + }, + setLayoutStyle(value: LayoutStyleType) { + disableTransition(); + this.layoutStyle = value; + cacheSetting('layoutStyle', value); + this.delayUpdateContentSize(); + }, + setSideMenuStyle(value: SideMenuStyleType) { + disableTransition(); + this.sideMenuStyle = value; + cacheSetting('sideMenuStyle', value); + this.delayUpdateContentSize(); + }, + setTabStyle(value: TabStyleType) { + this.tabStyle = value; + cacheSetting('tabStyle', value); + }, + setTransitionName(value: string) { + this.transitionName = value; + cacheSetting('transitionName', value); + }, + setFixedHeader(value: boolean) { + disableTransition(); + this.fixedHeader = value; + cacheSetting('fixedHeader', value); + }, + setFixedSidebar(value: boolean) { + disableTransition(); + this.fixedSidebar = value; + cacheSetting('fixedSidebar', value); + }, + setFixedBody(value: boolean) { + disableTransition(); + this.fixedBody = value; + cacheSetting('fixedBody', value); + }, + setBodyFull(value: boolean) { + this.bodyFull = value; + cacheSetting('bodyFull', value); + this.delayUpdateContentSize(); + }, + setLogoAutoSize(value: boolean) { + disableTransition(); + this.logoAutoSize = value; + cacheSetting('logoAutoSize', value); + }, + setColorfulIcon(value: boolean) { + this.colorfulIcon = value; + cacheSetting('colorfulIcon', value); + }, + setSideUniqueOpen(value: boolean) { + this.sideUniqueOpen = value; + cacheSetting('sideUniqueOpen', value); + }, + setStyleResponsive(value: boolean) { + changeStyleResponsive(value); + this.styleResponsive = value; + cacheSetting('styleResponsive', value); + }, + /** + * 切换色弱模式 + * @param value 是否是色弱模式 + */ + setWeakMode(value: boolean) { + return new Promise<void>((resolve) => { + changeWeakMode(value); + this.weakMode = value; + cacheSetting('weakMode', value); + resolve(); + }); + }, + /** + * 切换暗黑模式 + * @param value 是否是暗黑模式 + */ + setDarkMode(value: boolean) { + return new Promise<void>((resolve, reject) => { + changeTheme(this.color, value) + .then(() => { + this.darkMode = value; + cacheSetting('darkMode', value); + resolve(); + }) + .catch((e) => { + reject(e); + }); + }); + }, + /** + * 切换主题色 + * @param value 主题色 + */ + setColor(value?: string) { + return new Promise<void>((resolve, reject) => { + changeTheme(value, this.darkMode) + .then(() => { + this.color = value; + cacheSetting('color', value); + resolve(); + }) + .catch((e) => { + reject(e); + }); + }); + }, + /** + * 设置主页路由对应的组件名称 + * @param components 组件名称 + */ + setHomeComponents(components: string[]) { + this.homeComponents = components; + }, + /** + * 设置刷新路由信息 + * @param option 路由刷新参数 + */ + setRouteReload(option: RouteReloadOption | null) { + this.routeReload = option; + }, + /** + * 更新屏幕尺寸 + */ + updateScreenSize() { + this.screenWidth = screenWidth(); + this.screenHeight = screenHeight(); + this.updateContentSize(); + }, + /** + * 更新内容区域尺寸 + */ + updateContentSize() { + this.contentWidth = contentWidth(); + this.contentHeight = contentHeight(); + }, + /** + * 延时更新内容区域尺寸 + * @param delay 延迟时间 + */ + delayUpdateContentSize(delay?: number) { + updateContentSizeTimer && clearTimeout(updateContentSizeTimer); + updateContentSizeTimer = setTimeout(() => { + this.updateContentSize(); + }, delay ?? 100) as unknown as number; + }, + /** + * 重置设置 + */ + resetSetting() { + return new Promise<void>((resolve, reject) => { + disableTransition(); + this.showTabs = DEFAULT_STATE.showTabs; + this.showFooter = DEFAULT_STATE.showFooter; + this.headStyle = DEFAULT_STATE.headStyle; + this.sideStyle = DEFAULT_STATE.sideStyle; + this.layoutStyle = DEFAULT_STATE.layoutStyle; + this.sideMenuStyle = DEFAULT_STATE.sideMenuStyle; + this.tabStyle = DEFAULT_STATE.tabStyle; + this.transitionName = DEFAULT_STATE.transitionName; + this.fixedHeader = DEFAULT_STATE.fixedHeader; + this.fixedSidebar = DEFAULT_STATE.fixedSidebar; + this.fixedBody = DEFAULT_STATE.fixedBody; + this.bodyFull = DEFAULT_STATE.bodyFull; + this.logoAutoSize = DEFAULT_STATE.logoAutoSize; + this.colorfulIcon = DEFAULT_STATE.colorfulIcon; + this.sideUniqueOpen = DEFAULT_STATE.sideUniqueOpen; + this.styleResponsive = DEFAULT_STATE.styleResponsive; + this.weakMode = DEFAULT_STATE.weakMode; + this.darkMode = DEFAULT_STATE.darkMode; + this.color = DEFAULT_STATE.color; + localStorage.removeItem(THEME_STORE_NAME); + Promise.all([ + changeStyleResponsive(this.styleResponsive), + changeWeakMode(this.weakMode), + changeTheme(this.color, this.darkMode) + ]) + .then(() => { + resolve(); + }) + .catch((e) => { + reject(e); + }); + }); + }, + /** + * 恢复主题 + */ + recoverTheme() { + // 关闭响应式布局 + if (!this.styleResponsive) { + changeStyleResponsive(false); + } + // 恢复色弱模式 + if (this.weakMode) { + changeWeakMode(true); + } + // 恢复主题色 + if (this.color || this.darkMode) { + changeTheme(this.color, this.darkMode).catch((e) => { + console.error(e); + }); + } + }, + /** + * 添加页签或更新相同 key 的页签数据 + * @param data 页签数据 + */ + tabAdd(data: TabItem | TabItem[]) { + if (Array.isArray(data)) { + data.forEach((d) => { + this.tabAdd(d); + }); + return; + } + const i = this.tabs.findIndex((d) => d.key === data.key); + if (i === -1) { + this.setTabs(this.tabs.concat([data])); + } else if (data.fullPath !== this.tabs[i].fullPath) { + this.setTabs( + this.tabs + .slice(0, i) + .concat([data]) + .concat(this.tabs.slice(i + 1)) + ); + } + }, + /** + * 关闭页签 + * @param key 页签 key + */ + async tabRemove({ + key, + active + }: TabRemoveOption): Promise<TabRemoveResult> { + const i = this.tabs.findIndex((t) => t.key === key || t.fullPath === key); + if (i === -1) { + return {}; + } + const t = this.tabs[i]; + if (!t.closable) { + return Promise.reject(); + } + const path = this.tabs[i - 1]?.fullPath; + this.setTabs(this.tabs.filter((_d, j) => j !== i)); + return t.key === active ? { path, home: !path } : {}; + }, + /** + * 关闭左侧页签 + */ + async tabRemoveLeft({ + key, + active + }: TabRemoveOption): Promise<TabRemoveResult> { + let index = -1; // 选中页签的 index + for (let i = 0; i < this.tabs.length; i++) { + if (this.tabs[i].key === active) { + index = i; + } + if (this.tabs[i].key === key) { + if (i === 0) { + break; + } + const temp = this.tabs.filter((d, j) => !d.closable && j < i); + if (temp.length === i + 1) { + break; + } + const path = index === -1 ? void 0 : this.tabs[i].fullPath; + this.setTabs(temp.concat(this.tabs.slice(i))); + return { path }; + } + } + return Promise.reject(); + }, + /** + * 关闭右侧页签 + */ + async tabRemoveRight({ + key, + active + }: TabRemoveOption): Promise<TabRemoveResult> { + if (this.tabs.length) { + let index = -1; // 选中页签的 index + for (let i = 0; i < this.tabs.length; i++) { + if (this.tabs[i].key === active) { + index = i; + } + if (this.tabs[i].key === key) { + if (i === this.tabs.length - 1) { + return Promise.reject(); + } + const temp = this.tabs.filter((d, j) => !d.closable && j > i); + if (temp.length === this.tabs.length - i - 1) { + return Promise.reject(); + } + const path = index === -1 ? this.tabs[i].fullPath : void 0; + this.setTabs( + this.tabs + .slice(0, i + 1) + .concat(this.tabs.filter((d, j) => !d.closable && j > i)) + ); + return { path }; + } + } + // 主页时关闭全部 + const temp = this.tabs.filter((d) => !d.closable); + if (temp.length !== this.tabs.length) { + this.setTabs(temp); + return { home: index !== -1 }; + } + } + return Promise.reject(); + }, + /** + * 关闭其它页签 + */ + async tabRemoveOther({ + key, + active + }: TabRemoveOption): Promise<TabRemoveResult> { + let index = -1; // 选中页签的 index + let path: string | undefined; // 关闭后跳转的 path + const temp = this.tabs.filter((d, i) => { + if (d.key === active) { + index = i; + } + if (d.key === key) { + path = d.fullPath; + } + return !d.closable || d.key === key; + }); + if (temp.length === this.tabs.length) { + return Promise.reject(); + } + this.setTabs(temp); + if (index === -1) { + return {}; + } + return key === active ? {} : { path, home: !path }; + }, + /** + * 关闭全部页签 + * @param active 选中页签的 key + */ + async tabRemoveAll(active: string): Promise<TabRemoveResult> { + const t = this.tabs.find((d) => d.key === active); + const home = typeof t !== 'undefined' && t.closable === true; // 是否跳转主页 + const temp = this.tabs.filter((d) => !d.closable); + if (temp.length === this.tabs.length) { + return Promise.reject(); + } + this.setTabs(temp); + return { home }; + }, + /** + * 修改页签 + * @param data 页签数据 + */ + tabSetItem(data: TabItem) { + let i = -1; + if (data.key) { + i = this.tabs.findIndex((d) => d.key === data.key); + } else if (data.fullPath) { + i = this.tabs.findIndex((d) => d.fullPath === data.fullPath); + } else if (data.path) { + i = this.tabs.findIndex((d) => d.path === data.path); + } + if (i !== -1) { + const item = { ...this.tabs[i] }; + if (data.title) { + item.title = data.title; + } + if (typeof data.closable === 'boolean') { + item.closable = data.closable; + } + if (data.components) { + item.components = data.components; + } + this.setTabs( + this.tabs + .slice(0, i) + .concat([item]) + .concat(this.tabs.slice(i + 1)) + ); + } + } + } +}); + +/** + * 主题 State 类型 + */ +export interface ThemeState { + tabs: TabItem[]; + collapse: boolean; + sideNavCollapse: boolean; + bodyFullscreen: boolean; + showTabs: boolean; + showFooter: boolean; + headStyle: HeadStyleType; + sideStyle: SideStyleType; + layoutStyle: LayoutStyleType; + sideMenuStyle: SideMenuStyleType; + tabStyle: TabStyleType; + transitionName: string; + fixedHeader: boolean; + fixedSidebar: boolean; + fixedBody: boolean; + bodyFull: boolean; + logoAutoSize: boolean; + colorfulIcon: boolean; + sideUniqueOpen: boolean; + weakMode: boolean; + darkMode: boolean; + color?: string | null; + homeComponents: string[]; + routeReload: RouteReloadOption | null; + screenWidth: number; + screenHeight: number; + contentWidth: number; + contentHeight: number; + styleResponsive: boolean; +} + +/** + * 设置路由刷新方法的参数 + */ +export interface RouteReloadOption { + // 是否是刷新主页 + reloadHome?: boolean; + // 要刷新的页签路由地址 + reloadPath?: string; +} + +/** + * 关闭页签返回类型 + */ +export interface TabRemoveResult { + // 关闭后要跳转的地址 + path?: string; + // 关闭后是否跳转到主页 + home?: boolean; +} diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts new file mode 100644 index 0000000..9592267 --- /dev/null +++ b/src/store/modules/user.ts @@ -0,0 +1,93 @@ +/** + * 登录用户 store + */ +import { defineStore } from 'pinia'; +import { formatMenus, toTreeData, formatTreeData } from 'ele-admin-pro/es'; +import type { MenuItemType } from 'ele-admin-pro/es'; +import type { User } from '@/api/system/user/model'; +import { USER_MENUS } from '@/config/setting'; +import { getUserInfo } from '@/api/layout'; +const EXTRA_MENUS: any = []; + +export interface UserState { + info: User | null; + menus: MenuItemType[] | null | undefined; + authorities: (string | undefined)[]; + roles: (string | undefined)[]; +} + +export const useUserStore = defineStore({ + id: 'user', + state: (): UserState => ({ + // 当前登录用户的信息 + info: null, + // 当前登录用户的菜单 + menus: null, + // 当前登录用户的权限 + authorities: [], + // 当前登录用户的角色 + roles: [] + }), + getters: {}, + actions: { + /** + * 请求用户信息、权限、角色、菜单 + */ + async fetchUserInfo() { + const result = await getUserInfo().catch(() => {}); + if (!result) { + return {}; + } + // 用户信息 + this.info = result; + // 用户权限 + this.authorities = + result.authorities + ?.filter((d) => !!d.authority) + ?.map((d) => d.authority) ?? []; + // 用户角色 + this.roles = result.roles?.map((d) => d.roleCode) ?? []; + // 用户菜单, 过滤掉按钮类型并转为 children 形式 + const { menus, homePath } = formatMenus( + USER_MENUS ?? + toTreeData({ + data: result.authorities?.filter((d) => d.menuType !== 1), + idField: 'menuId', + parentIdField: 'parentId' + }).concat(EXTRA_MENUS) + ); + this.menus = menus; + return { menus, homePath }; + }, + /** + * 更新用户信息 + */ + setInfo(value: User) { + this.info = value; + }, + /** + * 更新菜单数据 + */ + setMenus(menus: MenuItemType[]) { + this.menus = menus; + }, + /** + * 更新菜单的 badge + */ + setMenuBadge(path: string, value?: number | string, color?: string) { + this.menus = formatTreeData(this.menus, (m) => { + if (path === m.path) { + return { + ...m, + meta: { + ...m.meta, + badge: value, + badgeColor: color + } + }; + } + return m; + }); + } + } +}); diff --git a/src/styles/as-needed.less b/src/styles/as-needed.less new file mode 100644 index 0000000..1bda515 --- /dev/null +++ b/src/styles/as-needed.less @@ -0,0 +1,6 @@ +/** 按需引入 */ +@import 'ant-design-vue/es/message/style/index.less'; +@import 'ant-design-vue/es/notification/style/index.less'; +@import 'ele-admin-pro/es/style/nprogress.less'; +@import 'ele-admin-pro/es/style/display.less'; +@import 'ele-admin-pro/es/style/common.less'; diff --git a/src/styles/global-import.less b/src/styles/global-import.less new file mode 100644 index 0000000..0d47070 --- /dev/null +++ b/src/styles/global-import.less @@ -0,0 +1,5 @@ +/** 全局引入 */ +@import 'cropperjs/dist/cropper.css'; +@import 'xgplayer/dist/index.min.css'; +@import 'ant-design-vue/dist/antd.less'; +@import 'ele-admin-pro/es/style/index.less'; diff --git a/src/styles/index.less b/src/styles/index.less new file mode 100644 index 0000000..33b2f2f --- /dev/null +++ b/src/styles/index.less @@ -0,0 +1,7 @@ +/** 全局样式 */ +@style-entry-file: as-needed; +@import './@{style-entry-file}.less'; +@import './transition/index.less'; + +// 主题 +@import 'ele-admin-pro/es/style/themes/dynamic.less'; diff --git a/src/styles/transition/fade.less b/src/styles/transition/fade.less new file mode 100644 index 0000000..9433fc0 --- /dev/null +++ b/src/styles/transition/fade.less @@ -0,0 +1,10 @@ +/* 渐变 */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.2s ease-in-out; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} diff --git a/src/styles/transition/index.less b/src/styles/transition/index.less new file mode 100644 index 0000000..be030fb --- /dev/null +++ b/src/styles/transition/index.less @@ -0,0 +1,4 @@ +/** 路由切换动画 */ +@import './fade.less'; +@import './slide.less'; +@import './zoom.less'; diff --git a/src/styles/transition/slide.less b/src/styles/transition/slide.less new file mode 100644 index 0000000..b1336e1 --- /dev/null +++ b/src/styles/transition/slide.less @@ -0,0 +1,31 @@ +/* 底部消退 */ +.slide-bottom-enter-active, +.slide-bottom-leave-active { + transition: opacity 0.2s ease-out, transform 0.25s ease-out; +} + +.slide-bottom-enter-from { + opacity: 0; + transform: translateY(-10%); +} + +.slide-bottom-leave-to { + opacity: 0; + transform: translateY(10%); +} + +/* 右侧消退 */ +.slide-right-leave-active, +.slide-right-enter-active { + transition: opacity 0.2s ease-out, transform 0.25s ease-out; +} + +.slide-right-enter-from { + opacity: 0; + transform: translateX(-60px); +} + +.slide-right-leave-to { + opacity: 0; + transform: translateX(60px); +} diff --git a/src/styles/transition/zoom.less b/src/styles/transition/zoom.less new file mode 100644 index 0000000..28fe401 --- /dev/null +++ b/src/styles/transition/zoom.less @@ -0,0 +1,31 @@ +/* 放大渐变 */ +.zoom-in-enter-active, +.zoom-in-leave-active { + transition: opacity 0.2s ease-out, transform 0.25s ease-out; +} + +.zoom-in-enter-from { + opacity: 0; + transform: scale(0.9); +} + +.zoom-in-leave-to { + opacity: 0; + transform: scale(1.1); +} + +/* 缩小渐变 */ +.zoom-out-leave-active, +.zoom-out-enter-active { + transition: opacity 0.2s ease-out, transform 0.25s ease-out; +} + +.zoom-out-enter-from { + opacity: 0; + transform: scale(1.2); +} + +.zoom-out-leave-to { + opacity: 0; + transform: scale(0.8); +} diff --git a/src/utils/document-title-util.ts b/src/utils/document-title-util.ts new file mode 100644 index 0000000..fc8202e --- /dev/null +++ b/src/utils/document-title-util.ts @@ -0,0 +1,68 @@ +import { watch } from 'vue'; +import { useRouter } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import type { RouteLocationNormalizedLoaded } from 'vue-router'; +import { + routeI18nKey, + findTabByPath +} from 'ele-admin-pro/es/ele-pro-layout/util'; +import { storeToRefs } from 'pinia'; +import { useThemeStore } from '@/store/modules/theme'; +import { PROJECT_NAME, REDIRECT_PATH, I18N_ENABLE } from '@/config/setting'; + +/** + * 修改浏览器标题 + * @param title 标题 + */ +export function setDocumentTitle(title: string) { + const names: string[] = []; + if (title) { + names.push(title); + } + if (PROJECT_NAME) { + names.push(PROJECT_NAME); + } + document.title = names.join(' - '); +} + +/** + * 路由切换更新浏览器标题 + */ +export function useSetDocumentTitle() { + const { currentRoute } = useRouter(); + const { t, locale } = useI18n(); + const themeStore = useThemeStore(); + const { tabs } = storeToRefs(themeStore); + + const updateTitle = (route: RouteLocationNormalizedLoaded) => { + const { path, meta, fullPath } = route; + if (path.includes(REDIRECT_PATH)) { + return; + } + const pathKey = routeI18nKey(path); + if (!pathKey) { + return; + } + const tab = findTabByPath(fullPath, tabs.value); + const title = tab?.title || (meta?.title as string); + if (!I18N_ENABLE) { + setDocumentTitle(title); + return; + } + const k = `route.${pathKey}._name`; + const v = t(k); + setDocumentTitle(v === k || !v ? title : v); + }; + + watch( + currentRoute, + (route) => { + updateTitle(route); + }, + { immediate: true } + ); + + watch(locale, () => { + updateTitle(currentRoute.value); + }); +} diff --git a/src/utils/on-size-change.ts b/src/utils/on-size-change.ts new file mode 100644 index 0000000..4cce32c --- /dev/null +++ b/src/utils/on-size-change.ts @@ -0,0 +1,18 @@ +/** + * 监听屏幕尺寸改变封装 + */ +import { watch } from 'vue'; +import { storeToRefs } from 'pinia'; +import { useThemeStore } from '@/store/modules/theme'; + +export function onSizeChange(hook: Function) { + if (!hook) { + return; + } + const themeStore = useThemeStore(); + const { contentWidth } = storeToRefs(themeStore); + + watch(contentWidth, () => { + hook(); + }); +} diff --git a/src/utils/page-tab-util.ts b/src/utils/page-tab-util.ts new file mode 100644 index 0000000..e651736 --- /dev/null +++ b/src/utils/page-tab-util.ts @@ -0,0 +1,262 @@ +/** + * 页签操作封装 + */ +import { unref } from 'vue'; +import type { RouteLocationNormalizedLoaded } from 'vue-router'; +import type { TabItem, TabRemoveOption } from 'ele-admin-pro/es'; +import { message } from 'ant-design-vue/es'; +import router from '@/router'; +import { useThemeStore } from '@/store/modules/theme'; +import type { RouteReloadOption } from '@/store/modules/theme'; +import { removeToken } from '@/utils/token-util'; +import { setDocumentTitle } from '@/utils/document-title-util'; +import { + HOME_PATH, + LAYOUT_PATH, + REDIRECT_PATH, + REPEATABLE_TABS +} from '@/config/setting'; +const HOME_ROUTE = HOME_PATH || LAYOUT_PATH; +const BASE_URL = import.meta.env.BASE_URL; + +/** + * 刷新页签参数类型 + */ +export interface TabReloadOptions { + // 是否是主页 + isHome?: boolean; + // 路由地址 + fullPath?: string; +} + +/** + * 刷新当前路由 + */ +export function reloadPageTab(option?: TabReloadOptions) { + if (!option) { + // 刷新当前路由 + const { path, fullPath, query } = unref(router.currentRoute); + if (path.includes(REDIRECT_PATH)) { + return; + } + const isHome = isHomeRoute(unref(router.currentRoute)); + setRouteReload({ + reloadHome: isHome, + reloadPath: isHome ? void 0 : fullPath + }); + router.replace({ + path: REDIRECT_PATH + path, + query + }); + } else { + // 刷新指定页签 + const { fullPath, isHome } = option; + setRouteReload({ + reloadHome: isHome, + reloadPath: isHome ? void 0 : fullPath + }); + router.replace(REDIRECT_PATH + fullPath); + } +} + +/** + * 关闭当前页签 + */ +export function finishPageTab() { + const key = getRouteTabKey(); + removePageTab({ key, active: key }); +} + +/** + * 关闭页签 + */ +export function removePageTab(option: TabRemoveOption) { + useThemeStore() + .tabRemove(option) + .then(({ path, home }) => { + if (path) { + router.push(path); + } else if (home) { + router.push(HOME_ROUTE); + } + }) + .catch(() => { + message.error('当前页签不可关闭'); + }); +} + +/** + * 关闭左侧页签 + */ +export function removeLeftPageTab(option: TabRemoveOption) { + useThemeStore() + .tabRemoveLeft(option) + .then(({ path }) => { + if (path) { + router.push(path); + } + }) + .catch(() => { + message.error('左侧没有可关闭的页签'); + }); +} + +/** + * 关闭右侧页签 + */ +export function removeRightPageTab(option: TabRemoveOption) { + useThemeStore() + .tabRemoveRight(option) + .then(({ path, home }) => { + if (path) { + router.push(path); + } else if (home) { + router.push(HOME_ROUTE); + } + }) + .catch(() => { + message.error('右侧没有可关闭的页签'); + }); +} + +/** + * 关闭其它页签 + */ +export function removeOtherPageTab(option: TabRemoveOption) { + useThemeStore() + .tabRemoveOther(option) + .then(({ path, home }) => { + if (path) { + router.push(path); + } else if (home) { + router.push(HOME_ROUTE); + } + }) + .catch(() => { + message.error('没有可关闭的页签'); + }); +} + +/** + * 关闭全部页签 + * @param active 当前选中页签 + */ +export function removeAllPageTab(active: string) { + useThemeStore() + .tabRemoveAll(active) + .then(({ home }) => { + if (home) { + router.push(HOME_ROUTE); + } + }) + .catch(() => { + message.error('没有可关闭的页签'); + }); +} + +/** + * 登录成功后清空页签 + */ +export function cleanPageTabs() { + useThemeStore().setTabs([]); +} + +/** + * 添加页签 + * @param data 页签数据 + */ +export function addPageTab(data: TabItem | TabItem[]) { + useThemeStore().tabAdd(data); +} + +/** + * 修改页签 + * @param data 页签数据 + */ +export function setPageTab(data: TabItem) { + useThemeStore().tabSetItem(data); +} + +/** + * 更新页签数据 + * @param data 页签数据 + */ +export function setPageTabs(data: TabItem[]) { + useThemeStore().setTabs(data); +} + +/** + * 修改页签标题 + * @param title 标题 + */ +export function setPageTabTitle(title: string) { + setPageTab({ key: getRouteTabKey(), title }); + setDocumentTitle(title); +} + +/** + * 获取当前路由对应的页签 key + */ +export function getRouteTabKey() { + const { path, fullPath, meta } = unref(router.currentRoute); + const isUnique = meta.tabUnique === false || REPEATABLE_TABS.includes(path); + return isUnique ? fullPath : path; +} + +/** + * 设置主页的组件名称 + * @param components 组件名称 + */ +export function setHomeComponents(components: string[]) { + useThemeStore().setHomeComponents(components); +} + +/** + * 设置路由刷新信息 + * @param option 路由刷新参数 + */ +export function setRouteReload(option: RouteReloadOption | null) { + return useThemeStore().setRouteReload(option); +} + +/** + * 判断路由是否是主页 + * @param route 路由信息 + */ +export function isHomeRoute(route: RouteLocationNormalizedLoaded) { + const { path, matched } = route; + if (HOME_ROUTE === path) { + return true; + } + return ( + matched[0] && + matched[0].path === LAYOUT_PATH && + matched[0].redirect === path + ); +} + +/** + * 登录成功后跳转首页 + * @param from 登录前的地址 + */ +export function goHomeRoute(from?: string) { + router.replace(from || HOME_ROUTE); +} + +/** + * 退出登录 + * @param route 是否使用路由跳转 + * @param from 登录后跳转的地址 + */ +export function logout(route?: boolean, from?: string) { + removeToken(); + if (route) { + router.push({ + path: '/login', + query: from ? { from } : void 0 + }); + } else { + // 这样跳转避免再次登录重复注册动态路由 + location.replace(BASE_URL + 'login' + (from ? '?from=' + from : '')); + } +} diff --git a/src/utils/permission.ts b/src/utils/permission.ts new file mode 100644 index 0000000..4324d98 --- /dev/null +++ b/src/utils/permission.ts @@ -0,0 +1,119 @@ +/** + * 按钮级权限控制 + */ +import type { App } from 'vue'; +import { useUserStore } from '@/store/modules/user'; + +/* 判断数组是否有某些值 */ +function arrayHas( + array: (string | undefined)[], + value: string | string[] +): boolean { + if (!value) { + return true; + } + if (!array) { + return false; + } + if (Array.isArray(value)) { + for (let i = 0; i < value.length; i++) { + if (array.indexOf(value[i]) === -1) { + return false; + } + } + return true; + } + return array.indexOf(value) !== -1; +} + +/* 判断数组是否有任意值 */ +function arrayHasAny( + array: (string | undefined)[], + value: string | string[] +): boolean { + if (!value) { + return true; + } + if (!array) { + return false; + } + if (Array.isArray(value)) { + for (let i = 0; i < value.length; i++) { + if (array.indexOf(value[i]) !== -1) { + return true; + } + } + return false; + } + return array.indexOf(value) !== -1; +} + +/** + * 是否有某些角色 + * @param value 角色字符或字符数组 + */ +export function hasRole(value: string | string[]): boolean { + const userStore = useUserStore(); + return arrayHas(userStore?.roles, value); +} + +/** + * 是否有任意角色 + * @param value 角色字符或字符数组 + */ +export function hasAnyRole(value: string | string[]): boolean { + const userStore = useUserStore(); + return arrayHasAny(userStore?.roles, value); +} + +/** + * 是否有某些权限 + * @param value 权限字符或字符数组 + */ +export function hasPermission(value: string | string[]): boolean { + const userStore = useUserStore(); + return arrayHas(userStore?.authorities, value); +} + +/** + * 是否有任意权限 + * @param value 权限字符或字符数组 + */ +export function hasAnyPermission(value: string | string[]): boolean { + const userStore = useUserStore(); + return arrayHasAny(userStore?.authorities, value); +} + +export default { + install(app: App) { + // 添加自定义指令 + app.directive('role', { + mounted: (el, binding) => { + if (!hasRole(binding.value)) { + el.parentNode?.removeChild(el); + } + } + }); + app.directive('any-role', { + mounted: (el, binding) => { + if (!hasAnyRole(binding.value)) { + el.parentNode?.removeChild(el); + } + } + }); + app.directive('permission', { + mounted: (el, binding) => { + if (!hasPermission(binding.value)) { + el.parentNode?.removeChild(el); + } + } + }); + app.directive('any-permission', { + mounted: (el, binding) => { + if (!hasAnyPermission(binding.value)) { + el.parentNode?.removeChild(el); + } + } + }); + } +}; diff --git a/src/utils/request.ts b/src/utils/request.ts new file mode 100644 index 0000000..6ba6634 --- /dev/null +++ b/src/utils/request.ts @@ -0,0 +1,70 @@ +/** + * axios 实例 + */ +import axios from 'axios'; +import type { AxiosResponse } from 'axios'; +import { unref } from 'vue'; +import router from '@/router'; +import { Modal } from 'ant-design-vue/es'; +import { API_BASE_URL, TOKEN_HEADER_NAME, LAYOUT_PATH } from '@/config/setting'; +import { getToken, setToken } from './token-util'; +import { logout } from './page-tab-util'; +import type { ApiResult } from '@/api'; + +const service = axios.create({ + baseURL: API_BASE_URL +}); + +/** + * 添加请求拦截器 + */ +service.interceptors.request.use( + (config) => { + // 添加 token 到 header + const token = getToken(); + if (token && config.headers) { + config.headers[TOKEN_HEADER_NAME] = token; + } + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +/** + * 添加响应拦截器 + */ +service.interceptors.response.use( + (res: AxiosResponse<ApiResult<unknown>>) => { + // 登录过期处理 + if (res.data?.code === 401) { + const currentPath = unref(router.currentRoute).path; + if (currentPath == LAYOUT_PATH) { + logout(true); + } else { + Modal.destroyAll(); + Modal.info({ + title: '系统提示', + content: '登录状态已过期, 请退出重新登录!', + okText: '重新登录', + onOk: () => { + logout(false, currentPath); + } + }); + } + return Promise.reject(new Error(res.data.message)); + } + // token 自动续期 + const token = res.headers[TOKEN_HEADER_NAME.toLowerCase()]; + if (token) { + setToken(token); + } + return res; + }, + (error) => { + return Promise.reject(error); + } +); + +export default service; diff --git a/src/utils/token-util.ts b/src/utils/token-util.ts new file mode 100644 index 0000000..48cbaf2 --- /dev/null +++ b/src/utils/token-util.ts @@ -0,0 +1,39 @@ +/** + * token 操作封装 + */ +import { TOKEN_STORE_NAME } from '@/config/setting'; + +/** + * 获取缓存的 token + */ +export function getToken(): string | null { + const token = localStorage.getItem(TOKEN_STORE_NAME); + if (!token) { + return sessionStorage.getItem(TOKEN_STORE_NAME); + } + return token; +} + +/** + * 缓存 token + * @param token token + * @param remember 是否永久存储 + */ +export function setToken(token?: string, remember?: boolean) { + removeToken(); + if (token) { + if (remember) { + localStorage.setItem(TOKEN_STORE_NAME, token); + } else { + sessionStorage.setItem(TOKEN_STORE_NAME, token); + } + } +} + +/** + * 移除 token + */ +export function removeToken() { + localStorage.removeItem(TOKEN_STORE_NAME); + sessionStorage.removeItem(TOKEN_STORE_NAME); +} diff --git a/src/utils/use-echarts.ts b/src/utils/use-echarts.ts new file mode 100644 index 0000000..06531d8 --- /dev/null +++ b/src/utils/use-echarts.ts @@ -0,0 +1,76 @@ +/** + * echarts 自动切换主题、重置尺寸封装 + */ +import type { Ref } from 'vue'; +import { + ref, + reactive, + unref, + provide, + watch, + onActivated, + onDeactivated, + nextTick +} from 'vue'; +import { storeToRefs } from 'pinia'; +import { THEME_KEY } from 'vue-echarts'; +import type VChart from 'vue-echarts'; +import { ChartTheme, ChartThemeDark } from 'ele-admin-pro/es'; +import { useThemeStore } from '@/store/modules/theme'; +import { onSizeChange } from './on-size-change'; + +export default function (chartRefs: Ref<InstanceType<typeof VChart> | null>[]) { + // 当前框架是否是暗黑主题 + const themeStore = useThemeStore(); + const { darkMode } = storeToRefs(themeStore); + // 是否为 deactivated 状态 + const deactivated = ref<boolean>(false); + // 当前图表是否是暗黑主题 + const isDark = ref<boolean>(unref(darkMode)); + // 当前图表主题 + const chartsTheme = reactive({ + ...(unref(isDark) ? ChartThemeDark : ChartTheme) + }); + + // 设置图表主题 + provide(THEME_KEY, chartsTheme); + + /* 重置图表尺寸 */ + const resizeCharts = () => { + chartRefs.forEach((chartRef) => { + unref(chartRef)?.resize(); + }); + }; + + /* 屏幕尺寸变化监听 */ + onSizeChange(() => { + resizeCharts(); + }); + + /* 更改图表主题 */ + const changeTheme = (dark: boolean) => { + isDark.value = dark; + Object.assign(chartsTheme, dark ? ChartThemeDark : ChartTheme); + }; + + onActivated(() => { + deactivated.value = false; + nextTick(() => { + if (unref(isDark) !== unref(darkMode)) { + changeTheme(unref(darkMode)); + } else { + resizeCharts(); + } + }); + }); + + onDeactivated(() => { + deactivated.value = true; + }); + + watch(darkMode, (dark) => { + if (!unref(deactivated)) { + changeTheme(dark); + } + }); +} diff --git a/src/utils/use-form-data.ts b/src/utils/use-form-data.ts new file mode 100644 index 0000000..a3e6511 --- /dev/null +++ b/src/utils/use-form-data.ts @@ -0,0 +1,29 @@ +import { reactive } from 'vue'; + +/** + * 表单数据 hook + * @param initValue 默认值 + */ +export default function <T extends object>(initValue?: T) { + const form = reactive<T>({ ...initValue } as T); + + const resetFields = () => { + Object.keys(form).forEach((key) => { + form[key] = initValue ? initValue[key] : void 0; + }); + }; + + const assignFields = (data: object) => { + Object.keys(form).forEach((key) => { + form[key] = data[key]; + }); + }; + + return { + form, + // 重置为初始值 + resetFields, + // 赋值不改变字段 + assignFields + }; +} diff --git a/src/views-demo/dashboard/analysis/components/hot-search.vue b/src/views-demo/dashboard/analysis/components/hot-search.vue new file mode 100644 index 0000000..c1a5531 --- /dev/null +++ b/src/views-demo/dashboard/analysis/components/hot-search.vue @@ -0,0 +1,72 @@ +<template> + <a-card :bordered="false" title="热门搜索"> + <v-chart + ref="hotSearchChartRef" + :option="hotSearchChartOption" + style="height: 330px" + /> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { use } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { LineChart, BarChart } from 'echarts/charts'; + import { GridComponent, TooltipComponent } from 'echarts/components'; + import VChart from 'vue-echarts'; + import 'echarts-wordcloud'; + import { wordCloudColor } from 'ele-admin-pro/es'; + import { getWordCloudList } from '@/api/dashboard/analysis'; + import useEcharts from '@/utils/use-echarts'; + + use([CanvasRenderer, LineChart, BarChart, GridComponent, TooltipComponent]); + + // + const hotSearchChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([hotSearchChartRef]); + + // 词云图表配置 + const hotSearchChartOption: EChartsCoreOption = reactive({}); + + /* 获取词云数据 */ + const getWordCloudData = () => { + getWordCloudList() + .then((data) => { + Object.assign(hotSearchChartOption, { + tooltip: { + show: true, + confine: true, + borderWidth: 1 + }, + series: [ + { + type: 'wordCloud', + width: '100%', + height: '100%', + sizeRange: [12, 24], + gridSize: 6, + textStyle: { + color: wordCloudColor + }, + emphasis: { + textStyle: { + shadowBlur: 8, + shadowColor: 'rgba(0, 0, 0, .15)' + } + }, + data: data + } + ] + }); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + getWordCloudData(); +</script> diff --git a/src/views-demo/dashboard/analysis/components/sale-card.vue b/src/views-demo/dashboard/analysis/components/sale-card.vue new file mode 100644 index 0000000..45d6c62 --- /dev/null +++ b/src/views-demo/dashboard/analysis/components/sale-card.vue @@ -0,0 +1,248 @@ +<template> + <a-card :bordered="false" :body-style="{ padding: 0 }"> + <a-tabs + size="large" + v-model:activeKey="saleSearch.type" + class="monitor-card-tabs" + @change="onSaleTypeChange" + > + <a-tab-pane tab="销售额" key="saleroom" /> + <a-tab-pane tab="访问量" key="visits" /> + <template #rightExtra> + <a-space + size="middle" + :class="[ + 'analysis-tabs-extra', + { 'hidden-lg-and-down': styleResponsive } + ]" + > + <a-radio-group v-model:value="saleSearch.dateType"> + <a-radio-button value="1">今天</a-radio-button> + <a-radio-button value="2">本周</a-radio-button> + <a-radio-button value="3">本月</a-radio-button> + <a-radio-button value="4">本年</a-radio-button> + </a-radio-group> + <div style="width: 300px"> + <a-range-picker + value-format="YYYY-MM-DD" + v-model:value="saleSearch.datetime" + /> + </div> + </a-space> + </template> + </a-tabs> + <div style="padding-bottom: 10px"> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { lg: 17, md: 15, sm: 24, xs: 24 } : { span: 17 } + " + > + <div v-if="saleSearch.type === 'saleroom'" class="demo-monitor-title"> + 销售量趋势 + </div> + <div v-else class="demo-monitor-title">访问量趋势</div> + <v-chart + ref="saleChartRef" + :option="saleChartOption" + style="height: 320px" + /> + </a-col> + <a-col + v-bind=" + styleResponsive ? { lg: 7, md: 9, sm: 24, xs: 24 } : { span: 7 } + " + > + <div v-if="saleSearch.type === 'saleroom'"> + <div class="demo-monitor-title">门店销售额排名</div> + <div + v-for="(item, index) in saleroomRankData" + :key="index" + class="demo-monitor-rank-item ele-cell" + > + <ele-tag + shape="circle" + :color="index < 3 ? '#314659' : ''" + style="border: none" + > + {{ index + 1 }} + </ele-tag> + <div class="ele-cell-content ele-elip">{{ item.name }}</div> + <div class="ele-text-secondary">{{ item.value }}</div> + </div> + </div> + <div v-else> + <div class="demo-monitor-title">门店访问量排名</div> + <div + v-for="(item, index) in visitsRankData" + :key="index" + class="demo-monitor-rank-item ele-cell" + > + <ele-tag + shape="circle" + :color="index < 3 ? '#314659' : ''" + style="border: none" + > + {{ index + 1 }} + </ele-tag> + <div class="ele-cell-content ele-elip">{{ item.name }}</div> + <div class="ele-text-secondary">{{ item.value }}</div> + </div> + </div> + </a-col> + </a-row> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { use } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { BarChart } from 'echarts/charts'; + import { GridComponent, TooltipComponent } from 'echarts/components'; + import VChart from 'vue-echarts'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { getSaleroomList } from '@/api/dashboard/analysis'; + import type { SaleroomData } from '@/api/dashboard/analysis/model'; + import useEcharts from '@/utils/use-echarts'; + + use([CanvasRenderer, BarChart, GridComponent, TooltipComponent]); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const saleChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([saleChartRef]); + + // 销售额柱状图配置 + const saleChartOption: EChartsCoreOption = reactive({}); + + // 门店销售排名数据 + const saleroomRankData = ref([ + { name: '工专路 1 号店', value: '323,234' }, + { name: '工专路 2 号店', value: '323,234' }, + { name: '工专路 3 号店', value: '323,234' }, + { name: '工专路 4 号店', value: '323,234' }, + { name: '工专路 5 号店', value: '323,234' }, + { name: '工专路 6 号店', value: '323,234' }, + { name: '工专路 7 号店', value: '323,234' } + ]); + + // 门店访问排名数据 + const visitsRankData = ref([ + { name: '工专路 1 号店', value: '323,234' }, + { name: '工专路 2 号店', value: '323,234' }, + { name: '工专路 3 号店', value: '323,234' }, + { name: '工专路 4 号店', value: '323,234' }, + { name: '工专路 5 号店', value: '323,234' }, + { name: '工专路 6 号店', value: '323,234' }, + { name: '工专路 7 号店', value: '323,234' } + ]); + + // 销售量趋势数据 + const saleroomData1 = ref<SaleroomData[]>([]); + + // 访问量趋势数据 + const saleroomData2 = ref<SaleroomData[]>([]); + + interface SaleSearchType { + type: string; + dateType: string; + datetime: [string, string]; + } + + // 销售量搜索参数 + const saleSearch = reactive<SaleSearchType>({ + type: 'saleroom', + dateType: '1', + datetime: ['2022-01-08', '2022-02-12'] + }); + + /* 获取销售量数据 */ + const getSaleroomData = () => { + getSaleroomList() + .then((data) => { + saleroomData1.value = data.list1; + saleroomData2.value = data.list2; + onSaleTypeChange(); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + /* 销售量tab选择改变事件 */ + const onSaleTypeChange = () => { + if (saleSearch.type === 'saleroom') { + Object.assign(saleChartOption, { + tooltip: { + trigger: 'axis' + }, + xAxis: [ + { + type: 'category', + data: saleroomData1.value.map((d) => d.month) + } + ], + yAxis: [ + { + type: 'value' + } + ], + series: [ + { + type: 'bar', + data: saleroomData1.value.map((d) => d.value) + } + ] + }); + } else { + Object.assign(saleChartOption, { + tooltip: { + trigger: 'axis' + }, + xAxis: [ + { + type: 'category', + data: saleroomData2.value.map((d) => d.month) + } + ], + yAxis: [ + { + type: 'value' + } + ], + series: [ + { + type: 'bar', + data: saleroomData2.value.map((d) => d.value) + } + ] + }); + } + }; + + getSaleroomData(); +</script> + +<style lang="less" scoped> + .monitor-card-tabs :deep(.ant-tabs-nav) { + padding: 0 16px; + } + + .demo-monitor-title { + padding: 6px 20px; + } + + .demo-monitor-rank-item { + padding: 0 20px; + margin-top: 18px; + } +</style> diff --git a/src/views-demo/dashboard/analysis/components/statistics-card.vue b/src/views-demo/dashboard/analysis/components/statistics-card.vue new file mode 100644 index 0000000..f4fefd5 --- /dev/null +++ b/src/views-demo/dashboard/analysis/components/statistics-card.vue @@ -0,0 +1,246 @@ +<!-- 统计卡片 --> +<template> + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }" + > + <a-card class="analysis-chart-card" :bordered="false"> + <div class="ele-text-secondary ele-cell"> + <div class="ele-cell-content">总销售额</div> + <a-tooltip title="指标说明"> + <question-circle-outlined /> + </a-tooltip> + </div> + <h1 class="analysis-chart-card-num">¥ 126,560</h1> + <div class="analysis-chart-card-content" style="padding-top: 16px"> + <a-space size="middle"> + <span class="analysis-trend-text"> + 周同比12% <caret-up-outlined class="ele-text-danger" /> + </span> + <span class="analysis-trend-text"> + 日同比11% <caret-down-outlined class="ele-text-success" /> + </span> + </a-space> + </div> + <a-divider /> + <div>日销售额 ¥12,423</div> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }" + > + <a-card class="analysis-chart-card" :bordered="false"> + <div class="ele-text-secondary ele-cell"> + <div class="ele-cell-content">访问量</div> + <ele-tag color="red">日</ele-tag> + </div> + <h1 class="analysis-chart-card-num">8,846</h1> + <v-chart + ref="visitChartRef" + :option="visitChartOption" + style="height: 40px" + /> + <a-divider /> + <div>日访问量 1,234</div> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }" + > + <a-card class="analysis-chart-card" :bordered="false"> + <div class="ele-text-secondary ele-cell"> + <div class="ele-cell-content">支付笔数</div> + <ele-tag color="blue">月</ele-tag> + </div> + <h1 class="analysis-chart-card-num">6,560</h1> + <v-chart + ref="payNumChartRef" + :option="payNumChartOption" + style="height: 40px" + /> + <a-divider /> + <div>转化率 60%</div> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }" + > + <a-card class="analysis-chart-card" :bordered="false"> + <div class="ele-text-secondary ele-cell"> + <div class="ele-cell-content">活动运营效果</div> + <ele-tag color="green">周</ele-tag> + </div> + <h1 class="analysis-chart-card-num">78%</h1> + <div class="analysis-chart-card-content" style="padding-top: 16px"> + <a-progress + :percent="78" + :show-info="false" + stroke-color="#13c2c2" + status="active" + /> + </div> + <a-divider /> + <a-space size="middle"> + <span class="analysis-trend-text"> + 周同比12% <caret-up-outlined class="ele-text-danger" /> + </span> + <span class="analysis-trend-text"> + 日同比11% <caret-down-outlined class="ele-text-success" /> + </span> + </a-space> + </a-card> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { + QuestionCircleOutlined, + CaretUpOutlined, + CaretDownOutlined + } from '@ant-design/icons-vue'; + import { use } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { LineChart, BarChart } from 'echarts/charts'; + import { GridComponent, TooltipComponent } from 'echarts/components'; + import VChart from 'vue-echarts'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { getPayNumList } from '@/api/dashboard/analysis'; + import useEcharts from '@/utils/use-echarts'; + + use([CanvasRenderer, LineChart, BarChart, GridComponent, TooltipComponent]); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const visitChartRef = ref<InstanceType<typeof VChart> | null>(null); + const payNumChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([visitChartRef, payNumChartRef]); + + // 访问量折线图配置 + const visitChartOption: EChartsCoreOption = reactive({}); + + // 支付笔数柱状图配置 + const payNumChartOption: EChartsCoreOption = reactive({}); + + /* 获取支付笔数数据 */ + const getPayNumData = () => { + getPayNumList() + .then((data) => { + Object.assign(visitChartOption, { + color: '#975fe5', + tooltip: { + trigger: 'axis', + formatter: + '<i class="ele-chart-dot" style="background: #975fe5;"></i>{b0}: {c0}' + }, + grid: { + top: 10, + bottom: 0, + left: 0, + right: 0 + }, + xAxis: [ + { + show: false, + type: 'category', + boundaryGap: false, + data: data.map((d) => d.date) + } + ], + yAxis: [ + { + show: false, + type: 'value', + splitLine: { + show: false + } + } + ], + series: [ + { + type: 'line', + smooth: true, + symbol: 'none', + areaStyle: { + opacity: 0.5 + }, + data: data.map((d) => d.value) + } + ] + }); + + Object.assign(payNumChartOption, { + tooltip: { + trigger: 'axis', + formatter: + '<i class="ele-chart-dot" style="background: #5b8ff9;"></i>{b0}: {c0}' + }, + grid: { + top: 10, + bottom: 0, + left: 0, + right: 0 + }, + xAxis: [ + { + show: false, + type: 'category', + data: data.map((d) => d.date) + } + ], + yAxis: [ + { + show: false, + type: 'value', + splitLine: { + show: false + } + } + ], + series: [ + { + type: 'bar', + data: data.map((d) => d.value) + } + ] + }); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + getPayNumData(); +</script> + +<style lang="less" scoped> + .analysis-chart-card { + :deep(.ant-card-body) { + padding: 16px 22px 12px 22px; + } + + :deep(.ant-divider) { + margin: 12px 0; + } + } + + .analysis-chart-card-num { + font-size: 30px; + } + + .analysis-chart-card-content { + height: 40px; + } + + .analysis-trend-text { + white-space: nowrap; + } +</style> diff --git a/src/views-demo/dashboard/analysis/components/visit-hour.vue b/src/views-demo/dashboard/analysis/components/visit-hour.vue new file mode 100644 index 0000000..ca371d3 --- /dev/null +++ b/src/views-demo/dashboard/analysis/components/visit-hour.vue @@ -0,0 +1,101 @@ +<template> + <a-card + :bordered="false" + title="最近1小时访问情况" + :body-style="{ padding: '16px 6px 0 0' }" + > + <v-chart + ref="visitHourChartRef" + :option="visitHourChartOption" + style="height: 362px" + /> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { use } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { LineChart } from 'echarts/charts'; + import { + GridComponent, + TooltipComponent, + LegendComponent + } from 'echarts/components'; + import VChart from 'vue-echarts'; + import { getVisitHourList } from '@/api/dashboard/analysis'; + import useEcharts from '@/utils/use-echarts'; + + use([ + CanvasRenderer, + LineChart, + GridComponent, + TooltipComponent, + LegendComponent + ]); + + // + const visitHourChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([visitHourChartRef]); + + // 最近 1 小时访问情况折线图配置 + const visitHourChartOption: EChartsCoreOption = reactive({}); + + /* 获取最近 1 小时访问情况数据 */ + const getVisitHourData = () => { + getVisitHourList() + .then((data) => { + Object.assign(visitHourChartOption, { + tooltip: { + trigger: 'axis' + }, + legend: { + data: ['浏览量', '访问量'], + right: 20 + }, + xAxis: [ + { + type: 'category', + boundaryGap: false, + data: data.map((d) => d.time) + } + ], + yAxis: [ + { + type: 'value' + } + ], + series: [ + { + name: '浏览量', + type: 'line', + smooth: true, + symbol: 'none', + areaStyle: { + opacity: 0.5 + }, + data: data.map((d) => d.views) + }, + { + name: '访问量', + type: 'line', + smooth: true, + symbol: 'none', + areaStyle: { + opacity: 0.5 + }, + data: data.map((d) => d.visits) + } + ] + }); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + getVisitHourData(); +</script> diff --git a/src/views-demo/dashboard/analysis/index.vue b/src/views-demo/dashboard/analysis/index.vue new file mode 100644 index 0000000..d0ede32 --- /dev/null +++ b/src/views-demo/dashboard/analysis/index.vue @@ -0,0 +1,41 @@ +<template> + <div class="ele-body ele-body-card"> + <statistics-card /> + <sale-card /> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { lg: 16, md: 14, sm: 24, xs: 24 } : { span: 16 } + " + > + <visit-hour /> + </a-col> + <a-col + v-bind=" + styleResponsive ? { lg: 8, md: 10, sm: 24, xs: 24 } : { span: 8 } + " + > + <hot-search /> + </a-col> + </a-row> + </div> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import StatisticsCard from './components/statistics-card.vue'; + import SaleCard from './components/sale-card.vue'; + import VisitHour from './components/visit-hour.vue'; + import HotSearch from './components/hot-search.vue'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); +</script> + +<script lang="ts"> + export default { + name: 'DashboardAnalysis' + }; +</script> diff --git a/src/views-demo/dashboard/monitor/components/browser-card.vue b/src/views-demo/dashboard/monitor/components/browser-card.vue new file mode 100644 index 0000000..067abff --- /dev/null +++ b/src/views-demo/dashboard/monitor/components/browser-card.vue @@ -0,0 +1,69 @@ +<template> + <a-card :bordered="false" title="浏览器分布" :body-style="{ padding: '0px' }"> + <v-chart + ref="browserChartRef" + :option="browserChartOption" + style="height: 222px" + /> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { use } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { PieChart } from 'echarts/charts'; + import { TooltipComponent, LegendComponent } from 'echarts/components'; + import VChart from 'vue-echarts'; + import { getBrowserCountList } from '@/api/dashboard/monitor'; + import useEcharts from '@/utils/use-echarts'; + + use([CanvasRenderer, PieChart, TooltipComponent, LegendComponent]); + + // + const browserChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([browserChartRef]); + + // 浏览器分布饼图配置 + const browserChartOption: EChartsCoreOption = reactive({}); + + /* 获取用户浏览器分布数据 */ + const getBrowserCountData = () => { + getBrowserCountList() + .then((data) => { + Object.assign(browserChartOption, { + tooltip: { + trigger: 'item', + confine: true, + borderWidth: 1 + }, + legend: { + bottom: 5, + itemWidth: 10, + itemHeight: 10, + icon: 'circle', + data: data.map((d) => d.name) + }, + series: [ + { + type: 'pie', + radius: ['45%', '70%'], + center: ['50%', '43%'], + label: { + show: false + }, + data: data + } + ] + }); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + getBrowserCountData(); +</script> diff --git a/src/views-demo/dashboard/monitor/components/map-card.vue b/src/views-demo/dashboard/monitor/components/map-card.vue new file mode 100644 index 0000000..87982f5 --- /dev/null +++ b/src/views-demo/dashboard/monitor/components/map-card.vue @@ -0,0 +1,147 @@ +<template> + <a-card :bordered="false" title="用户分布"> + <a-row> + <a-col v-bind="styleResponsive ? { sm: 18, xs: 24 } : { span: 18 }"> + <v-chart + ref="userCountMapChartRef" + :option="userCountMapOption" + style="height: 469px" + /> + </a-col> + <a-col v-bind="styleResponsive ? { sm: 6, xs: 24 } : { span: 6 }"> + <div + v-for="item in userCountDataRank" + :key="item.name" + class="monitor-user-count-item ele-cell" + > + <div>{{ item.name }}</div> + <div class="ele-cell-content"> + <a-progress + status="normal" + :show-info="false" + :percent="item.percent" + /> + </div> + <div>{{ item.value }}</div> + </div> + </a-col> + </a-row> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { use, registerMap } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { MapChart } from 'echarts/charts'; + import { + VisualMapComponent, + GeoComponent, + TooltipComponent + } from 'echarts/components'; + import VChart from 'vue-echarts'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { getChinaMapData, getUserCountList } from '@/api/dashboard/monitor'; + import type { UserCount } from '@/api/dashboard/monitor/model'; + import useEcharts from '@/utils/use-echarts'; + + use([ + CanvasRenderer, + MapChart, + VisualMapComponent, + GeoComponent, + TooltipComponent + ]); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const userCountMapChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([userCountMapChartRef]); + + // 用户分布前 10 名 + const userCountDataRank = ref<UserCount[]>([]); + + // 用户分布地图配置 + const userCountMapOption: EChartsCoreOption = reactive({}); + + /* 获取中国地图数据并注册地图 */ + const registerChinaMap = () => { + getChinaMapData() + .then((data) => { + registerMap('china', data); + getUserCountData(); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + /* 获取用户分布数据 */ + const getUserCountData = () => { + getUserCountList() + .then((data) => { + const temp = data.sort((a, b) => b.value - a.value); + const min = temp[temp.length - 1].value || 0; + const max = temp[0].value || 1; + // + const list = temp.length > 10 ? temp.slice(0, 15) : temp; + userCountDataRank.value = list.map((d) => { + return { + name: d.name, + value: d.value, + percent: (d.value / max) * 100 + }; + }); + // + Object.assign(userCountMapOption, { + tooltip: { + trigger: 'item', + borderWidth: 1 + }, + visualMap: { + min: min, + max: max, + text: ['高', '低'], + calculable: true + }, + series: [ + { + name: '用户数', + label: { + show: true + }, + type: 'map', + map: 'china', + data: data + } + ] + }); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + registerChinaMap(); +</script> + +<style lang="less" scoped> + .monitor-user-count-item { + margin-bottom: 8px; + + :deep(.ant-progress-inner) { + background: none; + } + + .ele-cell-content { + padding-right: 10px; + } + } +</style> diff --git a/src/views-demo/dashboard/monitor/components/online-num.vue b/src/views-demo/dashboard/monitor/components/online-num.vue new file mode 100644 index 0000000..e8bad2f --- /dev/null +++ b/src/views-demo/dashboard/monitor/components/online-num.vue @@ -0,0 +1,70 @@ +<template> + <a-card :bordered="false" title="在线人数"> + <div class="monitor-online-num-card"> + <div>{{ currentTime }}</div> + <div class="monitor-online-num-title"> + <ele-count-up :end-val="onlineNum" /> + </div> + <div class="monitor-online-num-text">在线总人数</div> + <a-badge status="processing" :text="updateTimeText" /> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, computed, onBeforeUnmount } from 'vue'; + import { toDateString } from 'ele-admin-pro/es'; + // 在线人数更新定时器 + let onlineNumTimer: number | null = null; + + // 在线总人数倒计时 + const updateTime = ref(10); + + // 当前时间 + const currentTime = ref(toDateString(new Date(), 'HH:mm:ss')); + + // 在线人数 + const onlineNum = ref(228); + + // 在线人数倒计时文字 + const updateTimeText = computed(() => updateTime.value + ' 秒后更新'); + + /* 在线人数更新倒计时 */ + const startUpdateOnlineNum = () => { + onlineNumTimer = window.setInterval(() => { + currentTime.value = toDateString(new Date(), 'HH:mm:ss'); + if (updateTime.value === 1) { + updateTime.value = 10; + onlineNum.value = 100 + Math.round(Math.random() * 900); + } else { + updateTime.value--; + } + }, 1000); + }; + + onBeforeUnmount(() => { + // 销毁定时器 + if (onlineNumTimer) { + clearInterval(onlineNumTimer); + onlineNumTimer = null; + } + }); + + startUpdateOnlineNum(); +</script> + +<style lang="less" scoped> + .monitor-online-num-card { + text-align: center; + } + + .monitor-online-num-title { + line-height: 1; + font-size: 50px; + margin: 22px 0 14px; + } + + .monitor-online-num-text { + margin-bottom: 22px; + } +</style> diff --git a/src/views-demo/dashboard/monitor/components/statistics-card.vue b/src/views-demo/dashboard/monitor/components/statistics-card.vue new file mode 100644 index 0000000..8ff75b5 --- /dev/null +++ b/src/views-demo/dashboard/monitor/components/statistics-card.vue @@ -0,0 +1,166 @@ +<!-- 统计卡片 --> +<template> + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }" + > + <a-card :bordered="false" class="monitor-count-card"> + <ele-tag color="blue" shape="circle" size="large"> + <eye-filled /> + </ele-tag> + <h1 class="monitor-count-card-num">21.2 k</h1> + <div class="monitor-count-card-text">总访问人数</div> + <ele-avatar-list :data="visitUsers" size="small" :max="4" /> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }" + > + <a-card :bordered="false" class="monitor-count-card"> + <ele-tag color="orange" shape="circle" size="large"> + <fire-filled /> + </ele-tag> + <h1 class="monitor-count-card-num">1.6 k</h1> + <div class="monitor-count-card-text">点击量(近30天)</div> + <div class="monitor-count-card-trend ele-text-success"> + <up-outlined /> + <span>110.5%</span> + </div> + <a-tooltip title="指标说明"> + <question-circle-outlined class="monitor-count-card-tips" /> + </a-tooltip> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }" + > + <a-card :bordered="false" class="monitor-count-card"> + <ele-tag color="red" shape="circle" size="large"> + <flag-filled /> + </ele-tag> + <h1 class="monitor-count-card-num">826.0</h1> + <div class="monitor-count-card-text">到达量(近30天)</div> + <div class="monitor-count-card-trend ele-text-danger"> + <down-outlined /> + <span>15.5%</span> + </div> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }" + > + <a-card :bordered="false" class="monitor-count-card"> + <ele-tag color="green" shape="circle" size="large"> + <thunderbolt-filled /> + </ele-tag> + <h1 class="monitor-count-card-num">28.8 %</h1> + <div class="monitor-count-card-text">转化率(近30天)</div> + <div class="monitor-count-card-trend ele-text-success"> + <up-outlined /> + <span>65.8%</span> + </div> + <a-tooltip title="指标说明"> + <question-circle-outlined class="monitor-count-card-tips" /> + </a-tooltip> + </a-card> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { + QuestionCircleOutlined, + EyeFilled, + FireFilled, + FlagFilled, + ThunderboltFilled, + UpOutlined, + DownOutlined + } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + interface VisitUserType { + key: string | number; + name: string; + avatar: string; + } + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // 访问人数 + const visitUsers = ref<VisitUserType[]>([ + { + key: 1, + name: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + key: 2, + name: '你的名字很好听', + avatar: + 'https://cdn.eleadmin.com/20200609/b6a811873e704db49db994053a5019b2.jpg' + }, + { + key: 3, + name: '全村人的希望', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + }, + { + key: 4, + name: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg' + }, + { + key: 5, + name: '酷酷的大叔', + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + }, + { + key: 6, + name: '管理员', + avatar: 'https://cdn.eleadmin.com/20200610/avatar.jpg' + } + ]); +</script> + +<style lang="less" scoped> + .monitor-count-card { + text-align: center; + + .monitor-count-card-num { + margin-top: 6px; + font-size: 32px; + } + + .monitor-count-card-text { + font-size: 12px; + margin: 8px 0; + opacity: 0.8; + } + + .monitor-count-card-trend { + font-weight: bold; + line-height: 26px; + + & > .anticon { + margin-right: 6px; + } + } + + .monitor-count-card-tips { + position: absolute; + top: 16px; + right: 16px; + cursor: pointer; + opacity: 0.6; + } + } +</style> diff --git a/src/views-demo/dashboard/monitor/components/user-liveness.vue b/src/views-demo/dashboard/monitor/components/user-liveness.vue new file mode 100644 index 0000000..9eb2e71 --- /dev/null +++ b/src/views-demo/dashboard/monitor/components/user-liveness.vue @@ -0,0 +1,75 @@ +<template> + <a-card + :bordered="false" + title="用户活跃度" + :body-style="{ padding: '56px 0' }" + > + <div class="ele-cell"> + <div class="ele-cell-content ele-text-center"> + <div class="monitor-progress-group"> + <a-progress + type="circle" + :percent="70" + stroke-color="#52c41a" + :show-info="false" + :width="161" + /> + <a-progress + type="circle" + :percent="60" + stroke-color="#1890ff" + :show-info="false" + :width="121" + :stroke-width="5" + /> + <a-progress + type="circle" + :percent="35" + stroke-color="#f5222d" + :show-info="false" + :width="91" + :stroke-width="4" + /> + </div> + </div> + <div class="monitor-progress-legends"> + <div class="ele-text-small ele-elip"> + <a-badge color="green" text="活跃率: 70%" /> + </div> + <div class="ele-text-small ele-elip"> + <a-badge color="blue" text="留存率: 60%" /> + </div> + <div class="ele-text-small ele-elip"> + <a-badge color="red" text="跳出率: 35%" /> + </div> + </div> + </div> + </a-card> +</template> + +<style lang="less" scoped> + .monitor-progress-group { + position: relative; + display: inline-block; + + .ant-progress:not(:first-child) { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + margin-top: -1px; + } + } + + .monitor-progress-legends { + padding-right: 24px; + + :deep(.ant-badge-status-text) { + font-size: 12px; + } + + & > div + div { + margin-top: 8px; + } + } +</style> diff --git a/src/views-demo/dashboard/monitor/components/user-rate.vue b/src/views-demo/dashboard/monitor/components/user-rate.vue new file mode 100644 index 0000000..e88c449 --- /dev/null +++ b/src/views-demo/dashboard/monitor/components/user-rate.vue @@ -0,0 +1,86 @@ +<template> + <a-card :bordered="false" title="用户评价"> + <div class="ele-cell ele-cell-align-bottom"> + <div style="font-size: 51px; line-height: 1">4.5</div> + <div class="ele-cell-content"> + <a-rate :value="userRate" disabled /> + <span style="color: #fadb14; margin-left: 8px">很棒</span> + </div> + </div> + <div class="ele-cell" style="margin: 18px 0"> + <div style="font-size: 28px; line-height: 1" class="ele-text-placeholder"> + -0% + </div> + <div class="ele-cell-content ele-text-small ele-text-secondary"> + 当前没有评价波动 + </div> + </div> + <div class="ele-cell"> + <div class="ele-cell-content"> + <a-progress :percent="60" stroke-color="#52c41a" :show-info="false" /> + </div> + <div class="monitor-evaluate-text"> + <star-filled /> + <span>5 : 368 人</span> + </div> + </div> + <div class="ele-cell"> + <div class="ele-cell-content"> + <a-progress :percent="40" stroke-color="#1890ff" :show-info="false" /> + </div> + <div class="monitor-evaluate-text"> + <star-filled /> + <span>4 : 256 人</span> + </div> + </div> + <div class="ele-cell"> + <div class="ele-cell-content"> + <a-progress :percent="20" stroke-color="#faad14" :show-info="false" /> + </div> + <div class="monitor-evaluate-text"> + <star-filled /> + <span>3 : 49 人</span> + </div> + </div> + <div class="ele-cell"> + <div class="ele-cell-content"> + <a-progress :percent="10" stroke-color="#f5222d" :show-info="false" /> + </div> + <div class="monitor-evaluate-text"> + <star-filled /> + <span>2 : 14 人</span> + </div> + </div> + <div class="ele-cell"> + <div class="ele-cell-content"> + <a-progress :percent="0" :show-info="false" /> + </div> + <div class="monitor-evaluate-text"> + <star-filled /> + <span>1 : 0 人</span> + </div> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { StarFilled } from '@ant-design/icons-vue'; + + // 用户评分 + const userRate = ref(4.5); +</script> + +<style lang="less" scoped> + .monitor-evaluate-text { + width: 90px; + flex-shrink: 0; + white-space: nowrap; + opacity: 0.8; + + & > .anticon { + font-size: 12px; + margin: 0 6px 0 8px; + } + } +</style> diff --git a/src/views-demo/dashboard/monitor/components/user-satisfaction.vue b/src/views-demo/dashboard/monitor/components/user-satisfaction.vue new file mode 100644 index 0000000..4933d1a --- /dev/null +++ b/src/views-demo/dashboard/monitor/components/user-satisfaction.vue @@ -0,0 +1,79 @@ +<template> + <a-card :bordered="false" title="用户满意度"> + <div class="ele-cell ele-text-center"> + <div class="ele-cell-content" style="font-size: 24px">856</div> + <div class="ele-cell-content"> + <div class="monitor-face-smile"><i></i></div> + <div class="ele-text-secondary ele-elip" style="margin-top: 8px"> + 正面评论 + </div> + </div> + <h2 class="ele-cell-content ele-text-success">82%</h2> + </div> + <a-divider style="margin: 26px 0" /> + <div class="ele-cell ele-text-center"> + <div class="ele-cell-content" style="font-size: 24px">60</div> + <div class="ele-cell-content"> + <div class="monitor-face-cry"><i></i></div> + <div class="ele-text-secondary ele-elip" style="margin-top: 8px"> + 负面评论 + </div> + </div> + <h2 class="ele-cell-content ele-text-danger">9%</h2> + </div> + </a-card> +</template> + +<style lang="less" scoped> + .monitor-face-smile, + .monitor-face-cry { + width: 50px; + height: 50px; + display: inline-block; + background: #fbd971; + border-radius: 50%; + position: relative; + } + + .monitor-face-smile > i, + .monitor-face-smile:before, + .monitor-face-smile:after, + .monitor-face-cry > i, + .monitor-face-cry:before, + .monitor-face-cry:after { + width: 28px; + height: 28px; + border-radius: 50%; + transform: rotate(225deg); + border: 3px solid #f0c419; + border-right-color: transparent !important; + border-bottom-color: transparent !important; + position: absolute; + bottom: 8px; + left: 11px; + } + + .monitor-face-smile:before, + .monitor-face-smile:after, + .monitor-face-cry:before, + .monitor-face-cry:after { + content: ''; + width: 12px; + height: 12px; + left: 8px; + top: 14px; + border-color: #f29c1f; + transform: rotate(45deg); + } + + .monitor-face-smile:after, + .monitor-face-cry:after { + left: auto; + right: 8px; + } + + .monitor-face-cry > i { + transform: rotate(45deg); + bottom: -6px; + } +</style> diff --git a/src/views-demo/dashboard/monitor/index.vue b/src/views-demo/dashboard/monitor/index.vue new file mode 100644 index 0000000..c9a72d8 --- /dev/null +++ b/src/views-demo/dashboard/monitor/index.vue @@ -0,0 +1,91 @@ +<template> + <div class="ele-body ele-body-card"> + <statistics-card /> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { lg: 18, md: 24, sm: 24, xs: 24 } : { span: 18 } + " + > + <map-card /> + </a-col> + <a-col + v-bind=" + styleResponsive ? { lg: 6, md: 24, sm: 24, xs: 24 } : { span: 6 } + " + > + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive + ? { lg: 24, md: 12, sm: 12, xs: 24 } + : { span: 24 } + " + > + <online-num /> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { lg: 24, md: 12, sm: 12, xs: 24 } + : { span: 24 } + " + > + <browser-card /> + </a-col> + </a-row> + </a-col> + </a-row> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive + ? { xl: 12, lg: 24, md: 24, sm: 24, xs: 24 } + : { span: 12 } + " + > + <user-rate /> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 12, xs: 24 } + : { span: 6 } + " + > + <user-satisfaction /> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 12, xs: 24 } + : { span: 6 } + " + > + <user-liveness /> + </a-col> + </a-row> + </div> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import StatisticsCard from './components/statistics-card.vue'; + import MapCard from './components/map-card.vue'; + import OnlineNum from './components/online-num.vue'; + import BrowserCard from './components/browser-card.vue'; + import UserRate from './components/user-rate.vue'; + import UserSatisfaction from './components/user-satisfaction.vue'; + import UserLiveness from './components/user-liveness.vue'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); +</script> + +<script lang="ts"> + export default { + name: 'DashboardMonitor' + }; +</script> diff --git a/src/views-demo/dashboard/workplace/components/activities-card.vue b/src/views-demo/dashboard/workplace/components/activities-card.vue new file mode 100644 index 0000000..864f5d9 --- /dev/null +++ b/src/views-demo/dashboard/workplace/components/activities-card.vue @@ -0,0 +1,138 @@ +<!-- 最新动态 --> +<template> + <a-card :title="title" :bordered="false" :body-style="{ padding: '6px 0' }"> + <template #extra> + <more-icon @remove="onRemove" @edit="onEdit" /> + </template> + <div + style="height: 346px; padding: 22px 20px 0 20px" + class="ele-scrollbar-hover" + > + <a-timeline> + <a-timeline-item + v-for="item in activities" + :key="item.id" + :color="item.color" + > + <em>{{ item.time }}</em> + <em>{{ item.title }}</em> + </a-timeline-item> + </a-timeline> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import MoreIcon from './more-icon.vue'; + + defineProps<{ + title?: string; + }>(); + + const emit = defineEmits<{ + (e: 'remove'): void; + (e: 'edit'): void; + }>(); + + interface Activitie { + id: number; + title: string; + time: string; + color?: string; + } + + // 最新动态数据 + const activities = ref<Activitie[]>([]); + + /* 查询最新动态 */ + const queryActivities = () => { + activities.value = [ + { + id: 1, + title: 'SunSmile 解决了bug 登录提示操作失败', + time: '20:30', + color: 'gray' + }, + { + id: 2, + title: 'Jasmine 解决了bug 按钮颜色与设计不符', + time: '19:30', + color: 'gray' + }, + { + id: 3, + title: '项目经理 指派了任务 解决项目一的bug', + time: '18:30' + }, + { + id: 4, + title: '项目经理 指派了任务 解决项目二的bug', + time: '17:30' + }, + { + id: 5, + title: '项目经理 指派了任务 解决项目三的bug', + time: '16:30' + }, + { + id: 6, + title: '项目经理 指派了任务 解决项目四的bug', + time: '15:30', + color: 'gray' + }, + { + id: 7, + title: '项目经理 指派了任务 解决项目五的bug', + time: '14:30', + color: 'gray' + }, + { + id: 8, + title: '项目经理 指派了任务 解决项目六的bug', + time: '12:30', + color: 'gray' + }, + { + id: 9, + title: '项目经理 指派了任务 解决项目七的bug', + time: '11:30' + }, + { + id: 10, + title: '项目经理 指派了任务 解决项目八的bug', + time: '10:30', + color: 'gray' + }, + { + id: 11, + title: '项目经理 指派了任务 解决项目九的bug', + time: '09:30', + color: 'green' + }, + { + id: 12, + title: '项目经理 指派了任务 解决项目十的bug', + time: '08:30', + color: 'red' + } + ]; + }; + + const onRemove = () => { + emit('remove'); + }; + + const onEdit = () => { + emit('edit'); + }; + + queryActivities(); +</script> + +<style lang="less" scoped> + .ele-scrollbar-hover + :deep(.ant-timeline-item-last > .ant-timeline-item-content) { + min-height: auto; + } +</style> diff --git a/src/views-demo/dashboard/workplace/components/goal-card.vue b/src/views-demo/dashboard/workplace/components/goal-card.vue new file mode 100644 index 0000000..b5ebf76 --- /dev/null +++ b/src/views-demo/dashboard/workplace/components/goal-card.vue @@ -0,0 +1,70 @@ +<!-- 本月目标 --> +<template> + <a-card :title="title" :bordered="false"> + <template #extra> + <more-icon @remove="onRemove" @edit="onEdit" /> + </template> + <div class="workplace-goal-group"> + <a-progress + :width="180" + :percent="80" + type="dashboard" + :stroke-width="4" + :show-info="false" + /> + <div class="workplace-goal-content"> + <ele-tag color="blue" size="large" shape="circle"> + <trophy-outlined /> + </ele-tag> + <div class="workplace-goal-num">285</div> + </div> + <div class="workplace-goal-text">恭喜, 本月目标已达标!</div> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { TrophyOutlined } from '@ant-design/icons-vue'; + import MoreIcon from './more-icon.vue'; + + defineProps<{ + title?: string; + }>(); + + const emit = defineEmits<{ + (e: 'remove'): void; + (e: 'edit'): void; + }>(); + + const onRemove = () => { + emit('remove'); + }; + + const onEdit = () => { + emit('edit'); + }; +</script> + +<style lang="less" scoped> + .workplace-goal-group { + height: 310px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + + .workplace-goal-content { + position: absolute; + top: 50%; + left: 50%; + width: 180px; + margin: -50px 0 0 -90px; + text-align: center; + } + + .workplace-goal-num { + font-size: 40px; + } + } +</style> diff --git a/src/views-demo/dashboard/workplace/components/link-card.vue b/src/views-demo/dashboard/workplace/components/link-card.vue new file mode 100644 index 0000000..ae1352e --- /dev/null +++ b/src/views-demo/dashboard/workplace/components/link-card.vue @@ -0,0 +1,187 @@ +<!-- 快捷方式 --> +<template> + <a-row :gutter="16" ref="wrapRef"> + <a-col + v-for="item in data" + :key="item.url" + v-bind="styleResponsive ? { lg: 3, md: 6, sm: 12, xs: 12 } : { span: 3 }" + > + <a-card :bordered="false" hoverable :body-style="{ padding: 0 }"> + <router-link :to="item.url" class="app-link-block"> + <component + :is="item.icon" + class="app-link-icon" + :style="{ color: item.color }" + /> + <div class="app-link-title">{{ item.title }}</div> + </router-link> + </a-card> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { ref, onMounted, onBeforeUnmount } from 'vue'; + import SortableJs from 'sortablejs'; + import type { Row as ARow } from 'ant-design-vue/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + const CACHE_KEY = 'workplace-links'; + + interface LinkItem { + icon: string; + title: string; + url: string; + color?: string; + } + + // 默认顺序 + const DEFAULT: LinkItem[] = [ + { + icon: 'user-outlined', + title: '用户', + url: '/system/user' + }, + { + icon: 'shopping-cart-outlined', + title: '分析', + url: '/dashboard/analysis', + color: '#95de64' + }, + { + icon: 'fund-projection-screen-outlined', + title: '商品', + url: '/list/card/project', + color: '#ff9c6e' + }, + { + icon: 'file-search-outlined', + title: '订单', + url: '/list/basic', + color: '#b37feb' + }, + { + icon: 'credit-card-outlined', + title: '票据', + url: '/list/advanced', + color: '#ffd666' + }, + { + icon: 'mail-outlined', + title: '消息', + url: '/user/message', + color: '#5cdbd3' + }, + { + icon: 'tags-outlined', + title: '标签', + url: '/extension/tag', + color: '#ff85c0' + }, + { + icon: 'control-outlined', + title: '配置', + url: '/user/profile', + color: '#ffc069' + } + ]; + + // 获取缓存的顺序 + const cache = (() => { + const str = localStorage.getItem(CACHE_KEY); + try { + return str ? JSON.parse(str) : null; + } catch (e) { + return null; + } + })(); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const data = ref<LinkItem[]>([...(cache ?? DEFAULT)]); + + const wrapRef = ref<InstanceType<typeof ARow> | null>(null); + + let sortableIns: SortableJs | null = null; + + /* 重置布局 */ + const reset = () => { + data.value = [...DEFAULT]; + cacheData(); + }; + + /* 缓存布局 */ + const cacheData = () => { + localStorage.setItem(CACHE_KEY, JSON.stringify(data.value)); + }; + + onMounted(() => { + const isTouchDevice = 'ontouchstart' in document.documentElement; + if (isTouchDevice) { + return; + } + sortableIns = new SortableJs(wrapRef.value?.$el, { + animation: 300, + onUpdate: ({ oldIndex, newIndex }) => { + if (typeof oldIndex === 'number' && typeof newIndex === 'number') { + const temp = [...data.value]; + temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]); + data.value = temp; + cacheData(); + } + }, + setData: () => {} + }); + }); + + onBeforeUnmount(() => { + if (sortableIns) { + sortableIns.destroy(); + } + }); + + defineExpose({ reset }); +</script> + +<script lang="ts"> + import { + UserOutlined, + ShoppingCartOutlined, + FundProjectionScreenOutlined, + FileSearchOutlined, + CreditCardOutlined, + MailOutlined, + TagsOutlined, + ControlOutlined + } from '@ant-design/icons-vue'; + + export default { + components: { + UserOutlined, + ShoppingCartOutlined, + FundProjectionScreenOutlined, + FileSearchOutlined, + CreditCardOutlined, + MailOutlined, + TagsOutlined, + ControlOutlined + } + }; +</script> + +<style lang="less" scoped> + .app-link-block { + padding: 12px; + text-align: center; + display: block; + color: inherit; + + .app-link-icon { + color: #69c0ff; + font-size: 30px; + margin: 6px 0 10px 0; + } + } +</style> diff --git a/src/views-demo/dashboard/workplace/components/more-icon.vue b/src/views-demo/dashboard/workplace/components/more-icon.vue new file mode 100644 index 0000000..2823738 --- /dev/null +++ b/src/views-demo/dashboard/workplace/components/more-icon.vue @@ -0,0 +1,38 @@ +<template> + <a-dropdown placement="bottomRight"> + <more-outlined class="ele-text-secondary" style="font-size: 18px" /> + <template #overlay> + <a-menu :selectable="false" @click="onClick"> + <a-menu-item key="edit"> + <div class="ele-cell"> + <edit-outlined /> + <div class="ele-cell-content">编辑</div> + </div> + </a-menu-item> + <a-menu-item key="remove"> + <div class="ele-cell ele-text-danger"> + <delete-outlined /> + <div class="ele-cell-content">删除</div> + </div> + </a-menu-item> + </a-menu> + </template> + </a-dropdown> +</template> + +<script lang="ts" setup> + import { + MoreOutlined, + EditOutlined, + DeleteOutlined + } from '@ant-design/icons-vue'; + + const emit = defineEmits<{ + (e: 'edit'): void; + (e: 'remove'): void; + }>(); + + const onClick = ({ key }) => { + emit(key); + }; +</script> diff --git a/src/views-demo/dashboard/workplace/components/profile-card.vue b/src/views-demo/dashboard/workplace/components/profile-card.vue new file mode 100644 index 0000000..1007e4b --- /dev/null +++ b/src/views-demo/dashboard/workplace/components/profile-card.vue @@ -0,0 +1,119 @@ +<!-- 用户信息 --> +<template> + <a-card :bordered="false" :body-style="{ padding: '20px' }"> + <div + :class="[ + 'ele-cell', + 'workplace-user-card', + { 'workplace-user-responsive': styleResponsive } + ]" + > + <div class="ele-cell-content ele-cell"> + <a-avatar :size="68" :src="loginUser.avatar" /> + <div class="ele-cell-content"> + <h4 class="ele-elip"> + 早安, {{ loginUser.nickname }}, 开始您一天的工作吧! + </h4> + <div class="ele-elip ele-text-secondary"> + <cloud-outlined /> + <em>今日多云转阴,18℃ - 22℃,出门记得穿外套哦~</em> + </div> + </div> + </div> + <div class="workplace-count-group"> + <div class="workplace-count-item"> + <div class="workplace-count-header"> + <ele-tag color="blue" shape="circle" size="small"> + <appstore-filled /> + </ele-tag> + <span class="workplace-count-name">项目数</span> + </div> + <h2>3</h2> + </div> + <div class="workplace-count-item"> + <div class="workplace-count-header"> + <ele-tag color="orange" shape="circle" size="small"> + <check-square-outlined /> + </ele-tag> + <span class="workplace-count-name">待办项</span> + </div> + <h2>6 / 24</h2> + </div> + <div class="workplace-count-item"> + <div class="workplace-count-header"> + <ele-tag color="green" shape="circle" size="small"> + <bell-filled /> + </ele-tag> + <span class="workplace-count-name">消息</span> + </div> + <h2>1,689</h2> + </div> + </div> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { computed } from 'vue'; + import { + CloudOutlined, + AppstoreFilled, + CheckSquareOutlined, + BellFilled + } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { useUserStore } from '@/store/modules/user'; + + const userStore = useUserStore(); + + // 当前登录用户信息 + const loginUser = computed(() => userStore.info ?? {}); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); +</script> + +<style lang="less" scoped> + .workplace-user-card { + .ele-cell-content { + overflow: hidden; + } + + h4 { + margin-bottom: 6px; + } + } + + .workplace-count-group { + white-space: nowrap; + text-align: right; + flex-shrink: 0; + } + + .workplace-count-item { + display: inline-block; + margin: 0 4px 0 24px; + } + + .workplace-count-name { + margin-left: 8px; + } + + @media screen and (max-width: 992px) { + .workplace-user-responsive .workplace-count-item { + margin: 0 2px 0 12px; + } + } + + @media screen and (max-width: 768px) { + .workplace-user-responsive.workplace-user-card { + display: block; + + .workplace-count-group { + margin-top: 8px; + } + } + } +</style> diff --git a/src/views-demo/dashboard/workplace/components/project-card.vue b/src/views-demo/dashboard/workplace/components/project-card.vue new file mode 100644 index 0000000..14cb2a3 --- /dev/null +++ b/src/views-demo/dashboard/workplace/components/project-card.vue @@ -0,0 +1,179 @@ +<!-- 项目进度 --> +<template> + <a-card + :title="title" + :bordered="false" + :body-style="{ padding: '14px', height: '358px' }" + > + <template #extra> + <more-icon @remove="onRemove" @edit="onEdit" /> + </template> + <a-table + row-key="id" + size="middle" + :pagination="false" + :data-source="projectList" + :columns="projectColumns" + :scroll="{ x: 600 }" + > + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'projectName'"> + <a>{{ record.projectName }}</a> + </template> + <template v-else-if="column.key === 'status'"> + <span v-if="record.status === 0" class="ele-text-success"> + 进行中 + </span> + <span v-else-if="record.status === 1" class="ele-text-danger"> + 已延期 + </span> + <span v-else-if="record.status === 2" class="ele-text-warning"> + 未开始 + </span> + <span + v-else-if="record.status === 3" + class="ele-text-info ele-text-delete" + > + 已结束 + </span> + </template> + <template v-else-if="column.key === 'progress'"> + <a-progress :percent="record.progress" size="small" /> + </template> + </template> + </a-table> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import MoreIcon from './more-icon.vue'; + import type { ColumnsType } from 'ant-design-vue/es/table'; + + defineProps<{ + title?: string; + }>(); + + const emit = defineEmits<{ + (e: 'remove'): void; + (e: 'edit'): void; + }>(); + + interface Project { + id: number; + projectName: string; + status: number; + startDate: string; + endDate: string; + progress: number; + } + + const projectColumns = ref<ColumnsType>([ + { + key: 'index', + align: 'center', + width: 38, + customRender: ({ index }) => index + 1, + fixed: 'left' + }, + { + title: '项目名称', + key: 'projectName', + ellipsis: true, + minWidth: 120 + }, + { + title: '开始时间', + dataIndex: 'startDate', + align: 'center', + minWidth: 100, + ellipsis: true + }, + { + title: '结束时间', + dataIndex: 'endDate', + align: 'center', + minWidth: 100, + ellipsis: true + }, + { + title: '状态', + key: 'status', + align: 'center', + width: 90 + }, + { + title: '进度', + key: 'progress', + align: 'center', + width: 180 + } + ]); + + // 项目进度数据 + const projectList = ref<Project[]>([]); + + /* 查询项目进度 */ + const queryProjectList = () => { + projectList.value = [ + { + id: 1, + projectName: '项目0000001', + status: 0, + startDate: '2020-03-01', + endDate: '2020-06-01', + progress: 30 + }, + { + id: 2, + projectName: '项目0000002', + status: 0, + startDate: '2020-03-01', + endDate: '2020-08-01', + progress: 10 + }, + { + id: 3, + projectName: '项目0000003', + status: 1, + startDate: '2020-01-01', + endDate: '2020-05-01', + progress: 60 + }, + { + id: 4, + projectName: '项目0000004', + status: 1, + startDate: '2020-06-01', + endDate: '2020-10-01', + progress: 0 + }, + { + id: 5, + projectName: '项目0000005', + status: 2, + startDate: '2020-01-01', + endDate: '2020-03-01', + progress: 100 + }, + { + id: 6, + projectName: '项目0000006', + status: 3, + startDate: '2020-01-01', + endDate: '2020-03-01', + progress: 100 + } + ]; + }; + + const onRemove = () => { + emit('remove'); + }; + + const onEdit = () => { + emit('edit'); + }; + + queryProjectList(); +</script> diff --git a/src/views-demo/dashboard/workplace/components/task-card.vue b/src/views-demo/dashboard/workplace/components/task-card.vue new file mode 100644 index 0000000..c0a60ed --- /dev/null +++ b/src/views-demo/dashboard/workplace/components/task-card.vue @@ -0,0 +1,157 @@ +<!-- 我的任务 --> +<template> + <a-card + :title="title" + :bordered="false" + :body-style="{ padding: '10px', height: '358px' }" + class="workplace-table-card" + > + <template #extra> + <more-icon @remove="onRemove" @edit="onEdit" /> + </template> + <div style="overflow: auto; position: relative"> + <table class="ele-table" style="table-layout: fixed; min-width: 300px"> + <colgroup> + <col width="38" /> + <col width="65" /> + <col /> + <col width="70" /> + </colgroup> + <thead> + <tr> + <th style="position: sticky; left: 0"></th> + <th style="text-align: center">优先级</th> + <th>任务名称</th> + <th style="text-align: center">状态</th> + </tr> + </thead> + <vue-draggable + tag="tbody" + item-key="id" + v-model="taskList" + handle=".sort-handle" + :animation="300" + :set-data="() => void 0" + > + <template #item="{ element }"> + <tr> + <td style="text-align: center; position: sticky; left: 0"> + <menu-outlined class="sort-handle ele-text-secondary" /> + </td> + <td style="text-align: center"> + <ele-tag + :color="['red', 'orange', 'blue'][element.priority - 1]" + shape="circle" + > + {{ element.priority }} + </ele-tag> + </td> + <td class="ele-elip" :title="element.taskName"> + <a>{{ element.taskName }}</a> + </td> + <td style="text-align: center"> + <span v-if="element.status === 0" class="ele-text-warning"> + 未开始 + </span> + <span v-else-if="element.status === 1" class="ele-text-success"> + 进行中 + </span> + <span v-else-if="element.status === 2" class="ele-text-info"> + 已完成 + </span> + </td> + </tr> + </template> + </vue-draggable> + </table> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import VueDraggable from 'vuedraggable'; + import { MenuOutlined } from '@ant-design/icons-vue'; + import MoreIcon from './more-icon.vue'; + + defineProps<{ + title?: string; + }>(); + + const emit = defineEmits<{ + (e: 'remove'): void; + (e: 'edit'): void; + }>(); + + interface Task { + id: number; + priority: number; + taskName: string; + status: number; + } + + // 我的任务数据 + const taskList = ref<Task[]>([]); + + /* 查询我的任务 */ + const queryTaskList = () => { + taskList.value = [ + { + id: 1, + priority: 1, + taskName: '解决项目一的bug', + status: 0 + }, + { + id: 2, + priority: 2, + taskName: '解决项目二的bug', + status: 0 + }, + { + id: 3, + priority: 2, + taskName: '解决项目三的bug', + status: 1 + }, + { + id: 4, + priority: 3, + taskName: '解决项目四的bug', + status: 1 + }, + { + id: 5, + priority: 3, + taskName: '解决项目五的bug', + status: 2 + }, + { + id: 6, + priority: 3, + taskName: '解决项目六的bug', + status: 2 + } + ]; + }; + + const onRemove = () => { + emit('remove'); + }; + + const onEdit = () => { + emit('edit'); + }; + + queryTaskList(); +</script> + +<style lang="less" scoped> + .ele-table tr.sortable-chosen { + background: hsla(0, 0%, 60%, 0.1); + } + + .workplace-table-card .sort-handle { + cursor: move; + } +</style> diff --git a/src/views-demo/dashboard/workplace/components/user-list.vue b/src/views-demo/dashboard/workplace/components/user-list.vue new file mode 100644 index 0000000..7ae8bc7 --- /dev/null +++ b/src/views-demo/dashboard/workplace/components/user-list.vue @@ -0,0 +1,123 @@ +<!-- 小组成员 --> +<template> + <a-card :title="title" :bordered="false" :body-style="{ padding: '2px 0px' }"> + <template #extra> + <more-icon @remove="onRemove" @edit="onEdit" /> + </template> + <div + v-for="(item, index) in userList" + :key="index" + class="ele-cell user-list-item" + > + <div style="flex-shrink: 0"> + <a-avatar :size="46" :src="item.avatar" /> + </div> + <div class="ele-cell-content"> + <div class="ele-cell-title ele-elip">{{ item.name }}</div> + <div class="ele-cell-desc ele-elip">{{ item.introduction }}</div> + </div> + <div style="flex-shrink: 0"> + <a-tag :color="['green', 'red'][item.status]"> + {{ ['在线', '离线'][item.status] }} + </a-tag> + </div> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import MoreIcon from './more-icon.vue'; + + defineProps<{ + title?: string; + }>(); + + const emit = defineEmits<{ + (e: 'remove'): void; + (e: 'edit'): void; + }>(); + + interface User { + name: string; + introduction: string; + status: number; + avatar: string; + } + + // 小组成员数据 + const userList = ref<User[]>([]); + + /* 查询小组成员 */ + const queryUserList = () => { + userList.value = [ + { + name: 'SunSmile', + introduction: 'UI设计师、交互专家', + status: 0, + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + name: '你的名字很好听', + introduction: '前端工程师', + status: 0, + avatar: + 'https://cdn.eleadmin.com/20200609/b6a811873e704db49db994053a5019b2.jpg' + }, + { + name: '全村人的希望', + introduction: '前端工程师', + status: 0, + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + }, + { + name: 'Jasmine', + introduction: '产品经理、项目经理', + status: 1, + avatar: + 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg' + }, + { + name: '酷酷的大叔', + introduction: '组长、后端工程师', + status: 1, + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + } + ]; + }; + + const onRemove = () => { + emit('remove'); + }; + + const onEdit = () => { + emit('edit'); + }; + + queryUserList(); +</script> + +<style lang="less" scoped> + .user-list-item { + padding: 12px 18px; + + & + .user-list-item { + border-top: 1px solid hsla(0, 0%, 60%, 0.15); + } + + .ele-cell-content { + overflow: hidden; + } + + .ele-cell-desc { + margin-top: 0; + } + + .ant-tag { + margin: 0; + } + } +</style> diff --git a/src/views-demo/dashboard/workplace/index.vue b/src/views-demo/dashboard/workplace/index.vue new file mode 100644 index 0000000..ff55d7c --- /dev/null +++ b/src/views-demo/dashboard/workplace/index.vue @@ -0,0 +1,294 @@ +<template> + <div class="ele-body ele-body-card"> + <profile-card /> + <link-card ref="linkCardRef" /> + <a-row :gutter="16" ref="wrapRef"> + <a-col + v-for="(item, index) in data" + :key="item.name" + v-bind=" + styleResponsive + ? { lg: item.lg, md: item.md, sm: item.sm, xs: item.xs } + : { span: item.lg } + " + > + <component + :is="item.name" + :title="item.title" + @remove="onRemove(index)" + @edit="onEdit(index)" + /> + </a-col> + </a-row> + <a-card :bordered="false" :body-style="{ padding: 0 }"> + <div class="ele-cell" style="line-height: 42px"> + <div + class="ele-cell-content ele-text-primary workplace-bottom-btn" + @click="add" + > + <plus-circle-outlined /> 添加视图 + </div> + <a-divider type="vertical" /> + <div + class="ele-cell-content ele-text-primary workplace-bottom-btn" + @click="reset" + > + <undo-outlined /> 重置布局 + </div> + </div> + </a-card> + <ele-modal + :width="680" + v-model:visible="visible" + title="未添加的视图" + :footer="null" + > + <a-row :gutter="16"> + <a-col + v-for="item in notAddedData" + :key="item.name" + v-bind="styleResponsive ? { md: 8, sm: 12, xs: 24 } : { span: 8 }" + > + <div + class="workplace-card-item ele-border-split" + @click="addView(item)" + > + <div class="workplace-card-header ele-border-split"> + {{ item.title }} + </div> + <div class="workplace-card-body ele-text-placeholder"> + <plus-circle-outlined /> + </div> + </div> + </a-col> + </a-row> + <a-empty v-if="!notAddedData.length" description="已添加所有视图" /> + </ele-modal> + </div> +</template> + +<script lang="ts" setup> + import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; + import SortableJs from 'sortablejs'; + import type { Row as ARow } from 'ant-design-vue/es'; + import { message } from 'ant-design-vue/es'; + import { PlusCircleOutlined, UndoOutlined } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import ProfileCard from './components/profile-card.vue'; + import LinkCard from './components/link-card.vue'; + const CACHE_KEY = 'workplace-layout'; + + interface ViewItem { + name: string; + title: string; + lg: number; + md: number; + sm: number; + xs: number; + } + + // 默认布局 + const DEFAULT: ViewItem[] = [ + { + name: 'activities-card', + title: '最新动态', + lg: 8, + md: 24, + sm: 24, + xs: 24 + }, + { + name: 'task-card', + title: '我的任务', + lg: 8, + md: 24, + sm: 24, + xs: 24 + }, + { + name: 'goal-card', + title: '本月目标', + lg: 8, + md: 24, + sm: 24, + xs: 24 + }, + { + name: 'project-card', + title: '项目进度', + lg: 16, + md: 24, + sm: 24, + xs: 24 + }, + { + name: 'user-list', + title: '小组成员', + lg: 8, + md: 24, + sm: 24, + xs: 24 + } + ]; + + // 获取缓存的顺序 + const cache = (() => { + const str = localStorage.getItem(CACHE_KEY); + try { + return str ? JSON.parse(str) : null; + } catch (e) { + return null; + } + })(); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const data = ref<ViewItem[]>([...(cache ?? DEFAULT)]); + + const visible = ref(false); + + const linkCardRef = ref<InstanceType<typeof LinkCard> | null>(null); + + const wrapRef = ref<InstanceType<typeof ARow> | null>(null); + + let sortableIns: SortableJs | null = null; + + // 未添加的数据 + const notAddedData = computed(() => { + return DEFAULT.filter((d) => !data.value.some((t) => t.name === d.name)); + }); + + /* 添加 */ + const add = () => { + visible.value = true; + }; + + /* 重置布局 */ + const reset = () => { + data.value = [...DEFAULT]; + cacheData(); + linkCardRef.value?.reset(); + message.success('已重置'); + }; + + /* 缓存布局 */ + const cacheData = () => { + localStorage.setItem(CACHE_KEY, JSON.stringify(data.value)); + }; + + /* 删除视图 */ + const onRemove = (index: number) => { + data.value = data.value.filter((_d, i) => i !== index); + cacheData(); + }; + + /* 编辑视图 */ + const onEdit = (index: number) => { + console.log('index:', index); + message.info('点击了编辑'); + }; + + /* 添加视图 */ + const addView = (item) => { + data.value.push(item); + cacheData(); + message.success('已添加'); + }; + + onMounted(() => { + const isTouchDevice = 'ontouchstart' in document.documentElement; + if (isTouchDevice) { + return; + } + sortableIns = new SortableJs(wrapRef.value?.$el, { + handle: '.ant-card-head', + animation: 300, + onUpdate: ({ oldIndex, newIndex }) => { + if (typeof oldIndex === 'number' && typeof newIndex === 'number') { + const temp = [...data.value]; + temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]); + data.value = temp; + cacheData(); + } + }, + setData: () => {} + }); + }); + + onBeforeUnmount(() => { + if (sortableIns) { + sortableIns.destroy(); + } + }); +</script> + +<script lang="ts"> + import ActivitiesCard from './components/activities-card.vue'; + import TaskCard from './components/task-card.vue'; + import GoalCard from './components/goal-card.vue'; + import ProjectCard from './components/project-card.vue'; + import UserList from './components/user-list.vue'; + + export default { + name: 'DashboardWorkplace', + components: { + ActivitiesCard, + TaskCard, + GoalCard, + ProjectCard, + UserList + } + }; +</script> + +<style lang="less" scoped> + .ele-body :deep(.ant-card-head) { + cursor: move; + position: relative; + } + + .ele-body :deep(.ant-row > .ant-col.sortable-chosen > .ant-card) { + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2); + } + + .workplace-bottom-btn { + text-align: center; + cursor: pointer; + transition: background-color 0.2s; + } + + .workplace-bottom-btn:hover { + background: hsla(0, 0%, 60%, 0.05); + } + + /* 添加弹窗 */ + .workplace-card-item { + margin-bottom: 15px; + border-width: 1px; + border-style: solid; + border-radius: 4px; + position: relative; + cursor: pointer; + transition: box-shadow 0.2s, background-color 0.2s; + } + + .workplace-card-item:hover { + box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1); + background: hsla(0, 0%, 60%, 0.05); + } + + .workplace-card-item .workplace-card-header { + border-bottom-width: 1px; + border-bottom-style: solid; + padding: 8px; + } + + .workplace-card-body { + font-size: 26px; + padding: 24px 10px; + text-align: center; + } +</style> diff --git a/src/views-demo/example/choose/index.vue b/src/views-demo/example/choose/index.vue new file mode 100644 index 0000000..956c9a7 --- /dev/null +++ b/src/views-demo/example/choose/index.vue @@ -0,0 +1,178 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false" :body-style="{ padding: '16px 16px' }"> + <a-row :gutter="14"> + <a-col + v-bind=" + styleResponsive ? { lg: 12, md: 24, sm: 24, xs: 24 } : { span: 12 } + " + > + <!-- 未选择的班级数据表格 --> + <ele-pro-table + bordered + size="small" + :toolkit="[]" + :columns="columns" + row-key="classesId" + sub-title="未选班级:" + empty-text="已全部选择" + tools-theme="default" + :show-size-changer="false" + :datasource="unChooseClass" + :scroll="{ x: 400 }" + > + <template #toolkit> + <a-button type="dashed" class="ele-btn-icon" @click="addAll"> + 全部添加 + </a-button> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'action'"> + <a-button size="small" type="dashed" @click="addItem(record)"> + 添加 + </a-button> + </template> + </template> + </ele-pro-table> + </a-col> + <a-col + v-bind=" + styleResponsive ? { lg: 12, md: 24, sm: 24, xs: 24 } : { span: 12 } + " + > + <!-- 已选择的班级数据表格 --> + <ele-pro-table + bordered + size="small" + :toolkit="[]" + :columns="columns" + row-key="classesId" + sub-title="已选班级:" + emptyText="未选择班级" + tools-theme="default" + :show-size-changer="false" + :datasource="chooseClasses" + :scroll="{ x: 400 }" + > + <template #toolkit> + <a-button + danger + type="dashed" + class="ele-btn-icon" + @click="removeAll" + > + 全部移除 + </a-button> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'action'"> + <a-button + danger + size="small" + type="dashed" + @click="removeItem(record)" + > + 移除 + </a-button> + </template> + </template> + </ele-pro-table> + </a-col> + </a-row> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, computed } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { getAllClasses } from '@/api/example/choose'; + import type { Classes } from '@/api/example/choose/model'; + import type { ColumnItem } from 'ele-admin-pro/es/ele-pro-table/types'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // 全部班级 + const classes = ref<Classes[]>([]); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + width: 90, + title: '操作', + key: 'action', + align: 'center', + fixed: 'left' + }, + { + title: '班级名称', + dataIndex: 'classesName', + ellipsis: true, + sorter: true + }, + { + title: '专业', + dataIndex: 'major', + ellipsis: true, + sorter: true + }, + { + title: '学院', + dataIndex: 'college', + ellipsis: true, + sorter: true + } + ]); + + // 已选择的班级数据 + const chooseClasses = ref<Classes[]>([]); + + // 未选择的班级数据 + const unChooseClass = computed(() => + classes.value.filter((d) => chooseClasses.value.indexOf(d) === -1) + ); + + /* 获取全部班级 */ + const query = () => { + getAllClasses() + .then((data) => { + classes.value = data; + }) + .catch((e) => { + message.error(e.message); + }); + }; + + query(); + + /* 添加 */ + const addItem = (row: Classes) => { + chooseClasses.value = [...chooseClasses.value, row]; + }; + + /* 移除 */ + const removeItem = (row: Classes) => { + const index = chooseClasses.value.indexOf(row); + chooseClasses.value = chooseClasses.value.filter((_d, i) => i !== index); + }; + + /* 添加全部 */ + const addAll = () => { + chooseClasses.value = [...classes.value]; + }; + + /* 移除所有 */ + const removeAll = () => { + chooseClasses.value = []; + }; +</script> + +<script lang="ts"> + export default { + name: 'ExampleChoose' + }; +</script> diff --git a/src/views-demo/example/document/components/file-sort.vue b/src/views-demo/example/document/components/file-sort.vue new file mode 100644 index 0000000..6560480 --- /dev/null +++ b/src/views-demo/example/document/components/file-sort.vue @@ -0,0 +1,340 @@ +<template> + <ele-modal + :width="1200" + :visible="visible" + title="卷内文件调整" + :body-style="{ padding: '16px 16px 0 16px' }" + @update:visible="updateVisible" + @cancel="close" + @ok="save" + > + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { lg: 8, md: 24, sm: 24, xs: 24 } : { span: 8 } + " + > + <!-- 表格 --> + <ele-pro-table + bordered + size="small" + :toolkit="[]" + height="360px" + :current="current" + :need-page="false" + row-key="piece_no" + sub-title="案卷列表" + :columns="columns1" + tools-theme="default" + :datasource="documents" + :scroll="{ x: 280 }" + selection-type="radio" + :row-selection="{ columnWidth: 38 }" + :tool-style="{ padding: '7px 14px' }" + class="demo-file-sort-table" + style="margin-bottom: 16px" + @update:current="updateCurrent" + /> + </a-col> + <a-col + v-bind=" + styleResponsive ? { lg: 8, md: 24, sm: 24, xs: 24 } : { span: 8 } + " + > + <!-- 表格 --> + <ele-pro-table + bordered + size="small" + :toolkit="[]" + height="360px" + :need-page="false" + :loading="loading" + sub-title="卷内列表" + :datasource="data1" + :columns="columns2" + row-key="archive_no" + tools-theme="default" + :scroll="{ x: 280 }" + v-model:selection="selection1" + :row-selection="{ columnWidth: 38 }" + class="demo-file-sort-table" + style="margin-bottom: 16px" + > + <template #toolkit> + <a-space> + <a-button + ghost + type="primary" + class="ele-btn-icon" + @click="moveUp" + > + <span><arrow-up-outlined />上移</span> + </a-button> + <a-button + ghost + type="primary" + class="ele-btn-icon" + @click="moveDown" + > + <span><arrow-down-outlined />下移</span> + </a-button> + <a-button + ghost + type="primary" + class="ele-btn-icon" + @click="moveOut" + > + <span>调出<arrow-right-outlined /></span> + </a-button> + </a-space> + </template> + </ele-pro-table> + </a-col> + <a-col + v-bind=" + styleResponsive ? { lg: 8, md: 24, sm: 24, xs: 24 } : { span: 8 } + " + > + <!-- 表格 --> + <ele-pro-table + bordered + size="small" + :toolkit="[]" + height="360px" + :need-page="false" + :loading="loading" + :datasource="data2" + :columns="columns2" + sub-title="未归档列表" + row-key="archive_no" + tools-theme="default" + :scroll="{ x: 280 }" + v-model:selection="selection2" + :row-selection="{ columnWidth: 38 }" + class="demo-file-sort-table" + style="margin-bottom: 16px" + > + <template #toolkit> + <a-button ghost type="primary" class="ele-btn-icon" @click="moveIn"> + <span><arrow-left-outlined /> 调入</span> + </a-button> + </template> + </ele-pro-table> + </a-col> + </a-row> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, unref, computed, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { + ArrowUpOutlined, + ArrowDownOutlined, + ArrowLeftOutlined, + ArrowRightOutlined + } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { getArchiveList } from '@/api/example/document'; + import type { Piece, Archive } from '@/api/example/document/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 案卷列表 + documents: Piece[]; + }>(); + + const emit = defineEmits<{ + (e: 'update:visible', value: boolean): void; + }>(); + + // 案卷表格列配置 + const columns1 = ref([ + { + title: '案卷题名', + dataIndex: 'title', + ellipsis: true + }, + { + title: '案卷档号', + dataIndex: 'piece_no', + ellipsis: true + } + ]); + + // 卷内表格列配置 + const columns2 = ref([ + { + title: '文件题名', + dataIndex: 'title', + ellipsis: true + }, + { + title: '文件档号', + dataIndex: 'archive_no', + ellipsis: true + } + ]); + + // 所选案卷下的全部文件列表 + const data = ref<Archive[]>([]); + + // 选中案卷 + const current = ref<Archive | null>(null); + + // 加载loading + const loading = ref(true); + + // 卷内列表选中数据 + const selection1 = ref<Archive[]>([]); + + // 未归档列表选中数据 + const selection2 = ref<Archive[]>([]); + + // 选中案卷的卷内文件 + const data1 = computed(() => + unref(current) + ? data.value.filter((d) => d.piece_no === unref(current)?.piece_no) + : [] + ); + + // 未归档的卷内文件 + const data2 = computed(() => data.value.filter((d) => !d.piece_no)); + + /* 上移 */ + const moveUp = () => { + if (!selection1.value.length) { + message.error('请选择一条数据'); + return; + } + if (selection1.value.length > 1) { + message.error('只能选择一条数据'); + return; + } + if (data1.value.indexOf(selection1.value[0]) === 0) { + return; + } + const index = data.value.indexOf(selection1.value[0]); + const old = data.value[index - 1]; + data.value[index - 1] = selection1.value[0]; + data.value[index] = old; + selection1.value = [data.value[index - 1]]; + }; + + /* 下移 */ + const moveDown = () => { + if (!selection1.value.length) { + message.error('请选择一条数据'); + return; + } + if (selection1.value.length > 1) { + message.error('只能选择一条数据'); + return; + } + if (data1.value.indexOf(selection1.value[0]) === data1.value.length - 1) { + return; + } + const index = data.value.indexOf(selection1.value[0]); + const old = data.value[index + 1]; + data.value[index + 1] = selection1.value[0]; + data.value[index] = old; + selection1.value = [data.value[index + 1]]; + }; + + /* 调出 */ + const moveOut = () => { + if (!selection1.value.length) { + message.error('请至少选择一条数据'); + return; + } + selection1.value.forEach((d) => { + d.piece_no = ''; + }); + selection1.value = []; + }; + + /* 调入 */ + const moveIn = () => { + if (!unref(current)) { + return; + } + if (!selection2.value.length) { + message.error('请至少选择一条数据'); + return; + } + selection2.value.forEach((d) => { + d.piece_no = unref(current)?.piece_no; + }); + selection2.value = []; + }; + + /* 保存 */ + const save = () => { + const result = data.value.map((d) => { + return { + archive_no: d.archive_no, + piece_no: d.piece_no + }; + }); + console.log(result); + message.success('调整成功'); + close(); + }; + + /* 关闭弹窗 */ + const close = () => { + data.value = []; + selection1.value = []; + selection2.value = []; + updateVisible(false); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + /* 更新current */ + const updateCurrent = (value: Archive) => { + current.value = value; + selection1.value = []; + }; + + /* 查询所选案卷的卷内文件 */ + const query = () => { + loading.value = true; + getArchiveList({ + piece_no_in: props.documents.map((d) => d.piece_no) + }) + .then((list) => { + loading.value = false; + data.value = list; + current.value = props.documents[0]; + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }; + + watch( + () => props.documents, + (documents) => { + if (documents.length) { + query(); + } + } + ); +</script> + +<style lang="less" scoped> + :deep(.demo-file-sort-table .ant-table-body) { + overflow: auto !important; + } +</style> diff --git a/src/views-demo/example/document/index.vue b/src/views-demo/example/document/index.vue new file mode 100644 index 0000000..08ae034 --- /dev/null +++ b/src/views-demo/example/document/index.vue @@ -0,0 +1,176 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false" :body-style="{ padding: '10px 20px' }"> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="piece_no" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 900 }" + :height="fixedHeight ? 'calc(100vh - 420px)' : void 0" + @done="onTableDone" + > + <template #toolbar> + <a-button type="primary" class="ele-btn-icon" @click="openFileSort"> + 卷内文件调整 + </a-button> + <span> 高度铺满<s></s><s></s></span> + <a-switch v-model:checked="fixedHeight" size="small" /> + </template> + <!-- 合计行 --> + <template #summary> + <a-table-summary fixed> + <a-table-summary-row> + <a-table-summary-cell /> + <a-table-summary-cell /> + <a-table-summary-cell>合计</a-table-summary-cell> + <a-table-summary-cell /> + <a-table-summary-cell /> + <a-table-summary-cell /> + <a-table-summary-cell /> + <a-table-summary-cell /> + <a-table-summary-cell /> + <a-table-summary-cell>{{ amountSummary }}</a-table-summary-cell> + </a-table-summary-row> + </a-table-summary> + </template> + </ele-pro-table> + </a-card> + <!-- 卷内文件调整弹窗 --> + <file-sort v-model:visible="showFileSort" :documents="fileSortChoose" /> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem, + EleProTableDone + } from 'ele-admin-pro/es/ele-pro-table/types'; + import FileSort from './components/file-sort.vue'; + import { getPieceList } from '@/api/example/document'; + import type { Piece } from '@/api/example/document/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 案卷数据 + const data = ref<Piece[]>([]); + + // 列表数据源 + const datasource: DatasourceFunction = ({ page, limit }) => { + return getPieceList({ page, limit }); + }; + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '案卷档号', + dataIndex: 'piece_no', + ellipsis: true + }, + { + title: '案卷题名', + dataIndex: 'title', + ellipsis: true + }, + { + title: '年度', + dataIndex: 'year', + width: 100, + ellipsis: true + }, + { + title: '保管期限', + dataIndex: 'retention', + width: 120, + ellipsis: true + }, + { + title: '密级', + dataIndex: 'secret', + width: 100, + ellipsis: true + }, + { + title: '档案类别', + dataIndex: 'type', + ellipsis: true + }, + { + title: '载体规格', + dataIndex: 'carrier', + width: 120, + ellipsis: true + }, + { + title: '件数', + dataIndex: 'amount', + ellipsis: true + } + ]); + + // 表格选中数据 + const selection = ref<Piece[]>([]); + + // 是否显示卷内文件调整弹窗 + const showFileSort = ref(false); + + // 选中的案卷 + const fileSortChoose = ref<Piece[]>([]); + + // 件数合计 + const amountSummary = ref(0); + + // 表格固定高度 + const fixedHeight = ref(false); + + /* 表格数据加载完成事件 */ + const onTableDone: EleProTableDone<Piece> = (res) => { + data.value = res.data; + amountSummary.value = res.data + .map((item) => Number(item.amount)) + .reduce((prev, curr) => { + const value = Number(curr); + if (!isNaN(value)) { + return prev + curr; + } else { + return prev; + } + }, 0); + }; + + /* 打开卷内文件调整弹窗 */ + const openFileSort = () => { + if (selection.value.length < 2) { + message.error('请至少选择两条数据'); + return; + } + // 实际项目用这一行 + /* fileSortChoose.value = selection.value.map((d) => { + return { ...d }; + }); */ + // 演示强制选前三个演示 + fileSortChoose.value = data.value.slice(0, 3); + showFileSort.value = true; + }; +</script> + +<script lang="ts"> + export default { + name: 'ExampleDocument' + }; +</script> diff --git a/src/views-demo/example/menu-badge/index.vue b/src/views-demo/example/menu-badge/index.vue new file mode 100644 index 0000000..6dd9dfd --- /dev/null +++ b/src/views-demo/example/menu-badge/index.vue @@ -0,0 +1,134 @@ +<template> + <div class="ele-body ele-body-card"> + <a-card title="修改菜单徽章数据" :bordered="false"> + <a-form + :label-col="styleResponsive ? { sm: 6, xs: 24 } : { flex: '80px' }" + :wrapper-col="styleResponsive ? { sm: 18, xs: 24 } : { flex: '1' }" + style="max-width: 360px" + > + <a-form-item label="菜单"> + <a-tree-select + :tree-data="treeData" + tree-default-expand-all + placeholder="请选择菜单" + v-model:value="path" + /> + </a-form-item> + <a-form-item label="徽章值"> + <a-input placeholder="请输入徽章值" v-model:value="badge" /> + </a-form-item> + <a-form-item label="徽章颜色"> + <ele-color-picker + size="large" + :show-alpha="true" + v-model:value="color" + /> + </a-form-item> + <a-form-item :wrapper-col="{ sm: { offset: 6 } }"> + <a-button type="primary" @click="setBadge">更新</a-button> + </a-form-item> + </a-form> + </a-card> + <a-card title="分组菜单" :bordered="false"> + <div> + <a-button type="primary" @click="toMenuGroup1"> + 一级菜单变为分组形式 + </a-button> + </div> + <div style="margin-top: 16px"> + <a-button type="primary" @click="toMenuGroup2"> + 二级菜单变为分组形式 + </a-button> + </div> + <div class="ele-text-secondary" style="margin-top: 6px"> + 二级菜单可查看列表页面/卡片列表的效果 + </div> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, computed } from 'vue'; + import { useUserStore } from '@/store/modules/user'; + import { message } from 'ant-design-vue/es'; + import { formatTreeData } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const userStore = useUserStore(); + const { menus } = storeToRefs(userStore); + + const treeData = computed(() => { + return formatTreeData(menus.value, (m) => { + return { + ...m, + value: m.path, + title: m.meta.title + }; + }); + }); + + const path = ref<string>(); + + const badge = ref<string>(); + + const color = ref<string>(); + + const setBadge = () => { + if (!path.value) { + message.error('请选择菜单'); + return; + } + userStore.setMenuBadge(path.value, badge.value, color.value); + }; + + // + const orgMenus = JSON.parse(JSON.stringify(menus.value)); + + /* 一级菜单变为分组形式 */ + const toMenuGroup1 = () => { + userStore.setMenus( + orgMenus.map((m: any) => { + return { + ...m, + meta: { + ...m.meta, + group: true + } + }; + }) + ); + }; + + /* 二级菜单变为分组形式 */ + const toMenuGroup2 = () => { + userStore.setMenus( + orgMenus.map((m: any) => { + return { + ...m, + children: m.children + ? m.children.map((c: any) => { + return { + ...c, + meta: { + ...c.meta, + group: true + } + }; + }) + : void 0 + }; + }) + ); + }; +</script> + +<script lang="ts"> + export default { + name: 'ExampleMenuBadge' + }; +</script> diff --git a/src/views-demo/example/table/components/default-sorter.vue b/src/views-demo/example/table/components/default-sorter.vue new file mode 100644 index 0000000..df2e975 --- /dev/null +++ b/src/views-demo/example/table/components/default-sorter.vue @@ -0,0 +1,90 @@ +<template> + <a-card :bordered="false" :body-style="{ padding: '10px 20px' }"> + <ele-pro-table + ref="tableRef" + size="small" + title="设置默认排序和筛选" + row-key="userId" + :columns="columns" + :datasource="datasource" + :scroll="{ x: 800 }" + /> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { toDateString } from 'ele-admin-pro/es'; + import { pageUsers } from '@/api/system/user'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '用户账号', + dataIndex: 'username', + sorter: true, + ellipsis: true, + defaultSortOrder: 'ascend' + }, + { + title: '用户名', + dataIndex: 'nickname', + sorter: true, + ellipsis: true + }, + { + title: '性别', + dataIndex: 'sexName', + width: 140, + align: 'center', + sorter: true, + filters: [ + { + text: '男', + value: '男' + }, + { + text: '女', + value: '女' + } + ], + filterMultiple: false, + defaultFilteredValue: ['男'] + }, + { + title: '手机号', + dataIndex: 'phone', + sorter: true, + ellipsis: true + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + ellipsis: true, + customRender: ({ text }) => toDateString(text), + width: 180 + } + ]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, orders, filters }) => { + return pageUsers({ ...orders, ...filters, page, limit }); + }; +</script> diff --git a/src/views-demo/example/table/components/lazy-tree-table.vue b/src/views-demo/example/table/components/lazy-tree-table.vue new file mode 100644 index 0000000..3fe4f35 --- /dev/null +++ b/src/views-demo/example/table/components/lazy-tree-table.vue @@ -0,0 +1,74 @@ +<template> + <a-card :bordered="false" :body-style="{ padding: '10px 20px' }"> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + size="small" + title="树形表格懒加载" + row-key="menuId" + :columns="columns" + :datasource="datasource" + :need-page="false" + :lazy-load="true" + :expand-icon-column-index="1" + :scroll="{ x: 600 }" + /> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { toDateString } from 'ele-admin-pro/es'; + import { listMenus } from '@/api/system/menu'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '菜单名称', + dataIndex: 'title', + ellipsis: true + }, + { + title: '路由地址', + dataIndex: 'path', + ellipsis: true + }, + { + title: '组件路径', + dataIndex: 'component', + ellipsis: true + }, + { + title: '排序', + dataIndex: 'sortNumber', + width: 60 + }, + { + title: '创建时间', + dataIndex: 'createTime', + ellipsis: true, + customRender: ({ text }) => toDateString(text) + } + ]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ where, parent }) => { + return listMenus({ ...where, parentId: parent?.menuId || 0 }); + }; +</script> diff --git a/src/views-demo/example/table/components/merge-cell.vue b/src/views-demo/example/table/components/merge-cell.vue new file mode 100644 index 0000000..f218abf --- /dev/null +++ b/src/views-demo/example/table/components/merge-cell.vue @@ -0,0 +1,68 @@ +<template> + <a-card :bordered="false" :body-style="{ padding: '10px 20px' }"> + <ele-pro-table + ref="tableRef" + size="small" + title="合并单元格" + row-key="id" + :columns="columns" + :datasource="datasource" + :scroll="{ x: 800 }" + :bordered="true" + > + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'userName'"> + {{ record.userName }} + </template> + </template> + </ele-pro-table> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { pageUserScores } from '@/api/example/table'; + import type { UserScore } from '@/api/example/table/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + hideInSetting: true, + fixed: 'left', + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '姓名', + key: 'userName', + customCell: (record) => { + return { + rowSpan: (record as UserScore).userNameRowSpan + }; + } + }, + { + title: '课程', + dataIndex: 'courseName' + }, + { + title: '得分', + dataIndex: 'score' + } + ]); + + // 表格数据源 + const datasource: DatasourceFunction = () => { + return pageUserScores(); + }; +</script> diff --git a/src/views-demo/example/table/components/multiple-sorter.vue b/src/views-demo/example/table/components/multiple-sorter.vue new file mode 100644 index 0000000..fa83c69 --- /dev/null +++ b/src/views-demo/example/table/components/multiple-sorter.vue @@ -0,0 +1,87 @@ +<template> + <a-card :bordered="false" :body-style="{ padding: '10px 20px' }"> + <ele-pro-table + ref="tableRef" + size="small" + title="多列排序" + row-key="userId" + :columns="columns" + :datasource="datasource" + :scroll="{ x: 800 }" + /> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { toDateString } from 'ele-admin-pro/es'; + import { pageUsers } from '@/api/system/user'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '用户账号', + dataIndex: 'username', + sorter: { + multiple: 1 + }, + ellipsis: true + }, + { + title: '用户名', + dataIndex: 'nickname', + sorter: { + multiple: 1 + }, + ellipsis: true + }, + { + title: '性别', + dataIndex: 'sexName', + width: 140, + align: 'center', + sorter: { + multiple: 2 + } + }, + { + title: '手机号', + dataIndex: 'phone', + sorter: { + multiple: 1 + }, + ellipsis: true + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: { + multiple: 1 + }, + ellipsis: true, + customRender: ({ text }) => toDateString(text), + width: 180 + } + ]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, orders, filters }) => { + return pageUsers({ ...orders, ...filters, page, limit }); + }; +</script> diff --git a/src/views-demo/example/table/components/reset-sorter.vue b/src/views-demo/example/table/components/reset-sorter.vue new file mode 100644 index 0000000..453b0f0 --- /dev/null +++ b/src/views-demo/example/table/components/reset-sorter.vue @@ -0,0 +1,171 @@ +<template> + <a-card :bordered="false" :body-style="{ padding: '10px 20px' }"> + <ele-pro-table + ref="tableRef" + size="small" + title="可控的排序和筛选" + row-key="userId" + :columns="columns" + :datasource="datasource" + :scroll="{ x: 800 }" + @change="onChange" + > + <template #toolkit> + <a-space size="small" style="flex-wrap: wrap"> + <a-button type="primary" class="ele-btn-icon" @click="setSorter"> + 设置用户名排序 + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="setFilter"> + 设置性别筛选 + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="resetAll"> + 重置排序和筛选 + </a-button> + <a-divider type="vertical" /> + </a-space> + </template> + </ele-pro-table> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, computed, unref } from 'vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem, + FilterType, + SorterType + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { toDateString } from 'ele-admin-pro/es'; + import { pageUsers } from '@/api/system/user'; + import type { TablePaginationConfig } from 'ant-design-vue/es/table/interface'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格筛选值 + const filteredInfo = ref<FilterType | null>(null); + + // 表格排序值 + const sortedInfo = ref<SorterType | null>(null); + + // 表格列配置 + const columns = computed<ColumnItem[]>(() => { + const sorted = + (Array.isArray(sortedInfo.value) + ? sortedInfo.value[0] + : sortedInfo.value) ?? {}; + const filtered = unref(filteredInfo) ?? {}; + const cols: ColumnItem[] = [ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '用户账号', + dataIndex: 'username', + sorter: true, + ellipsis: true, + sortOrder: sorted.field === 'username' ? sorted.order : null + }, + { + title: '用户名', + dataIndex: 'nickname', + sorter: true, + ellipsis: true, + sortOrder: sorted.field === 'nickname' ? sorted.order : null + }, + { + title: '性别', + dataIndex: 'sexName', + width: 140, + align: 'center', + sorter: true, + filters: [ + { + text: '男', + value: '男' + }, + { + text: '女', + value: '女' + } + ], + filterMultiple: false, + sortOrder: sorted.field === 'sexName' ? sorted.order : null, + filteredValue: filtered.sexName || null + }, + { + title: '手机号', + dataIndex: 'phone', + sorter: true, + ellipsis: true, + sortOrder: sorted.field === 'phone' ? sorted.order : null + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + ellipsis: true, + customRender: ({ text }) => toDateString(text), + width: 180, + sortOrder: sorted.field === 'createTime' ? sorted.order : null + } + ]; + return cols; + }); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, orders, filters }) => { + return pageUsers({ ...orders, ...filters, page, limit }); + }; + + // 表格分页、排序、筛选改变事件 + const onChange = ( + _pagination: TablePaginationConfig, + filters: FilterType, + sorter: SorterType + ) => { + filteredInfo.value = filters; + sortedInfo.value = sorter; + }; + + // 重置排序和筛选 + const resetAll = () => { + filteredInfo.value = {}; + sortedInfo.value = {}; + tableRef?.value?.reload({ + page: 1, + sorter: sortedInfo.value, + filters: filteredInfo.value + }); + }; + + // 设置排序 + const setSorter = () => { + sortedInfo.value = { + order: 'descend', + field: 'nickname' + }; + tableRef?.value?.reload({ + page: 1, + sorter: sortedInfo.value + }); + }; + + // 设置筛选 + const setFilter = () => { + filteredInfo.value = { + sexName: ['女'] + }; + tableRef?.value?.reload({ + page: 1, + filters: filteredInfo.value + }); + }; +</script> diff --git a/src/views-demo/example/table/index.vue b/src/views-demo/example/table/index.vue new file mode 100644 index 0000000..5596311 --- /dev/null +++ b/src/views-demo/example/table/index.vue @@ -0,0 +1,23 @@ +<template> + <div class="ele-body ele-body-card"> + <lazy-tree-table /> + <default-sorter /> + <reset-sorter /> + <multiple-sorter /> + <merge-cell /> + </div> +</template> + +<script lang="ts" setup> + import LazyTreeTable from './components/lazy-tree-table.vue'; + import DefaultSorter from './components/default-sorter.vue'; + import ResetSorter from './components/reset-sorter.vue'; + import MultipleSorter from './components/multiple-sorter.vue'; + import MergeCell from './components/merge-cell.vue'; +</script> + +<script lang="ts"> + export default { + name: 'ExampleTable' + }; +</script> diff --git a/src/views-demo/exception/403/index.vue b/src/views-demo/exception/403/index.vue new file mode 100644 index 0000000..6b2bc27 --- /dev/null +++ b/src/views-demo/exception/403/index.vue @@ -0,0 +1,17 @@ +<template> + <div style="padding-top: 80px"> + <a-result status="403" title="403" sub-title="抱歉, 你无权访问该页面."> + <template #extra> + <router-link to="/"> + <a-button type="primary">返回首页</a-button> + </router-link> + </template> + </a-result> + </div> +</template> + +<script lang="ts"> + export default { + name: 'Exception403' + }; +</script> diff --git a/src/views-demo/exception/404/index.vue b/src/views-demo/exception/404/index.vue new file mode 100644 index 0000000..1c2b453 --- /dev/null +++ b/src/views-demo/exception/404/index.vue @@ -0,0 +1,17 @@ +<template> + <div style="padding-top: 80px"> + <a-result status="404" title="404" sub-title="抱歉, 你访问的页面不存在."> + <template #extra> + <router-link to="/"> + <a-button type="primary">返回首页</a-button> + </router-link> + </template> + </a-result> + </div> +</template> + +<script lang="ts"> + export default { + name: 'Exception404' + }; +</script> diff --git a/src/views-demo/exception/500/index.vue b/src/views-demo/exception/500/index.vue new file mode 100644 index 0000000..ff7c853 --- /dev/null +++ b/src/views-demo/exception/500/index.vue @@ -0,0 +1,17 @@ +<template> + <div style="padding-top: 80px"> + <a-result status="500" title="500" sub-title="抱歉, 服务器出错了."> + <template #extra> + <router-link to="/"> + <a-button type="primary">返回首页</a-button> + </router-link> + </template> + </a-result> + </div> +</template> + +<script lang="ts"> + export default { + name: 'Exception500' + }; +</script> diff --git a/src/views-demo/extension/bar-code/index.vue b/src/views-demo/extension/bar-code/index.vue new file mode 100644 index 0000000..a1af1a1 --- /dev/null +++ b/src/views-demo/extension/bar-code/index.vue @@ -0,0 +1,144 @@ +<template> + <div class="ele-body"> + <a-card title="条形码" :bordered="false"> + <div ref="printRef" class="demo-barcode-images ele-bg-white"> + <ele-bar-code :value="text" :tag="tag" :options="options" /> + </div> + <a-form + style="max-width: 340px" + :label-col="{ flex: '88px' }" + :wrapper-col="{ flex: '1' }" + > + <a-form-item label="条码类型" style="flex-wrap: nowrap"> + <a-radio-group :value="options.format" @update:value="updateFormat"> + <a-radio value="CODE128">CODE128</a-radio> + <a-radio value="EAN13">EAN13</a-radio> + </a-radio-group> + </a-form-item> + <a-form-item label="渲染方式" style="flex-wrap: nowrap"> + <a-radio-group v-model:value="tag"> + <a-radio value="svg">svg</a-radio> + <a-radio value="img">img</a-radio> + <a-radio value="canvas">canvas</a-radio> + </a-radio-group> + </a-form-item> + <a-form-item label="条码文本" style="flex-wrap: nowrap"> + <a-select v-if="options.format === 'EAN13'" v-model:value="text"> + <a-select-option value="1234567890128"> + 1234567890128 + </a-select-option> + <a-select-option value="6971872201359"> + 6971872201359 + </a-select-option> + <a-select-option value="6954531770199"> + 6954531770199 + </a-select-option> + <a-select-option value="6923644240318"> + 6923644240318 + </a-select-option> + </a-select> + <a-input v-else v-model:value="text" :maxlength="20" /> + </a-form-item> + <a-form-item label="高度" style="flex-wrap: nowrap"> + <a-slider + v-model:value="options.height" + :min="40" + :max="160" + :step="10" + /> + </a-form-item> + <a-form-item label="宽度" style="flex-wrap: nowrap"> + <a-slider v-model:value="options.width" :min="1" :max="6" /> + </a-form-item> + <a-form-item label="间距" style="flex-wrap: nowrap"> + <a-slider v-model:value="options.margin" :min="0" :max="40" /> + </a-form-item> + <a-form-item label="显示文本" style="flex-wrap: nowrap"> + <a-switch v-model:checked="options.displayValue" size="small" /> + </a-form-item> + <a-form-item + v-if="options.displayValue" + label="文本大小" + style="flex-wrap: nowrap" + > + <a-slider + v-model:value="options.fontSize" + :min="12" + :max="36" + :step="2" + /> + </a-form-item> + <a-form-item + v-if="options.displayValue && options.format === 'CODE128'" + label="文本位置" + style="flex-wrap: nowrap" + > + <a-radio-group v-model:value="options.textPosition"> + <a-radio value="bottom">bottom</a-radio> + <a-radio value="top">top</a-radio> + </a-radio-group> + </a-form-item> + <a-form-item style="flex-wrap: nowrap"> + <div style="padding-left: 88px"> + <a-button type="primary" @click="print">打印</a-button> + </div> + </a-form-item> + </a-form> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive, nextTick } from 'vue'; + import { printElement } from 'ele-admin-pro/es'; + import type { Options } from 'jsbarcode'; + + const printRef = ref<HTMLElement | null>(null); + + const text = ref('1234567890'); + + const tag = ref('svg'); + + const options = reactive<Options>({ + height: 60, + width: 2, + margin: 2, + displayValue: true, + textPosition: 'bottom', + fontSize: 14, + format: 'CODE128' + }); + + const updateFormat = (value: string) => { + if (value === 'EAN13') { + text.value = '1234567890128'; + nextTick(() => { + options.format = value; + }); + } else { + options.format = value; + } + }; + + const print = () => { + printElement(printRef.value as HTMLElement); + }; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionBarCode' + }; +</script> + +<style lang="less" scoped> + .demo-barcode-images { + padding-bottom: 16px; + margin-bottom: 4px; + position: sticky; + top: 0; + overflow: auto; + z-index: 1; + line-height: 0; + } +</style> diff --git a/src/views-demo/extension/color-picker/index.vue b/src/views-demo/extension/color-picker/index.vue new file mode 100644 index 0000000..5c20ea1 --- /dev/null +++ b/src/views-demo/extension/color-picker/index.vue @@ -0,0 +1,62 @@ +<template> + <div class="ele-body ele-body-card"> + <a-card title="颜色选择器" :bordered="false"> + <a-space> + <ele-color-picker + size="large" + :show-alpha="true" + v-model:value="color" + :predefine="predefineColors" + /> + <ele-color-picker + :show-alpha="true" + v-model:value="color2" + :predefine="predefineColors" + /> + <ele-color-picker + size="small" + :show-alpha="true" + v-model:value="color3" + :predefine="predefineColors" + /> + </a-space> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + + // 选中颜色 + const color = ref('rgba(255, 69, 0, 0.68)'); + + // 选中颜色 + const color2 = ref('rgba(255, 69, 0, 0.68)'); + + // 选中颜色 + const color3 = ref('rgba(255, 69, 0, 0.68)'); + + // 预设颜色 + const predefineColors = ref([ + '#ff4500', + '#ff8c00', + '#ffd700', + '#90ee90', + '#00ced1', + '#1e90ff', + '#c71585', + 'rgba(255, 69, 0, 0.68)', + 'rgb(255, 120, 0)', + 'hsv(51, 100, 98)', + 'hsva(120, 40, 94, 0.5)', + 'hsl(181, 100%, 37%)', + 'hsla(209, 100%, 56%, 0.73)', + '#c7158577' + ]); +</script> + +<script lang="ts"> + export default { + name: 'ExtensionColorPicker' + }; +</script> diff --git a/src/views-demo/extension/count-up/index.vue b/src/views-demo/extension/count-up/index.vue new file mode 100644 index 0000000..8313688 --- /dev/null +++ b/src/views-demo/extension/count-up/index.vue @@ -0,0 +1,63 @@ +<template> + <div class="ele-body ele-body-card"> + <a-card title="滚动数字" :bordered="false"> + <h1 style="padding-left: 10px; margin-bottom: 15px"> + <ele-count-up + :delay="0" + :end-val="demoNum" + :options="option" + @ready="onReady" + /> + </h1> + <a-space> + <a-button class="ele-btn-icon" @click="restart">重新开始</a-button> + <a-button class="ele-btn-icon" @click="update">更新数字</a-button> + </a-space> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import type { CountUp } from 'countup.js'; + + // 值 + const demoNum = ref(6317); + + // 配置 + const option = reactive({ + useEasing: true, + useGrouping: true, + separator: ',', + decimal: '.', + prefix: '', + suffix: '' + }); + + let instance: CountUp; + + /* 渲染完成 */ + const onReady = (ins: CountUp) => { + instance = ins; + }; + + /* 重新开始 */ + const restart = () => { + if (!instance) { + return; + } + instance.reset(); + instance.start(); + }; + + /* 更新 */ + const update = () => { + demoNum.value += 100 + Math.round(Math.random() * 300); + }; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionCountUp' + }; +</script> diff --git a/src/views-demo/extension/dashboard/index.vue b/src/views-demo/extension/dashboard/index.vue new file mode 100644 index 0000000..f20201a --- /dev/null +++ b/src/views-demo/extension/dashboard/index.vue @@ -0,0 +1,41 @@ +<template> + <div class="ele-body ele-body-card"> + <a-card title="基本用法" :bordered="false"> + <ele-dashboard type="success" style="margin-right: 18px"> + <div style="line-height: 1"> + <span style="font-size: 48px">100</span> + <span style="font-size: 12px; margin-left: 4px">分</span> + </div> + <div style="margin-top: 4px">安全</div> + </ele-dashboard> + <ele-dashboard type="warning" style="margin-right: 18px"> + <div style="line-height: 1"> + <span style="font-size: 48px">70</span> + <span style="font-size: 12px; margin-left: 4px">分</span> + </div> + <div style="margin-top: 4px">待优化</div> + </ele-dashboard> + <ele-dashboard type="danger"> + <div style="line-height: 1"> + <span style="font-size: 48px">40</span> + <span style="font-size: 12px; margin-left: 4px">分</span> + </div> + <div style="margin-top: 4px">高风险</div> + </ele-dashboard> + </a-card> + <a-card title="自定义颜色和尺寸" :bordered="false"> + <ele-dashboard color="#722ED1" style="margin-right: 18px"> + <div style="font-size: 48px">100</div> + </ele-dashboard> + <ele-dashboard size="116px" space="12px"> + <div style="font-size: 38px">100</div> + </ele-dashboard> + </a-card> + </div> +</template> + +<script lang="ts"> + export default { + name: 'ExtensionDashboard' + }; +</script> diff --git a/src/views-demo/extension/dialog/components/demo-modal.vue b/src/views-demo/extension/dialog/components/demo-modal.vue new file mode 100644 index 0000000..ec02398 --- /dev/null +++ b/src/views-demo/extension/dialog/components/demo-modal.vue @@ -0,0 +1,245 @@ +<template> + <a-card title="可拖拽、拉伸、全屏弹窗" :bordered="false"> + <a-form + style="max-width: 360px" + :label-col=" + styleResponsive ? { md: 10, sm: 8, xs: 24 } : { flex: '140px' } + " + :wrapper-col=" + styleResponsive ? { md: 14, sm: 16, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="是否可拖出边界"> + <a-select v-model:value="moveOut"> + <a-select-option :value="0">不可拖出边界</a-select-option> + <a-select-option :value="1">可以拖出边界</a-select-option> + <a-select-option :value="2">只可右下方向拖出边界</a-select-option> + </a-select> + </a-form-item> + <a-form-item label="是否可拉伸大小"> + <a-select v-model:value="resizable"> + <a-select-option value="false">不可拉伸大小</a-select-option> + <a-select-option value="true">可以拉伸大小</a-select-option> + <a-select-option value="horizontal">只可横向拉伸</a-select-option> + <a-select-option value="vertical">只可纵向拉伸</a-select-option> + </a-select> + </a-form-item> + <a-form-item label="最大化切换按钮"> + <a-switch v-model:checked="maxable" size="small" /> + </a-form-item> + <!-- <a-form-item label="是否垂直居中"> + <a-switch v-model:checked="centered" size="small" /> + </a-form-item> --> + <a-form-item label="关闭后重置位置"> + <a-switch v-model:checked="resetOnClose" size="small" /> + </a-form-item> + <a-form-item label="限制在主体区域"> + <a-switch v-model:checked="inner" size="small" /> + </a-form-item> + <a-form-item label="默认位置"> + <a-select allow-clear v-model:value="position" placeholder="请选择"> + <a-select-option value="top">顶部</a-select-option> + <a-select-option value="bottom">底部</a-select-option> + <a-select-option value="left">左边</a-select-option> + <a-select-option value="right">右边</a-select-option> + <a-select-option value="leftTop">左上角</a-select-option> + <a-select-option value="leftBottom">左下角</a-select-option> + <a-select-option value="rightTop">右上角</a-select-option> + <a-select-option value="rightBottom">右下角</a-select-option> + <a-select-option value="center">正中间</a-select-option> + </a-select> + </a-form-item> + <a-form-item + :wrapper-col=" + styleResponsive + ? { md: { offset: 10 }, sm: { offset: 8 } } + : { offset: 9 } + " + > + <a-button type="primary" class="ele-btn-icon" @click="openDialog"> + 打开可拖拽弹窗 + </a-button> + </a-form-item> + </a-form> + </a-card> + <ele-modal + :width="400" + title="拖拽弹窗" + v-model:visible="visible" + :move-out="moveOut > 0" + :move-out-positive="moveOut === 2" + :resizable="modalResizable" + :maxable="maxable" + :inner="inner" + :centered="centered" + :reset-on-close="resetOnClose" + :position="position" + :body-style="{ paddingBottom: '16px' }" + @cancel="cancel" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="{ flex: '70px' }" + :wrapper-col="{ flex: '1' }" + > + <a-form-item label="用户名" name="nickname" style="flex-wrap: nowrap"> + <a-input + allow-clear + placeholder="请输入用户名" + v-model:value="form.nickname" + /> + </a-form-item> + <a-form-item label="性别" name="sex"> + <a-select allow-clear placeholder="请选择性别" v-model:value="form.sex"> + <a-select-option value="男">男</a-select-option> + <a-select-option value="女">女</a-select-option> + </a-select> + </a-form-item> + <a-form-item label="手机号" name="phone" style="flex-wrap: nowrap"> + <a-input + allow-clear + placeholder="请输入手机号" + v-model:value="form.phone" + /> + </a-form-item> + <a-form-item label="邮箱" name="email" style="flex-wrap: nowrap"> + <a-input + allow-clear + placeholder="请输入邮箱" + v-model:value="form.email" + /> + </a-form-item> + <a-form-item label="个人简介" style="flex-wrap: nowrap"> + <a-textarea + :rows="4" + placeholder="请输入个人简介" + v-model:value="form.introduction" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, computed } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import type { PositionStringType } from 'ele-admin-pro/es/ele-modal/types'; + import useFormData from '@/utils/use-form-data'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // 弹窗是否打开 + const visible = ref(false); + + // 是否允许拖出边界 + const moveOut = ref(0); + + // 是否可拉伸 + const resizable = ref<'false' | 'true' | 'horizontal' | 'vertical'>('false'); + + // 是否显示最大化切换按钮 + const maxable = ref(true); + + // 关闭后重置位置 + const resetOnClose = ref(true); + + // 限制在主体区域 + const inner = ref(false); + + // 垂直居中 + const centered = ref(false); + + // 默认位置 + const position = ref<PositionStringType>(); + + // + const formRef = ref<FormInstance | null>(null); + + // + const modalResizable = computed(() => { + return resizable.value === 'true' + ? true + : resizable.value === 'false' + ? false + : resizable.value; + }); + + // 表单数据 + const { form, resetFields } = useFormData({ + nickname: '', + sex: undefined, + phone: '', + email: '', + introduction: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + nickname: [ + { + required: true, + message: '请输入用户名', + type: 'string', + trigger: 'blur' + } + ], + sex: [ + { + required: true, + message: '请选择性别', + type: 'string', + trigger: 'blur' + } + ], + phone: [ + { + required: true, + message: '请输入手机号', + type: 'string', + trigger: 'blur' + } + ], + email: [ + { + required: true, + message: '请输入邮箱', + type: 'string', + trigger: 'blur' + } + ] + }); + + /* 打开弹窗 */ + const openDialog = () => { + if (!visible.value) { + visible.value = true; + } + }; + + /* 弹窗关闭回调 */ + const cancel = () => { + resetFields(); + formRef.value?.clearValidate(); + }; + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + message.success('保存成功'); + }) + .catch(() => {}); + }; +</script> diff --git a/src/views-demo/extension/dialog/components/multiple-modal.vue b/src/views-demo/extension/dialog/components/multiple-modal.vue new file mode 100644 index 0000000..37c5a46 --- /dev/null +++ b/src/views-demo/extension/dialog/components/multiple-modal.vue @@ -0,0 +1,78 @@ +<template> + <a-card title="同时打开多个弹窗" :bordered="false"> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openDialog1"> + 打开弹窗1 + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="openDialog2"> + 打开弹窗2 + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="openDialog3"> + 打开弹窗3 + </a-button> + </a-space> + <p style="margin-top: 20px">同时打开多个弹窗时点击会自动置顶</p> + </a-card> + <ele-modal + :width="400" + title="弹窗1" + v-model:visible="visible1" + :resizable="true" + :maxable="true" + :multiple="true" + :destroy-on-close="false" + :move-out="true" + :move-out-positive="true" + position="center" + > + <div style="padding: 20px 0">弹窗1</div> + </ele-modal> + <ele-modal + :width="400" + title="弹窗2" + v-model:visible="visible2" + :resizable="true" + :maxable="true" + :multiple="true" + :destroy-on-close="false" + :move-out="true" + :move-out-positive="true" + position="rightTop" + > + <div style="padding: 20px 0">弹窗2</div> + </ele-modal> + <ele-modal + :width="400" + title="弹窗3" + v-model:visible="visible3" + :resizable="true" + :maxable="true" + :multiple="true" + :destroy-on-close="false" + :move-out="true" + :move-out-positive="true" + position="rightBottom" + > + <div style="padding: 20px 0">弹窗3</div> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + + // 弹窗是否打开 + const visible1 = ref(false); + const visible2 = ref(false); + const visible3 = ref(false); + + /* 打开弹窗 */ + const openDialog1 = () => { + visible1.value = true; + }; + const openDialog2 = () => { + visible2.value = true; + }; + const openDialog3 = () => { + visible3.value = true; + }; +</script> diff --git a/src/views-demo/extension/dialog/index.vue b/src/views-demo/extension/dialog/index.vue new file mode 100644 index 0000000..c21b178 --- /dev/null +++ b/src/views-demo/extension/dialog/index.vue @@ -0,0 +1,17 @@ +<template> + <div class="ele-body ele-body-card"> + <demo-modal /> + <multiple-modal /> + </div> +</template> + +<script lang="ts" setup> + import DemoModal from './components/demo-modal.vue'; + import MultipleModal from './components/multiple-modal.vue'; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionDialog' + }; +</script> diff --git a/src/views-demo/extension/dragsort/components/demo-grid.vue b/src/views-demo/extension/dragsort/components/demo-grid.vue new file mode 100644 index 0000000..e0d3f9e --- /dev/null +++ b/src/views-demo/extension/dragsort/components/demo-grid.vue @@ -0,0 +1,154 @@ +<template> + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { lg: 8, md: 24, sm: 24, xs: 24 } : { span: 8 }" + > + <a-card title="宫格拖拽排序" :bordered="false"> + <div class="demo-drag-grid"> + <vue-draggable + v-model="grid" + item-key="id" + :animation="300" + :set-data="() => void 0" + > + <template #item="{ element }"> + <div class="demo-drag-grid-item">{{ element.name }}</div> + </template> + </vue-draggable> + </div> + </a-card> + </a-col> + <a-col + v-bind=" + styleResponsive ? { lg: 16, md: 24, sm: 24, xs: 24 } : { span: 16 } + " + > + <a-card title="宫格相互拖拽" :bordered="false"> + <a-row :gutter="16"> + <a-col :span="12"> + <div class="demo-drag-grid"> + <vue-draggable + v-model="grid1" + item-key="id" + :animation="300" + group="demoDragGrid" + :set-data="() => void 0" + > + <template #item="{ element }"> + <div class="demo-drag-grid-item">{{ element.name }}</div> + </template> + </vue-draggable> + </div> + </a-col> + <a-col :span="12"> + <div class="demo-drag-grid"> + <vue-draggable + v-model="grid2" + item-key="id" + :animation="300" + group="demoDragGrid" + :set-data="() => void 0" + > + <template #item="{ element }"> + <div class="demo-drag-grid-item">{{ element.name }}</div> + </template> + </vue-draggable> + </div> + </a-col> + </a-row> + </a-card> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import VueDraggable from 'vuedraggable'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const grid = ref([ + { id: 1, name: '001' }, + { id: 2, name: '002' }, + { id: 3, name: '003' }, + { id: 4, name: '004' }, + { id: 5, name: '005' }, + { id: 6, name: '006' } + ]); + + const grid1 = ref([ + { id: 1, name: '001' }, + { id: 2, name: '002' }, + { id: 3, name: '003' }, + { id: 4, name: '004' }, + { id: 5, name: '005' }, + { id: 6, name: '006' } + ]); + + const grid2 = ref([ + { id: 7, name: '007' }, + { id: 8, name: '008' }, + { id: 9, name: '009' }, + { id: 10, name: '010' }, + { id: 11, name: '011' }, + { id: 12, name: '012' } + ]); +</script> + +<style lang="less" scoped> + @import 'ant-design-vue/es/style/themes/default.less'; + + .demo-drag-grid { + position: relative; + + & > div { + border: 1px solid @border-color-split; + border-right: none; + border-bottom: none; + display: grid; + grid-template-columns: repeat(3, 33.33%); + min-height: 201px; + } + + &:before, + &:after { + content: ''; + position: absolute; + background: @border-color-split; + bottom: 0; + right: 0; + } + + &:before { + width: 1px; + top: 0; + } + + &:after { + height: 1px; + left: 0; + } + } + + .demo-drag-grid-item { + cursor: move; + border: 1px solid @border-color-split; + border-top: none; + border-left: none; + height: 100px; + line-height: 100px; + white-space: nowrap; + word-break: break-all; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; + + &.sortable-chosen { + background: hsla(0, 0%, 60%, 0.1); + } + } +</style> diff --git a/src/views-demo/extension/dragsort/components/demo-list.vue b/src/views-demo/extension/dragsort/components/demo-list.vue new file mode 100644 index 0000000..4475a12 --- /dev/null +++ b/src/views-demo/extension/dragsort/components/demo-list.vue @@ -0,0 +1,135 @@ +<template> + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { lg: 8, md: 24, sm: 24, xs: 24 } : { span: 8 }" + > + <a-card title="列表拖拽排序" :bordered="false"> + <div class="demo-drag-list"> + <vue-draggable + v-model="list" + item-key="id" + :animation="300" + handle=".sort-handle" + :set-data="() => void 0" + > + <template #item="{ element }"> + <div class="demo-drag-list-item ele-cell"> + <drag-outlined class="sort-handle ele-text-secondary" /> + <div class="ele-cell-content">{{ element.name }}</div> + </div> + </template> + </vue-draggable> + </div> + </a-card> + </a-col> + <a-col + v-bind=" + styleResponsive ? { lg: 16, md: 24, sm: 24, xs: 24 } : { span: 16 } + " + > + <a-card title="列表相互拖拽" :bordered="false"> + <a-row :gutter="16"> + <a-col :span="12"> + <div class="demo-drag-list"> + <vue-draggable + v-model="list1" + item-key="id" + :animation="300" + handle=".sort-handle" + group="demoDragList" + :set-data="() => void 0" + > + <template #item="{ element }"> + <div class="demo-drag-list-item ele-cell"> + <drag-outlined class="sort-handle ele-text-secondary" /> + <div class="ele-cell-content">{{ element.name }}</div> + </div> + </template> + </vue-draggable> + </div> + </a-col> + <a-col :span="12"> + <div class="demo-drag-list"> + <vue-draggable + v-model="list2" + item-key="id" + :animation="300" + handle=".sort-handle" + group="demoDragList" + :set-data="() => void 0" + > + <template #item="{ element }"> + <div class="demo-drag-list-item ele-cell"> + <drag-outlined class="sort-handle ele-text-secondary" /> + <div class="ele-cell-content">{{ element.name }}</div> + </div> + </template> + </vue-draggable> + </div> + </a-col> + </a-row> + </a-card> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { DragOutlined } from '@ant-design/icons-vue'; + import VueDraggable from 'vuedraggable'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const list = ref([ + { id: 1, name: '项目0000001' }, + { id: 2, name: '项目0000002' }, + { id: 3, name: '项目0000003' }, + { id: 4, name: '项目0000004' }, + { id: 5, name: '项目0000005' } + ]); + + const list1 = ref([ + { id: 1, name: '项目0000001' }, + { id: 2, name: '项目0000002' }, + { id: 3, name: '项目0000003' }, + { id: 4, name: '项目0000004' }, + { id: 5, name: '项目0000005' } + ]); + + const list2 = ref([ + { id: 6, name: '项目0000006' }, + { id: 7, name: '项目0000007' }, + { id: 8, name: '项目0000008' }, + { id: 9, name: '项目0000009' }, + { id: 10, name: '项目0000010' } + ]); +</script> + +<style lang="less" scoped> + .demo-drag-list > div { + border: 1px solid hsla(0, 0%, 60%, 0.2); + min-height: 81px; + } + + .demo-drag-list-item { + line-height: 1; + padding: 12px 16px; + + & + .demo-drag-list-item { + border-top: 1px solid hsla(0, 0%, 60%, 0.2); + } + + &.sortable-chosen { + background: hsla(0, 0%, 60%, 0.1); + } + + .sort-handle { + cursor: move; + font-size: 16px; + } + } +</style> diff --git a/src/views-demo/extension/dragsort/components/demo-table.vue b/src/views-demo/extension/dragsort/components/demo-table.vue new file mode 100644 index 0000000..86aeecf --- /dev/null +++ b/src/views-demo/extension/dragsort/components/demo-table.vue @@ -0,0 +1,136 @@ +<template> + <a-card title="表格拖拽排序" :bordered="false"> + <template #extra> + <a @click="viewData">查看数据</a> + </template> + <a-row :gutter="16"> + <a-col + v-for="(item, index) in taskList" + :key="index" + v-bind=" + styleResponsive ? { lg: 8, md: 24, sm: 24, xs: 24 } : { span: 8 } + " + > + <table class="ele-table ele-table-border ele-table-medium"> + <colgroup> + <col width="40" /> + <col /> + <col width="80" /> + </colgroup> + <thead> + <tr> + <th></th> + <th>任务名称</th> + <th style="text-align: center">状态</th> + </tr> + </thead> + <vue-draggable + tag="tbody" + item-key="id" + :animation="300" + :modelValue="item" + group="demoDragTable" + handle=".demo-table-drag-handle" + :set-data="() => void 0" + @update:modelValue="(value) => updateModelValue(value, index)" + > + <template #item="{ element }"> + <tr> + <td style="text-align: center"> + <drag-outlined + class="demo-table-drag-handle ele-text-secondary" + style="cursor: move" + /> + </td> + <td>{{ element.taskName }}</td> + <td style="text-align: center"> + <span + :class=" + ['ele-text-warning', 'ele-text-success', 'ele-text-info'][ + element.status + ] + " + > + {{ ['未开始', '进行中', '已完成'][element.status] }} + </span> + </td> + </tr> + </template> + <template #footer v-if="!item.length"> + <tr style="background: none"> + <td colspan="3"> + <div class="ele-text-secondary ele-text-center">暂无数据</div> + </td> + </tr> + </template> + </vue-draggable> + </table> + </a-col> + </a-row> + </a-card> + <ele-modal v-model:visible="visible" title="拖拽后数据" :footer="null"> + <div style="max-height: 240px; overflow: auto"> + <pre>{{ result }}</pre> + </div> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { DragOutlined } from '@ant-design/icons-vue'; + import VueDraggable from 'vuedraggable'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + interface Task { + id: number; + taskName: string; + status: number; + } + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const taskList = ref<Task[][]>([]); + + // + const result = ref(''); + + // + const visible = ref(false); + + /* 更新数据 */ + const updateModelValue = (value: Task[], index: number) => { + taskList.value[index] = value; + }; + + /* 查看数据 */ + const viewData = () => { + result.value = JSON.stringify(taskList.value, null, 4); + visible.value = true; + }; + + // 处理数据 + const temp: Task[][] = []; + for (let i = 0; i < 18; i++) { + const index = parseInt(String(i / 6)); + if (temp[index] == null) { + temp[index] = []; + } + temp[index].push({ + id: i, + taskName: '测试任务' + (i + 1), + status: 0 + }); + } + taskList.value = temp; +</script> + +<style lang="less" scoped> + /* 表格行拖拽按下去样式 */ + .ele-table tr.sortable-chosen { + background: hsla(0, 0%, 60%, 0.1); + } +</style> diff --git a/src/views-demo/extension/dragsort/index.vue b/src/views-demo/extension/dragsort/index.vue new file mode 100644 index 0000000..4e0c3ea --- /dev/null +++ b/src/views-demo/extension/dragsort/index.vue @@ -0,0 +1,19 @@ +<template> + <div class="ele-body ele-body-card"> + <demo-list /> + <demo-grid /> + <demo-table /> + </div> +</template> + +<script lang="ts" setup> + import DemoList from './components/demo-list.vue'; + import DemoGrid from './components/demo-grid.vue'; + import DemoTable from './components/demo-table.vue'; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionDragsort' + }; +</script> diff --git a/src/views-demo/extension/editor/index.vue b/src/views-demo/extension/editor/index.vue new file mode 100644 index 0000000..3ad3825 --- /dev/null +++ b/src/views-demo/extension/editor/index.vue @@ -0,0 +1,125 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 按钮 --> + <div style="margin-bottom: 16px"> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="setContent"> + 修改内容 + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="showHtml"> + 获取html + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="showText"> + 获取文本 + </a-button> + <a-button + type="primary" + :danger="!disabled" + class="ele-btn-icon" + @click="toggleDisabled" + > + {{ disabled ? '启用' : '禁用' }} + </a-button> + </a-space> + </div> + <!-- 编辑器 --> + <tinymce-editor + ref="editorRef" + :init="config" + v-model:value="content" + :disabled="disabled" + /> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { Modal } from 'ant-design-vue/es'; + import { htmlToText } from 'ele-admin-pro/es'; + import TinymceEditor from '@/components/TinymceEditor/index.vue'; + + const editorRef = ref<InstanceType<typeof TinymceEditor> | null>(null); + + const config = ref({ + height: 520, + // 自定义文件上传(这里使用把选择的文件转成 blob 演示) + file_picker_callback: (callback: any, _value: any, meta: any) => { + const input = document.createElement('input'); + input.setAttribute('type', 'file'); + // 设定文件可选类型 + if (meta.filetype === 'image') { + input.setAttribute('accept', 'image/*'); + } else if (meta.filetype === 'media') { + input.setAttribute('accept', 'video/*'); + } + input.onchange = () => { + const file = input.files?.[0]; + if (!file) { + return; + } + if (meta.filetype === 'media') { + if (!file.type.startsWith('video/')) { + editorRef.value?.alert({ content: '只能选择视频文件' }); + return; + } + } + if (file.size / 1024 / 1024 > 20) { + editorRef.value?.alert({ content: '大小不能超过 20MB' }); + return; + } + const reader = new FileReader(); + reader.onload = (e) => { + if (e.target?.result != null) { + const blob = new Blob([e.target.result], { type: file.type }); + callback(URL.createObjectURL(blob)); + } + }; + reader.readAsArrayBuffer(file); + }; + input.click(); + } + }); + + const content = ref(''); + + const disabled = ref(false); + + /* 获取编辑器内容 */ + const showHtml = () => { + Modal.info({ + maskClosable: true, + content: content.value + }); + }; + + /* 获取编辑器纯文本内容 */ + const showText = () => { + Modal.info({ + maskClosable: true, + content: htmlToText(content.value) + }); + }; + + /* 修改编辑器内容 */ + const setContent = () => { + content.value = [ + '<div style="text-align: center;color: #fff;background-image: linear-gradient(-90deg, rgb(62,119,255), rgb(159,98,212), rgb(255,78,170));padding: 32px 0;">', + ' <div style="font-size: 28px;margin-bottom: 16px;">EleAdminPro后台管理模板</div>', + ' <div style="font-size:18px">通用型后台管理模板,界面美观、开箱即用,拥有丰富的组件和模板</div>', + '</div><br/>' + ].join(''); + }; + + /* 禁用启用切换 */ + const toggleDisabled = () => { + disabled.value = !disabled.value; + }; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionEditor' + }; +</script> diff --git a/src/views-demo/extension/excel/components/excel-export.vue b/src/views-demo/extension/excel/components/excel-export.vue new file mode 100644 index 0000000..4eff656 --- /dev/null +++ b/src/views-demo/extension/excel/components/excel-export.vue @@ -0,0 +1,266 @@ +<template> + <a-card title="导出 Excel" :bordered="false"> + <!-- 表格 --> + <ele-pro-table + bordered + row-key="key" + :columns="columns" + :datasource="data" + :need-page="false" + tools-theme="default" + v-model:selection="selection" + :toolkit="['size', 'columns', 'fullscreen']" + :scroll="{ x: 800 }" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="exportBas"> + 导出 + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="exportAdv"> + 导出带表头合并 + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="exportSel"> + 导出选中 + </a-button> + </a-space> + </template> + </ele-pro-table> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { utils, writeFile } from 'xlsx'; + import { message } from 'ant-design-vue/es'; + import type { ColumnItem } from 'ele-admin-pro/es/ele-pro-table/types'; + // + interface UserType { + key: number; + username: string; + amount: number; + province: string; + city: string; + zone: string; + street: string; + address: string; + } + + // 表格数据 + const data = ref<UserType[]>([ + { + key: 1, + username: '张小三', + amount: 18, + province: '浙江', + city: '杭州', + zone: '西湖区', + street: '西溪街道', + address: '西溪花园30栋1单元' + }, + { + key: 2, + username: '李小四', + amount: 39, + province: '江苏', + city: '苏州', + zone: '姑苏区', + street: '丝绸路', + address: '天墅之城9幢2单元' + }, + { + key: 3, + username: '王小五', + amount: 8, + province: '江西', + city: '南昌', + zone: '青山湖区', + street: '艾溪湖办事处', + address: '中兴和园1幢3单元' + }, + { + key: 4, + username: '赵小六', + amount: 16, + province: '福建', + city: '泉州', + zone: '丰泽区', + street: '南洋街道', + address: '南洋村6幢1单元' + }, + { + key: 5, + username: '孙小七', + amount: 12, + province: '湖北', + city: '武汉', + zone: '武昌区', + street: '武昌大道', + address: '两湖花园16幢2单元' + }, + { + key: 6, + username: '周小八', + amount: 11, + province: '安徽', + city: '黄山', + zone: '黄山区', + street: '汤口镇', + address: '温泉村21号' + } + ]); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + 1 + }, + { + title: '用户名', + dataIndex: 'username', + align: 'center' + }, + { + title: '地址', + key: 'cityAddress', + children: [ + { + title: '省', + dataIndex: 'province', + align: 'center' + }, + { + title: '市', + dataIndex: 'city', + align: 'center' + }, + { + title: '区', + dataIndex: 'zone', + align: 'center' + }, + { + title: '街道', + dataIndex: 'street', + align: 'center' + }, + { + title: '详细地址', + dataIndex: 'address', + align: 'center' + } + ] + }, + { + title: '金额', + dataIndex: 'amount', + align: 'center' + } + ]); + + // 选中数据 + const selection = ref<UserType[]>([]); + + /* 导出excel */ + const exportBas = () => { + const array: (string | number)[][] = [ + ['用户名', '省', '市', '区', '街道', '详细地址', '金额'] + ]; + data.value.forEach((d) => { + array.push([ + d.username, + d.province, + d.city, + d.zone, + d.street, + d.address, + d.amount + ]); + }); + const sheetName = 'Sheet1'; + const workbook = { + SheetNames: [sheetName], + Sheets: {} + }; + const sheet = utils.aoa_to_sheet(array); + workbook.Sheets[sheetName] = sheet; + // 设置列宽 + sheet['!cols'] = [ + { wch: 10 }, + { wch: 10 }, + { wch: 10 }, + { wch: 10 }, + { wch: 20 }, + { wch: 40 }, + { wch: 10 } + ]; + writeFile(workbook, '用户数据.xlsx'); + }; + + /* 导出带单元格合并 */ + const exportAdv = () => { + const array: (string | number | null)[][] = [ + ['用户名', '地址', null, null, null, null, '金额'], + [null, '省', '市', '区', '街道', '详细地址', null] + ]; + data.value.forEach((d) => { + array.push([ + d.username, + d.province, + d.city, + d.zone, + d.street, + d.address, + d.amount + ]); + }); + const sheet = utils.aoa_to_sheet(array); + sheet['!merges'] = [ + { s: { r: 0, c: 1 }, e: { r: 0, c: 5 } }, // 合并第 0 行第 1 列到第 0 行第5列 + { s: { r: 0, c: 1 }, e: { r: 0, c: 5 } }, // 合并第 0 行第 1 列到第 0 行第 5 列 + { s: { r: 0, c: 0 }, e: { r: 1, c: 0 } }, // 合并第 0 行第 0 列到第 1 行第 0 列 + { s: { r: 0, c: 6 }, e: { r: 1, c: 6 } } // 合并第 0 行第 6 列到第 1 行第 6 列 + ]; + const sheetName = 'Sheet1'; + const workbook = { + SheetNames: [sheetName], + Sheets: {} + }; + workbook.Sheets[sheetName] = sheet; + writeFile(workbook, '用户数据.xlsx'); + }; + + /* 导出选中数据 */ + const exportSel = () => { + if (selection.value.length === 0) { + message.error('请至少选择一条数据'); + return; + } + const array: (string | number)[][] = [ + ['用户名', '省', '市', '区', '街道', '详细地址', '金额'] + ]; + selection.value.forEach((d) => { + array.push([ + d.username, + d.province, + d.city, + d.zone, + d.street, + d.address, + d.amount + ]); + }); + const sheetName = 'Sheet1'; + const workbook = { + SheetNames: [sheetName], + Sheets: {} + }; + workbook.Sheets[sheetName] = utils.aoa_to_sheet(array); + writeFile(workbook, '用户数据.xlsx'); + }; +</script> diff --git a/src/views-demo/extension/excel/components/excel-import.vue b/src/views-demo/extension/excel/components/excel-import.vue new file mode 100644 index 0000000..f154db1 --- /dev/null +++ b/src/views-demo/extension/excel/components/excel-import.vue @@ -0,0 +1,316 @@ +<template> + <a-card title="导入 Excel" :bordered="false"> + <!-- 操作按钮 --> + <ele-toolbar :tools="[]"> + <a-space> + <a-upload + :before-upload="importFile" + :show-upload-list="false" + accept=".xls,.xlsx" + > + <a-button type="primary" class="ele-btn-icon">导入</a-button> + </a-upload> + <a-upload + :before-upload="importFile2" + :show-upload-list="false" + accept=".xls,.xlsx" + > + <a-button type="primary" class="ele-btn-icon">导入拆分合并</a-button> + </a-upload> + <a-upload + :before-upload="importFile3" + :show-upload-list="false" + accept=".xls,.xlsx" + > + <a-button type="primary" class="ele-btn-icon">导入保持合并</a-button> + </a-upload> + </a-space> + </ele-toolbar> + <div style="overflow: auto"> + <table class="ele-table ele-table-border" style="min-width: max-content"> + <thead> + <tr> + <th></th> + <th + v-for="item in importTitle" + :key="item" + style="text-align: center" + > + {{ item }} + </th> + </tr> + </thead> + <tbody> + <tr v-for="(item, index) in importData" :key="index"> + <td style="text-align: center">{{ index + 1 }}</td> + <template v-for="key in importTitle"> + <td + v-if=" + item['__colspan__' + key] !== 0 && + item['__rowspan__' + key] !== 0 + " + :key="key" + :colspan="item['__colspan__' + key]" + :rowspan="item['__rowspan__' + key]" + style="text-align: center" + > + {{ item[key] }} + </td> + </template> + </tr> + </tbody> + </table> + </div> + <a-row :gutter="32"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <div style="margin: 16px 0">二维数组格式数据:</div> + <pre style="max-height: 300px; padding: 16px; overflow: auto" + >{{ JSON.stringify(importDataAoa, null, 4) }} + </pre + > + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <div style="margin: 16px 0">JSON格式数据:</div> + <pre style="max-height: 300px; padding: 16px; overflow: auto" + >{{ JSON.stringify(importData, null, 4) }} + </pre + > + </a-col> + </a-row> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { utils, read } from 'xlsx'; + import { message } from 'ant-design-vue/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // 导入数据的列 + const importTitle = ref<string[]>(['A', 'B', 'C', 'D', 'E', 'F', 'G']); + + // 导入的数据 + const importData = ref<Record<string, any>[]>([]); + + // 导入数据二维数组形式 + const importDataAoa = ref<(string | number)[][]>([]); + + /* 导入本地 excel 文件 */ + const importFile = (file: File) => { + if ( + ![ + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ].includes(file.type) + ) { + message.error('只能选择 excel 文件'); + return false; + } + if (file.size / 1024 / 1024 > 20) { + message.error('大小不能超过 20MB'); + return false; + } + const reader = new FileReader(); + reader.onload = (e) => { + const data = new Uint8Array(e.target?.result as any); + const workbook = read(data, { type: 'array' }); + const sheetNames = workbook.SheetNames; + const worksheet = workbook.Sheets[sheetNames[0]]; + // 解析成二维数组 + const aoa = utils.sheet_to_json<string[]>(worksheet, { header: 1 }); + // 生成表格需要的数据 + let list: Record<string, any>[] = []; + let maxCols = 0; + let title: string[] = []; + aoa.forEach((d) => { + if (d.length > maxCols) { + maxCols = d.length; + } + const row = {}; + for (let i = 0; i < d.length; i++) { + const key = getCharByIndex(i); + row[key] = d[i]; + row['__colspan__' + key] = 1; + row['__rowspan__' + key] = 1; + } + list.push(row); + }); + for (let i = 0; i < maxCols; i++) { + title.push(getCharByIndex(i)); + } + importTitle.value = title; + importData.value = list; + importDataAoa.value = aoa; + }; + reader.readAsArrayBuffer(file); + return false; + }; + + /* 导入 excel 拆分合并单元格 */ + const importFile2 = (file: File) => { + if ( + ![ + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ].includes(file.type) + ) { + message.error('只能选择 excel 文件'); + return false; + } + if (file.size / 1024 / 1024 > 20) { + message.error('大小不能超过 20MB'); + return false; + } + const reader = new FileReader(); + reader.onload = (e) => { + const data = new Uint8Array(e.target?.result as any); + const workbook = read(data, { type: 'array' }); + const sheetNames = workbook.SheetNames; + const worksheet = workbook.Sheets[sheetNames[0]]; + // 解析成二维数组 + const aoa = utils.sheet_to_json<string[]>(worksheet, { header: 1 }); + // 拆分合并单元格 + if (worksheet['!merges']) { + worksheet['!merges'].forEach((m) => { + for (let r = m.s.r; r <= m.e.r; r++) { + for (let c = m.s.c; c <= m.e.c; c++) { + aoa[r][c] = aoa[m.s.r][m.s.c]; + } + } + }); + } + // 生成表格需要的数据 + let list: Record<string, any>[] = []; + let maxCols = 0; + let title: string[] = []; + aoa.forEach((d) => { + if (d.length > maxCols) { + maxCols = d.length; + } + const row = {}; + for (let i = 0; i < d.length; i++) { + row[getCharByIndex(i)] = d[i]; + } + list.push(row); + }); + for (let i = 0; i < maxCols; i++) { + title.push(getCharByIndex(i)); + } + importTitle.value = title; + importData.value = list; + importDataAoa.value = aoa; + }; + reader.readAsArrayBuffer(file); + return false; + }; + + /* 导入 excel 读取合并信息 */ + const importFile3 = (file: File) => { + if ( + ![ + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ].includes(file.type) + ) { + message.error('只能选择 excel 文件'); + return false; + } + if (file.size / 1024 / 1024 > 20) { + message.error('大小不能超过 20MB'); + return false; + } + const reader = new FileReader(); + reader.onload = (e) => { + const data = new Uint8Array(e.target?.result as any); + const workbook = read(data, { type: 'array' }); + const sheetNames = workbook.SheetNames; + const worksheet = workbook.Sheets[sheetNames[0]]; + // 解析成二维数组 + const aoa = utils.sheet_to_json<string[]>(worksheet, { header: 1 }); + // 生成表格需要的数据 + let list: Record<string, any>[] = []; + let maxCols = 0; + let title: string[] = []; + aoa.forEach((d) => { + if (d.length > maxCols) { + maxCols = d.length; + } + const row = {}; + for (let i = 0; i < d.length; i++) { + row[getCharByIndex(i)] = d[i]; + } + list.push(row); + }); + for (let i = 0; i < maxCols; i++) { + title.push(getCharByIndex(i)); + } + // 记录合并单元格 + if (worksheet['!merges']) { + worksheet['!merges'].forEach((m) => { + for (let r = m.s.r; r <= m.e.r; r++) { + for (let c = m.s.c; c <= m.e.c; c++) { + const cc = getCharByIndex(c); + list[r]['__colspan__' + cc] = 0; + list[r]['__rowspan__' + cc] = 0; + } + } + const char = getCharByIndex(m.s.c); + list[m.s.r]['__colspan__' + char] = m.e.c - m.s.c + 1; + list[m.s.r]['__rowspan__' + char] = m.e.r - m.s.r + 1; + }); + } + importTitle.value = title; + importData.value = list; + importDataAoa.value = aoa; + }; + reader.readAsArrayBuffer(file); + return false; + }; + + /* 生成Excel列字母序号 */ + const getCharByIndex = (index: number) => { + const chars = [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z' + ]; + if (index < chars.length) { + return chars[index]; + } + const n = parseInt(String(index / chars.length)); + const m = index % chars.length; + return chars[n] + chars[m]; + }; +</script> diff --git a/src/views-demo/extension/excel/index.vue b/src/views-demo/extension/excel/index.vue new file mode 100644 index 0000000..1d5cd79 --- /dev/null +++ b/src/views-demo/extension/excel/index.vue @@ -0,0 +1,17 @@ +<template> + <div class="ele-body ele-body-card"> + <excel-export /> + <excel-import /> + </div> +</template> + +<script lang="ts" setup> + import ExcelExport from './components/excel-export.vue'; + import ExcelImport from './components/excel-import.vue'; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionExcel' + }; +</script> diff --git a/src/views-demo/extension/file/components/file-header.vue b/src/views-demo/extension/file/components/file-header.vue new file mode 100644 index 0000000..61c6355 --- /dev/null +++ b/src/views-demo/extension/file/components/file-header.vue @@ -0,0 +1,119 @@ +<!-- 文件目录面包屑 --> +<template> + <div class="ele-file-breadcrumb-group ele-cell"> + <div class="ele-cell-content ele-cell"> + <div + v-if="directorys.length" + class="ele-file-breadcrumb-back ele-text-primary" + @click="goBack" + > + 返回上一级 + </div> + <div class="ele-file-breadcrumb-list ele-cell-content ele-cell"> + <div + :class="[ + 'ele-file-breadcrumb-item ele-cell', + { 'ele-text-primary': !!directorys.length } + ]" + @click="goRoot" + > + <div class="ele-file-breadcrumb-item-title">全部文件</div> + <right-outlined v-if="directorys.length" class="ele-text-secondary" /> + </div> + <div + v-for="(item, i) in directorys" + :key="item.id" + :class="[ + 'ele-file-breadcrumb-item ele-cell', + { 'ele-text-primary': i !== directorys.length - 1 } + ]" + @click="goDirectory(i)" + > + <div class="ele-file-breadcrumb-item-title">{{ item.name }}</div> + <right-outlined + v-if="i !== directorys.length - 1" + class="ele-text-secondary" + /> + </div> + </div> + </div> + <div :class="{ 'hidden-xs-only': styleResponsive }"> + 已全部加载,共 {{ total }} 个 + </div> + </div> +</template> + +<script lang="ts" setup> + import { RightOutlined } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import type { UserFile } from '@/api/system/user-file/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const props = defineProps<{ + // 文件夹数据 + directorys: UserFile[]; + // 总文件数 + total: number; + }>(); + + const emit = defineEmits<{ + (e: 'update:directorys', value: UserFile[]): void; + }>(); + + /* 回到上级 */ + const goBack = () => { + emit( + 'update:directorys', + props.directorys.slice(0, props.directorys.length - 1) + ); + }; + + /* 回到根目录 */ + const goRoot = () => { + if (props.directorys.length) { + emit('update:directorys', []); + } + }; + + /* 回到指定目录 */ + const goDirectory = (index: number) => { + if (index !== props.directorys.length - 1) { + emit('update:directorys', props.directorys.slice(0, index + 1)); + } + }; +</script> + +<style lang="less" scoped> + /* 文件目录面包屑 */ + .ele-file-breadcrumb-group { + line-height: 1; + } + + .ele-file-breadcrumb-back { + padding-right: 12px; + border-right: 1px solid hsla(0, 0%, 60%, 0.3); + } + + .ele-file-breadcrumb-back:hover, + .ele-file-breadcrumb-item.ele-text-primary:hover + > .ele-file-breadcrumb-item-title { + text-decoration: underline; + cursor: pointer; + } + + .ele-file-breadcrumb-item .anticon { + margin: 0 4px; + font-size: 12px; + } + + @media screen and (max-width: 768px) { + .ele-table-tool > .ele-table-tool-title + div, + .ele-file-breadcrumb-group > .ele-cell-content + div { + display: none; + } + } +</style> diff --git a/src/views-demo/extension/file/components/file-list.vue b/src/views-demo/extension/file/components/file-list.vue new file mode 100644 index 0000000..f2df076 --- /dev/null +++ b/src/views-demo/extension/file/components/file-list.vue @@ -0,0 +1,243 @@ +<!-- 文件展示列表 --> +<template> + <div class="demo-file-list-group"> + <ele-file-list + :data="data" + :grid="grid" + :sort="sort" + :order="order" + :checked="checked" + :style="{ minHeight: '400px', minWidth: grid ? 'auto' : '456px' }" + @item-click="onItemClick" + @sort-change="onSortChange" + @update:checked="updateChecked" + > + <template #context-menu="{ item }"> + <a-menu + :selectable="false" + @click="({ key }) => onCtxMenuClick(key, item)" + > + <a-menu-item key="open">打开</a-menu-item> + <a-menu-divider /> + <a-menu-item key="download" v-if="!item.isDirectory"> + <div class="ele-cell"> + <download-outlined /> + <div class="ele-cell-content">下载</div> + </div> + </a-menu-item> + <a-menu-item key="edit"> + <div class="ele-cell"> + <edit-outlined /> + <div class="ele-cell-content">重命名</div> + </div> + </a-menu-item> + <a-menu-item key="move"> + <div class="ele-cell"> + <drag-outlined /> + <div class="ele-cell-content">移动到</div> + </div> + </a-menu-item> + <a-menu-divider /> + <a-menu-item key="remove"> + <div class="ele-cell ele-text-danger"> + <delete-outlined /> + <div class="ele-cell-content">删除</div> + </div> + </a-menu-item> + </a-menu> + </template> + </ele-file-list> + <div v-if="!data.length" class="demo-file-list-empty"> + <a-empty /> + </div> + </div> + <!-- 用于图片预览 --> + <div style="display: none"> + <AImagePreviewGroup v-if="previewOption.visible" :preview="previewOption"> + <AImage + v-for="item in previewImages" + :key="String(item.id)" + :src="item.url" + /> + </AImagePreviewGroup> + </div> + <!-- 文件重命名弹窗 --> + <name-edit + v-model:visible="nameEditVisible" + :data="nameEditData" + @done="onDone" + /> +</template> + +<script lang="ts" setup> + import { ref, reactive, createVNode } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + DownloadOutlined, + DragOutlined, + EditOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import { messageLoading } from 'ele-admin-pro/es'; + import type { + FileItem, + SortValue + } from 'ele-admin-pro/es/ele-file-list/types'; + import { removeUserFile } from '@/api/system/user-file'; + import type { UserFile } from '@/api/system/user-file/model'; + import NameEdit from './name-edit.vue'; + + const props = defineProps<{ + // 父级 id + parentId?: number; + // 文件列表数据 + data: FileItem[]; + // 排序字段 + sort?: string; + // 排序方式 + order?: string; + // 选中数据 + checked: FileItem[]; + // 是否网格展示 + grid: boolean; + }>(); + + const emit = defineEmits<{ + (e: 'sort-change', value: SortValue): void; + (e: 'update:checked', value: FileItem[]): void; + (e: 'go-directory', value: UserFile): void; + (e: 'done'): void; + }>(); + + // 图片预览配置 + const previewOption = reactive({ + current: 0, + visible: false, + onVisibleChange: (visible: boolean) => { + previewOption.visible = visible; + } + }); + + // 图片预览列表 + const previewImages = ref<FileItem[]>([]); + + // 文件重命名弹窗是否打开 + const nameEditVisible = ref<boolean>(false); + + // 文件重命名的数据 + const nameEditData = ref<UserFile>(); + + /* 文件列表排序方式改变 */ + const onSortChange = (option: SortValue) => { + emit('sort-change', option); + }; + + /* 更新选中数据 */ + const updateChecked = (value: FileItem[]) => { + emit('update:checked', value); + }; + + /* item 点击事件 */ + const onItemClick = (item: FileItem) => { + if (item.isDirectory) { + // 进入文件夹 + emit('go-directory', item as unknown as UserFile); + } else if (isImageFile(item)) { + // 预览图片文件 + previewItemImage(item); + } else { + // 选中或取消选中文件 + updateChecked( + props.checked.includes(item) + ? props.checked.filter((d) => d !== item) + : [...props.checked, item] + ); + } + }; + + /* 右键菜单点击事件 */ + const onCtxMenuClick = (key: any, item: FileItem) => { + if (key === 'open') { + // 打开文件 + if (item.isDirectory || isImageFile(item)) { + onItemClick(item); + } else { + window.open(item.url); + } + } else if (key === 'download') { + // 下载文件 + if (typeof item.downloadUrl === 'string') { + window.open(item.downloadUrl); + } + } else if (key === 'edit') { + // 重命名 + nameEditData.value = item as unknown as UserFile; + nameEditVisible.value = true; + } else if (key === 'remove') { + // 删除文件 + removeItem(item); + } + }; + + /* 删除 */ + const removeItem = (item: FileItem) => { + Modal.confirm({ + title: '提示', + content: '确定要删除此文件吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeUserFile(item.id as number) + .then((msg) => { + hide(); + message.success(msg); + onDone(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 完成刷新列表数据 */ + const onDone = () => { + emit('done'); + }; + + /* 判断是否是图片文件 */ + const isImageFile = (item: FileItem) => { + return ( + typeof item.contentType === 'string' && + item.contentType.startsWith('image/') && + item.url + ); + }; + + /* 预览图片文件 */ + const previewItemImage = (item: FileItem) => { + previewImages.value = props.data.filter((d) => isImageFile(d)); + const index = previewImages.value.indexOf(item); + if (index !== -1) { + previewOption.current = index; + previewOption.visible = true; + } + }; +</script> + +<style lang="less" scoped> + .demo-file-list-group { + position: relative; + overflow-x: auto; + + .demo-file-list-empty { + position: absolute; + top: 100px; + left: 50%; + transform: translateX(-50%); + } + } +</style> diff --git a/src/views-demo/extension/file/components/file-toolbar.vue b/src/views-demo/extension/file/components/file-toolbar.vue new file mode 100644 index 0000000..1ee5f07 --- /dev/null +++ b/src/views-demo/extension/file/components/file-toolbar.vue @@ -0,0 +1,183 @@ +<template> + <ele-toolbar> + <a-space> + <a-upload :show-upload-list="false" :customRequest="onUpload"> + <a-button type="primary" class="ele-btn-icon"> + <template #icon> + <upload-outlined /> + </template> + <span>上传</span> + </a-button> + </a-upload> + <a-button type="dashed" class="ele-btn-icon" @click="openFolderAdd"> + <template #icon> + <folder-add-outlined /> + </template> + <span>新建文件夹</span> + </a-button> + <a-button + danger + type="dashed" + :disabled="!checked.length" + :class="['ele-btn-icon', { 'hidden-xs-only': styleResponsive }]" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + <template #action> + <!-- 搜索框 --> + <div + style="max-width: 240px" + :class="{ 'hidden-sm-and-down': styleResponsive }" + > + <a-input-search v-model:value="search" placeholder="搜索您的文件" /> + </div> + <!-- 显示方式切换 --> + <menu-outlined + v-if="grid" + class="ele-file-tool-btn" + @click="toggleShowType" + /> + <appstore-outlined + v-else + class="ele-file-tool-btn" + @click="toggleShowType" + /> + </template> + </ele-toolbar> + <!-- 新建文件夹弹窗 --> + <folder-add + v-model:visible="folderAddVisible" + :parent-id="parentId" + @done="onDone" + /> +</template> + +<script lang="ts" setup> + import { ref, createVNode } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + MenuOutlined, + AppstoreOutlined, + DeleteOutlined, + UploadOutlined, + FolderAddOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import { messageLoading } from 'ele-admin-pro/es'; + import type { FileItem } from 'ele-admin-pro/es/ele-file-list/types'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { uploadFile } from '@/api/system/file'; + import { addUserFile, removeUserFiles } from '@/api/system/user-file'; + import FolderAdd from './folder-add.vue'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const props = defineProps<{ + // 是否网格展示 + grid: boolean; + // 选中数据 + checked: FileItem[]; + // 父级 id + parentId?: number; + }>(); + + const emit = defineEmits<{ + (e: 'update:grid', value: boolean): void; + (e: 'done'): void; + }>(); + + // 搜索关键字 + const search = ref<string>(''); + + // 新建文件夹弹窗是否打开 + const folderAddVisible = ref<boolean>(false); + + /* 上传 */ + const onUpload = ({ file }) => { + if (file.size / 1024 / 1024 > 100) { + message.error('大小不能超过 100MB'); + return false; + } + const hide = messageLoading('上传中..', 0); + uploadFile(file) + .then((data) => { + addUserFile({ + name: data.name, + isDirectory: 0, + parentId: props.parentId, + path: data.path, + length: data.length, + contentType: data.contentType + }) + .then(() => { + hide(); + message.success('上传成功'); + onDone(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + return false; + }; + + /* 打开新建文件夹弹窗 */ + const openFolderAdd = () => { + folderAddVisible.value = true; + }; + + /* 批量删除 */ + const removeBatch = () => { + Modal.confirm({ + title: '提示', + content: '确定要删除选中的文件吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeUserFiles(props.checked.map((d) => d.id as number)) + .then((msg) => { + hide(); + message.success(msg); + onDone(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 完成刷新列表数据 */ + const onDone = () => { + emit('done'); + }; + + /* 显示方式切换 */ + const toggleShowType = () => { + emit('update:grid', !props.grid); + }; +</script> + +<style lang="less" scoped> + /* 图标按钮 */ + .ele-file-tool-btn { + font-size: 20px; + margin-left: 16px; + cursor: pointer; + } +</style> diff --git a/src/views-demo/extension/file/components/folder-add.vue b/src/views-demo/extension/file/components/folder-add.vue new file mode 100644 index 0000000..f638820 --- /dev/null +++ b/src/views-demo/extension/file/components/folder-add.vue @@ -0,0 +1,123 @@ +<template> + <ele-modal + :width="460" + :visible="visible" + :confirm-loading="loading" + title="新建文件夹" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col=" + styleResponsive ? { md: 6, sm: 6, xs: 24 } : { flex: '100px' } + " + :wrapper-col=" + styleResponsive ? { md: 18, sm: 18, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="文件夹名称" name="name"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入文件夹名称" + v-model:value="form.name" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { addUserFile } from '@/api/system/user-file'; + import type { UserFile } from '@/api/system/user-file/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 父级 id + parentId?: number; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields } = useFormData<UserFile>({ name: '' }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + name: [ + { + required: true, + message: '请输入文件夹名称', + type: 'string', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + addUserFile({ + ...form, + parentId: props.parentId, + isDirectory: 1 + }) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新 visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (!visible) { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views-demo/extension/file/components/name-edit.vue b/src/views-demo/extension/file/components/name-edit.vue new file mode 100644 index 0000000..3c3beff --- /dev/null +++ b/src/views-demo/extension/file/components/name-edit.vue @@ -0,0 +1,121 @@ +<template> + <ele-modal + :width="460" + :visible="visible" + :confirm-loading="loading" + title="重命名" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col=" + styleResponsive ? { md: 6, sm: 6, xs: 24 } : { flex: '100px' } + " + :wrapper-col=" + styleResponsive ? { md: 18, sm: 18, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="文件/夹名称" name="name"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入文件/夹名称" + v-model:value="form.name" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { updateUserFile } from '@/api/system/user-file'; + import type { UserFile } from '@/api/system/user-file/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 文件数据 + data?: UserFile; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields } = useFormData<UserFile>({ name: '' }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + name: [ + { + required: true, + message: '请输入文件/夹名称', + type: 'string', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + updateUserFile({ ...form, id: props.data?.id }) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新 visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (!visible) { + resetFields(); + formRef.value?.clearValidate(); + } else if (props.data) { + form.name = props.data.name; + } + } + ); +</script> diff --git a/src/views-demo/extension/file/index.vue b/src/views-demo/extension/file/index.vue new file mode 100644 index 0000000..2b2a244 --- /dev/null +++ b/src/views-demo/extension/file/index.vue @@ -0,0 +1,147 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false" :body-style="{ padding: 0 }"> + <div style="padding: 16px 16px 12px 16px"> + <file-toolbar + v-model:grid="grid" + :checked="checked" + :parentId="parentId" + @done="onDone" + /> + <file-header + :total="total" + :directorys="directorys" + @update:directorys="updateDirectorys" + /> + </div> + <a-spin :spinning="loading"> + <file-list + :grid="grid" + :data="data" + :sort="sort" + :order="order" + :parentId="parentId" + v-model:checked="checked" + @sort-change="onSortChange" + @go-directory="onGoDirectory" + @done="onDone" + /> + </a-spin> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { + FileItem, + SortValue + } from 'ele-admin-pro/es/ele-file-list/types'; + import FileToolbar from './components/file-toolbar.vue'; + import FileHeader from './components/file-header.vue'; + import FileList from './components/file-list.vue'; + import { listUserFiles } from '@/api/system/user-file'; + import type { UserFile } from '@/api/system/user-file/model'; + + // 加载状态 + const loading = ref(true); + + // 文件列表数据 + const data = ref<FileItem[]>([]); + + // 排序字段 + const sort = ref<string>(''); + + // 排序方式 + const order = ref<string>(''); + + // 选中数据 + const checked = ref<FileItem[]>([]); + + // 文件夹数据 + const directorys = ref<UserFile[]>([]); + + // 总文件数 + const total = ref<number>(0); + + // 是否网格展示 + const grid = ref(true); + + // 父级 id + const parentId = ref<number>(0); + + /* 查询文件列表 */ + const query = () => { + data.value = []; + checked.value = []; + loading.value = true; + listUserFiles({ + sort: order.value ? sort.value : '', + order: order.value, + parentId: parentId.value + }) + .then((list) => { + loading.value = false; + data.value = list.map((d) => { + return Object.assign({ name: d.name }, d, { + isDirectory: d.isDirectory === 1 ? true : false, + length: formatLength(d.length) + }); + }); + total.value = list.length; + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }; + + /* 刷新列表数据 */ + const onDone = () => { + query(); + }; + + /* 文件列表排序方式改变 */ + const onSortChange = (option: SortValue) => { + order.value = option.order; + sort.value = option.sort; + query(); + }; + + /* 进入文件夹 */ + const onGoDirectory = (item: UserFile) => { + updateDirectorys([...directorys.value, item]); + }; + + /* 更新文件夹数据 */ + const updateDirectorys = (values: UserFile[]) => { + directorys.value = values; + parentId.value = directorys.value[directorys.value.length - 1]?.id ?? 0; + query(); + }; + + /* 格式化文件大小 */ + const formatLength = (length?: number) => { + if (length == null) { + return '-'; + } + if (length < 1024) { + return length + 'B'; + } else if (length < 1024 * 1024) { + return (length / 1024).toFixed(1) + 'KB'; + } else if (length < 1024 * 1024 * 1024) { + return (length / 1024 / 1024).toFixed(1) + 'M'; + } else { + return (length / 1024 / 1024 / 1024).toFixed(1) + 'G'; + } + }; + + query(); +</script> + +<script lang="ts"> + export default { + name: 'ExtensionFile' + }; +</script> diff --git a/src/views-demo/extension/map/components/demo-map.vue b/src/views-demo/extension/map/components/demo-map.vue new file mode 100644 index 0000000..5c446a5 --- /dev/null +++ b/src/views-demo/extension/map/components/demo-map.vue @@ -0,0 +1,98 @@ +<template> + <a-card title="官网底部地图" :bordered="false"> + <div ref="locationMapRef" style="height: 360px; max-width: 800px"></div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, watch, onMounted, onUnmounted } from 'vue'; + import AMapLoader from '@amap/amap-jsapi-loader'; + import { useThemeStore } from '@/store/modules/theme'; + import { storeToRefs } from 'pinia'; + import { MAP_KEY } from '@/config/setting'; + + const themeStore = useThemeStore(); + const { darkMode } = storeToRefs(themeStore); + + // + const locationMapRef = ref<HTMLElement | null>(null); + + // 官网底部地图的实例 + let mapInsLocation: any; + + /* 渲染官网底部地图 */ + const renderLocationMap = () => { + AMapLoader.load({ + key: MAP_KEY, + version: '2.0', + plugins: ['AMap.InfoWindow', 'AMap.Marker'] + }) + .then((AMap) => { + // 渲染地图 + const option = { + zoom: 13, // 初缩放级别 + center: [114.346084, 30.511215 + 0.005], // 初始中心点 + mapStyle: darkMode.value ? 'amap://styles/dark' : void 0 + }; + mapInsLocation = new AMap.Map(locationMapRef.value, option); + // 创建信息窗体 + const infoWindow = new AMap.InfoWindow({ + content: ` + <div style="color: #333;"> + <div style="padding: 5px;font-size: 16px;">武汉易云智科技有限公司</div> + <div style="padding: 0 5px;">地址: 湖北省武汉市洪山区雄楚大道222号</div> + <div style="padding: 0 5px;">电话: 020-123456789</div> + </div> + <a + class="ele-text-primary" + style="padding: 8px 5px 0;text-decoration: none;display: inline-block;" + href="//uri.amap.com/marker?position=114.346084,30.511215&name=武汉易云智科技有限公司" + target="_blank">到这里去→ + </a> + ` + }); + infoWindow.open(mapInsLocation, [114.346084, 30.511215]); + const icon = new AMap.Icon({ + size: new AMap.Size(25, 34), + image: + '//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-red.png', + imageSize: new AMap.Size(25, 34) + }); + const marker = new AMap.Marker({ + icon: icon, + position: [114.346084, 30.511215], + offset: new AMap.Pixel(-12, -28) + }); + marker.setMap(mapInsLocation); + marker.on('click', () => { + infoWindow.open(mapInsLocation); + }); + }) + .catch((e) => { + console.error(e); + }); + }; + + /* 渲染地图 */ + onMounted(() => { + renderLocationMap(); + }); + + /* 销毁地图 */ + onUnmounted(() => { + if (mapInsLocation) { + mapInsLocation.destroy(); + } + }); + + /* 同步框架暗黑模式切换 */ + watch(darkMode, (value) => { + if (mapInsLocation) { + if (value) { + mapInsLocation.setMapStyle('amap://styles/dark'); + } else { + mapInsLocation.setMapStyle('amap://styles/normal'); + } + } + }); +</script> diff --git a/src/views-demo/extension/map/components/demo-picker.vue b/src/views-demo/extension/map/components/demo-picker.vue new file mode 100644 index 0000000..9e651e1 --- /dev/null +++ b/src/views-demo/extension/map/components/demo-picker.vue @@ -0,0 +1,63 @@ +<template> + <a-card title="地图位置选择器" :bordered="false"> + <a-space> + <div style="width: 140px"> + <a-select v-model:value="searchType" class="ele-fluid"> + <a-select-option :value="0">POI检索模式</a-select-option> + <a-select-option :value="1">关键字检索模式</a-select-option> + </a-select> + </div> + <a-button class="ele-btn-icon" @click="openMapPicker"> + 打开地图位置选择器 + </a-button> + </a-space> + <div style="margin-top: 12px">选择位置: {{ result.location }}</div> + <div style="margin-top: 12px">详细地址: {{ result.address }}</div> + <div style="margin-top: 12px">经 纬 度 : {{ result.lngAndLat }}</div> + </a-card> + <!-- 地图位置选择弹窗 --> + <ele-map-picker + :need-city="true" + :dark-mode="darkMode" + v-model:visible="visible" + :search-type="searchType" + @done="onChoose" + /> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import type { CenterPoint } from 'ele-admin-pro/es/ele-map-picker/types'; + import { useThemeStore } from '@/store/modules/theme'; + import { storeToRefs } from 'pinia'; + + const themeStore = useThemeStore(); + const { darkMode } = storeToRefs(themeStore); + + // 是否显示地图选择弹窗 + const visible = ref(false); + + // 地点检索类型 + const searchType = ref(0); + + // 选择结果 + const result = reactive({ + location: '', + address: '', + lngAndLat: '' + }); + + /* 打开位置选择 */ + const openMapPicker = () => { + visible.value = true; + }; + + /* 地图选择后回调 */ + const onChoose = (location: CenterPoint) => { + console.log(location); + result.location = `${location.city?.province}/${location.city?.city}/${location.city?.district}`; + result.address = `${location.name} ${location.address}`; + result.lngAndLat = `${location.lng},${location.lat}`; + visible.value = false; + }; +</script> diff --git a/src/views-demo/extension/map/components/demo-track.vue b/src/views-demo/extension/map/components/demo-track.vue new file mode 100644 index 0000000..661288f --- /dev/null +++ b/src/views-demo/extension/map/components/demo-track.vue @@ -0,0 +1,164 @@ +<template> + <a-card title="轨迹展示及轨迹回放" :bordered="false"> + <div + ref="trackMapRef" + style="height: 360px; max-width: 800px; margin-bottom: 16px" + > + </div> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="startTrackAnim"> + 开始移动 + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="pauseTrackAnim"> + 暂停移动 + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="resumeTrackAnim"> + 继续移动 + </a-button> + </a-space> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, watch, onMounted, onUnmounted } from 'vue'; + import AMapLoader from '@amap/amap-jsapi-loader'; + import { useThemeStore } from '@/store/modules/theme'; + import { storeToRefs } from 'pinia'; + import { MAP_KEY } from '@/config/setting'; + + const themeStore = useThemeStore(); + const { darkMode } = storeToRefs(themeStore); + + // + const trackMapRef = ref<HTMLElement | null>(null); + + // 小车轨迹地图的实例 + let mapInsTrack: any; + + // 小车的 marker + let carMarker: any; + + // 轨迹路线 + const lineData = [ + [116.478935, 39.997761], + [116.478939, 39.997825], + [116.478912, 39.998549], + [116.478912, 39.998549], + [116.478998, 39.998555], + [116.478998, 39.998555], + [116.479282, 39.99856], + [116.479658, 39.998528], + [116.480151, 39.998453], + [116.480784, 39.998302], + [116.480784, 39.998302], + [116.481149, 39.998184], + [116.481573, 39.997997], + [116.481863, 39.997846], + [116.482072, 39.997718], + [116.482362, 39.997718], + [116.483633, 39.998935], + [116.48367, 39.998968], + [116.484648, 39.999861] + ]; + + /* 渲染轨迹回放地图 */ + const renderTrackMap = () => { + AMapLoader.load({ + key: MAP_KEY, + version: '2.0', + plugins: ['AMap.MoveAnimation', 'AMap.Marker', 'AMap.Polyline'] + }) + .then((AMap) => { + // 渲染地图 + const option = { + zoom: 17, + center: [116.478935, 39.997761], + + mapStyle: darkMode.value ? 'amap://styles/dark' : void 0 + }; + mapInsTrack = new AMap.Map(trackMapRef.value, option); + // 创建小车 marker + carMarker = new AMap.Marker({ + map: mapInsTrack, + position: [116.478935, 39.997761], + icon: 'https://a.amap.com/jsapi_demos/static/demo-center-v2/car.png', + offset: new AMap.Pixel(-13, -26) + }); + // 绘制轨迹 + new AMap.Polyline({ + map: mapInsTrack, + path: lineData, + showDir: true, + strokeColor: '#2288FF', // 线颜色 + strokeOpacity: 1, // 线透明度 + strokeWeight: 6 // 线宽 + //strokeStyle: 'solid' // 线样式 + }); + // 通过的轨迹 + const passedPolyline = new AMap.Polyline({ + map: mapInsTrack, + showDir: true, + strokeColor: '#44BB55', // 线颜色 + strokeOpacity: 1, // 线透明度 + strokeWeight: 6 // 线宽 + }); + // 小车移动回调 + carMarker.on('moving', (e) => { + passedPolyline.setPath(e.passedPath); + }); + // 地图自适应 + mapInsTrack.setFitView(); + }) + .catch((e) => { + console.error(e); + }); + }; + + /* 开始轨迹回放动画 */ + const startTrackAnim = () => { + if (carMarker) { + carMarker.stopMove(); + carMarker.moveAlong(lineData, { + duration: 200, + autoRotation: true + }); + } + }; + + /* 暂停轨迹回放动画 */ + const pauseTrackAnim = () => { + if (carMarker) { + carMarker.pauseMove(); + } + }; + + /* 继续开始轨迹回放动画 */ + const resumeTrackAnim = () => { + if (carMarker) { + carMarker.resumeMove(); + } + }; + + /* 渲染地图 */ + onMounted(() => { + renderTrackMap(); + }); + + /* 销毁地图 */ + onUnmounted(() => { + if (mapInsTrack) { + mapInsTrack.destroy(); + } + }); + + /* 同步框架暗黑模式切换 */ + watch(darkMode, () => { + if (mapInsTrack) { + if (darkMode.value) { + mapInsTrack.setMapStyle('amap://styles/dark'); + } else { + mapInsTrack.setMapStyle('amap://styles/normal'); + } + } + }); +</script> diff --git a/src/views-demo/extension/map/index.vue b/src/views-demo/extension/map/index.vue new file mode 100644 index 0000000..e640c53 --- /dev/null +++ b/src/views-demo/extension/map/index.vue @@ -0,0 +1,19 @@ +<template> + <div class="ele-body ele-body-card"> + <demo-picker /> + <demo-map /> + <demo-track /> + </div> +</template> + +<script lang="ts" setup> + import DemoPicker from './components/demo-picker.vue'; + import DemoMap from './components/demo-map.vue'; + import DemoTrack from './components/demo-track.vue'; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionMap' + }; +</script> diff --git a/src/views-demo/extension/markdown/index.vue b/src/views-demo/extension/markdown/index.vue new file mode 100644 index 0000000..3f319c3 --- /dev/null +++ b/src/views-demo/extension/markdown/index.vue @@ -0,0 +1,68 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 按钮 --> + <div style="margin-bottom: 16px"> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="setContent"> + 修改内容 + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="showText"> + 获取内容 + </a-button> + </a-space> + </div> + <!-- 编辑器 --> + <byte-md-editor + v-model:value="content" + :locale="zh_Hans" + :plugins="plugins" + height="600px" + :editorConfig="{ lineNumbers: true }" + /> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { Modal } from 'ant-design-vue/es'; + import ByteMdEditor from '@/components/ByteMdEditor/index.vue'; + // 中文语言文件 + import zh_Hans from 'bytemd/locales/zh_Hans.json'; + // 链接、删除线、复选框、表格等的插件 + import gfm from '@bytemd/plugin-gfm'; + // 插件的中文语言文件 + import zh_HansGfm from '@bytemd/plugin-gfm/locales/zh_Hans.json'; + // 预览界面的样式,这里用的 github 的 markdown 主题 + import 'github-markdown-css/github-markdown-light.css'; + + // 编辑器内容,双向绑定 + const content = ref(''); + + // 插件 + const plugins = ref([ + gfm({ + locale: zh_HansGfm + }) + ]); + + /* 获取编辑器内容 */ + const showText = () => { + Modal.info({ + maskClosable: true, + content: content.value + }); + }; + + /* 修改编辑器内容 */ + const setContent = () => { + content.value = '> **EleAdminPro**后台管理模板'; + }; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionMarkdown' + }; +</script> diff --git a/src/views-demo/extension/player/index.vue b/src/views-demo/extension/player/index.vue new file mode 100644 index 0000000..22e786b --- /dev/null +++ b/src/views-demo/extension/player/index.vue @@ -0,0 +1,116 @@ +<template> + <div class="ele-body"> + <a-card title="基础演示" :bordered="false"> + <!-- 操作按钮 --> + <a-space style="margin-bottom: 16px"> + <a-button + type="primary" + :disabled="!ready" + class="ele-btn-icon" + @click="play" + > + 播放 + </a-button> + <a-button + type="primary" + :disabled="!ready" + class="ele-btn-icon" + @click="pause" + > + 暂停 + </a-button> + <a-button + type="primary" + :disabled="!ready" + class="ele-btn-icon" + @click="replay" + > + 重新播放 + </a-button> + <a-button + type="primary" + :disabled="!ready" + class="ele-btn-icon" + @click="changeSrc" + > + 切换视频源 + </a-button> + </a-space> + <!-- 播放器 --> + <ele-xg-player :config="config" @player="onPlayer" /> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import type Player from 'xgplayer'; + + // 视频播放器配置 + const config = reactive({ + id: 'demoPlayer1', + lang: 'zh-cn', + fluid: true, + // 视频地址 + url: 'https://s1.pstatp.com/cdn/expire-1-M/byted-player-videos/1.0.0/xgplayer-demo.mp4', + // 封面 + poster: + 'https://imgcache.qq.com/open_proj/proj_qcloud_v2/gateway/solution/general-video/css/img/scene/1.png', + // 开启倍速播放 + playbackRate: [0.5, 1, 1.5, 2], + // 开启画中画 + pip: true + }); + + // 视频播放器是否实例化完成 + const ready = ref(false); + + // 视频播放器实例 + let player: Player; + + /* 播放器渲染完成 */ + const onPlayer = (e: Player) => { + player = e; + player.on('play', () => { + ready.value = true; + }); + }; + + /* 播放 */ + const play = () => { + if (player && player.paused) { + player.play(); + } + }; + + /* 暂停 */ + const pause = () => { + if (player) { + player.pause(); + } + }; + + /* 重新播放 */ + const replay = () => { + if (player) { + player.replay(); + } + }; + + /* 切换视频源 */ + const changeSrc = () => { + if (player) { + player.src = + 'https://blz-videos.nosdn.127.net/1/OverWatch/AnimatedShots/Overwatch_TheatricalTeaser_WeAreOverwatch_zhCN.mp4'; + if (player.paused) { + player.play(); + } + } + }; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionPlayer' + }; +</script> diff --git a/src/views-demo/extension/printer/components/print-advanced.vue b/src/views-demo/extension/printer/components/print-advanced.vue new file mode 100644 index 0000000..d859358 --- /dev/null +++ b/src/views-demo/extension/printer/components/print-advanced.vue @@ -0,0 +1,282 @@ +<template> + <a-card title="进阶示例" :bordered="false"> + <a-space style="flex-wrap: wrap"> + <a-button class="ele-btn-icon" @click="printDataTable"> + 打印数据表格 + </a-button> + <a-tooltip title="对于复杂的打印需求,可以后端生成pdf给前端打印"> + <a-button class="ele-btn-icon" @click="printPdfUrl">打印pdf</a-button> + </a-tooltip> + <a-button class="ele-btn-icon" @click="printQrCode">打印条码</a-button> + <a-button class="ele-btn-icon" @click="printAnyTable"> + 打印自定义表格 + </a-button> + </a-space> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { printHtml, printPdf, makeTable } from 'ele-admin-pro/es'; + + interface UserType { + key: number; + username: string; + amount: number; + province: string; + city: string; + zone: string; + street: string; + address: string; + } + + const users = ref<UserType[]>([ + { + key: 1, + username: '张小三', + amount: 18, + province: '浙江', + city: '杭州', + zone: '西湖区', + street: '西溪街道', + address: '西溪花园30栋1单元' + }, + { + key: 2, + username: '李小四', + amount: 39, + province: '江苏', + city: '苏州', + zone: '姑苏区', + street: '丝绸路', + address: '天墅之城9幢2单元' + }, + { + key: 3, + username: '王小五', + amount: 8, + province: '江西', + city: '南昌', + zone: '青山湖区', + street: '艾溪湖办事处', + address: '中兴和园1幢3单元' + }, + { + key: 4, + username: '赵小六', + amount: 16, + province: '福建', + city: '泉州', + zone: '丰泽区', + street: '南洋街道', + address: '南洋村6幢1单元' + }, + { + key: 5, + username: '孙小七', + amount: 12, + province: '湖北', + city: '武汉', + zone: '武昌区', + street: '武昌大道', + address: '两湖花园16幢2单元' + }, + { + key: 6, + username: '周小八', + amount: 11, + province: '安徽', + city: '黄山', + zone: '黄山区', + street: '汤口镇', + address: '温泉村21号' + } + ]); + + /* 打印数据表格 */ + const printDataTable = () => { + const html = makeTable(users.value, [ + [ + { + field: 'username', + width: 150, + rowspan: 2, + title: '联系人' + }, + { + align: 'center', + colspan: 3, + title: '地址' + }, + { + field: 'amount', + width: 120, + rowspan: 2, + title: '金额', + align: 'center' + } + ], + [ + { + field: 'province', + width: 120, + title: '省' + }, + { + field: 'city', + width: 120, + title: '市' + }, + { + width: 200, + title: '区', + templet: (d) => { + return `<span style="color: red;">${d.zone}</span>`; + } + } + ] + ]); + printHtml({ + html: '<p>提供数据和cols配置自动生成复杂表格,非常的方便</p>' + html, + loading: false + }); + }; + + /* 打印 pdf */ + const printPdfUrl = () => { + printPdf({ + url: 'https://cdn.eleadmin.com/20200610/20200708224450.pdf' + }); + }; + + /* 打印条码 */ + const printQrCode = () => { + const html = ` + <div class="code-group"> + <div class="code-group-title">EasyWeb授权凭证</div> + <div class="code-group-body"> + <p>手机扫描右侧二维码,或登录</p> + <p>网站https://easyweb.vip</p> + <p>查询产品真伪</p> + <img src="https://cdn.eleadmin.com/20200610/20200708230820.png" width="70px" height="70px"/> + <span>515AE3X1</span> + </div> + </div> + <style> + .code-group { + display: inline-block; + border: 1px solid #ccc; + border-radius: 5px; + background-color: #fff; + } + .code-group-title { + border-bottom: 1px solid #ccc; + padding: 10px 15px; + text-align: center; + font-size: 18px; + } + .code-group-body { + text-align: center; + position: relative; + padding: 15px 115px 0 25px; + min-height: 90px; + } + .code-group-body > p { + margin: 0 0 13px 0; + font-size: 15px; + font-family: 幼圆; + color: #333; + font-weight: 600; + } + .code-group-body > img, .code-group-body > span { + position: absolute; + right: 25px; + top: 15px; + } + .code-group-body > span { + top: 90px; + } + </style> + `; + printHtml({ + html: html, + loading: false + }); + }; + + /* 打印自定义表格 */ + const printAnyTable = () => { + const html = ` + <h2 style="text-align: center;color: #333;">XxxXx班课程表</h2> + <table class="ele-printer-table"> + <colgroup> + <col width="130px"/> + </colgroup> + <tr> + <th style="position: relative;"> + <div style="position: absolute;right: 20px;top: 10px;line-height: normal;">星期</div> + <div style="position: absolute;left: 20px;bottom: 10px;line-height: normal;">时间</div> + <div + style="border-top: 1px solid #000;width:141px;height: 0;position: absolute;left: 0;top: 0;transform: rotate(22deg);transform-origin: 0 0;"> + </div> + </th> + <th>周一</th> + <th>周二</th> + <th>周三</th> + <th>周四</th> + <th>周五</th> + </tr> + <tr> + <td>8:00-10:00</td> + <td>HTML5网页设计<br/>曲丽丽 - 441教室</td> + <td>数据库原理及应用<br/>严良 - 716机房</td> + <td>JavaSE初级程序设计<br/>肖萧 - 715机房</td> + <td></td> + <td>JavaScript程序设计<br/>董娜 - 733机房</td> + </tr> + <tr> + <td>10:30-12:30</td> + <td></td> + <td>JavaScript程序设计<br/>董娜 - 733机房</td> + <td></td> + <td>锋利的jQuery<br/>程咏 - 303教室</td> + <td>JavaEE应用开发<br/>周星 - 303教室</td> + </tr> + <tr> + <td colspan="6" style="height: auto;">午休</td> + </tr> + <tr> + <td>13:30-15:30</td> + <td>JavaSE初级程序设计<br/>肖萧 - 715机房</td> + <td></td> + <td>HTML5网页设计<br/>曲丽丽 - 441教室</td> + <td></td> + <td></td> + </tr> + <tr> + <td>16:00-18:00</td> + <td></td> + <td>JavaEE应用开发<br/>周星 - 303教室</td> + <td></td> + <td>数据库原理及应用<br/>严良 - 716机房</td> + <td></td> + </tr> + </table> + <style> + th, td { + text-align: center; + line-height: 35px; + } + td { + height: 100px; + } + </style> + `; + printHtml({ + html: html, + horizontal: true, + title: '.', + loading: false + }); + }; +</script> diff --git a/src/views-demo/extension/printer/components/print-div.vue b/src/views-demo/extension/printer/components/print-div.vue new file mode 100644 index 0000000..b70cdfa --- /dev/null +++ b/src/views-demo/extension/printer/components/print-div.vue @@ -0,0 +1,64 @@ +<template> + <a-card title="打印指定区域" :bordered="false"> + <div ref="printRef" class="demo-print-group"> + <div class="demo-print-div">示例示例示例示例示例</div> + <div class="demo-print-right"> + <div> + <ele-tag size="mini" color="blue">示例</ele-tag> + <ele-tag size="mini" color="green">示例</ele-tag> + <ele-tag size="mini" color="orange">示例</ele-tag> + </div> + <div style="margin-top: 12px"> + <a-input v-model:value="value" /> + </div> + </div> + </div> + <div style="margin-top: 20px"> + <a-button @click="print">打印</a-button> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { printElement } from 'ele-admin-pro/es'; + + const printRef = ref<HTMLElement | null>(null); + + const print = () => { + printElement(printRef.value as HTMLElement); + }; + + const value = ref('示例示例示例'); +</script> + +<style lang="less" scoped> + .demo-print-group { + display: flex; + align-items: center; + } + + .demo-print-div { + background: #096dd9; + color: #fff; + font-size: 18px; + text-align: center; + padding: 40px 0; + flex: 1; + border: 2px solid #096dd9; + height: 120px; + box-sizing: border-box; + border-top-left-radius: 8px; + border-bottom-left-radius: 8px; + } + + .demo-print-right { + flex: 1; + padding: 20px; + border: 2px solid #096dd9; + height: 120px; + box-sizing: border-box; + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; + } +</style> diff --git a/src/views-demo/extension/printer/components/print-html.vue b/src/views-demo/extension/printer/components/print-html.vue new file mode 100644 index 0000000..d901d89 --- /dev/null +++ b/src/views-demo/extension/printer/components/print-html.vue @@ -0,0 +1,81 @@ +<template> + <a-card title="打印任意内容" :bordered="false"> + <a-form + :label-col="{ span: 6 }" + :wrapper-col="{ span: 18 }" + style="max-width: 320px" + > + <a-form-item label="loading"> + <a-radio-group v-model:value="option.loading"> + <a-radio :value="false">不显示</a-radio> + <a-radio :value="true">显示</a-radio> + </a-radio-group> + </a-form-item> + </a-form> + <a-space style="flex-wrap: wrap"> + <a-button class="ele-btn-icon" @click="printAnyHtml"> + 打印任意内容 + </a-button> + <a-button class="ele-btn-icon" @click="printAddHeader"> + 设置页眉页脚 + </a-button> + <a-button class="ele-btn-icon" @click="printImage">打印图片</a-button> + </a-space> + </a-card> +</template> + +<script lang="ts" setup> + import { reactive } from 'vue'; + import { printHtml } from 'ele-admin-pro/es'; + + // 打印任意内容参数 + const option = reactive({ + loading: false + }); + + /* 打印任意内容 */ + const printAnyHtml = () => { + printHtml({ + ...option, + html: [ + '<h1 style="color: #1890ff;">EleAdmin 后台管理模板</h1>', + '<div style="color: #F51D2C;">通用型后台管理模板,界面美观、开箱即用</div>' + ].join('') + }); + }; + + /* 打印设置页眉页脚 */ + const printAddHeader = () => { + printHtml({ + ...option, + margin: 0, + html: [ + '<div style="padding: 0 60px;">', + Array(38).join('<h3>EleAdmin 后台管理模板</h3>'), + '</div>' + ].join(''), + header: ` + <div style="display: flex;font-size: 12px;padding: 15px 30px 25px;"> + <div>我是页眉左侧</div> + <div style="flex: 1;text-align: center;">我是页眉</div> + <div>我是页眉右侧</div> + </div>`, + footer: ` + <div style="display: flex;font-size: 12px;padding: 15px 30px 25px;"> + <div>我是页脚左侧</div> + <div style="flex: 1;text-align: center;">我是页脚</div> + <div>我是页脚右侧</div> + </div>`, + style: '<style> h3 { color: red; } </style>' + }); + }; + + /* 打印图片 */ + const printImage = () => { + printHtml({ + ...option, + margin: 0, + html: '<img src="https://cdn.eleadmin.com/20200610/LrCTN2j94lo9N7wEql7cBr1Ux4rHMvmZ.jpg" style="width: 100%;"/>' + }); + }; +</script> diff --git a/src/views-demo/extension/printer/components/print-page.vue b/src/views-demo/extension/printer/components/print-page.vue new file mode 100644 index 0000000..612b5ea --- /dev/null +++ b/src/views-demo/extension/printer/components/print-page.vue @@ -0,0 +1,58 @@ +<template> + <a-card title="分页打印" :bordered="false"> + <a-space> + <a-button class="ele-btn-icon" @click="printAnyPage">分页打印</a-button> + <a-button class="ele-btn-icon" @click="printPageAddHeader"> + 分页打印设置页眉页脚 + </a-button> + </a-space> + </a-card> +</template> + +<script lang="ts" setup> + import { printPage } from 'ele-admin-pro/es'; + + /* 分页打印 */ + const printAnyPage = () => { + printPage({ + pages: [ + '<h3>我是第一页</h3>', + '<h3>我是第二页</h3>', + '<h3>我是第三页</h3>', + '<h3>我是第四页</h3>', + '<h3>我是第五页</h3>' + ], + style: '<style> h3 { color: red; } </style>', + loading: false + }); + }; + + /* 分页打印设置页眉页脚 */ + const printPageAddHeader = () => { + printPage({ + pages: [ + '<h3>我是第一页</h3>', + '<h3>我是第二页</h3>', + '<h3>我是第三页</h3>', + '<h3>我是第四页</h3>', + '<h3>我是第五页</h3>' + ], + margin: 0, + padding: '20px 60px', + header: ` + <div style="display: flex;font-size: 12px;padding: 15px 30px;"> + <div>我是页眉左侧</div> + <div style="flex: 1;text-align: center;">我是页眉</div> + <div>我是页眉右侧</div> + </div>`, + footer: ` + <div style="display: flex;font-size: 12px;padding: 15px 30px;"> + <div>我是页脚左侧</div> + <div style="flex: 1;text-align: center;">我是页脚</div> + <div>我是页脚右侧</div> + </div>`, + style: '<style> h3 { color: red; } </style>', + loading: false + }); + }; +</script> diff --git a/src/views-demo/extension/printer/components/print-this.vue b/src/views-demo/extension/printer/components/print-this.vue new file mode 100644 index 0000000..9d0fc37 --- /dev/null +++ b/src/views-demo/extension/printer/components/print-this.vue @@ -0,0 +1,89 @@ +<template> + <a-card title="打印当前页面" :bordered="false"> + <a-form + :label-col="styleResponsive ? { span: 6 } : { flex: '80px' }" + :wrapper-col="styleResponsive ? { span: 18 } : { flex: '1' }" + style="max-width: 320px" + > + <a-form-item label="纸张方向"> + <a-select + allow-clear + :value="{ true: 1, false: 0 }[String(option.horizontal)]" + placeholder="不设置" + @update:value="updateHorizontal" + > + <a-select-option :value="1">横向</a-select-option> + <a-select-option :value="0">纵向</a-select-option> + </a-select> + </a-form-item> + <a-form-item label="页面间距"> + <a-select + allow-clear + v-model:value="option.margin" + placeholder="不设置" + > + <a-select-option value="0px">0px</a-select-option> + <a-select-option value="50px">50px</a-select-option> + <a-select-option value="100px">100px</a-select-option> + </a-select> + </a-form-item> + <a-form-item label="页面标题"> + <a-input + allow-clear + v-model:value="option.title" + placeholder="不设置" + /> + </a-form-item> + </a-form> + <a-space> + <a-button @click="print">打印</a-button> + <a-button class="ele-btn-icon" @click="printHide"> + 打印隐藏指定内容 + </a-button> + </a-space> + <div style="margin-top: 16px"> + <span class="ele-text-primary ele-printer-hide"> + 此段内容在所有打印时隐藏, 打印完复原。 + </span> + <span class="ele-text-danger demo-hide-1"> + 此段内容在指定打印时才隐藏。 + </span> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { reactive } from 'vue'; + import { printThis } from 'ele-admin-pro/es'; + import type { PrintHtmlOption } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // 打印当前页面参数 + const option: PrintHtmlOption = reactive({ + horizontal: undefined, + margin: undefined, + title: '' + }); + + /* 打印当前页面 */ + const print = () => { + printThis(option); + }; + + /* 打印隐藏指定内容 */ + const printHide = () => { + printThis({ + ...option, + hide: ['.demo-hide-1'] + }); + }; + + const updateHorizontal = (value?: number) => { + option.horizontal = { '1': true, '0': false }[String(value)]; + }; +</script> diff --git a/src/views-demo/extension/printer/index.vue b/src/views-demo/extension/printer/index.vue new file mode 100644 index 0000000..5fa07ba --- /dev/null +++ b/src/views-demo/extension/printer/index.vue @@ -0,0 +1,23 @@ +<template> + <div class="ele-body ele-body-card"> + <print-this /> + <print-div /> + <print-html /> + <print-page /> + <print-advanced /> + </div> +</template> + +<script lang="ts" setup> + import PrintThis from './components/print-this.vue'; + import PrintDiv from './components/print-div.vue'; + import PrintHtml from './components/print-html.vue'; + import PrintPage from './components/print-page.vue'; + import PrintAdvanced from './components/print-advanced.vue'; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionPrinter' + }; +</script> diff --git a/src/views-demo/extension/qr-code/index.vue b/src/views-demo/extension/qr-code/index.vue new file mode 100644 index 0000000..1ae7062 --- /dev/null +++ b/src/views-demo/extension/qr-code/index.vue @@ -0,0 +1,211 @@ +<template> + <div class="ele-body"> + <a-card title="二维码" :bordered="false"> + <div ref="printRef" class="demo-qrcode-images ele-bg-white"> + <div class="demo-qrcode-image-item"> + <div class="demo-qr-code-title">canvas 渲染</div> + <ele-qr-code + :value="text" + :size="size" + :level="level" + :margin="margin" + :image-settings="image" + /> + </div> + <div class="demo-qrcode-image-item"> + <div class="demo-qr-code-title">svg 渲染</div> + <ele-qr-code-svg + :value="text" + :size="size" + :level="level" + :margin="margin" + :image-settings="image" + /> + </div> + </div> + <a-form + style="max-width: 340px" + :label-col="{ flex: '88px' }" + :wrapper-col="{ flex: '1' }" + > + <a-form-item label="二维码内容" style="flex-wrap: nowrap"> + <a-input v-model:value="text" :maxlength="150" /> + </a-form-item> + <a-form-item label="容错等级" style="flex-wrap: nowrap"> + <a-select v-model:value="level"> + <a-select-option value="L">L</a-select-option> + <a-select-option value="M">M</a-select-option> + <a-select-option value="Q">Q</a-select-option> + <a-select-option value="H">H</a-select-option> + </a-select> + </a-form-item> + <a-form-item label="尺寸" style="flex-wrap: nowrap"> + <a-slider v-model:value="size" :min="80" :max="280" :step="40" /> + </a-form-item> + <a-form-item label="间距" style="flex-wrap: nowrap"> + <a-slider v-model:value="margin" :min="0" :max="20" /> + </a-form-item> + <a-form-item label="自定义图片" style="flex-wrap: nowrap"> + <a-switch + v-model:checked="showImage" + size="small" + @change="onShowImageChange" + /> + </a-form-item> + <template v-if="showImage"> + <a-form-item label="图片地址" style="flex-wrap: nowrap"> + <a-input v-model:value="image.src" :maxlength="400" /> + </a-form-item> + <a-form-item label="图片宽高" style="flex-wrap: nowrap"> + <div class="ele-cell"> + <div style="width: 80px; margin-right: 20px"> + <a-input-number + v-model:value="image.width" + size="small" + :min="0" + :max="size" + class="ele-fluid" + /> + </div> + <div style="width: 80px"> + <a-input-number + v-model:value="image.height" + size="small" + :min="0" + :max="size" + class="ele-fluid" + /> + </div> + </div> + </a-form-item> + <a-form-item label="位置居中" style="flex-wrap: nowrap"> + <div class="ele-cell"> + <a-switch + v-model:checked="center" + size="small" + @change="onCenterChange" + /> + <template v-if="!center"> + <div style="padding: 0 10px">x</div> + <div style="width: 60px"> + <a-input-number + v-model:value="image.x" + size="small" + :min="0" + :max="size" + class="ele-fluid" + /> + </div> + <div style="padding: 0 10px">y</div> + <div style="width: 60px"> + <a-input-number + v-model:value="image.y" + size="small" + :min="0" + :max="size" + class="ele-fluid" + /> + </div> + </template> + </div> + </a-form-item> + <a-form-item label="背景擦除" style="flex-wrap: nowrap"> + <a-switch v-model:checked="image.excavate" size="small" /> + </a-form-item> + <a-form-item style="flex-wrap: nowrap"> + <div style="padding-left: 88px"> + <a-button type="primary" @click="print">打印</a-button> + </div> + </a-form-item> + </template> + </a-form> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { printElement } from 'ele-admin-pro/es'; + import type { + LevelType, + ImageSettings + } from 'ele-admin-pro/es/ele-qr-code/types'; + const IMAGE_SRC = 'https://cdn.eleadmin.com/20200610/logo-radius.png'; + + const text = ref('https://eleadmin.com'); + + const level = ref<LevelType>('L'); + + const size = ref(120); + + const margin = ref(0); + + const showImage = ref(true); + + const image = reactive<ImageSettings>({ + src: IMAGE_SRC, + width: 28, + height: 28, + x: undefined, + y: undefined, + excavate: false + }); + + const center = ref(true); + + const printRef = ref<HTMLElement | null>(null); + + const onShowImageChange = (checked: boolean) => { + if (checked) { + image.src = IMAGE_SRC; + } else { + image.src = undefined; + } + }; + + const onCenterChange = (checked: boolean) => { + if (checked) { + image.x = undefined; + image.y = undefined; + } else { + image.x = 0; + image.y = 0; + } + }; + + const print = () => { + printElement(printRef.value as HTMLElement); + }; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionQrCode' + }; +</script> + +<style lang="less" scoped> + .demo-qrcode-images { + display: flex; + padding-bottom: 16px; + margin-bottom: 4px; + position: sticky; + top: 0; + overflow: auto; + z-index: 1; + + .demo-qrcode-image-item { + padding: 0 20px; + + .demo-qr-code-title { + margin-bottom: 6px; + } + } + } + + @media screen and (max-width: 768px) { + .demo-qrcode-images .demo-qrcode-image-item { + padding: 0 10px; + } + } +</style> diff --git a/src/views-demo/extension/regions/index.vue b/src/views-demo/extension/regions/index.vue new file mode 100644 index 0000000..977d8db --- /dev/null +++ b/src/views-demo/extension/regions/index.vue @@ -0,0 +1,53 @@ +<template> + <div class="ele-body ele-body-card"> + <a-card title="省市区级联选择" :bordered="false"> + <div style="max-width: 280px"> + <regions-select + v-model:value="city" + placeholder="请选择省市区" + class="ele-fluid" + /> + </div> + </a-card> + <a-card title="省市级联选择" :bordered="false"> + <div style="max-width: 280px"> + <regions-select + v-model:value="provinceCity" + placeholder="请选择省市" + type="provinceCity" + class="ele-fluid" + /> + </div> + </a-card> + <a-card title="省选择" :bordered="false"> + <div style="max-width: 280px"> + <regions-select + v-model:value="province" + placeholder="请选择省" + type="province" + class="ele-fluid" + /> + </div> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import RegionsSelect from '@/components/RegionsSelect/index.vue'; + + // 选中的省市区 + const city = ref<string[]>([]); + + // 选中的省市 + const provinceCity = ref<string[]>([]); + + // 选中的省 + const province = ref<string[]>([]); +</script> + +<script lang="ts"> + export default { + name: 'ExtensionRegions' + }; +</script> diff --git a/src/views-demo/extension/split/index.vue b/src/views-demo/extension/split/index.vue new file mode 100644 index 0000000..5d81261 --- /dev/null +++ b/src/views-demo/extension/split/index.vue @@ -0,0 +1,195 @@ +<template> + <div class="ele-body ele-body-card"> + <a-card title="基本用法" :bordered="false"> + <div class="option-item"> + <div>显示折叠按钮:</div> + <div class="option-item-body"> + <a-radio-group + :options="[ + { label: '是', value: true }, + { label: '否', value: false } + ]" + v-model:value="allowCollapse" + /> + </div> + </div> + <div class="option-item"> + <div>支持自由拉伸:</div> + <div class="option-item-body"> + <a-radio-group + :options="[ + { label: '是', value: true }, + { label: '否', value: false } + ]" + v-model:value="resizable" + /> + </div> + </div> + <div class="option-item"> + <div>上下布局模式:</div> + <div class="option-item-body"> + <a-radio-group + :options="[ + { label: '是', value: true }, + { label: '否', value: false } + ]" + v-model:value="vertical" + /> + </div> + </div> + <div class="option-item"> + <div>反转布局方向:</div> + <div class="option-item-body"> + <a-radio-group + :options="[ + { label: '是', value: true }, + { label: '否', value: false } + ]" + v-model:value="reverse" + /> + </div> + </div> + <ele-split-layout + space="0px" + :allow-collapse="allowCollapse" + :resizable="resizable" + :vertical="vertical" + :reverse="reverse" + :min-size="40" + :left-style="{ + background: 'rgba(185, 182, 229, .4)', + overflow: 'hidden' + }" + :right-style="{ + background: 'rgba(125, 226, 252, .4)', + overflow: 'hidden' + }" + style="height: 480px; margin-top: 12px" + > + <div>边栏</div> + <template #content> + <div>内容</div> + </template> + </ele-split-layout> + </a-card> + <a-card title="组合使用" :bordered="false"> + <div style="margin: 0 0 8px 0">先左右再上下</div> + <ele-split-layout + space="0px" + :resizable="true" + :min-size="40" + :max-size="-40" + :left-style="{ + background: 'rgba(185, 182, 229, .4)', + overflow: 'hidden' + }" + :right-style="{ overflow: 'hidden' }" + :responsive="false" + style="height: 400px" + > + <div>边栏</div> + <template #content> + <ele-split-layout + space="0px" + width="240px" + :min-size="40" + :max-size="-40" + :resizable="true" + :vertical="true" + :left-style="{ + background: 'rgba(171, 199, 255, .5)', + overflow: 'hidden' + }" + :right-style="{ + background: 'rgba(125, 226, 252, .4)', + overflow: 'hidden' + }" + :responsive="false" + style="height: 400px" + > + <div>内容一</div> + <template #content> + <div>内容二</div> + </template> + </ele-split-layout> + </template> + </ele-split-layout> + <div style="margin: 16px 0 8px 0">先上下再左右</div> + <ele-split-layout + space="0px" + width="120px" + :min-size="40" + :max-size="-40" + :vertical="true" + :resizable="true" + :left-style="{ + background: 'rgba(185, 182, 229, .4)', + overflow: 'hidden' + }" + :right-style="{ overflow: 'hidden' }" + :responsive="false" + style="height: 400px" + > + <div>顶栏</div> + <template #content> + <ele-split-layout + space="0px" + :min-size="40" + :max-size="-40" + :resizable="true" + :left-style="{ + background: 'rgba(171, 199, 255, .5)', + overflow: 'hidden' + }" + :right-style="{ + background: 'rgba(125, 226, 252, .4)', + overflow: 'hidden' + }" + :responsive="false" + style="height: 100%" + > + <div>边栏</div> + <template #content> + <div>内容</div> + </template> + </ele-split-layout> + </template> + </ele-split-layout> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + + const allowCollapse = ref(true); + + const resizable = ref(false); + + const vertical = ref(false); + + const reverse = ref(false); +</script> + +<script lang="ts"> + export default { + name: 'ExtensionSplit' + }; +</script> + +<style lang="less" scoped> + .option-item { + display: flex; + align-items: center; + + .option-item-body { + flex: 1; + padding-left: 12px; + display: flex; + } + + & + .option-item { + margin-top: 6px; + } + } +</style> diff --git a/src/views-demo/extension/table-select/components/demo-advanced-search.vue b/src/views-demo/extension/table-select/components/demo-advanced-search.vue new file mode 100644 index 0000000..1ca1b72 --- /dev/null +++ b/src/views-demo/extension/table-select/components/demo-advanced-search.vue @@ -0,0 +1,35 @@ +<template> + <div style="max-width: 160px"> + <a-input + allow-clear + size="small" + v-model:value="where.keywords" + placeholder="输入关键字搜索" + @change="search" + > + <template #prefix> + <search-outlined class="ele-text-placeholder" /> + </template> + </a-input> + </div> +</template> + +<script lang="ts" setup> + import { reactive } from 'vue'; + import { SearchOutlined } from '@ant-design/icons-vue'; + import type { WhereType } from '../types'; + + const emit = defineEmits<{ + (e: 'search', where: WhereType): void; + }>(); + + // 搜索表单 + const where = reactive<WhereType>({ + keywords: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', where); + }; +</script> diff --git a/src/views-demo/extension/table-select/components/demo-advanced.vue b/src/views-demo/extension/table-select/components/demo-advanced.vue new file mode 100644 index 0000000..e7e3de1 --- /dev/null +++ b/src/views-demo/extension/table-select/components/demo-advanced.vue @@ -0,0 +1,128 @@ +<template> + <a-card title="可搜索" :bordered="false"> + <div style="max-width: 260px"> + <ele-table-select + ref="selectRef" + :multiple="true" + :allow-clear="true" + placeholder="请选择" + value-key="userId" + label-key="nickname" + v-model:value="selectedValue" + :table-config="tableConfig" + :overlay-style="{ width: '520px', maxWidth: '80%' }" + :init-value="initValue" + > + <!-- 角色列 --> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'roles'"> + <a-tag v-for="item in record.roles" :key="item.roleId" color="blue"> + {{ item.roleName }} + </a-tag> + </template> + </template> + <!-- 表头工具栏 --> + <template #toolbar> + <demo-advanced-search @search="search" /> + </template> + </ele-table-select> + </div> + <div style="margin-top: 12px"> + <a-button type="primary" @click="setInitValue">回显数据</a-button> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import DemoAdvancedSearch from './demo-advanced-search.vue'; + import { pageUsers } from '@/api/system/user'; + import type { EleTableSelect } from 'ele-admin-pro/es'; + import type { ProTableProps } from 'ele-admin-pro/es/ele-pro-table/types'; + import type { User } from '@/api/system/user/model'; + import type { WhereType } from '../types'; + + const selectedValue = ref<number[]>([]); + + // 选择框实例 + const selectRef = ref<InstanceType<typeof EleTableSelect> | null>(null); + + // 表格配置 + const tableConfig = reactive<ProTableProps>({ + datasource: ({ page, limit, where, orders }) => { + return pageUsers({ ...where, ...orders, page, limit }); + }, + columns: [ + { + title: '用户账号', + dataIndex: 'username', + sorter: true, + showSorterTooltip: false + }, + { + title: '用户名', + key: 'nickname', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false + }, + { + title: '性别', + dataIndex: 'sexName', + width: 80, + align: 'center', + sorter: true, + showSorterTooltip: false + }, + { + title: '角色', + key: 'roles' + } + ], + toolkit: ['reload', 'columns'], + pageSize: 5, + pageSizeOptions: ['5', '10', '15', '20'], + size: 'small', + rowSelection: { + columnWidth: 38, + preserveSelectedRowKeys: true, + fixed: 'left' + }, + toolsTheme: 'default', + bordered: true, + toolStyle: { + padding: '0 8px' + }, + scroll: { x: 480 } + }); + + // 回显值 + const initValue = ref<User[]>(); + + /* 回显数据 */ + const setInitValue = () => { + //selectedValue.value = [14, 18, 19]; + initValue.value = [ + { + userId: 14, + nickname: '管理员' + }, + { + userId: 18, + nickname: '用户四' + }, + { + userId: 19, + nickname: '用户五' + } + ]; + }; + + // 搜索 + const search = (where: WhereType) => { + selectRef.value?.reload({ + where, + page: 1 + }); + }; +</script> diff --git a/src/views-demo/extension/table-select/components/demo-basic-page.vue b/src/views-demo/extension/table-select/components/demo-basic-page.vue new file mode 100644 index 0000000..2646d28 --- /dev/null +++ b/src/views-demo/extension/table-select/components/demo-basic-page.vue @@ -0,0 +1,110 @@ +<template> + <a-card title="表格后端分页" :bordered="false"> + <div style="max-width: 260px"> + <ele-table-select + :allow-clear="true" + placeholder="请选择" + value-key="userId" + label-key="nickname" + v-model:value="selectedValue" + :table-config="tableConfig" + :overlay-style="{ width: '520px', maxWidth: '80%' }" + :disabled="disabled" + :init-value="initValue" + @select="onSelect" + > + <!-- 角色列 --> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'roles'"> + <a-tag v-for="item in record.roles" :key="item.roleId" color="blue"> + {{ item.roleName }} + </a-tag> + </template> + </template> + </ele-table-select> + </div> + <div class="ele-cell" style="margin-top: 15px"> + <div style="line-height: 22px"> 禁用:</div> + <div class="ele-cell-content"> + <a-radio-group v-model:value="disabled" name="disabled"> + <a-radio :value="false">否</a-radio> + <a-radio :value="true">是</a-radio> + </a-radio-group> + </div> + </div> + <div style="margin-top: 12px"> + <a-button type="primary" @click="setInitValue">回显数据</a-button> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { pageUsers } from '@/api/system/user'; + import type { ProTableProps } from 'ele-admin-pro/es/ele-pro-table/types'; + import type { User } from '@/api/system/user/model'; + + const selectedValue = ref<number>(); + + // 表格配置 + const tableConfig = reactive<ProTableProps>({ + datasource: ({ page, limit, where, orders }) => { + return pageUsers({ ...where, ...orders, page, limit }); + }, + columns: [ + { + title: '用户账号', + dataIndex: 'username', + sorter: true, + showSorterTooltip: false + }, + { + title: '用户名', + key: 'nickname', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false + }, + { + title: '性别', + dataIndex: 'sexName', + width: 80, + sorter: true, + showSorterTooltip: false + }, + { + title: '角色', + key: 'roles' + } + ], + toolbar: false, + pageSize: 5, + pageSizeOptions: ['5', '10', '15', '20'], + size: 'small', + rowSelection: { + columnWidth: 38, + fixed: 'left' + }, + scroll: { x: 480 } + }); + + // 禁用 + const disabled = ref(false); + + // 回显值 + const initValue = ref<User>(); + + /* 回显数据 */ + const setInitValue = () => { + //selectedValue.value = 14; + initValue.value = { + userId: 14, + nickname: '管理员' + }; + }; + + /* 选中事件 */ + const onSelect = (item: User) => { + console.log('item:', item); + }; +</script> diff --git a/src/views-demo/extension/table-select/components/demo-basic.vue b/src/views-demo/extension/table-select/components/demo-basic.vue new file mode 100644 index 0000000..1b15597 --- /dev/null +++ b/src/views-demo/extension/table-select/components/demo-basic.vue @@ -0,0 +1,84 @@ +<template> + <a-card title="基础用法" :bordered="false"> + <div style="max-width: 260px"> + <ele-table-select + :allow-clear="true" + placeholder="请选择" + value-key="userId" + label-key="nickname" + v-model:value="selectedValue" + :table-config="tableConfig" + :overlay-style="{ width: '520px', maxWidth: '80%' }" + > + <!-- 角色列 --> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'roles'"> + <a-tag v-for="item in record.roles" :key="item.roleId" color="blue"> + {{ item.roleName }} + </a-tag> + </template> + </template> + </ele-table-select> + </div> + <div style="margin-top: 12px"> + <a-button type="primary" @click="setInitValue">回显数据</a-button> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { listUsers } from '@/api/system/user'; + import type { ProTableProps } from 'ele-admin-pro/es/ele-pro-table/types'; + + const selectedValue = ref<number>(); + + // 表格配置 + const tableConfig = reactive<ProTableProps>({ + datasource: [], + columns: [ + { + title: '用户账号', + dataIndex: 'username', + sorter: true, + showSorterTooltip: false + }, + { + title: '用户名', + key: 'nickname', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false + }, + { + title: '性别', + dataIndex: 'sexName', + width: 80, + sorter: true, + showSorterTooltip: false + }, + { + title: '角色', + key: 'roles' + } + ], + toolbar: false, + pageSize: 5, + pageSizeOptions: ['5', '10', '15', '20'], + size: 'small', + rowSelection: { + columnWidth: 38, + fixed: 'left' + }, + scroll: { x: 480 } + }); + + listUsers().then((data) => { + tableConfig.datasource = data; + }); + + /* 回显数据 */ + const setInitValue = () => { + selectedValue.value = 14; + }; +</script> diff --git a/src/views-demo/extension/table-select/components/demo-multiple.vue b/src/views-demo/extension/table-select/components/demo-multiple.vue new file mode 100644 index 0000000..0c5c600 --- /dev/null +++ b/src/views-demo/extension/table-select/components/demo-multiple.vue @@ -0,0 +1,102 @@ +<template> + <a-card title="多选" :bordered="false"> + <div style="max-width: 260px"> + <ele-table-select + :multiple="true" + :allow-clear="true" + placeholder="请选择" + value-key="userId" + label-key="nickname" + v-model:value="selectedValue" + :table-config="tableConfig" + :overlay-style="{ width: '520px', maxWidth: '80%' }" + :disabled="disabled" + :max-tag-text-length="3" + :max-tag-count="5" + > + <!-- 角色列 --> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'roles'"> + <a-tag v-for="item in record.roles" :key="item.roleId" color="blue"> + {{ item.roleName }} + </a-tag> + </template> + </template> + </ele-table-select> + </div> + <div class="ele-cell" style="margin-top: 15px"> + <div style="line-height: 22px"> 禁用:</div> + <div class="ele-cell-content"> + <a-radio-group v-model:value="disabled" name="disabled"> + <a-radio :value="false">否</a-radio> + <a-radio :value="true">是</a-radio> + </a-radio-group> + </div> + </div> + <div style="margin-top: 12px"> + <a-button type="primary" @click="setInitValue">回显数据</a-button> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { listUsers } from '@/api/system/user'; + import type { ProTableProps } from 'ele-admin-pro/es/ele-pro-table/types'; + + const selectedValue = ref<number[]>([]); + + // 表格配置 + const tableConfig = reactive<ProTableProps>({ + datasource: [], + columns: [ + { + title: '用户账号', + dataIndex: 'username', + sorter: true, + showSorterTooltip: false + }, + { + title: '用户名', + key: 'nickname', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false + }, + { + title: '性别', + dataIndex: 'sexName', + width: 80, + align: 'center', + sorter: true, + showSorterTooltip: false + }, + { + title: '角色', + key: 'roles' + } + ], + toolbar: false, + pageSize: 5, + pageSizeOptions: ['5', '10', '15', '20'], + size: 'small', + rowSelection: { + columnWidth: 38, + preserveSelectedRowKeys: true, + fixed: 'left' + }, + scroll: { x: 480 } + }); + + // 禁用 + const disabled = ref(false); + + listUsers().then((data) => { + tableConfig.datasource = data; + }); + + /* 回显数据 */ + const setInitValue = () => { + selectedValue.value = [14, 18, 19]; + }; +</script> diff --git a/src/views-demo/extension/table-select/index.vue b/src/views-demo/extension/table-select/index.vue new file mode 100644 index 0000000..1857669 --- /dev/null +++ b/src/views-demo/extension/table-select/index.vue @@ -0,0 +1,21 @@ +<template> + <div class="ele-body ele-body-card"> + <demo-basic /> + <demo-basic-page /> + <demo-multiple /> + <demo-advanced /> + </div> +</template> + +<script lang="ts" setup> + import DemoBasic from './components/demo-basic.vue'; + import DemoBasicPage from './components/demo-basic-page.vue'; + import DemoMultiple from './components/demo-multiple.vue'; + import DemoAdvanced from './components/demo-advanced.vue'; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionTableSelect' + }; +</script> diff --git a/src/views-demo/extension/table-select/types/index.ts b/src/views-demo/extension/table-select/types/index.ts new file mode 100644 index 0000000..fe57925 --- /dev/null +++ b/src/views-demo/extension/table-select/types/index.ts @@ -0,0 +1,6 @@ +/** + * 搜索表单类型 + */ +export interface WhereType { + keywords?: string; +} diff --git a/src/views-demo/extension/tag/index.vue b/src/views-demo/extension/tag/index.vue new file mode 100644 index 0000000..8a3b99e --- /dev/null +++ b/src/views-demo/extension/tag/index.vue @@ -0,0 +1,131 @@ +<template> + <div class="ele-body ele-body-card"> + <a-card title="标签组件" :bordered="false"> + <div class="ele-cell"> + <div>预设颜色:</div> + <div class="ele-cell-content"> + <ele-tag + v-for="(item, index) in list" + :key="item" + :size="size" + :color="colors[type][index]" + > + 标签{{ item }} + </ele-tag> + </div> + </div> + <div class="ele-cell"> + <div>圆角样式:</div> + <div class="ele-cell-content"> + <ele-tag + v-for="(item, index) in list" + :key="item" + :size="size" + shape="round" + :color="colors[type][index]" + > + 标签{{ item }} + </ele-tag> + </div> + </div> + <div class="ele-cell"> + <div>圆形样式:</div> + <div class="ele-cell-content"> + <ele-tag + v-for="(item, index) in list" + :key="item" + :size="size" + shape="circle" + :color="colors[type][index]" + > + {{ index + 1 }} + </ele-tag> + </div> + </div> + <div class="ele-cell"> + <div>尺寸选择:</div> + <div class="ele-cell-content"> + <a-radio-group :options="sizes" v-model:value="size" /> + </div> + </div> + <div class="ele-cell"> + <div>主题选择:</div> + <div class="ele-cell-content"> + <a-radio-group :options="types" v-model:value="type" /> + </div> + </div> + </a-card> + <a-card title="标签输入" :bordered="false"> + <ele-edit-tag v-model:data="tags" :size="size" :color="colors[type][0]" /> + <div style="padding: 8px 0">{{ JSON.stringify(tags) }}</div> + <div style="padding: 8px 0">自定义异步验证:</div> + <ele-edit-tag + v-model:data="tags2" + :size="size" + :color="colors[type][0]" + :validator="validator" + /> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import type { SizeType } from 'ele-admin-pro/es/ele-tag/types'; + + // 尺寸 + const sizes = ref([ + { label: 'mini', value: 'mini' }, + { label: 'small', value: 'small' }, + { label: 'middle', value: 'middle' }, + { label: 'large', value: 'large' } + ]); + + // 选中尺寸 + const size = ref<SizeType>('mini'); + + // 颜色 + const colors = ref([ + ['', 'blue', 'green', 'orange', 'red'], + ['#909399', '#1890ff', '#52c41a', '#fa8c16', '#f5222d'] + ]); + + // 主题 + const types = ref([ + { label: 'presets', value: 0 }, + { label: 'custom', value: 1 } + ]); + + // 选中主题 + const type = ref(0); + + // 标签输入 + const tags = ref(['标签一', '标签二', '标签三']); + + // + const list = ['一', '二', '三', '四', '五']; + + // 标签输入 + const tags2 = ref(['标签一', '标签二', '标签三']); + + // + const validator = (value: string) => { + return new Promise<void>((_resolve, reject) => { + setTimeout(() => { + reject(new Error(value + '不合法, 请重新输入')); + }, 1000); + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionTag' + }; +</script> + +<style lang="less" scoped> + .ele-cell + .ele-cell { + margin-top: 16px; + } +</style> diff --git a/src/views-demo/extension/tour/index.vue b/src/views-demo/extension/tour/index.vue new file mode 100644 index 0000000..e36c89d --- /dev/null +++ b/src/views-demo/extension/tour/index.vue @@ -0,0 +1,172 @@ +<template> + <div class="ele-body ele-body-card"> + <a-card title="基本用法" :bordered="false"> + <div> + <a-button type="primary" @click="onStart1">开始引导</a-button> + </div> + <div style="margin-top: 20px"> + <a-space size="large"> + <a-button ref="uploadRef1">Upload</a-button> + <a-button ref="saveRef1" type="primary">Save</a-button> + <a-button ref="moreRef1">More</a-button> + </a-space> + </div> + <ele-tour v-model="current1" :steps="steps1" /> + </a-card> + <a-card title="不带遮罩层" :bordered="false"> + <div> + <a-button type="primary" @click="onStart2">开始引导</a-button> + </div> + <div style="margin-top: 20px"> + <a-space size="large"> + <a-button ref="uploadRef2">Upload</a-button> + <a-button ref="saveRef2" type="primary">Save</a-button> + <a-button ref="moreRef2">More</a-button> + </a-space> + </div> + <ele-tour v-model="current2" :steps="steps2" :mask="false" /> + </a-card> + <a-card title="混合弹窗等多种形式" :bordered="false"> + <div> + <a-button type="primary" @click="onStart3">开始引导</a-button> + </div> + <div style="margin-top: 20px"> + <a-space size="large"> + <a-button ref="uploadRef3">Upload</a-button> + <a-button ref="saveRef3" type="primary">Save</a-button> + <a-button ref="moreRef3">More</a-button> + </a-space> + </div> + <ele-tour v-model="current3" :steps="steps3"> + <template #text="{ step, current }"> + <template v-if="current === 0"> + <div style="margin-bottom: 10px"> + <img + src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*P0S-QIRUbsUAAAAAAAAAAABkARQnAQ" + style="height: 184px; width: 100%; object-fit: cover" + /> + </div> + <div>{{ step.description }}</div> + </template> + </template> + </ele-tour> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import type { Button as AButton } from 'ant-design-vue/es'; + import type { TourStep } from 'ele-admin-pro/es/ele-tour/types'; + + // 当前步骤 + const current1 = ref<number | null>(null); + + // 按钮 + const uploadRef1 = ref<InstanceType<typeof AButton> | null>(null); + const saveRef1 = ref<InstanceType<typeof AButton> | null>(null); + const moreRef1 = ref<InstanceType<typeof AButton> | null>(null); + + // 步骤 + const steps1 = ref<TourStep[]>([ + { + target: () => uploadRef1.value?.$el, + title: '如何进行文件上传', + description: '点击这个按钮在弹出框中选择想要上传的文件即可.' + }, + { + target: () => saveRef1.value?.$el, + title: '如何提交数据', + description: '数据录入完成后点击这个按钮即可提交数据到后台.' + }, + { + target: () => moreRef1.value?.$el, + title: '如何进行更多的操作', + description: '鼠标移入到此按钮上即可展示出更多的操作功能.' + } + ]); + + /* 开始引导 */ + const onStart1 = () => { + current1.value = 0; + }; + + // 当前步骤 + const current2 = ref<number | null>(null); + + // 按钮 + const uploadRef2 = ref<InstanceType<typeof AButton> | null>(null); + const saveRef2 = ref<InstanceType<typeof AButton> | null>(null); + const moreRef2 = ref<InstanceType<typeof AButton> | null>(null); + + // 步骤 + const steps2 = ref<TourStep[]>([ + { + target: () => uploadRef2.value?.$el, + title: '如何进行文件上传', + description: '点击这个按钮在弹出框中选择想要上传的文件即可.', + popoverProps: { placement: 'topLeft' } + }, + { + target: () => saveRef2.value?.$el, + title: '如何提交数据', + description: '数据录入完成后点击这个按钮即可提交数据到后台.', + popoverProps: { placement: 'bottom' } + }, + { + target: () => moreRef2.value?.$el, + title: '如何进行更多的操作', + description: '鼠标移入到此按钮上即可展示出更多的操作功能.', + popoverProps: { placement: 'topRight' } + } + ]); + + /* 开始引导 */ + const onStart2 = () => { + current2.value = 0; + }; + + // 当前步骤 + const current3 = ref<number | null>(null); + + // 按钮 + const uploadRef3 = ref<InstanceType<typeof AButton> | null>(null); + const saveRef3 = ref<InstanceType<typeof AButton> | null>(null); + const moreRef3 = ref<InstanceType<typeof AButton> | null>(null); + + // 步骤 + const steps3 = ref<TourStep[]>([ + { + title: '欢迎使用 EleAdminPro 系统', + description: + '下面将为您介绍一些常用功能的操作说明, 如果之前已经为您介绍过, 您可以直接点击跳过结束指引.' + }, + { + target: () => uploadRef3.value?.$el, + title: '如何进行文件上传', + description: '点击这个按钮在弹出框中选择想要上传的文件即可.' + }, + { + target: () => saveRef3.value?.$el, + title: '如何提交数据', + description: '数据录入完成后点击这个按钮即可提交数据到后台.', + mask: false + }, + { + target: () => moreRef3.value?.$el, + title: '如何进行更多的操作', + description: '鼠标移入到此按钮上即可展示出更多的操作功能.' + } + ]); + + /* 开始引导 */ + const onStart3 = () => { + current3.value = 0; + }; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionTour' + }; +</script> diff --git a/src/views-demo/extension/upload/components/demo-advanced.vue b/src/views-demo/extension/upload/components/demo-advanced.vue new file mode 100644 index 0000000..e932fc7 --- /dev/null +++ b/src/views-demo/extension/upload/components/demo-advanced.vue @@ -0,0 +1,114 @@ +<template> + <a-card title="手动上传" :bordered="false"> + <ele-image-upload + v-model:value="images" + :auto-upload="false" + :before-remove="onBeforeRemove" + /> + <div class="ele-cell"> + <a-button type="primary" :loading="loading" @click="onSubmit"> + 提交 + </a-button> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, createVNode } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { ExclamationCircleOutlined } from '@ant-design/icons-vue'; + import type { + ItemType, + BeforeRemoveType + } from 'ele-admin-pro/es/ele-image-upload/types'; + + const images = ref<ItemType[]>([ + { + uid: 1, + url: 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg', + status: 'done' + }, + { + uid: 2, + url: 'https://cdn.eleadmin.com/20200610/WLXm7gp1EbLDtvVQgkeQeyq5OtDm00Jd.jpg', + status: 'done' + }, + { + uid: 3, + url: 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg', + status: 'done' + } + ]); + + const loading = ref(false); + + /* 手动上传 */ + const onSubmit = () => { + if (checkDone()) { + submitForm(); + return; + } + loading.value = true; + images.value.forEach((item) => { + if (!item.status) { + uploadItem(item); + } + }); + }; + + /* 上传图片 */ + const uploadItem = (item: ItemType) => { + // 模拟上传 + if (item.progress == null) { + item.progress = 20; + } else { + item.progress += 20; + } + item.status = 'uploading'; + const timer = setInterval(() => { + if (item.progress == null) { + item.progress = 20; + } else { + item.progress += 20; + } + if (item.progress === 100) { + item.status = 'done'; + clearInterval(timer); + // 每个图片上传完成后都检查是否全部上传完成 + if (checkDone()) { + submitForm(); + } + } + }, Math.round(Math.random() * 2500) + 500); + }; + + /* 检查是否全部上传完毕 */ + const checkDone = () => { + return !images.value.some((d) => d.status !== 'done'); + }; + + /* 全部上传完毕后与其它表单数据一起提交 */ + const submitForm = () => { + message.success('已全部上传完毕'); + console.log('data:', images.value); + loading.value = false; + }; + + /* 删除增加确认弹窗 */ + const onBeforeRemove: BeforeRemoveType = () => { + return new Promise<void>((resolve, reject) => { + Modal.confirm({ + title: '提示', + content: '确定要删除吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + resolve(); + }, + onCancel: () => { + reject(); + } + }); + }); + }; +</script> diff --git a/src/views-demo/extension/upload/components/demo-basic.vue b/src/views-demo/extension/upload/components/demo-basic.vue new file mode 100644 index 0000000..0f40c59 --- /dev/null +++ b/src/views-demo/extension/upload/components/demo-basic.vue @@ -0,0 +1,110 @@ +<template> + <a-card title="基础示例" :bordered="false"> + <ele-image-upload + v-model:value="images" + :limit="8" + :disabled="disabled" + :before-upload="onBeforeUpload" + :drag="true" + @upload="onUpload" + @item-click="onItemClick" + /> + <div class="ele-cell"> + <a-button type="primary" @click="getData">获取数据</a-button> + <div style="line-height: 22px"><em></em>禁用:</div> + <div class="ele-cell-content"> + <a-radio-group v-model:value="disabled"> + <a-radio :value="false">否</a-radio> + <a-radio :value="true">是</a-radio> + </a-radio-group> + </div> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { + ItemType, + BeforeUploadType + } from 'ele-admin-pro/es/ele-image-upload/types'; + + // 已上传数据 + const images = ref<ItemType[]>([ + { + uid: 1, + url: 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg', + status: 'done' + }, + { + uid: 2, + url: 'https://cdn.eleadmin.com/20200610/WLXm7gp1EbLDtvVQgkeQeyq5OtDm00Jd.jpg', + status: 'done' + }, + { + uid: 3, + url: 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg', + status: 'done' + } + ]); + + // 禁用 + const disabled = ref(false); + + /* 获取当前数据 */ + const getData = () => { + console.log('data:', images.value); + message.success('数据已打印在控制台'); + }; + + /* 上传事件 */ + const onUpload = (d: ItemType) => { + const item = images.value.find((t) => t.uid === d.uid) ?? d; + console.log('item:', item); + // 模拟上传 + if (images.value.length !== 5) { + item.status = 'uploading'; + item.progress = 20; + const timer = setInterval(() => { + if (item.progress == null) { + item.progress = 20; + } else { + item.progress += 20; + } + if (item.progress === 100) { + item.status = 'done'; + clearInterval(timer); + } + }, 1000); + } else { + item.status = 'uploading'; + if (item.progress == null) { + item.progress = 20; + } else if (item.progress < 80) { + item.progress += 20; + } + setTimeout(() => { + item.status = 'exception'; + message.error('上传失败, 服务器繁忙'); + }, 2000); + } + }; + + /* 上传前钩子 */ + const onBeforeUpload: BeforeUploadType = (file: File) => { + if (!file.type.startsWith('image')) { + message.error('只能选择图片'); + return false; + } + if (file.size / 1024 / 1024 > 2) { + message.error('大小不能超过 2MB'); + return false; + } + }; + + /* item 点击事件 */ + const onItemClick = (item: ItemType) => { + console.log('item-click:', item); + }; +</script> diff --git a/src/views-demo/extension/upload/components/demo-multiple.vue b/src/views-demo/extension/upload/components/demo-multiple.vue new file mode 100644 index 0000000..4ac06d3 --- /dev/null +++ b/src/views-demo/extension/upload/components/demo-multiple.vue @@ -0,0 +1,86 @@ +<template> + <a-card title="支持多选" :bordered="false"> + <ele-image-upload + v-model:value="images" + :limit="8" + :drag="true" + :multiple="true" + :upload-handler="uploadHandler" + @upload="onUpload" + /> + <a-button type="primary" @click="getData">获取数据</a-button> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { ItemType } from 'ele-admin-pro/es/ele-image-upload/types'; + + // 已上传数据 + const images = ref<ItemType[]>([ + { + uid: 1, + url: 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg', + status: 'done' + }, + { + uid: 2, + url: 'https://cdn.eleadmin.com/20200610/WLXm7gp1EbLDtvVQgkeQeyq5OtDm00Jd.jpg', + status: 'done' + }, + { + uid: 3, + url: 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg', + status: 'done' + } + ]); + + /* 获取当前数据 */ + const getData = () => { + console.log('data:', images.value); + message.success('数据已打印在控制台'); + }; + + /* 上传事件 */ + const uploadHandler = (file: File) => { + const item: ItemType = { + file, + uid: (file as any).uid, + name: file.name, + progress: 0, + status: undefined + }; + if (!file.type.startsWith('image')) { + message.error('只能选择图片'); + return; + } + if (file.size / 1024 / 1024 > 2) { + message.error('大小不能超过 2MB'); + return; + } + item.url = window.URL.createObjectURL(file); + images.value.push(item); + onUpload(item); + }; + + /* 上传 item */ + const onUpload = (d: ItemType) => { + const item = images.value.find((t) => t.uid === d.uid) ?? d; + console.log('item:', item); + // 模拟上传 + item.status = 'uploading'; + item.progress = 20; + const timer = setInterval(() => { + if (item.progress == null) { + item.progress = 20; + } else { + item.progress += 20; + } + if (item.progress === 100) { + item.status = 'done'; + clearInterval(timer); + } + }, 1000); + }; +</script> diff --git a/src/views-demo/extension/upload/index.vue b/src/views-demo/extension/upload/index.vue new file mode 100644 index 0000000..9dd67c3 --- /dev/null +++ b/src/views-demo/extension/upload/index.vue @@ -0,0 +1,19 @@ +<template> + <div class="ele-body ele-body-card"> + <demo-basic /> + <demo-advanced /> + <demo-multiple /> + </div> +</template> + +<script lang="ts" setup> + import DemoBasic from './components/demo-basic.vue'; + import DemoAdvanced from './components/demo-advanced.vue'; + import DemoMultiple from './components/demo-multiple.vue'; +</script> + +<script lang="ts"> + export default { + name: 'ExtensionUpload' + }; +</script> diff --git a/src/views-demo/extension/watermark/index.vue b/src/views-demo/extension/watermark/index.vue new file mode 100644 index 0000000..897a888 --- /dev/null +++ b/src/views-demo/extension/watermark/index.vue @@ -0,0 +1,33 @@ +<template> + <div class="ele-body ele-body-card"> + <a-card title="基本用法" :bordered="false"> + <ele-watermark content="Ele Admin Pro" :gap="[60, 40]"> + <div style="height: 200px"></div> + </ele-watermark> + </a-card> + <a-card title="多行水印" :bordered="false"> + <ele-watermark + :content="['Ele Admin Pro', 'Happy Working']" + :gap="[60, 40]" + > + <div style="height: 200px"></div> + </ele-watermark> + </a-card> + <a-card title="图片水印" :bordered="false"> + <ele-watermark + :height="30" + :width="130" + image="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*lkAoRbywo0oAAAAAAAAAAAAADrJ8AQ/original" + :gap="[60, 40]" + > + <div style="height: 200px"></div> + </ele-watermark> + </a-card> + </div> +</template> + +<script lang="ts"> + export default { + name: 'ExtensionWatermark' + }; +</script> diff --git a/src/views-demo/forget/index.vue b/src/views-demo/forget/index.vue new file mode 100644 index 0000000..13d655b --- /dev/null +++ b/src/views-demo/forget/index.vue @@ -0,0 +1,407 @@ +<template> + <div class="login-wrapper"> + <a-form + ref="formRef" + :model="form" + :rules="rules" + class="login-form ele-bg-white" + > + <h4>忘记密码</h4> + <a-form-item name="phone"> + <a-input + placeholder="请输入绑定手机号" + v-model:value="form.phone" + allow-clear + size="large" + > + <template #prefix> + <mobile-outlined /> + </template> + </a-input> + </a-form-item> + <a-form-item name="password"> + <a-input-password + placeholder="请输入新的登录密码" + v-model:value="form.password" + size="large" + > + <template #prefix> + <lock-outlined /> + </template> + </a-input-password> + </a-form-item> + <a-form-item name="password2"> + <a-input-password + placeholder="请再次输入登录密码" + v-model:value="form.password2" + size="large" + > + <template #prefix> + <key-outlined /> + </template> + </a-input-password> + </a-form-item> + <a-form-item name="code"> + <div class="login-input-group"> + <a-input + placeholder="请输入验证码" + v-model:value="form.code" + allow-clear + size="large" + > + <template #prefix> + <safety-certificate-outlined /> + </template> + </a-input> + <a-button + class="login-captcha" + :disabled="!!countdownTime" + @click="openImgCodeModal" + > + <span v-if="!countdownTime">发送验证码</span> + <span v-else>已发送 {{ countdownTime }} s</span> + </a-button> + </div> + </a-form-item> + <a-form-item> + <router-link + to="/login" + class="ele-pull-right" + style="line-height: 22px" + > + 返回登录 + </router-link> + </a-form-item> + <a-form-item> + <a-button + block + size="large" + type="primary" + :loading="loading" + @click="submit" + > + 修改密码 + </a-button> + </a-form-item> + </a-form> + <div class="login-copyright"> + copyright © 2022 eleadmin.com all rights reserved. + </div> + </div> + <!-- 编辑弹窗 --> + <a-modal + :width="340" + :footer="null" + title="发送验证码" + v-model:visible="visible" + > + <div class="login-input-group" style="margin-bottom: 16px"> + <a-input + v-model:value="imgCode" + placeholder="请输入图形验证码" + allow-clear + size="large" + /> + <a-button class="login-captcha"> + <img alt="" :src="captcha" @click="changeImgCode" /> + </a-button> + </div> + <a-button + block + size="large" + type="primary" + :loading="codeLoading" + @click="sendCode" + > + 立即发送 + </a-button> + </a-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, onBeforeUnmount } from 'vue'; + import { useRouter } from 'vue-router'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { + MobileOutlined, + LockOutlined, + KeyOutlined, + SafetyCertificateOutlined + } from '@ant-design/icons-vue'; + + const { push } = useRouter(); + + // + const formRef = ref<FormInstance | null>(null); + + // 加载状态 + const loading = ref(false); + + // 表单数据 + const form = reactive({ + phone: '1234567890', + password: '', + password2: '', + code: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + phone: [ + { + required: true, + message: '请输入绑定手机号', + type: 'string', + trigger: 'blur' + } + ], + password: [ + { + required: true, + message: '请输入新的登录密码', + type: 'string', + trigger: 'blur' + } + ], + password2: [ + { + required: true, + type: 'string', + validator: async (_rule: Rule, value: string) => { + if (!value) { + return Promise.reject('请再次输入新密码'); + } + if (value !== form.password) { + return Promise.reject('两次输入密码不一致'); + } + return Promise.resolve(); + }, + trigger: 'blur' + } + ], + code: [ + { + required: true, + message: '请输入验证码', + type: 'string', + trigger: 'blur' + } + ] + }); + + // 是否显示图形验证码弹窗 + const visible = ref(false); + + // 图形验证码 + const imgCode = ref(''); + + // 发送验证码按钮loading + const codeLoading = ref(false); + + // 验证码倒计时时间 + const countdownTime = ref(0); + + // 图形验证码地址 + const captcha = ref('https://eleadmin.com/assets/captcha?v='); + + // 验证码倒计时定时器 + let countdownTimer: number | null = null; + + /* 提交 */ + const submit = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + setTimeout(() => { + message.success('密码修改成功'); + push('/login'); + }, 1000); + }) + .catch(() => {}); + }; + + /* 更换图形验证码 */ + const changeImgCode = () => { + // 这里演示的验证码是后端地址直接是图片的形式, 如果后端返回base64格式请参考登录页面 + captcha.value = captcha.value.replace(/v=.*/, 'v=' + new Date().getTime()); + }; + + /* 显示发送短信验证码弹窗 */ + const openImgCodeModal = () => { + if (!form.phone) { + message.error('请输入手机号码'); + return; + } + imgCode.value = ''; + changeImgCode(); + visible.value = true; + }; + + /* 发送短信验证码 */ + const sendCode = () => { + if (!imgCode.value) { + message.error('请输入图形验证码'); + return; + } + codeLoading.value = true; + setTimeout(() => { + message.success('短信验证码发送成功, 请注意查收!'); + visible.value = false; + codeLoading.value = false; + countdownTime.value = 30; + // 开始对按钮进行倒计时 + countdownTimer = window.setInterval(() => { + if (countdownTime.value <= 1) { + countdownTimer && clearInterval(countdownTimer); + countdownTimer = null; + } + countdownTime.value--; + }, 1000); + }, 1000); + }; + + onBeforeUnmount(() => { + countdownTimer && clearInterval(countdownTimer); + }); +</script> + +<style lang="less" scoped> + /* 背景 */ + .login-wrapper { + padding: 48px 16px 0 16px; + position: relative; + box-sizing: border-box; + background-image: url('@/assets/bg-login.jpg'); + background-repeat: no-repeat; + background-size: cover; + min-height: 100vh; + + &:before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.2); + } + } + + /* 卡片 */ + .login-form { + width: 360px; + margin: 0 auto; + max-width: 100%; + padding: 0 28px 16px 28px; + box-sizing: border-box; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15); + border-radius: 2px; + position: relative; + z-index: 2; + + h4 { + padding: 22px 0; + text-align: center; + } + } + + .login-form-right .login-form { + margin: 0 15% 0 auto; + } + + .login-form-left .login-form { + margin: 0 auto 0 15%; + } + + /* 验证码 */ + .login-input-group { + display: flex; + align-items: center; + + :deep(.ant-input-affix-wrapper) { + flex: 1; + } + + .login-captcha { + width: 102px; + height: 40px; + margin-left: 10px; + padding: 0; + + & > img { + width: 100%; + height: 100%; + } + } + } + + /* 第三方登录图标 */ + .login-oauth-icon { + color: #fff; + padding: 5px; + margin: 0 12px; + font-size: 18px; + border-radius: 50%; + cursor: pointer; + } + + /* 底部版权 */ + .login-copyright { + color: #eee; + text-align: center; + padding: 48px 0 22px 0; + position: relative; + z-index: 1; + } + + /* 响应式 */ + @media screen and (min-height: 640px) { + .login-wrapper { + padding-top: 0; + } + + .login-form { + position: absolute; + top: 50%; + left: 50%; + transform: translateX(-50%); + margin-top: -230px; + } + + .login-form-right .login-form, + .login-form-left .login-form { + left: auto; + right: 15%; + transform: translateX(0); + margin: -230px auto auto auto; + } + + .login-form-left .login-form { + right: auto; + left: 15%; + } + + .login-copyright { + position: absolute; + left: 0; + right: 0; + bottom: 0; + } + } + + @media screen and (max-width: 768px) { + .login-form-right .login-form, + .login-form-left .login-form { + left: 50%; + right: auto; + margin-left: 0; + margin-right: auto; + transform: translateX(-50%); + } + } +</style> diff --git a/src/views-demo/form/advanced/components/user-select.vue b/src/views-demo/form/advanced/components/user-select.vue new file mode 100644 index 0000000..4bceaa5 --- /dev/null +++ b/src/views-demo/form/advanced/components/user-select.vue @@ -0,0 +1,148 @@ +<template> + <a-card :bordered="false" title="选择成员"> + <a-table + size="middle" + row-key="key" + :pagination="false" + :data-source="users" + :columns="columns" + :scroll="{ x: 900 }" + > + <template #bodyCell="{ column, record, index }"> + <template v-if="column.key === 'name'"> + <a-input + v-if="record.isEdit" + v-model:value="record.name" + placeholder="请输入用户名" + /> + <div v-else>{{ record.name }}</div> + </template> + <template v-else-if="column.key === 'number'"> + <a-input + v-if="record.isEdit" + v-model:value="record.number" + placeholder="请输入工号" + /> + <div v-else>{{ record.number }}</div> + </template> + <template v-else-if="column.key === 'department'"> + <a-select + v-if="record.isEdit" + v-model:value="record.department" + placeholder="请选择部门" + class="ele-fluid" + > + <a-select-option value="研发部">研发部</a-select-option> + <a-select-option value="测试部">测试部</a-select-option> + <a-select-option value="产品部">产品部</a-select-option> + </a-select> + <div v-else>{{ record.department }}</div> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a v-if="record.isEdit" @click="done(record, index)">完成</a> + <a v-else @click="edit(record, index)">修改</a> + <a-divider type="vertical" /> + <a-popconfirm + title="确定要删除此用户吗?" + @confirm="remove(record, index)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </a-table> + <a-button block type="dashed" style="margin-top: 16px" @click="add"> + <template #icon> + <plus-outlined /> + </template> + <span>新增成员</span> + </a-button> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { PlusOutlined } from '@ant-design/icons-vue'; + import { uuid } from 'ele-admin-pro/es'; + import { queryList } from '@/api/form/advanced'; + import type { UserItem } from '@/api/form/advanced/model'; + import type { ColumnsType } from 'ant-design-vue/es/table'; + + // 已添加成员 + const users = ref<UserItem[]>([]); + + // 列 + const columns = reactive<ColumnsType>([ + { + key: 'index', + align: 'center', + width: 48, + customRender: ({ index }) => index + 1, + fixed: 'left' + }, + { + title: '用户名', + key: 'name', + width: 200 + }, + { + title: '工号', + key: 'number', + width: 200 + }, + { + title: '所属部门', + key: 'department', + width: 200 + }, + { + title: '操作', + key: 'action', + align: 'center', + width: 160 + } + ]); + + /* 添加 */ + const add = () => { + users.value.push({ + key: uuid(8), + isEdit: true, + number: '00001', + name: 'John Brown', + department: '研发部' + }); + }; + + /* 编辑 */ + const edit = (_row: UserItem, index: number) => { + users.value[index].isEdit = true; + }; + + /* 完成编辑 */ + const done = (_row: UserItem, index: number) => { + users.value[index].isEdit = false; + }; + + /* 删除 */ + const remove = (_row: UserItem, index: number) => { + users.value.splice(index, 1); + }; + + /* 查询已添加 */ + queryList() + .then((data) => { + users.value = data.map((d) => { + return { + ...d, + isEdit: false + }; + }); + }) + .catch((e) => { + message.error(e.message); + }); +</script> diff --git a/src/views-demo/form/advanced/index.vue b/src/views-demo/form/advanced/index.vue new file mode 100644 index 0000000..9a3dbb2 --- /dev/null +++ b/src/views-demo/form/advanced/index.vue @@ -0,0 +1,447 @@ +<template> + <div> + <a-page-header :ghost="false" title="复杂表单"> + <div class="ele-text-secondary"> + 复杂表单常见于一次性输入和提交大批量数据的场景。 + </div> + </a-page-header> + <div class="ele-body ele-body-card" style="padding-bottom: 48px"> + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col=" + styleResponsive + ? { + xxl: 6, + xl: 8, + lg: 6, + md: 8, + sm: 5, + xs: 24 + } + : { flex: '90px' } + " + :wrapper-col=" + styleResponsive + ? { + xxl: 18, + xl: 16, + lg: 18, + md: 16, + sm: 19, + xs: 24 + } + : { flex: '1' } + " + > + <a-card :bordered="false" title="仓库信息"> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="仓库名" name="name"> + <a-input + allow-clear + v-model:value="form.name" + placeholder="请输入仓库名" + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="仓库域名" name="url"> + <a-input + allow-clear + addon-after=".com" + addon-before="https://" + v-model:value="form.url" + placeholder="请输入" + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="仓库管理员" name="administrator"> + <a-select + allow-clear + v-model:value="form.administrator" + placeholder="请选择仓库管理员" + > + <a-select-option value="1">SunSmile</a-select-option> + <a-select-option value="2">Jasmine</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="审批人" name="approver"> + <a-select + allow-clear + v-model:value="form.approver" + placeholder="请选择审批人" + > + <a-select-option value="1">SunSmile</a-select-option> + <a-select-option value="2">Jasmine</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="生效日期" name="datetime"> + <a-range-picker + class="ele-fluid" + value-format="YYYY-MM-DD" + v-model:value="form.datetime" + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="仓库类型" name="type"> + <a-select + allow-clear + v-model:value="form.type" + placeholder="请选择仓库类型" + > + <a-select-option value="private">私密</a-select-option> + <a-select-option value="public">公开</a-select-option> + </a-select> + </a-form-item> + </a-col> + </a-row> + </a-card> + <a-card :bordered="false" title="任务信息"> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="任务名" name="task"> + <a-input + allow-clear + v-model:value="form.task" + placeholder="请输入任务名" + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="任务表述" name="description"> + <a-input + allow-clear + v-model:value="form.description" + placeholder="请输入任务表述" + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="执行人" name="executor"> + <a-select + allow-clear + v-model:value="form.executor" + placeholder="请选择执行人" + > + <a-select-option value="1">SunSmile</a-select-option> + <a-select-option value="2">Jasmine</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="责任人" name="officer"> + <a-select + allow-clear + v-model:value="form.officer" + placeholder="请选择责任人" + > + <a-select-option value="1">SunSmile</a-select-option> + <a-select-option value="2">Jasmine</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="提醒时间" name="reminder"> + <a-time-picker + class="ele-fluid" + value-format="HH:mm:ss" + v-model:value="form.reminder" + placeholder="请选择提醒时间" + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="任务类型" name="taskType"> + <a-select + allow-clear + v-model:value="form.taskType" + placeholder="请选择任务类型" + > + <a-select-option value="1">私密</a-select-option> + <a-select-option value="2">公开</a-select-option> + </a-select> + </a-form-item> + </a-col> + </a-row> + </a-card> + <user-select /> + <!-- 底部工具栏 --> + <div class="ele-bottom-tool"> + <div v-if="validMsg" class="ele-text-danger"> + <close-circle-outlined /> + <span>{{ validMsg }}</span> + </div> + <div class="ele-bottom-tool-actions"> + <a-button type="primary" :loading="loading" @click="submit"> + 提交 + </a-button> + </div> + </div> + </a-form> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { CloseCircleOutlined } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import UserSelect from './components/user-select.vue'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const formRef = ref<FormInstance | null>(null); + + // 加载状态 + const loading = ref(false); + + // + interface FormType { + name?: string; + url?: string; + administrator?: string; + approver?: string; + datetime?: [string, string]; + type?: string; + task?: string; + description?: string; + executor?: string; + officer?: string; + reminder?: string; + taskType?: string; + } + + // 表单数据 + const { form, resetFields } = useFormData<FormType>({ + name: '', + url: '', + administrator: undefined, + approver: undefined, + datetime: ['', ''], + type: undefined, + task: '', + description: '', + executor: undefined, + officer: undefined, + reminder: undefined, + taskType: undefined + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + name: [ + { + required: true, + message: '请输入仓库名', + type: 'string', + trigger: 'blur' + } + ], + url: [ + { + required: true, + message: '请输入仓库域名', + type: 'string', + trigger: 'blur' + } + ], + administrator: [ + { + required: true, + message: '请选择仓库管理员', + type: 'string', + trigger: 'blur' + } + ], + approver: [ + { + required: true, + message: '请选择审批人', + type: 'string', + trigger: 'blur' + } + ], + datetime: [ + { + required: true, + message: '请选择生效日期', + type: 'array', + trigger: 'blur' + } + ], + type: [ + { + required: true, + message: '请选择仓库类型', + type: 'string', + trigger: 'blur' + } + ], + task: [ + { + required: true, + message: '请输入任务名', + type: 'string', + trigger: 'blur' + } + ], + description: [ + { + required: true, + message: '请输入任务表述', + type: 'string', + trigger: 'blur' + } + ], + executor: [ + { + required: true, + message: '请选择执行人', + type: 'string', + trigger: 'blur' + } + ], + officer: [ + { + required: true, + message: '请选择责任人', + type: 'string', + trigger: 'blur' + } + ], + reminder: [ + { + required: true, + message: '请选择提醒时间', + type: 'string', + trigger: 'blur' + } + ], + taskType: [ + { + required: true, + message: '请选择任务类型', + type: 'string', + trigger: 'blur' + } + ] + }); + + // 表单验证失败提示信息 + const validMsg = ref(''); + + /* 表单提交 */ + const submit = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + validMsg.value = ''; + loading.value = true; + setTimeout(() => { + loading.value = false; + message.success('提交成功'); + resetFields(); + formRef.value?.clearValidate(); + }, 1000); + }) + .catch((e: any) => { + validMsg.value = ` 共有 ${e.errorFields.length} 项校验不通过`; + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'FormAdvanced' + }; +</script> diff --git a/src/views-demo/form/basic/index.vue b/src/views-demo/form/basic/index.vue new file mode 100644 index 0000000..2f0ed69 --- /dev/null +++ b/src/views-demo/form/basic/index.vue @@ -0,0 +1,230 @@ +<template> + <div> + <a-page-header :ghost="false" title="基础表单"> + <div class="ele-text-secondary"> + 表单页用于向用户收集或验证信息,基础表单常见于数据项较少的表单场景。 + </div> + </a-page-header> + <div class="ele-body"> + <a-card :bordered="false"> + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { sm: 4, xs: 24 } : { flex: '100px' }" + :wrapper-col="styleResponsive ? { sm: 20, xs: 24 } : { flex: '1' }" + style="max-width: 800px; margin: 0 auto" + > + <a-form-item label="标题" name="title"> + <a-input + allow-clear + placeholder="请输入标题" + v-model:value="form.title" + /> + </a-form-item> + <a-form-item label="起止日期" name="datetime"> + <a-range-picker + class="ele-fluid" + value-format="YYYY-MM-DD" + v-model:value="form.datetime" + /> + </a-form-item> + <a-form-item label="目标描述" name="goal"> + <a-textarea + :rows="4" + v-model:value="form.goal" + placeholder="请输入目标描述" + /> + </a-form-item> + <a-form-item label="衡量标准" name="standard"> + <a-textarea + :rows="4" + v-model:value="form.standard" + placeholder="请输入衡量标准" + /> + </a-form-item> + <a-form-item label="地点" name="address"> + <a-select + allow-clear + v-model:value="form.address" + placeholder="请选择地点" + > + <a-select-option value="1">地点一</a-select-option> + <a-select-option value="2">地点二</a-select-option> + <a-select-option value="3">地点三</a-select-option> + </a-select> + </a-form-item> + <a-form-item label="邀评人"> + <a-select + allow-clear + mode="multiple" + v-model:value="form.invites" + placeholder="请选择邀评人" + > + <a-select-option + v-for="item in users" + :key="item.id" + :value="item.name" + > + {{ item.name }} + </a-select-option> + </a-select> + </a-form-item> + <a-form-item label="权重"> + <a-space> + <a-input-number :min="0" :max="100" v-model:value="form.weight" /> + <span>%</span> + </a-space> + </a-form-item> + <a-form-item label="目标公开"> + <a-radio-group name="publicType" v-model:value="form.publicType"> + <a-radio :value="1">公开</a-radio> + <a-radio :value="2">部分公开</a-radio> + <a-radio :value="3">不公开</a-radio> + </a-radio-group> + <div style="margin-top: 12px"> + <a-input v-if="form.publicType === 2" placeholder="公开给" /> + </div> + <div class="ele-text-secondary" style="margin-top: 12px"> + 客户、邀评人默认被分享 + </div> + </a-form-item> + <a-form-item + :wrapper-col=" + styleResponsive ? { sm: { offset: 4 } } : { offset: 3 } + " + > + <a-space size="middle"> + <a-button @click="finishPageTab()">关闭</a-button> + <a-button type="primary" :loading="loading" @click="submit"> + 提交 + </a-button> + </a-space> + </a-form-item> + </a-form> + </a-card> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { finishPageTab } from '@/utils/page-tab-util'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const formRef = ref<FormInstance | null>(null); + + // 加载状态 + const loading = ref(false); + + // + interface FormType { + title?: string; + datetime?: [string, string]; + goal?: string; + standard?: string; + address?: string; + invites?: []; + weight?: number; + publicType?: number; + } + + // 表单数据 + const { form, resetFields } = useFormData<FormType>({ + title: '', + datetime: ['', ''], + goal: '', + standard: '', + address: undefined, + invites: [], + weight: 0, + publicType: 1 + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + title: [ + { + required: true, + message: '请输入标题', + type: 'string', + trigger: 'blur' + } + ], + datetime: [ + { + required: true, + message: '请选择起止日期', + type: 'array', + trigger: 'blur' + } + ], + goal: [ + { + required: true, + message: '请输入目标描述', + type: 'string', + trigger: 'blur' + } + ], + standard: [ + { + required: true, + message: '请输入衡量标准', + type: 'string', + trigger: 'blur' + } + ], + address: [ + { + required: true, + message: '请选择地点', + type: 'string', + trigger: 'blur' + } + ] + }); + + // 邀评人下拉列表数据 + const users = ref([ + { id: 1, name: 'SunSmile' }, + { id: 2, name: '你的名字很好听' }, + { id: 3, name: '全村人的希望' }, + { id: 4, name: 'Jasmine' }, + { id: 5, name: '酷酷的大叔' } + ]); + + /* 提交 */ + const submit = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + setTimeout(() => { + loading.value = false; + resetFields(); + formRef.value?.clearValidate(); + message.success('提交成功'); + }, 1500); + }) + .catch(() => {}); + }; +</script> + +<script lang="ts"> + export default { + name: 'FormBasic' + }; +</script> diff --git a/src/views-demo/form/step/components/step-confirm.vue b/src/views-demo/form/step/components/step-confirm.vue new file mode 100644 index 0000000..9a8c743 --- /dev/null +++ b/src/views-demo/form/step/components/step-confirm.vue @@ -0,0 +1,112 @@ +<template> + <a-form + ref="formRef" + :model="form" + :rules="rules" + class="ele-form-detail" + :label-col="styleResponsive ? { sm: 5, xs: 24 } : { flex: '130px' }" + :wrapper-col="styleResponsive ? { sm: 19, xs: 24 } : { flex: '1' }" + > + <a-alert + closable + show-icon + type="info" + message="确认转账后,资金将直接打入对方账户,无法退回。" + /> + <a-form-item label="付款账户" style="margin-top: 24px"> + {{ data.account }} + </a-form-item> + <a-form-item label="收款账户">{{ data.receiver }}</a-form-item> + <a-form-item label="收款人姓名">{{ data.name }}</a-form-item> + <a-form-item label="转账金额"> + <span style="font-size: 24px; line-height: 1"> + {{ data.amount }} + </span> + 元 + </a-form-item> + <a-divider style="margin: 20px 0 30px 0" /> + <a-form-item label="支付密码" name="password"> + <div style="max-width: 220px"> + <a-input-password + v-model:value="form.password" + placeholder="请输入支付密码" + /> + </div> + </a-form-item> + <a-form-item + :wrapper-col="styleResponsive ? { sm: { offset: 5 } } : { offset: 4 }" + style="margin-top: 24px" + > + <a-space size="middle"> + <a-button type="primary" :loading="loading" @click="submit"> + 下一步 + </a-button> + <a-button @click="back">上一步</a-button> + </a-space> + </a-form-item> + </a-form> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import type { StepForm } from '../model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + defineProps<{ + data: StepForm; + }>(); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'back'): void; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const form = reactive({ + password: '123456' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + password: [ + { + required: true, + message: '请输入支付密码', + type: 'string', + trigger: 'blur' + } + ] + }); + + const submit = () => { + if (!formRef.value) { + return; + } + formRef.value + ?.validate() + .then(() => { + loading.value = true; + setTimeout(() => { + loading.value = false; + emit('done'); + }, 300); + }) + .catch(() => {}); + }; + + const back = () => { + emit('back'); + }; +</script> diff --git a/src/views-demo/form/step/components/step-edit.vue b/src/views-demo/form/step/components/step-edit.vue new file mode 100644 index 0000000..d017270 --- /dev/null +++ b/src/views-demo/form/step/components/step-edit.vue @@ -0,0 +1,144 @@ +<template> + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { sm: 5, xs: 24 } : { flex: '130px' }" + :wrapper-col="styleResponsive ? { sm: 19, xs: 24 } : { flex: '1' }" + > + <a-form-item label="付款账户" name="account"> + <a-select + allow-clear + v-model:value="form.account" + placeholder="请选择付款账户" + > + <a-select-option value="eleadmin@eclouds.com"> + eleadmin@eclouds.com + </a-select-option> + </a-select> + </a-form-item> + <a-form-item label="收款账户" name="receiver"> + <a-input + allow-clear + v-model:value="form.receiver" + placeholder="请输入收款账户" + > + <template #addonBefore> + <a-select + v-model:value="form.pay" + style="width: 100px; margin: -5px -12px" + > + <a-select-option value="alipay">支付宝</a-select-option> + <a-select-option value="wxpay">微信</a-select-option> + </a-select> + </template> + </a-input> + </a-form-item> + <a-form-item label="收款人姓名" name="name"> + <a-input + allow-clear + v-model:value="form.name" + placeholder="请输入收款人姓名" + /> + </a-form-item> + <a-form-item label="转账金额" name="amount"> + <a-input + prefix="¥" + allow-clear + v-model:value.number="form.amount" + placeholder="请输入转账金额" + /> + </a-form-item> + <a-form-item + :wrapper-col="styleResponsive ? { sm: { offset: 5 } } : { offset: 4 }" + > + <a-button type="primary" :loading="loading" @click="submit"> + 下一步 + </a-button> + </a-form-item> + </a-form> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import type { StepForm } from '../model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done', data: StepForm): void; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const form = reactive<StepForm>({ + account: 'eleadmin@eclouds.com', + receiver: 'test@example.com', + pay: 'alipay', + name: 'Alex', + amount: 500 + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + account: [ + { + required: true, + message: '请选择付款账户', + type: 'string', + trigger: 'blur' + } + ], + receiver: [ + { + required: true, + message: '请输入收款账户', + type: 'string', + trigger: 'blur' + } + ], + name: [ + { + required: true, + message: '请输入收款人姓名', + type: 'string', + trigger: 'blur' + } + ], + amount: [ + { + required: true, + message: '请输入合法金额数字', + type: 'number', + trigger: 'blur' + } + ] + }); + + /* 步骤一提交 */ + const submit = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + setTimeout(() => { + loading.value = false; + emit('done', form); + }, 300); + }) + .catch(() => {}); + }; +</script> diff --git a/src/views-demo/form/step/components/step-success.vue b/src/views-demo/form/step/components/step-success.vue new file mode 100644 index 0000000..427070f --- /dev/null +++ b/src/views-demo/form/step/components/step-success.vue @@ -0,0 +1,49 @@ +<template> + <div> + <a-result title="操作成功" status="success" sub-title="预计两小时内到账"> + <template #extra> + <a-space size="middle"> + <a-button type="primary" @click="back"> 再转一笔 </a-button> + <a-button>查看账单</a-button> + </a-space> + </template> + <a-form + class="ele-form-detail" + :label-col="styleResponsive ? { sm: 5, xs: 24 } : { flex: '100px' }" + :wrapper-col="styleResponsive ? { sm: 19, xs: 24 } : { flex: '1' }" + > + <a-form-item label="付款账户">{{ data.account }}</a-form-item> + <a-form-item label="收款账户">{{ data.receiver }}</a-form-item> + <a-form-item label="收款人姓名">{{ data.name }}</a-form-item> + <a-form-item label="转账金额"> + <span style="font-size: 24px; line-height: 1"> + {{ data.amount }} + </span> + 元 + </a-form-item> + </a-form> + </a-result> + </div> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import type { StepForm } from '../model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + defineProps<{ + data: StepForm; + }>(); + + const emit = defineEmits<{ + (e: 'back'): void; + }>(); + + const back = () => { + emit('back'); + }; +</script> diff --git a/src/views-demo/form/step/index.vue b/src/views-demo/form/step/index.vue new file mode 100644 index 0000000..d1a5408 --- /dev/null +++ b/src/views-demo/form/step/index.vue @@ -0,0 +1,94 @@ +<template> + <div> + <a-page-header :ghost="false" title="分步表单"> + <div class="ele-text-secondary"> + 将一个冗长或用户不熟悉的表单任务分成多个步骤,指导用户完成。 + </div> + </a-page-header> + <div class="ele-body"> + <a-card :bordered="false"> + <div style="max-width: 800px; margin: 0 auto"> + <div style="margin: 10px 0 30px 0"> + <a-steps + :current="active" + direction="horizontal" + :responsive="styleResponsive" + > + <a-step title="第一步" description="填写转账信息" /> + <a-step title="第二步" description="确认转账信息" /> + <a-step title="第三步" description="转账成功" /> + </a-steps> + </div> + <step-edit v-if="active === 0" @done="onDone" /> + <step-confirm + v-if="active === 1" + :data="form" + @done="onNext" + @back="onBack" + /> + <step-success v-if="active === 2" :data="form" @back="onBack" /> + </div> + <div v-if="active === 0"> + <a-divider style="margin: 35px 0 25px 0" /> + <a-alert type="info"> + <template #description> + <h6 style="margin: 5px 0 15px 0">说明</h6> + <h6 style="margin-bottom: 10px">转账到支付宝</h6> + <p style="margin-bottom: 15px"> + 如果需要,这里可以放一些关于产品的常见问题说明。如果需要, + 这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。 + </p> + <h6 style="margin-bottom: 10px">转账到微信</h6> + <p style="margin-bottom: 15px"> + 如果需要,这里可以放一些关于产品的常见问题说明。如果需要, + 这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。 + </p> + </template> + </a-alert> + </div> + </a-card> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import StepEdit from './components/step-edit.vue'; + import StepConfirm from './components/step-confirm.vue'; + import StepSuccess from './components/step-success.vue'; + import type { StepForm } from './model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // 选中步骤 + const active = ref(0); + + // + const form = reactive<StepForm>({}); + + // + const onDone = (data: StepForm) => { + Object.assign(form, data); + active.value = 1; + }; + + // + const onNext = () => { + active.value = 2; + }; + + // + const onBack = () => { + active.value = 0; + }; +</script> + +<script lang="ts"> + export default { + name: 'FormStep' + }; +</script> diff --git a/src/views-demo/form/step/model/index.ts b/src/views-demo/form/step/model/index.ts new file mode 100644 index 0000000..725d712 --- /dev/null +++ b/src/views-demo/form/step/model/index.ts @@ -0,0 +1,7 @@ +export interface StepForm { + account?: string; + receiver?: string; + pay?: string; + name?: string; + amount?: number; +} diff --git a/src/views-demo/list/advanced/index.vue b/src/views-demo/list/advanced/index.vue new file mode 100644 index 0000000..ce2e6d2 --- /dev/null +++ b/src/views-demo/list/advanced/index.vue @@ -0,0 +1,488 @@ +<template> + <div + :class="[ + 'ele-body ele-body-card', + { 'list-adv-responsive': styleResponsive } + ]" + > + <a-card :bordered="false"> + <a-row> + <a-col + v-bind="styleResponsive ? { md: 8, sm: 24, xs: 24 } : { span: 8 }" + > + <div class="ele-text-center"> + <div style="margin-bottom: 8px">进行中的任务</div> + <h2>10 个任务</h2> + </div> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 8, sm: 24, xs: 24 } : { span: 8 }" + > + <div class="ele-text-center"> + <div style="margin-bottom: 8px">剩余任务</div> + <h2>3 个任务</h2> + </div> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 8, sm: 24, xs: 24 } : { span: 8 }" + > + <div class="ele-text-center"> + <div style="margin-bottom: 8px">任务总耗时</div> + <h2>120 个小时</h2> + </div> + </a-col> + </a-row> + </a-card> + <a-card :bordered="false"> + <!-- 头部工具栏 --> + <ele-toolbar title="复杂列表"> + <template #action> + <a-space size="middle"> + <a-radio-group v-model:value="where.state" @change="query"> + <a-radio-button value="0">全部</a-radio-button> + <a-radio-button value="1">进行中</a-radio-button> + <a-radio-button value="2">已完成</a-radio-button> + </a-radio-group> + <div + style="width: 200px" + :class="{ 'hidden-sm-and-down': styleResponsive }" + > + <a-input-search + v-model:value="where.keyword" + placeholder="请输入" + @search="query" + /> + </div> + </a-space> + </template> + </ele-toolbar> + <a-button block type="dashed" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>添加</span> + </a-button> + <!-- 数据列表 --> + <a-spin :spinning="loading"> + <div style="min-height: 100px"> + <div v-for="item in data" :key="item.id"> + <div class="basic-list-item"> + <div class="ele-cell"> + <a-avatar :size="60" shape="square" :src="item.cover" /> + <div class="ele-cell-content"> + <div class="ele-cell-title">{{ item.title }}</div> + <div class="ele-cell-desc">{{ item.content }}</div> + </div> + </div> + <div class="basic-list-item-owner"> + <div class="ele-text-heading">发布人</div> + <div class="ele-text-secondary">{{ item.user }}</div> + </div> + <div class="basic-list-item-time"> + <div class="ele-text-heading">开始时间</div> + <div class="ele-text-secondary">{{ item.time }}</div> + </div> + <div class="basic-list-item-progress"> + <a-progress :status="item.status" :percent="item.progress" /> + </div> + <div class="basic-list-item-tool"> + <a-space> + <a @click="openEdit(item)">编辑</a> + <a-divider type="vertical" /> + <a-dropdown> + <a>更多<down-outlined class="ele-text-small" /></a> + <template #overlay> + <a-menu @click="(obj: any) => dropClick(obj.key, item)"> + <a-menu-item key="share">分享</a-menu-item> + <a-menu-item key="remove">删除</a-menu-item> + </a-menu> + </template> + </a-dropdown> + </a-space> + </div> + </div> + <a-divider /> + </div> + </div> + <div class="ele-text-center" style="margin-top: 18px"> + <a-pagination + :total="count" + v-model:page-size="limit" + show-quick-jumper + v-model:current="page" + @change="query" + /> + </div> + </a-spin> + </a-card> + <!-- 编辑弹窗 --> + <ele-modal + :width="460" + v-model:visible="visible" + :confirm-loading="submitLoading" + :title="form.id ? '任务编辑' : '任务添加'" + :body-style="{ paddingBottom: '8px' }" + @ok="submit" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col=" + styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="任务名称:" name="title"> + <a-input + allow-clear + v-model:value="form.title" + placeholder="请输入任务名称" + /> + </a-form-item> + <a-form-item label="开始时间:" name="time"> + <a-date-picker + show-time + class="ele-fluid" + v-model:value="form.time" + placeholder="请选择开始时间" + value-format="YYYY-MM-DD HH:mm:ss" + /> + </a-form-item> + <a-form-item label="负责人:" name="user"> + <a-select + allow-clear + v-model:value="form.user" + placeholder="请选择负责人" + > + <a-select-option value="SunSmile">SunSmile</a-select-option> + <a-select-option value="Pojin">Pojin</a-select-option> + <a-select-option value="SuperWill">SuperWill</a-select-option> + <a-select-option value="Jasmine">Jasmine</a-select-option> + <a-select-option value="Vast">Vast</a-select-option> + </a-select> + </a-form-item> + <a-form-item label="任务描述:"> + <a-textarea + :rows="4" + v-model:value="form.content" + placeholder="请输入任务描述" + /> + </a-form-item> + </a-form> + </ele-modal> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive, createVNode } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { + PlusOutlined, + DownOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + + interface ListItem { + id?: number; + title?: string; + time?: string; + user?: string; + progress?: number; + content?: string; + cover?: string; + status?: 'normal' | 'active' | 'success' | 'exception'; + } + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const formRef = ref<FormInstance | null>(null); + + // 列表加载状态 + const loading = ref(false); + + // 列表数据 + const data = ref<ListItem[]>([]); + + // 搜索表单 + const where = reactive({ + state: '0', + keyword: '' + }); + + // 第几页 + const page = ref(1); + + // 每页多少条 + const limit = ref(5); + + // 总数量 + const count = ref(0); + + // 编辑弹窗是否显示 + const visible = ref(false); + + // 编辑弹窗表单数据 + const { form, resetFields, assignFields } = useFormData<ListItem>({ + id: undefined, + title: 'Vue Router', + time: undefined, + user: '', + content: '' + }); + + // 编辑弹窗表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + title: [ + { + required: true, + message: '请输入任务名称', + type: 'string', + trigger: 'blur' + } + ], + time: [ + { + required: true, + message: '请选择开始时间', + type: 'string', + trigger: 'blur' + } + ], + user: [ + { + required: true, + message: '请选择负责人', + type: 'string', + trigger: 'blur' + } + ] + }); + + // 编辑表单提交状态 + const submitLoading = ref(false); + + /* 查询数据 */ + const query = () => { + loading.value = true; + setTimeout(() => { + loading.value = false; + count.value = 25; + data.value = [ + { + id: 1, + title: 'ElementUI', + time: '2020-06-13 08:33:12', + user: 'SunSmile', + progress: 87, + content: + 'Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的组件库,提供了配套设计资源,帮助你的网站快速成型。', + cover: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + id: 2, + title: 'Vue.js', + time: '2020-06-13 06:40:13', + user: 'Pojin', + progress: 100, + content: + 'Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。', + cover: + 'https://cdn.eleadmin.com/20200609/b6a811873e704db49db994053a5019b2.jpg' + }, + { + id: 3, + title: 'Vuex', + time: '2020-06-13 04:40:20', + user: 'SuperWill', + progress: 75, + content: + 'Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。', + cover: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + }, + { + id: 4, + title: 'Vue Router', + time: '2020-06-13 02:40:05', + user: 'Jasmine', + progress: 65, + content: + 'Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。', + cover: + 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg' + }, + { + id: 5, + title: 'Sass', + time: '2020-06-13 00:40:58', + user: 'Vast', + progress: 45, + status: 'exception', + content: 'Sass 是世界上最成熟、稳定、强大的专业级 CSS 扩展语言。', + cover: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + } + ]; + }, 300); + }; + + /* 显示编辑弹窗 */ + const openEdit = (row?: ListItem) => { + visible.value = true; + resetFields(); + formRef.value?.clearValidate(); + if (row) { + assignFields(row); + } + }; + + /* 保存编辑 */ + const submit = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + submitLoading.value = true; + setTimeout(() => { + submitLoading.value = false; + visible.value = false; + message.success('保存成功'); + if (form.id) { + // 保存修改 + Object.assign( + data.value[data.value.findIndex((d) => d.id === form.id)], + form + ); + } else { + // 保存添加 + data.value.push({ + ...form, + id: new Date().getTime(), + cover: + 'https://cdn.eleadmin.com/20200610/RZ8FQmZfHkcffMlTBCJllBFjEhEsObVo.jpg' + }); + } + }, 300); + }) + .catch(() => {}); + }; + + /* 下拉菜单点击事件 */ + const dropClick = (key: string, item: ListItem) => { + console.log(item); + if (key === 'remove') { + // 删除 + Modal.confirm({ + title: '提示', + content: '确定删除该任务吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + message.success('删除成功'); + } + }); + } else if (key === 'share') { + message.success('点击了分享'); + } + }; + + query(); +</script> + +<script lang="ts"> + export default { + name: 'ListAdvanced' + }; +</script> + +<style lang="less" scoped> + /* 列表样式 */ + .basic-list-item { + display: flex; + align-items: center; + padding: 16px 8px; + + & > .ele-cell { + flex: 1; + } + + & > div + div { + margin-left: 20px; + flex-shrink: 0; + } + + .basic-list-item-owner { + width: 80px; + } + + .basic-list-item-time { + width: 160px; + } + + .basic-list-item-progress { + width: 180px; + } + + .ele-text-heading + .ele-text-secondary { + margin-top: 8px; + } + } + + /* 响应式 */ + @media screen and (max-width: 1340px) { + .basic-list-item { + & > div + div { + margin-left: 10px; + } + + .basic-list-item-owner { + width: 70px; + } + + .basic-list-item-time { + width: 140px; + } + + .basic-list-item-progress { + width: 100px; + } + } + } + + @media screen and (max-width: 1100px) { + .list-adv-responsive .basic-list-item { + display: block; + + .basic-list-item-owner, + .basic-list-item-time, + .basic-list-item-progress { + width: auto; + margin: 8px 0 0 0; + display: flex; + align-items: center; + } + + .basic-list-item-tool { + margin-top: 8px; + text-align: right; + } + + .ele-text-heading + .ele-text-secondary { + margin: 0 0 0 16px; + } + } + } +</style> diff --git a/src/views-demo/list/basic/add/index.vue b/src/views-demo/list/basic/add/index.vue new file mode 100644 index 0000000..147393f --- /dev/null +++ b/src/views-demo/list/basic/add/index.vue @@ -0,0 +1,17 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <edit-form /> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import EditForm from '../components/edit-form.vue'; +</script> + +<script lang="ts"> + export default { + name: 'ListBasicAdd' + }; +</script> diff --git a/src/views-demo/list/basic/components/edit-form.vue b/src/views-demo/list/basic/components/edit-form.vue new file mode 100644 index 0000000..8a6837d --- /dev/null +++ b/src/views-demo/list/basic/components/edit-form.vue @@ -0,0 +1,264 @@ +<template> + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { sm: 4, xs: 24 } : { flex: '100px' }" + :wrapper-col="styleResponsive ? { sm: 20, xs: 24 } : { flex: '1' }" + style="max-width: 600px; margin: 0 auto" + > + <a-form-item label="用户账号" name="username"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入用户账号" + v-model:value="form.username" + /> + </a-form-item> + <a-form-item label="用户名" name="nickname"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入用户名" + v-model:value="form.nickname" + /> + </a-form-item> + <a-form-item label="性别" name="sex"> + <sex-select v-model:value="form.sex" /> + </a-form-item> + <a-form-item label="角色" name="roles"> + <role-select v-model:value="form.roles" /> + </a-form-item> + <a-form-item label="邮箱" name="email"> + <a-input + allow-clear + :maxlength="100" + placeholder="请输入邮箱" + v-model:value="form.email" + /> + </a-form-item> + <a-form-item label="手机号" name="phone"> + <a-input + allow-clear + :maxlength="11" + placeholder="请输入手机号" + v-model:value="form.phone" + /> + </a-form-item> + <a-form-item label="出生日期"> + <a-date-picker + class="ele-fluid" + placeholder="请选择出生日期" + value-format="YYYY-MM-DD" + v-model:value="form.birthday" + /> + </a-form-item> + <a-form-item v-if="!isUpdate" label="登录密码" name="password"> + <a-input-password + :maxlength="20" + v-model:value="form.password" + placeholder="请输入登录密码" + /> + </a-form-item> + <a-form-item label="个人简介"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入个人简介" + v-model:value="form.introduction" + /> + </a-form-item> + <a-form-item + :wrapper-col="styleResponsive ? { sm: { offset: 4 } } : { offset: 4 }" + > + <a-space size="middle"> + <a-button @click="onClose">关闭</a-button> + <a-button type="primary" :loading="loading" @click="save"> + 保存 + </a-button> + </a-space> + </a-form-item> + </a-form> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch, unref } from 'vue'; + import { useRouter } from 'vue-router'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { emailReg, phoneReg } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import RoleSelect from '@/views/system/user/components/role-select.vue'; + import SexSelect from '@/views/system/user/components/sex-select.vue'; + import { addUser, updateUser, checkExistence } from '@/api/system/user'; + import type { User } from '@/api/system/user/model'; + import { removePageTab, reloadPageTab } from '@/utils/page-tab-util'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const props = defineProps<{ + // 修改回显的数据 + data?: User | null; + }>(); + + const { currentRoute, push } = useRouter(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<User>({ + userId: undefined, + username: '', + nickname: '', + sex: undefined, + roles: [], + email: '', + phone: '', + password: '', + introduction: '', + birthday: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + username: [ + { + required: true, + type: 'string', + validator: (_rule: Rule, value: string) => { + return new Promise<void>((resolve, reject) => { + if (!value) { + return reject('请输入用户账号'); + } + checkExistence('username', value, form.userId) + .then(() => { + reject('账号已经存在'); + }) + .catch(() => { + resolve(); + }); + }); + }, + trigger: 'blur' + } + ], + nickname: [ + { + required: true, + message: '请输入用户名', + type: 'string', + trigger: 'blur' + } + ], + sex: [ + { + required: true, + message: '请选择性别', + type: 'string', + trigger: 'blur' + } + ], + roles: [ + { + required: true, + message: '请选择角色', + type: 'array', + trigger: 'blur' + } + ], + email: [ + { + pattern: emailReg, + message: '邮箱格式不正确', + type: 'string', + trigger: 'blur' + } + ], + password: [ + { + required: true, + type: 'string', + validator: async (_rule: Rule, value: string) => { + if (isUpdate.value || /^[\S]{5,18}$/.test(value)) { + return Promise.resolve(); + } + return Promise.reject('密码必须为5-18位非空白字符'); + }, + trigger: 'blur' + } + ], + phone: [ + { + pattern: phoneReg, + message: '手机号格式不正确', + type: 'string', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateUser : addUser; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + onDone(); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 关闭当前页面并跳转到列表页面 */ + const onClose = () => { + removePageTab({ key: unref(currentRoute).path }); + push('/list/basic'); + }; + + /* 关闭当前页面并刷新列表页面 */ + const onDone = () => { + removePageTab({ key: unref(currentRoute).path }); + reloadPageTab({ fullPath: '/list/basic' }); + }; + + watch( + () => props.data, + () => { + if (props.data) { + assignFields({ + ...props.data, + password: '' + }); + isUpdate.value = true; + } else { + isUpdate.value = false; + resetFields(); + formRef.value?.clearValidate(); + } + }, + { immediate: true } + ); +</script> diff --git a/src/views-demo/list/basic/components/nickname-filter.vue b/src/views-demo/list/basic/components/nickname-filter.vue new file mode 100644 index 0000000..2aedab1 --- /dev/null +++ b/src/views-demo/list/basic/components/nickname-filter.vue @@ -0,0 +1,57 @@ +<!-- 自定义表格筛选dropdown的内容 --> +<template> + <div style="padding: 8px"> + <div style="margin-bottom: 8px"> + <a-input + placeholder="请输入关键字" + v-model:value="nickname" + @pressEnter="search" + /> + </div> + <a-space> + <a-button size="small" type="primary" @click="search"> + <template #icon> + <search-outlined /> + </template> + <span>搜索</span> + </a-button> + <a-button size="small" style="min-width: 66px" @click="reset"> + 重置 + </a-button> + </a-space> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { SearchOutlined } from '@ant-design/icons-vue'; + + const emit = defineEmits<{ + (e: 'search', nickname: string): void; + }>(); + + const props = defineProps<{ + // 设置筛选选中的方法 + setSelectedKeys: (value: any[]) => void; + // 筛选确认的方法 + confirm: () => void; + // 清除筛选的方法 + clearFilters: () => void; + }>(); + + const nickname = ref(''); + + /* 搜索 */ + const search = () => { + props.setSelectedKeys(nickname.value ? [nickname.value] : []); + props.confirm(); + emit('search', nickname.value); + }; + + /* 重置 */ + const reset = () => { + nickname.value = ''; + props.clearFilters(); + search(); + }; +</script> diff --git a/src/views-demo/list/basic/components/search-form.vue b/src/views-demo/list/basic/components/search-form.vue new file mode 100644 index 0000000..3555196 --- /dev/null +++ b/src/views-demo/list/basic/components/search-form.vue @@ -0,0 +1,165 @@ +<!-- 搜索表单 --> +<template> + <a-card :bordered="false" :body-style="{ paddingBottom: 0 }"> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="用户账号"> + <a-input + v-model:value.trim="form.username" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="性别"> + <a-select v-model:value="form.sex" placeholder="请选择" allow-clear> + <a-select-option value="1">男</a-select-option> + <a-select-option value="2">女</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col + v-if="searchExpand" + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="用户名"> + <a-input + v-model:value.trim="form.nickname" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-if="searchExpand" + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="手机号"> + <a-input + v-model:value.trim="form.phone" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-if="searchExpand" + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="状态"> + <a-select + v-model:value="form.status" + placeholder="请选择" + allow-clear + > + <a-select-option :value="0">正常</a-select-option> + <a-select-option :value="1">冻结</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + <a @click="toggleExpand"> + <span v-if="searchExpand"> + 收起 <up-outlined class="ele-text-small" /> + </span> + <span v-else> + 展开 <down-outlined class="ele-text-small" /> + </span> + </a> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { UserParam } from '@/api/system/user/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: UserParam): void; + (e: 'expand-change', expand: boolean): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<UserParam>({ + username: '', + nickname: '', + sex: undefined, + phone: '', + status: undefined + }); + + // 搜索表单是否展开 + const searchExpand = ref(false); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; + + /* 搜索展开/收起 */ + const toggleExpand = () => { + searchExpand.value = !searchExpand.value; + emit('expand-change', searchExpand.value); + }; +</script> diff --git a/src/views-demo/list/basic/details/index.vue b/src/views-demo/list/basic/details/index.vue new file mode 100644 index 0000000..da49e55 --- /dev/null +++ b/src/views-demo/list/basic/details/index.vue @@ -0,0 +1,121 @@ +<template> + <div class="ele-body"> + <a-card title="基本信息" :bordered="false"> + <a-form + class="ele-form-detail" + :label-col=" + styleResponsive ? { md: 2, sm: 4, xs: 6 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { md: 22, sm: 20, xs: 18 } : { flex: '1' } + " + > + <a-form-item label="账号"> + <div class="ele-text-secondary">{{ form.username }}</div> + </a-form-item> + <a-form-item label="用户名"> + <div class="ele-text-secondary">{{ form.nickname }}</div> + </a-form-item> + <a-form-item label="性别"> + <div class="ele-text-secondary">{{ form.sexName }}</div> + </a-form-item> + <a-form-item label="手机号"> + <div class="ele-text-secondary">{{ form.phone }}</div> + </a-form-item> + <a-form-item label="角色"> + <a-tag v-for="item in form.roles" :key="item.roleId" color="blue"> + {{ item.roleName }} + </a-tag> + </a-form-item> + <a-form-item label="创建时间"> + <div class="ele-text-secondary">{{ form.createTime }}</div> + </a-form-item> + <a-form-item label="状态"> + <a-badge + v-if="typeof form.status === 'number'" + :status="(['processing', 'error'][form.status] as any)" + :text="['正常', '冻结'][form.status]" + /> + </a-form-item> + </a-form> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, watch, unref } from 'vue'; + import { useRouter } from 'vue-router'; + import { message } from 'ant-design-vue/es'; + import { toDateString } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { setPageTabTitle } from '@/utils/page-tab-util'; + import { getUser } from '@/api/system/user'; + import type { User } from '@/api/system/user/model'; + const ROUTE_PATH = '/list/basic/details'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const { currentRoute } = useRouter(); + + // 用户信息 + const { form, assignFields } = useFormData<User>({ + userId: undefined, + username: '', + nickname: '', + sexName: '', + phone: '', + roles: [], + createTime: undefined, + status: undefined + }); + + // 请求状态 + const loading = ref(true); + + const query = () => { + const { params } = unref(currentRoute); + const id = params.id; + if (!id || form.userId === Number(id)) { + return; + } + loading.value = true; + getUser(Number(id)) + .then((data) => { + loading.value = false; + assignFields({ + ...data, + createTime: toDateString(data.createTime) + }); + // 修改页签标题 + if (unref(currentRoute).path.startsWith(ROUTE_PATH)) { + setPageTabTitle(data.nickname + '的信息'); + } + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }; + + watch( + currentRoute, + (route) => { + const { fullPath } = unref(route); + if (!fullPath.startsWith(ROUTE_PATH)) { + return; + } + query(); + }, + { immediate: true } + ); +</script> + +<script lang="ts"> + export default { + name: 'ListBasicDetails' + }; +</script> diff --git a/src/views-demo/list/basic/edit/index.vue b/src/views-demo/list/basic/edit/index.vue new file mode 100644 index 0000000..edd1940 --- /dev/null +++ b/src/views-demo/list/basic/edit/index.vue @@ -0,0 +1,49 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <a-spin :spinning="loading"> + <edit-form :data="user" /> + </a-spin> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, unref } from 'vue'; + import { useRouter } from 'vue-router'; + import { message } from 'ant-design-vue/es'; + import EditForm from '../components/edit-form.vue'; + import { getUser } from '@/api/system/user'; + import type { User } from '@/api/system/user/model'; + + const { currentRoute } = useRouter(); + + // 查询状态 + const loading = ref(true); + + // 用户信息 + const user = ref<User>(); + + /* 查询用户信息 */ + const query = () => { + const { query } = unref(currentRoute); + if (query.id) { + getUser(Number(query.id)) + .then((data) => { + loading.value = false; + user.value = data; + }) + .catch((e) => { + message.error(e.message); + }); + } + }; + + query(); +</script> + +<script lang="ts"> + export default { + name: 'ListBasicEdit' + }; +</script> diff --git a/src/views-demo/list/basic/index.vue b/src/views-demo/list/basic/index.vue new file mode 100644 index 0000000..e544a6b --- /dev/null +++ b/src/views-demo/list/basic/index.vue @@ -0,0 +1,433 @@ +<template> + <div class="ele-body ele-body-card"> + <!-- 搜索表单 --> + <search-form @search="reload" @expand-change="onExpandChange" /> + <a-card :bordered="false"> + <!-- 提示信息 --> + <a-alert type="info" show-icon style="margin-bottom: 16px"> + <template #message> + <span> + 已选择 + <b class="ele-text-primary">{{ selection.length }}</b> + 项数据<em></em> + </span> + <span> + 其中冻结状态的用户有 + <b>{{ selection.filter((d) => d.status === 1).length }} 个</b> + <em></em><em></em> + </span> + <a @click="clearChoose">清空</a> + </template> + </a-alert> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="userId" + title="基础列表" + :resizable="true" + :bordered="bordered" + :striped="striped" + :tools-theme="toolDefault ? 'default' : 'none'" + :height="tableHeight" + :full-height="fixedHeight ? 'calc(100vh - 168px)' : void 0" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :custom-row="customRow" + :scroll="{ x: 1000 }" + :row-selection="{ columnWidth: 38 }" + cache-key="proListBasicTable" + @done="onDone" + > + <!-- 表头工具按钮 --> + <template #toolkit> + <a-space size="middle" style="flex-wrap: wrap"> + <div class="list-tool-item"> + <span>边框</span> + <a-switch v-model:checked="bordered" size="small" /> + </div> + <a-divider type="vertical" /> + <div class="list-tool-item"> + <span>斑马线</span> + <a-switch v-model:checked="striped" size="small" /> + </div> + <a-divider type="vertical" /> + <div class="list-tool-item"> + <span>表头背景</span> + <a-switch v-model:checked="toolDefault" size="small" /> + </div> + <a-divider type="vertical" /> + <div class="list-tool-item"> + <span>高度铺满</span> + <a-switch v-model:checked="fixedHeight" size="small" /> + </div> + <a-divider type="vertical" /> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-dropdown :disabled="!selection.length"> + <a-button class="ele-btn-icon"> + <span>批量操作 <down-outlined class="ele-text-small" /></span> + </a-button> + <template #overlay> + <a-menu @click="onDropClick"> + <a-menu-item key="del">批量删除</a-menu-item> + <a-menu-item key="edit">批量修改</a-menu-item> + </a-menu> + </template> + </a-dropdown> + <a-divider type="vertical" /> + </a-space> + </template> + <!-- 自定义列 --> + <template #bodyCell="{ column, record }"> + <!-- 头像列 --> + <template v-if="column.key === 'avatar'"> + <a-avatar + v-if="record.avatar" + :src="record.avatar" + :size="32" + @click.stop="" + /> + <a-avatar v-else class="ele-bg-primary" :size="32" @click.stop=""> + {{ + record.nickname && record.nickname.length > 2 + ? record.nickname.substring(record.nickname.length - 2) + : record.nickname + }} + </a-avatar> + </template> + <!-- 用户名列 --> + <template v-else-if="column.key === 'nickname'"> + <router-link + :to="'/list/basic/details/' + record.userId" + @click.stop="" + > + {{ record.nickname }} + </router-link> + </template> + <!-- 状态列 --> + <template v-else-if="column.key === 'status'"> + <a-badge + :status="(['processing', 'error'][record.status] as any)" + :text="['正常', '冻结'][record.status]" + /> + </template> + <!-- 操作列 --> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click.stop="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a class="ele-text-danger" @click.stop="remove(record)">删除</a> + </a-space> + </template> + </template> + <!-- 自定义筛选dropdown --> + <template + #customFilterDropdown="{ + column, + setSelectedKeys, + confirm, + clearFilters + }" + > + <!-- 用户名 --> + <template v-if="column.key === 'nickname'"> + <nickname-filter + :setSelectedKeys="setSelectedKeys" + :confirm="confirm" + :clearFilters="clearFilters" + /> + </template> + </template> + </ele-pro-table> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, computed, nextTick } from 'vue'; + import { useRouter } from 'vue-router'; + import { message } from 'ant-design-vue/es'; + import { DownOutlined, PlusOutlined } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem, + EleProTableDone + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import SearchForm from './components/search-form.vue'; + import NicknameFilter from './components/nickname-filter.vue'; + import { pageUsers } from '@/api/system/user'; + import type { User, UserParam } from '@/api/system/user/model'; + import { removePageTab } from '@/utils/page-tab-util'; + import { useI18n } from 'vue-i18n'; + + const { t } = useI18n(); + + const { push } = useRouter(); + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = computed<ColumnItem[]>(() => { + return [ + { + key: 'index', + width: 52, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + width: 80, + title: t('list.basic.table.avatar'), + key: 'avatar', + dataIndex: 'avatar', + ellipsis: true, + align: 'center' + }, + { + title: t('list.basic.table.username'), + dataIndex: 'username', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + width: 160, + minWidth: 100, + resizable: true + }, + { + title: t('list.basic.table.nickname'), + key: 'nickname', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false, + customFilterDropdown: true, + ellipsis: true, + width: 160, + minWidth: 100, + resizable: true + }, + { + title: t('list.basic.table.organizationName'), + dataIndex: 'organizationName', + sorter: true, + showSorterTooltip: false, + hideInTable: true, + ellipsis: true, + width: 160, + minWidth: 100, + resizable: true + }, + { + title: t('list.basic.table.phone'), + dataIndex: 'phone', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + width: 160, + minWidth: 100, + resizable: true + }, + { + title: t('list.basic.table.sexName'), + dataIndex: 'sexName', + width: 80, + align: 'center', + sorter: true, + showSorterTooltip: false, + filters: [ + { + text: '男', + value: '男' + }, + { + text: '女', + value: '女' + } + ], + filterMultiple: false, + ellipsis: true + }, + { + title: t('list.basic.table.createTime'), + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text), + customCell: (record: User) => { + return { + onClick: (e: MouseEvent) => { + e.stopPropagation(); + message.info('点击了创建时间: ' + record.createTime); + } + }; + }, + defaultSortOrder: 'ascend', + width: 160, + minWidth: 100, + resizable: true + }, + { + title: t('list.basic.table.status'), + key: 'status', + dataIndex: 'status', + sorter: true, + showSorterTooltip: false, + width: 90, + align: 'center', + ellipsis: true + }, + { + title: t('list.basic.table.action'), + key: 'action', + width: 110, + align: 'center', + hideInSetting: true, + fixed: 'right' + } + ]; + }); + + // 表格选中数据 + const selection = ref<User[]>([]); + + // 表格是否显示边框 + const bordered = ref(false); + + // 表格是否斑马纹 + const striped = ref(false); + + // 表头工具栏风格 + const toolDefault = ref(false); + + // 表格固定高度 + const fixedHeight = ref(false); + + // 搜索是否展开 + const searchExpand = ref(false); + + // 表格高度 + const tableHeight = computed(() => { + return fixedHeight.value + ? searchExpand.value + ? 'calc(100vh - 618px)' + : 'calc(100vh - 562px)' + : void 0; + }); + + // 表格数据源 + const datasource: DatasourceFunction = ({ + page, + limit, + where, + orders, + filters + }) => { + return pageUsers({ + ...where, + ...orders, + ...filters, + page, + limit + }); + }; + + /* 表格数据请求完成事件 */ + const onDone: EleProTableDone<User> = ({ data }) => { + // 回显 id 为 19、22、21 的数据的复选框 + const ids = [19, 22, 21]; + selection.value = data.filter((d) => d.userId && ids.includes(d.userId)); + }; + + /* 自定义行属性 */ + const customRow = (record: User) => { + return { + // 行点击事件 + onClick: () => { + if (selection.value.some((d) => d.userId === record.userId)) { + selection.value = selection.value.filter( + (d) => d.userId !== record.userId + ); + } else { + selection.value = selection.value.concat([record]); + } + } + }; + }; + + /* 刷新表格 */ + const reload = (where?: UserParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 清空选择 */ + const clearChoose = () => { + selection.value = []; + }; + + /* 编辑 */ + const openEdit = (row?: User) => { + const path = row ? '/list/basic/edit' : '/list/basic/add'; + removePageTab({ key: path }); + nextTick(() => { + push({ + path, + query: row ? { id: row.userId } : undefined + }); + }); + }; + + /* 删除 */ + const remove = (row: User) => { + console.log(row); + const hide = messageLoading({ + content: '请求中...', + duration: 0, + mask: true + }); + setTimeout(() => { + hide(); + message.info('点击了删除'); + }, 1500); + }; + + /* 下拉按钮点击 */ + const onDropClick = ({ key }) => { + if (key === 'del') { + message.info('点击了批量删除'); + } else if (key === 'edit') { + message.info('点击了批量修改'); + } + }; + + /* 搜索展开改变事件 */ + const onExpandChange = (value: boolean) => { + searchExpand.value = value; + }; +</script> + +<script lang="ts"> + export default { + name: 'ListBasic' + }; +</script> + +<style lang="less" scoped> + .list-tool-item { + & > span { + vertical-align: middle; + margin-right: 6px; + opacity: 0.9; + } + } +</style> diff --git a/src/views-demo/list/card/application/index.vue b/src/views-demo/list/card/application/index.vue new file mode 100644 index 0000000..83ed6e5 --- /dev/null +++ b/src/views-demo/list/card/application/index.vue @@ -0,0 +1,149 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false" :body-style="{ padding: '44px 16px' }"> + <div style="max-width: 500px; margin: 0 auto"> + <a-input-search + size="large" + enter-button="搜索" + placeholder="请输入内容" + v-model:value="keyword" + /> + </div> + </a-card> + <a-row :gutter="16"> + <a-col + v-for="(item, index) in data" + :key="index" + v-bind=" + styleResponsive + ? { xl: 6, lg: 8, md: 12, sm: 12, xs: 24 } + : { span: 6 } + " + > + <a-card :bordered="false" hoverable style="margin-top: 16px"> + <div class="ele-cell" style="margin-bottom: 16px"> + <a-avatar size="large" :src="item.cover" /> + <h6 class="ele-cell-content ele-elip">{{ item.title }}</h6> + </div> + <div class="ele-elip" style="margin-bottom: 6px"> + 网址: {{ item.url }} + </div> + <div class="ele-elip">最后更新时间: {{ item.time }}</div> + <template #actions> + <a-tooltip title="下载"> + <download-outlined /> + </a-tooltip> + <a-tooltip title="编辑"> + <edit-outlined /> + </a-tooltip> + <a-tooltip title="分享"> + <share-alt-outlined /> + </a-tooltip> + <a-dropdown placement="bottom"> + <ellipsis-outlined /> + <template #overlay> + <a-menu> + <a-menu-item>1st menu item</a-menu-item> + <a-menu-item>2nd menu item</a-menu-item> + <a-menu-item>3rd menu item</a-menu-item> + </a-menu> + </template> + </a-dropdown> + </template> + </a-card> + </a-col> + </a-row> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { + DownloadOutlined, + EditOutlined, + ShareAltOutlined, + EllipsisOutlined + } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + interface DataType { + title: string; + url: string; + time: string; + cover: string; + } + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const data = ref<DataType[]>([]); + + data.value = [ + { + title: 'ElementUI', + url: 'https://element.eleme.cn', + time: '2 小时前', + cover: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + title: 'Vue.js', + url: 'https://cn.vuejs.org', + time: '4 小时前', + cover: + 'https://cdn.eleadmin.com/20200609/b6a811873e704db49db994053a5019b2.jpg' + }, + { + title: 'Vuex', + url: 'https://vuex.vuejs.org', + time: '12 小时前', + cover: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + }, + { + title: 'Vue Router', + url: 'https://vuex.vuejs.org', + time: '14 小时前', + cover: + 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg' + }, + { + title: 'Sass', + url: 'https://www.sass.hk', + time: '10 小时前', + cover: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + }, + { + title: 'Axios', + url: 'http://www.axios-js.com', + time: '16 小时前', + cover: + 'https://cdn.eleadmin.com/20200609/faa0202700ee455b90fe77d8bef98bc0.jpg' + }, + { + title: 'Webpack', + url: 'https://www.webpackjs.com', + time: '6 小时前', + cover: + 'https://cdn.eleadmin.com/20200609/d3519518b00d42d3936b2ab5ce3a4cc3.jpg' + }, + { + title: 'Node.js', + url: 'http://nodejs.cn', + time: '8 小时前', + cover: + 'https://cdn.eleadmin.com/20200609/fe9196dd091e438fba115205c1003ee7.jpg' + } + ]; + + const keyword = ref(''); +</script> + +<script lang="ts"> + export default { + name: 'ListCardApplication' + }; +</script> diff --git a/src/views-demo/list/card/article/index.vue b/src/views-demo/list/card/article/index.vue new file mode 100644 index 0000000..e3261c9 --- /dev/null +++ b/src/views-demo/list/card/article/index.vue @@ -0,0 +1,268 @@ +<template> + <div :class="['ele-body', { 'list-article-responsive': styleResponsive }]"> + <a-card + :bordered="false" + :body-style="{ padding: '44px 16px' }" + style="margin-bottom: 16px" + > + <div style="max-width: 500px; margin: 0 auto"> + <a-input-search + size="large" + enter-button="搜索" + placeholder="请输入内容" + v-model:value="keyword" + /> + </div> + </a-card> + <a-card :bordered="false" :body-style="{ padding: '16px 8px' }"> + <a-image-preview-group> + <a-list + :data-source="data" + :loading="loading && page === 1" + item-layout="vertical" + size="large" + > + <template #renderItem="{ item, index }"> + <a-list-item :key="index"> + <a-list-item-meta :title="item.title"> + <template #description> + <a-tag v-for="(tag, i) in item.tags" :key="i"> + {{ tag }} + </a-tag> + </template> + </a-list-item-meta> + <div class="ele-text-heading"> + {{ item.content }} + </div> + <div class="ele-cell" style="margin-top: 16px"> + <a-avatar :src="item.avatar" size="small" /> + <div class="ele-cell-content"> + {{ item.user }} 发表于 {{ item.time }} + </div> + </div> + <template #extra> + <div class="list-image-wrap"> + <a-image width="100%" :src="item.cover" /> + </div> + </template> + <template #actions> + <span> + <like-outlined /> + <span><s></s>{{ item.likes }}</span> + </span> + <span> + <star-outlined /> + <span><s></s>{{ item.favorites }}</span> + </span> + <span> + <message-outlined /> + <span><s></s>{{ item.comments }}</span> + </span> + </template> + </a-list-item> + </template> + <template #loadMore> + <div class="ele-text-center" style="margin-top: 16px"> + <a-button v-if="page !== 1" :loading="loading" @click="query"> + {{ loading ? '加载中..' : '加载更多' }} + </a-button> + </div> + </template> + </a-list> + </a-image-preview-group> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { + LikeOutlined, + StarOutlined, + MessageOutlined + } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + interface DataType { + title: string; + content: string; + time: string; + cover: string; + tags: string[]; + user: string; + avatar: string; + favorites: number; + likes: number; + comments: number; + } + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const data = ref<DataType[]>([]); + + const loading = ref(false); + + const page = ref(2); + + const query = () => { + loading.value = true; + setTimeout(() => { + loading.value = false; + page.value++; + data.value = data.value.concat(data.value.slice(0, 3)); + }, 1000); + }; + + data.value = [ + { + title: 'ElementUI', + content: + 'Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的组件库,提供了配套设计资源,帮助你的网站快速成型。', + time: '2 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/RZ8FQmZfHkcffMlTBCJllBFjEhEsObVo.jpg', + tags: ['EleAdminPro', 'UI框架', '设计语言'], + user: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg', + favorites: 104, + likes: 189, + comments: 15 + }, + { + title: 'Vue.js', + content: + 'Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。', + time: '4 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/WLXm7gp1EbLDtvVQgkeQeyq5OtDm00Jd.jpg', + tags: ['EleAdminPro', 'UI框架', '设计语言'], + user: '你的名字很好听', + avatar: + 'https://cdn.eleadmin.com/20200609/b6a811873e704db49db994053a5019b2.jpg', + favorites: 104, + likes: 189, + comments: 15 + }, + { + title: 'Vuex', + content: + 'Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。', + time: '12 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/4Z0QR2L0J1XStxBh99jVJ8qLfsGsOgjU.jpg', + tags: ['EleAdminPro', 'UI框架', '设计语言'], + user: '全村人的希望', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg', + favorites: 104, + likes: 189, + comments: 15 + }, + { + title: 'Vue Router', + content: + 'Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。', + time: '14 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/ttkIjNPlVDuv4lUTvRX8GIlM2QqSe0jg.jpg', + tags: ['EleAdminPro', 'UI框架', '设计语言'], + user: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg', + favorites: 104, + likes: 189, + comments: 15 + }, + { + title: 'Sass', + content: 'Sass 是世界上最成熟、稳定、强大的专业级 CSS 扩展语言。', + time: '10 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/fAenQ8nvRjL7x0i0jEfuDBZHvJfHf3v6.jpg', + tags: ['EleAdminPro', 'UI框架', '设计语言'], + user: '酷酷的大叔', + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg', + favorites: 104, + likes: 189, + comments: 15 + }, + { + title: 'Axios', + content: + 'Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。', + time: '16 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/LrCTN2j94lo9N7wEql7cBr1Ux4rHMvmZ.jpg', + tags: ['EleAdminPro', 'UI框架', '设计语言'], + user: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg', + favorites: 104, + likes: 189, + comments: 15 + }, + { + title: 'Webpack', + content: + 'webpack 是一个模块打包器。webpack 的主要目标是将 JavaScript 文件打包在一起,打包后的文件用于在浏览器中使用。', + time: '6 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/yeKvhT20lMU0f1T3Y743UlGEOLLnZSnp.jpg', + tags: ['EleAdminPro', 'UI框架', '设计语言'], + user: '全村人的希望', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg', + favorites: 104, + likes: 189, + comments: 15 + }, + { + title: 'Node.js', + content: + 'Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。', + time: '8 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/CyrCNmTJfv7D6GFAg39bjT3eRkkRm5dI.jpg', + tags: ['EleAdminPro', 'UI框架', '设计语言'], + user: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg', + favorites: 104, + likes: 189, + comments: 15 + } + ]; + + const keyword = ref(''); +</script> + +<script lang="ts"> + export default { + name: 'ListCardArticle' + }; +</script> + +<style lang="less" scoped> + .list-image-wrap { + width: 280px; + border-radius: 6px; + overflow: hidden; + } + + @media screen and (max-width: 880px) { + .list-article-responsive .list-image-wrap { + width: 200px; + } + } + + @media screen and (max-width: 576px) { + .list-article-responsive .list-image-wrap { + width: 100%; + } + } +</style> diff --git a/src/views-demo/list/card/project/index.vue b/src/views-demo/list/card/project/index.vue new file mode 100644 index 0000000..5eddc66 --- /dev/null +++ b/src/views-demo/list/card/project/index.vue @@ -0,0 +1,314 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false" :body-style="{ padding: '44px 16px' }"> + <div style="max-width: 500px; margin: 0 auto"> + <a-input-search + size="large" + enter-button="搜索" + placeholder="请输入内容" + v-model:value="keyword" + /> + </div> + </a-card> + <a-row :gutter="16"> + <a-col + v-for="(item, index) in data" + :key="index" + v-bind=" + styleResponsive + ? { xl: 6, lg: 8, md: 12, sm: 12, xs: 24 } + : { span: 6 } + " + > + <a-card :bordered="false" hoverable style="margin-top: 16px"> + <template #cover> + <img :src="item.cover" alt="" /> + </template> + <a-card-meta :title="item.title"> + <template #description> + <div class="project-list-desc" :title="item.content"> + {{ item.content }} + </div> + </template> + </a-card-meta> + <div class="ele-cell"> + <div class="ele-cell-content ele-text-secondary"> + {{ item.time }} + </div> + <ele-avatar-list :data="item.users" size="small" /> + </div> + </a-card> + </a-col> + </a-row> + <div class="ele-text-center" style="margin-top: 18px"> + <a-pagination + :total="count" + v-model:current="page" + v-model:page-size="limit" + @change="query" + /> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + interface UserType { + name: string; + avatar: string; + } + + interface DataType { + title: string; + content: string; + time: string; + cover: string; + users: UserType[]; + } + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const data = ref<DataType[]>([]); + + const keyword = ref(''); + + // 第几页 + const page = ref(1); + + // 每页多少条 + const limit = ref(8); + + // 总数量 + const count = ref(0); + + /* 查询数据 */ + const query = () => { + count.value = 40; + data.value = [ + { + title: 'ElementUI', + content: + 'Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的组件库,提供了配套设计资源,帮助你的网站快速成型。', + time: '2 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/RZ8FQmZfHkcffMlTBCJllBFjEhEsObVo.jpg', + users: [ + { + name: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + name: '酷酷的大叔', + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + }, + { + name: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + } + ] + }, + { + title: 'Vue.js', + content: + 'Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。', + time: '4 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/WLXm7gp1EbLDtvVQgkeQeyq5OtDm00Jd.jpg', + users: [ + { + name: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + name: '酷酷的大叔', + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + }, + { + name: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + } + ] + }, + { + title: 'Vuex', + content: + 'Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。', + time: '12 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/4Z0QR2L0J1XStxBh99jVJ8qLfsGsOgjU.jpg', + users: [ + { + name: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + name: '酷酷的大叔', + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + }, + { + name: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + } + ] + }, + { + title: 'Vue Router', + content: + 'Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。', + time: '14 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/ttkIjNPlVDuv4lUTvRX8GIlM2QqSe0jg.jpg', + users: [ + { + name: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + name: '酷酷的大叔', + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + }, + { + name: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + } + ] + }, + { + title: 'Sass', + content: 'Sass 是世界上最成熟、稳定、强大的专业级 CSS 扩展语言。', + time: '10 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/fAenQ8nvRjL7x0i0jEfuDBZHvJfHf3v6.jpg', + users: [ + { + name: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + name: '酷酷的大叔', + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + }, + { + name: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + } + ] + }, + { + title: 'Axios', + content: + 'Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。', + time: '16 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/LrCTN2j94lo9N7wEql7cBr1Ux4rHMvmZ.jpg', + users: [ + { + name: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + name: '酷酷的大叔', + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + }, + { + name: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + } + ] + }, + { + title: 'Webpack', + content: + 'webpack 是一个模块打包器。webpack 的主要目标是将 JavaScript 文件打包在一起,打包后的文件用于在浏览器中使用。', + time: '6 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/yeKvhT20lMU0f1T3Y743UlGEOLLnZSnp.jpg', + users: [ + { + name: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + name: '酷酷的大叔', + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + }, + { + name: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + } + ] + }, + { + title: 'Node.js', + content: + 'Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。', + time: '8 小时前', + cover: + 'https://cdn.eleadmin.com/20200610/CyrCNmTJfv7D6GFAg39bjT3eRkkRm5dI.jpg', + users: [ + { + name: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + name: '酷酷的大叔', + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + }, + { + name: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + } + ] + } + ]; + }; + + query(); +</script> + +<script lang="ts"> + export default { + name: 'ListCardProject' + }; +</script> + +<style lang="less" scoped> + .project-list-desc { + height: 44px; + line-height: 22px; + margin-bottom: 20px; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } +</style> diff --git a/src/views-demo/login/index.vue b/src/views-demo/login/index.vue new file mode 100644 index 0000000..9197c22 --- /dev/null +++ b/src/views-demo/login/index.vue @@ -0,0 +1,371 @@ +<template> + <div + :class="[ + 'login-wrapper', + ['', 'login-form-right', 'login-form-left'][direction] + ]" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + class="login-form ele-bg-white" + > + <h4>{{ t('login.title') }}</h4> + <a-form-item name="username"> + <a-input + allow-clear + size="large" + v-model:value="form.username" + :placeholder="t('login.username')" + > + <template #prefix> + <user-outlined /> + </template> + </a-input> + </a-form-item> + <a-form-item name="password"> + <a-input-password + size="large" + v-model:value="form.password" + :placeholder="t('login.password')" + > + <template #prefix> + <lock-outlined /> + </template> + </a-input-password> + </a-form-item> + <a-form-item name="code"> + <div class="login-input-group"> + <a-input + allow-clear + size="large" + v-model:value="form.code" + :placeholder="t('login.code')" + > + <template #prefix> + <safety-certificate-outlined /> + </template> + </a-input> + <a-button class="login-captcha" @click="changeCaptcha"> + <img v-if="captcha" :src="captcha" alt="" /> + </a-button> + </div> + </a-form-item> + <a-form-item> + <a-checkbox v-model:checked="form.remember"> + {{ t('login.remember') }} + </a-checkbox> + <router-link + to="/forget" + class="ele-pull-right" + style="line-height: 22px" + > + {{ t('login.forget') }} + </router-link> + </a-form-item> + <a-form-item> + <a-button + block + size="large" + type="primary" + :loading="loading" + @click="submit" + > + {{ loading ? t('login.loading') : t('login.login') }} + </a-button> + </a-form-item> + <div class="ele-text-center" style="padding-bottom: 32px"> + <qq-outlined class="login-oauth-icon" style="background: #3492ed" /> + <wechat-outlined class="login-oauth-icon" style="background: #4daf29" /> + <weibo-outlined class="login-oauth-icon" style="background: #cf1900" /> + </div> + </a-form> + <div class="login-copyright"> + copyright © 2022 eleadmin.com all rights reserved. + </div> + <!-- 多语言切换 --> + <div style="position: absolute; right: 30px; top: 20px; z-index: 999"> + <i18n-icon + placement="bottomLeft" + :style="{ fontSize: '18px', color: '#fff' }" + /> + </div> + <!-- 实际项目去掉这段 --> + <div style="position: absolute; left: 30px; top: 20px; z-index: 999"> + <a-radio-group v-model:value="direction" size="small"> + <a-radio-button :value="2">居左</a-radio-button> + <a-radio-button :value="0">居中</a-radio-button> + <a-radio-button :value="1">居右</a-radio-button> + </a-radio-group> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive, computed, unref } from 'vue'; + import { useI18n } from 'vue-i18n'; + import { useRouter } from 'vue-router'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { + UserOutlined, + LockOutlined, + SafetyCertificateOutlined, + QqOutlined, + WechatOutlined, + WeiboOutlined + } from '@ant-design/icons-vue'; + import I18nIcon from '@/layout/components/i18n-icon.vue'; + import { getToken } from '@/utils/token-util'; + import { goHomeRoute, cleanPageTabs } from '@/utils/page-tab-util'; + import { login, getCaptcha } from '@/api/login'; + + const { currentRoute } = useRouter(); + const { t } = useI18n(); + + // 登录框方向, 0 居中, 1 居右, 2 居左 + const direction = ref(0); + + // + const formRef = ref<FormInstance | null>(null); + + // 加载状态 + const loading = ref(false); + + // 表单数据 + const form = reactive({ + username: 'admin', + password: 'admin', + code: '', + remember: true + }); + + // 验证码 base64 数据 + const captcha = ref(''); + + // 验证码内容, 实际项目去掉 + const text = ref(''); + + // 表单验证规则 + const rules = computed<Record<string, Rule[]>>(() => { + return { + username: [ + { + required: true, + message: t('login.username'), + type: 'string', + trigger: 'blur' + } + ], + password: [ + { + required: true, + message: t('login.password'), + type: 'string', + trigger: 'blur' + } + ], + code: [ + { + required: true, + message: t('login.code'), + type: 'string', + trigger: 'blur' + } + ] + }; + }); + + /* 跳转到首页 */ + const goHome = () => { + const { query } = unref(currentRoute); + goHomeRoute(query.from as string); + }; + + /* 提交 */ + const submit = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + if (form.code.toLowerCase() !== text.value) { + message.error('验证码错误'); + return; + } + loading.value = true; + login(form) + .then((msg) => { + message.success(msg); + cleanPageTabs(); + goHome(); + }) + .catch((e: Error) => { + message.error(e.message); + loading.value = false; + }); + }) + .catch(() => {}); + }; + + /* 获取图形验证码 */ + const changeCaptcha = () => { + // 这里演示的验证码是后端返回base64格式的形式, 如果后端地址直接是图片请参考忘记密码页面 + getCaptcha() + .then((data) => { + captcha.value = data.base64; + // 实际项目后端一般会返回验证码的key而不是直接返回验证码的内容, 登录用key去验证, 你可以根据自己后端接口修改 + text.value = data.text; + // 自动回填验证码, 实际项目去掉这个 + form.code = data.text; + formRef.value?.clearValidate(); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + if (getToken()) { + goHome(); + } else { + changeCaptcha(); + } +</script> + +<style lang="less" scoped> + /* 背景 */ + .login-wrapper { + padding: 48px 16px 0 16px; + position: relative; + box-sizing: border-box; + background-image: url('@/assets/bg-login.jpg'); + background-repeat: no-repeat; + background-size: cover; + min-height: 100vh; + + &:before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.2); + } + } + + /* 卡片 */ + .login-form { + width: 360px; + margin: 0 auto; + max-width: 100%; + padding: 0 28px; + box-sizing: border-box; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15); + border-radius: 2px; + position: relative; + z-index: 2; + + h4 { + padding: 22px 0; + text-align: center; + } + } + + .login-form-right .login-form { + margin: 0 15% 0 auto; + } + + .login-form-left .login-form { + margin: 0 auto 0 15%; + } + + /* 验证码 */ + .login-input-group { + display: flex; + align-items: center; + + :deep(.ant-input-affix-wrapper) { + flex: 1; + } + + .login-captcha { + width: 102px; + height: 40px; + margin-left: 10px; + padding: 0; + + & > img { + width: 100%; + height: 100%; + } + } + } + + /* 第三方登录图标 */ + .login-oauth-icon { + color: #fff; + padding: 5px; + margin: 0 12px; + font-size: 18px; + border-radius: 50%; + cursor: pointer; + } + + /* 底部版权 */ + .login-copyright { + color: #eee; + text-align: center; + padding: 48px 0 22px 0; + position: relative; + z-index: 1; + } + + /* 响应式 */ + @media screen and (min-height: 640px) { + .login-wrapper { + padding-top: 0; + } + + .login-form { + position: absolute; + top: 50%; + left: 50%; + transform: translateX(-50%); + margin-top: -230px; + } + + .login-form-right .login-form, + .login-form-left .login-form { + left: auto; + right: 15%; + transform: translateX(0); + margin: -230px auto auto auto; + } + + .login-form-left .login-form { + right: auto; + left: 15%; + } + + .login-copyright { + position: absolute; + left: 0; + right: 0; + bottom: 0; + } + } + + @media screen and (max-width: 768px) { + .login-form-right .login-form, + .login-form-left .login-form { + left: 50%; + right: auto; + margin-left: 0; + margin-right: auto; + transform: translateX(-50%); + } + } +</style> diff --git a/src/views-demo/result/fail/index.vue b/src/views-demo/result/fail/index.vue new file mode 100644 index 0000000..c3881e9 --- /dev/null +++ b/src/views-demo/result/fail/index.vue @@ -0,0 +1,57 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <div style="max-width: 960px; margin: 0 auto"> + <a-result + status="error" + title="提交失败" + sub-title="请核对并修改以下信息后,再重新提交。" + > + <div>您提交的内容有如下错误:</div> + <div class="error-tips-item"> + <close-circle-outlined class="ele-text-danger" /> + <div>您的账户已被冻结</div> + <a>立即解冻></a> + </div> + <div class="error-tips-item"> + <close-circle-outlined class="ele-text-danger" /> + <div>您的账户还不具备申请资格</div> + <a>立即升级></a> + </div> + <template #extra> + <a-space size="middle"> + <a-button type="primary">返回修改</a-button> + <a-button>重新提交</a-button> + </a-space> + </template> + </a-result> + </div> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { CloseCircleOutlined } from '@ant-design/icons-vue'; +</script> + +<script lang="ts"> + export default { + name: 'ResultFail' + }; +</script> + +<style lang="less" scoped> + .error-tips-item { + display: flex; + align-items: center; + margin-top: 16px; + + & > div { + margin: 0 10px; + } + + a { + white-space: nowrap; + } + } +</style> diff --git a/src/views-demo/result/success/index.vue b/src/views-demo/result/success/index.vue new file mode 100644 index 0000000..9f80288 --- /dev/null +++ b/src/views-demo/result/success/index.vue @@ -0,0 +1,28 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <div style="max-width: 960px; margin: 0 auto"> + <a-result + status="success" + title="提交成功" + sub-title="提交结果页用于反馈一系列操作任务的处理结果,如果仅是简单操作,使用 Message 全局提示反馈即可。灰色区域可以显示一些补充的信息。" + > + <div>已提交申请,等待部门审核。</div> + <template #extra> + <a-space size="middle"> + <a-button type="primary">返回列表</a-button> + <a-button>查看项目</a-button> + <a-button>打印</a-button> + </a-space> + </template> + </a-result> + </div> + </a-card> + </div> +</template> + +<script lang="ts"> + export default { + name: 'ResultSuccess' + }; +</script> diff --git a/src/views-demo/system/dictionary/components/dict-data-edit.vue b/src/views-demo/system/dictionary/components/dict-data-edit.vue new file mode 100644 index 0000000..8a7cb77 --- /dev/null +++ b/src/views-demo/system/dictionary/components/dict-data-edit.vue @@ -0,0 +1,186 @@ +<!-- 字典项编辑弹窗 --> +<template> + <ele-modal + :width="460" + :visible="visible" + :confirm-loading="loading" + :body-style="{ paddingBottom: '8px' }" + :title="isUpdate ? '修改字典项' : '添加字典项'" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 6, sm: 6, xs: 24 } : { flex: '98px' }" + :wrapper-col=" + styleResponsive ? { md: 18, sm: 18, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="字典项名称" name="dictDataName"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入字典项名称" + v-model:value="form.dictDataName" + /> + </a-form-item> + <a-form-item label="字典项值" name="dictDataCode"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入字典项值" + v-model:value="form.dictDataCode" + /> + </a-form-item> + <a-form-item label="排序号" name="sortNumber"> + <a-input-number + :min="0" + :max="9999" + class="ele-fluid" + placeholder="请输入排序号" + v-model:value="form.sortNumber" + /> + </a-form-item> + <a-form-item label="备注"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入备注" + v-model:value="form.comments" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { + addDictionaryData, + updateDictionaryData + } from '@/api/system/dictionary-data'; + import type { DictionaryData } from '@/api/system/dictionary-data/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: DictionaryData | null; + // 字典id + dictId: number; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<DictionaryData>({ + dictDataId: undefined, + dictDataName: '', + dictDataCode: '', + sortNumber: '', + comments: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + dictDataName: [ + { + required: true, + message: '请输入字典项名称', + type: 'string', + trigger: 'blur' + } + ], + dictDataCode: [ + { + required: true, + message: '请输入字典项值', + type: 'string', + trigger: 'blur' + } + ], + sortNumber: [ + { + required: true, + message: '请输入排序号', + type: 'number', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value + ? updateDictionaryData + : addDictionaryData; + saveOrUpdate({ + ...form, + dictId: props.dictId + }) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views-demo/system/dictionary/components/dict-data-search.vue b/src/views-demo/system/dictionary/components/dict-data-search.vue new file mode 100644 index 0000000..8dfafa3 --- /dev/null +++ b/src/views-demo/system/dictionary/components/dict-data-search.vue @@ -0,0 +1,86 @@ +<!-- 搜索表单 --> +<template> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { xl: 6, lg: 8, md: 11, sm: 24, xs: 24 } : { span: 6 } + " + > + <a-input + v-model:value.trim="form.keywords" + placeholder="输入关键字搜索" + allow-clear + /> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 18, lg: 16, md: 13, sm: 24, xs: 24 } + : { span: 18 } + " + > + <a-space :size="10" style="flex-wrap: wrap"> + <a-button type="primary" class="ele-btn-icon" @click="search"> + <template #icon> + <search-outlined /> + </template> + <span>查询</span> + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="add"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button danger type="primary" class="ele-btn-icon" @click="remove"> + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { + PlusOutlined, + DeleteOutlined, + SearchOutlined + } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { DictionaryDataParam } from '@/api/system/dictionary-data/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: DictionaryDataParam): void; + (e: 'add'): void; + (e: 'remove'): void; + }>(); + + // 表单数据 + const { form } = useFormData<DictionaryDataParam>({ + keywords: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 添加 */ + const add = () => { + emit('add'); + }; + + /* 删除 */ + const remove = () => { + emit('remove'); + }; +</script> diff --git a/src/views-demo/system/dictionary/components/dict-data.vue b/src/views-demo/system/dictionary/components/dict-data.vue new file mode 100644 index 0000000..eb1821b --- /dev/null +++ b/src/views-demo/system/dictionary/components/dict-data.vue @@ -0,0 +1,212 @@ +<template> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="dictDataId" + :columns="columns" + :datasource="datasource" + tool-class="ele-toolbar-form" + v-model:selection="selection" + :row-selection="{ columnWidth: 48 }" + :scroll="{ x: 800 }" + height="calc(100vh - 290px)" + tools-theme="default" + bordered + cache-key="proSystemDictDataTable" + class="sys-dict-data-table" + > + <template #toolbar> + <dict-data-search + @search="reload" + @add="openEdit()" + @remove="removeBatch" + /> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此字典项吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + <!-- 编辑弹窗 --> + <dict-data-edit + v-model:visible="showEdit" + :data="current" + :dict-id="dictId" + @done="reload" + /> +</template> + +<script lang="ts" setup> + import { createVNode, ref, watch } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { ExclamationCircleOutlined } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import DictDataSearch from './dict-data-search.vue'; + import DictDataEdit from './dict-data-edit.vue'; + import { + pageDictionaryData, + removeDictionaryData, + removeDictionaryDataBatch + } from '@/api/system/dictionary-data'; + import type { + DictionaryData, + DictionaryDataParam + } from '@/api/system/dictionary-data/model'; + + const props = defineProps<{ + // 字典id + dictId: number; + }>(); + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + title: '字典项名称', + dataIndex: 'dictDataName', + ellipsis: true, + sorter: true, + showSorterTooltip: false + }, + { + title: '字典项值', + dataIndex: 'dictDataCode', + ellipsis: true, + sorter: true, + showSorterTooltip: false + }, + { + title: '排序号', + dataIndex: 'sortNumber', + sorter: true, + showSorterTooltip: false, + width: 120, + align: 'center' + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '操作', + key: 'action', + width: 130, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<DictionaryData[]>([]); + + // 当前编辑数据 + const current = ref<DictionaryData | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageDictionaryData({ + ...where, + ...orders, + page, + limit, + dictId: props.dictId + }); + }; + + /* 刷新表格 */ + const reload = (where?: DictionaryDataParam) => { + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: DictionaryData) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 删除单个 */ + const remove = (row: DictionaryData) => { + const hide = messageLoading('请求中..', 0); + removeDictionaryData(row.dictDataId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的字典项吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeDictionaryDataBatch(selection.value.map((d) => d.dictDataId)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + // 监听字典id变化 + watch( + () => props.dictId, + () => { + reload(); + } + ); +</script> + +<style lang="less" scoped> + .sys-dict-data-table :deep(.ant-table-body) { + overflow: auto !important; + overflow: overlay !important; + } + + .sys-dict-data-table :deep(.ant-table-pagination.ant-pagination) { + padding: 0 4px; + margin-bottom: 0; + } +</style> diff --git a/src/views-demo/system/dictionary/components/dict-edit.vue b/src/views-demo/system/dictionary/components/dict-edit.vue new file mode 100644 index 0000000..b2de3db --- /dev/null +++ b/src/views-demo/system/dictionary/components/dict-edit.vue @@ -0,0 +1,176 @@ +<!-- 字典编辑弹窗 --> +<template> + <ele-modal + :width="460" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改字典' : '添加字典'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }" + :wrapper-col=" + styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="字典名称" name="dictName"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入字典名称" + v-model:value="form.dictName" + /> + </a-form-item> + <a-form-item label="字典值" name="dictCode"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入字典值" + v-model:value="form.dictCode" + /> + </a-form-item> + <a-form-item label="排序号" name="sortNumber"> + <a-input-number + :min="0" + :max="9999" + class="ele-fluid" + placeholder="请输入排序号" + v-model:value="form.sortNumber" + /> + </a-form-item> + <a-form-item label="备注"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入备注" + v-model:value="form.comments" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { addDictionary, updateDictionary } from '@/api/system/dictionary'; + import type { Dictionary } from '@/api/system/dictionary/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Dictionary | null; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Dictionary>({ + dictId: undefined, + dictName: '', + dictCode: '', + sortNumber: undefined, + comments: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + dictName: [ + { + required: true, + message: '请输入字典名称', + type: 'string', + trigger: 'blur' + } + ], + dictCode: [ + { + required: true, + message: '请输入字典值', + type: 'string', + trigger: 'blur' + } + ], + sortNumber: [ + { + required: true, + message: '请输入排序号', + type: 'number', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateDictionary : addDictionary; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views-demo/system/dictionary/index.vue b/src/views-demo/system/dictionary/index.vue new file mode 100644 index 0000000..be8be9c --- /dev/null +++ b/src/views-demo/system/dictionary/index.vue @@ -0,0 +1,197 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false" :body-style="{ padding: '16px' }"> + <ele-split-layout + width="266px" + allow-collapse + :right-style="{ overflow: 'hidden' }" + :style="{ minHeight: 'calc(100vh - 152px)' }" + > + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="dictId" + :columns="columns" + :datasource="datasource" + v-model:current="current" + selection-type="radio" + :row-selection="{ columnWidth: 32 }" + :need-page="false" + :toolkit="[]" + height="calc(100vh - 290px)" + tools-theme="default" + bordered + :custom-row="customRow" + class="sys-dict-table" + @done="done" + > + <template #toolbar> + <a-space :size="10"> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + type="primary" + :disabled="!current" + class="ele-btn-icon" + @click="openEdit(current)" + > + <template #icon> + <edit-outlined /> + </template> + <span>修改</span> + </a-button> + <a-button + danger + type="primary" + :disabled="!current" + class="ele-btn-icon" + @click="remove" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + </ele-pro-table> + <template #content> + <dict-data + v-if="current && current.dictId" + :dict-id="current.dictId" + /> + </template> + </ele-split-layout> + </a-card> + <!-- 编辑弹窗 --> + <dict-edit v-model:visible="showEdit" :data="editData" @done="reload" /> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + EditOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import { messageLoading } from 'ele-admin-pro/es'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem, + EleProTableDone + } from 'ele-admin-pro/es/ele-pro-table/types'; + import DictData from './components/dict-data.vue'; + import DictEdit from './components/dict-edit.vue'; + import { listDictionaries, removeDictionary } from '@/api/system/dictionary'; + import type { Dictionary } from '@/api/system/dictionary/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 32, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '字典名称', + dataIndex: 'dictName' + } + ]); + + // 表格选中数据 + const current = ref<Dictionary | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 编辑回显数据 + const editData = ref<Dictionary | null>(null); + + // 表格数据源 + const datasource: DatasourceFunction = () => { + return listDictionaries(); + }; + + /* 表格渲染完成回调 */ + const done: EleProTableDone<Dictionary> = (res) => { + if (res.data?.length) { + current.value = res.data[0]; + } + }; + + /* 刷新表格 */ + const reload = () => { + tableRef?.value?.reload(); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: Dictionary | null) => { + editData.value = row ?? null; + showEdit.value = true; + }; + + /* 删除 */ + const remove = () => { + Modal.confirm({ + title: '提示', + content: '确定要删除选中的字典吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeDictionary(current.value?.dictId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 行点击事件 */ + const customRow = (record: Dictionary) => { + return { + onClick: () => { + current.value = record; + } + }; + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemDictionary' + }; +</script> + +<style lang="less" scoped> + .sys-dict-table { + :deep(.ant-table-body) { + overflow: auto !important; + overflow: overlay !important; + } + + :deep(.ant-table-row) { + cursor: pointer; + } + } +</style> diff --git a/src/views-demo/system/file/components/file-search.vue b/src/views-demo/system/file/components/file-search.vue new file mode 100644 index 0000000..bd9ded8 --- /dev/null +++ b/src/views-demo/system/file/components/file-search.vue @@ -0,0 +1,106 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="文件名称"> + <a-input + v-model:value.trim="form.name" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="文件路径"> + <a-input + v-model:value.trim="form.path" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="上传人"> + <a-input + v-model:value.trim="form.createNickname" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { FileRecordParam } from '@/api/system/file/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: FileRecordParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<FileRecordParam>({ + name: '', + path: '', + createNickname: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views-demo/system/file/index.vue b/src/views-demo/system/file/index.vue new file mode 100644 index 0000000..e2e5cb4 --- /dev/null +++ b/src/views-demo/system/file/index.vue @@ -0,0 +1,244 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <file-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 800 }" + cache-key="proSystemFileTable" + > + <template #toolbar> + <a-space> + <a-upload :show-upload-list="false" :customRequest="onUpload"> + <a-button type="primary" class="ele-btn-icon"> + <template #icon> + <upload-outlined /> + </template> + <span>上传</span> + </a-button> + </a-upload> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'path'"> + <a :href="record.url" target="_blank"> + {{ record.path }} + </a> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a :href="record.downloadUrl" target="_blank">下载</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此文件吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + UploadOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import FileSearch from './components/file-search.vue'; + import { + pageFiles, + removeFile, + removeFiles, + uploadFile + } from '@/api/system/file'; + import type { FileRecord, FileRecordParam } from '@/api/system/file/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '文件名称', + dataIndex: 'name', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '文件路径', + key: 'path', + dataIndex: 'path', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '文件大小', + dataIndex: 'length', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => { + if (text < 1024) { + return text + 'B'; + } else if (text < 1024 * 1024) { + return (text / 1024).toFixed(1) + 'KB'; + } else if (text < 1024 * 1024 * 1024) { + return (text / 1024 / 1024).toFixed(1) + 'M'; + } else { + return (text / 1024 / 1024 / 1024).toFixed(1) + 'G'; + } + }, + width: 120 + }, + { + title: '上传人', + dataIndex: 'createNickname', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + width: 120 + }, + { + title: '上传时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text), + width: 160 + }, + { + title: '操作', + key: 'action', + width: 120, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<FileRecord[]>([]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageFiles({ ...where, ...orders, page, limit }); + }; + + /* 搜索 */ + const reload = (where?: FileRecordParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 删除单个 */ + const remove = (row: FileRecord) => { + const hide = messageLoading('请求中..', 0); + removeFile(row.id) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的文件吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeFiles(selection.value.map((d) => d.id)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 上传 */ + const onUpload = ({ file }) => { + if (file.size / 1024 / 1024 > 100) { + message.error('大小不能超过 100MB'); + return false; + } + const hide = messageLoading({ + content: '上传中..', + duration: 0, + mask: true + }); + uploadFile(file) + .then(() => { + hide(); + message.success('上传成功'); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + return false; + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemFile' + }; +</script> diff --git a/src/views-demo/system/login-record/components/login-record-search.vue b/src/views-demo/system/login-record/components/login-record-search.vue new file mode 100644 index 0000000..c1c480b --- /dev/null +++ b/src/views-demo/system/login-record/components/login-record-search.vue @@ -0,0 +1,115 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="用户账号"> + <a-input + v-model:value.trim="form.username" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="用户名"> + <a-input + v-model:value.trim="form.nickname" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="登录时间"> + <a-range-picker + v-model:value="dateRange" + value-format="YYYY-MM-DD" + class="ele-fluid" + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { LoginRecordParam } from '@/api/system/login-record/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: LoginRecordParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<LoginRecordParam>({ + username: '', + nickname: '' + }); + + // 日期范围选择 + const dateRange = ref<[string, string]>(['', '']); + + /* 搜索 */ + const search = () => { + const [d1, d2] = dateRange.value ?? []; + emit('search', { + ...form, + createTimeStart: d1 ? d1 + ' 00:00:00' : '', + createTimeEnd: d2 ? d2 + ' 23:59:59' : '' + }); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + dateRange.value = ['', '']; + search(); + }; +</script> diff --git a/src/views-demo/system/login-record/index.vue b/src/views-demo/system/login-record/index.vue new file mode 100644 index 0000000..2f62337 --- /dev/null +++ b/src/views-demo/system/login-record/index.vue @@ -0,0 +1,235 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <login-record-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + :scroll="{ x: 900 }" + cache-key="proSystemLoginRecordTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="exportData"> + <template #icon> + <download-outlined /> + </template> + <span>导出</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'loginType'"> + <a-tag v-if="record.loginType === 0" color="green">登录成功</a-tag> + <a-tag v-else-if="record.loginType === 1" color="red"> + 登录失败 + </a-tag> + <a-tag v-else-if="record.loginType === 2">退出登录</a-tag> + <a-tag v-else-if="record.loginType === 3" color="orange"> + 刷新TOKEN + </a-tag> + </template> + </template> + </ele-pro-table> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { utils, writeFile } from 'xlsx'; + import { DownloadOutlined } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import LoginRecordSearch from './components/login-record-search.vue'; + import { + pageLoginRecords, + listLoginRecords + } from '@/api/system/login-record'; + import type { LoginRecordParam } from '@/api/system/login-record/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '账号', + dataIndex: 'username', + sorter: true, + showSorterTooltip: false + }, + { + title: '用户名', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false + }, + { + title: 'IP地址', + dataIndex: 'ip', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '设备型号', + dataIndex: 'device', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '操作系统', + dataIndex: 'os', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '浏览器', + dataIndex: 'browser', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '操作类型', + key: 'loginType', + dataIndex: 'loginType', + sorter: true, + showSorterTooltip: false, + width: 120, + filters: [ + { + text: '登录成功', + value: 0 + }, + { + text: '登录失败', + value: 1 + }, + { + text: '退出登录', + value: 2 + }, + { + text: '刷新TOKEN', + value: 3 + } + ], + filterMultiple: false + }, + { + title: '备注', + dataIndex: 'comments', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '登录时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + } + ]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ + page, + limit, + where, + orders, + filters + }) => { + return pageLoginRecords({ + ...where, + ...orders, + ...filters, + page, + limit + }); + }; + + /* 刷新表格 */ + const reload = (where?: LoginRecordParam) => { + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 导出数据 */ + const exportData = () => { + const array = [ + [ + '账号', + '用户名', + 'IP地址', + '设备型号', + '操作系统', + '浏览器', + '操作类型', + '备注', + '登录时间' + ] + ]; + // 请求查询全部接口 + const hide = messageLoading('请求中..', 0); + tableRef.value?.doRequest(({ where, orders, filters }) => { + listLoginRecords({ ...where, ...orders, ...filters }) + .then((data) => { + hide(); + data.forEach((d) => { + array.push([ + d.username, + d.nickname, + d.ip, + d.device, + d.os, + d.browser, + ['登录成功', '登录失败', '退出登录', '刷新TOKEN'][d.loginType], + d.comments, + toDateString(d.createTime) + ]); + }); + writeFile( + { + SheetNames: ['Sheet1'], + Sheets: { + Sheet1: utils.aoa_to_sheet(array) + } + }, + '登录日志.xlsx' + ); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemLoginRecord' + }; +</script> diff --git a/src/views-demo/system/menu/components/menu-edit.vue b/src/views-demo/system/menu/components/menu-edit.vue new file mode 100644 index 0000000..f336a0d --- /dev/null +++ b/src/views-demo/system/menu/components/menu-edit.vue @@ -0,0 +1,414 @@ +<!-- 编辑弹窗 --> +<template> + <ele-modal + :width="740" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改菜单' : '新建菜单'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 6, sm: 4, xs: 24 } : { flex: '90px' }" + :wrapper-col=" + styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' } + " + > + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="上级菜单" name="parentId"> + <a-tree-select + allow-clear + :tree-data="menuList" + tree-default-expand-all + placeholder="请选择上级菜单" + :value="form.parentId || undefined" + :dropdown-style="{ maxHeight: '360px', overflow: 'auto' }" + @update:value="(value?: number) => (form.parentId = value)" + /> + </a-form-item> + <a-form-item label="菜单名称" name="title"> + <a-input + allow-clear + placeholder="请输入菜单名称" + v-model:value="form.title" + /> + </a-form-item> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="菜单类型" name="menuType"> + <a-radio-group + v-model:value="form.menuType" + @change="onMenuTypeChange" + > + <a-radio :value="0">目录</a-radio> + <a-radio :value="1">菜单</a-radio> + <a-radio :value="2">按钮</a-radio> + </a-radio-group> + </a-form-item> + <a-form-item label="打开方式"> + <a-radio-group + v-model:value="form.openType" + :disabled="form.menuType === 0 || form.menuType === 2" + @change="onOpenTypeChange" + > + <a-radio :value="0">组件</a-radio> + <a-radio :value="1">内链</a-radio> + <a-radio :value="2">外链</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + </a-row> + <div style="margin-bottom: 22px"> + <a-divider /> + </div> + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="菜单图标" name="icon"> + <ele-icon-picker + :data="iconData" + :allow-search="false" + v-model:value="form.icon" + placeholder="请选择菜单图标" + :disabled="form.menuType === 2" + > + <template #icon="{ icon }"> + <component :is="icon" /> + </template> + </ele-icon-picker> + </a-form-item> + <a-form-item name="path"> + <template #label> + <a-tooltip + v-if="form.openType === 2" + title="需要以`http://`、`https://`、`//`开头" + > + <question-circle-outlined + style="vertical-align: -2px; margin-right: 4px" + /> + </a-tooltip> + <span>{{ form.openType === 2 ? '外链地址' : '路由地址' }}</span> + </template> + <a-input + allow-clear + v-model:value="form.path" + :disabled="form.menuType === 2" + :placeholder=" + form.openType === 2 ? '请输入外链地址' : '请输入路由地址' + " + /> + </a-form-item> + <a-form-item name="component"> + <template #label> + <a-tooltip + v-if="form.openType === 1" + title="需要以`http://`、`https://`、`//`开头" + > + <question-circle-outlined + style="vertical-align: -2px; margin-right: 4px" + /> + </a-tooltip> + <span>{{ form.openType === 1 ? '内链地址' : '组件路径' }}</span> + </template> + <a-input + allow-clear + v-model:value="form.component" + :disabled=" + form.menuType === 0 || + form.menuType === 2 || + form.openType === 2 + " + :placeholder=" + form.openType === 1 ? '请输入内链地址' : '请输入组件路径' + " + /> + </a-form-item> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="权限标识" name="authority"> + <a-input + allow-clear + placeholder="请输入权限标识" + v-model:value="form.authority" + :disabled=" + form.menuType === 0 || + (form.menuType === 1 && form.openType === 2) + " + /> + </a-form-item> + <a-form-item label="排序号" name="sortNumber"> + <a-input-number + :min="0" + :max="99999" + class="ele-fluid" + placeholder="请输入排序号" + v-model:value="form.sortNumber" + /> + </a-form-item> + <a-form-item label="是否展示"> + <a-switch + checked-children="是" + un-checked-children="否" + :checked="form.hide === 0" + :disabled="form.menuType === 2" + @update:checked="updateHideValue" + /> + <a-tooltip + title="选择不展示只注册路由不展示在侧边栏, 比如添加页面应该选择不展示" + > + <question-circle-outlined + style="vertical-align: -4px; margin-left: 16px" + /> + </a-tooltip> + </a-form-item> + </a-col> + </a-row> + <a-form-item + label="路由元数据" + name="meta" + :label-col=" + styleResponsive ? { md: 3, sm: 4, xs: 24 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' } + " + > + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入JSON格式的路由元数据" + v-model:value="form.meta" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { QuestionCircleOutlined } from '@ant-design/icons-vue'; + import { isExternalLink } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { addMenu, updateMenu } from '@/api/system/menu'; + import type { Menu } from '@/api/system/menu/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Menu | null; + // 上级菜单id + parentId?: number; + // 全部菜单数据 + menuList: Menu[]; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Menu>({ + menuId: undefined, + parentId: undefined, + title: '', + menuType: 0, + openType: 0, + icon: '', + path: '', + component: '', + authority: '', + sortNumber: undefined, + hide: 0, + meta: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + title: [ + { + required: true, + message: '请输入菜单名称', + type: 'string', + trigger: 'blur' + } + ], + sortNumber: [ + { + required: true, + message: '请输入排序号', + type: 'number', + trigger: 'blur' + } + ], + meta: [ + { + type: 'string', + validator: async (_rule: Rule, value: string) => { + if (value) { + const msg = '请输入正确的JSON格式'; + try { + const obj = JSON.parse(value); + if (typeof obj !== 'object' || obj === null) { + return Promise.reject(msg); + } + } catch (_e) { + return Promise.reject(msg); + } + } + return Promise.resolve(); + }, + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const menuForm = { + ...form, + // menuType 对应的值与后端不一致在前端处理 + menuType: form.menuType === 2 ? 1 : 0, + parentId: form.parentId || 0 + }; + const saveOrUpdate = isUpdate.value ? updateMenu : addMenu; + saveOrUpdate(menuForm) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + /* menuType选择改变 */ + const onMenuTypeChange = () => { + if (form.menuType === 0) { + form.authority = ''; + form.openType = 0; + form.component = ''; + } else if (form.menuType === 1) { + if (form.openType === 2) { + form.authority = ''; + } + } else { + form.openType = 0; + form.icon = ''; + form.path = ''; + form.component = ''; + form.hide = 0; + } + }; + + /* openType选择改变 */ + const onOpenTypeChange = () => { + if (form.openType === 2) { + form.component = ''; + form.authority = ''; + } + }; + + const updateHideValue = (value: boolean) => { + form.hide = value ? 0 : 1; + }; + + /* 判断是否是目录 */ + const isDirectory = (d: Menu) => { + return !!d.children?.length && !d.component; + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + const isExternal = isExternalLink(props.data.path); + const isInner = isExternalLink(props.data.component); + // menuType 对应的值与后端不一致在前端处理 + const menuType = + props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1; + assignFields({ + ...props.data, + menuType, + openType: isExternal ? 2 : isInner ? 1 : 0, + parentId: + props.data.parentId === 0 ? undefined : props.data.parentId + }); + isUpdate.value = true; + } else { + form.parentId = props.parentId; + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> + +<script lang="ts"> + import * as icons from '@/layout/menu-icons'; + + export default { + components: icons, + data() { + return { + iconData: [ + { + title: '已引入的图标', + icons: Object.keys(icons) + } + ] + }; + } + }; +</script> diff --git a/src/views-demo/system/menu/components/menu-search.vue b/src/views-demo/system/menu/components/menu-search.vue new file mode 100644 index 0000000..81afbf5 --- /dev/null +++ b/src/views-demo/system/menu/components/menu-search.vue @@ -0,0 +1,106 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="菜单名称"> + <a-input + v-model:value.trim="form.title" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="菜单地址"> + <a-input + v-model:value.trim="form.path" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="权限标识"> + <a-input + v-model:value.trim="form.authority" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { MenuParam } from '@/api/system/menu/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: MenuParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<MenuParam>({ + title: '', + path: '', + authority: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views-demo/system/menu/index.vue b/src/views-demo/system/menu/index.vue new file mode 100644 index 0000000..24a24e1 --- /dev/null +++ b/src/views-demo/system/menu/index.vue @@ -0,0 +1,291 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <menu-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="menuId" + :columns="columns" + :datasource="datasource" + :parse-data="parseData" + :need-page="false" + :expand-icon-column-index="1" + :expanded-row-keys="expandedRowKeys" + :scroll="{ x: 1200 }" + cache-key="proSystemMenuTable" + @done="onDone" + @expand="onExpand" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button type="dashed" class="ele-btn-icon" @click="expandAll"> + 展开全部 + </a-button> + <a-button type="dashed" class="ele-btn-icon" @click="foldAll"> + 折叠全部 + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'menuType'"> + <a-tag v-if="isExternalLink(record.path)" color="red">外链</a-tag> + <a-tag v-else-if="isExternalLink(record.component)" color="orange"> + 内链 + </a-tag> + <a-tag v-else-if="isDirectory(record)" color="blue">目录</a-tag> + <a-tag v-else-if="record.menuType === 0" color="green">菜单</a-tag> + <a-tag v-else-if="record.menuType === 1">按钮</a-tag> + </template> + <template v-else-if="column.key === 'title'"> + <component v-if="record.icon" :is="record.icon" /> + <span style="padding-left: 8px">{{ record.title }}</span> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(null, record.menuId)">添加</a> + <a-divider type="vertical" /> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此菜单吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 编辑弹窗 --> + <menu-edit + v-model:visible="showEdit" + :data="current" + :parent-id="parentId" + :menu-list="menuData" + @done="reload" + /> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { PlusOutlined } from '@ant-design/icons-vue'; + import type { + DatasourceFunction, + ColumnItem, + EleProTableDone + } from 'ele-admin-pro/es/ele-pro-table/types'; + import MenuSearch from './components/menu-search.vue'; + import { + messageLoading, + toDateString, + isExternalLink, + toTreeData, + eachTreeData + } from 'ele-admin-pro/es'; + import type { EleProTable } from 'ele-admin-pro/es'; + import MenuEdit from './components/menu-edit.vue'; + import { listMenus, removeMenu } from '@/api/system/menu'; + import type { Menu, MenuParam } from '@/api/system/menu/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '菜单名称', + key: 'title', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '路由地址', + dataIndex: 'path', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '组件路径', + dataIndex: 'component', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '权限标识', + dataIndex: 'authority', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '排序', + dataIndex: 'sortNumber', + sorter: true, + showSorterTooltip: false, + width: 90 + }, + { + title: '可见', + dataIndex: 'hide', + sorter: true, + showSorterTooltip: false, + customRender: ({ text }) => ['是', '否'][text], + width: 90 + }, + { + title: '类型', + key: 'menuType', + sorter: true, + showSorterTooltip: false, + width: 90 + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '操作', + key: 'action', + width: 200, + align: 'center' + } + ]); + + // 当前编辑数据 + const current = ref<Menu | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 上级菜单id + const parentId = ref<number>(); + + // 菜单数据 + const menuData = ref<Menu[]>([]); + + // 表格展开的行 + const expandedRowKeys = ref<number[]>([]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ where }) => { + return listMenus({ ...where }); + }; + + /* 数据转为树形结构 */ + const parseData = (data: Menu[]) => { + return toTreeData({ + data: data.map((d) => { + return { ...d, key: d.menuId, value: d.menuId }; + }), + idField: 'menuId', + parentIdField: 'parentId' + }); + }; + + /* 表格渲染完成回调 */ + const onDone: EleProTableDone<Menu> = ({ data }) => { + menuData.value = data; + }; + + /* 刷新表格 */ + const reload = (where?: MenuParam) => { + tableRef?.value?.reload({ where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: Menu | null, id?: number) => { + current.value = row ?? null; + parentId.value = id; + showEdit.value = true; + }; + + /* 删除单个 */ + const remove = (row: Menu) => { + if (row.children?.length) { + message.error('请先删除子节点'); + return; + } + const hide = messageLoading('请求中..', 0); + removeMenu(row.menuId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 展开全部 */ + const expandAll = () => { + let keys: number[] = []; + eachTreeData(menuData.value, (d) => { + if (d.children && d.children.length && d.menuId) { + keys.push(d.menuId); + } + }); + expandedRowKeys.value = keys; + }; + + /* 折叠全部 */ + const foldAll = () => { + expandedRowKeys.value = []; + }; + + /* 点击展开图标时触发 */ + const onExpand = (expanded: boolean, record: Menu) => { + if (expanded) { + expandedRowKeys.value = [ + ...expandedRowKeys.value, + record.menuId as number + ]; + } else { + expandedRowKeys.value = expandedRowKeys.value.filter( + (d) => d !== record.menuId + ); + } + }; + + /* 判断是否是目录 */ + const isDirectory = (d: Menu) => { + return !!d.children?.length && !d.component; + }; +</script> + +<script lang="ts"> + import * as MenuIcons from '@/layout/menu-icons'; + + export default { + name: 'SystemMenu', + components: MenuIcons + }; +</script> diff --git a/src/views-demo/system/operation-record/components/operation-record-detail.vue b/src/views-demo/system/operation-record/components/operation-record-detail.vue new file mode 100644 index 0000000..deb5740 --- /dev/null +++ b/src/views-demo/system/operation-record/components/operation-record-detail.vue @@ -0,0 +1,131 @@ +<!-- 详情弹窗 --> +<template> + <ele-modal + title="详情" + :width="640" + :footer="null" + :visible="visible" + @update:visible="updateVisible" + > + <a-form + class="ele-form-detail" + :label-col="{ sm: { span: 8 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 16 }, xs: { span: 18 } }" + > + <a-row :gutter="16"> + <a-col :sm="12" :xs="24"> + <a-form-item label="操作人"> + <div class="ele-text-secondary"> + {{ data.nickname }}({{ data.username }}) + </div> + </a-form-item> + <a-form-item label="操作模块"> + <div class="ele-text-secondary"> + {{ data.module }} + </div> + </a-form-item> + <a-form-item label="操作时间"> + <div class="ele-text-secondary"> + {{ toDateString(data.createTime) }} + </div> + </a-form-item> + <a-form-item label="请求方式"> + <div class="ele-text-secondary"> + {{ data.requestMethod }} + </div> + </a-form-item> + </a-col> + <a-col :sm="12" :xs="24"> + <a-form-item label="IP地址"> + <div class="ele-text-secondary"> + {{ data.ip }} + </div> + </a-form-item> + <a-form-item label="操作功能"> + <div class="ele-text-secondary"> + {{ data.description }} + </div> + </a-form-item> + <a-form-item label="请求耗时"> + <div v-if="!isNaN(data.spendTime)" class="ele-text-secondary"> + {{ data.spendTime / 1000 }}s + </div> + </a-form-item> + <a-form-item label="请求状态"> + <a-tag :color="['green', 'red'][data.status]"> + {{ ['正常', '异常'][data.status] }} + </a-tag> + </a-form-item> + </a-col> + </a-row> + <div style="margin: 12px 0"> + <a-divider /> + </div> + <a-form-item + label="请求地址" + :label-col="{ sm: { span: 4 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }" + > + <div class="ele-text-secondary"> + {{ data.url }} + </div> + </a-form-item> + <a-form-item + label="调用方法" + :label-col="{ sm: { span: 4 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }" + > + <div class="ele-text-secondary"> + {{ data.method }} + </div> + </a-form-item> + <a-form-item + label="请求参数" + :label-col="{ sm: { span: 4 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }" + > + <div class="ele-text-secondary"> + {{ data.params }} + </div> + </a-form-item> + <a-form-item + v-if="data.status === 0" + label="返回结果" + :label-col="{ sm: { span: 4 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }" + > + <text-ellipsis :content="data.result" class="ele-text-secondary" /> + </a-form-item> + <a-form-item + v-else + label="异常信息" + :label-col="{ sm: { span: 4 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }" + > + <text-ellipsis :content="data.error" class="ele-text-secondary" /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { toDateString } from 'ele-admin-pro/es'; + import type { OperationRecord } from '@/api/system/operation-record/model'; + import TextEllipsis from './text-ellipsis.vue'; + + const emit = defineEmits<{ + (e: 'update:visible', visible: boolean): void; + }>(); + + defineProps<{ + // 弹窗是否打开 + visible?: boolean; + // 修改回显的数据 + data: OperationRecord; + }>(); + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; +</script> diff --git a/src/views-demo/system/operation-record/components/operation-record-search.vue b/src/views-demo/system/operation-record/components/operation-record-search.vue new file mode 100644 index 0000000..d6e4309 --- /dev/null +++ b/src/views-demo/system/operation-record/components/operation-record-search.vue @@ -0,0 +1,112 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="用户账号"> + <a-input + v-model:value.trim="form.username" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="操作模块"> + <a-input + v-model:value.trim="form.module" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="操作时间"> + <a-range-picker + v-model:value="dateRange" + :show-time="true" + value-format="YYYY-MM-DD HH:mm:ss" + class="ele-fluid" + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { OperationRecordParam } from '@/api/system/operation-record/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: OperationRecordParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<OperationRecordParam>({ + username: '', + module: '' + }); + + // 日期范围选择 + const dateRange = ref<[string, string]>(['', '']); + + /* 搜索 */ + const search = () => { + const [createTimeStart, createTimeEnd] = dateRange.value; + emit('search', { ...form, createTimeStart, createTimeEnd }); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + dateRange.value = ['', '']; + search(); + }; +</script> diff --git a/src/views-demo/system/operation-record/components/text-ellipsis.vue b/src/views-demo/system/operation-record/components/text-ellipsis.vue new file mode 100644 index 0000000..05b204a --- /dev/null +++ b/src/views-demo/system/operation-record/components/text-ellipsis.vue @@ -0,0 +1,59 @@ +<!-- 文本超出隐藏 --> +<template> + <div + :class="[ + 'demo-text-ellipsis ele-bg-white ele-border-split', + { expanded: expanded } + ]" + > + <div>{{ content }}</div> + <div + class="demo-text-ellipsis-footer ele-border-split ele-bg-white" + @click="expanded = !expanded" + > + <up-outlined v-if="expanded" /> + <down-outlined v-else /> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'; + + defineProps<{ + content?: string; + }>(); + + const expanded = ref(false); +</script> + +<style lang="less" scoped> + .demo-text-ellipsis { + border-radius: 4px; + padding: 6px 12px 20px 12px; + position: relative; + border-width: 1px; + border-style: solid; + word-break: break-all; + + &:not(.expanded) { + max-height: 192px; + overflow: hidden; + } + } + + .demo-text-ellipsis-footer { + border-top-width: 1px; + border-top-style: solid; + position: absolute; + bottom: 0; + left: 0; + right: 0; + text-align: center; + font-size: 12px; + cursor: pointer; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + } +</style> diff --git a/src/views-demo/system/operation-record/index.vue b/src/views-demo/system/operation-record/index.vue new file mode 100644 index 0000000..760464e --- /dev/null +++ b/src/views-demo/system/operation-record/index.vue @@ -0,0 +1,273 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <operation-record-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + :scroll="{ x: 1000 }" + cache-key="proSystemOperationRecordTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="exportData"> + <template #icon> + <download-outlined /> + </template> + <span>导出</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'status'"> + <a-tag v-if="record.status === 0" color="green">正常</a-tag> + <a-tag v-else-if="record.status === 1" color="red">异常</a-tag> + </template> + <template v-else-if="column.key === 'action'"> + <a @click="openDetail(record)">详情</a> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 详情弹窗 --> + <operation-record-detail v-model:visible="showInfo" :data="current" /> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { DownloadOutlined } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { utils, writeFile } from 'xlsx'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import OperationRecordSearch from './components/operation-record-search.vue'; + import OperationRecordDetail from './components/operation-record-detail.vue'; + import { + pageOperationRecords, + listOperationRecords + } from '@/api/system/operation-record'; + import type { + OperationRecord, + OperationRecordParam + } from '@/api/system/operation-record/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '账号', + dataIndex: 'username', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '用户名', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '操作模块', + dataIndex: 'module', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '操作功能', + dataIndex: 'description', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '请求地址', + dataIndex: 'url', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '请求方式', + dataIndex: 'requestMethod', + sorter: true, + showSorterTooltip: false, + width: 100, + align: 'center' + }, + { + title: '状态', + key: 'status', + dataIndex: 'status', + sorter: true, + showSorterTooltip: false, + width: 100, + filters: [ + { + text: '正常', + value: 0 + }, + { + text: '异常', + value: 1 + } + ], + filterMultiple: false, + align: 'center' + }, + { + title: '耗时', + dataIndex: 'spendTime', + sorter: true, + showSorterTooltip: false, + width: 100, + customRender: ({ text }) => text / 1000 + 's' + }, + { + title: '操作时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text), + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 90, + align: 'center', + fixed: 'right' + } + ]); + + // 当前选中数据 + const current = ref<OperationRecord>({ + module: '', + description: '', + url: '', + requestMethod: '', + method: '', + params: '', + result: '', + error: '', + spendTime: 0, + os: '', + device: '', + browser: '', + ip: '', + status: 0, + createTime: '', + nickname: '', + username: '' + }); + + // 是否显示查看弹窗 + const showInfo = ref(false); + + // 表格数据源 + const datasource: DatasourceFunction = ({ + page, + limit, + where, + orders, + filters + }) => { + return pageOperationRecords({ + ...where, + ...orders, + ...filters, + page, + limit + }); + }; + + /* 刷新表格 */ + const reload = (where?: OperationRecordParam) => { + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 详情 */ + const openDetail = (row: OperationRecord) => { + current.value = row; + showInfo.value = true; + }; + + /* 导出数据 */ + const exportData = () => { + const array = [ + [ + '账号', + '用户名', + '操作模块', + '操作功能', + '请求地址', + '请求方式', + '状态', + '耗时', + '操作时间' + ] + ]; + // 请求查询全部(不分页)的接口 + const hide = messageLoading('请求中..', 0); + tableRef.value?.doRequest(({ where, orders, filters }) => { + listOperationRecords({ ...where, ...orders, ...filters }) + .then((data) => { + hide(); + data.forEach((d) => { + array.push([ + d.username, + d.nickname, + d.module, + d.description, + d.url, + d.requestMethod, + ['正常', '异常'][d.status], + d.spendTime / 1000 + 's', + toDateString(d.createTime) + ]); + }); + writeFile( + { + SheetNames: ['Sheet1'], + Sheets: { + Sheet1: utils.aoa_to_sheet(array) + } + }, + '操作日志.xlsx' + ); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemOperationRecord' + }; +</script> diff --git a/src/views-demo/system/organization/components/org-edit.vue b/src/views-demo/system/organization/components/org-edit.vue new file mode 100644 index 0000000..0c1b910 --- /dev/null +++ b/src/views-demo/system/organization/components/org-edit.vue @@ -0,0 +1,229 @@ +<!-- 机构编辑弹窗 --> +<template> + <ele-modal + :width="620" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改机构' : '添加机构'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 7, sm: 24 } : { flex: '90px' }" + :wrapper-col="styleResponsive ? { md: 17, sm: 24 } : { flex: '1' }" + > + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="上级机构" name="parentId"> + <org-select + :data="organizationList" + placeholder="请选择上级机构" + v-model:value="form.parentId" + /> + </a-form-item> + <a-form-item label="机构名称" name="organizationName"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入机构名称" + v-model:value="form.organizationName" + /> + </a-form-item> + <a-form-item label="机构全称" name="organizationFullName"> + <a-input + allow-clear + :maxlength="100" + placeholder="请输入机构全称" + v-model:value="form.organizationFullName" + /> + </a-form-item> + <a-form-item label="机构代码"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入机构代码" + v-model:value="form.organizationCode" + /> + </a-form-item> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="机构类型" name="organizationType"> + <org-type-select v-model:value="form.organizationType" /> + </a-form-item> + <a-form-item label="排序号" name="sortNumber"> + <a-input-number + :min="0" + :max="99999" + class="ele-fluid" + placeholder="请输入排序号" + v-model:value="form.sortNumber" + /> + </a-form-item> + <a-form-item label="备注"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入备注" + v-model:value="form.comments" + /> + </a-form-item> + </a-col> + </a-row> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import OrgSelect from './org-select.vue'; + import OrgTypeSelect from './org-type-select.vue'; + import { + addOrganization, + updateOrganization + } from '@/api/system/organization'; + import type { Organization } from '@/api/system/organization/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Organization | null; + // 机构id + organizationId?: number; + // 全部机构 + organizationList: Organization[]; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Organization>({ + organizationId: undefined, + parentId: undefined, + organizationName: '', + organizationFullName: '', + organizationCode: '', + organizationType: undefined, + sortNumber: undefined, + comments: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + organizationName: [ + { + required: true, + message: '请输入机构名称', + type: 'string', + trigger: 'blur' + } + ], + organizationFullName: [ + { + required: true, + message: '请输入机构全称', + type: 'string', + trigger: 'blur' + } + ], + organizationType: [ + { + required: true, + message: '请选择机构类型', + type: 'string', + trigger: 'blur' + } + ], + sortNumber: [ + { + required: true, + message: '请输入排序号', + type: 'number', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const orgData = { + ...form, + parentId: form.parentId || 0 + }; + const saveOrUpdate = isUpdate.value + ? updateOrganization + : addOrganization; + saveOrUpdate(orgData) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + isUpdate.value = true; + } else { + form.parentId = props.organizationId; + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views-demo/system/organization/components/org-select.vue b/src/views-demo/system/organization/components/org-select.vue new file mode 100644 index 0000000..587424f --- /dev/null +++ b/src/views-demo/system/organization/components/org-select.vue @@ -0,0 +1,39 @@ +<!-- 机构选择下拉框 --> +<template> + <a-tree-select + allow-clear + tree-default-expand-all + :placeholder="placeholder" + :value="value || undefined" + :tree-data="data" + :dropdown-style="{ maxHeight: '360px', overflow: 'auto' }" + @update:value="updateValue" + /> +</template> + +<script lang="ts" setup> + import type { Organization } from '@/api/system/organization/model'; + + const emit = defineEmits<{ + (e: 'update:value', value?: number): void; + }>(); + + withDefaults( + defineProps<{ + // 选中的数据(v-modal) + value?: number; + // 提示信息 + placeholder?: string; + // 机构数据 + data: Organization[]; + }>(), + { + placeholder: '请选择角色' + } + ); + + /* 更新选中数据 */ + const updateValue = (value?: number) => { + emit('update:value', value); + }; +</script> diff --git a/src/views-demo/system/organization/components/org-type-select.vue b/src/views-demo/system/organization/components/org-type-select.vue new file mode 100644 index 0000000..b45f351 --- /dev/null +++ b/src/views-demo/system/organization/components/org-type-select.vue @@ -0,0 +1,57 @@ +<!-- 机构类型选择下拉框 --> +<template> + <a-select + allow-clear + :value="value" + :placeholder="placeholder" + @update:value="updateValue" + > + <a-select-option + v-for="item in data" + :key="item.dictDataCode" + :value="item.dictDataCode" + > + {{ item.dictDataName }} + </a-select-option> + </a-select> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { listDictionaryData } from '@/api/system/dictionary-data'; + import type { DictionaryData } from '@/api/system/dictionary-data/model'; + + const emit = defineEmits<{ + (e: 'update:value', value: string): void; + }>(); + + withDefaults( + defineProps<{ + value?: string; + placeholder?: string; + }>(), + { + placeholder: '请选择机构类型' + } + ); + + // 机构类型数据 + const data = ref<DictionaryData[]>([]); + + /* 更新选中数据 */ + const updateValue = (value: string) => { + emit('update:value', value); + }; + + /* 获取机构类型数据 */ + listDictionaryData({ + dictCode: 'organization_type' + }) + .then((list) => { + data.value = list; + }) + .catch((e) => { + message.error(e.message); + }); +</script> diff --git a/src/views-demo/system/organization/components/org-user-edit.vue b/src/views-demo/system/organization/components/org-user-edit.vue new file mode 100644 index 0000000..dbfb2f1 --- /dev/null +++ b/src/views-demo/system/organization/components/org-user-edit.vue @@ -0,0 +1,276 @@ +<!-- 用户编辑弹窗 --> +<template> + <ele-modal + :width="680" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改用户' : '新建用户'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 7, sm: 24 } : { flex: '90px' }" + :wrapper-col="styleResponsive ? { md: 17, sm: 24 } : { flex: '1' }" + > + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="所属机构"> + <org-select + :data="organizationList" + placeholder="请选择所属机构" + v-model:value="form.organizationId" + /> + </a-form-item> + <a-form-item label="用户账号" name="username"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入用户账号" + v-model:value="form.username" + /> + </a-form-item> + <a-form-item label="用户名" name="nickname"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入用户名" + v-model:value="form.nickname" + /> + </a-form-item> + <a-form-item label="性别" name="sex"> + <sex-select v-model:value="form.sex" /> + </a-form-item> + <a-form-item label="角色" name="roleIds"> + <role-select v-model:value="form.roles" /> + </a-form-item> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="手机号" name="phone"> + <a-input + allow-clear + :maxlength="11" + placeholder="请输入手机号" + v-model:value="form.phone" + /> + </a-form-item> + <a-form-item label="邮箱" name="email"> + <a-input + allow-clear + :maxlength="100" + placeholder="请输入邮箱" + v-model:value="form.email" + /> + </a-form-item> + <a-form-item v-if="!isUpdate" label="登录密码" name="password"> + <a-input-password + :maxlength="20" + v-model:value="form.password" + placeholder="请输入登录密码" + /> + </a-form-item> + <a-form-item label="个人简介"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入个人简介" + v-model:value="form.introduction" + /> + </a-form-item> + </a-col> + </a-row> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { emailReg, phoneReg } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import OrgSelect from './org-select.vue'; + import RoleSelect from '../../user/components/role-select.vue'; + import SexSelect from '../../user/components/sex-select.vue'; + import { addUser, updateUser, checkExistence } from '@/api/system/user'; + import type { User } from '@/api/system/user/model'; + import type { Organization } from '@/api/system/organization/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: User | null; + // 全部机构 + organizationList: Organization[]; + // 机构id + organizationId?: number; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<User>({ + userId: undefined, + organizationId: undefined, + username: '', + nickname: '', + sex: undefined, + roles: [], + email: '', + phone: '', + password: '', + introduction: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + username: [ + { + required: true, + type: 'string', + validator: (_rule: Rule, value: string) => { + return new Promise<void>((resolve, reject) => { + if (!value) { + return reject('请输入用户账号'); + } + checkExistence('username', value, props.data?.userId) + .then(() => { + reject('账号已经存在'); + }) + .catch(() => { + resolve(); + }); + }); + }, + trigger: 'blur' + } + ], + nickname: [ + { + required: true, + message: '请输入用户名', + type: 'string', + trigger: 'blur' + } + ], + sex: [ + { + required: true, + message: '请选择性别', + type: 'string', + trigger: 'blur' + } + ], + roleIds: [ + { + required: true, + message: '请选择角色', + type: 'array', + trigger: 'blur' + } + ], + email: [ + { + pattern: emailReg, + message: '邮箱格式不正确', + type: 'string', + trigger: 'blur' + } + ], + password: [ + { + required: true, + type: 'string', + validator: async (_rule: Rule, value: string) => { + if (isUpdate.value || /^[\S]{5,18}$/.test(value)) { + return Promise.resolve(); + } + return Promise.reject('密码必须为5-18位非空白字符'); + }, + trigger: 'blur' + } + ], + phone: [ + { + pattern: phoneReg, + message: '手机号格式不正确', + type: 'string', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateUser : addUser; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + isUpdate.value = true; + } else { + form.organizationId = props.organizationId; + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views-demo/system/organization/components/org-user-list.vue b/src/views-demo/system/organization/components/org-user-list.vue new file mode 100644 index 0000000..fdb0936 --- /dev/null +++ b/src/views-demo/system/organization/components/org-user-list.vue @@ -0,0 +1,214 @@ +<template> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="userId" + :columns="columns" + :datasource="datasource" + height="calc(100vh - 290px)" + tool-class="ele-toolbar-form" + :scroll="{ x: 800 }" + tools-theme="default" + bordered + cache-key="proSystemOrgUserTable" + class="sys-org-table" + > + <template #toolbar> + <org-user-search @search="reload" @add="openEdit()" /> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'roles'"> + <a-tag v-for="item in record.roles" :key="item.roleId" color="blue"> + {{ item.roleName }} + </a-tag> + </template> + <template v-else-if="column.key === 'status'"> + <a-switch + :checked="record.status === 0" + @change="(checked: boolean) => editStatus(checked, record)" + /> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此用户吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + <!-- 编辑弹窗 --> + <org-user-edit + :data="current" + v-model:visible="showEdit" + :organization-list="organizationList" + :organization-id="organizationId" + @done="reload" + /> +</template> + +<script lang="ts" setup> + import { ref, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import OrgUserSearch from './org-user-search.vue'; + import OrgUserEdit from './org-user-edit.vue'; + import { pageUsers, removeUser, updateUserStatus } from '@/api/system/user'; + import type { User, UserParam } from '@/api/system/user/model'; + import type { Organization } from '@/api/system/organization/model'; + + const props = defineProps<{ + // 机构 id + organizationId?: number; + // 全部机构 + organizationList: Organization[]; + }>(); + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '用户账号', + dataIndex: 'username', + sorter: true, + showSorterTooltip: false + }, + { + title: '用户名', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false + }, + { + title: '性别', + dataIndex: 'sexName', + width: 80, + align: 'center', + sorter: true, + showSorterTooltip: false + }, + { + title: '角色', + key: 'roles' + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '状态', + key: 'status', + sorter: true, + showSorterTooltip: false, + width: 80, + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 100, + align: 'center' + } + ]); + + // 当前编辑数据 + const current = ref<User | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageUsers({ + ...where, + ...orders, + page, + limit, + organizationId: props.organizationId + }); + }; + + /* 搜索 */ + const reload = (where?: UserParam) => { + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: User) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 删除单个 */ + const remove = (row: User) => { + const hide = messageLoading('请求中..', 0); + removeUser(row.userId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 修改用户状态 */ + const editStatus = (checked: boolean, row: User) => { + const status = checked ? 0 : 1; + updateUserStatus(row.userId, status) + .then((msg) => { + row.status = status; + message.success(msg); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + // 监听机构 id 变化 + watch( + () => props.organizationId, + () => { + reload(); + } + ); +</script> + +<style lang="less" scoped> + .sys-org-table :deep(.ant-table-body) { + overflow: auto !important; + overflow: overlay !important; + } + + .sys-org-table :deep(.ant-table-pagination.ant-pagination) { + padding: 0 4px; + margin-bottom: 0; + } +</style> diff --git a/src/views-demo/system/organization/components/org-user-search.vue b/src/views-demo/system/organization/components/org-user-search.vue new file mode 100644 index 0000000..cf68c22 --- /dev/null +++ b/src/views-demo/system/organization/components/org-user-search.vue @@ -0,0 +1,82 @@ +<!-- 搜索表单 --> +<template> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { xl: 6, lg: 8, md: 12, sm: 24, xs: 24 } : { span: 6 } + " + > + <a-input + v-model:value.trim="form.username" + placeholder="请输入用户账号" + allow-clear + /> + </a-col> + <a-col + v-bind=" + styleResponsive ? { xl: 6, lg: 8, md: 12, sm: 24, xs: 24 } : { span: 6 } + " + > + <a-input + v-model:value.trim="form.nickname" + placeholder="请输入用户名" + allow-clear + /> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 12, lg: 8, md: 24, sm: 24, xs: 24 } + : { span: 12 } + " + > + <a-space :size="10" style="flex-wrap: wrap"> + <a-button type="primary" class="ele-btn-icon" @click="search"> + <template #icon> + <search-outlined /> + </template> + <span>查询</span> + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="add"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + </a-space> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { UserParam } from '@/api/system/user/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: UserParam): void; + (e: 'add'): void; + }>(); + + // 表单数据 + const { form } = useFormData<UserParam>({ + username: '', + nickname: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 添加 */ + const add = () => { + emit('add'); + }; +</script> diff --git a/src/views-demo/system/organization/index.vue b/src/views-demo/system/organization/index.vue new file mode 100644 index 0000000..cc576f1 --- /dev/null +++ b/src/views-demo/system/organization/index.vue @@ -0,0 +1,205 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false" :body-style="{ padding: '16px' }"> + <ele-split-layout + width="266px" + allow-collapse + :right-style="{ overflow: 'hidden' }" + :style="{ minHeight: 'calc(100vh - 152px)' }" + > + <div> + <ele-toolbar theme="default"> + <a-space :size="10"> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + type="primary" + :disabled="!current" + class="ele-btn-icon" + @click="openEdit(current)" + > + <template #icon> + <edit-outlined /> + </template> + <span>修改</span> + </a-button> + <a-button + danger + type="primary" + :disabled="!current" + class="ele-btn-icon" + @click="remove" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </ele-toolbar> + <div class="ele-border-split sys-organization-list"> + <a-tree + :tree-data="(data as any)" + v-model:expanded-keys="expandedRowKeys" + v-model:selected-keys="selectedRowKeys" + @select="onTreeSelect" + /> + </div> + </div> + <template #content> + <org-user-list + v-if="current" + :organization-list="data" + :organization-id="current.organizationId" + /> + </template> + </ele-split-layout> + </a-card> + <!-- 编辑弹窗 --> + <org-edit + v-model:visible="showEdit" + :data="editData" + :organization-list="data" + :organization-id="current?.organizationId" + @done="query" + /> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + EditOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import { messageLoading, toTreeData, eachTreeData } from 'ele-admin-pro/es'; + import OrgUserList from './components/org-user-list.vue'; + import OrgEdit from './components/org-edit.vue'; + import { + listOrganizations, + removeOrganization + } from '@/api/system/organization'; + import type { Organization } from '@/api/system/organization/model'; + + // 加载状态 + const loading = ref(true); + + // 树形数据 + const data = ref<Organization[]>([]); + + // 树展开的key + const expandedRowKeys = ref<number[]>([]); + + // 树选中的key + const selectedRowKeys = ref<number[]>([]); + + // 选中数据 + const current = ref<Organization | null>(null); + + // 是否显示表单弹窗 + const showEdit = ref(false); + + // 编辑回显数据 + const editData = ref<Organization | null>(null); + + /* 查询 */ + const query = () => { + loading.value = true; + listOrganizations() + .then((list) => { + loading.value = false; + const eks: number[] = []; + list.forEach((d) => { + d.key = d.organizationId; + d.value = d.organizationId; + d.title = d.organizationName; + if (typeof d.key === 'number') { + eks.push(d.key); + } + }); + expandedRowKeys.value = eks; + data.value = toTreeData({ + data: list, + idField: 'organizationId', + parentIdField: 'parentId' + }); + if (list.length) { + if (typeof list[0].key === 'number') { + selectedRowKeys.value = [list[0].key]; + } + current.value = list[0]; + } else { + selectedRowKeys.value = []; + current.value = null; + } + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }; + + /* 选择数据 */ + const onTreeSelect = () => { + eachTreeData(data.value, (d) => { + if (typeof d.key === 'number' && selectedRowKeys.value.includes(d.key)) { + current.value = d; + return false; + } + }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (item?: Organization | null) => { + editData.value = item ?? null; + showEdit.value = true; + }; + + /* 删除 */ + const remove = () => { + Modal.confirm({ + title: '提示', + content: '确定要删除选中的机构吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeOrganization(current.value?.organizationId) + .then((msg) => { + hide(); + message.success(msg); + query(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + query(); +</script> + +<script lang="ts"> + export default { + name: 'SystemOrganization' + }; +</script> + +<style lang="less" scoped> + .sys-organization-list { + padding: 12px 6px; + height: calc(100vh - 242px); + border-width: 1px; + border-style: solid; + overflow: auto; + } +</style> diff --git a/src/views-demo/system/role/components/role-auth.vue b/src/views-demo/system/role/components/role-auth.vue new file mode 100644 index 0000000..ae3ff0b --- /dev/null +++ b/src/views-demo/system/role/components/role-auth.vue @@ -0,0 +1,159 @@ +<!-- 角色权限分配弹窗 --> +<template> + <ele-modal + :width="460" + title="分配权限" + :visible="visible" + :confirm-loading="loading" + @update:visible="updateVisible" + @ok="save" + > + <a-spin :spinning="authLoading"> + <div style="height: 60vh" class="ele-scrollbar-hover"> + <a-tree + :checkable="true" + :show-icon="true" + :tree-data="(authData as any)" + v-model:expandedKeys="expandKeys" + v-model:checkedKeys="checkedKeys" + > + <template #icon="{ menuIcon }"> + <component v-if="menuIcon" :is="menuIcon" /> + </template> + </a-tree> + </div> + </a-spin> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, watch, nextTick } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { toTreeData, eachTreeData } from 'ele-admin-pro/es'; + import { listRoleMenus, updateRoleMenus } from '@/api/system/role'; + import type { Role } from '@/api/system/role/model'; + import type { Menu } from '@/api/system/menu/model'; + + const emit = defineEmits<{ + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 当前角色数据 + data?: Role | null; + }>(); + + // 权限数据 + const authData = ref<Menu[]>([]); + + // 权限数据请求状态 + const authLoading = ref(false); + + // 提交状态 + const loading = ref(false); + + // 角色权限展开的keys + const expandKeys = ref<number[]>([]); + + // 角色权限选中的keys + const checkedKeys = ref<number[]>([]); + + /* 查询权限数据 */ + const query = () => { + authData.value = []; + expandKeys.value = []; + checkedKeys.value = []; + if (!props.data) { + return; + } + authLoading.value = true; + listRoleMenus(props.data.roleId) + .then((data) => { + authLoading.value = false; + // 转成树形结构的数据 + authData.value = toTreeData({ + data: data?.map((d) => ({ + ...d, + key: d.menuId, + icon: undefined, + menuIcon: d.icon + })), + idField: 'menuId', + parentIdField: 'parentId', + addParentIds: true, + parentIds: [] + }); + // 全部默认展开以及回显选中的数据 + nextTick(() => { + const eks: number[] = []; + const cks: number[] = []; + eachTreeData(authData.value, (d) => { + if (d.key) { + if (d.children?.length) { + eks.push(d.key); + } else if (d.checked) { + cks.push(d.key); + } + } + }); + expandKeys.value = eks; + checkedKeys.value = cks; + }); + }) + .catch((e) => { + authLoading.value = false; + message.error(e.message); + }); + }; + + /* 保存权限分配 */ + const save = () => { + loading.value = true; + // 获取选中的id,包含所有半选的父级的id + const ids = new Set<number>(); + eachTreeData(authData.value, (d) => { + if (d.key && checkedKeys.value.some((c) => c === d.key)) { + ids.add(d.key); + if (d.parentIds) { + d.parentIds.forEach((id: number) => { + ids.add(id); + }); + } + } + }); + updateRoleMenus(props.data?.roleId, Array.from(ids)) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + query(); + } + } + ); +</script> + +<script lang="ts"> + import * as MenuIcons from '@/layout/menu-icons'; + + export default { + components: MenuIcons + }; +</script> diff --git a/src/views-demo/system/role/components/role-edit.vue b/src/views-demo/system/role/components/role-edit.vue new file mode 100644 index 0000000..b3a5f39 --- /dev/null +++ b/src/views-demo/system/role/components/role-edit.vue @@ -0,0 +1,158 @@ +<!-- 角色编辑弹窗 --> +<template> + <ele-modal + :width="460" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改角色' : '添加角色'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }" + :wrapper-col=" + styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="角色名称" name="roleName"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入角色名称" + v-model:value="form.roleName" + /> + </a-form-item> + <a-form-item label="角色标识" name="roleCode"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入角色标识" + v-model:value="form.roleCode" + /> + </a-form-item> + <a-form-item label="备注"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入备注" + v-model:value="form.comments" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { addRole, updateRole } from '@/api/system/role'; + import type { Role } from '@/api/system/role/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Role | null; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Role>({ + roleId: undefined, + roleName: '', + roleCode: '', + comments: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + roleName: [ + { + required: true, + message: '请输入角色名称', + type: 'string', + trigger: 'blur' + } + ], + roleCode: [ + { + required: true, + message: '请输入角色标识', + type: 'string', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateRole : addRole; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views-demo/system/role/components/role-search.vue b/src/views-demo/system/role/components/role-search.vue new file mode 100644 index 0000000..2169dca --- /dev/null +++ b/src/views-demo/system/role/components/role-search.vue @@ -0,0 +1,106 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="角色名称"> + <a-input + v-model:value.trim="form.roleName" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="角色标识"> + <a-input + v-model:value.trim="form.roleCode" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="备注"> + <a-input + v-model:value.trim="form.comments" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { RoleParam } from '@/api/system/role/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: RoleParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<RoleParam>({ + roleName: '', + roleCode: '', + comments: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views-demo/system/role/index.vue b/src/views-demo/system/role/index.vue new file mode 100644 index 0000000..497d2fb --- /dev/null +++ b/src/views-demo/system/role/index.vue @@ -0,0 +1,212 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <role-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="roleId" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 800 }" + cache-key="proSystemRoleTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a @click="openAuth(record)">分配权限</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此角色吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 编辑弹窗 --> + <role-edit v-model:visible="showEdit" :data="current" @done="reload" /> + <!-- 权限分配弹窗 --> + <role-auth v-model:visible="showAuth" :data="current" /> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import RoleSearch from './components/role-search.vue'; + import RoleEdit from './components/role-edit.vue'; + import RoleAuth from './components/role-auth.vue'; + import { pageRoles, removeRole, removeRoles } from '@/api/system/role'; + import type { Role, RoleParam } from '@/api/system/role/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '角色名称', + dataIndex: 'roleName', + sorter: true, + showSorterTooltip: false + }, + { + title: '角色标识', + dataIndex: 'roleCode', + sorter: true, + showSorterTooltip: false + }, + { + title: '备注', + dataIndex: 'comments', + sorter: true, + showSorterTooltip: false + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '操作', + key: 'action', + width: 200, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<Role[]>([]); + + // 当前编辑数据 + const current = ref<Role | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 是否显示权限分配弹窗 + const showAuth = ref(false); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageRoles({ ...where, ...orders, page, limit }); + }; + + /* 搜索 */ + const reload = (where?: RoleParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: Role) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 打开权限分配弹窗 */ + const openAuth = (row?: Role) => { + current.value = row ?? null; + showAuth.value = true; + }; + + /* 删除单个 */ + const remove = (row: Role) => { + const hide = messageLoading('请求中..', 0); + removeRole(row.roleId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的角色吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeRoles(selection.value.map((d) => d.roleId)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemRole' + }; +</script> diff --git a/src/views-demo/system/user/components/role-select.vue b/src/views-demo/system/user/components/role-select.vue new file mode 100644 index 0000000..04534b9 --- /dev/null +++ b/src/views-demo/system/user/components/role-select.vue @@ -0,0 +1,71 @@ +<!-- 角色选择下拉框 --> +<template> + <a-select + allow-clear + mode="multiple" + :value="roleIds" + :placeholder="placeholder" + @update:value="updateValue" + @blur="onBlur" + > + <a-select-option + v-for="item in data" + :key="item.roleId" + :value="item.roleId" + > + {{ item.roleName }} + </a-select-option> + </a-select> +</template> + +<script lang="ts" setup> + import { ref, computed } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { listRoles } from '@/api/system/role'; + import type { Role } from '@/api/system/role/model'; + + const emit = defineEmits<{ + (e: 'update:value', value: Role[]): void; + (e: 'blur'): void; + }>(); + + const props = withDefaults( + defineProps<{ + // 选中的角色 + value?: Role[]; + // + placeholder?: string; + }>(), + { + placeholder: '请选择角色' + } + ); + + // 选中的角色id + const roleIds = computed(() => props.value?.map((d) => d.roleId as number)); + + // 角色数据 + const data = ref<Role[]>([]); + + /* 更新选中数据 */ + const updateValue = (value: number[]) => { + emit( + 'update:value', + value.map((v) => ({ roleId: v })) + ); + }; + + /* 获取角色数据 */ + listRoles() + .then((list) => { + data.value = list; + }) + .catch((e) => { + message.error(e.message); + }); + + /* 失去焦点 */ + const onBlur = () => { + emit('blur'); + }; +</script> diff --git a/src/views-demo/system/user/components/sex-select.vue b/src/views-demo/system/user/components/sex-select.vue new file mode 100644 index 0000000..a1f6020 --- /dev/null +++ b/src/views-demo/system/user/components/sex-select.vue @@ -0,0 +1,64 @@ +<!-- 性别选择下拉框 --> +<template> + <a-select + show-search + option-filter-prop="label" + :options="data" + allow-clear + :value="value" + :placeholder="placeholder" + @update:value="updateValue" + @blur="onBlur" + /> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { listDictionaryData } from '@/api/system/dictionary-data'; + import type { SelectProps } from 'ant-design-vue/es'; + + const emit = defineEmits<{ + (e: 'update:value', value: string): void; + (e: 'blur'): void; + }>(); + + withDefaults( + defineProps<{ + value?: string; + placeholder?: string; + }>(), + { + placeholder: '请选择性别' + } + ); + + // 字典数据 + const data = ref<SelectProps['options']>([]); + + /* 更新选中数据 */ + const updateValue = (value: string) => { + emit('update:value', value); + }; + + /* 获取字典数据 */ + listDictionaryData({ + dictCode: 'sex' + }) + .then((list) => { + data.value = list.map((d) => { + return { + value: d.dictDataCode, + label: d.dictDataName + }; + }); + }) + .catch((e) => { + message.error(e.message); + }); + + /* 失去焦点 */ + const onBlur = () => { + emit('blur'); + }; +</script> diff --git a/src/views-demo/system/user/components/user-edit.vue b/src/views-demo/system/user/components/user-edit.vue new file mode 100644 index 0000000..80972f5 --- /dev/null +++ b/src/views-demo/system/user/components/user-edit.vue @@ -0,0 +1,275 @@ +<!-- 用户编辑弹窗 --> +<template> + <ele-modal + :width="680" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改用户' : '新建用户'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 7, sm: 4, xs: 24 } : { flex: '90px' }" + :wrapper-col=" + styleResponsive ? { md: 17, sm: 20, xs: 24 } : { flex: '1' } + " + > + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="用户账号" name="username"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入用户账号" + v-model:value="form.username" + /> + </a-form-item> + <a-form-item label="用户名" name="nickname"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入用户名" + v-model:value="form.nickname" + /> + </a-form-item> + <a-form-item label="性别" name="sex"> + <sex-select v-model:value="form.sex" /> + </a-form-item> + <a-form-item label="角色" name="roles"> + <role-select v-model:value="form.roles" /> + </a-form-item> + <a-form-item label="邮箱" name="email"> + <a-input + allow-clear + :maxlength="100" + placeholder="请输入邮箱" + v-model:value="form.email" + /> + </a-form-item> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="手机号" name="phone"> + <a-input + allow-clear + :maxlength="11" + placeholder="请输入手机号" + v-model:value="form.phone" + /> + </a-form-item> + <a-form-item label="出生日期"> + <a-date-picker + class="ele-fluid" + value-format="YYYY-MM-DD" + placeholder="请选择出生日期" + v-model:value="form.birthday" + /> + </a-form-item> + <a-form-item v-if="!isUpdate" label="登录密码" name="password"> + <a-input-password + :maxlength="20" + v-model:value="form.password" + placeholder="请输入登录密码" + /> + </a-form-item> + <a-form-item label="个人简介"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入个人简介" + v-model:value="form.introduction" + /> + </a-form-item> + </a-col> + </a-row> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { emailReg, phoneReg } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import RoleSelect from './role-select.vue'; + import SexSelect from './sex-select.vue'; + import { addUser, updateUser, checkExistence } from '@/api/system/user'; + import type { User } from '@/api/system/user/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: User | null; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<User>({ + userId: undefined, + username: '', + nickname: '', + sex: undefined, + roles: [], + email: '', + phone: '', + password: '', + introduction: '', + birthday: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + username: [ + { + required: true, + type: 'string', + validator: (_rule: Rule, value: string) => { + return new Promise<void>((resolve, reject) => { + if (!value) { + return reject('请输入用户账号'); + } + checkExistence('username', value, props.data?.userId) + .then(() => { + reject('账号已经存在'); + }) + .catch(() => { + resolve(); + }); + }); + }, + trigger: 'blur' + } + ], + nickname: [ + { + required: true, + message: '请输入用户名', + type: 'string', + trigger: 'blur' + } + ], + sex: [ + { + required: true, + message: '请选择性别', + type: 'string', + trigger: 'blur' + } + ], + roles: [ + { + required: true, + message: '请选择角色', + type: 'array', + trigger: 'blur' + } + ], + email: [ + { + pattern: emailReg, + message: '邮箱格式不正确', + type: 'string', + trigger: 'blur' + } + ], + password: [ + { + required: true, + type: 'string', + validator: async (_rule: Rule, value: string) => { + if (isUpdate.value || /^[\S]{5,18}$/.test(value)) { + return Promise.resolve(); + } + return Promise.reject('密码必须为5-18位非空白字符'); + }, + trigger: 'blur' + } + ], + phone: [ + { + pattern: phoneReg, + message: '手机号格式不正确', + type: 'string', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateUser : addUser; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields({ + ...props.data, + password: '' + }); + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views-demo/system/user/components/user-import.vue b/src/views-demo/system/user/components/user-import.vue new file mode 100644 index 0000000..0d8cbcb --- /dev/null +++ b/src/views-demo/system/user/components/user-import.vue @@ -0,0 +1,88 @@ +<!-- 用户导入弹窗 --> +<template> + <ele-modal + :width="520" + :footer="null" + title="导入用户" + :visible="visible" + @update:visible="updateVisible" + > + <a-spin :spinning="loading"> + <a-upload-dragger + accept=".xls,.xlsx" + :show-upload-list="false" + :customRequest="doUpload" + style="padding: 24px 0; margin-bottom: 16px" + > + <p class="ant-upload-drag-icon"> + <cloud-upload-outlined /> + </p> + <p class="ant-upload-hint">将文件拖到此处,或点击上传</p> + </a-upload-dragger> + </a-spin> + <div class="ele-text-center"> + <span>只能上传xls、xlsx文件,</span> + <a + href="https://cdn.eleadmin.com/20200610/用户导入模板.xlsx" + download="用户导入模板.xlsx" + > + 下载模板 + </a> + </div> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { CloudUploadOutlined } from '@ant-design/icons-vue'; + import { importUsers } from '@/api/system/user'; + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + defineProps<{ + // 是否打开弹窗 + visible: boolean; + }>(); + + // 导入请求状态 + const loading = ref(false); + + /* 上传 */ + const doUpload = ({ file }) => { + if ( + ![ + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ].includes(file.type) + ) { + message.error('只能选择 excel 文件'); + return false; + } + if (file.size / 1024 / 1024 > 10) { + message.error('大小不能超过 10MB'); + return false; + } + loading.value = true; + importUsers(file) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + return false; + }; + + /* 更新 visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; +</script> diff --git a/src/views-demo/system/user/components/user-search.vue b/src/views-demo/system/user/components/user-search.vue new file mode 100644 index 0000000..3e6e866 --- /dev/null +++ b/src/views-demo/system/user/components/user-search.vue @@ -0,0 +1,111 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="用户账号"> + <a-input + v-model:value.trim="form.username" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="用户名"> + <a-input + v-model:value.trim="form.nickname" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="性别"> + <a-select v-model:value="form.sex" placeholder="请选择" allow-clear> + <a-select-option value="1">男</a-select-option> + <a-select-option value="2">女</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { UserParam } from '@/api/system/user/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const props = defineProps<{ + // 默认搜索条件 + where?: UserParam; + }>(); + + const emit = defineEmits<{ + (e: 'search', where?: UserParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<UserParam>({ + username: '', + nickname: '', + sex: undefined, + ...props.where + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views-demo/system/user/details/index.vue b/src/views-demo/system/user/details/index.vue new file mode 100644 index 0000000..177bb22 --- /dev/null +++ b/src/views-demo/system/user/details/index.vue @@ -0,0 +1,122 @@ +<template> + <div class="ele-body"> + <a-card title="基本信息" :bordered="false"> + <a-form + class="ele-form-detail" + :label-col=" + styleResponsive ? { md: 2, sm: 4, xs: 6 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { md: 22, sm: 20, xs: 18 } : { flex: '1' } + " + > + <a-form-item label="账号"> + <div class="ele-text-secondary">{{ form.username }}</div> + </a-form-item> + <a-form-item label="用户名"> + <div class="ele-text-secondary">{{ form.nickname }}</div> + </a-form-item> + <a-form-item label="性别"> + <div class="ele-text-secondary">{{ form.sexName }}</div> + </a-form-item> + <a-form-item label="手机号"> + <div class="ele-text-secondary">{{ form.phone }}</div> + </a-form-item> + <a-form-item label="角色"> + <a-tag v-for="item in form.roles" :key="item.roleId" color="blue"> + {{ item.roleName }} + </a-tag> + </a-form-item> + <a-form-item label="创建时间"> + <div class="ele-text-secondary">{{ form.createTime }}</div> + </a-form-item> + <a-form-item label="状态"> + <a-badge + v-if="typeof form.status === 'number'" + :status="(['processing', 'error'][form.status] as any)" + :text="['正常', '冻结'][form.status]" + /> + </a-form-item> + </a-form> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, watch, unref } from 'vue'; + import { useRouter } from 'vue-router'; + import { message } from 'ant-design-vue/es'; + import { toDateString } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { setPageTabTitle } from '@/utils/page-tab-util'; + import { getUser } from '@/api/system/user'; + import type { User } from '@/api/system/user/model'; + const ROUTE_PATH = '/system/user/details'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const { currentRoute } = useRouter(); + + // 用户信息 + const { form, assignFields } = useFormData<User>({ + userId: undefined, + username: '', + nickname: '', + sexName: '', + phone: '', + roles: [], + createTime: undefined, + status: undefined + }); + + // 请求状态 + const loading = ref(true); + + /* */ + const query = () => { + const { query } = unref(currentRoute); + const id = query.id; + if (!id || form.userId === Number(id)) { + return; + } + loading.value = true; + getUser(Number(id)) + .then((data) => { + loading.value = false; + assignFields({ + ...data, + createTime: toDateString(data.createTime) + }); + // 修改页签标题 + if (unref(currentRoute).path === ROUTE_PATH) { + setPageTabTitle(data.nickname + '的信息'); + } + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }; + + watch( + currentRoute, + (route) => { + const { path } = unref(route); + if (path !== ROUTE_PATH) { + return; + } + query(); + }, + { immediate: true } + ); +</script> + +<script lang="ts"> + export default { + name: 'SystemUserDetails' + }; +</script> diff --git a/src/views-demo/system/user/index.vue b/src/views-demo/system/user/index.vue new file mode 100644 index 0000000..13730cf --- /dev/null +++ b/src/views-demo/system/user/index.vue @@ -0,0 +1,304 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <user-search :where="defaultWhere" @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="userId" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 1000 }" + :where="defaultWhere" + cache-key="proSystemUserTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + <a-button type="dashed" class="ele-btn-icon" @click="openImport"> + <template #icon> + <upload-outlined /> + </template> + <span>导入</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'nickname'"> + <router-link :to="'/system/user/details?id=' + record.userId"> + {{ record.nickname }} + </router-link> + </template> + <template v-else-if="column.key === 'roles'"> + <a-tag v-for="item in record.roles" :key="item.roleId" color="blue"> + {{ item.roleName }} + </a-tag> + </template> + <template v-else-if="column.key === 'status'"> + <a-switch + :checked="record.status === 0" + @change="(checked: boolean) => editStatus(checked, record)" + /> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a @click="resetPsw(record)">重置密码</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此用户吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 编辑弹窗 --> + <user-edit v-model:visible="showEdit" :data="current" @done="reload" /> + <!-- 导入弹窗 --> + <user-import v-model:visible="showImport" @done="reload" /> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref, reactive } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + DeleteOutlined, + UploadOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { toDateString, messageLoading } from 'ele-admin-pro/es'; + import UserSearch from './components/user-search.vue'; + import UserEdit from './components/user-edit.vue'; + import UserImport from './components/user-import.vue'; + import { + pageUsers, + removeUser, + removeUsers, + updateUserStatus, + updateUserPassword + } from '@/api/system/user'; + import type { User, UserParam } from '@/api/system/user/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '用户账号', + dataIndex: 'username', + sorter: true, + showSorterTooltip: false + }, + { + title: '用户名', + key: 'nickname', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false + }, + { + title: '性别', + dataIndex: 'sexName', + width: 80, + align: 'center', + sorter: true, + showSorterTooltip: false + }, + { + title: '手机号', + dataIndex: 'phone', + sorter: true, + showSorterTooltip: false + }, + { + title: '角色', + key: 'roles' + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '状态', + key: 'status', + dataIndex: 'status', + sorter: true, + showSorterTooltip: false, + width: 90, + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 200, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<User[]>([]); + + // 当前编辑数据 + const current = ref<User | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 是否显示用户导入弹窗 + const showImport = ref(false); + + // 默认搜索条件 + const defaultWhere = reactive({ + username: '', + nickname: '' + }); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageUsers({ ...where, ...orders, page, limit }); + }; + + /* 搜索 */ + const reload = (where?: UserParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: User) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 打开编辑弹窗 */ + const openImport = () => { + showImport.value = true; + }; + + /* 删除单个 */ + const remove = (row: User) => { + const hide = messageLoading('请求中..', 0); + removeUser(row.userId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的用户吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeUsers(selection.value.map((d) => d.userId)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 重置用户密码 */ + const resetPsw = (row: User) => { + Modal.confirm({ + title: '提示', + content: '确定要重置此用户的密码为"123456"吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + updateUserPassword(row.userId) + .then((msg) => { + hide(); + message.success(msg); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 修改用户状态 */ + const editStatus = (checked: boolean, row: User) => { + const status = checked ? 0 : 1; + updateUserStatus(row.userId, status) + .then((msg) => { + row.status = status; + message.success(msg); + }) + .catch((e) => { + message.error(e.message); + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemUser' + }; +</script> diff --git a/src/views-demo/user/message/components/message-letter.vue b/src/views-demo/user/message/components/message-letter.vue new file mode 100644 index 0000000..e3313cc --- /dev/null +++ b/src/views-demo/user/message/components/message-letter.vue @@ -0,0 +1,152 @@ +<template> + <div> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 600 }" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="read"> + 标记已读 + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + 删除消息 + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'status'"> + <span :class="['ele-text-warning', 'ele-text-info'][record.status]"> + {{ ['未读', '已读'][record.status] }} + </span> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="reply(record)">回复</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此消息吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { EleProTable } from 'ele-admin-pro/es'; + import { pageLetters } from '@/api/user/message'; + import type { Message } from '@/api/user/message/model'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + + const emit = defineEmits<{ + (e: 'update-data'): void; + }>(); + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '私信内容', + dataIndex: 'title', + ellipsis: true + }, + { + title: '发送时间', + dataIndex: 'time', + ellipsis: true, + width: 140, + align: 'center' + }, + { + title: '状态', + key: 'status', + width: 90, + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 120, + align: 'center', + hideInSetting: true + } + ]); + + // 列表选中数据 + const selection = ref<Message[]>([]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageLetters({ ...where, ...orders, page, limit }); + }; + + /* 回复 */ + const reply = (row: Message) => { + console.log(row); + message.info('点击了回复'); + }; + + /* 删除单个 */ + const remove = (row: Message) => { + console.log(row); + message.info('点击了删除'); + updateUnReadNum(); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + message.info('点击了删除'); + updateUnReadNum(); + }; + + /* 标记已读 */ + const read = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + selection.value.forEach((d) => { + d.status = 1; + }); + updateUnReadNum(); + }; + + /* 触发更新未读数量事件 */ + const updateUnReadNum = () => { + emit('update-data'); + }; +</script> diff --git a/src/views-demo/user/message/components/message-notice.vue b/src/views-demo/user/message/components/message-notice.vue new file mode 100644 index 0000000..3a3548a --- /dev/null +++ b/src/views-demo/user/message/components/message-notice.vue @@ -0,0 +1,152 @@ +<template> + <div> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 600 }" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="confirmBatch"> + 批量确认 + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + 删除通知 + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'status'"> + <span :class="['ele-text-warning', 'ele-text-info'][record.status]"> + {{ ['未确认', '已确认'][record.status] }} + </span> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="confirm(record)">确认</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此通知吗" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { EleProTable } from 'ele-admin-pro/es'; + import { pageNotices } from '@/api/user/message'; + import type { Message } from '@/api/user/message/model'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + + const emit = defineEmits<{ + (e: 'update-data'): void; + }>(); + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '通知标题', + dataIndex: 'title', + ellipsis: true + }, + { + title: '通知时间', + dataIndex: 'time', + ellipsis: true, + width: 140, + align: 'center' + }, + { + title: '状态', + key: 'status', + width: 90, + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 120, + align: 'center', + hideInSetting: true + } + ]); + + // 列表选中数据 + const selection = ref<Message[]>([]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageNotices({ ...where, ...orders, page, limit }); + }; + + /* 确认 */ + const confirm = (row: Message) => { + console.log(row); + message.info('点击了确认'); + }; + + /* 删除单个 */ + const remove = (row: Message) => { + console.log(row); + message.info('点击了删除'); + updateUnReadNum(); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + message.info('点击了删除'); + updateUnReadNum(); + }; + + /* 批量确认 */ + const confirmBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + selection.value.forEach((d) => { + d.status = 1; + }); + updateUnReadNum(); + }; + + /* 触发更新未读数量事件 */ + const updateUnReadNum = () => { + emit('update-data'); + }; +</script> diff --git a/src/views-demo/user/message/components/message-todo.vue b/src/views-demo/user/message/components/message-todo.vue new file mode 100644 index 0000000..e392a27 --- /dev/null +++ b/src/views-demo/user/message/components/message-todo.vue @@ -0,0 +1,152 @@ +<template> + <div> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 600 }" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="okBatch"> + 批量完成 + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + 删除待办 + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'status'"> + <span :class="['ele-text-warning', 'ele-text-info'][record.status]"> + {{ ['未完成', '已完成'][record.status] }} + </span> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="ok(record)">完成</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此消息吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { EleProTable } from 'ele-admin-pro/es'; + import { pageTodos } from '@/api/user/message'; + import type { Message } from '@/api/user/message/model'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + + const emit = defineEmits<{ + (e: 'update-data'): void; + }>(); + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '待办内容', + dataIndex: 'title', + ellipsis: true + }, + { + title: '结束时间', + dataIndex: 'time', + ellipsis: true, + width: 140, + align: 'center' + }, + { + title: '状态', + key: 'status', + width: 90, + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 120, + align: 'center', + hideInSetting: true + } + ]); + + // 列表选中数据 + const selection = ref<Message[]>([]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageTodos({ ...where, ...orders, page, limit }); + }; + + /* 完成 */ + const ok = (row: Message) => { + console.log(row); + message.info('点击了完成'); + }; + + /* 删除单个 */ + const remove = (row: Message) => { + console.log(row); + message.info('点击了删除'); + updateUnReadNum(); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + message.info('点击了删除'); + updateUnReadNum(); + }; + + /* 批量完成 */ + const okBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + selection.value.forEach((d) => { + d.status = 1; + }); + updateUnReadNum(); + }; + + /* 触发更新未读数量事件 */ + const updateUnReadNum = () => { + emit('update-data'); + }; +</script> diff --git a/src/views-demo/user/message/index.vue b/src/views-demo/user/message/index.vue new file mode 100644 index 0000000..777cb3d --- /dev/null +++ b/src/views-demo/user/message/index.vue @@ -0,0 +1,180 @@ +<template> + <div :class="['ele-body', { 'demo-message-responsive': styleResponsive }]"> + <a-card :bordered="false" :body-style="{ padding: '0px' }"> + <div class="ele-cell ele-cell-align-top ele-user-message"> + <div class="message-menu-wrap"> + <a-menu :selected-keys="active" :mode="mode"> + <a-menu-item key="notice"> + <router-link to="/user/message?type=notice"> + <a-badge v-if="unReadNotice" :count="unReadNotice" /> + <span>系统通知</span> + </router-link> + </a-menu-item> + <a-menu-item key="letter"> + <router-link to="/user/message?type=letter"> + <a-badge v-if="unReadLetter" :count="unReadLetter" /> + <span>用户私信</span> + </router-link> + </a-menu-item> + <a-menu-item key="todo"> + <router-link to="/user/message?type=todo"> + <a-badge v-if="unReadTodo" :count="unReadTodo" /> + <span>代办事项</span> + </router-link> + </a-menu-item> + </a-menu> + </div> + <div class="ele-cell-content" style="overflow-x: hidden"> + <transition name="slide-right" mode="out-in"> + <message-notice + v-if="active.includes('notice')" + @update-data="queryUnReadNum" + /> + <message-letter + v-else-if="active.includes('letter')" + @update-data="queryUnReadNum" + /> + <message-todo v-else @update-data="queryUnReadNum" /> + </transition> + </div> + </div> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, watch, unref, computed } from 'vue'; + import { useRouter } from 'vue-router'; + import { storeToRefs } from 'pinia'; + import { message } from 'ant-design-vue/es'; + import { useThemeStore } from '@/store/modules/theme'; + import MessageNotice from './components/message-notice.vue'; + import MessageLetter from './components/message-letter.vue'; + import MessageTodo from './components/message-todo.vue'; + import { getUnReadNum } from '@/api/user/message'; + + const { currentRoute } = useRouter(); + const themeStore = useThemeStore(); + const { screenWidth, styleResponsive } = storeToRefs(themeStore); + + // 导航选中 + const active = ref<string[]>([]); + + // 通知未读数量 + const unReadNotice = ref(0); + + // 私信未读数量 + const unReadLetter = ref(0); + + // 代办未读数量 + const unReadTodo = ref(0); + + // 导航模式 + const mode = computed(() => { + return styleResponsive.value && screenWidth.value < 768 + ? 'horizontal' + : 'inline'; + }); + + watch( + currentRoute, + (route) => { + const { path, query } = unref(route); + if (path === '/user/message') { + const defaultType = 'notice'; + if (!query.type) { + active.value = [defaultType]; + } else if (typeof query.type === 'string') { + active.value = [query.type || defaultType]; + } else if (query.type.length && query.type[0]) { + active.value = [query.type[0]]; + } else { + active.value = [defaultType]; + } + } + }, + { + immediate: true + } + ); + + /* 查询未读数量 */ + const queryUnReadNum = () => { + getUnReadNum() + .then((result) => { + unReadNotice.value = result.notice; + unReadLetter.value = result.letter; + unReadTodo.value = result.todo; + }) + .catch((e) => { + message.error(e.message); + }); + }; + + queryUnReadNum(); +</script> + +<script lang="ts"> + export default { + name: 'UserMessage' + }; +</script> + +<style lang="less" scoped> + .message-menu-wrap { + width: 150px; + display: flex; + + :deep(.ant-menu) { + padding-top: 16px; + + .ant-badge { + vertical-align: -2px; + margin-right: 10px; + } + + .ant-badge-count { + height: 16px; + line-height: 16px; + border-radius: 8px; + box-shadow: none; + min-width: 16px; + padding: 0 2px; + } + + .ant-scroll-number-only { + height: 16px; + + & > p.ant-scroll-number-only-unit { + height: 16px; + } + } + } + + & + .ele-cell-content { + padding: 16px 24px; + overflow: auto; + } + } + + @media screen and (max-width: 768px) { + .demo-message-responsive { + .ele-user-message { + display: block; + + & > .ele-cell-content { + padding: 16px 16px; + } + } + + .message-menu-wrap { + width: auto; + display: block; + + :deep(.ant-menu) { + padding-top: 0; + } + } + } + } +</style> diff --git a/src/views-demo/user/profile/index.vue b/src/views-demo/user/profile/index.vue new file mode 100644 index 0000000..1d7970f --- /dev/null +++ b/src/views-demo/user/profile/index.vue @@ -0,0 +1,426 @@ +<template> + <div class="ele-body ele-body-card"> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive + ? { xxl: 6, xl: 7, lg: 9, md: 10, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-card :bordered="false"> + <div class="ele-text-center"> + <div class="user-info-avatar-group" @click="openCropper"> + <a-avatar :size="110" :src="form.avatar" /> + <upload-outlined class="user-info-avatar-icon" /> + </div> + <h1>{{ loginUser.nickname }}</h1> + <div>{{ loginUser.introduction }}</div> + </div> + <div class="user-info-list"> + <div class="ele-cell"> + <user-outlined /> + <div class="ele-cell-content">资深前端工程师</div> + </div> + <div class="ele-cell"> + <home-outlined /> + <div class="ele-cell-content">某某公司 - 研发部 - 某某组</div> + </div> + <div class="ele-cell"> + <environment-outlined /> + <div class="ele-cell-content">中国 • 浙江省 • 杭州市</div> + </div> + <div class="ele-cell"> + <tag-outlined /> + <div class="ele-cell-content">JavaScript、HTML、CSS</div> + </div> + </div> + <a-divider dashed /> + <h6>标签</h6> + <div class="user-info-tags"> + <a-tag>很有想法的</a-tag> + <a-tag>专注设计</a-tag> + <a-tag>辣~</a-tag> + <a-tag>大长腿</a-tag> + <a-tag>川妹子</a-tag> + <a-tag>海纳百川</a-tag> + </div> + </a-card> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xxl: 18, xl: 17, lg: 15, md: 14, sm: 24, xs: 24 } + : { span: 18 } + " + > + <a-card + :bordered="false" + :body-style="{ paddingTop: '0px', minHeight: '600px' }" + > + <a-tabs v-model:active-key="active" size="large"> + <a-tab-pane tab="基本信息" key="info"> + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col=" + styleResponsive + ? { lg: 4, md: 6, sm: 4, xs: 24 } + : { flex: '100px' } + " + :wrapper-col=" + styleResponsive + ? { lg: 20, md: 18, sm: 20, xs: 24 } + : { flex: '1' } + " + style="max-width: 580px; margin-top: 20px" + > + <a-form-item label="昵称" name="nickname"> + <a-input + v-model:value="form.nickname" + placeholder="请输入昵称" + allow-clear + /> + </a-form-item> + <a-form-item label="性别" name="sex"> + <a-select + v-model:value="form.sex" + placeholder="请选择性别" + allow-clear + > + <a-select-option value="保密">保密</a-select-option> + <a-select-option value="男">男</a-select-option> + <a-select-option value="女">女</a-select-option> + </a-select> + </a-form-item> + <a-form-item label="邮箱" name="email"> + <a-input + v-model:value="form.email" + placeholder="请输入邮箱" + allow-clear + /> + </a-form-item> + <a-form-item label="个人简介"> + <a-textarea + v-model:value="form.introduction" + placeholder="请输入个人简介" + :rows="4" + /> + </a-form-item> + <a-form-item label="街道地址"> + <a-input + v-model:value="form.address" + placeholder="请输入街道地址" + allow-clear + /> + </a-form-item> + <a-form-item label="联系电话:"> + <div class="ele-cell"> + <a-input v-model:value="form.tellPre" style="width: 65px" /> + <div class="ele-cell-content"> + <a-input + v-model:value="form.tell" + placeholder="请输入联系电话" + allow-clear + /> + </div> + </div> + </a-form-item> + <a-form-item + :wrapper-col=" + styleResponsive + ? { + lg: { offset: 4 }, + md: { offset: 6 }, + sm: { offset: 4 } + } + : { offset: 4 } + " + > + <a-button type="primary" :loading="loading" @click="save"> + {{ loading ? '保存中..' : '保存更改' }} + </a-button> + </a-form-item> + </a-form> + </a-tab-pane> + <a-tab-pane tab="账号绑定" key="account"> + <div class="user-account-list"> + <div class="ele-cell"> + <div class="ele-cell-content"> + <div class="ele-cell-title">密保手机</div> + <div class="ele-cell-desc">已绑定手机: 138****8293</div> + </div> + <a>去修改</a> + </div> + <a-divider /> + <div class="ele-cell"> + <div class="ele-cell-content"> + <div class="ele-cell-title">密保邮箱</div> + <div class="ele-cell-desc"> + 已绑定邮箱: eleadmin@eclouds.com + </div> + </div> + <a>去修改</a> + </div> + <a-divider /> + <div class="ele-cell"> + <div class="ele-cell-content"> + <div class="ele-cell-title">密保问题</div> + <div class="ele-cell-desc">未设置密保问题</div> + </div> + <a>去设置</a> + </div> + <a-divider /> + <div class="ele-cell"> + <qq-outlined class="user-account-icon" /> + <div class="ele-cell-content"> + <div class="ele-cell-title">绑定QQ</div> + <div class="ele-cell-desc">当前未绑定QQ账号</div> + </div> + <a>去绑定</a> + </div> + <a-divider /> + <div class="ele-cell"> + <wechat-outlined class="user-account-icon" /> + <div class="ele-cell-content"> + <div class="ele-cell-title">绑定微信</div> + <div class="ele-cell-desc">当前未绑定绑定微信账号</div> + </div> + <a>去绑定</a> + </div> + <a-divider /> + <div class="ele-cell"> + <alipay-outlined class="user-account-icon" /> + <div class="ele-cell-content"> + <div class="ele-cell-title">绑定支付宝</div> + <div class="ele-cell-desc">当前未绑定绑定支付宝账号</div> + </div> + <a>去绑定</a> + </div> + </div> + </a-tab-pane> + </a-tabs> + </a-card> + </a-col> + </a-row> + <!-- 头像裁剪弹窗 --> + <ele-cropper-modal + :src="form.avatar" + v-model:visible="visible" + :options="{ autoCropArea: 1, viewMode: 1, dragMode: 'move' }" + @done="onCrop" + /> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive, computed } from 'vue'; + import { + UploadOutlined, + UserOutlined, + HomeOutlined, + EnvironmentOutlined, + TagOutlined, + QqOutlined, + WechatOutlined, + AlipayOutlined + } from '@ant-design/icons-vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { useUserStore } from '@/store/modules/user'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const userStore = useUserStore(); + + // + const formRef = ref<FormInstance | null>(null); + + // tab 页选中 + const active = ref('info'); + + // 保存按钮 loading + const loading = ref(false); + + // 是否显示裁剪弹窗 + const visible = ref(false); + + // 登录用户信息 + const loginUser = computed(() => userStore.info ?? {}); + + // 表单数据 + const form = reactive({ + nickname: loginUser.value.nickname, + sex: '保密', + email: 'eleadmin@eclouds.com', + introduction: loginUser.value.introduction, + address: '', + tellPre: '0752', + tell: '', + avatar: 'https://cdn.eleadmin.com/20200610/avatar.jpg' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + nickname: [ + { + required: true, + message: '请输入昵称', + type: 'string' + } + ], + sex: [ + { + required: true, + message: '请选择性别', + type: 'string' + } + ], + email: [ + { + required: true, + message: '请输入邮箱', + type: 'string' + } + ] + }); + + /* 修改登录用户 */ + const updateLoginUser = (obj: Record<string, any>) => { + userStore.setInfo({ ...loginUser.value, ...obj }); + }; + + /* 保存更改 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + setTimeout(() => { + loading.value = false; + message.success('保存成功'); + updateLoginUser(form); + }, 800); + }) + .catch(() => {}); + }; + + /* 头像裁剪完成回调 */ + const onCrop = (result: string) => { + form.avatar = result; + visible.value = false; + updateLoginUser(form); + }; + + /* 打开图片裁剪 */ + const openCropper = () => { + visible.value = true; + }; +</script> + +<script lang="ts"> + export default { + name: 'UserProfile' + }; +</script> + +<style lang="less" scoped> + /* 用户资料卡片 */ + .user-info-avatar-group { + margin: 16px 0; + display: inline-block; + position: relative; + cursor: pointer; + + .user-info-avatar-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #fff; + font-size: 30px; + display: none; + z-index: 2; + } + + &:hover .user-info-avatar-icon { + display: block; + } + + &:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 50%; + background-color: transparent; + transition: background-color 0.3s; + } + + &:hover:after { + background-color: rgba(0, 0, 0, 0.4); + } + + & + h1 { + margin-bottom: 8px; + } + } + + /* 用户信息列表 */ + .user-info-list { + margin: 47px 0 32px 0; + + .ele-cell + .ele-cell { + margin-top: 16px; + } + + & + .ant-divider { + margin-bottom: 16px; + } + } + + /* 用户标签 */ + .user-info-tags { + margin: 16px 0 4px 0; + + .ant-tag { + margin: 0 12px 8px 0; + } + } + + /* 用户账号绑定列表 */ + .user-account-list { + & > .ele-cell { + padding: 16px 8px; + } + + .user-account-icon { + color: #fff; + padding: 8px; + font-size: 26px; + border-radius: 50%; + + &.anticon-qq { + background: #3492ed; + } + + &.anticon-wechat { + background: #4daf29; + } + + &.anticon-alipay { + background: #1476fe; + } + } + } +</style> diff --git a/src/views/content/category/components/cate-article-edit.vue b/src/views/content/category/components/cate-article-edit.vue new file mode 100644 index 0000000..d9b506a --- /dev/null +++ b/src/views/content/category/components/cate-article-edit.vue @@ -0,0 +1,443 @@ +<!-- 文章编辑弹窗 --> +<template> + <ele-modal + :width="680" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改内容' : '新建内容'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 7, sm: 24 } : { flex: '90px' }" + :wrapper-col="styleResponsive ? { md: 17, sm: 24 } : { flex: '1' }" + > + <a-row :gutter="16"> + <a-col :span="24"> + <a-form-item + label="文章标题" + name="title" + :label-col="{ flex: '90px' }" + :wrapper-col="{ flex: 1 }" + > + <a-input + allow-clear + placeholder="请输入文章标题" + v-model:value="form.title" + /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="所属栏目"> + <cate-select + :data="cateList" + placeholder="请选择所属栏目" + v-model:value="form.cateId" + /> + </a-form-item> + <a-form-item v-if="form.type === 1" label="内容来源" name="origin"> + <a-input + allow-clear + placeholder="请输入内容来源" + v-model:value="form.origin" + /> + </a-form-item> + <a-form-item v-if="form.type !== 1" label="发布者" name="user"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入发布者" + v-model:value="form.user" + /> + </a-form-item> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item + label="文章模板" + :label-col="{ flex: '90px' }" + :wrapper-col="{ flex: 1 }" + > + <a-input + allow-clear + :disabled="form.type !== 0" + placeholder="请输入模板路径" + v-model:value="form.template" + /> + </a-form-item> + + <a-form-item label="文章类型" name="type"> + <a-radio-group v-model:value="form.type" @change="onTypeChange"> + <a-radio :value="0">文章</a-radio> + <a-radio :value="1">外链</a-radio> + <a-radio :value="2">图集</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="16"> + <a-col :span="12"> + <a-form-item label="关键字" name="keywords"> + <a-input + allow-clear + placeholder="请输入关键字" + v-model:value="form.keywords" + /> + </a-form-item> + </a-col> + <a-col :span="12"> + <a-form-item label="发布时间" name="createTime"> + <a-date-picker + show-time + format="YYYY-MM-DD HH:mm:ss" + placeholder="请选择发布时间" + v-model:value="form.createTime" + /> + </a-form-item> + </a-col> + </a-row> + <a-row> + <a-col :span="24"> + <a-form-item + label="图片" + name="image" + :label-col="{ flex: '90px' }" + :wrapper-col="{ flex: '1' }" + > + <ele-image-upload + v-model:value="images" + :limit="12" + :drag="true" + :multiple="true" + :upload-handler="uploadHandler" + :remove-handler="removeHandler" + @upload="onUpload" + /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="16" v-if="form.type === 1"> + <a-col :span="24"> + <a-form-item + label="外链地址" + :label-col="{ flex: '90px' }" + :wrapper-col="{ flex: 1 }" + > + <a-input + allow-clear + :disabled="form.type !== 1" + placeholder="请输入外链地址" + v-model:value="form.link" + /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="16"> + <a-col :span="24"> + <a-form-item + label="文章描述" + :label-col="{ flex: '90px' }" + :wrapper-col="{ flex: '1' }" + > + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入文章描述" + v-model:value="form.summary" + /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="16" v-if="form.type === 0"> + <a-col :span="24"> + <a-form-item + label="内容" + :label-col="{ flex: '90px' }" + :wrapper-col="{ flex: '1' }" + name="review" + > + <tinymce-editor v-model:value="form.content" :init="config" /> + </a-form-item> + </a-col> + </a-row> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { ItemType } from 'ele-admin-pro/es/ele-image-upload/types'; + import CateSelect from './cate-select.vue'; + import type { Article } from '@/api/content/article/model'; + import { addArticle, updateArticle } from '@/api/content/article'; + import request from '@/utils/request'; + import type { Category } from '@/api/content/category/model'; + import TinymceEditor from '@/components/TinymceEditor/index.vue'; + import dayjs from 'dayjs'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Article | null; + // 全部栏目 + cateList: Category[]; + // 栏目id + cateId?: number; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + const editorRef = ref<InstanceType<typeof TinymceEditor> | null>(null); + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 标题图 + const images = ref<ItemType[]>([]); + + const config = ref({ + height: 280, + menubar: false, + images_upload_handler: (blobInfo, success, error) => { + const file = blobInfo.blob(); + // 使用 axios 上传,实际开发这段建议写在 api 中再调用 api + const formData = new FormData(); + formData.append('file', file, file.name); + request + .post('/file/upload', formData) + .then((res) => { + if (res.data.code === 0) { + success(res.data.data); + } else { + error(res.data.message); + } + }) + .catch((e) => { + console.error(e); + error(e.message); + }); + }, + // 自定义文件上传(这里使用把选择的文件转成 blob 演示) + file_picker_callback: (callback: any, _value: any, meta: any) => { + const input = document.createElement('input'); + input.setAttribute('type', 'file'); + // 设定文件可选类型 + if (meta.filetype === 'image') { + input.setAttribute('accept', 'image/*'); + } else if (meta.filetype === 'media') { + input.setAttribute('accept', 'video/*'); + } + input.onchange = () => { + const file = input.files?.[0]; + if (!file) { + return; + } + if (meta.filetype === 'media') { + if (!file.type.startsWith('video/')) { + editorRef.value?.alert({ content: '只能选择视频文件' }); + return; + } + } + if (file.size / 1024 / 1024 > 20) { + editorRef.value?.alert({ content: '大小不能超过 20MB' }); + return; + } + const reader = new FileReader(); + reader.onload = (e) => { + if (e.target?.result != null) { + const blob = new Blob([e.target.result], { type: file.type }); + callback(URL.createObjectURL(blob)); + } + }; + reader.readAsArrayBuffer(file); + }; + input.click(); + } + }); + //单选改变事件 + const onTypeChange = () => { + if (form.type === 0) { + form.link = ''; + } + }; + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Article>({ + id: undefined, + cateId: undefined, + title: '', + user: '', + origin: '', + type: 0, + link: '', + image: '', + keywords: '', + template: '', + summary: '', + content: '', + createTime: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + title: [ + { + required: true, + message: '请输入标题', + type: 'string', + trigger: 'blur' + } + ] + }); + /* 上传事件 */ + const uploadHandler = (file: File) => { + const item: ItemType = { + file, + uid: (file as any).uid, + name: file.name, + progress: 0, + status: undefined + }; + if (!file.type.startsWith('image')) { + message.error('只能选择图片'); + return; + } + if (file.size / 1024 / 1024 > 2) { + message.error('大小不能超过 2MB'); + return; + } + item.url = window.URL.createObjectURL(file); + images.value.push(item); + onUpload(item); + }; + + const removeHandler = (item) => { + images.value = images.value.filter((d) => d !== item); + }; + + /* 上传 item */ + const onUpload = (d: ItemType) => { + const item: any = images.value.find((t) => t.uid === d.uid) ?? d; + // 上传 + item.status = 'uploading'; + const formData = new FormData(); + formData.append('file', item.file); + request({ + url: '/file/upload', + method: 'post', + data: formData, + onUploadProgress: (e: any) => { + // 文件上传进度回调 + if (e.lengthComputable) { + item.progress = (e.loaded / e.total) * 100; + } + } + }) + .then((res) => { + if (res.data.code === 0) { + item.status = 'done'; + item.url = res.data.data; + } + }) + .catch((e) => { + message.error(e); + item.status = 'exception'; + }); + }; + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + let img_arr = []; + if (images.value.length > 0) { + images.value.forEach((item) => { + img_arr.push(item.url); + }); + } + const articleForm = { + ...form, + createTime: dayjs(form.createTime).format('YY-MM-DD HH:mm:ss'), + image: JSON.stringify(img_arr) + }; + const saveOrUpdate = isUpdate.value ? updateArticle : addArticle; + saveOrUpdate(articleForm) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + if (props.data.image != undefined) { + let img_arr = JSON.parse(props.data.image); + img_arr.map((item, uid) => { + images.value.push({ + uid: uid, + url: item + }); + }); + } + assignFields({ + ...props.data, + createTime: dayjs(props.data.createTime, 'YY-MM-DD HH:mm:ss') + }); + isUpdate.value = true; + } else { + form.cateId = props.cateId; + isUpdate.value = false; + } + } else { + resetFields(); + images.value = []; + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views/content/category/components/cate-article-list.vue b/src/views/content/category/components/cate-article-list.vue new file mode 100644 index 0000000..9e326a1 --- /dev/null +++ b/src/views/content/category/components/cate-article-list.vue @@ -0,0 +1,222 @@ +<template> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + height="calc(100vh - 290px)" + tool-class="ele-toolbar-form" + :scroll="{ x: 800 }" + tools-theme="default" + bordered + cache-key="contentCategpryArticle" + class="content-article-table" + > + <template #toolbar> + <cate-article-search @search="reload" @add="openEdit()" /> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'type'"> + <a-tag color="blue" v-if="record.type === 0">文章</a-tag> + <a-tag color="red" v-if="record.type === 1">外链</a-tag> + <a-tag color="green" v-if="record.type === 2">图集</a-tag> + </template> + <template v-else-if="column.key === 'status'"> + <a-switch + :checked="record.status === 0" + @change="(checked: boolean) => editStatus(checked, record)" + /> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此内容吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + <!-- 编辑弹窗 --> + <cate-article-edit + :data="current" + v-model:visible="showEdit" + :cate-list="cateList" + :cate-id="cateId" + @done="reload" + /> +</template> + +<script lang="ts" setup> + import { ref, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import CateArticleSearch from './cate-article-search.vue'; + import CateArticleEdit from './cate-article-edit.vue'; + import type { Category } from '@/api/content/category/model'; + import { Article, ArticleParam } from '@/api/content/article/model'; + import { + pageArticles, + removeArticle, + updateArticleStatus + } from '@/api/content/article'; + + const props = defineProps<{ + // 栏目 id + cateId?: number; + // 全部栏目 + cateList: Category[]; + }>(); + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: 'ID', + width: 60, + align: 'center', + dataIndex: 'id', + sorter: true, + showSorterTooltip: false + }, + { + title: '标题', + dataIndex: 'title', + sorter: true, + ellipsis: true, + showSorterTooltip: false + }, + { + title: '作者', + dataIndex: 'user', + width: 80, + align: 'center', + sorter: true, + showSorterTooltip: false + }, + { + title: '类型', + width: 80, + dataIndex: 'type', + key: 'type' + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '状态', + key: 'status', + showSorterTooltip: false, + width: 80, + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 100, + align: 'center' + } + ]); + + // 当前编辑数据 + const current = ref<Article | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageArticles({ + ...where, + ...orders, + page, + limit, + cateId: props.cateId + }); + }; + + /* 搜索 */ + const reload = (where?: ArticleParam) => { + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: Article) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 删除单个 */ + const remove = (row: Article) => { + const hide = messageLoading('请求中..', 0); + removeArticle(row.id) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 修改用户状态 */ + const editStatus = (checked: boolean, row: Article) => { + const status = checked ? 0 : 1; + updateArticleStatus(row.id, status) + .then((msg) => { + row.status = status; + message.success(msg); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + // 监听 id 变化 + watch( + () => props.cateId, + () => { + reload(); + } + ); +</script> + +<style lang="less" scoped> + .content-article-table :deep(.ant-table-body) { + overflow: auto !important; + overflow: overlay !important; + } + + .content-article-table :deep(.ant-table-pagination.ant-pagination) { + padding: 0 4px; + margin-bottom: 0; + } +</style> diff --git a/src/views/content/category/components/cate-article-search.vue b/src/views/content/category/components/cate-article-search.vue new file mode 100644 index 0000000..995a92b --- /dev/null +++ b/src/views/content/category/components/cate-article-search.vue @@ -0,0 +1,82 @@ +<!-- 搜索表单 --> +<template> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { xl: 6, lg: 8, md: 12, sm: 24, xs: 24 } : { span: 6 } + " + > + <a-input + v-model:value.trim="form.title" + placeholder="请输入标题" + allow-clear + /> + </a-col> + <a-col + v-bind=" + styleResponsive ? { xl: 6, lg: 8, md: 12, sm: 24, xs: 24 } : { span: 6 } + " + > + <a-input + v-model:value.trim="form.user" + placeholder="请输入发布者" + allow-clear + /> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 12, lg: 8, md: 24, sm: 24, xs: 24 } + : { span: 12 } + " + > + <a-space :size="10" style="flex-wrap: wrap"> + <a-button type="primary" class="ele-btn-icon" @click="search"> + <template #icon> + <search-outlined /> + </template> + <span>查询</span> + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="add"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + </a-space> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { ArticleParam } from '@/api/content/article/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: ArticleParam): void; + (e: 'add'): void; + }>(); + + // 表单数据 + const { form } = useFormData<ArticleParam>({ + title: '', + user: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 添加 */ + const add = () => { + emit('add'); + }; +</script> diff --git a/src/views/content/category/components/cate-edit.vue b/src/views/content/category/components/cate-edit.vue new file mode 100644 index 0000000..145203a --- /dev/null +++ b/src/views/content/category/components/cate-edit.vue @@ -0,0 +1,328 @@ +<!-- 编辑弹窗 --> +<template> + <ele-modal + :width="860" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改栏目' : '添加栏目'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 7, sm: 24 } : { flex: '90px' }" + :wrapper-col="styleResponsive ? { md: 17, sm: 24 } : { flex: '1' }" + > + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="上级栏目" name="parentId"> + <cate-select + :data="cateList" + placeholder="请选择上级栏目" + v-model:value="form.parentId" + /> + </a-form-item> + <a-form-item label="栏目名称" name="cateName"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入栏目名称" + v-model:value="form.cateName" + /> + </a-form-item> + <a-form-item label="主题颜色"> + <ele-color-picker + :show-alpha="true" + v-model:value="form.color" + :predefine="predefineColors" + /> + </a-form-item> + <a-form-item label="栏目图片"> + <ele-image-upload + v-model:value="images" + :limit="1" + :drag="true" + :upload-handler="uploadHandler" + :remove-handler="removeHandler" + @upload="onUpload" + /> + </a-form-item> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="栏目类型" name="menuType"> + <a-radio-group + v-model:value="form.menuType" + @change="onMenuTypeChange" + > + <a-radio :value="0">目录</a-radio> + <a-radio :value="1">菜单</a-radio> + <a-radio :value="2">外链</a-radio> + </a-radio-group> + </a-form-item> + <a-form-item label="外链地址" name="url" v-if="form.menuType === 2"> + <a-input + allow-clear + placeholder="请输入外链地址" + v-model:value="form.url" + /> + </a-form-item> + <a-form-item + label="栏目模板" + name="template" + v-if="form.menuType !== 2" + > + <a-input + allow-clear + placeholder="请输入栏目模板" + :disabled="form.menuType === 0" + v-model:value="form.template" + /> + </a-form-item> + <a-form-item label="排序号" name="sortNumber"> + <a-input-number + :min="0" + :max="99999" + class="ele-fluid" + placeholder="请输入排序号" + v-model:value="form.sortNumber" + /> + </a-form-item> + <a-form-item label="描述"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入描述" + v-model:value="form.introduction" + /> + </a-form-item> + </a-col> + </a-row> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import CateSelect from './cate-select.vue'; + import type { ItemType } from 'ele-admin-pro/es/ele-image-upload/types'; + import type { Category } from '@/api/content/category/model'; + import { addCategory, updateCategory } from '@/api/content/category'; + import request from '@/utils/request'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Category | null; + // 栏目id + cateId?: number; + // 全部栏目 + cateList: Category[]; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + const images = ref<ItemType[]>([]); + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Category>({ + cateId: undefined, + parentId: undefined, + cateName: '', + menuType: 0, + url: '', + template: '', + color: 'rgba(255, 69, 0, 0.68)', + image: '', + sortNumber: undefined, + introduction: '' + }); + // 预设颜色 + const predefineColors = ref([ + '#ff4500', + '#ff8c00', + '#ffd700', + '#90ee90', + '#00ced1', + '#1e90ff', + '#c71585', + 'rgba(255, 69, 0, 0.68)', + 'rgb(255, 120, 0)', + 'hsv(51, 100, 98)', + 'hsva(120, 40, 94, 0.5)', + 'hsl(181, 100%, 37%)', + 'hsla(209, 100%, 56%, 0.73)', + '#c7158577' + ]); + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + cateName: [ + { + required: true, + message: '请输入栏目名称', + type: 'string', + trigger: 'blur' + } + ], + sortNumber: [ + { + required: true, + message: '请输入排序号', + type: 'number', + trigger: 'blur' + } + ] + }); + /* menuType选择改变 */ + const onMenuTypeChange = () => { + if (form.menuType === 0) { + form.url = ''; + form.template = ''; + } else if (form.menuType === 1) { + form.url = ''; + } else { + form.template = ''; + } + }; + /* 上传事件 */ + const uploadHandler = (file: File) => { + const item: ItemType = { + file, + uid: (file as any).uid, + name: file.name, + progress: 0, + status: undefined + }; + if (!file.type.startsWith('image')) { + message.error('只能选择图片'); + return; + } + if (file.size / 1024 / 1024 > 2) { + message.error('大小不能超过 2MB'); + return; + } + item.url = window.URL.createObjectURL(file); + images.value.push(item); + onUpload(item); + }; + + const removeHandler = (item) => { + images.value = images.value.filter((d) => d !== item); + }; + + /* 上传 item */ + const onUpload = (d: ItemType) => { + const item: any = images.value.find((t) => t.uid === d.uid) ?? d; + // 上传 + item.status = 'uploading'; + const formData = new FormData(); + formData.append('file', item.file); + request({ + url: '/file/upload', + method: 'post', + data: formData, + onUploadProgress: (e: any) => { + // 文件上传进度回调 + if (e.lengthComputable) { + item.progress = (e.loaded / e.total) * 100; + } + } + }) + .then((res) => { + if (res.data.code === 0) { + item.status = 'done'; + item.url = res.data.data; + } + }) + .catch((e) => { + message.error(e); + item.status = 'exception'; + }); + }; + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const cateData = { + ...form, + parentId: form.parentId || 0, + image: images.value.length !== 0 ? images.value[0].url : undefined + }; + const saveOrUpdate = isUpdate.value ? updateCategory : addCategory; + saveOrUpdate(cateData) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + if (props.data.image != undefined) { + images.value.push({ + url: props.data.image, + uid: '' + }); + } + assignFields(props.data); + isUpdate.value = true; + } else { + form.parentId = props.cateId; + isUpdate.value = false; + } + } else { + resetFields(); + images.value = []; + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views/content/category/components/cate-outlink-edit.vue b/src/views/content/category/components/cate-outlink-edit.vue new file mode 100644 index 0000000..b2abaef --- /dev/null +++ b/src/views/content/category/components/cate-outlink-edit.vue @@ -0,0 +1,296 @@ +<template> + <a-card title="修改配置"> + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 7, sm: 24 } : { flex: '90px' }" + :wrapper-col="styleResponsive ? { md: 17, sm: 24 } : { flex: '1' }" + > + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="上级栏目" name="parentId"> + <cate-select + :data="cateList" + placeholder="请选择上级栏目" + v-model:value="form.parentId" + /> + </a-form-item> + <a-form-item label="栏目名称" name="cateName"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入栏目名称" + v-model:value="form.cateName" + /> + </a-form-item> + <a-form-item label="主题颜色"> + <ele-color-picker + :show-alpha="true" + v-model:value="form.color" + :predefine="predefineColors" + /> + </a-form-item> + <a-form-item label="栏目图片"> + <ele-image-upload + v-model:value="images" + :limit="1" + :drag="true" + :upload-handler="uploadHandler" + :remove-handler="removeHandler" + @upload="onUpload" + /> + </a-form-item> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="栏目类型" name="menuType"> + <a-radio-group + v-model:value="form.menuType" + @change="onMenuTypeChange" + > + <a-radio :value="0">目录</a-radio> + <a-radio :value="1">菜单</a-radio> + <a-radio :value="2">外链</a-radio> + </a-radio-group> + </a-form-item> + <a-form-item label="外链地址" name="url" v-if="form.menuType === 2"> + <a-input + allow-clear + placeholder="请输入外链地址" + v-model:value="form.url" + /> + </a-form-item> + <a-form-item + label="栏目模板" + name="template" + v-if="form.menuType !== 2" + > + <a-input + allow-clear + placeholder="请输入栏目模板" + :disabled="form.menuType === 0" + v-model:value="form.template" + /> + </a-form-item> + <a-form-item label="排序号" name="sortNumber"> + <a-input-number + :min="0" + :max="99999" + class="ele-fluid" + placeholder="请输入排序号" + v-model:value="form.sortNumber" + /> + </a-form-item> + <a-form-item label="描述"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入描述" + v-model:value="form.introduction" + /> + </a-form-item> + </a-col> + </a-row> + <a-row justify="end"> + <a-col :span="3"> + <a-form-item :wrapper-col="{ flex: 1 }" justify="end"> + <a-button type="primary" @click="save">提交</a-button> + </a-form-item> + </a-col> + </a-row> + </a-form> + </a-card> +</template> +<script lang="ts" setup> + import { Category } from '@/api/content/category/model'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types'; + import { storeToRefs } from 'pinia'; + import CateSelect from './cate-select.vue'; + import { reactive, ref, watch } from 'vue'; + import request from '@/utils/request'; + import { getCategory, updateCategory } from '@/api/content/category'; + + const props = defineProps<{ + // 栏目 id + cateId?: number; + // 全部栏目 + cateList: Category[]; + }>(); + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + const formRef = ref<FormInstance | null>(null); + const images = ref<ItemType[]>([]); + // 提交状态 + const loading = ref(false); + // 表单数据 + const { form } = useFormData<Category>({ + cateId: undefined, + parentId: undefined, + cateName: '', + menuType: 0, + url: '', + template: '', + color: 'rgba(255, 69, 0, 0.68)', + image: '', + sortNumber: undefined, + introduction: '' + }); + // 预设颜色 + const predefineColors = ref([ + '#ff4500', + '#ff8c00', + '#ffd700', + '#90ee90', + '#00ced1', + '#1e90ff', + '#c71585', + 'rgba(255, 69, 0, 0.68)', + 'rgb(255, 120, 0)', + 'hsv(51, 100, 98)', + 'hsva(120, 40, 94, 0.5)', + 'hsl(181, 100%, 37%)', + 'hsla(209, 100%, 56%, 0.73)', + '#c7158577' + ]); + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + cateName: [ + { + required: true, + message: '请输入栏目名称', + type: 'string', + trigger: 'blur' + } + ], + sortNumber: [ + { + required: true, + message: '请输入排序号', + type: 'number', + trigger: 'blur' + } + ] + }); + /* menuType选择改变 */ + const onMenuTypeChange = () => { + if (form.menuType === 0) { + form.url = ''; + form.template = ''; + } else if (form.menuType === 1) { + form.url = ''; + } else { + form.template = ''; + } + }; + /* 上传事件 */ + const uploadHandler = (file: File) => { + const item: ItemType = { + file, + uid: (file as any).uid, + name: file.name, + progress: 0, + status: undefined + }; + if (!file.type.startsWith('image')) { + message.error('只能选择图片'); + return; + } + if (file.size / 1024 / 1024 > 2) { + message.error('大小不能超过 2MB'); + return; + } + item.url = window.URL.createObjectURL(file); + images.value.push(item); + onUpload(item); + }; + + const removeHandler = (item) => { + images.value = images.value.filter((d) => d !== item); + }; + + /* 上传 item */ + const onUpload = (d: ItemType) => { + const item: any = images.value.find((t) => t.uid === d.uid) ?? d; + // 上传 + item.status = 'uploading'; + const formData = new FormData(); + formData.append('file', item.file); + request({ + url: '/file/upload', + method: 'post', + data: formData, + onUploadProgress: (e: any) => { + // 文件上传进度回调 + if (e.lengthComputable) { + item.progress = (e.loaded / e.total) * 100; + } + } + }) + .then((res) => { + if (res.data.code === 0) { + item.status = 'done'; + item.url = res.data.data; + } + }) + .catch((e) => { + message.error(e); + item.status = 'exception'; + }); + }; + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const cateData = { + ...form, + parentId: form.parentId || 0, + image: images.value.length !== 0 ? images.value[0].url : undefined + }; + updateCategory(cateData) + .then((msg) => { + loading.value = false; + message.success(msg); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + const getCateDetail = (cateId) => { + images.value = []; + getCategory(cateId).then((res: any) => { + Object.assign(form, res); + if (res.image && res.image != undefined) { + images.value.push({ + url: res.image, + uid: '' + }); + } + }); + }; + getCateDetail(props.cateId); + watch( + () => props.cateId, + (value) => { + if (value) { + getCateDetail(value); + } + } + ); +</script> +<style lang="scss" scoped></style> diff --git a/src/views/content/category/components/cate-select.vue b/src/views/content/category/components/cate-select.vue new file mode 100644 index 0000000..c6d7dc0 --- /dev/null +++ b/src/views/content/category/components/cate-select.vue @@ -0,0 +1,36 @@ +<!-- 选择下拉框 --> +<template> + <a-tree-select + allow-clear + tree-default-expand-all + :placeholder="placeholder" + :value="value || undefined" + :tree-data="data" + :dropdown-style="{ maxHeight: '360px', overflow: 'auto' }" + @update:value="updateValue" + /> +</template> + +<script lang="ts" setup> + import { Category } from '@/api/content/category/model'; + + const emit = defineEmits<{ + (e: 'update:value', value?: number): void; + }>(); + + withDefaults( + defineProps<{ + value?: number; + placeholder?: string; + data: Category[]; + }>(), + { + placeholder: '请选择分类' + } + ); + + /* 更新选中数据 */ + const updateValue = (value?: number) => { + emit('update:value', value); + }; +</script> diff --git a/src/views/content/category/index.vue b/src/views/content/category/index.vue new file mode 100644 index 0000000..eb80a79 --- /dev/null +++ b/src/views/content/category/index.vue @@ -0,0 +1,209 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false" :body-style="{ padding: '16px' }"> + <ele-split-layout + width="266px" + allow-collapse + :right-style="{ overflow: 'hidden' }" + :style="{ minHeight: 'calc(100vh - 152px)' }" + > + <div> + <ele-toolbar theme="default"> + <a-space :size="10"> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + type="primary" + :disabled="!current" + class="ele-btn-icon" + @click="openEdit(current)" + > + <template #icon> + <edit-outlined /> + </template> + <span>修改</span> + </a-button> + <a-button + danger + type="primary" + :disabled="!current" + class="ele-btn-icon" + @click="remove" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </ele-toolbar> + <div class="ele-border-split content-category-list"> + <a-tree + :tree-data="(data as any)" + v-model:expanded-keys="expandedRowKeys" + v-model:selected-keys="selectedRowKeys" + @select="onTreeSelect" + /> + </div> + </div> + <template #content> + <cate-outlink-edit + v-if="current && current.menuType === 2" + :cate-list="data" + :cate-id="current.cateId" + /> + <cate-article-list + v-if="current && current.menuType === 1" + :cate-list="data" + :cate-id="current.cateId" + /> + </template> + </ele-split-layout> + </a-card> + <!-- 编辑弹窗 --> + <cate-edit + v-model:visible="showEdit" + :data="editData" + :cate-list="data" + :cate-id="current?.cateId" + @done="query" + /> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + EditOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import { messageLoading, toTreeData, eachTreeData } from 'ele-admin-pro/es'; + import CateArticleList from './components/cate-article-list.vue'; + import cateOutlinkEdit from './components/cate-outlink-edit.vue'; + import CateEdit from './components/cate-edit.vue'; + import { listCategories, removeCategory } from '@/api/content/category'; + import type { Category } from '@/api/content/category/model'; + + // 加载状态 + const loading = ref(true); + + // 树形数据 + const data = ref<Category[]>([]); + + // 树展开的key + const expandedRowKeys = ref<number[]>([]); + + // 树选中的key + const selectedRowKeys = ref<number[]>([]); + + // 选中数据 + const current = ref<Category | null>(null); + + // 是否显示表单弹窗 + const showEdit = ref(false); + + // 编辑回显数据 + const editData = ref<Category | null>(null); + + /* 查询 */ + const query = () => { + loading.value = true; + listCategories() + .then((list) => { + loading.value = false; + const eks: number[] = []; + list.forEach((d) => { + d.key = d.cateId; + d.value = d.cateId; + d.title = d.cateName; + d.disabled = Boolean(d.disabled); + if (typeof d.key === 'number') { + eks.push(d.key); + } + }); + expandedRowKeys.value = eks; + data.value = toTreeData({ + data: list, + idField: 'cateId', + parentIdField: 'parentId' + }); + if (list.length) { + if (typeof list[0].key === 'number') { + selectedRowKeys.value = [list[0].key]; + } + current.value = list[0]; + } else { + selectedRowKeys.value = []; + current.value = null; + } + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }; + + /* 选择数据 */ + const onTreeSelect = () => { + eachTreeData(data.value, (d) => { + if (typeof d.key === 'number' && selectedRowKeys.value.includes(d.key)) { + current.value = d; + return false; + } + }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (item?: Category | null) => { + editData.value = item ?? null; + showEdit.value = true; + }; + + /* 删除 */ + const remove = () => { + Modal.confirm({ + title: '提示', + content: '确定要删除选中的栏目吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeCategory(current.value?.cateId) + .then((msg) => { + hide(); + message.success(msg); + query(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + query(); +</script> + +<script lang="ts"> + export default { + name: 'ArticleCategory' + }; +</script> + +<style lang="less" scoped> + .content-category-list { + padding: 12px 6px; + height: calc(100vh - 242px); + border-width: 1px; + border-style: solid; + overflow: auto; + } +</style> diff --git a/src/views/dashboard/analysis/components/hot-search.vue b/src/views/dashboard/analysis/components/hot-search.vue new file mode 100644 index 0000000..c1a5531 --- /dev/null +++ b/src/views/dashboard/analysis/components/hot-search.vue @@ -0,0 +1,72 @@ +<template> + <a-card :bordered="false" title="热门搜索"> + <v-chart + ref="hotSearchChartRef" + :option="hotSearchChartOption" + style="height: 330px" + /> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { use } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { LineChart, BarChart } from 'echarts/charts'; + import { GridComponent, TooltipComponent } from 'echarts/components'; + import VChart from 'vue-echarts'; + import 'echarts-wordcloud'; + import { wordCloudColor } from 'ele-admin-pro/es'; + import { getWordCloudList } from '@/api/dashboard/analysis'; + import useEcharts from '@/utils/use-echarts'; + + use([CanvasRenderer, LineChart, BarChart, GridComponent, TooltipComponent]); + + // + const hotSearchChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([hotSearchChartRef]); + + // 词云图表配置 + const hotSearchChartOption: EChartsCoreOption = reactive({}); + + /* 获取词云数据 */ + const getWordCloudData = () => { + getWordCloudList() + .then((data) => { + Object.assign(hotSearchChartOption, { + tooltip: { + show: true, + confine: true, + borderWidth: 1 + }, + series: [ + { + type: 'wordCloud', + width: '100%', + height: '100%', + sizeRange: [12, 24], + gridSize: 6, + textStyle: { + color: wordCloudColor + }, + emphasis: { + textStyle: { + shadowBlur: 8, + shadowColor: 'rgba(0, 0, 0, .15)' + } + }, + data: data + } + ] + }); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + getWordCloudData(); +</script> diff --git a/src/views/dashboard/analysis/components/sale-card.vue b/src/views/dashboard/analysis/components/sale-card.vue new file mode 100644 index 0000000..45d6c62 --- /dev/null +++ b/src/views/dashboard/analysis/components/sale-card.vue @@ -0,0 +1,248 @@ +<template> + <a-card :bordered="false" :body-style="{ padding: 0 }"> + <a-tabs + size="large" + v-model:activeKey="saleSearch.type" + class="monitor-card-tabs" + @change="onSaleTypeChange" + > + <a-tab-pane tab="销售额" key="saleroom" /> + <a-tab-pane tab="访问量" key="visits" /> + <template #rightExtra> + <a-space + size="middle" + :class="[ + 'analysis-tabs-extra', + { 'hidden-lg-and-down': styleResponsive } + ]" + > + <a-radio-group v-model:value="saleSearch.dateType"> + <a-radio-button value="1">今天</a-radio-button> + <a-radio-button value="2">本周</a-radio-button> + <a-radio-button value="3">本月</a-radio-button> + <a-radio-button value="4">本年</a-radio-button> + </a-radio-group> + <div style="width: 300px"> + <a-range-picker + value-format="YYYY-MM-DD" + v-model:value="saleSearch.datetime" + /> + </div> + </a-space> + </template> + </a-tabs> + <div style="padding-bottom: 10px"> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { lg: 17, md: 15, sm: 24, xs: 24 } : { span: 17 } + " + > + <div v-if="saleSearch.type === 'saleroom'" class="demo-monitor-title"> + 销售量趋势 + </div> + <div v-else class="demo-monitor-title">访问量趋势</div> + <v-chart + ref="saleChartRef" + :option="saleChartOption" + style="height: 320px" + /> + </a-col> + <a-col + v-bind=" + styleResponsive ? { lg: 7, md: 9, sm: 24, xs: 24 } : { span: 7 } + " + > + <div v-if="saleSearch.type === 'saleroom'"> + <div class="demo-monitor-title">门店销售额排名</div> + <div + v-for="(item, index) in saleroomRankData" + :key="index" + class="demo-monitor-rank-item ele-cell" + > + <ele-tag + shape="circle" + :color="index < 3 ? '#314659' : ''" + style="border: none" + > + {{ index + 1 }} + </ele-tag> + <div class="ele-cell-content ele-elip">{{ item.name }}</div> + <div class="ele-text-secondary">{{ item.value }}</div> + </div> + </div> + <div v-else> + <div class="demo-monitor-title">门店访问量排名</div> + <div + v-for="(item, index) in visitsRankData" + :key="index" + class="demo-monitor-rank-item ele-cell" + > + <ele-tag + shape="circle" + :color="index < 3 ? '#314659' : ''" + style="border: none" + > + {{ index + 1 }} + </ele-tag> + <div class="ele-cell-content ele-elip">{{ item.name }}</div> + <div class="ele-text-secondary">{{ item.value }}</div> + </div> + </div> + </a-col> + </a-row> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { use } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { BarChart } from 'echarts/charts'; + import { GridComponent, TooltipComponent } from 'echarts/components'; + import VChart from 'vue-echarts'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { getSaleroomList } from '@/api/dashboard/analysis'; + import type { SaleroomData } from '@/api/dashboard/analysis/model'; + import useEcharts from '@/utils/use-echarts'; + + use([CanvasRenderer, BarChart, GridComponent, TooltipComponent]); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const saleChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([saleChartRef]); + + // 销售额柱状图配置 + const saleChartOption: EChartsCoreOption = reactive({}); + + // 门店销售排名数据 + const saleroomRankData = ref([ + { name: '工专路 1 号店', value: '323,234' }, + { name: '工专路 2 号店', value: '323,234' }, + { name: '工专路 3 号店', value: '323,234' }, + { name: '工专路 4 号店', value: '323,234' }, + { name: '工专路 5 号店', value: '323,234' }, + { name: '工专路 6 号店', value: '323,234' }, + { name: '工专路 7 号店', value: '323,234' } + ]); + + // 门店访问排名数据 + const visitsRankData = ref([ + { name: '工专路 1 号店', value: '323,234' }, + { name: '工专路 2 号店', value: '323,234' }, + { name: '工专路 3 号店', value: '323,234' }, + { name: '工专路 4 号店', value: '323,234' }, + { name: '工专路 5 号店', value: '323,234' }, + { name: '工专路 6 号店', value: '323,234' }, + { name: '工专路 7 号店', value: '323,234' } + ]); + + // 销售量趋势数据 + const saleroomData1 = ref<SaleroomData[]>([]); + + // 访问量趋势数据 + const saleroomData2 = ref<SaleroomData[]>([]); + + interface SaleSearchType { + type: string; + dateType: string; + datetime: [string, string]; + } + + // 销售量搜索参数 + const saleSearch = reactive<SaleSearchType>({ + type: 'saleroom', + dateType: '1', + datetime: ['2022-01-08', '2022-02-12'] + }); + + /* 获取销售量数据 */ + const getSaleroomData = () => { + getSaleroomList() + .then((data) => { + saleroomData1.value = data.list1; + saleroomData2.value = data.list2; + onSaleTypeChange(); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + /* 销售量tab选择改变事件 */ + const onSaleTypeChange = () => { + if (saleSearch.type === 'saleroom') { + Object.assign(saleChartOption, { + tooltip: { + trigger: 'axis' + }, + xAxis: [ + { + type: 'category', + data: saleroomData1.value.map((d) => d.month) + } + ], + yAxis: [ + { + type: 'value' + } + ], + series: [ + { + type: 'bar', + data: saleroomData1.value.map((d) => d.value) + } + ] + }); + } else { + Object.assign(saleChartOption, { + tooltip: { + trigger: 'axis' + }, + xAxis: [ + { + type: 'category', + data: saleroomData2.value.map((d) => d.month) + } + ], + yAxis: [ + { + type: 'value' + } + ], + series: [ + { + type: 'bar', + data: saleroomData2.value.map((d) => d.value) + } + ] + }); + } + }; + + getSaleroomData(); +</script> + +<style lang="less" scoped> + .monitor-card-tabs :deep(.ant-tabs-nav) { + padding: 0 16px; + } + + .demo-monitor-title { + padding: 6px 20px; + } + + .demo-monitor-rank-item { + padding: 0 20px; + margin-top: 18px; + } +</style> diff --git a/src/views/dashboard/analysis/components/statistics-card.vue b/src/views/dashboard/analysis/components/statistics-card.vue new file mode 100644 index 0000000..f4fefd5 --- /dev/null +++ b/src/views/dashboard/analysis/components/statistics-card.vue @@ -0,0 +1,246 @@ +<!-- 统计卡片 --> +<template> + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }" + > + <a-card class="analysis-chart-card" :bordered="false"> + <div class="ele-text-secondary ele-cell"> + <div class="ele-cell-content">总销售额</div> + <a-tooltip title="指标说明"> + <question-circle-outlined /> + </a-tooltip> + </div> + <h1 class="analysis-chart-card-num">¥ 126,560</h1> + <div class="analysis-chart-card-content" style="padding-top: 16px"> + <a-space size="middle"> + <span class="analysis-trend-text"> + 周同比12% <caret-up-outlined class="ele-text-danger" /> + </span> + <span class="analysis-trend-text"> + 日同比11% <caret-down-outlined class="ele-text-success" /> + </span> + </a-space> + </div> + <a-divider /> + <div>日销售额 ¥12,423</div> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }" + > + <a-card class="analysis-chart-card" :bordered="false"> + <div class="ele-text-secondary ele-cell"> + <div class="ele-cell-content">访问量</div> + <ele-tag color="red">日</ele-tag> + </div> + <h1 class="analysis-chart-card-num">8,846</h1> + <v-chart + ref="visitChartRef" + :option="visitChartOption" + style="height: 40px" + /> + <a-divider /> + <div>日访问量 1,234</div> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }" + > + <a-card class="analysis-chart-card" :bordered="false"> + <div class="ele-text-secondary ele-cell"> + <div class="ele-cell-content">支付笔数</div> + <ele-tag color="blue">月</ele-tag> + </div> + <h1 class="analysis-chart-card-num">6,560</h1> + <v-chart + ref="payNumChartRef" + :option="payNumChartOption" + style="height: 40px" + /> + <a-divider /> + <div>转化率 60%</div> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }" + > + <a-card class="analysis-chart-card" :bordered="false"> + <div class="ele-text-secondary ele-cell"> + <div class="ele-cell-content">活动运营效果</div> + <ele-tag color="green">周</ele-tag> + </div> + <h1 class="analysis-chart-card-num">78%</h1> + <div class="analysis-chart-card-content" style="padding-top: 16px"> + <a-progress + :percent="78" + :show-info="false" + stroke-color="#13c2c2" + status="active" + /> + </div> + <a-divider /> + <a-space size="middle"> + <span class="analysis-trend-text"> + 周同比12% <caret-up-outlined class="ele-text-danger" /> + </span> + <span class="analysis-trend-text"> + 日同比11% <caret-down-outlined class="ele-text-success" /> + </span> + </a-space> + </a-card> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { + QuestionCircleOutlined, + CaretUpOutlined, + CaretDownOutlined + } from '@ant-design/icons-vue'; + import { use } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { LineChart, BarChart } from 'echarts/charts'; + import { GridComponent, TooltipComponent } from 'echarts/components'; + import VChart from 'vue-echarts'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { getPayNumList } from '@/api/dashboard/analysis'; + import useEcharts from '@/utils/use-echarts'; + + use([CanvasRenderer, LineChart, BarChart, GridComponent, TooltipComponent]); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const visitChartRef = ref<InstanceType<typeof VChart> | null>(null); + const payNumChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([visitChartRef, payNumChartRef]); + + // 访问量折线图配置 + const visitChartOption: EChartsCoreOption = reactive({}); + + // 支付笔数柱状图配置 + const payNumChartOption: EChartsCoreOption = reactive({}); + + /* 获取支付笔数数据 */ + const getPayNumData = () => { + getPayNumList() + .then((data) => { + Object.assign(visitChartOption, { + color: '#975fe5', + tooltip: { + trigger: 'axis', + formatter: + '<i class="ele-chart-dot" style="background: #975fe5;"></i>{b0}: {c0}' + }, + grid: { + top: 10, + bottom: 0, + left: 0, + right: 0 + }, + xAxis: [ + { + show: false, + type: 'category', + boundaryGap: false, + data: data.map((d) => d.date) + } + ], + yAxis: [ + { + show: false, + type: 'value', + splitLine: { + show: false + } + } + ], + series: [ + { + type: 'line', + smooth: true, + symbol: 'none', + areaStyle: { + opacity: 0.5 + }, + data: data.map((d) => d.value) + } + ] + }); + + Object.assign(payNumChartOption, { + tooltip: { + trigger: 'axis', + formatter: + '<i class="ele-chart-dot" style="background: #5b8ff9;"></i>{b0}: {c0}' + }, + grid: { + top: 10, + bottom: 0, + left: 0, + right: 0 + }, + xAxis: [ + { + show: false, + type: 'category', + data: data.map((d) => d.date) + } + ], + yAxis: [ + { + show: false, + type: 'value', + splitLine: { + show: false + } + } + ], + series: [ + { + type: 'bar', + data: data.map((d) => d.value) + } + ] + }); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + getPayNumData(); +</script> + +<style lang="less" scoped> + .analysis-chart-card { + :deep(.ant-card-body) { + padding: 16px 22px 12px 22px; + } + + :deep(.ant-divider) { + margin: 12px 0; + } + } + + .analysis-chart-card-num { + font-size: 30px; + } + + .analysis-chart-card-content { + height: 40px; + } + + .analysis-trend-text { + white-space: nowrap; + } +</style> diff --git a/src/views/dashboard/analysis/components/visit-hour.vue b/src/views/dashboard/analysis/components/visit-hour.vue new file mode 100644 index 0000000..ca371d3 --- /dev/null +++ b/src/views/dashboard/analysis/components/visit-hour.vue @@ -0,0 +1,101 @@ +<template> + <a-card + :bordered="false" + title="最近1小时访问情况" + :body-style="{ padding: '16px 6px 0 0' }" + > + <v-chart + ref="visitHourChartRef" + :option="visitHourChartOption" + style="height: 362px" + /> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { use } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { LineChart } from 'echarts/charts'; + import { + GridComponent, + TooltipComponent, + LegendComponent + } from 'echarts/components'; + import VChart from 'vue-echarts'; + import { getVisitHourList } from '@/api/dashboard/analysis'; + import useEcharts from '@/utils/use-echarts'; + + use([ + CanvasRenderer, + LineChart, + GridComponent, + TooltipComponent, + LegendComponent + ]); + + // + const visitHourChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([visitHourChartRef]); + + // 最近 1 小时访问情况折线图配置 + const visitHourChartOption: EChartsCoreOption = reactive({}); + + /* 获取最近 1 小时访问情况数据 */ + const getVisitHourData = () => { + getVisitHourList() + .then((data) => { + Object.assign(visitHourChartOption, { + tooltip: { + trigger: 'axis' + }, + legend: { + data: ['浏览量', '访问量'], + right: 20 + }, + xAxis: [ + { + type: 'category', + boundaryGap: false, + data: data.map((d) => d.time) + } + ], + yAxis: [ + { + type: 'value' + } + ], + series: [ + { + name: '浏览量', + type: 'line', + smooth: true, + symbol: 'none', + areaStyle: { + opacity: 0.5 + }, + data: data.map((d) => d.views) + }, + { + name: '访问量', + type: 'line', + smooth: true, + symbol: 'none', + areaStyle: { + opacity: 0.5 + }, + data: data.map((d) => d.visits) + } + ] + }); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + getVisitHourData(); +</script> diff --git a/src/views/dashboard/analysis/index.vue b/src/views/dashboard/analysis/index.vue new file mode 100644 index 0000000..d0ede32 --- /dev/null +++ b/src/views/dashboard/analysis/index.vue @@ -0,0 +1,41 @@ +<template> + <div class="ele-body ele-body-card"> + <statistics-card /> + <sale-card /> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { lg: 16, md: 14, sm: 24, xs: 24 } : { span: 16 } + " + > + <visit-hour /> + </a-col> + <a-col + v-bind=" + styleResponsive ? { lg: 8, md: 10, sm: 24, xs: 24 } : { span: 8 } + " + > + <hot-search /> + </a-col> + </a-row> + </div> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import StatisticsCard from './components/statistics-card.vue'; + import SaleCard from './components/sale-card.vue'; + import VisitHour from './components/visit-hour.vue'; + import HotSearch from './components/hot-search.vue'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); +</script> + +<script lang="ts"> + export default { + name: 'DashboardAnalysis' + }; +</script> diff --git a/src/views/dashboard/monitor/components/browser-card.vue b/src/views/dashboard/monitor/components/browser-card.vue new file mode 100644 index 0000000..067abff --- /dev/null +++ b/src/views/dashboard/monitor/components/browser-card.vue @@ -0,0 +1,69 @@ +<template> + <a-card :bordered="false" title="浏览器分布" :body-style="{ padding: '0px' }"> + <v-chart + ref="browserChartRef" + :option="browserChartOption" + style="height: 222px" + /> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { use } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { PieChart } from 'echarts/charts'; + import { TooltipComponent, LegendComponent } from 'echarts/components'; + import VChart from 'vue-echarts'; + import { getBrowserCountList } from '@/api/dashboard/monitor'; + import useEcharts from '@/utils/use-echarts'; + + use([CanvasRenderer, PieChart, TooltipComponent, LegendComponent]); + + // + const browserChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([browserChartRef]); + + // 浏览器分布饼图配置 + const browserChartOption: EChartsCoreOption = reactive({}); + + /* 获取用户浏览器分布数据 */ + const getBrowserCountData = () => { + getBrowserCountList() + .then((data) => { + Object.assign(browserChartOption, { + tooltip: { + trigger: 'item', + confine: true, + borderWidth: 1 + }, + legend: { + bottom: 5, + itemWidth: 10, + itemHeight: 10, + icon: 'circle', + data: data.map((d) => d.name) + }, + series: [ + { + type: 'pie', + radius: ['45%', '70%'], + center: ['50%', '43%'], + label: { + show: false + }, + data: data + } + ] + }); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + getBrowserCountData(); +</script> diff --git a/src/views/dashboard/monitor/components/map-card.vue b/src/views/dashboard/monitor/components/map-card.vue new file mode 100644 index 0000000..87982f5 --- /dev/null +++ b/src/views/dashboard/monitor/components/map-card.vue @@ -0,0 +1,147 @@ +<template> + <a-card :bordered="false" title="用户分布"> + <a-row> + <a-col v-bind="styleResponsive ? { sm: 18, xs: 24 } : { span: 18 }"> + <v-chart + ref="userCountMapChartRef" + :option="userCountMapOption" + style="height: 469px" + /> + </a-col> + <a-col v-bind="styleResponsive ? { sm: 6, xs: 24 } : { span: 6 }"> + <div + v-for="item in userCountDataRank" + :key="item.name" + class="monitor-user-count-item ele-cell" + > + <div>{{ item.name }}</div> + <div class="ele-cell-content"> + <a-progress + status="normal" + :show-info="false" + :percent="item.percent" + /> + </div> + <div>{{ item.value }}</div> + </div> + </a-col> + </a-row> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { use, registerMap } from 'echarts/core'; + import type { EChartsCoreOption } from 'echarts/core'; + import { CanvasRenderer } from 'echarts/renderers'; + import { MapChart } from 'echarts/charts'; + import { + VisualMapComponent, + GeoComponent, + TooltipComponent + } from 'echarts/components'; + import VChart from 'vue-echarts'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { getChinaMapData, getUserCountList } from '@/api/dashboard/monitor'; + import type { UserCount } from '@/api/dashboard/monitor/model'; + import useEcharts from '@/utils/use-echarts'; + + use([ + CanvasRenderer, + MapChart, + VisualMapComponent, + GeoComponent, + TooltipComponent + ]); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const userCountMapChartRef = ref<InstanceType<typeof VChart> | null>(null); + + useEcharts([userCountMapChartRef]); + + // 用户分布前 10 名 + const userCountDataRank = ref<UserCount[]>([]); + + // 用户分布地图配置 + const userCountMapOption: EChartsCoreOption = reactive({}); + + /* 获取中国地图数据并注册地图 */ + const registerChinaMap = () => { + getChinaMapData() + .then((data) => { + registerMap('china', data); + getUserCountData(); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + /* 获取用户分布数据 */ + const getUserCountData = () => { + getUserCountList() + .then((data) => { + const temp = data.sort((a, b) => b.value - a.value); + const min = temp[temp.length - 1].value || 0; + const max = temp[0].value || 1; + // + const list = temp.length > 10 ? temp.slice(0, 15) : temp; + userCountDataRank.value = list.map((d) => { + return { + name: d.name, + value: d.value, + percent: (d.value / max) * 100 + }; + }); + // + Object.assign(userCountMapOption, { + tooltip: { + trigger: 'item', + borderWidth: 1 + }, + visualMap: { + min: min, + max: max, + text: ['高', '低'], + calculable: true + }, + series: [ + { + name: '用户数', + label: { + show: true + }, + type: 'map', + map: 'china', + data: data + } + ] + }); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + registerChinaMap(); +</script> + +<style lang="less" scoped> + .monitor-user-count-item { + margin-bottom: 8px; + + :deep(.ant-progress-inner) { + background: none; + } + + .ele-cell-content { + padding-right: 10px; + } + } +</style> diff --git a/src/views/dashboard/monitor/components/online-num.vue b/src/views/dashboard/monitor/components/online-num.vue new file mode 100644 index 0000000..e8bad2f --- /dev/null +++ b/src/views/dashboard/monitor/components/online-num.vue @@ -0,0 +1,70 @@ +<template> + <a-card :bordered="false" title="在线人数"> + <div class="monitor-online-num-card"> + <div>{{ currentTime }}</div> + <div class="monitor-online-num-title"> + <ele-count-up :end-val="onlineNum" /> + </div> + <div class="monitor-online-num-text">在线总人数</div> + <a-badge status="processing" :text="updateTimeText" /> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref, computed, onBeforeUnmount } from 'vue'; + import { toDateString } from 'ele-admin-pro/es'; + // 在线人数更新定时器 + let onlineNumTimer: number | null = null; + + // 在线总人数倒计时 + const updateTime = ref(10); + + // 当前时间 + const currentTime = ref(toDateString(new Date(), 'HH:mm:ss')); + + // 在线人数 + const onlineNum = ref(228); + + // 在线人数倒计时文字 + const updateTimeText = computed(() => updateTime.value + ' 秒后更新'); + + /* 在线人数更新倒计时 */ + const startUpdateOnlineNum = () => { + onlineNumTimer = window.setInterval(() => { + currentTime.value = toDateString(new Date(), 'HH:mm:ss'); + if (updateTime.value === 1) { + updateTime.value = 10; + onlineNum.value = 100 + Math.round(Math.random() * 900); + } else { + updateTime.value--; + } + }, 1000); + }; + + onBeforeUnmount(() => { + // 销毁定时器 + if (onlineNumTimer) { + clearInterval(onlineNumTimer); + onlineNumTimer = null; + } + }); + + startUpdateOnlineNum(); +</script> + +<style lang="less" scoped> + .monitor-online-num-card { + text-align: center; + } + + .monitor-online-num-title { + line-height: 1; + font-size: 50px; + margin: 22px 0 14px; + } + + .monitor-online-num-text { + margin-bottom: 22px; + } +</style> diff --git a/src/views/dashboard/monitor/components/statistics-card.vue b/src/views/dashboard/monitor/components/statistics-card.vue new file mode 100644 index 0000000..8ff75b5 --- /dev/null +++ b/src/views/dashboard/monitor/components/statistics-card.vue @@ -0,0 +1,166 @@ +<!-- 统计卡片 --> +<template> + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }" + > + <a-card :bordered="false" class="monitor-count-card"> + <ele-tag color="blue" shape="circle" size="large"> + <eye-filled /> + </ele-tag> + <h1 class="monitor-count-card-num">21.2 k</h1> + <div class="monitor-count-card-text">总访问人数</div> + <ele-avatar-list :data="visitUsers" size="small" :max="4" /> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }" + > + <a-card :bordered="false" class="monitor-count-card"> + <ele-tag color="orange" shape="circle" size="large"> + <fire-filled /> + </ele-tag> + <h1 class="monitor-count-card-num">1.6 k</h1> + <div class="monitor-count-card-text">点击量(近30天)</div> + <div class="monitor-count-card-trend ele-text-success"> + <up-outlined /> + <span>110.5%</span> + </div> + <a-tooltip title="指标说明"> + <question-circle-outlined class="monitor-count-card-tips" /> + </a-tooltip> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }" + > + <a-card :bordered="false" class="monitor-count-card"> + <ele-tag color="red" shape="circle" size="large"> + <flag-filled /> + </ele-tag> + <h1 class="monitor-count-card-num">826.0</h1> + <div class="monitor-count-card-text">到达量(近30天)</div> + <div class="monitor-count-card-trend ele-text-danger"> + <down-outlined /> + <span>15.5%</span> + </div> + </a-card> + </a-col> + <a-col + v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }" + > + <a-card :bordered="false" class="monitor-count-card"> + <ele-tag color="green" shape="circle" size="large"> + <thunderbolt-filled /> + </ele-tag> + <h1 class="monitor-count-card-num">28.8 %</h1> + <div class="monitor-count-card-text">转化率(近30天)</div> + <div class="monitor-count-card-trend ele-text-success"> + <up-outlined /> + <span>65.8%</span> + </div> + <a-tooltip title="指标说明"> + <question-circle-outlined class="monitor-count-card-tips" /> + </a-tooltip> + </a-card> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { + QuestionCircleOutlined, + EyeFilled, + FireFilled, + FlagFilled, + ThunderboltFilled, + UpOutlined, + DownOutlined + } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + + interface VisitUserType { + key: string | number; + name: string; + avatar: string; + } + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // 访问人数 + const visitUsers = ref<VisitUserType[]>([ + { + key: 1, + name: 'SunSmile', + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + key: 2, + name: '你的名字很好听', + avatar: + 'https://cdn.eleadmin.com/20200609/b6a811873e704db49db994053a5019b2.jpg' + }, + { + key: 3, + name: '全村人的希望', + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + }, + { + key: 4, + name: 'Jasmine', + avatar: + 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg' + }, + { + key: 5, + name: '酷酷的大叔', + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + }, + { + key: 6, + name: '管理员', + avatar: 'https://cdn.eleadmin.com/20200610/avatar.jpg' + } + ]); +</script> + +<style lang="less" scoped> + .monitor-count-card { + text-align: center; + + .monitor-count-card-num { + margin-top: 6px; + font-size: 32px; + } + + .monitor-count-card-text { + font-size: 12px; + margin: 8px 0; + opacity: 0.8; + } + + .monitor-count-card-trend { + font-weight: bold; + line-height: 26px; + + & > .anticon { + margin-right: 6px; + } + } + + .monitor-count-card-tips { + position: absolute; + top: 16px; + right: 16px; + cursor: pointer; + opacity: 0.6; + } + } +</style> diff --git a/src/views/dashboard/monitor/components/user-liveness.vue b/src/views/dashboard/monitor/components/user-liveness.vue new file mode 100644 index 0000000..9eb2e71 --- /dev/null +++ b/src/views/dashboard/monitor/components/user-liveness.vue @@ -0,0 +1,75 @@ +<template> + <a-card + :bordered="false" + title="用户活跃度" + :body-style="{ padding: '56px 0' }" + > + <div class="ele-cell"> + <div class="ele-cell-content ele-text-center"> + <div class="monitor-progress-group"> + <a-progress + type="circle" + :percent="70" + stroke-color="#52c41a" + :show-info="false" + :width="161" + /> + <a-progress + type="circle" + :percent="60" + stroke-color="#1890ff" + :show-info="false" + :width="121" + :stroke-width="5" + /> + <a-progress + type="circle" + :percent="35" + stroke-color="#f5222d" + :show-info="false" + :width="91" + :stroke-width="4" + /> + </div> + </div> + <div class="monitor-progress-legends"> + <div class="ele-text-small ele-elip"> + <a-badge color="green" text="活跃率: 70%" /> + </div> + <div class="ele-text-small ele-elip"> + <a-badge color="blue" text="留存率: 60%" /> + </div> + <div class="ele-text-small ele-elip"> + <a-badge color="red" text="跳出率: 35%" /> + </div> + </div> + </div> + </a-card> +</template> + +<style lang="less" scoped> + .monitor-progress-group { + position: relative; + display: inline-block; + + .ant-progress:not(:first-child) { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + margin-top: -1px; + } + } + + .monitor-progress-legends { + padding-right: 24px; + + :deep(.ant-badge-status-text) { + font-size: 12px; + } + + & > div + div { + margin-top: 8px; + } + } +</style> diff --git a/src/views/dashboard/monitor/components/user-rate.vue b/src/views/dashboard/monitor/components/user-rate.vue new file mode 100644 index 0000000..e88c449 --- /dev/null +++ b/src/views/dashboard/monitor/components/user-rate.vue @@ -0,0 +1,86 @@ +<template> + <a-card :bordered="false" title="用户评价"> + <div class="ele-cell ele-cell-align-bottom"> + <div style="font-size: 51px; line-height: 1">4.5</div> + <div class="ele-cell-content"> + <a-rate :value="userRate" disabled /> + <span style="color: #fadb14; margin-left: 8px">很棒</span> + </div> + </div> + <div class="ele-cell" style="margin: 18px 0"> + <div style="font-size: 28px; line-height: 1" class="ele-text-placeholder"> + -0% + </div> + <div class="ele-cell-content ele-text-small ele-text-secondary"> + 当前没有评价波动 + </div> + </div> + <div class="ele-cell"> + <div class="ele-cell-content"> + <a-progress :percent="60" stroke-color="#52c41a" :show-info="false" /> + </div> + <div class="monitor-evaluate-text"> + <star-filled /> + <span>5 : 368 人</span> + </div> + </div> + <div class="ele-cell"> + <div class="ele-cell-content"> + <a-progress :percent="40" stroke-color="#1890ff" :show-info="false" /> + </div> + <div class="monitor-evaluate-text"> + <star-filled /> + <span>4 : 256 人</span> + </div> + </div> + <div class="ele-cell"> + <div class="ele-cell-content"> + <a-progress :percent="20" stroke-color="#faad14" :show-info="false" /> + </div> + <div class="monitor-evaluate-text"> + <star-filled /> + <span>3 : 49 人</span> + </div> + </div> + <div class="ele-cell"> + <div class="ele-cell-content"> + <a-progress :percent="10" stroke-color="#f5222d" :show-info="false" /> + </div> + <div class="monitor-evaluate-text"> + <star-filled /> + <span>2 : 14 人</span> + </div> + </div> + <div class="ele-cell"> + <div class="ele-cell-content"> + <a-progress :percent="0" :show-info="false" /> + </div> + <div class="monitor-evaluate-text"> + <star-filled /> + <span>1 : 0 人</span> + </div> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { StarFilled } from '@ant-design/icons-vue'; + + // 用户评分 + const userRate = ref(4.5); +</script> + +<style lang="less" scoped> + .monitor-evaluate-text { + width: 90px; + flex-shrink: 0; + white-space: nowrap; + opacity: 0.8; + + & > .anticon { + font-size: 12px; + margin: 0 6px 0 8px; + } + } +</style> diff --git a/src/views/dashboard/monitor/components/user-satisfaction.vue b/src/views/dashboard/monitor/components/user-satisfaction.vue new file mode 100644 index 0000000..4933d1a --- /dev/null +++ b/src/views/dashboard/monitor/components/user-satisfaction.vue @@ -0,0 +1,79 @@ +<template> + <a-card :bordered="false" title="用户满意度"> + <div class="ele-cell ele-text-center"> + <div class="ele-cell-content" style="font-size: 24px">856</div> + <div class="ele-cell-content"> + <div class="monitor-face-smile"><i></i></div> + <div class="ele-text-secondary ele-elip" style="margin-top: 8px"> + 正面评论 + </div> + </div> + <h2 class="ele-cell-content ele-text-success">82%</h2> + </div> + <a-divider style="margin: 26px 0" /> + <div class="ele-cell ele-text-center"> + <div class="ele-cell-content" style="font-size: 24px">60</div> + <div class="ele-cell-content"> + <div class="monitor-face-cry"><i></i></div> + <div class="ele-text-secondary ele-elip" style="margin-top: 8px"> + 负面评论 + </div> + </div> + <h2 class="ele-cell-content ele-text-danger">9%</h2> + </div> + </a-card> +</template> + +<style lang="less" scoped> + .monitor-face-smile, + .monitor-face-cry { + width: 50px; + height: 50px; + display: inline-block; + background: #fbd971; + border-radius: 50%; + position: relative; + } + + .monitor-face-smile > i, + .monitor-face-smile:before, + .monitor-face-smile:after, + .monitor-face-cry > i, + .monitor-face-cry:before, + .monitor-face-cry:after { + width: 28px; + height: 28px; + border-radius: 50%; + transform: rotate(225deg); + border: 3px solid #f0c419; + border-right-color: transparent !important; + border-bottom-color: transparent !important; + position: absolute; + bottom: 8px; + left: 11px; + } + + .monitor-face-smile:before, + .monitor-face-smile:after, + .monitor-face-cry:before, + .monitor-face-cry:after { + content: ''; + width: 12px; + height: 12px; + left: 8px; + top: 14px; + border-color: #f29c1f; + transform: rotate(45deg); + } + + .monitor-face-smile:after, + .monitor-face-cry:after { + left: auto; + right: 8px; + } + + .monitor-face-cry > i { + transform: rotate(45deg); + bottom: -6px; + } +</style> diff --git a/src/views/dashboard/monitor/index.vue b/src/views/dashboard/monitor/index.vue new file mode 100644 index 0000000..c9a72d8 --- /dev/null +++ b/src/views/dashboard/monitor/index.vue @@ -0,0 +1,91 @@ +<template> + <div class="ele-body ele-body-card"> + <statistics-card /> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { lg: 18, md: 24, sm: 24, xs: 24 } : { span: 18 } + " + > + <map-card /> + </a-col> + <a-col + v-bind=" + styleResponsive ? { lg: 6, md: 24, sm: 24, xs: 24 } : { span: 6 } + " + > + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive + ? { lg: 24, md: 12, sm: 12, xs: 24 } + : { span: 24 } + " + > + <online-num /> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { lg: 24, md: 12, sm: 12, xs: 24 } + : { span: 24 } + " + > + <browser-card /> + </a-col> + </a-row> + </a-col> + </a-row> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive + ? { xl: 12, lg: 24, md: 24, sm: 24, xs: 24 } + : { span: 12 } + " + > + <user-rate /> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 12, xs: 24 } + : { span: 6 } + " + > + <user-satisfaction /> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 12, xs: 24 } + : { span: 6 } + " + > + <user-liveness /> + </a-col> + </a-row> + </div> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import StatisticsCard from './components/statistics-card.vue'; + import MapCard from './components/map-card.vue'; + import OnlineNum from './components/online-num.vue'; + import BrowserCard from './components/browser-card.vue'; + import UserRate from './components/user-rate.vue'; + import UserSatisfaction from './components/user-satisfaction.vue'; + import UserLiveness from './components/user-liveness.vue'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); +</script> + +<script lang="ts"> + export default { + name: 'DashboardMonitor' + }; +</script> diff --git a/src/views/dashboard/workplace/components/activities-card.vue b/src/views/dashboard/workplace/components/activities-card.vue new file mode 100644 index 0000000..864f5d9 --- /dev/null +++ b/src/views/dashboard/workplace/components/activities-card.vue @@ -0,0 +1,138 @@ +<!-- 最新动态 --> +<template> + <a-card :title="title" :bordered="false" :body-style="{ padding: '6px 0' }"> + <template #extra> + <more-icon @remove="onRemove" @edit="onEdit" /> + </template> + <div + style="height: 346px; padding: 22px 20px 0 20px" + class="ele-scrollbar-hover" + > + <a-timeline> + <a-timeline-item + v-for="item in activities" + :key="item.id" + :color="item.color" + > + <em>{{ item.time }}</em> + <em>{{ item.title }}</em> + </a-timeline-item> + </a-timeline> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import MoreIcon from './more-icon.vue'; + + defineProps<{ + title?: string; + }>(); + + const emit = defineEmits<{ + (e: 'remove'): void; + (e: 'edit'): void; + }>(); + + interface Activitie { + id: number; + title: string; + time: string; + color?: string; + } + + // 最新动态数据 + const activities = ref<Activitie[]>([]); + + /* 查询最新动态 */ + const queryActivities = () => { + activities.value = [ + { + id: 1, + title: 'SunSmile 解决了bug 登录提示操作失败', + time: '20:30', + color: 'gray' + }, + { + id: 2, + title: 'Jasmine 解决了bug 按钮颜色与设计不符', + time: '19:30', + color: 'gray' + }, + { + id: 3, + title: '项目经理 指派了任务 解决项目一的bug', + time: '18:30' + }, + { + id: 4, + title: '项目经理 指派了任务 解决项目二的bug', + time: '17:30' + }, + { + id: 5, + title: '项目经理 指派了任务 解决项目三的bug', + time: '16:30' + }, + { + id: 6, + title: '项目经理 指派了任务 解决项目四的bug', + time: '15:30', + color: 'gray' + }, + { + id: 7, + title: '项目经理 指派了任务 解决项目五的bug', + time: '14:30', + color: 'gray' + }, + { + id: 8, + title: '项目经理 指派了任务 解决项目六的bug', + time: '12:30', + color: 'gray' + }, + { + id: 9, + title: '项目经理 指派了任务 解决项目七的bug', + time: '11:30' + }, + { + id: 10, + title: '项目经理 指派了任务 解决项目八的bug', + time: '10:30', + color: 'gray' + }, + { + id: 11, + title: '项目经理 指派了任务 解决项目九的bug', + time: '09:30', + color: 'green' + }, + { + id: 12, + title: '项目经理 指派了任务 解决项目十的bug', + time: '08:30', + color: 'red' + } + ]; + }; + + const onRemove = () => { + emit('remove'); + }; + + const onEdit = () => { + emit('edit'); + }; + + queryActivities(); +</script> + +<style lang="less" scoped> + .ele-scrollbar-hover + :deep(.ant-timeline-item-last > .ant-timeline-item-content) { + min-height: auto; + } +</style> diff --git a/src/views/dashboard/workplace/components/goal-card.vue b/src/views/dashboard/workplace/components/goal-card.vue new file mode 100644 index 0000000..b5ebf76 --- /dev/null +++ b/src/views/dashboard/workplace/components/goal-card.vue @@ -0,0 +1,70 @@ +<!-- 本月目标 --> +<template> + <a-card :title="title" :bordered="false"> + <template #extra> + <more-icon @remove="onRemove" @edit="onEdit" /> + </template> + <div class="workplace-goal-group"> + <a-progress + :width="180" + :percent="80" + type="dashboard" + :stroke-width="4" + :show-info="false" + /> + <div class="workplace-goal-content"> + <ele-tag color="blue" size="large" shape="circle"> + <trophy-outlined /> + </ele-tag> + <div class="workplace-goal-num">285</div> + </div> + <div class="workplace-goal-text">恭喜, 本月目标已达标!</div> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { TrophyOutlined } from '@ant-design/icons-vue'; + import MoreIcon from './more-icon.vue'; + + defineProps<{ + title?: string; + }>(); + + const emit = defineEmits<{ + (e: 'remove'): void; + (e: 'edit'): void; + }>(); + + const onRemove = () => { + emit('remove'); + }; + + const onEdit = () => { + emit('edit'); + }; +</script> + +<style lang="less" scoped> + .workplace-goal-group { + height: 310px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + + .workplace-goal-content { + position: absolute; + top: 50%; + left: 50%; + width: 180px; + margin: -50px 0 0 -90px; + text-align: center; + } + + .workplace-goal-num { + font-size: 40px; + } + } +</style> diff --git a/src/views/dashboard/workplace/components/link-card.vue b/src/views/dashboard/workplace/components/link-card.vue new file mode 100644 index 0000000..ae1352e --- /dev/null +++ b/src/views/dashboard/workplace/components/link-card.vue @@ -0,0 +1,187 @@ +<!-- 快捷方式 --> +<template> + <a-row :gutter="16" ref="wrapRef"> + <a-col + v-for="item in data" + :key="item.url" + v-bind="styleResponsive ? { lg: 3, md: 6, sm: 12, xs: 12 } : { span: 3 }" + > + <a-card :bordered="false" hoverable :body-style="{ padding: 0 }"> + <router-link :to="item.url" class="app-link-block"> + <component + :is="item.icon" + class="app-link-icon" + :style="{ color: item.color }" + /> + <div class="app-link-title">{{ item.title }}</div> + </router-link> + </a-card> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { ref, onMounted, onBeforeUnmount } from 'vue'; + import SortableJs from 'sortablejs'; + import type { Row as ARow } from 'ant-design-vue/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + const CACHE_KEY = 'workplace-links'; + + interface LinkItem { + icon: string; + title: string; + url: string; + color?: string; + } + + // 默认顺序 + const DEFAULT: LinkItem[] = [ + { + icon: 'user-outlined', + title: '用户', + url: '/system/user' + }, + { + icon: 'shopping-cart-outlined', + title: '分析', + url: '/dashboard/analysis', + color: '#95de64' + }, + { + icon: 'fund-projection-screen-outlined', + title: '商品', + url: '/list/card/project', + color: '#ff9c6e' + }, + { + icon: 'file-search-outlined', + title: '订单', + url: '/list/basic', + color: '#b37feb' + }, + { + icon: 'credit-card-outlined', + title: '票据', + url: '/list/advanced', + color: '#ffd666' + }, + { + icon: 'mail-outlined', + title: '消息', + url: '/user/message', + color: '#5cdbd3' + }, + { + icon: 'tags-outlined', + title: '标签', + url: '/extension/tag', + color: '#ff85c0' + }, + { + icon: 'control-outlined', + title: '配置', + url: '/user/profile', + color: '#ffc069' + } + ]; + + // 获取缓存的顺序 + const cache = (() => { + const str = localStorage.getItem(CACHE_KEY); + try { + return str ? JSON.parse(str) : null; + } catch (e) { + return null; + } + })(); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const data = ref<LinkItem[]>([...(cache ?? DEFAULT)]); + + const wrapRef = ref<InstanceType<typeof ARow> | null>(null); + + let sortableIns: SortableJs | null = null; + + /* 重置布局 */ + const reset = () => { + data.value = [...DEFAULT]; + cacheData(); + }; + + /* 缓存布局 */ + const cacheData = () => { + localStorage.setItem(CACHE_KEY, JSON.stringify(data.value)); + }; + + onMounted(() => { + const isTouchDevice = 'ontouchstart' in document.documentElement; + if (isTouchDevice) { + return; + } + sortableIns = new SortableJs(wrapRef.value?.$el, { + animation: 300, + onUpdate: ({ oldIndex, newIndex }) => { + if (typeof oldIndex === 'number' && typeof newIndex === 'number') { + const temp = [...data.value]; + temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]); + data.value = temp; + cacheData(); + } + }, + setData: () => {} + }); + }); + + onBeforeUnmount(() => { + if (sortableIns) { + sortableIns.destroy(); + } + }); + + defineExpose({ reset }); +</script> + +<script lang="ts"> + import { + UserOutlined, + ShoppingCartOutlined, + FundProjectionScreenOutlined, + FileSearchOutlined, + CreditCardOutlined, + MailOutlined, + TagsOutlined, + ControlOutlined + } from '@ant-design/icons-vue'; + + export default { + components: { + UserOutlined, + ShoppingCartOutlined, + FundProjectionScreenOutlined, + FileSearchOutlined, + CreditCardOutlined, + MailOutlined, + TagsOutlined, + ControlOutlined + } + }; +</script> + +<style lang="less" scoped> + .app-link-block { + padding: 12px; + text-align: center; + display: block; + color: inherit; + + .app-link-icon { + color: #69c0ff; + font-size: 30px; + margin: 6px 0 10px 0; + } + } +</style> diff --git a/src/views/dashboard/workplace/components/more-icon.vue b/src/views/dashboard/workplace/components/more-icon.vue new file mode 100644 index 0000000..2823738 --- /dev/null +++ b/src/views/dashboard/workplace/components/more-icon.vue @@ -0,0 +1,38 @@ +<template> + <a-dropdown placement="bottomRight"> + <more-outlined class="ele-text-secondary" style="font-size: 18px" /> + <template #overlay> + <a-menu :selectable="false" @click="onClick"> + <a-menu-item key="edit"> + <div class="ele-cell"> + <edit-outlined /> + <div class="ele-cell-content">编辑</div> + </div> + </a-menu-item> + <a-menu-item key="remove"> + <div class="ele-cell ele-text-danger"> + <delete-outlined /> + <div class="ele-cell-content">删除</div> + </div> + </a-menu-item> + </a-menu> + </template> + </a-dropdown> +</template> + +<script lang="ts" setup> + import { + MoreOutlined, + EditOutlined, + DeleteOutlined + } from '@ant-design/icons-vue'; + + const emit = defineEmits<{ + (e: 'edit'): void; + (e: 'remove'): void; + }>(); + + const onClick = ({ key }) => { + emit(key); + }; +</script> diff --git a/src/views/dashboard/workplace/components/profile-card.vue b/src/views/dashboard/workplace/components/profile-card.vue new file mode 100644 index 0000000..1007e4b --- /dev/null +++ b/src/views/dashboard/workplace/components/profile-card.vue @@ -0,0 +1,119 @@ +<!-- 用户信息 --> +<template> + <a-card :bordered="false" :body-style="{ padding: '20px' }"> + <div + :class="[ + 'ele-cell', + 'workplace-user-card', + { 'workplace-user-responsive': styleResponsive } + ]" + > + <div class="ele-cell-content ele-cell"> + <a-avatar :size="68" :src="loginUser.avatar" /> + <div class="ele-cell-content"> + <h4 class="ele-elip"> + 早安, {{ loginUser.nickname }}, 开始您一天的工作吧! + </h4> + <div class="ele-elip ele-text-secondary"> + <cloud-outlined /> + <em>今日多云转阴,18℃ - 22℃,出门记得穿外套哦~</em> + </div> + </div> + </div> + <div class="workplace-count-group"> + <div class="workplace-count-item"> + <div class="workplace-count-header"> + <ele-tag color="blue" shape="circle" size="small"> + <appstore-filled /> + </ele-tag> + <span class="workplace-count-name">项目数</span> + </div> + <h2>3</h2> + </div> + <div class="workplace-count-item"> + <div class="workplace-count-header"> + <ele-tag color="orange" shape="circle" size="small"> + <check-square-outlined /> + </ele-tag> + <span class="workplace-count-name">待办项</span> + </div> + <h2>6 / 24</h2> + </div> + <div class="workplace-count-item"> + <div class="workplace-count-header"> + <ele-tag color="green" shape="circle" size="small"> + <bell-filled /> + </ele-tag> + <span class="workplace-count-name">消息</span> + </div> + <h2>1,689</h2> + </div> + </div> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { computed } from 'vue'; + import { + CloudOutlined, + AppstoreFilled, + CheckSquareOutlined, + BellFilled + } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { useUserStore } from '@/store/modules/user'; + + const userStore = useUserStore(); + + // 当前登录用户信息 + const loginUser = computed(() => userStore.info ?? {}); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); +</script> + +<style lang="less" scoped> + .workplace-user-card { + .ele-cell-content { + overflow: hidden; + } + + h4 { + margin-bottom: 6px; + } + } + + .workplace-count-group { + white-space: nowrap; + text-align: right; + flex-shrink: 0; + } + + .workplace-count-item { + display: inline-block; + margin: 0 4px 0 24px; + } + + .workplace-count-name { + margin-left: 8px; + } + + @media screen and (max-width: 992px) { + .workplace-user-responsive .workplace-count-item { + margin: 0 2px 0 12px; + } + } + + @media screen and (max-width: 768px) { + .workplace-user-responsive.workplace-user-card { + display: block; + + .workplace-count-group { + margin-top: 8px; + } + } + } +</style> diff --git a/src/views/dashboard/workplace/components/project-card.vue b/src/views/dashboard/workplace/components/project-card.vue new file mode 100644 index 0000000..14cb2a3 --- /dev/null +++ b/src/views/dashboard/workplace/components/project-card.vue @@ -0,0 +1,179 @@ +<!-- 项目进度 --> +<template> + <a-card + :title="title" + :bordered="false" + :body-style="{ padding: '14px', height: '358px' }" + > + <template #extra> + <more-icon @remove="onRemove" @edit="onEdit" /> + </template> + <a-table + row-key="id" + size="middle" + :pagination="false" + :data-source="projectList" + :columns="projectColumns" + :scroll="{ x: 600 }" + > + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'projectName'"> + <a>{{ record.projectName }}</a> + </template> + <template v-else-if="column.key === 'status'"> + <span v-if="record.status === 0" class="ele-text-success"> + 进行中 + </span> + <span v-else-if="record.status === 1" class="ele-text-danger"> + 已延期 + </span> + <span v-else-if="record.status === 2" class="ele-text-warning"> + 未开始 + </span> + <span + v-else-if="record.status === 3" + class="ele-text-info ele-text-delete" + > + 已结束 + </span> + </template> + <template v-else-if="column.key === 'progress'"> + <a-progress :percent="record.progress" size="small" /> + </template> + </template> + </a-table> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import MoreIcon from './more-icon.vue'; + import type { ColumnsType } from 'ant-design-vue/es/table'; + + defineProps<{ + title?: string; + }>(); + + const emit = defineEmits<{ + (e: 'remove'): void; + (e: 'edit'): void; + }>(); + + interface Project { + id: number; + projectName: string; + status: number; + startDate: string; + endDate: string; + progress: number; + } + + const projectColumns = ref<ColumnsType>([ + { + key: 'index', + align: 'center', + width: 38, + customRender: ({ index }) => index + 1, + fixed: 'left' + }, + { + title: '项目名称', + key: 'projectName', + ellipsis: true, + minWidth: 120 + }, + { + title: '开始时间', + dataIndex: 'startDate', + align: 'center', + minWidth: 100, + ellipsis: true + }, + { + title: '结束时间', + dataIndex: 'endDate', + align: 'center', + minWidth: 100, + ellipsis: true + }, + { + title: '状态', + key: 'status', + align: 'center', + width: 90 + }, + { + title: '进度', + key: 'progress', + align: 'center', + width: 180 + } + ]); + + // 项目进度数据 + const projectList = ref<Project[]>([]); + + /* 查询项目进度 */ + const queryProjectList = () => { + projectList.value = [ + { + id: 1, + projectName: '项目0000001', + status: 0, + startDate: '2020-03-01', + endDate: '2020-06-01', + progress: 30 + }, + { + id: 2, + projectName: '项目0000002', + status: 0, + startDate: '2020-03-01', + endDate: '2020-08-01', + progress: 10 + }, + { + id: 3, + projectName: '项目0000003', + status: 1, + startDate: '2020-01-01', + endDate: '2020-05-01', + progress: 60 + }, + { + id: 4, + projectName: '项目0000004', + status: 1, + startDate: '2020-06-01', + endDate: '2020-10-01', + progress: 0 + }, + { + id: 5, + projectName: '项目0000005', + status: 2, + startDate: '2020-01-01', + endDate: '2020-03-01', + progress: 100 + }, + { + id: 6, + projectName: '项目0000006', + status: 3, + startDate: '2020-01-01', + endDate: '2020-03-01', + progress: 100 + } + ]; + }; + + const onRemove = () => { + emit('remove'); + }; + + const onEdit = () => { + emit('edit'); + }; + + queryProjectList(); +</script> diff --git a/src/views/dashboard/workplace/components/task-card.vue b/src/views/dashboard/workplace/components/task-card.vue new file mode 100644 index 0000000..c0a60ed --- /dev/null +++ b/src/views/dashboard/workplace/components/task-card.vue @@ -0,0 +1,157 @@ +<!-- 我的任务 --> +<template> + <a-card + :title="title" + :bordered="false" + :body-style="{ padding: '10px', height: '358px' }" + class="workplace-table-card" + > + <template #extra> + <more-icon @remove="onRemove" @edit="onEdit" /> + </template> + <div style="overflow: auto; position: relative"> + <table class="ele-table" style="table-layout: fixed; min-width: 300px"> + <colgroup> + <col width="38" /> + <col width="65" /> + <col /> + <col width="70" /> + </colgroup> + <thead> + <tr> + <th style="position: sticky; left: 0"></th> + <th style="text-align: center">优先级</th> + <th>任务名称</th> + <th style="text-align: center">状态</th> + </tr> + </thead> + <vue-draggable + tag="tbody" + item-key="id" + v-model="taskList" + handle=".sort-handle" + :animation="300" + :set-data="() => void 0" + > + <template #item="{ element }"> + <tr> + <td style="text-align: center; position: sticky; left: 0"> + <menu-outlined class="sort-handle ele-text-secondary" /> + </td> + <td style="text-align: center"> + <ele-tag + :color="['red', 'orange', 'blue'][element.priority - 1]" + shape="circle" + > + {{ element.priority }} + </ele-tag> + </td> + <td class="ele-elip" :title="element.taskName"> + <a>{{ element.taskName }}</a> + </td> + <td style="text-align: center"> + <span v-if="element.status === 0" class="ele-text-warning"> + 未开始 + </span> + <span v-else-if="element.status === 1" class="ele-text-success"> + 进行中 + </span> + <span v-else-if="element.status === 2" class="ele-text-info"> + 已完成 + </span> + </td> + </tr> + </template> + </vue-draggable> + </table> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import VueDraggable from 'vuedraggable'; + import { MenuOutlined } from '@ant-design/icons-vue'; + import MoreIcon from './more-icon.vue'; + + defineProps<{ + title?: string; + }>(); + + const emit = defineEmits<{ + (e: 'remove'): void; + (e: 'edit'): void; + }>(); + + interface Task { + id: number; + priority: number; + taskName: string; + status: number; + } + + // 我的任务数据 + const taskList = ref<Task[]>([]); + + /* 查询我的任务 */ + const queryTaskList = () => { + taskList.value = [ + { + id: 1, + priority: 1, + taskName: '解决项目一的bug', + status: 0 + }, + { + id: 2, + priority: 2, + taskName: '解决项目二的bug', + status: 0 + }, + { + id: 3, + priority: 2, + taskName: '解决项目三的bug', + status: 1 + }, + { + id: 4, + priority: 3, + taskName: '解决项目四的bug', + status: 1 + }, + { + id: 5, + priority: 3, + taskName: '解决项目五的bug', + status: 2 + }, + { + id: 6, + priority: 3, + taskName: '解决项目六的bug', + status: 2 + } + ]; + }; + + const onRemove = () => { + emit('remove'); + }; + + const onEdit = () => { + emit('edit'); + }; + + queryTaskList(); +</script> + +<style lang="less" scoped> + .ele-table tr.sortable-chosen { + background: hsla(0, 0%, 60%, 0.1); + } + + .workplace-table-card .sort-handle { + cursor: move; + } +</style> diff --git a/src/views/dashboard/workplace/components/user-list.vue b/src/views/dashboard/workplace/components/user-list.vue new file mode 100644 index 0000000..7ae8bc7 --- /dev/null +++ b/src/views/dashboard/workplace/components/user-list.vue @@ -0,0 +1,123 @@ +<!-- 小组成员 --> +<template> + <a-card :title="title" :bordered="false" :body-style="{ padding: '2px 0px' }"> + <template #extra> + <more-icon @remove="onRemove" @edit="onEdit" /> + </template> + <div + v-for="(item, index) in userList" + :key="index" + class="ele-cell user-list-item" + > + <div style="flex-shrink: 0"> + <a-avatar :size="46" :src="item.avatar" /> + </div> + <div class="ele-cell-content"> + <div class="ele-cell-title ele-elip">{{ item.name }}</div> + <div class="ele-cell-desc ele-elip">{{ item.introduction }}</div> + </div> + <div style="flex-shrink: 0"> + <a-tag :color="['green', 'red'][item.status]"> + {{ ['在线', '离线'][item.status] }} + </a-tag> + </div> + </div> + </a-card> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import MoreIcon from './more-icon.vue'; + + defineProps<{ + title?: string; + }>(); + + const emit = defineEmits<{ + (e: 'remove'): void; + (e: 'edit'): void; + }>(); + + interface User { + name: string; + introduction: string; + status: number; + avatar: string; + } + + // 小组成员数据 + const userList = ref<User[]>([]); + + /* 查询小组成员 */ + const queryUserList = () => { + userList.value = [ + { + name: 'SunSmile', + introduction: 'UI设计师、交互专家', + status: 0, + avatar: + 'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg' + }, + { + name: '你的名字很好听', + introduction: '前端工程师', + status: 0, + avatar: + 'https://cdn.eleadmin.com/20200609/b6a811873e704db49db994053a5019b2.jpg' + }, + { + name: '全村人的希望', + introduction: '前端工程师', + status: 0, + avatar: + 'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg' + }, + { + name: 'Jasmine', + introduction: '产品经理、项目经理', + status: 1, + avatar: + 'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg' + }, + { + name: '酷酷的大叔', + introduction: '组长、后端工程师', + status: 1, + avatar: + 'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg' + } + ]; + }; + + const onRemove = () => { + emit('remove'); + }; + + const onEdit = () => { + emit('edit'); + }; + + queryUserList(); +</script> + +<style lang="less" scoped> + .user-list-item { + padding: 12px 18px; + + & + .user-list-item { + border-top: 1px solid hsla(0, 0%, 60%, 0.15); + } + + .ele-cell-content { + overflow: hidden; + } + + .ele-cell-desc { + margin-top: 0; + } + + .ant-tag { + margin: 0; + } + } +</style> diff --git a/src/views/dashboard/workplace/index.vue b/src/views/dashboard/workplace/index.vue new file mode 100644 index 0000000..ff55d7c --- /dev/null +++ b/src/views/dashboard/workplace/index.vue @@ -0,0 +1,294 @@ +<template> + <div class="ele-body ele-body-card"> + <profile-card /> + <link-card ref="linkCardRef" /> + <a-row :gutter="16" ref="wrapRef"> + <a-col + v-for="(item, index) in data" + :key="item.name" + v-bind=" + styleResponsive + ? { lg: item.lg, md: item.md, sm: item.sm, xs: item.xs } + : { span: item.lg } + " + > + <component + :is="item.name" + :title="item.title" + @remove="onRemove(index)" + @edit="onEdit(index)" + /> + </a-col> + </a-row> + <a-card :bordered="false" :body-style="{ padding: 0 }"> + <div class="ele-cell" style="line-height: 42px"> + <div + class="ele-cell-content ele-text-primary workplace-bottom-btn" + @click="add" + > + <plus-circle-outlined /> 添加视图 + </div> + <a-divider type="vertical" /> + <div + class="ele-cell-content ele-text-primary workplace-bottom-btn" + @click="reset" + > + <undo-outlined /> 重置布局 + </div> + </div> + </a-card> + <ele-modal + :width="680" + v-model:visible="visible" + title="未添加的视图" + :footer="null" + > + <a-row :gutter="16"> + <a-col + v-for="item in notAddedData" + :key="item.name" + v-bind="styleResponsive ? { md: 8, sm: 12, xs: 24 } : { span: 8 }" + > + <div + class="workplace-card-item ele-border-split" + @click="addView(item)" + > + <div class="workplace-card-header ele-border-split"> + {{ item.title }} + </div> + <div class="workplace-card-body ele-text-placeholder"> + <plus-circle-outlined /> + </div> + </div> + </a-col> + </a-row> + <a-empty v-if="!notAddedData.length" description="已添加所有视图" /> + </ele-modal> + </div> +</template> + +<script lang="ts" setup> + import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; + import SortableJs from 'sortablejs'; + import type { Row as ARow } from 'ant-design-vue/es'; + import { message } from 'ant-design-vue/es'; + import { PlusCircleOutlined, UndoOutlined } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import ProfileCard from './components/profile-card.vue'; + import LinkCard from './components/link-card.vue'; + const CACHE_KEY = 'workplace-layout'; + + interface ViewItem { + name: string; + title: string; + lg: number; + md: number; + sm: number; + xs: number; + } + + // 默认布局 + const DEFAULT: ViewItem[] = [ + { + name: 'activities-card', + title: '最新动态', + lg: 8, + md: 24, + sm: 24, + xs: 24 + }, + { + name: 'task-card', + title: '我的任务', + lg: 8, + md: 24, + sm: 24, + xs: 24 + }, + { + name: 'goal-card', + title: '本月目标', + lg: 8, + md: 24, + sm: 24, + xs: 24 + }, + { + name: 'project-card', + title: '项目进度', + lg: 16, + md: 24, + sm: 24, + xs: 24 + }, + { + name: 'user-list', + title: '小组成员', + lg: 8, + md: 24, + sm: 24, + xs: 24 + } + ]; + + // 获取缓存的顺序 + const cache = (() => { + const str = localStorage.getItem(CACHE_KEY); + try { + return str ? JSON.parse(str) : null; + } catch (e) { + return null; + } + })(); + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const data = ref<ViewItem[]>([...(cache ?? DEFAULT)]); + + const visible = ref(false); + + const linkCardRef = ref<InstanceType<typeof LinkCard> | null>(null); + + const wrapRef = ref<InstanceType<typeof ARow> | null>(null); + + let sortableIns: SortableJs | null = null; + + // 未添加的数据 + const notAddedData = computed(() => { + return DEFAULT.filter((d) => !data.value.some((t) => t.name === d.name)); + }); + + /* 添加 */ + const add = () => { + visible.value = true; + }; + + /* 重置布局 */ + const reset = () => { + data.value = [...DEFAULT]; + cacheData(); + linkCardRef.value?.reset(); + message.success('已重置'); + }; + + /* 缓存布局 */ + const cacheData = () => { + localStorage.setItem(CACHE_KEY, JSON.stringify(data.value)); + }; + + /* 删除视图 */ + const onRemove = (index: number) => { + data.value = data.value.filter((_d, i) => i !== index); + cacheData(); + }; + + /* 编辑视图 */ + const onEdit = (index: number) => { + console.log('index:', index); + message.info('点击了编辑'); + }; + + /* 添加视图 */ + const addView = (item) => { + data.value.push(item); + cacheData(); + message.success('已添加'); + }; + + onMounted(() => { + const isTouchDevice = 'ontouchstart' in document.documentElement; + if (isTouchDevice) { + return; + } + sortableIns = new SortableJs(wrapRef.value?.$el, { + handle: '.ant-card-head', + animation: 300, + onUpdate: ({ oldIndex, newIndex }) => { + if (typeof oldIndex === 'number' && typeof newIndex === 'number') { + const temp = [...data.value]; + temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]); + data.value = temp; + cacheData(); + } + }, + setData: () => {} + }); + }); + + onBeforeUnmount(() => { + if (sortableIns) { + sortableIns.destroy(); + } + }); +</script> + +<script lang="ts"> + import ActivitiesCard from './components/activities-card.vue'; + import TaskCard from './components/task-card.vue'; + import GoalCard from './components/goal-card.vue'; + import ProjectCard from './components/project-card.vue'; + import UserList from './components/user-list.vue'; + + export default { + name: 'DashboardWorkplace', + components: { + ActivitiesCard, + TaskCard, + GoalCard, + ProjectCard, + UserList + } + }; +</script> + +<style lang="less" scoped> + .ele-body :deep(.ant-card-head) { + cursor: move; + position: relative; + } + + .ele-body :deep(.ant-row > .ant-col.sortable-chosen > .ant-card) { + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2); + } + + .workplace-bottom-btn { + text-align: center; + cursor: pointer; + transition: background-color 0.2s; + } + + .workplace-bottom-btn:hover { + background: hsla(0, 0%, 60%, 0.05); + } + + /* 添加弹窗 */ + .workplace-card-item { + margin-bottom: 15px; + border-width: 1px; + border-style: solid; + border-radius: 4px; + position: relative; + cursor: pointer; + transition: box-shadow 0.2s, background-color 0.2s; + } + + .workplace-card-item:hover { + box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1); + background: hsla(0, 0%, 60%, 0.05); + } + + .workplace-card-item .workplace-card-header { + border-bottom-width: 1px; + border-bottom-style: solid; + padding: 8px; + } + + .workplace-card-body { + font-size: 26px; + padding: 24px 10px; + text-align: center; + } +</style> diff --git a/src/views/employ/category/components/cate-edit.vue b/src/views/employ/category/components/cate-edit.vue new file mode 100644 index 0000000..62b96d3 --- /dev/null +++ b/src/views/employ/category/components/cate-edit.vue @@ -0,0 +1,141 @@ +<!-- 编辑弹窗 --> +<template> + <ele-modal + :width="460" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改职位分类' : '添加职位分类'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }" + :wrapper-col=" + styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="分类名称" name="name"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入分类名称" + v-model:value="form.name" + /> + </a-form-item> + <a-form-item label="备注"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入备注" + v-model:value="form.comments" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { Category } from '@/api/employ/category/model'; + import { addCategory, updateCategory } from '@/api/employ/category'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Category | null; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Category>({ + categoryId: undefined, + name: '', + comments: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + name: [ + { + required: true, + message: '请输入分类名称', + type: 'string', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateCategory : addCategory; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views/employ/category/components/cate-search.vue b/src/views/employ/category/components/cate-search.vue new file mode 100644 index 0000000..5601faa --- /dev/null +++ b/src/views/employ/category/components/cate-search.vue @@ -0,0 +1,90 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="分类名称"> + <a-input + v-model:value.trim="form.name" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="备注"> + <a-input + v-model:value.trim="form.comments" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { CategoryParam } from '@/api/employ/category/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: CategoryParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<CategoryParam>({ + name: '', + comments: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views/employ/category/index.vue b/src/views/employ/category/index.vue new file mode 100644 index 0000000..09b0440 --- /dev/null +++ b/src/views/employ/category/index.vue @@ -0,0 +1,202 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <cate-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="categoryId" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 800 }" + cache-key="proEmployCategoryTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此角色吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 编辑弹窗 --> + <cate-edit v-model:visible="showEdit" :data="current" @done="reload" /> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import CateSearch from './components/cate-search.vue'; + import CateEdit from './components/cate-edit.vue'; + import type { Category, CategoryParam } from '@/api/employ/category/model'; + import { + pageCategory, + removeCategory, + removeCategoryBatch + } from '@/api/employ/category'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '分类ID', + dataIndex: 'categoryId', + sorter: true, + showSorterTooltip: false + }, + { + title: '分类名称', + dataIndex: 'name', + sorter: true, + showSorterTooltip: false + }, + { + title: '备注', + dataIndex: 'comments', + sorter: true, + showSorterTooltip: false + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '操作', + key: 'action', + width: 200, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<Category[]>([]); + + // 当前编辑数据 + const current = ref<Category | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageCategory({ ...where, ...orders, page, limit }); + }; + + /* 搜索 */ + const reload = (where?: CategoryParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: Category) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 删除单个 */ + const remove = (row: Category) => { + const hide = messageLoading('请求中..', 0); + removeCategory(row.categoryId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的角色吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeCategoryBatch(selection.value.map((d) => d.categoryId)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'EmployCate' + }; +</script> diff --git a/src/views/employ/company/components/company-edit.vue b/src/views/employ/company/components/company-edit.vue new file mode 100644 index 0000000..155ba08 --- /dev/null +++ b/src/views/employ/company/components/company-edit.vue @@ -0,0 +1,420 @@ +<!-- 用户编辑弹窗 --> +<template> + <ele-modal + :width="680" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改企业' : '新建企业'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 3, sm: 4, xs: 24 } : { flex: '80px' }" + :wrapper-col=" + styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' } + " + > + <a-row> + <a-col :span="24"> + <a-form-item label="企业名称" name="name"> + <a-input + allow-clear + placeholder="请输入企业名称" + v-model:value="form.name" + /> + </a-form-item> + </a-col> + </a-row> + + <a-row> + <a-col :span="24"> + <a-form-item label="企业地址" name="address"> + <a-input + allow-clear + placeholder="请输入企业地址" + v-model:value="form.address" + /> + </a-form-item> + </a-col> + </a-row> + + <a-row :gutttr="24"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item :label-col="{ span: 6 }" label="联系人" name="hr"> + <a-input + allow-clear + placeholder="请输入企业联系人" + v-model:value="form.hr" + /> + </a-form-item> + <a-form-item :label-col="{ span: 6 }" label="联系电话" name="phone"> + <a-input + allow-clear + placeholder="请输入联系电话" + v-model:value="form.phone" + /> + </a-form-item> + <a-form-item :label-col="{ span: 6 }" label="联系邮箱" name="email"> + <a-input + allow-clear + placeholder="请输入联系邮箱" + v-model:value="form.email" + /> + </a-form-item> + </a-col> + <a-col :span="12"> + <a-form-item :label-col="{ span: 8 }" label="企业logo" name="logo"> + <ele-image-upload + v-model:value="images" + :limit="1" + :before-upload="onBeforeUpload" + :remove-handler="removeHandler" + :item-style="{ width: '152px', height: '90px' }" + :button-style="{ width: '152px', height: '90px' }" + @upload="onUpload" + /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutttr="24"> + <a-col :span="12"> + <a-form-item + :label-col="{ span: 6 }" + label="企业位置" + name="location" + > + <a-input + allow-clear + placeholder="请输入地图" + v-model:value="result.lngAndLat" + /> + </a-form-item> + </a-col> + <a-col :span="12"> + <a-button @click="showMap">选择地址</a-button> + </a-col> + </a-row> + <a-row> + <a-col :span="24"> + <a-form-item + label="文章内容" + :label-col=" + styleResponsive ? { md: 3, sm: 4, xs: 24 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' } + " + > + <tinymce-editor v-model:value="form.comment" :init="config" /> + </a-form-item> + </a-col> + </a-row> + <ele-map-picker + :need-city="true" + :dark-mode="darkMode" + v-model:visible="mapVisible" + @done="onChoose" + /> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { emailReg, phoneReg } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import TinymceEditor from '@/components/TinymceEditor/index.vue'; + import type { CenterPoint } from 'ele-admin-pro/es/ele-map-picker/types'; + import { Company } from '@/api/employ/company/model'; + import request from '@/utils/request'; + import { + addCompany, + checkExistence, + updateCompany + } from '@/api/employ/company'; + import type { + BeforeUploadType, + ItemType + } from 'ele-admin-pro/es/ele-image-upload/types'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Company | null; + }>(); + const mapVisible = ref(false); + const { darkMode } = storeToRefs(themeStore); + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + // 选择结果 + const result = reactive({ + location: '', + address: '', + lngAndLat: '' + }); + const images = ref<any>([]); + const img_obj = reactive<any>({ + url: '' + }); + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Company>({ + companyId: undefined, + name: '', + address: '', + hr: '', + email: '', + phone: '', + location: '', + comment: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + name: [ + { + required: true, + type: 'string', + validator: (_rule: Rule, value: string) => { + return new Promise<void>((resolve, reject) => { + if (!value) { + return reject('请输入企业名称'); + } + checkExistence('name', value, props.data?.companyId) + .then(() => { + reject('该企业已经存在'); + }) + .catch(() => { + resolve(); + }); + }); + }, + trigger: 'blur' + } + ], + email: [ + { + pattern: emailReg, + message: '邮箱格式不正确', + type: 'string', + trigger: 'blur' + } + ], + phone: [ + { + pattern: phoneReg, + message: '手机号格式不正确', + type: 'string', + trigger: 'blur' + } + ] + }); + const showMap = () => { + mapVisible.value = true; + }; + const onUpload = (d: ItemType) => { + const item = images.value.find((t: any) => t.uid === d.uid) ?? d; + // item 包含的字段参考前面说明 + item.status = 'uploading'; + const formData = new FormData(); + formData.append('file', item.file); + request({ + url: '/file/upload', + method: 'post', + data: formData, + onUploadProgress: (e: any) => { + // 文件上传进度回调 + if (e.lengthComputable) { + item.progress = (e.loaded / e.total) * 100; + } + } + }) + .then((res) => { + if (res.data.code === 0) { + item.status = 'done'; + item.url = res.data.data; + form.logo = res.data.data; + } + }) + .catch((e: Error) => { + message.warning(e.message); + item.status = 'exception'; + }); + }; + const onBeforeUpload: BeforeUploadType = (file: File) => { + // file 即选择后的文件 + if (!file.type.startsWith('image')) { + message.error('只能选择图片'); + return false; + } + if (file.size / 1024 / 1024 > 2) { + message.error('大小不能超过 2MB'); + return false; + } + }; + const removeHandler = (item) => { + images.value.forEach((d: any) => { + if (d.uid === item.uid) { + images.value = []; + form.logo = ''; + d.deleted = 1; + } + }); + }; + //编辑器配置 + const config = ref({ + height: 300, + external_plugins: { + editor135: 'http://cdn.cqtlcm.com/plugin/plugin.js' + }, + menubar: false, + toolbar: + 'removeformat forecolor backcolor bold italic underline strikethrough | alignleft aligncenter alignright alignjustify outdent indent | formatselect fontselect editor135', + plugins: + 'editor135 media image link table code preview fullscreen wordcount', + automatic_uploads: true, + paste_data_images: true, // 设置为允许粘帖图片 + images_upload_handler: (blobInfo, success, error) => { + const file = blobInfo.blob(); + // 使用 axios 上传,实际开发这段建议写在 api 中再调用 api + const formData = new FormData(); + formData.append('file', file, file.name); + request({ + url: '/file/upload', + method: 'post', + data: formData + }) + .then((res) => { + if (res.data.data) { + success(res.data.data); + } else { + error(res.data.message); + } + }) + .catch((e) => { + error(e.message); + }); + }, + file_picker_callback: (callback, value, meta) => { + const input = document.createElement('input'); + input.setAttribute('type', 'file'); + // 设定文件可选类型 + if (meta.filetype === 'image') { + input.setAttribute('accept', 'image/*'); + } else if (meta.filetype === 'media') { + input.setAttribute('accept', 'video/*'); + //input.setAttribute('accept', 'audio/*'); + } + input.onchange = () => { + const file = input.files[0]; + // 使用 axios 上传,实际开发这段建议写在 api 中再调用 api + const formData = new FormData(); + formData.append('file', file, file.name); + request({ + url: '/file/upload', + method: 'post', + data: formData + }) + .then((res) => { + if (res.data.data) { + callback(res.data.data); + } else { + message.error(res.data.message); + } + }) + .catch((e) => { + message.error(e.message); + }); + }; + input.click(); + } + }); + /* 地图选择后回调 */ + const onChoose = (location: CenterPoint) => { + result.location = `${location.city?.province}/${location.city?.city}/${location.city?.district}`; + result.address = `${location.name} ${location.address}`; + result.lngAndLat = `${location.lng},${location.lat}`; + mapVisible.value = false; + }; + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateCompany : addCompany; + form.location = result.lngAndLat; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + if (props.data.location) { + result.lngAndLat = props.data.location; + } + if (props.data.logo != null && props.data.logo != '') { + img_obj.url = props.data.logo; + images.value.push(img_obj); + } + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + images.value = []; + result.lngAndLat = ''; + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views/employ/company/components/company-search.vue b/src/views/employ/company/components/company-search.vue new file mode 100644 index 0000000..a90b528 --- /dev/null +++ b/src/views/employ/company/components/company-search.vue @@ -0,0 +1,115 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="企业名称"> + <a-input + v-model:value.trim="form.name" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="企业地址"> + <a-input + v-model:value.trim="form.address" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="状态"> + <a-select + v-model:value="form.status" + placeholder="请选择" + allow-clear + > + <a-select-option value="0">正常</a-select-option> + <a-select-option value="1">禁用</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { CompanyParam } from '@/api/employ/company/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const props = defineProps<{ + // 默认搜索条件 + where?: CompanyParam; + }>(); + + const emit = defineEmits<{ + (e: 'search', where?: CompanyParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<CompanyParam>({ + name: '', + address: '', + status: undefined, + ...props.where + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views/employ/company/index.vue b/src/views/employ/company/index.vue new file mode 100644 index 0000000..5eb86b7 --- /dev/null +++ b/src/views/employ/company/index.vue @@ -0,0 +1,242 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <company-search :where="defaultWhere" @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="companyId" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 1000 }" + :where="defaultWhere" + cache-key="proEmployCompanyTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'logo'"> + <a-avatar + v-if="record.logo" + :size="60" + shape="square" + :src="record.logo" + /> + <a-avatar v-else :size="60" shape="square" :src="noImage" /> + </template> + <template v-else-if="column.key === 'status'"> + <a-switch + :checked="record.status === 0" + @change="(checked: boolean) => editStatus(checked, record)" + /> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此公司吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 编辑弹窗 --> + <company-edit v-model:visible="showEdit" :data="current" @done="reload" /> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref, reactive } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { toDateString, messageLoading } from 'ele-admin-pro/es'; + import CompanySearch from './components/company-search.vue'; + import CompanyEdit from './components/company-edit.vue'; + import type { Company, CompanyParam } from '@/api/employ/company/model'; + import { + pageCompany, + removeCompany, + removeCompanyBatch, + updateCompanyStatus + } from '@/api/employ/company'; + import noImage from '@/assets/noimage.png'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '企业名称', + dataIndex: 'name', + sorter: true, + showSorterTooltip: false + }, + { + title: '企业地址', + dataIndex: 'address', + sorter: true, + showSorterTooltip: false + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '状态', + key: 'status', + dataIndex: 'status', + sorter: true, + showSorterTooltip: false, + width: 90, + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 200, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<Company[]>([]); + + // 当前编辑数据 + const current = ref<Company | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 默认搜索条件 + const defaultWhere = reactive({ + name: '', + address: '' + }); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageCompany({ ...where, ...orders, page, limit }); + }; + + /* 搜索 */ + const reload = (where?: CompanyParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: Company) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 删除单个 */ + const remove = (row: Company) => { + const hide = messageLoading('请求中..', 0); + removeCompany(row.companyId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的企业吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeCompanyBatch(selection.value.map((d) => d.companyId)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 修改公司状态 */ + const editStatus = (checked: boolean, row: Company) => { + const status = checked ? 0 : 1; + updateCompanyStatus(row.companyId, status) + .then((msg) => { + row.status = status; + message.success(msg); + }) + .catch((e) => { + message.error(e.message); + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'EmployCompany' + }; +</script> diff --git a/src/views/employ/job/components/category-select.vue b/src/views/employ/job/components/category-select.vue new file mode 100644 index 0000000..d528ce2 --- /dev/null +++ b/src/views/employ/job/components/category-select.vue @@ -0,0 +1,66 @@ +<!-- 角色选择下拉框 --> +<template> + <a-select + allow-clear + :value="cateId" + :placeholder="placeholder" + @update:value="updateValue" + @blur="onBlur" + > + <a-select-option + v-for="item in data" + :key="item.categoryId" + :value="item.categoryId" + > + {{ item.name }} + </a-select-option> + </a-select> +</template> + +<script lang="ts" setup> + import { computed, ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { Category } from '@/api/employ/category/model'; + import { listCategory } from '@/api/employ/category'; + + const emit = defineEmits<{ + (e: 'update:value', value: Category[]): void; + (e: 'blur'): void; + }>(); + + const props = withDefaults( + defineProps<{ + // 选中的企业 + value?: Category[]; + // + placeholder?: string; + }>(), + { + placeholder: '请选择岗位类型' + } + ); + + // 数据 + const data = ref<Category[]>([]); + const cateId = computed(() => + props.value?.map((d) => d.categoryId as number) + ); + /* 更新选中数据 */ + const updateValue = (value: number) => { + emit('update:value', [{ categoryId: value }]); + }; + + /* 获取企业数据 */ + listCategory() + .then((list) => { + data.value = list; + }) + .catch((e) => { + message.error(e.message); + }); + + /* 失去焦点 */ + const onBlur = () => { + emit('blur'); + }; +</script> diff --git a/src/views/employ/job/components/company-select.vue b/src/views/employ/job/components/company-select.vue new file mode 100644 index 0000000..edc2148 --- /dev/null +++ b/src/views/employ/job/components/company-select.vue @@ -0,0 +1,67 @@ +<!-- 角色选择下拉框 --> +<template> + <a-select + allow-clear + :value="company" + :placeholder="placeholder" + @update:value="updateValue" + @blur="onBlur" + > + <a-select-option + v-for="item in data" + :key="item.companyId" + :value="item.companyId" + > + {{ item.name }} + </a-select-option> + </a-select> +</template> + +<script lang="ts" setup> + import { computed, ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { Company } from '@/api/employ/company/model'; + import { listCompany } from '@/api/employ/company'; + + const emit = defineEmits<{ + (e: 'update:value', value: Company[]): void; + (e: 'blur'): void; + }>(); + + const props = withDefaults( + defineProps<{ + // 选中的企业 + value?: Company[]; + // + placeholder?: string; + }>(), + { + placeholder: '请选择企业' + } + ); + + // 企业数据 + const data = ref<Company[]>([]); + + const company = computed(() => + props.value?.map((d) => d.companyId as number) + ); + /* 更新选中数据 */ + const updateValue = (value: number) => { + emit('update:value', [{ companyId: value }]); + }; + + /* 获取企业数据 */ + listCompany() + .then((list) => { + data.value = list; + }) + .catch((e) => { + message.error(e.message); + }); + + /* 失去焦点 */ + const onBlur = () => { + emit('blur'); + }; +</script> diff --git a/src/views/employ/job/components/job-edit.vue b/src/views/employ/job/components/job-edit.vue new file mode 100644 index 0000000..bbbc097 --- /dev/null +++ b/src/views/employ/job/components/job-edit.vue @@ -0,0 +1,305 @@ +<!-- 用户编辑弹窗 --> +<template> + <ele-modal + :width="780" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改职位' : '新建职位'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 3, sm: 4, xs: 24 } : { flex: '90px' }" + :wrapper-col=" + styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' } + " + > + <a-row> + <a-col :span="24"> + <a-form-item label="岗位名称" name="title"> + <a-input + allow-clear + placeholder="请输入岗位名称" + v-model:value="form.title" + /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :span="12"> + <a-form-item :label-col="{ span: 6 }" label="所属企业" name="company"> + <company-select v-model:value="company" /> + </a-form-item> + </a-col> + <a-col :span="12"> + <a-form-item + :label-col="{ span: 6 }" + label="所属分类" + name="category" + > + <cate-select v-model:value="category" /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :span="12"> + <a-form-item :label-col="{ span: 6 }" label="招聘人数" name="needs"> + <a-input + allow-clear + placeholder="请输入招聘人数" + v-model:value="form.needs" + /> + </a-form-item> + </a-col> + <a-col :span="12"> + <a-form-item :label-col="{ span: 6 }" label="学历要求" name="degree"> + <a-input + allow-clear + placeholder="请输入学历要求" + v-model:value="form.degree" + /> + </a-form-item> + </a-col> + </a-row> + <a-row :gutter="24"> + <a-col :span="12"> + <a-form-item + :label-col="{ span: 6 }" + label="工作经历" + name="seniority" + > + <a-input + allow-clear + placeholder="请输入工作经历" + v-model:value="form.seniority" + /> + </a-form-item> + </a-col> + <a-col :span="12"> + <a-form-item :label-col="{ span: 6 }" label="工资待遇" name="salary"> + <a-input + allow-clear + placeholder="请输入工作经历" + v-model:value="form.salary" + /> + </a-form-item> + </a-col> + </a-row> + <a-row> + <a-col :span="24"> + <a-form-item label="岗位福利" name="tags"> + <tag-select v-model:value="form.tags" /> + </a-form-item> + </a-col> + </a-row> + <a-row> + <a-col :span="24"> + <a-form-item label="岗位介绍" name="description"> + <tinymce-editor v-model:value="form.description" :init="config" /> + </a-form-item> + </a-col> + </a-row> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import CompanySelect from './company-select.vue'; + import CateSelect from './category-select.vue'; + import TagSelect from './tag-select.vue'; + import { addJob, updateJob } from '@/api/employ/jobs'; + import { Job } from '@/api/employ/jobs/model'; + import request from '@/utils/request'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Job | null; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Job>({ + jobId: undefined, + title: '', + companyId: '', + categoryId: undefined, + needs: '', + degree: '', + seniority: '', + salary: '', + tags: [], + description: '' + }); + + const category = ref(); + const company = ref(); + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + title: [ + { + required: true, + type: 'string', + message: '请输入岗位名称', + trigger: 'blur' + } + ], + description: [ + { + required: true, + message: '请输入岗位描述', + type: 'string', + trigger: 'blur' + } + ] + }); + const config = ref({ + height: 300, + external_plugins: { + editor135: 'http://cdn.cqtlcm.com/plugin/plugin.js' + }, + toolbar: + 'removeformat forecolor backcolor bold italic underline strikethrough | alignleft aligncenter alignright alignjustify outdent indent | formatselect fontselect editor135', + plugins: + 'editor135 media image link table code preview fullscreen wordcount', + automatic_uploads: true, + paste_data_images: true, // 设置为允许粘帖图片 + images_upload_handler: (blobInfo, success, error) => { + const file = blobInfo.blob(); + // 使用 axios 上传,实际开发这段建议写在 api 中再调用 api + const formData = new FormData(); + formData.append('file', file, file.name); + request({ + url: '/file/upload', + method: 'post', + data: formData + }) + .then((res) => { + if (res.data.data) { + success(res.data.data); + } else { + error(res.data.message); + } + }) + .catch((e) => { + error(e.message); + }); + }, + file_picker_callback: (callback, value, meta) => { + const input = document.createElement('input'); + input.setAttribute('type', 'file'); + // 设定文件可选类型 + if (meta.filetype === 'image') { + input.setAttribute('accept', 'image/*'); + } else if (meta.filetype === 'media') { + input.setAttribute('accept', 'video/*'); + //input.setAttribute('accept', 'audio/*'); + } + input.onchange = () => { + const file = input.files[0]; + // 使用 axios 上传,实际开发这段建议写在 api 中再调用 api + const formData = new FormData(); + formData.append('file', file, file.name); + request({ + url: '/file/upload', + method: 'post', + data: formData + }) + .then((res) => { + if (res.data.data) { + callback(res.data.data); + } else { + message.error(res.data.message); + } + }) + .catch((e) => { + message.error(e.message); + }); + }; + input.click(); + } + }); + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateJob : addJob; + assignFields({ + ...form, + companyId: company.value[0].companyId, + categoryId: category.value[0].categoryId + }); + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + category.value = []; + company.value = []; + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + company.value = props.data.company; + category.value = props.data.category; + assignFields(props.data); + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views/employ/job/components/job-search.vue b/src/views/employ/job/components/job-search.vue new file mode 100644 index 0000000..71a4577 --- /dev/null +++ b/src/views/employ/job/components/job-search.vue @@ -0,0 +1,115 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="职位名称"> + <a-input + v-model:value.trim="form.title" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="所属公司"> + <a-input + v-model:value.trim="form.company" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="状态"> + <a-select + v-model:value="form.status" + placeholder="请选择" + allow-clear + > + <a-select-option value="0">正常</a-select-option> + <a-select-option value="1">禁用</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { JobParam } from '@/api/employ/jobs/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const props = defineProps<{ + // 默认搜索条件 + where?: JobParam; + }>(); + + const emit = defineEmits<{ + (e: 'search', where?: JobParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<JobParam>({ + title: '', + company: '', + status: '', + ...props.where + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views/employ/job/components/tag-select.vue b/src/views/employ/job/components/tag-select.vue new file mode 100644 index 0000000..20b14a0 --- /dev/null +++ b/src/views/employ/job/components/tag-select.vue @@ -0,0 +1,67 @@ +<!-- 选择下拉框 --> +<template> + <a-select + allow-clear + mode="multiple" + :value="tagIds" + :placeholder="placeholder" + @update:value="updateValue" + @blur="onBlur" + > + <a-select-option v-for="item in data" :key="item.tagId" :value="item.tagId"> + {{ item.name }} + </a-select-option> + </a-select> +</template> + +<script lang="ts" setup> + import { ref, computed } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { listTags } from '@/api/employ/tags'; + import { Tag } from '@/api/employ/tags/model'; + + const emit = defineEmits<{ + (e: 'update:value', value: Tag[]): void; + (e: 'blur'): void; + }>(); + + const props = withDefaults( + defineProps<{ + // 选中的标签 + value?: Tag[]; + // + placeholder?: string; + }>(), + { + placeholder: '请选择岗位福利' + } + ); + + // 选中的id + const tagIds = computed(() => props.value?.map((d) => d.tagId as number)); + + //数据 + const data = ref<Tag[]>([]); + + /* 更新选中数据 */ + const updateValue = (value: number[]) => { + emit( + 'update:value', + value.map((v) => ({ tagId: v })) + ); + }; + + /* 获取数据 */ + listTags() + .then((list) => { + data.value = list; + }) + .catch((e) => { + message.error(e.message); + }); + + /* 失去焦点 */ + const onBlur = () => { + emit('blur'); + }; +</script> diff --git a/src/views/employ/job/index.vue b/src/views/employ/job/index.vue new file mode 100644 index 0000000..27c8ed1 --- /dev/null +++ b/src/views/employ/job/index.vue @@ -0,0 +1,256 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <job-search :where="defaultWhere" @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="jobId" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 1000 }" + :where="defaultWhere" + cache-key="proEmployJobTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'company'"> + {{ record.company[0].name }} + </template> + <template v-else-if="column.key === 'category'"> + {{ record.category[0].name }} + </template> + <template v-else-if="column.key === 'tags'"> + <a-tag v-for="item in record.tags" :key="item.tagId" color="blue"> + {{ item.name }} + </a-tag> + </template> + <template v-else-if="column.key === 'status'"> + <a-switch + :checked="record.status === 0" + @change="(checked: boolean) => editStatus(checked, record)" + /> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此岗位吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 编辑弹窗 --> + <job-edit v-model:visible="showEdit" :data="current" @done="reload" /> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref, reactive } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { + pageJobs, + removeJob, + removeJobBatch, + updateJobStatus + } from '@/api/employ/jobs'; + import { toDateString, messageLoading } from 'ele-admin-pro/es'; + import JobSearch from './components/job-search.vue'; + import JobEdit from './components/job-edit.vue'; + import { Job, JobParam } from '@/api/employ/jobs/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '职位名称', + dataIndex: 'title', + sorter: true, + showSorterTooltip: false + }, + { + title: '职位分类', + key: 'category', + dataIndex: 'category', + showSorterTooltip: false + }, + { + title: '所属企业', + key: 'company', + dataIndex: 'company', + showSorterTooltip: false + }, + { + title: '福利待遇', + key: 'tags', + dataIndex: 'tags', + showSorterTooltip: false + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '状态', + key: 'status', + dataIndex: 'status', + sorter: true, + showSorterTooltip: false, + width: 90, + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 200, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<Job[]>([]); + + // 当前编辑数据 + const current = ref<Job | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 默认搜索条件 + const defaultWhere = reactive({ + title: '', + company: '', + status: '' + }); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageJobs({ ...where, ...orders, page, limit }); + }; + + /* 搜索 */ + const reload = (where?: JobParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: Job) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 删除单个 */ + const remove = (row: Job) => { + const hide = messageLoading('请求中..', 0); + removeJob(row.jobId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的岗位吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeJobBatch(selection.value.map((d) => d.jobId)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 修改状态 */ + const editStatus = (checked: boolean, row: Job) => { + const status = checked ? 0 : 1; + updateJobStatus(row.jobId, status) + .then((msg) => { + row.status = status; + message.success(msg); + }) + .catch((e) => { + message.error(e.message); + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'EmployJob' + }; +</script> diff --git a/src/views/employ/tag/components/tag-edit.vue b/src/views/employ/tag/components/tag-edit.vue new file mode 100644 index 0000000..511a33b --- /dev/null +++ b/src/views/employ/tag/components/tag-edit.vue @@ -0,0 +1,141 @@ +<!-- 编辑弹窗 --> +<template> + <ele-modal + :width="460" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改福利标签' : '添加福利标签'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }" + :wrapper-col=" + styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="标签名称" name="name"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入标签名称" + v-model:value="form.name" + /> + </a-form-item> + <a-form-item label="备注"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入备注" + v-model:value="form.comments" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { addTag, updateTag } from '@/api/employ/tags'; + import { Tag } from '@/api/employ/tags/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Tag | null; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Tag>({ + tagId: undefined, + name: '', + comments: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + name: [ + { + required: true, + message: '请输入分类名称', + type: 'string', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateTag : addTag; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views/employ/tag/components/tag-search.vue b/src/views/employ/tag/components/tag-search.vue new file mode 100644 index 0000000..d30f8ec --- /dev/null +++ b/src/views/employ/tag/components/tag-search.vue @@ -0,0 +1,89 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="标签名称"> + <a-input + v-model:value.trim="form.name" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item label="备注"> + <a-input + v-model:value.trim="form.comments" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 8, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 8 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { TagParam } from '@/api/employ/tags/model'; + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: TagParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<TagParam>({ + name: '', + comments: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views/employ/tag/index.vue b/src/views/employ/tag/index.vue new file mode 100644 index 0000000..67df81c --- /dev/null +++ b/src/views/employ/tag/index.vue @@ -0,0 +1,198 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <tag-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="tagId" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 800 }" + cache-key="proEmployTagTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此角色吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 编辑弹窗 --> + <tag-edit v-model:visible="showEdit" :data="current" @done="reload" /> + </div> +</template> + +<script lang="ts" setup> +import { createVNode, ref } from 'vue'; +import { message, Modal } from 'ant-design-vue/es'; +import { + PlusOutlined, + DeleteOutlined, + ExclamationCircleOutlined +} from '@ant-design/icons-vue'; +import type { EleProTable } from 'ele-admin-pro/es'; +import type { + DatasourceFunction, + ColumnItem +} from 'ele-admin-pro/es/ele-pro-table/types'; +import { messageLoading, toDateString } from 'ele-admin-pro/es'; +import TagSearch from './components/tag-search.vue'; +import TagEdit from './components/tag-edit.vue'; +import { pageTags, removeTag, removeTagBatch } from '@/api/employ/tags'; +import { Tag, TagParam } from '@/api/employ/tags/model'; + +// 表格实例 +const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + +// 表格列配置 +const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '标签ID', + dataIndex: 'tagId', + sorter: true, + showSorterTooltip: false + }, + { + title: '标签名称', + dataIndex: 'name', + sorter: true, + showSorterTooltip: false + }, + { + title: '备注', + dataIndex: 'comments', + sorter: true, + showSorterTooltip: false + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '操作', + key: 'action', + width: 200, + align: 'center' + } +]); + +// 表格选中数据 +const selection = ref<Tag[]>([]); + +// 当前编辑数据 +const current = ref<Tag | null>(null); + +// 是否显示编辑弹窗 +const showEdit = ref(false); + +// 表格数据源 +const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageTags({ ...where, ...orders, page, limit }); +}; + +/* 搜索 */ +const reload = (where?: TagParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); +}; + +/* 打开编辑弹窗 */ +const openEdit = (row?: Tag) => { + current.value = row ?? null; + showEdit.value = true; +}; + +/* 删除单个 */ +const remove = (row: Tag) => { + const hide = messageLoading('请求中..', 0); + removeTag(row.tagId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); +}; + +/* 批量删除 */ +const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的标签吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeTagBatch(selection.value.map((d) => d.tagId)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); +}; +</script> + +<script lang="ts"> +export default { + name: 'EmployTags' +}; +</script> diff --git a/src/views/exception/403/index.vue b/src/views/exception/403/index.vue new file mode 100644 index 0000000..6b2bc27 --- /dev/null +++ b/src/views/exception/403/index.vue @@ -0,0 +1,17 @@ +<template> + <div style="padding-top: 80px"> + <a-result status="403" title="403" sub-title="抱歉, 你无权访问该页面."> + <template #extra> + <router-link to="/"> + <a-button type="primary">返回首页</a-button> + </router-link> + </template> + </a-result> + </div> +</template> + +<script lang="ts"> + export default { + name: 'Exception403' + }; +</script> diff --git a/src/views/exception/404/index.vue b/src/views/exception/404/index.vue new file mode 100644 index 0000000..1c2b453 --- /dev/null +++ b/src/views/exception/404/index.vue @@ -0,0 +1,17 @@ +<template> + <div style="padding-top: 80px"> + <a-result status="404" title="404" sub-title="抱歉, 你访问的页面不存在."> + <template #extra> + <router-link to="/"> + <a-button type="primary">返回首页</a-button> + </router-link> + </template> + </a-result> + </div> +</template> + +<script lang="ts"> + export default { + name: 'Exception404' + }; +</script> diff --git a/src/views/exception/500/index.vue b/src/views/exception/500/index.vue new file mode 100644 index 0000000..ff7c853 --- /dev/null +++ b/src/views/exception/500/index.vue @@ -0,0 +1,17 @@ +<template> + <div style="padding-top: 80px"> + <a-result status="500" title="500" sub-title="抱歉, 服务器出错了."> + <template #extra> + <router-link to="/"> + <a-button type="primary">返回首页</a-button> + </router-link> + </template> + </a-result> + </div> +</template> + +<script lang="ts"> + export default { + name: 'Exception500' + }; +</script> diff --git a/src/views/forget/index.vue b/src/views/forget/index.vue new file mode 100644 index 0000000..13d655b --- /dev/null +++ b/src/views/forget/index.vue @@ -0,0 +1,407 @@ +<template> + <div class="login-wrapper"> + <a-form + ref="formRef" + :model="form" + :rules="rules" + class="login-form ele-bg-white" + > + <h4>忘记密码</h4> + <a-form-item name="phone"> + <a-input + placeholder="请输入绑定手机号" + v-model:value="form.phone" + allow-clear + size="large" + > + <template #prefix> + <mobile-outlined /> + </template> + </a-input> + </a-form-item> + <a-form-item name="password"> + <a-input-password + placeholder="请输入新的登录密码" + v-model:value="form.password" + size="large" + > + <template #prefix> + <lock-outlined /> + </template> + </a-input-password> + </a-form-item> + <a-form-item name="password2"> + <a-input-password + placeholder="请再次输入登录密码" + v-model:value="form.password2" + size="large" + > + <template #prefix> + <key-outlined /> + </template> + </a-input-password> + </a-form-item> + <a-form-item name="code"> + <div class="login-input-group"> + <a-input + placeholder="请输入验证码" + v-model:value="form.code" + allow-clear + size="large" + > + <template #prefix> + <safety-certificate-outlined /> + </template> + </a-input> + <a-button + class="login-captcha" + :disabled="!!countdownTime" + @click="openImgCodeModal" + > + <span v-if="!countdownTime">发送验证码</span> + <span v-else>已发送 {{ countdownTime }} s</span> + </a-button> + </div> + </a-form-item> + <a-form-item> + <router-link + to="/login" + class="ele-pull-right" + style="line-height: 22px" + > + 返回登录 + </router-link> + </a-form-item> + <a-form-item> + <a-button + block + size="large" + type="primary" + :loading="loading" + @click="submit" + > + 修改密码 + </a-button> + </a-form-item> + </a-form> + <div class="login-copyright"> + copyright © 2022 eleadmin.com all rights reserved. + </div> + </div> + <!-- 编辑弹窗 --> + <a-modal + :width="340" + :footer="null" + title="发送验证码" + v-model:visible="visible" + > + <div class="login-input-group" style="margin-bottom: 16px"> + <a-input + v-model:value="imgCode" + placeholder="请输入图形验证码" + allow-clear + size="large" + /> + <a-button class="login-captcha"> + <img alt="" :src="captcha" @click="changeImgCode" /> + </a-button> + </div> + <a-button + block + size="large" + type="primary" + :loading="codeLoading" + @click="sendCode" + > + 立即发送 + </a-button> + </a-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, onBeforeUnmount } from 'vue'; + import { useRouter } from 'vue-router'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { + MobileOutlined, + LockOutlined, + KeyOutlined, + SafetyCertificateOutlined + } from '@ant-design/icons-vue'; + + const { push } = useRouter(); + + // + const formRef = ref<FormInstance | null>(null); + + // 加载状态 + const loading = ref(false); + + // 表单数据 + const form = reactive({ + phone: '1234567890', + password: '', + password2: '', + code: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + phone: [ + { + required: true, + message: '请输入绑定手机号', + type: 'string', + trigger: 'blur' + } + ], + password: [ + { + required: true, + message: '请输入新的登录密码', + type: 'string', + trigger: 'blur' + } + ], + password2: [ + { + required: true, + type: 'string', + validator: async (_rule: Rule, value: string) => { + if (!value) { + return Promise.reject('请再次输入新密码'); + } + if (value !== form.password) { + return Promise.reject('两次输入密码不一致'); + } + return Promise.resolve(); + }, + trigger: 'blur' + } + ], + code: [ + { + required: true, + message: '请输入验证码', + type: 'string', + trigger: 'blur' + } + ] + }); + + // 是否显示图形验证码弹窗 + const visible = ref(false); + + // 图形验证码 + const imgCode = ref(''); + + // 发送验证码按钮loading + const codeLoading = ref(false); + + // 验证码倒计时时间 + const countdownTime = ref(0); + + // 图形验证码地址 + const captcha = ref('https://eleadmin.com/assets/captcha?v='); + + // 验证码倒计时定时器 + let countdownTimer: number | null = null; + + /* 提交 */ + const submit = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + setTimeout(() => { + message.success('密码修改成功'); + push('/login'); + }, 1000); + }) + .catch(() => {}); + }; + + /* 更换图形验证码 */ + const changeImgCode = () => { + // 这里演示的验证码是后端地址直接是图片的形式, 如果后端返回base64格式请参考登录页面 + captcha.value = captcha.value.replace(/v=.*/, 'v=' + new Date().getTime()); + }; + + /* 显示发送短信验证码弹窗 */ + const openImgCodeModal = () => { + if (!form.phone) { + message.error('请输入手机号码'); + return; + } + imgCode.value = ''; + changeImgCode(); + visible.value = true; + }; + + /* 发送短信验证码 */ + const sendCode = () => { + if (!imgCode.value) { + message.error('请输入图形验证码'); + return; + } + codeLoading.value = true; + setTimeout(() => { + message.success('短信验证码发送成功, 请注意查收!'); + visible.value = false; + codeLoading.value = false; + countdownTime.value = 30; + // 开始对按钮进行倒计时 + countdownTimer = window.setInterval(() => { + if (countdownTime.value <= 1) { + countdownTimer && clearInterval(countdownTimer); + countdownTimer = null; + } + countdownTime.value--; + }, 1000); + }, 1000); + }; + + onBeforeUnmount(() => { + countdownTimer && clearInterval(countdownTimer); + }); +</script> + +<style lang="less" scoped> + /* 背景 */ + .login-wrapper { + padding: 48px 16px 0 16px; + position: relative; + box-sizing: border-box; + background-image: url('@/assets/bg-login.jpg'); + background-repeat: no-repeat; + background-size: cover; + min-height: 100vh; + + &:before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.2); + } + } + + /* 卡片 */ + .login-form { + width: 360px; + margin: 0 auto; + max-width: 100%; + padding: 0 28px 16px 28px; + box-sizing: border-box; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15); + border-radius: 2px; + position: relative; + z-index: 2; + + h4 { + padding: 22px 0; + text-align: center; + } + } + + .login-form-right .login-form { + margin: 0 15% 0 auto; + } + + .login-form-left .login-form { + margin: 0 auto 0 15%; + } + + /* 验证码 */ + .login-input-group { + display: flex; + align-items: center; + + :deep(.ant-input-affix-wrapper) { + flex: 1; + } + + .login-captcha { + width: 102px; + height: 40px; + margin-left: 10px; + padding: 0; + + & > img { + width: 100%; + height: 100%; + } + } + } + + /* 第三方登录图标 */ + .login-oauth-icon { + color: #fff; + padding: 5px; + margin: 0 12px; + font-size: 18px; + border-radius: 50%; + cursor: pointer; + } + + /* 底部版权 */ + .login-copyright { + color: #eee; + text-align: center; + padding: 48px 0 22px 0; + position: relative; + z-index: 1; + } + + /* 响应式 */ + @media screen and (min-height: 640px) { + .login-wrapper { + padding-top: 0; + } + + .login-form { + position: absolute; + top: 50%; + left: 50%; + transform: translateX(-50%); + margin-top: -230px; + } + + .login-form-right .login-form, + .login-form-left .login-form { + left: auto; + right: 15%; + transform: translateX(0); + margin: -230px auto auto auto; + } + + .login-form-left .login-form { + right: auto; + left: 15%; + } + + .login-copyright { + position: absolute; + left: 0; + right: 0; + bottom: 0; + } + } + + @media screen and (max-width: 768px) { + .login-form-right .login-form, + .login-form-left .login-form { + left: 50%; + right: auto; + margin-left: 0; + margin-right: auto; + transform: translateX(-50%); + } + } +</style> diff --git a/src/views/login/index.vue b/src/views/login/index.vue new file mode 100644 index 0000000..d73f40d --- /dev/null +++ b/src/views/login/index.vue @@ -0,0 +1,356 @@ +<template> + <div + :class="[ + 'login-wrapper', + ['', 'login-form-right', 'login-form-left'][direction] + ]" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + class="login-form ele-bg-white" + > + <h4>{{ t('login.title') }}</h4> + <a-form-item name="username"> + <a-input + allow-clear + size="large" + v-model:value="form.username" + :placeholder="t('login.username')" + > + <template #prefix> + <user-outlined /> + </template> + </a-input> + </a-form-item> + <a-form-item name="password"> + <a-input-password + size="large" + v-model:value="form.password" + :placeholder="t('login.password')" + > + <template #prefix> + <lock-outlined /> + </template> + </a-input-password> + </a-form-item> + <a-form-item name="code"> + <div class="login-input-group"> + <a-input + allow-clear + size="large" + v-model:value="form.code" + :placeholder="t('login.code')" + > + <template #prefix> + <safety-certificate-outlined /> + </template> + </a-input> + <a-button class="login-captcha" @click="changeCaptcha"> + <img v-if="captcha" :src="captcha" alt="" /> + </a-button> + </div> + </a-form-item> + <a-form-item> + <a-checkbox v-model:checked="form.remember"> + {{ t('login.remember') }} + </a-checkbox> + <router-link + to="/forget" + class="ele-pull-right" + style="line-height: 22px" + > + {{ t('login.forget') }} + </router-link> + </a-form-item> + <a-form-item> + <a-button + block + size="large" + type="primary" + :loading="loading" + @click="submit" + > + {{ loading ? t('login.loading') : t('login.login') }} + </a-button> + </a-form-item> + <div class="ele-text-center" style="padding-bottom: 32px"> + <wechat-outlined + class="login-oauth-icon" + style="background: #4daf29" + @click="wechatLogin" + /> + </div> + </a-form> + <div class="login-copyright"> + copyright © 2022 ERR5.com all rights reserved. + </div> + <!-- 多语言切换 --> + <div style="position: absolute; right: 30px; top: 20px; z-index: 999"> + <i18n-icon + placement="bottomLeft" + :style="{ fontSize: '18px', color: '#fff' }" + /> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive, computed, unref } from 'vue'; + import { useI18n } from 'vue-i18n'; + import { useRouter } from 'vue-router'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { + UserOutlined, + LockOutlined, + SafetyCertificateOutlined, + WechatOutlined + } from '@ant-design/icons-vue'; + import I18nIcon from '@/layout/components/i18n-icon.vue'; + import { getToken } from '@/utils/token-util'; + import { goHomeRoute, cleanPageTabs } from '@/utils/page-tab-util'; + import { login, getCaptcha } from '@/api/login'; + + const { currentRoute } = useRouter(); + const { t } = useI18n(); + + // 登录框方向, 0 居中, 1 居右, 2 居左 + const direction = ref(0); + + // + const formRef = ref<FormInstance | null>(null); + + // 加载状态 + const loading = ref(false); + + // 表单数据 + const form = reactive({ + username: '', + password: '', + code: '', + key: '', + remember: true + }); + + // 验证码 数据 + const captcha = ref(''); + + // 表单验证规则 + const rules = computed<Record<string, Rule[]>>(() => { + return { + username: [ + { + required: true, + message: t('login.username'), + type: 'string', + trigger: 'blur' + } + ], + password: [ + { + required: true, + message: t('login.password'), + type: 'string', + trigger: 'blur' + } + ], + code: [ + { + required: true, + message: t('login.code'), + type: 'string', + trigger: 'blur' + } + ] + }; + }); + + /* 跳转到首页 */ + const goHome = () => { + const { query } = unref(currentRoute); + goHomeRoute(query.from as string); + }; + + /* 提交 */ + const submit = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + login(form) + .then((msg) => { + message.success(msg); + cleanPageTabs(); + goHome(); + }) + .catch((e: Error) => { + message.error(e.message); + loading.value = false; + }); + }) + .catch(() => {}); + }; + const wechatLogin = () => { + //微信登录 + }; + + /* 获取图形验证码 */ + const changeCaptcha = () => { + getCaptcha() + .then((data) => { + captcha.value = data.img; + form.key = data.key; + formRef.value?.clearValidate(); + }) + .catch((e) => { + message.error(e.message); + }); + }; + + if (getToken()) { + goHome(); + } else { + changeCaptcha(); + } +</script> + +<style lang="less" scoped> + /* 背景 */ + .login-wrapper { + padding: 48px 16px 0 16px; + position: relative; + box-sizing: border-box; + background-image: url('@/assets/bg-login.jpg'); + background-repeat: no-repeat; + background-size: cover; + min-height: 100vh; + + &:before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.2); + } + } + + /* 卡片 */ + .login-form { + width: 360px; + margin: 0 auto; + max-width: 100%; + padding: 0 28px; + box-sizing: border-box; + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15); + border-radius: 2px; + position: relative; + z-index: 2; + + h4 { + padding: 22px 0; + text-align: center; + } + } + + .login-form-right .login-form { + margin: 0 15% 0 auto; + } + + .login-form-left .login-form { + margin: 0 auto 0 15%; + } + + /* 验证码 */ + .login-input-group { + display: flex; + align-items: center; + + :deep(.ant-input-affix-wrapper) { + flex: 1; + } + + .login-captcha { + width: 102px; + height: 40px; + margin-left: 10px; + padding: 0; + + & > img { + width: 100%; + height: 100%; + } + } + } + + /* 第三方登录图标 */ + .login-oauth-icon { + color: #fff; + padding: 5px; + margin: 0 12px; + font-size: 18px; + border-radius: 50%; + cursor: pointer; + } + + /* 底部版权 */ + .login-copyright { + color: #eee; + text-align: center; + padding: 48px 0 22px 0; + position: relative; + z-index: 1; + } + + /* 响应式 */ + @media screen and (min-height: 640px) { + .login-wrapper { + padding-top: 0; + } + + .login-form { + position: absolute; + top: 50%; + left: 50%; + transform: translateX(-50%); + margin-top: -230px; + } + + .login-form-right .login-form, + .login-form-left .login-form { + left: auto; + right: 15%; + transform: translateX(0); + margin: -230px auto auto auto; + } + + .login-form-left .login-form { + right: auto; + left: 15%; + } + + .login-copyright { + position: absolute; + left: 0; + right: 0; + bottom: 0; + } + } + + @media screen and (max-width: 768px) { + .login-form-right .login-form, + .login-form-left .login-form { + left: 50%; + right: auto; + margin-left: 0; + margin-right: auto; + transform: translateX(-50%); + } + } +</style> diff --git a/src/views/meeting/components/meeting-edit.vue b/src/views/meeting/components/meeting-edit.vue new file mode 100644 index 0000000..6e76bd6 --- /dev/null +++ b/src/views/meeting/components/meeting-edit.vue @@ -0,0 +1,348 @@ +<!-- 编辑弹窗 --> +<template> + <ele-modal + :width="800" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改会议' : '添加会议'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="{ flex: '90px' }" + :wrapper-col="{ flex: '1' }" + > + <a-form-item label="会议名称" name="title"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入会议名称" + v-model:value="form.title" + /> + </a-form-item> + <a-row> + <a-col :span="12"> + <a-form-item label="会议地址" name="room"> + <a-input + allow-clear + placeholder="请输入会议地址" + v-model:value="form.room" + /> + </a-form-item> + </a-col> + <a-col :span="12"> + <a-form-item label="坐标地址" name="location"> + <a-input + allow-clear + placeholder="请选择地址" + v-model:value="form.location" + /> + </a-form-item> + </a-col> + </a-row> + + <a-row> + <a-col :span="12"> + <a-form-item label="会议时间" name="meeting_time"> + <a-date-picker + :show-time="true" + value-format="YYYY-MM-DD HH:mm:ss" + placeholder="请选择会议时间" + v-model:value="form.meeting_time" + /> + <!-- <a-range-picker + class="ele-fluid" + :show-time="true" + value-format="YYYY-MM-DD HH:mm:ss" + v-model:value="form.meeting_time" + /> --> + </a-form-item> + </a-col> + <a-col :span="12"> + <a-form-item label="签到模式" name="mode"> + <a-radio-group v-model:value="form.mode"> + <a-radio :value="0">报名模式</a-radio> + <a-radio :value="1">不报名模式</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + </a-row> + + <a-row> + <a-col :span="12"> + <a-form-item label="报名日期" name="entry_time"> + <a-range-picker + class="ele-fluid" + :show-time="true" + :disabled="form.mode === 1" + value-format="YYYY-MM-DD HH:mm:ss" + v-model:value="form.entry_time" + /> + </a-form-item> + </a-col> + <a-col :span="12"> + <a-form-item label="签到日期" name="sign_time"> + <a-range-picker + class="ele-fluid" + :show-time="true" + value-format="YYYY-MM-DD HH:mm:ss" + v-model:value="form.sign_time" + /> + </a-form-item> + </a-col> + </a-row> + + <a-form-item label="会议会标" name="image"> + <ele-image-upload + v-model:value="images" + :limit="1" + :before-upload="onBeforeUpload" + :remove-handler="removeHandler" + :item-style="{ width: '250px', height: '135px' }" + :button-style="{ width: '250px', height: '135px' }" + @upload="onUpload" + /> + </a-form-item> + <a-form-item label="会议介绍"> + <tinymce-editor + ref="editorRef" + :init="config" + v-model:value="form.content" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import useFormData from '@/utils/use-form-data'; + import TinymceEditor from '@/components/TinymceEditor/index.vue'; + import { Meeting } from '@/api/meeting/model'; + import { addMeeting, updateMeeting } from '@/api/meeting'; + import { + BeforeUploadType, + ItemType + } from 'ele-admin-pro/es/ele-image-upload/types'; + import request from '@/utils/request'; + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Meeting | null; + }>(); + + const editorRef = ref<InstanceType<typeof TinymceEditor> | null>(null); + // + const formRef = ref<FormInstance | null>(null); + const images = ref<any>([]); + const img_obj = reactive<any>({ + url: '' + }); + // 是否是修改 + const isUpdate = ref(false); + // 提交状态 + const loading = ref(false); + + const config = ref({ + height: 360, + // 自定义文件上传(这里使用把选择的文件转成 blob 演示) + file_picker_callback: (callback: any, _value: any, meta: any) => { + const input = document.createElement('input'); + input.setAttribute('type', 'file'); + // 设定文件可选类型 + if (meta.filetype === 'image') { + input.setAttribute('accept', 'image/*'); + } else if (meta.filetype === 'media') { + input.setAttribute('accept', 'video/*'); + } + input.onchange = () => { + const file = input.files?.[0]; + if (!file) { + return; + } + if (meta.filetype === 'media') { + if (!file.type.startsWith('video/')) { + editorRef.value?.alert({ content: '只能选择视频文件' }); + return; + } + } + if (file.size / 1024 / 1024 > 20) { + editorRef.value?.alert({ content: '大小不能超过 20MB' }); + return; + } + const reader = new FileReader(); + reader.onload = (e) => { + if (e.target?.result != null) { + const blob = new Blob([e.target.result], { type: file.type }); + callback(URL.createObjectURL(blob)); + } + }; + reader.readAsArrayBuffer(file); + }; + input.click(); + } + }); + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Meeting>({ + id: undefined, + title: '', + room: '', + mode: 0, + image: '', + meeting_time: '', + entry_time: ['', ''], + sign_time: ['', ''], + location: '', + content: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + title: [ + { + required: true, + message: '请输入会议名称', + type: 'string', + trigger: 'blur' + } + ], + room: [ + { + required: true, + message: '请填写会议地址', + type: 'string', + trigger: 'blur' + } + ], + meeting_time: [ + { + required: true, + message: '请选择会议时间', + type: 'string', + trigger: 'blur' + } + ], + sign_time: [ + { + required: true, + message: '请选择签到时间', + type: 'array', + trigger: 'blur' + } + ] + }); + + const onBeforeUpload: BeforeUploadType = (file: File) => { + // file 即选择后的文件 + if (!file.type.startsWith('image')) { + message.error('只能选择图片'); + return false; + } + if (file.size / 1024 / 1024 > 2) { + message.error('大小不能超过 2MB'); + return false; + } + }; + const removeHandler = (item) => { + images.value.forEach((d: any) => { + if (d.uid === item.uid) { + images.value = []; + form.image = ''; + d.deleted = 1; + } + }); + }; + const onUpload = (d: ItemType) => { + const item = images.value.find((t: any) => t.uid === d.uid) ?? d; + // item 包含的字段参考前面说明 + item.status = 'uploading'; + const formData = new FormData(); + formData.append('file', item.file); + request({ + url: '/file/upload', + method: 'post', + data: formData, + onUploadProgress: (e: any) => { + // 文件上传进度回调 + if (e.lengthComputable) { + item.progress = (e.loaded / e.total) * 100; + } + } + }) + .then((res) => { + if (res.data.code === 0) { + item.status = 'done'; + item.url = res.data.data; + form.image = res.data.data; + } + }) + .catch((e: Error) => { + message.warning(e.message); + item.status = 'exception'; + }); + }; + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateMeeting : addMeeting; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + images.value = []; + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + if (props.data.image != null && props.data.image != '') { + img_obj.url = props.data.image; + images.value.push(img_obj); + } + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views/meeting/components/meeting-search.vue b/src/views/meeting/components/meeting-search.vue new file mode 100644 index 0000000..7a405b5 --- /dev/null +++ b/src/views/meeting/components/meeting-search.vue @@ -0,0 +1,106 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="会议名称"> + <a-input + v-model:value.trim="form.title" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="会议地址"> + <a-input + v-model:value.trim="form.room" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="会议内容"> + <a-input + v-model:value.trim="form.content" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { MeetingParam } from '@/api/meeting/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: MeetingParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<MeetingParam>({ + title: '', + room: '', + content: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views/meeting/components/sign-user.vue b/src/views/meeting/components/sign-user.vue new file mode 100644 index 0000000..b6d34c2 --- /dev/null +++ b/src/views/meeting/components/sign-user.vue @@ -0,0 +1,263 @@ +<template> + <div class="ele-body"> + <ele-modal + :width="1200" + :visible="visible" + :confirm-loading="loading" + title="报名管理" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + > + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 1000 }" + :where="defaultWhere" + :needPage="false" + cache-key="proSignUserTable" + > + <template #toolbar> + <a-space> + <user-search :where="defaultWhere" @search="reload" /> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'isSign'"> + <a-tag v-if="record.isSign === 0" color="cyan">未签到</a-tag> + <a-tag v-else color="red">已签到</a-tag> + </template> + <template v-else-if="column.key === 'row'"> + <a-input-number + v-model:value="record.row" + placeholder="行" + @blur="enterSeat(record)" + /> + </template> + <template v-else-if="column.key === 'column'"> + <a-input-number + v-model:value="record.column" + placeholder="列" + @blur="enterSeat(record)" + /> + </template> + </template> + </ele-pro-table> + <template #footer> + <a-button type="primary" danger @click="dataExport">导出</a-button> + <a-button type="primary" @click="close">关闭</a-button> + </template> + </ele-modal> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { toDateString } from 'ele-admin-pro/es'; + import UserSearch from './user-search.vue'; + import { listUsers, updateSignUser } from '@/api/meeting'; + import type { User, UserParam } from '@/api/meeting/model'; + import { message } from 'ant-design-vue'; + import { utils, writeFile } from 'xlsx'; + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: number | string; + }>(); + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 提交状态 + const loading = ref(false); + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '姓名', + dataIndex: 'name', + sorter: true, + showSorterTooltip: false + }, + { + title: '企业', + dataIndex: 'company', + sorter: true, + showSorterTooltip: false + }, + { + title: '职位', + dataIndex: 'position', + width: 80, + align: 'center', + sorter: true, + showSorterTooltip: false + }, + { + title: '手机号', + dataIndex: 'phone', + sorter: true, + showSorterTooltip: false + }, + { + title: '座位排', + key: 'row', + dataIndex: 'row', + sorter: true, + showSorterTooltip: false + }, + { + title: '座位列', + key: 'column', + dataIndex: 'column', + sorter: true, + showSorterTooltip: false + }, + { + title: '报名时间', + dataIndex: 'entry_time', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text, 'MM-dd HH:mm') + }, + { + title: '签到时间', + dataIndex: 'sign_time', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text, 'MM-dd HH:mm') + }, + { + title: '是否签到', + key: 'isSign', + dataIndex: 'isSign', + sorter: true, + showSorterTooltip: false, + width: 90, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<User[]>([]); + + // 默认搜索条件 + const defaultWhere = reactive<{ + meetingId?: number | string; + name?: string; + company?: string; + }>({ + meetingId: '', + name: '', + company: '' + }); + + // 表格数据源 + + const datasource: DatasourceFunction = ({ where, orders }) => { + return listUsers({ ...where, ...orders }); + }; + + /* 搜索 */ + const reload = (where?: UserParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 导出excel */ + const dataExport = () => { + listUsers({ meetingId: defaultWhere.meetingId }).then((res) => { + const array: (string | number | undefined)[][] = [ + [ + '序号', + '会议id', + '姓名', + '单位', + '职位', + '电话', + '是否签到(1:签到)', + '座位排', + '座位列', + '报名时间', + '签到时间' + ] + ]; + res.forEach((d, i) => { + array.push([ + i + 1, + d.meetingId, + d.name, + d.company, + d.position, + d.phone, + d.isSign, + d.row, + d.column, + d.entry_time, + d.sign_time + ]); + }); + const sheetName = 'Sheet1'; + const workbook = { + SheetNames: [sheetName], + Sheets: {} + }; + const sheet = utils.aoa_to_sheet(array); + workbook.Sheets[sheetName] = sheet; + writeFile(workbook, '签到数据.xlsx'); + }); + }; + + const close = () => { + updateVisible(false); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + const enterSeat = (value: any) => { + updateSignUser(value).then((res) => { + if (res.code !== 0) { + message.error('出错了!'); + } + }); + }; + watch( + () => props.data, + (value) => { + defaultWhere.meetingId = value; + reload(); + } + ); +</script> + +<script lang="ts"> + export default { + name: 'SignUser' + }; +</script> diff --git a/src/views/meeting/components/user-search.vue b/src/views/meeting/components/user-search.vue new file mode 100644 index 0000000..ce0ef3d --- /dev/null +++ b/src/views/meeting/components/user-search.vue @@ -0,0 +1,107 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 10, lg: 10, md: 10, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 14, lg: 14, md: 14, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="用户姓名" style="margin-bottom: 0"> + <a-input + v-model:value.trim="form.name" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="企业名称" style="margin-bottom: 0"> + <a-input + v-model:value.trim="form.company" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + /> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item + class="ele-text-right" + :wrapper-col="{ span: 24 }" + style="margin-bottom: 0" + > + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { UserParam } from '@/api/meeting/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const props = defineProps<{ + // 默认搜索条件 + where?: UserParam; + }>(); + + const emit = defineEmits<{ + (e: 'search', where?: UserParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<UserParam>({ + name: '', + company: '', + ...props.where + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views/meeting/index.vue b/src/views/meeting/index.vue new file mode 100644 index 0000000..4d5d5db --- /dev/null +++ b/src/views/meeting/index.vue @@ -0,0 +1,245 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <meet-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="roleId" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 800 }" + cache-key="signMeeting" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'action'"> + <a-space> + <a @click="showQrCode(record)">二维码</a> + <a-divider type="vertical" /> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a @click="openSign(record)">签到管理</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此会议吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 编辑弹窗 --> + <meet-edit v-model:visible="showEdit" :data="current" @done="reload" /> + <!-- 报名用户弹窗 --> + <sign-user v-model:visible="showSign" :data="meetingId" /> + <!-- 二维码弹窗 --> + <ele-modal + :width="170" + :footer="null" + title="二维码" + v-model:visible="qrShow" + > + <ele-qr-code :value="qrcode" :size="120" /> + </ele-modal> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import MeetSearch from './components/meeting-search.vue'; + import MeetEdit from './components/meeting-edit.vue'; + import SignUser from './components/sign-user.vue'; + import { + pageMeeting, + removeMeeting, + removeMeetingBatch + } from '@/api/meeting'; + import type { Meeting, MeetingParam } from '@/api/meeting/model'; + import { HOME_BASE_URL } from '@/config/setting'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + const meetingId = ref(); + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '会议名称', + dataIndex: 'title', + sorter: true, + showSorterTooltip: false + }, + { + title: '会议地址', + dataIndex: 'room', + sorter: true, + showSorterTooltip: false + }, + { + title: '会议时间', + dataIndex: 'meeting_time', + sorter: true, + showSorterTooltip: false + }, + { + title: '签到时间', + dataIndex: 'sign_time', + sorter: true, + showSorterTooltip: false, + customRender: ({ text }) => + toDateString(text[0], 'MM-dd HH:mm') + + '/' + + toDateString(text[1], 'MM-dd HH:mm') + }, + { + title: '创建时间', + dataIndex: 'created_at', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '操作', + key: 'action', + width: 240, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<Meeting[]>([]); + + const qrShow = ref(false); + const qrcode = ref<string>(); + // 当前编辑数据 + const current = ref<Meeting | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 是否显示报名弹窗 + const showSign = ref(false); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageMeeting({ ...where, ...orders, page, limit }); + }; + + /* 搜索 */ + const reload = (where?: MeetingParam) => { + meetingId.value = null; + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: Meeting) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 打开签到管理弹窗 */ + const openSign = (row?: Meeting) => { + meetingId.value = row?.id; + showSign.value = true; + }; + + const showQrCode = (row?: Meeting) => { + qrShow.value = true; + qrcode.value = HOME_BASE_URL + '/pages/attendance/index?id=' + row?.id; + }; + /* 删除单个 */ + const remove = (row: Meeting) => { + const hide = messageLoading('请求中..', 0); + removeMeeting(row.id) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的会议吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeMeetingBatch(selection.value.map((d) => d.id)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'SignMeeting' + }; +</script> diff --git a/src/views/setting/file/index.vue b/src/views/setting/file/index.vue new file mode 100644 index 0000000..b67069d --- /dev/null +++ b/src/views/setting/file/index.vue @@ -0,0 +1,189 @@ +<template> + <div> + <a-page-header :ghost="false" title="文件存储设置"> + <div class="ele-text-secondary"> 用于指定文件存储引擎 </div> + </a-page-header> + <div class="ele-body"> + <a-card :bordered="false"> + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { sm: 4, xs: 24 } : { flex: '100px' }" + :wrapper-col="styleResponsive ? { sm: 20, xs: 24 } : { flex: '1' }" + style="max-width: 800px; margin: 0 auto" + > + <a-form-item label="存储位置"> + <a-radio-group v-model:value="form.type" button-style="solid"> + <a-radio-button value="local">本地</a-radio-button> + <a-radio-button value="qiniu">七牛</a-radio-button> + </a-radio-group> + </a-form-item> + + <div class="local" v-if="form.type === 'local'"> + <a-form-item label="本地目录" name="file_path"> + <a-input + allow-clear + placeholder="请输入目录" + v-model:value="form.file_path" + /> + </a-form-item> + </div> + + <div class="qiniu" v-if="form.type === 'qiniu'"> + <a-form-item label="domain" name="domain"> + <a-input + allow-clear + placeholder="请输入domain" + v-model:value="form.domain" + /> + </a-form-item> + <a-form-item label="bucket" name="bucket"> + <a-input + allow-clear + placeholder="请输入bucket" + v-model:value="form.bucket" + /> + </a-form-item> + <a-form-item label="access_key" name="access_key"> + <a-input + allow-clear + placeholder="请输入AK" + v-model:value="form.access_key" + /> + </a-form-item> + <a-form-item label="secret_key" name="secret_key"> + <a-input + allow-clear + placeholder="请输入SK" + v-model:value="form.secret_key" + /> + </a-form-item> + </div> + <a-form-item + :wrapper-col=" + styleResponsive ? { sm: { offset: 4 } } : { offset: 3 } + " + > + <a-space size="middle"> + <a-button @click="finishPageTab()">关闭</a-button> + <a-button type="primary" :loading="loading" @click="submit"> + 提交 + </a-button> + </a-space> + </a-form-item> + </a-form> + </a-card> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { finishPageTab } from '@/utils/page-tab-util'; + import { FileForm } from '@/api/setting/model'; + import { getConfig, submitForm } from '@/api/setting'; + import { assignObject } from 'ele-admin-pro'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const formRef = ref<FormInstance | null>(null); + + // 加载状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields } = useFormData<FileForm>({ + type: 'local', + file_path: '', + domain: '', + bucket: '', + access_key: '', + secret_key: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + domain: [ + { + required: true, + message: 'domain', + type: 'string', + trigger: 'blur' + } + ], + bucket: [ + { + required: true, + message: '请输入bucket', + type: 'string', + trigger: 'blur' + } + ], + access_key: [ + { + required: true, + message: '请输入access_key', + type: 'string', + trigger: 'blur' + } + ], + secret_key: [ + { + required: true, + message: '请输入secret_key', + type: 'string', + trigger: 'blur' + } + ] + }); + + const query = () => { + getConfig().then((res) => { + let data = JSON.parse(res.file.data); + assignObject(form, data); + }); + }; + /* 提交 */ + const submit = () => { + if (!formRef.value) { + return; + } + const newForm = { + ...form, + field: 'file' + }; + formRef.value + .validate() + .then(() => { + loading.value = true; + submitForm(newForm).then((res) => { + if (res.code === 0) { + loading.value = false; + resetFields(); + query(); + return message.success(res.message); + } else { + loading.value = false; + return message.error(res.message); + } + }); + }) + .catch(() => {}); + }; + query(); +</script> + +<script lang="ts"> + export default { + name: 'FormFile' + }; +</script> diff --git a/src/views/setting/site/index.vue b/src/views/setting/site/index.vue new file mode 100644 index 0000000..c70441c --- /dev/null +++ b/src/views/setting/site/index.vue @@ -0,0 +1,334 @@ +<template> + <div> + <a-page-header :ghost="false" title="站点配置"> + <div class="ele-text-secondary"> 用于配置站点的基本信息 </div> + </a-page-header> + <div class="ele-body"> + <a-card :bordered="false"> + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { sm: 4, xs: 24 } : { flex: '100px' }" + :wrapper-col="styleResponsive ? { sm: 20, xs: 24 } : { flex: '1' }" + style="max-width: 800px; margin: 0 auto" + > + <a-form-item label="站点域名" name="url"> + <a-input v-model:value="form.url"> + <template #addonBefore> + <a-select v-model:value="form.urlPre" style="width: 90px"> + <a-select-option value="http://">http://</a-select-option> + <a-select-option value="https://">https://</a-select-option> + </a-select> + </template> + </a-input> + </a-form-item> + <a-form-item label="站点名称" name="siteName"> + <a-input + allow-clear + placeholder="请输入站点名称" + v-model:value="form.siteName" + /> + </a-form-item> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 } + " + > + <a-form-item + label="站点icon" + name="icon" + :label-col=" + styleResponsive ? { md: 8, sm: 4, xs: 24 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' } + " + > + <ele-image-upload + v-model:value="icon" + :limit="1" + :before-upload="onBeforeUpload" + :remove-handler="iconRemoveHandler" + :item-style="{ width: '150px', height: '100px' }" + :button-style="{ width: '150px', height: '100px' }" + @upload="iconUpload" + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 } + " + > + <a-form-item + label="站点logo" + name="logo" + :label-col=" + styleResponsive ? { md: 8, sm: 4, xs: 24 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' } + " + > + <ele-image-upload + v-model:value="logo" + :limit="1" + :before-upload="onBeforeUpload" + :remove-handler="logoRemoveHandler" + :item-style="{ width: '150px', height: '100px' }" + :button-style="{ width: '150px', height: '100px' }" + @upload="logoUpload" + /> + </a-form-item> + </a-col> + </a-row> + + <a-form-item label="关键字"> + <a-input + allow-clear + placeholder="请输入关键字" + v-model:value="form.keywords" + /> + </a-form-item> + <a-form-item label="站点描述"> + <a-textarea + :rows="4" + v-model:value="form.description" + placeholder="请输入站点描述" + /> + </a-form-item> + <a-form-item label="ICP备案"> + <a-input + allow-clear + placeholder="请输入ICP备案" + v-model:value="form.icp" + /> + </a-form-item> + <a-form-item label="公安备案"> + <a-input + allow-clear + placeholder="请输入公安备案" + v-model:value="form.beian" + /> + </a-form-item> + <a-form-item label="授权密钥"> + <a-input + allow-clear + placeholder="请输入授权密钥" + v-model:value="form.key" + /> + </a-form-item> + <a-form-item + :wrapper-col=" + styleResponsive ? { sm: { offset: 4 } } : { offset: 3 } + " + > + <a-space size="middle"> + <a-button @click="finishPageTab()">关闭</a-button> + <a-button type="primary" :loading="loading" @click="submit"> + 提交 + </a-button> + </a-space> + </a-form-item> + </a-form> + </a-card> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { finishPageTab } from '@/utils/page-tab-util'; + import { SiteForm } from '@/api/setting/model'; + import { + BeforeUploadType, + ItemType + } from 'ele-admin-pro/es/ele-image-upload/types'; + import request from '@/utils/request'; + import { submitForm, getConfig } from '@/api/setting'; + import { assignObject } from 'ele-admin-pro'; + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + url: [ + { + required: true, + message: '请输入url', + type: 'string', + trigger: 'blur' + } + ], + siteName: [ + { + required: true, + type: 'string', + message: '请输入站点名', + trigger: 'blur' + } + ] + }); + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const formRef = ref<FormInstance | null>(null); + + // 加载状态 + const loading = ref(false); + const icon = ref<any>([]); + const logo = ref<any>([]); + // 表单数据 + const { form, resetFields } = useFormData<SiteForm>({ + type: '', + urlPre: 'http://', + url: '', + icon: '', + logo: '', + siteName: '', + keywords: '', + description: '', + icp: '', + beian: '', + key: '' + }); + const query = () => { + getConfig().then((res) => { + let data = JSON.parse(res.basic.data); + assignObject(form, data); + if (icon.value == '') { + icon.value.push(...icon.value, { url: data.icon }); + } + if (logo.value == '') { + logo.value.push(...logo.value, { url: data.logo }); + } + }); + }; + const onBeforeUpload: BeforeUploadType = (file: File) => { + if (!file.type.startsWith('image')) { + message.error('只能选择图片'); + return false; + } + if (file.size / 1024 / 1024 > 2) { + message.error('大小不能超过 2MB'); + return false; + } + }; + //上传图片 + const iconUpload = (d: ItemType) => { + const item = icon.value.find((t) => t.uid === d.uid) ?? d; + // item 包含的字段参考前面说明 + item.status = 'uploading'; + const formData = new FormData(); + formData.append('file', item.file); + request({ + url: '/file/upload', + method: 'post', + data: formData, + onUploadProgress: (e: any) => { + // 文件上传进度回调 + if (e.lengthComputable) { + item.progress = (e.loaded / e.total) * 100; + } + } + }) + .then((res) => { + if (res.data.code === 0) { + item.status = 'done'; + item.url = res.data.data; + form.icon = res.data.data; + } + }) + .catch((e: Error) => { + message.warning(e.message); + item.status = 'exception'; + }); + }; + const logoUpload = (d: ItemType) => { + const item = logo.value.find((t) => t.uid === d.uid) ?? d; + // item 包含的字段参考前面说明 + item.status = 'uploading'; + const formData = new FormData(); + formData.append('file', item.file); + request({ + url: '/file/upload', + method: 'post', + data: formData, + onUploadProgress: (e: any) => { + // 文件上传进度回调 + if (e.lengthComputable) { + item.progress = (e.loaded / e.total) * 100; + } + } + }).then((res) => { + if (res.data.code === 0) { + item.status = 'done'; + item.url = res.data.data; + form.logo = res.data.data; + } + }) + .catch((e: Error) => { + message.warning(e.message); + item.status = 'exception'; + }); + }; + const iconRemoveHandler = (item) => { + icon.value.forEach((d: any) => { + if (d.uid === item.uid) { + icon.value = []; + form.icon = ''; + d.deleted = 1; + } + }); + }; + const logoRemoveHandler = (item) => { + logo.value.forEach((d: any) => { + if (d.uid === item.uid) { + logo.value = []; + form.logo = ''; + d.deleted = 1; + } + }); + }; + /* 提交 */ + const submit = () => { + if (!formRef.value) { + return; + } + const newForm = { + ...form, + field: 'basic' + }; + formRef.value + .validate() + .then(() => { + loading.value = true; + submitForm(newForm).then((res: any) => { + if (res.code === 0) { + loading.value = false; + resetFields(); + query(); + return message.success(res.message); + } else { + loading.value = false; + return message.error(res.message); + } + }); + }) + .catch(() => {}); + }; + query(); +</script> + +<script lang="ts"> + export default { + name: 'FormBasic' + }; +</script> diff --git a/src/views/setting/wechat/index.vue b/src/views/setting/wechat/index.vue new file mode 100644 index 0000000..c8743bb --- /dev/null +++ b/src/views/setting/wechat/index.vue @@ -0,0 +1,133 @@ +<template> + <div> + <a-page-header :ghost="false" title="微信配置"> + <div class="ele-text-secondary"> 用于绑定微信appid和app_secret </div> + </a-page-header> + <div class="ele-body"> + <a-card :bordered="false"> + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { sm: 4, xs: 24 } : { flex: '100px' }" + :wrapper-col="styleResponsive ? { sm: 20, xs: 24 } : { flex: '1' }" + style="max-width: 800px; margin: 0 auto" + > + <a-form-item label="appid" name="appid"> + <a-input + allow-clear + placeholder="请输入appid" + v-model:value="form.appid" + /> + </a-form-item> + <a-form-item label="appsecret" name="appsecret"> + <a-input + allow-clear + placeholder="请输入appsecret" + v-model:value="form.appsecret" + /> + </a-form-item> + <a-form-item + :wrapper-col=" + styleResponsive ? { sm: { offset: 4 } } : { offset: 3 } + " + > + <a-space size="middle"> + <a-button @click="finishPageTab()">关闭</a-button> + <a-button type="primary" :loading="loading" @click="submit"> + 提交 + </a-button> + </a-space> + </a-form-item> + </a-form> + </a-card> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { finishPageTab } from '@/utils/page-tab-util'; + import { WeChatForm } from '@/api/setting/model'; + import { submitForm, getConfig } from '@/api/setting'; + import { assignObject } from 'ele-admin-pro'; + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + appid: [ + { + required: true, + message: '请输入appid', + type: 'string', + trigger: 'blur' + } + ], + appsecret: [ + { + required: true, + type: 'string', + message: '请输入appsecret', + trigger: 'blur' + } + ] + }); + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + // + const formRef = ref<FormInstance | null>(null); + + // 加载状态 + const loading = ref(false); + // 表单数据 + const { form, resetFields } = useFormData<WeChatForm>({ + appid: '', + appsecret: '' + }); + const query = () => { + getConfig().then((res) => { + let data = JSON.parse(res.wechat.data); + assignObject(form, data); + }); + }; + /* 提交 */ + const submit = () => { + if (!formRef.value) { + return; + } + const newForm = { + ...form, + field: 'wechat' + }; + formRef.value + .validate() + .then(() => { + loading.value = true; + submitForm(newForm).then((res: any) => { + if (res.code === 0) { + loading.value = false; + resetFields(); + query(); + return message.success(res.message); + } else { + loading.value = false; + return message.error(res.message); + } + }); + }) + .catch(() => {}); + }; + query(); +</script> + +<script lang="ts"> + export default { + name: 'WeixinSetting' + }; +</script> diff --git a/src/views/system/dictionary/components/dict-data-edit.vue b/src/views/system/dictionary/components/dict-data-edit.vue new file mode 100644 index 0000000..8a7cb77 --- /dev/null +++ b/src/views/system/dictionary/components/dict-data-edit.vue @@ -0,0 +1,186 @@ +<!-- 字典项编辑弹窗 --> +<template> + <ele-modal + :width="460" + :visible="visible" + :confirm-loading="loading" + :body-style="{ paddingBottom: '8px' }" + :title="isUpdate ? '修改字典项' : '添加字典项'" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 6, sm: 6, xs: 24 } : { flex: '98px' }" + :wrapper-col=" + styleResponsive ? { md: 18, sm: 18, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="字典项名称" name="dictDataName"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入字典项名称" + v-model:value="form.dictDataName" + /> + </a-form-item> + <a-form-item label="字典项值" name="dictDataCode"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入字典项值" + v-model:value="form.dictDataCode" + /> + </a-form-item> + <a-form-item label="排序号" name="sortNumber"> + <a-input-number + :min="0" + :max="9999" + class="ele-fluid" + placeholder="请输入排序号" + v-model:value="form.sortNumber" + /> + </a-form-item> + <a-form-item label="备注"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入备注" + v-model:value="form.comments" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { + addDictionaryData, + updateDictionaryData + } from '@/api/system/dictionary-data'; + import type { DictionaryData } from '@/api/system/dictionary-data/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: DictionaryData | null; + // 字典id + dictId: number; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<DictionaryData>({ + dictDataId: undefined, + dictDataName: '', + dictDataCode: '', + sortNumber: '', + comments: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + dictDataName: [ + { + required: true, + message: '请输入字典项名称', + type: 'string', + trigger: 'blur' + } + ], + dictDataCode: [ + { + required: true, + message: '请输入字典项值', + type: 'string', + trigger: 'blur' + } + ], + sortNumber: [ + { + required: true, + message: '请输入排序号', + type: 'number', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value + ? updateDictionaryData + : addDictionaryData; + saveOrUpdate({ + ...form, + dictId: props.dictId + }) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views/system/dictionary/components/dict-data-search.vue b/src/views/system/dictionary/components/dict-data-search.vue new file mode 100644 index 0000000..8dfafa3 --- /dev/null +++ b/src/views/system/dictionary/components/dict-data-search.vue @@ -0,0 +1,86 @@ +<!-- 搜索表单 --> +<template> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive ? { xl: 6, lg: 8, md: 11, sm: 24, xs: 24 } : { span: 6 } + " + > + <a-input + v-model:value.trim="form.keywords" + placeholder="输入关键字搜索" + allow-clear + /> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 18, lg: 16, md: 13, sm: 24, xs: 24 } + : { span: 18 } + " + > + <a-space :size="10" style="flex-wrap: wrap"> + <a-button type="primary" class="ele-btn-icon" @click="search"> + <template #icon> + <search-outlined /> + </template> + <span>查询</span> + </a-button> + <a-button type="primary" class="ele-btn-icon" @click="add"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button danger type="primary" class="ele-btn-icon" @click="remove"> + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </a-col> + </a-row> +</template> + +<script lang="ts" setup> + import { + PlusOutlined, + DeleteOutlined, + SearchOutlined + } from '@ant-design/icons-vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { DictionaryDataParam } from '@/api/system/dictionary-data/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: DictionaryDataParam): void; + (e: 'add'): void; + (e: 'remove'): void; + }>(); + + // 表单数据 + const { form } = useFormData<DictionaryDataParam>({ + keywords: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 添加 */ + const add = () => { + emit('add'); + }; + + /* 删除 */ + const remove = () => { + emit('remove'); + }; +</script> diff --git a/src/views/system/dictionary/components/dict-data.vue b/src/views/system/dictionary/components/dict-data.vue new file mode 100644 index 0000000..eb1821b --- /dev/null +++ b/src/views/system/dictionary/components/dict-data.vue @@ -0,0 +1,212 @@ +<template> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="dictDataId" + :columns="columns" + :datasource="datasource" + tool-class="ele-toolbar-form" + v-model:selection="selection" + :row-selection="{ columnWidth: 48 }" + :scroll="{ x: 800 }" + height="calc(100vh - 290px)" + tools-theme="default" + bordered + cache-key="proSystemDictDataTable" + class="sys-dict-data-table" + > + <template #toolbar> + <dict-data-search + @search="reload" + @add="openEdit()" + @remove="removeBatch" + /> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此字典项吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + <!-- 编辑弹窗 --> + <dict-data-edit + v-model:visible="showEdit" + :data="current" + :dict-id="dictId" + @done="reload" + /> +</template> + +<script lang="ts" setup> + import { createVNode, ref, watch } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { ExclamationCircleOutlined } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import DictDataSearch from './dict-data-search.vue'; + import DictDataEdit from './dict-data-edit.vue'; + import { + pageDictionaryData, + removeDictionaryData, + removeDictionaryDataBatch + } from '@/api/system/dictionary-data'; + import type { + DictionaryData, + DictionaryDataParam + } from '@/api/system/dictionary-data/model'; + + const props = defineProps<{ + // 字典id + dictId: number; + }>(); + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + title: '字典项名称', + dataIndex: 'dictDataName', + ellipsis: true, + sorter: true, + showSorterTooltip: false + }, + { + title: '字典项值', + dataIndex: 'dictDataCode', + ellipsis: true, + sorter: true, + showSorterTooltip: false + }, + { + title: '排序号', + dataIndex: 'sortNumber', + sorter: true, + showSorterTooltip: false, + width: 120, + align: 'center' + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '操作', + key: 'action', + width: 130, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<DictionaryData[]>([]); + + // 当前编辑数据 + const current = ref<DictionaryData | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageDictionaryData({ + ...where, + ...orders, + page, + limit, + dictId: props.dictId + }); + }; + + /* 刷新表格 */ + const reload = (where?: DictionaryDataParam) => { + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: DictionaryData) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 删除单个 */ + const remove = (row: DictionaryData) => { + const hide = messageLoading('请求中..', 0); + removeDictionaryData(row.dictDataId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的字典项吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeDictionaryDataBatch(selection.value.map((d) => d.dictDataId)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + // 监听字典id变化 + watch( + () => props.dictId, + () => { + reload(); + } + ); +</script> + +<style lang="less" scoped> + .sys-dict-data-table :deep(.ant-table-body) { + overflow: auto !important; + overflow: overlay !important; + } + + .sys-dict-data-table :deep(.ant-table-pagination.ant-pagination) { + padding: 0 4px; + margin-bottom: 0; + } +</style> diff --git a/src/views/system/dictionary/components/dict-edit.vue b/src/views/system/dictionary/components/dict-edit.vue new file mode 100644 index 0000000..b2de3db --- /dev/null +++ b/src/views/system/dictionary/components/dict-edit.vue @@ -0,0 +1,176 @@ +<!-- 字典编辑弹窗 --> +<template> + <ele-modal + :width="460" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改字典' : '添加字典'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }" + :wrapper-col=" + styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="字典名称" name="dictName"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入字典名称" + v-model:value="form.dictName" + /> + </a-form-item> + <a-form-item label="字典值" name="dictCode"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入字典值" + v-model:value="form.dictCode" + /> + </a-form-item> + <a-form-item label="排序号" name="sortNumber"> + <a-input-number + :min="0" + :max="9999" + class="ele-fluid" + placeholder="请输入排序号" + v-model:value="form.sortNumber" + /> + </a-form-item> + <a-form-item label="备注"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入备注" + v-model:value="form.comments" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { addDictionary, updateDictionary } from '@/api/system/dictionary'; + import type { Dictionary } from '@/api/system/dictionary/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Dictionary | null; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Dictionary>({ + dictId: undefined, + dictName: '', + dictCode: '', + sortNumber: undefined, + comments: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + dictName: [ + { + required: true, + message: '请输入字典名称', + type: 'string', + trigger: 'blur' + } + ], + dictCode: [ + { + required: true, + message: '请输入字典值', + type: 'string', + trigger: 'blur' + } + ], + sortNumber: [ + { + required: true, + message: '请输入排序号', + type: 'number', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateDictionary : addDictionary; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views/system/dictionary/index.vue b/src/views/system/dictionary/index.vue new file mode 100644 index 0000000..be8be9c --- /dev/null +++ b/src/views/system/dictionary/index.vue @@ -0,0 +1,197 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false" :body-style="{ padding: '16px' }"> + <ele-split-layout + width="266px" + allow-collapse + :right-style="{ overflow: 'hidden' }" + :style="{ minHeight: 'calc(100vh - 152px)' }" + > + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="dictId" + :columns="columns" + :datasource="datasource" + v-model:current="current" + selection-type="radio" + :row-selection="{ columnWidth: 32 }" + :need-page="false" + :toolkit="[]" + height="calc(100vh - 290px)" + tools-theme="default" + bordered + :custom-row="customRow" + class="sys-dict-table" + @done="done" + > + <template #toolbar> + <a-space :size="10"> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + type="primary" + :disabled="!current" + class="ele-btn-icon" + @click="openEdit(current)" + > + <template #icon> + <edit-outlined /> + </template> + <span>修改</span> + </a-button> + <a-button + danger + type="primary" + :disabled="!current" + class="ele-btn-icon" + @click="remove" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + </ele-pro-table> + <template #content> + <dict-data + v-if="current && current.dictId" + :dict-id="current.dictId" + /> + </template> + </ele-split-layout> + </a-card> + <!-- 编辑弹窗 --> + <dict-edit v-model:visible="showEdit" :data="editData" @done="reload" /> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + EditOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import { messageLoading } from 'ele-admin-pro/es'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem, + EleProTableDone + } from 'ele-admin-pro/es/ele-pro-table/types'; + import DictData from './components/dict-data.vue'; + import DictEdit from './components/dict-edit.vue'; + import { listDictionaries, removeDictionary } from '@/api/system/dictionary'; + import type { Dictionary } from '@/api/system/dictionary/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 32, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '字典名称', + dataIndex: 'dictName' + } + ]); + + // 表格选中数据 + const current = ref<Dictionary | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 编辑回显数据 + const editData = ref<Dictionary | null>(null); + + // 表格数据源 + const datasource: DatasourceFunction = () => { + return listDictionaries(); + }; + + /* 表格渲染完成回调 */ + const done: EleProTableDone<Dictionary> = (res) => { + if (res.data?.length) { + current.value = res.data[0]; + } + }; + + /* 刷新表格 */ + const reload = () => { + tableRef?.value?.reload(); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: Dictionary | null) => { + editData.value = row ?? null; + showEdit.value = true; + }; + + /* 删除 */ + const remove = () => { + Modal.confirm({ + title: '提示', + content: '确定要删除选中的字典吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeDictionary(current.value?.dictId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 行点击事件 */ + const customRow = (record: Dictionary) => { + return { + onClick: () => { + current.value = record; + } + }; + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemDictionary' + }; +</script> + +<style lang="less" scoped> + .sys-dict-table { + :deep(.ant-table-body) { + overflow: auto !important; + overflow: overlay !important; + } + + :deep(.ant-table-row) { + cursor: pointer; + } + } +</style> diff --git a/src/views/system/file/components/file-search.vue b/src/views/system/file/components/file-search.vue new file mode 100644 index 0000000..bd9ded8 --- /dev/null +++ b/src/views/system/file/components/file-search.vue @@ -0,0 +1,106 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="文件名称"> + <a-input + v-model:value.trim="form.name" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="文件路径"> + <a-input + v-model:value.trim="form.path" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="上传人"> + <a-input + v-model:value.trim="form.createNickname" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { FileRecordParam } from '@/api/system/file/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: FileRecordParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<FileRecordParam>({ + name: '', + path: '', + createNickname: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views/system/file/index.vue b/src/views/system/file/index.vue new file mode 100644 index 0000000..e2e5cb4 --- /dev/null +++ b/src/views/system/file/index.vue @@ -0,0 +1,244 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <file-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 800 }" + cache-key="proSystemFileTable" + > + <template #toolbar> + <a-space> + <a-upload :show-upload-list="false" :customRequest="onUpload"> + <a-button type="primary" class="ele-btn-icon"> + <template #icon> + <upload-outlined /> + </template> + <span>上传</span> + </a-button> + </a-upload> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'path'"> + <a :href="record.url" target="_blank"> + {{ record.path }} + </a> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a :href="record.downloadUrl" target="_blank">下载</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此文件吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + UploadOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import FileSearch from './components/file-search.vue'; + import { + pageFiles, + removeFile, + removeFiles, + uploadFile + } from '@/api/system/file'; + import type { FileRecord, FileRecordParam } from '@/api/system/file/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '文件名称', + dataIndex: 'name', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '文件路径', + key: 'path', + dataIndex: 'path', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '文件大小', + dataIndex: 'length', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => { + if (text < 1024) { + return text + 'B'; + } else if (text < 1024 * 1024) { + return (text / 1024).toFixed(1) + 'KB'; + } else if (text < 1024 * 1024 * 1024) { + return (text / 1024 / 1024).toFixed(1) + 'M'; + } else { + return (text / 1024 / 1024 / 1024).toFixed(1) + 'G'; + } + }, + width: 120 + }, + { + title: '上传人', + dataIndex: 'createNickname', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + width: 120 + }, + { + title: '上传时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text), + width: 160 + }, + { + title: '操作', + key: 'action', + width: 120, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<FileRecord[]>([]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageFiles({ ...where, ...orders, page, limit }); + }; + + /* 搜索 */ + const reload = (where?: FileRecordParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 删除单个 */ + const remove = (row: FileRecord) => { + const hide = messageLoading('请求中..', 0); + removeFile(row.id) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的文件吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeFiles(selection.value.map((d) => d.id)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 上传 */ + const onUpload = ({ file }) => { + if (file.size / 1024 / 1024 > 100) { + message.error('大小不能超过 100MB'); + return false; + } + const hide = messageLoading({ + content: '上传中..', + duration: 0, + mask: true + }); + uploadFile(file) + .then(() => { + hide(); + message.success('上传成功'); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + return false; + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemFile' + }; +</script> diff --git a/src/views/system/login-record/components/login-record-search.vue b/src/views/system/login-record/components/login-record-search.vue new file mode 100644 index 0000000..c1c480b --- /dev/null +++ b/src/views/system/login-record/components/login-record-search.vue @@ -0,0 +1,115 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="用户账号"> + <a-input + v-model:value.trim="form.username" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="用户名"> + <a-input + v-model:value.trim="form.nickname" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="登录时间"> + <a-range-picker + v-model:value="dateRange" + value-format="YYYY-MM-DD" + class="ele-fluid" + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { LoginRecordParam } from '@/api/system/login-record/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: LoginRecordParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<LoginRecordParam>({ + username: '', + nickname: '' + }); + + // 日期范围选择 + const dateRange = ref<[string, string]>(['', '']); + + /* 搜索 */ + const search = () => { + const [d1, d2] = dateRange.value ?? []; + emit('search', { + ...form, + createTimeStart: d1 ? d1 + ' 00:00:00' : '', + createTimeEnd: d2 ? d2 + ' 23:59:59' : '' + }); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + dateRange.value = ['', '']; + search(); + }; +</script> diff --git a/src/views/system/login-record/index.vue b/src/views/system/login-record/index.vue new file mode 100644 index 0000000..2f62337 --- /dev/null +++ b/src/views/system/login-record/index.vue @@ -0,0 +1,235 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <login-record-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + :scroll="{ x: 900 }" + cache-key="proSystemLoginRecordTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="exportData"> + <template #icon> + <download-outlined /> + </template> + <span>导出</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'loginType'"> + <a-tag v-if="record.loginType === 0" color="green">登录成功</a-tag> + <a-tag v-else-if="record.loginType === 1" color="red"> + 登录失败 + </a-tag> + <a-tag v-else-if="record.loginType === 2">退出登录</a-tag> + <a-tag v-else-if="record.loginType === 3" color="orange"> + 刷新TOKEN + </a-tag> + </template> + </template> + </ele-pro-table> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { utils, writeFile } from 'xlsx'; + import { DownloadOutlined } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import LoginRecordSearch from './components/login-record-search.vue'; + import { + pageLoginRecords, + listLoginRecords + } from '@/api/system/login-record'; + import type { LoginRecordParam } from '@/api/system/login-record/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '账号', + dataIndex: 'username', + sorter: true, + showSorterTooltip: false + }, + { + title: '用户名', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false + }, + { + title: 'IP地址', + dataIndex: 'ip', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '设备型号', + dataIndex: 'device', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '操作系统', + dataIndex: 'os', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '浏览器', + dataIndex: 'browser', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '操作类型', + key: 'loginType', + dataIndex: 'loginType', + sorter: true, + showSorterTooltip: false, + width: 120, + filters: [ + { + text: '登录成功', + value: 0 + }, + { + text: '登录失败', + value: 1 + }, + { + text: '退出登录', + value: 2 + }, + { + text: '刷新TOKEN', + value: 3 + } + ], + filterMultiple: false + }, + { + title: '备注', + dataIndex: 'comments', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '登录时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + } + ]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ + page, + limit, + where, + orders, + filters + }) => { + return pageLoginRecords({ + ...where, + ...orders, + ...filters, + page, + limit + }); + }; + + /* 刷新表格 */ + const reload = (where?: LoginRecordParam) => { + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 导出数据 */ + const exportData = () => { + const array = [ + [ + '账号', + '用户名', + 'IP地址', + '设备型号', + '操作系统', + '浏览器', + '操作类型', + '备注', + '登录时间' + ] + ]; + // 请求查询全部接口 + const hide = messageLoading('请求中..', 0); + tableRef.value?.doRequest(({ where, orders, filters }) => { + listLoginRecords({ ...where, ...orders, ...filters }) + .then((data) => { + hide(); + data.forEach((d) => { + array.push([ + d.username, + d.nickname, + d.ip, + d.device, + d.os, + d.browser, + ['登录成功', '登录失败', '退出登录', '刷新TOKEN'][d.loginType], + d.comments, + toDateString(d.createTime) + ]); + }); + writeFile( + { + SheetNames: ['Sheet1'], + Sheets: { + Sheet1: utils.aoa_to_sheet(array) + } + }, + '登录日志.xlsx' + ); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemLoginRecord' + }; +</script> diff --git a/src/views/system/menu/components/menu-edit.vue b/src/views/system/menu/components/menu-edit.vue new file mode 100644 index 0000000..f336a0d --- /dev/null +++ b/src/views/system/menu/components/menu-edit.vue @@ -0,0 +1,414 @@ +<!-- 编辑弹窗 --> +<template> + <ele-modal + :width="740" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改菜单' : '新建菜单'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 6, sm: 4, xs: 24 } : { flex: '90px' }" + :wrapper-col=" + styleResponsive ? { md: 18, sm: 20, xs: 24 } : { flex: '1' } + " + > + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="上级菜单" name="parentId"> + <a-tree-select + allow-clear + :tree-data="menuList" + tree-default-expand-all + placeholder="请选择上级菜单" + :value="form.parentId || undefined" + :dropdown-style="{ maxHeight: '360px', overflow: 'auto' }" + @update:value="(value?: number) => (form.parentId = value)" + /> + </a-form-item> + <a-form-item label="菜单名称" name="title"> + <a-input + allow-clear + placeholder="请输入菜单名称" + v-model:value="form.title" + /> + </a-form-item> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="菜单类型" name="menuType"> + <a-radio-group + v-model:value="form.menuType" + @change="onMenuTypeChange" + > + <a-radio :value="0">目录</a-radio> + <a-radio :value="1">菜单</a-radio> + <a-radio :value="2">按钮</a-radio> + </a-radio-group> + </a-form-item> + <a-form-item label="打开方式"> + <a-radio-group + v-model:value="form.openType" + :disabled="form.menuType === 0 || form.menuType === 2" + @change="onOpenTypeChange" + > + <a-radio :value="0">组件</a-radio> + <a-radio :value="1">内链</a-radio> + <a-radio :value="2">外链</a-radio> + </a-radio-group> + </a-form-item> + </a-col> + </a-row> + <div style="margin-bottom: 22px"> + <a-divider /> + </div> + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="菜单图标" name="icon"> + <ele-icon-picker + :data="iconData" + :allow-search="false" + v-model:value="form.icon" + placeholder="请选择菜单图标" + :disabled="form.menuType === 2" + > + <template #icon="{ icon }"> + <component :is="icon" /> + </template> + </ele-icon-picker> + </a-form-item> + <a-form-item name="path"> + <template #label> + <a-tooltip + v-if="form.openType === 2" + title="需要以`http://`、`https://`、`//`开头" + > + <question-circle-outlined + style="vertical-align: -2px; margin-right: 4px" + /> + </a-tooltip> + <span>{{ form.openType === 2 ? '外链地址' : '路由地址' }}</span> + </template> + <a-input + allow-clear + v-model:value="form.path" + :disabled="form.menuType === 2" + :placeholder=" + form.openType === 2 ? '请输入外链地址' : '请输入路由地址' + " + /> + </a-form-item> + <a-form-item name="component"> + <template #label> + <a-tooltip + v-if="form.openType === 1" + title="需要以`http://`、`https://`、`//`开头" + > + <question-circle-outlined + style="vertical-align: -2px; margin-right: 4px" + /> + </a-tooltip> + <span>{{ form.openType === 1 ? '内链地址' : '组件路径' }}</span> + </template> + <a-input + allow-clear + v-model:value="form.component" + :disabled=" + form.menuType === 0 || + form.menuType === 2 || + form.openType === 2 + " + :placeholder=" + form.openType === 1 ? '请输入内链地址' : '请输入组件路径' + " + /> + </a-form-item> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="权限标识" name="authority"> + <a-input + allow-clear + placeholder="请输入权限标识" + v-model:value="form.authority" + :disabled=" + form.menuType === 0 || + (form.menuType === 1 && form.openType === 2) + " + /> + </a-form-item> + <a-form-item label="排序号" name="sortNumber"> + <a-input-number + :min="0" + :max="99999" + class="ele-fluid" + placeholder="请输入排序号" + v-model:value="form.sortNumber" + /> + </a-form-item> + <a-form-item label="是否展示"> + <a-switch + checked-children="是" + un-checked-children="否" + :checked="form.hide === 0" + :disabled="form.menuType === 2" + @update:checked="updateHideValue" + /> + <a-tooltip + title="选择不展示只注册路由不展示在侧边栏, 比如添加页面应该选择不展示" + > + <question-circle-outlined + style="vertical-align: -4px; margin-left: 16px" + /> + </a-tooltip> + </a-form-item> + </a-col> + </a-row> + <a-form-item + label="路由元数据" + name="meta" + :label-col=" + styleResponsive ? { md: 3, sm: 4, xs: 24 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { md: 21, sm: 20, xs: 24 } : { flex: '1' } + " + > + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入JSON格式的路由元数据" + v-model:value="form.meta" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { QuestionCircleOutlined } from '@ant-design/icons-vue'; + import { isExternalLink } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { addMenu, updateMenu } from '@/api/system/menu'; + import type { Menu } from '@/api/system/menu/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Menu | null; + // 上级菜单id + parentId?: number; + // 全部菜单数据 + menuList: Menu[]; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Menu>({ + menuId: undefined, + parentId: undefined, + title: '', + menuType: 0, + openType: 0, + icon: '', + path: '', + component: '', + authority: '', + sortNumber: undefined, + hide: 0, + meta: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + title: [ + { + required: true, + message: '请输入菜单名称', + type: 'string', + trigger: 'blur' + } + ], + sortNumber: [ + { + required: true, + message: '请输入排序号', + type: 'number', + trigger: 'blur' + } + ], + meta: [ + { + type: 'string', + validator: async (_rule: Rule, value: string) => { + if (value) { + const msg = '请输入正确的JSON格式'; + try { + const obj = JSON.parse(value); + if (typeof obj !== 'object' || obj === null) { + return Promise.reject(msg); + } + } catch (_e) { + return Promise.reject(msg); + } + } + return Promise.resolve(); + }, + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const menuForm = { + ...form, + // menuType 对应的值与后端不一致在前端处理 + menuType: form.menuType === 2 ? 1 : 0, + parentId: form.parentId || 0 + }; + const saveOrUpdate = isUpdate.value ? updateMenu : addMenu; + saveOrUpdate(menuForm) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + /* menuType选择改变 */ + const onMenuTypeChange = () => { + if (form.menuType === 0) { + form.authority = ''; + form.openType = 0; + form.component = ''; + } else if (form.menuType === 1) { + if (form.openType === 2) { + form.authority = ''; + } + } else { + form.openType = 0; + form.icon = ''; + form.path = ''; + form.component = ''; + form.hide = 0; + } + }; + + /* openType选择改变 */ + const onOpenTypeChange = () => { + if (form.openType === 2) { + form.component = ''; + form.authority = ''; + } + }; + + const updateHideValue = (value: boolean) => { + form.hide = value ? 0 : 1; + }; + + /* 判断是否是目录 */ + const isDirectory = (d: Menu) => { + return !!d.children?.length && !d.component; + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + const isExternal = isExternalLink(props.data.path); + const isInner = isExternalLink(props.data.component); + // menuType 对应的值与后端不一致在前端处理 + const menuType = + props.data.menuType === 1 ? 2 : isDirectory(props.data) ? 0 : 1; + assignFields({ + ...props.data, + menuType, + openType: isExternal ? 2 : isInner ? 1 : 0, + parentId: + props.data.parentId === 0 ? undefined : props.data.parentId + }); + isUpdate.value = true; + } else { + form.parentId = props.parentId; + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> + +<script lang="ts"> + import * as icons from '@/layout/menu-icons'; + + export default { + components: icons, + data() { + return { + iconData: [ + { + title: '已引入的图标', + icons: Object.keys(icons) + } + ] + }; + } + }; +</script> diff --git a/src/views/system/menu/components/menu-search.vue b/src/views/system/menu/components/menu-search.vue new file mode 100644 index 0000000..81afbf5 --- /dev/null +++ b/src/views/system/menu/components/menu-search.vue @@ -0,0 +1,106 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="菜单名称"> + <a-input + v-model:value.trim="form.title" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="菜单地址"> + <a-input + v-model:value.trim="form.path" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="权限标识"> + <a-input + v-model:value.trim="form.authority" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { MenuParam } from '@/api/system/menu/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: MenuParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<MenuParam>({ + title: '', + path: '', + authority: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue new file mode 100644 index 0000000..24a24e1 --- /dev/null +++ b/src/views/system/menu/index.vue @@ -0,0 +1,291 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <menu-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="menuId" + :columns="columns" + :datasource="datasource" + :parse-data="parseData" + :need-page="false" + :expand-icon-column-index="1" + :expanded-row-keys="expandedRowKeys" + :scroll="{ x: 1200 }" + cache-key="proSystemMenuTable" + @done="onDone" + @expand="onExpand" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button type="dashed" class="ele-btn-icon" @click="expandAll"> + 展开全部 + </a-button> + <a-button type="dashed" class="ele-btn-icon" @click="foldAll"> + 折叠全部 + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'menuType'"> + <a-tag v-if="isExternalLink(record.path)" color="red">外链</a-tag> + <a-tag v-else-if="isExternalLink(record.component)" color="orange"> + 内链 + </a-tag> + <a-tag v-else-if="isDirectory(record)" color="blue">目录</a-tag> + <a-tag v-else-if="record.menuType === 0" color="green">菜单</a-tag> + <a-tag v-else-if="record.menuType === 1">按钮</a-tag> + </template> + <template v-else-if="column.key === 'title'"> + <component v-if="record.icon" :is="record.icon" /> + <span style="padding-left: 8px">{{ record.title }}</span> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(null, record.menuId)">添加</a> + <a-divider type="vertical" /> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此菜单吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 编辑弹窗 --> + <menu-edit + v-model:visible="showEdit" + :data="current" + :parent-id="parentId" + :menu-list="menuData" + @done="reload" + /> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { PlusOutlined } from '@ant-design/icons-vue'; + import type { + DatasourceFunction, + ColumnItem, + EleProTableDone + } from 'ele-admin-pro/es/ele-pro-table/types'; + import MenuSearch from './components/menu-search.vue'; + import { + messageLoading, + toDateString, + isExternalLink, + toTreeData, + eachTreeData + } from 'ele-admin-pro/es'; + import type { EleProTable } from 'ele-admin-pro/es'; + import MenuEdit from './components/menu-edit.vue'; + import { listMenus, removeMenu } from '@/api/system/menu'; + import type { Menu, MenuParam } from '@/api/system/menu/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '菜单名称', + key: 'title', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '路由地址', + dataIndex: 'path', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '组件路径', + dataIndex: 'component', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '权限标识', + dataIndex: 'authority', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '排序', + dataIndex: 'sortNumber', + sorter: true, + showSorterTooltip: false, + width: 90 + }, + { + title: '可见', + dataIndex: 'hide', + sorter: true, + showSorterTooltip: false, + customRender: ({ text }) => ['是', '否'][text], + width: 90 + }, + { + title: '类型', + key: 'menuType', + sorter: true, + showSorterTooltip: false, + width: 90 + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '操作', + key: 'action', + width: 200, + align: 'center' + } + ]); + + // 当前编辑数据 + const current = ref<Menu | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 上级菜单id + const parentId = ref<number>(); + + // 菜单数据 + const menuData = ref<Menu[]>([]); + + // 表格展开的行 + const expandedRowKeys = ref<number[]>([]); + + // 表格数据源 + const datasource: DatasourceFunction = ({ where }) => { + return listMenus({ ...where }); + }; + + /* 数据转为树形结构 */ + const parseData = (data: Menu[]) => { + return toTreeData({ + data: data.map((d) => { + return { ...d, key: d.menuId, value: d.menuId }; + }), + idField: 'menuId', + parentIdField: 'parentId' + }); + }; + + /* 表格渲染完成回调 */ + const onDone: EleProTableDone<Menu> = ({ data }) => { + menuData.value = data; + }; + + /* 刷新表格 */ + const reload = (where?: MenuParam) => { + tableRef?.value?.reload({ where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: Menu | null, id?: number) => { + current.value = row ?? null; + parentId.value = id; + showEdit.value = true; + }; + + /* 删除单个 */ + const remove = (row: Menu) => { + if (row.children?.length) { + message.error('请先删除子节点'); + return; + } + const hide = messageLoading('请求中..', 0); + removeMenu(row.menuId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 展开全部 */ + const expandAll = () => { + let keys: number[] = []; + eachTreeData(menuData.value, (d) => { + if (d.children && d.children.length && d.menuId) { + keys.push(d.menuId); + } + }); + expandedRowKeys.value = keys; + }; + + /* 折叠全部 */ + const foldAll = () => { + expandedRowKeys.value = []; + }; + + /* 点击展开图标时触发 */ + const onExpand = (expanded: boolean, record: Menu) => { + if (expanded) { + expandedRowKeys.value = [ + ...expandedRowKeys.value, + record.menuId as number + ]; + } else { + expandedRowKeys.value = expandedRowKeys.value.filter( + (d) => d !== record.menuId + ); + } + }; + + /* 判断是否是目录 */ + const isDirectory = (d: Menu) => { + return !!d.children?.length && !d.component; + }; +</script> + +<script lang="ts"> + import * as MenuIcons from '@/layout/menu-icons'; + + export default { + name: 'SystemMenu', + components: MenuIcons + }; +</script> diff --git a/src/views/system/operation-record/components/operation-record-detail.vue b/src/views/system/operation-record/components/operation-record-detail.vue new file mode 100644 index 0000000..deb5740 --- /dev/null +++ b/src/views/system/operation-record/components/operation-record-detail.vue @@ -0,0 +1,131 @@ +<!-- 详情弹窗 --> +<template> + <ele-modal + title="详情" + :width="640" + :footer="null" + :visible="visible" + @update:visible="updateVisible" + > + <a-form + class="ele-form-detail" + :label-col="{ sm: { span: 8 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 16 }, xs: { span: 18 } }" + > + <a-row :gutter="16"> + <a-col :sm="12" :xs="24"> + <a-form-item label="操作人"> + <div class="ele-text-secondary"> + {{ data.nickname }}({{ data.username }}) + </div> + </a-form-item> + <a-form-item label="操作模块"> + <div class="ele-text-secondary"> + {{ data.module }} + </div> + </a-form-item> + <a-form-item label="操作时间"> + <div class="ele-text-secondary"> + {{ toDateString(data.createTime) }} + </div> + </a-form-item> + <a-form-item label="请求方式"> + <div class="ele-text-secondary"> + {{ data.requestMethod }} + </div> + </a-form-item> + </a-col> + <a-col :sm="12" :xs="24"> + <a-form-item label="IP地址"> + <div class="ele-text-secondary"> + {{ data.ip }} + </div> + </a-form-item> + <a-form-item label="操作功能"> + <div class="ele-text-secondary"> + {{ data.description }} + </div> + </a-form-item> + <a-form-item label="请求耗时"> + <div v-if="!isNaN(data.spendTime)" class="ele-text-secondary"> + {{ data.spendTime / 1000 }}s + </div> + </a-form-item> + <a-form-item label="请求状态"> + <a-tag :color="['green', 'red'][data.status]"> + {{ ['正常', '异常'][data.status] }} + </a-tag> + </a-form-item> + </a-col> + </a-row> + <div style="margin: 12px 0"> + <a-divider /> + </div> + <a-form-item + label="请求地址" + :label-col="{ sm: { span: 4 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }" + > + <div class="ele-text-secondary"> + {{ data.url }} + </div> + </a-form-item> + <a-form-item + label="调用方法" + :label-col="{ sm: { span: 4 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }" + > + <div class="ele-text-secondary"> + {{ data.method }} + </div> + </a-form-item> + <a-form-item + label="请求参数" + :label-col="{ sm: { span: 4 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }" + > + <div class="ele-text-secondary"> + {{ data.params }} + </div> + </a-form-item> + <a-form-item + v-if="data.status === 0" + label="返回结果" + :label-col="{ sm: { span: 4 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }" + > + <text-ellipsis :content="data.result" class="ele-text-secondary" /> + </a-form-item> + <a-form-item + v-else + label="异常信息" + :label-col="{ sm: { span: 4 }, xs: { span: 6 } }" + :wrapper-col="{ sm: { span: 20 }, xs: { span: 18 } }" + > + <text-ellipsis :content="data.error" class="ele-text-secondary" /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { toDateString } from 'ele-admin-pro/es'; + import type { OperationRecord } from '@/api/system/operation-record/model'; + import TextEllipsis from './text-ellipsis.vue'; + + const emit = defineEmits<{ + (e: 'update:visible', visible: boolean): void; + }>(); + + defineProps<{ + // 弹窗是否打开 + visible?: boolean; + // 修改回显的数据 + data: OperationRecord; + }>(); + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; +</script> diff --git a/src/views/system/operation-record/components/operation-record-search.vue b/src/views/system/operation-record/components/operation-record-search.vue new file mode 100644 index 0000000..d6e4309 --- /dev/null +++ b/src/views/system/operation-record/components/operation-record-search.vue @@ -0,0 +1,112 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="用户账号"> + <a-input + v-model:value.trim="form.username" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="操作模块"> + <a-input + v-model:value.trim="form.module" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="操作时间"> + <a-range-picker + v-model:value="dateRange" + :show-time="true" + value-format="YYYY-MM-DD HH:mm:ss" + class="ele-fluid" + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { OperationRecordParam } from '@/api/system/operation-record/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: OperationRecordParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<OperationRecordParam>({ + username: '', + module: '' + }); + + // 日期范围选择 + const dateRange = ref<[string, string]>(['', '']); + + /* 搜索 */ + const search = () => { + const [createTimeStart, createTimeEnd] = dateRange.value; + emit('search', { ...form, createTimeStart, createTimeEnd }); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + dateRange.value = ['', '']; + search(); + }; +</script> diff --git a/src/views/system/operation-record/components/text-ellipsis.vue b/src/views/system/operation-record/components/text-ellipsis.vue new file mode 100644 index 0000000..05b204a --- /dev/null +++ b/src/views/system/operation-record/components/text-ellipsis.vue @@ -0,0 +1,59 @@ +<!-- 文本超出隐藏 --> +<template> + <div + :class="[ + 'demo-text-ellipsis ele-bg-white ele-border-split', + { expanded: expanded } + ]" + > + <div>{{ content }}</div> + <div + class="demo-text-ellipsis-footer ele-border-split ele-bg-white" + @click="expanded = !expanded" + > + <up-outlined v-if="expanded" /> + <down-outlined v-else /> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'; + + defineProps<{ + content?: string; + }>(); + + const expanded = ref(false); +</script> + +<style lang="less" scoped> + .demo-text-ellipsis { + border-radius: 4px; + padding: 6px 12px 20px 12px; + position: relative; + border-width: 1px; + border-style: solid; + word-break: break-all; + + &:not(.expanded) { + max-height: 192px; + overflow: hidden; + } + } + + .demo-text-ellipsis-footer { + border-top-width: 1px; + border-top-style: solid; + position: absolute; + bottom: 0; + left: 0; + right: 0; + text-align: center; + font-size: 12px; + cursor: pointer; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + } +</style> diff --git a/src/views/system/operation-record/index.vue b/src/views/system/operation-record/index.vue new file mode 100644 index 0000000..760464e --- /dev/null +++ b/src/views/system/operation-record/index.vue @@ -0,0 +1,273 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <operation-record-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + :scroll="{ x: 1000 }" + cache-key="proSystemOperationRecordTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="exportData"> + <template #icon> + <download-outlined /> + </template> + <span>导出</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'status'"> + <a-tag v-if="record.status === 0" color="green">正常</a-tag> + <a-tag v-else-if="record.status === 1" color="red">异常</a-tag> + </template> + <template v-else-if="column.key === 'action'"> + <a @click="openDetail(record)">详情</a> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 详情弹窗 --> + <operation-record-detail v-model:visible="showInfo" :data="current" /> + </div> +</template> + +<script lang="ts" setup> + import { ref } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { DownloadOutlined } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { utils, writeFile } from 'xlsx'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import OperationRecordSearch from './components/operation-record-search.vue'; + import OperationRecordDetail from './components/operation-record-detail.vue'; + import { + pageOperationRecords, + listOperationRecords + } from '@/api/system/operation-record'; + import type { + OperationRecord, + OperationRecordParam + } from '@/api/system/operation-record/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '账号', + dataIndex: 'username', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '用户名', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '操作模块', + dataIndex: 'module', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '操作功能', + dataIndex: 'description', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '请求地址', + dataIndex: 'url', + sorter: true, + showSorterTooltip: false, + ellipsis: true + }, + { + title: '请求方式', + dataIndex: 'requestMethod', + sorter: true, + showSorterTooltip: false, + width: 100, + align: 'center' + }, + { + title: '状态', + key: 'status', + dataIndex: 'status', + sorter: true, + showSorterTooltip: false, + width: 100, + filters: [ + { + text: '正常', + value: 0 + }, + { + text: '异常', + value: 1 + } + ], + filterMultiple: false, + align: 'center' + }, + { + title: '耗时', + dataIndex: 'spendTime', + sorter: true, + showSorterTooltip: false, + width: 100, + customRender: ({ text }) => text / 1000 + 's' + }, + { + title: '操作时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text), + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 90, + align: 'center', + fixed: 'right' + } + ]); + + // 当前选中数据 + const current = ref<OperationRecord>({ + module: '', + description: '', + url: '', + requestMethod: '', + method: '', + params: '', + result: '', + error: '', + spendTime: 0, + os: '', + device: '', + browser: '', + ip: '', + status: 0, + createTime: '', + nickname: '', + username: '' + }); + + // 是否显示查看弹窗 + const showInfo = ref(false); + + // 表格数据源 + const datasource: DatasourceFunction = ({ + page, + limit, + where, + orders, + filters + }) => { + return pageOperationRecords({ + ...where, + ...orders, + ...filters, + page, + limit + }); + }; + + /* 刷新表格 */ + const reload = (where?: OperationRecordParam) => { + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 详情 */ + const openDetail = (row: OperationRecord) => { + current.value = row; + showInfo.value = true; + }; + + /* 导出数据 */ + const exportData = () => { + const array = [ + [ + '账号', + '用户名', + '操作模块', + '操作功能', + '请求地址', + '请求方式', + '状态', + '耗时', + '操作时间' + ] + ]; + // 请求查询全部(不分页)的接口 + const hide = messageLoading('请求中..', 0); + tableRef.value?.doRequest(({ where, orders, filters }) => { + listOperationRecords({ ...where, ...orders, ...filters }) + .then((data) => { + hide(); + data.forEach((d) => { + array.push([ + d.username, + d.nickname, + d.module, + d.description, + d.url, + d.requestMethod, + ['正常', '异常'][d.status], + d.spendTime / 1000 + 's', + toDateString(d.createTime) + ]); + }); + writeFile( + { + SheetNames: ['Sheet1'], + Sheets: { + Sheet1: utils.aoa_to_sheet(array) + } + }, + '操作日志.xlsx' + ); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemOperationRecord' + }; +</script> diff --git a/src/views/system/role/components/role-auth.vue b/src/views/system/role/components/role-auth.vue new file mode 100644 index 0000000..ae3ff0b --- /dev/null +++ b/src/views/system/role/components/role-auth.vue @@ -0,0 +1,159 @@ +<!-- 角色权限分配弹窗 --> +<template> + <ele-modal + :width="460" + title="分配权限" + :visible="visible" + :confirm-loading="loading" + @update:visible="updateVisible" + @ok="save" + > + <a-spin :spinning="authLoading"> + <div style="height: 60vh" class="ele-scrollbar-hover"> + <a-tree + :checkable="true" + :show-icon="true" + :tree-data="(authData as any)" + v-model:expandedKeys="expandKeys" + v-model:checkedKeys="checkedKeys" + > + <template #icon="{ menuIcon }"> + <component v-if="menuIcon" :is="menuIcon" /> + </template> + </a-tree> + </div> + </a-spin> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, watch, nextTick } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { toTreeData, eachTreeData } from 'ele-admin-pro/es'; + import { listRoleMenus, updateRoleMenus } from '@/api/system/role'; + import type { Role } from '@/api/system/role/model'; + import type { Menu } from '@/api/system/menu/model'; + + const emit = defineEmits<{ + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 当前角色数据 + data?: Role | null; + }>(); + + // 权限数据 + const authData = ref<Menu[]>([]); + + // 权限数据请求状态 + const authLoading = ref(false); + + // 提交状态 + const loading = ref(false); + + // 角色权限展开的keys + const expandKeys = ref<number[]>([]); + + // 角色权限选中的keys + const checkedKeys = ref<number[]>([]); + + /* 查询权限数据 */ + const query = () => { + authData.value = []; + expandKeys.value = []; + checkedKeys.value = []; + if (!props.data) { + return; + } + authLoading.value = true; + listRoleMenus(props.data.roleId) + .then((data) => { + authLoading.value = false; + // 转成树形结构的数据 + authData.value = toTreeData({ + data: data?.map((d) => ({ + ...d, + key: d.menuId, + icon: undefined, + menuIcon: d.icon + })), + idField: 'menuId', + parentIdField: 'parentId', + addParentIds: true, + parentIds: [] + }); + // 全部默认展开以及回显选中的数据 + nextTick(() => { + const eks: number[] = []; + const cks: number[] = []; + eachTreeData(authData.value, (d) => { + if (d.key) { + if (d.children?.length) { + eks.push(d.key); + } else if (d.checked) { + cks.push(d.key); + } + } + }); + expandKeys.value = eks; + checkedKeys.value = cks; + }); + }) + .catch((e) => { + authLoading.value = false; + message.error(e.message); + }); + }; + + /* 保存权限分配 */ + const save = () => { + loading.value = true; + // 获取选中的id,包含所有半选的父级的id + const ids = new Set<number>(); + eachTreeData(authData.value, (d) => { + if (d.key && checkedKeys.value.some((c) => c === d.key)) { + ids.add(d.key); + if (d.parentIds) { + d.parentIds.forEach((id: number) => { + ids.add(id); + }); + } + } + }); + updateRoleMenus(props.data?.roleId, Array.from(ids)) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + query(); + } + } + ); +</script> + +<script lang="ts"> + import * as MenuIcons from '@/layout/menu-icons'; + + export default { + components: MenuIcons + }; +</script> diff --git a/src/views/system/role/components/role-edit.vue b/src/views/system/role/components/role-edit.vue new file mode 100644 index 0000000..b3a5f39 --- /dev/null +++ b/src/views/system/role/components/role-edit.vue @@ -0,0 +1,158 @@ +<!-- 角色编辑弹窗 --> +<template> + <ele-modal + :width="460" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改角色' : '添加角色'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }" + :wrapper-col=" + styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' } + " + > + <a-form-item label="角色名称" name="roleName"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入角色名称" + v-model:value="form.roleName" + /> + </a-form-item> + <a-form-item label="角色标识" name="roleCode"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入角色标识" + v-model:value="form.roleCode" + /> + </a-form-item> + <a-form-item label="备注"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入备注" + v-model:value="form.comments" + /> + </a-form-item> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { addRole, updateRole } from '@/api/system/role'; + import type { Role } from '@/api/system/role/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: Role | null; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + // 表单数据 + const { form, resetFields, assignFields } = useFormData<Role>({ + roleId: undefined, + roleName: '', + roleCode: '', + comments: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + roleName: [ + { + required: true, + message: '请输入角色名称', + type: 'string', + trigger: 'blur' + } + ], + roleCode: [ + { + required: true, + message: '请输入角色标识', + type: 'string', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateRole : addRole; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields(props.data); + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views/system/role/components/role-search.vue b/src/views/system/role/components/role-search.vue new file mode 100644 index 0000000..2169dca --- /dev/null +++ b/src/views/system/role/components/role-search.vue @@ -0,0 +1,106 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="角色名称"> + <a-input + v-model:value.trim="form.roleName" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="角色标识"> + <a-input + v-model:value.trim="form.roleCode" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="备注"> + <a-input + v-model:value.trim="form.comments" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { RoleParam } from '@/api/system/role/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'search', where?: RoleParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<RoleParam>({ + roleName: '', + roleCode: '', + comments: '' + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue new file mode 100644 index 0000000..497d2fb --- /dev/null +++ b/src/views/system/role/index.vue @@ -0,0 +1,212 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <role-search @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="roleId" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 800 }" + cache-key="proSystemRoleTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a @click="openAuth(record)">分配权限</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此角色吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 编辑弹窗 --> + <role-edit v-model:visible="showEdit" :data="current" @done="reload" /> + <!-- 权限分配弹窗 --> + <role-auth v-model:visible="showAuth" :data="current" /> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { messageLoading, toDateString } from 'ele-admin-pro/es'; + import RoleSearch from './components/role-search.vue'; + import RoleEdit from './components/role-edit.vue'; + import RoleAuth from './components/role-auth.vue'; + import { pageRoles, removeRole, removeRoles } from '@/api/system/role'; + import type { Role, RoleParam } from '@/api/system/role/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '角色名称', + dataIndex: 'roleName', + sorter: true, + showSorterTooltip: false + }, + { + title: '角色标识', + dataIndex: 'roleCode', + sorter: true, + showSorterTooltip: false + }, + { + title: '备注', + dataIndex: 'comments', + sorter: true, + showSorterTooltip: false + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '操作', + key: 'action', + width: 200, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<Role[]>([]); + + // 当前编辑数据 + const current = ref<Role | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 是否显示权限分配弹窗 + const showAuth = ref(false); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageRoles({ ...where, ...orders, page, limit }); + }; + + /* 搜索 */ + const reload = (where?: RoleParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: Role) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 打开权限分配弹窗 */ + const openAuth = (row?: Role) => { + current.value = row ?? null; + showAuth.value = true; + }; + + /* 删除单个 */ + const remove = (row: Role) => { + const hide = messageLoading('请求中..', 0); + removeRole(row.roleId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的角色吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeRoles(selection.value.map((d) => d.roleId)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemRole' + }; +</script> diff --git a/src/views/system/user/components/role-select.vue b/src/views/system/user/components/role-select.vue new file mode 100644 index 0000000..04534b9 --- /dev/null +++ b/src/views/system/user/components/role-select.vue @@ -0,0 +1,71 @@ +<!-- 角色选择下拉框 --> +<template> + <a-select + allow-clear + mode="multiple" + :value="roleIds" + :placeholder="placeholder" + @update:value="updateValue" + @blur="onBlur" + > + <a-select-option + v-for="item in data" + :key="item.roleId" + :value="item.roleId" + > + {{ item.roleName }} + </a-select-option> + </a-select> +</template> + +<script lang="ts" setup> + import { ref, computed } from 'vue'; + import { message } from 'ant-design-vue/es'; + import { listRoles } from '@/api/system/role'; + import type { Role } from '@/api/system/role/model'; + + const emit = defineEmits<{ + (e: 'update:value', value: Role[]): void; + (e: 'blur'): void; + }>(); + + const props = withDefaults( + defineProps<{ + // 选中的角色 + value?: Role[]; + // + placeholder?: string; + }>(), + { + placeholder: '请选择角色' + } + ); + + // 选中的角色id + const roleIds = computed(() => props.value?.map((d) => d.roleId as number)); + + // 角色数据 + const data = ref<Role[]>([]); + + /* 更新选中数据 */ + const updateValue = (value: number[]) => { + emit( + 'update:value', + value.map((v) => ({ roleId: v })) + ); + }; + + /* 获取角色数据 */ + listRoles() + .then((list) => { + data.value = list; + }) + .catch((e) => { + message.error(e.message); + }); + + /* 失去焦点 */ + const onBlur = () => { + emit('blur'); + }; +</script> diff --git a/src/views/system/user/components/user-edit.vue b/src/views/system/user/components/user-edit.vue new file mode 100644 index 0000000..a27cbc2 --- /dev/null +++ b/src/views/system/user/components/user-edit.vue @@ -0,0 +1,285 @@ +<!-- 用户编辑弹窗 --> +<template> + <ele-modal + :width="680" + :visible="visible" + :confirm-loading="loading" + :title="isUpdate ? '修改用户' : '新建用户'" + :body-style="{ paddingBottom: '8px' }" + @update:visible="updateVisible" + @ok="save" + > + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col="styleResponsive ? { md: 7, sm: 4, xs: 24 } : { flex: '90px' }" + :wrapper-col=" + styleResponsive ? { md: 17, sm: 20, xs: 24 } : { flex: '1' } + " + > + <a-row :gutter="16"> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="用户账号" name="username"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入用户账号" + v-model:value="form.username" + /> + </a-form-item> + <a-form-item label="用户名" name="nickname"> + <a-input + allow-clear + :maxlength="20" + placeholder="请输入用户名" + v-model:value="form.nickname" + /> + </a-form-item> + <a-form-item label="性别" name="sex"> + <a-select v-model:value="form.sex" :options="sexOption"></a-select> + </a-form-item> + <a-form-item label="角色" name="roles"> + <role-select v-model:value="form.roles" /> + </a-form-item> + <a-form-item label="邮箱" name="email"> + <a-input + allow-clear + :maxlength="100" + placeholder="请输入邮箱" + v-model:value="form.email" + /> + </a-form-item> + </a-col> + <a-col + v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }" + > + <a-form-item label="手机号" name="phone"> + <a-input + allow-clear + :maxlength="11" + placeholder="请输入手机号" + v-model:value="form.phone" + /> + </a-form-item> + <a-form-item label="出生日期"> + <a-date-picker + class="ele-fluid" + value-format="YYYY-MM-DD" + placeholder="请选择出生日期" + v-model:value="form.birthday" + /> + </a-form-item> + <a-form-item v-if="!isUpdate" label="登录密码" name="password"> + <a-input-password + :maxlength="20" + v-model:value="form.password" + placeholder="请输入登录密码" + /> + </a-form-item> + <a-form-item label="个人简介"> + <a-textarea + :rows="4" + :maxlength="200" + placeholder="请输入个人简介" + v-model:value="form.introduction" + /> + </a-form-item> + </a-col> + </a-row> + </a-form> + </ele-modal> +</template> + +<script lang="ts" setup> + import { ref, reactive, watch } from 'vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { emailReg, phoneReg } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import RoleSelect from './role-select.vue'; + import { addUser, updateUser, checkExistence } from '@/api/system/user'; + import type { User } from '@/api/system/user/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const emit = defineEmits<{ + (e: 'done'): void; + (e: 'update:visible', visible: boolean): void; + }>(); + + const props = defineProps<{ + // 弹窗是否打开 + visible: boolean; + // 修改回显的数据 + data?: User | null; + }>(); + + // + const formRef = ref<FormInstance | null>(null); + + // 是否是修改 + const isUpdate = ref(false); + + // 提交状态 + const loading = ref(false); + + const sexOption = ref([ + { + value: '0', + label: '保密', + },{ + value: '1', + label: '男', + },{ + value: '2', + label: '女', + }, + ]) + // 表单数据 + const { form, resetFields, assignFields } = useFormData<User>({ + userId: undefined, + username: '', + nickname: '', + sex: undefined, + roles: [], + email: '', + phone: '', + introduction: '', + birthday: '' + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + username: [ + { + required: true, + type: 'string', + validator: (_rule: Rule, value: string) => { + return new Promise<void>((resolve, reject) => { + if (!value) { + return reject('请输入用户账号'); + } + checkExistence('username', value, props.data?.userId) + .then(() => { + reject('账号已经存在'); + }) + .catch(() => { + resolve(); + }); + }); + }, + trigger: 'blur' + } + ], + nickname: [ + { + required: true, + message: '请输入用户名', + type: 'string', + trigger: 'blur' + } + ], + sex: [ + { + required: true, + message: '请选择性别', + type: 'string', + trigger: 'blur' + } + ], + roles: [ + { + required: true, + message: '请选择角色', + type: 'array', + trigger: 'blur' + } + ], + email: [ + { + pattern: emailReg, + message: '邮箱格式不正确', + type: 'string', + trigger: 'blur' + } + ], + password: [ + { + required: true, + type: 'string', + validator: async (_rule: Rule, value: string) => { + if (isUpdate.value || /^[\S]{5,18}$/.test(value)) { + return Promise.resolve(); + } + return Promise.reject('密码必须为5-18位非空白字符'); + }, + trigger: 'blur' + } + ], + phone: [ + { + pattern: phoneReg, + message: '手机号格式不正确', + type: 'string', + trigger: 'blur' + } + ] + }); + + /* 保存编辑 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + const saveOrUpdate = isUpdate.value ? updateUser : addUser; + saveOrUpdate(form) + .then((msg) => { + loading.value = false; + message.success(msg); + updateVisible(false); + emit('done'); + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }) + .catch(() => {}); + }; + + /* 更新visible */ + const updateVisible = (value: boolean) => { + emit('update:visible', value); + }; + + watch( + () => props.visible, + (visible) => { + if (visible) { + if (props.data) { + assignFields({ + ...props.data, + password: '' + }); + isUpdate.value = true; + } else { + isUpdate.value = false; + } + } else { + resetFields(); + formRef.value?.clearValidate(); + } + } + ); +</script> diff --git a/src/views/system/user/components/user-search.vue b/src/views/system/user/components/user-search.vue new file mode 100644 index 0000000..aef0215 --- /dev/null +++ b/src/views/system/user/components/user-search.vue @@ -0,0 +1,112 @@ +<!-- 搜索表单 --> +<template> + <a-form + :label-col=" + styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' } + " + > + <a-row :gutter="8"> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="用户账号"> + <a-input + v-model:value.trim="form.username" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="用户名"> + <a-input + v-model:value.trim="form.nickname" + placeholder="请输入" + allow-clear + /> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item label="性别"> + <a-select v-model:value="form.sex" placeholder="请选择" allow-clear> + <a-select-option value="0">保密</a-select-option> + <a-select-option value="1">男</a-select-option> + <a-select-option value="2">女</a-select-option> + </a-select> + </a-form-item> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }"> + <a-space> + <a-button type="primary" @click="search">查询</a-button> + <a-button @click="reset">重置</a-button> + </a-space> + </a-form-item> + </a-col> + </a-row> + </a-form> +</template> + +<script lang="ts" setup> + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import type { UserParam } from '@/api/system/user/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const props = defineProps<{ + // 默认搜索条件 + where?: UserParam; + }>(); + + const emit = defineEmits<{ + (e: 'search', where?: UserParam): void; + }>(); + + // 表单数据 + const { form, resetFields } = useFormData<UserParam>({ + username: '', + nickname: '', + sex: undefined, + ...props.where + }); + + /* 搜索 */ + const search = () => { + emit('search', form); + }; + + /* 重置 */ + const reset = () => { + resetFields(); + search(); + }; +</script> diff --git a/src/views/system/user/details/index.vue b/src/views/system/user/details/index.vue new file mode 100644 index 0000000..177bb22 --- /dev/null +++ b/src/views/system/user/details/index.vue @@ -0,0 +1,122 @@ +<template> + <div class="ele-body"> + <a-card title="基本信息" :bordered="false"> + <a-form + class="ele-form-detail" + :label-col=" + styleResponsive ? { md: 2, sm: 4, xs: 6 } : { flex: '90px' } + " + :wrapper-col=" + styleResponsive ? { md: 22, sm: 20, xs: 18 } : { flex: '1' } + " + > + <a-form-item label="账号"> + <div class="ele-text-secondary">{{ form.username }}</div> + </a-form-item> + <a-form-item label="用户名"> + <div class="ele-text-secondary">{{ form.nickname }}</div> + </a-form-item> + <a-form-item label="性别"> + <div class="ele-text-secondary">{{ form.sexName }}</div> + </a-form-item> + <a-form-item label="手机号"> + <div class="ele-text-secondary">{{ form.phone }}</div> + </a-form-item> + <a-form-item label="角色"> + <a-tag v-for="item in form.roles" :key="item.roleId" color="blue"> + {{ item.roleName }} + </a-tag> + </a-form-item> + <a-form-item label="创建时间"> + <div class="ele-text-secondary">{{ form.createTime }}</div> + </a-form-item> + <a-form-item label="状态"> + <a-badge + v-if="typeof form.status === 'number'" + :status="(['processing', 'error'][form.status] as any)" + :text="['正常', '冻结'][form.status]" + /> + </a-form-item> + </a-form> + </a-card> + </div> +</template> + +<script lang="ts" setup> + import { ref, watch, unref } from 'vue'; + import { useRouter } from 'vue-router'; + import { message } from 'ant-design-vue/es'; + import { toDateString } from 'ele-admin-pro/es'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import useFormData from '@/utils/use-form-data'; + import { setPageTabTitle } from '@/utils/page-tab-util'; + import { getUser } from '@/api/system/user'; + import type { User } from '@/api/system/user/model'; + const ROUTE_PATH = '/system/user/details'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const { currentRoute } = useRouter(); + + // 用户信息 + const { form, assignFields } = useFormData<User>({ + userId: undefined, + username: '', + nickname: '', + sexName: '', + phone: '', + roles: [], + createTime: undefined, + status: undefined + }); + + // 请求状态 + const loading = ref(true); + + /* */ + const query = () => { + const { query } = unref(currentRoute); + const id = query.id; + if (!id || form.userId === Number(id)) { + return; + } + loading.value = true; + getUser(Number(id)) + .then((data) => { + loading.value = false; + assignFields({ + ...data, + createTime: toDateString(data.createTime) + }); + // 修改页签标题 + if (unref(currentRoute).path === ROUTE_PATH) { + setPageTabTitle(data.nickname + '的信息'); + } + }) + .catch((e) => { + loading.value = false; + message.error(e.message); + }); + }; + + watch( + currentRoute, + (route) => { + const { path } = unref(route); + if (path !== ROUTE_PATH) { + return; + } + query(); + }, + { immediate: true } + ); +</script> + +<script lang="ts"> + export default { + name: 'SystemUserDetails' + }; +</script> diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue new file mode 100644 index 0000000..51f3c92 --- /dev/null +++ b/src/views/system/user/index.vue @@ -0,0 +1,295 @@ +<template> + <div class="ele-body"> + <a-card :bordered="false"> + <!-- 搜索表单 --> + <user-search :where="defaultWhere" @search="reload" /> + <!-- 表格 --> + <ele-pro-table + ref="tableRef" + row-key="userId" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 1000 }" + :where="defaultWhere" + cache-key="proSystemUserTable" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="openEdit()"> + <template #icon> + <plus-outlined /> + </template> + <span>新建</span> + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + <template #icon> + <delete-outlined /> + </template> + <span>删除</span> + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'nickname'"> + <router-link :to="'/system/user/details?id=' + record.userId"> + {{ record.nickname }} + </router-link> + </template> + <template v-else-if="column.key === 'roles'"> + <a-tag v-for="item in record.roles" :key="item.roleId" color="blue"> + {{ item.roleName }} + </a-tag> + </template> + <template v-else-if="column.key === 'sex'"> + <text v-if="record.sex=='1'">男</text> + <text v-else-if="record.sex=='2'">女</text> + <text v-else>保密</text> + </template> + <template v-else-if="column.key === 'status'"> + <a-switch + :checked="record.status === 0" + @change="(checked: boolean) => editStatus(checked, record)" + /> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="openEdit(record)">修改</a> + <a-divider type="vertical" /> + <a @click="resetPsw(record)">重置密码</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此用户吗?" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </a-card> + <!-- 编辑弹窗 --> + <user-edit v-model:visible="showEdit" :data="current" @done="reload" /> + </div> +</template> + +<script lang="ts" setup> + import { createVNode, ref, reactive } from 'vue'; + import { message, Modal } from 'ant-design-vue/es'; + import { + PlusOutlined, + DeleteOutlined, + ExclamationCircleOutlined + } from '@ant-design/icons-vue'; + import type { EleProTable } from 'ele-admin-pro/es'; + import type { + DatasourceFunction, + ColumnItem + } from 'ele-admin-pro/es/ele-pro-table/types'; + import { toDateString, messageLoading } from 'ele-admin-pro/es'; + import UserSearch from './components/user-search.vue'; + import UserEdit from './components/user-edit.vue'; + import { + pageUsers, + removeUser, + removeUsers, + updateUserStatus, + updateUserPassword + } from '@/api/system/user'; + import type { User, UserParam } from '@/api/system/user/model'; + + // 表格实例 + const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + + // 表格列配置 + const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '用户账号', + dataIndex: 'username', + sorter: true, + showSorterTooltip: false + }, + { + title: '用户名', + key: 'nickname', + dataIndex: 'nickname', + sorter: true, + showSorterTooltip: false + }, + { + title: '性别', + key: 'sex', + dataIndex: 'sex', + width: 80, + align: 'center', + sorter: true, + showSorterTooltip: false + }, + { + title: '手机号', + dataIndex: 'phone', + sorter: true, + showSorterTooltip: false + }, + { + title: '角色', + key: 'roles' + }, + { + title: '创建时间', + dataIndex: 'createTime', + sorter: true, + showSorterTooltip: false, + ellipsis: true, + customRender: ({ text }) => toDateString(text) + }, + { + title: '状态', + key: 'status', + dataIndex: 'status', + sorter: true, + showSorterTooltip: false, + width: 90, + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 200, + align: 'center' + } + ]); + + // 表格选中数据 + const selection = ref<User[]>([]); + + // 当前编辑数据 + const current = ref<User | null>(null); + + // 是否显示编辑弹窗 + const showEdit = ref(false); + + // 是否显示用户导入弹窗 + const showImport = ref(false); + + // 默认搜索条件 + const defaultWhere = reactive({ + username: '', + nickname: '' + }); + + // 表格数据源 + const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageUsers({ ...where, ...orders, page, limit }); + }; + + /* 搜索 */ + const reload = (where?: UserParam) => { + selection.value = []; + tableRef?.value?.reload({ page: 1, where }); + }; + + /* 打开编辑弹窗 */ + const openEdit = (row?: User) => { + current.value = row ?? null; + showEdit.value = true; + }; + + /* 删除单个 */ + const remove = (row: User) => { + const hide = messageLoading('请求中..', 0); + removeUser(row.userId) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + }; + + /* 批量删除 */ + const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + Modal.confirm({ + title: '提示', + content: '确定要删除选中的用户吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + removeUsers(selection.value.map((d) => d.userId)) + .then((msg) => { + hide(); + message.success(msg); + reload(); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 重置用户密码 */ + const resetPsw = (row: User) => { + Modal.confirm({ + title: '提示', + content: '确定要重置此用户的密码为"123456"吗?', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + const hide = messageLoading('请求中..', 0); + updateUserPassword(row.userId) + .then((msg) => { + hide(); + message.success(msg); + }) + .catch((e) => { + hide(); + message.error(e.message); + }); + } + }); + }; + + /* 修改用户状态 */ + const editStatus = (checked: boolean, row: User) => { + const status = checked ? 0 : 1; + updateUserStatus(row.userId, status) + .then((msg) => { + row.status = status; + message.success(msg); + }) + .catch((e) => { + message.error(e.message); + }); + }; +</script> + +<script lang="ts"> + export default { + name: 'SystemUser' + }; +</script> diff --git a/src/views/user/message/components/message-notice.vue b/src/views/user/message/components/message-notice.vue new file mode 100644 index 0000000..0e81b77 --- /dev/null +++ b/src/views/user/message/components/message-notice.vue @@ -0,0 +1,152 @@ +<template> + <div> + <ele-pro-table + ref="tableRef" + row-key="id" + :columns="columns" + :datasource="datasource" + v-model:selection="selection" + :scroll="{ x: 600 }" + > + <template #toolbar> + <a-space> + <a-button type="primary" class="ele-btn-icon" @click="confirmBatch"> + 批量确认 + </a-button> + <a-button + danger + type="primary" + class="ele-btn-icon" + @click="removeBatch" + > + 删除通知 + </a-button> + </a-space> + </template> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'status'"> + <span :class="['ele-text-warning', 'ele-text-info'][record.status]"> + {{ ['未确认', '已确认'][record.status] }} + </span> + </template> + <template v-else-if="column.key === 'action'"> + <a-space> + <a @click="confirm(record)">确认</a> + <a-divider type="vertical" /> + <a-popconfirm + placement="topRight" + title="确定要删除此通知吗" + @confirm="remove(record)" + > + <a class="ele-text-danger">删除</a> + </a-popconfirm> + </a-space> + </template> + </template> + </ele-pro-table> + </div> +</template> + +<script lang="ts" setup> +import { ref } from 'vue'; +import { message } from 'ant-design-vue/es'; +import type { EleProTable } from 'ele-admin-pro/es'; +import { pageNotices } from '@/api/user/message'; +import type { Message } from '@/api/user/message/model'; +import type { + DatasourceFunction, + ColumnItem +} from 'ele-admin-pro/es/ele-pro-table/types'; + +const emit = defineEmits<{ + (e: 'update-data'): void; +}>(); + +// 表格实例 +const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); + +// 表格列配置 +const columns = ref<ColumnItem[]>([ + { + key: 'index', + width: 48, + align: 'center', + fixed: 'left', + hideInSetting: true, + customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0) + }, + { + title: '通知标题', + dataIndex: 'title', + ellipsis: true + }, + { + title: '通知时间', + dataIndex: 'time', + ellipsis: true, + width: 140, + align: 'center' + }, + { + title: '状态', + key: 'status', + width: 90, + align: 'center' + }, + { + title: '操作', + key: 'action', + width: 120, + align: 'center', + hideInSetting: true + } +]); + +// 列表选中数据 +const selection = ref<Message[]>([]); + +// 表格数据源 +const datasource: DatasourceFunction = ({ page, limit, where, orders }) => { + return pageNotices({ ...where, ...orders, page, limit }); +}; + +/* 确认 */ +const confirm = (row: Message) => { + console.log(row); + message.info('点击了确认'); +}; + +/* 删除单个 */ +const remove = (row: Message) => { + console.log(row); + message.info('点击了删除'); + updateUnReadNum(); +}; + +/* 批量删除 */ +const removeBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + message.info('点击了删除'); + updateUnReadNum(); +}; + +/* 批量确认 */ +const confirmBatch = () => { + if (!selection.value.length) { + message.error('请至少选择一条数据'); + return; + } + selection.value.forEach((d) => { + d.status = 1; + }); + updateUnReadNum(); +}; + +/* 触发更新未读数量事件 */ +const updateUnReadNum = () => { + emit('update-data'); +}; +</script> diff --git a/src/views/user/message/index.vue b/src/views/user/message/index.vue new file mode 100644 index 0000000..e837851 --- /dev/null +++ b/src/views/user/message/index.vue @@ -0,0 +1,153 @@ +<template> + <div :class="['ele-body', { 'demo-message-responsive': styleResponsive }]"> + <a-card :bordered="false" :body-style="{ padding: '0px' }"> + <div class="ele-cell ele-cell-align-top ele-user-message"> + <div class="message-menu-wrap"> + <a-menu :selected-keys="active" :mode="mode"> + <a-menu-item key="notice"> + <router-link to="/user/message?type=notice"> + <a-badge v-if="unReadNotice" :count="unReadNotice" /> + <span>系统通知</span> + </router-link> + </a-menu-item> + </a-menu> + </div> + <div class="ele-cell-content" style="overflow-x: hidden"> + <transition name="slide-right" mode="out-in"> + <message-notice + v-if="active.includes('notice')" + @update-data="queryUnReadNum" + /> + </transition> + </div> + </div> + </a-card> + </div> +</template> + +<script lang="ts" setup> +import { ref, watch, unref, computed } from 'vue'; +import { useRouter } from 'vue-router'; +import { storeToRefs } from 'pinia'; +import { message } from 'ant-design-vue/es'; +import { useThemeStore } from '@/store/modules/theme'; +import MessageNotice from './components/message-notice.vue'; +import { getUnReadNum } from '@/api/user/message'; + +const { currentRoute } = useRouter(); +const themeStore = useThemeStore(); +const { screenWidth, styleResponsive } = storeToRefs(themeStore); + +// 导航选中 +const active = ref<string[]>([]); + +// 通知未读数量 +const unReadNotice = ref(0); + +// 导航模式 +const mode = computed(() => { + return styleResponsive.value && screenWidth.value < 768 + ? 'horizontal' + : 'inline'; +}); + +watch( + currentRoute, + (route) => { + const { path, query } = unref(route); + if (path === '/user/message') { + const defaultType = 'notice'; + if (!query.type) { + active.value = [defaultType]; + } else if (typeof query.type === 'string') { + active.value = [query.type || defaultType]; + } else if (query.type.length && query.type[0]) { + active.value = [query.type[0]]; + } else { + active.value = [defaultType]; + } + } + }, + { + immediate: true + } +); + +/* 查询未读数量 */ +const queryUnReadNum = () => { + getUnReadNum() + .then((result) => { + unReadNotice.value = result.notice; + }) + .catch((e) => { + message.error(e.message); + }); +}; + +queryUnReadNum(); +</script> + +<script lang="ts"> +export default { + name: 'UserMessage' +}; +</script> + +<style lang="less" scoped> +.message-menu-wrap { + width: 150px; + display: flex; + + :deep(.ant-menu) { + padding-top: 16px; + + .ant-badge { + vertical-align: -2px; + margin-right: 10px; + } + + .ant-badge-count { + height: 16px; + line-height: 16px; + border-radius: 8px; + box-shadow: none; + min-width: 16px; + padding: 0 2px; + } + + .ant-scroll-number-only { + height: 16px; + + & > p.ant-scroll-number-only-unit { + height: 16px; + } + } + } + + & + .ele-cell-content { + padding: 16px 24px; + overflow: auto; + } +} + +@media screen and (max-width: 768px) { + .demo-message-responsive { + .ele-user-message { + display: block; + + & > .ele-cell-content { + padding: 16px 16px; + } + } + + .message-menu-wrap { + width: auto; + display: block; + + :deep(.ant-menu) { + padding-top: 0; + } + } + } +} +</style> diff --git a/src/views/user/profile/index.vue b/src/views/user/profile/index.vue new file mode 100644 index 0000000..7eb17ae --- /dev/null +++ b/src/views/user/profile/index.vue @@ -0,0 +1,366 @@ +<template> + <div class="ele-body ele-body-card"> + <a-row :gutter="16"> + <a-col + v-bind=" + styleResponsive + ? { xxl: 6, xl: 7, lg: 9, md: 10, sm: 24, xs: 24 } + : { span: 6 } + " + > + <a-card :bordered="false"> + <div class="ele-text-center"> + <div class="user-info-avatar-group" @click="openCropper"> + <a-avatar :size="110" :src="form.avatar" /> + <upload-outlined class="user-info-avatar-icon" /> + </div> + <h1>{{ loginUser.nickname }}</h1> + <div>{{ loginUser.introduction }}</div> + </div> + </a-card> + </a-col> + <a-col + v-bind=" + styleResponsive + ? { xxl: 18, xl: 17, lg: 15, md: 14, sm: 24, xs: 24 } + : { span: 18 } + " + > + <a-card + :bordered="false" + :body-style="{ paddingTop: '0px', minHeight: '600px' }" + > + <a-tabs v-model:active-key="active" size="large"> + <a-tab-pane tab="基本信息" key="info"> + <a-form + ref="formRef" + :model="form" + :rules="rules" + :label-col=" + styleResponsive + ? { lg: 4, md: 6, sm: 4, xs: 24 } + : { flex: '100px' } + " + :wrapper-col=" + styleResponsive + ? { lg: 20, md: 18, sm: 20, xs: 24 } + : { flex: '1' } + " + style="max-width: 580px; margin-top: 20px" + > + <a-form-item label="昵称" name="nickname"> + <a-input + v-model:value="form.nickname" + placeholder="请输入昵称" + allow-clear + /> + </a-form-item> + <a-form-item label="性别" name="sex"> + <a-select + v-model:value="form.sex" + placeholder="请选择性别" + allow-clear + > + <a-select-option value="0">保密</a-select-option> + <a-select-option value="1">男</a-select-option> + <a-select-option value="2">女</a-select-option> + </a-select> + </a-form-item> + <a-form-item label="邮箱" name="email"> + <a-input + v-model:value="form.email" + placeholder="请输入邮箱" + allow-clear + /> + </a-form-item> + <a-form-item label="个人简介"> + <a-textarea + v-model:value="form.introduction" + placeholder="请输入个人简介" + :rows="4" + /> + </a-form-item> + <a-form-item label="联系电话:"> + <div class="ele-cell"> + <div class="ele-cell-content"> + <a-input + v-model:value="form.phone" + placeholder="请输入联系电话" + allow-clear + /> + </div> + </div> + </a-form-item> + <a-form-item + :wrapper-col=" + styleResponsive + ? { + lg: { offset: 4 }, + md: { offset: 6 }, + sm: { offset: 4 } + } + : { offset: 4 } + " + > + <a-button type="primary" :loading="loading" @click="save"> + {{ loading ? '保存中..' : '保存更改' }} + </a-button> + </a-form-item> + </a-form> + </a-tab-pane> + <a-tab-pane tab="账号绑定" key="account"> + <div class="user-account-list"> + <div class="ele-cell"> + <wechat-outlined class="user-account-icon" /> + <div class="ele-cell-content"> + <div class="ele-cell-title">绑定微信</div> + <div class="ele-cell-desc">当前未绑定绑定微信账号</div> + </div> + <div @click="showQR">去绑定</div> + </div> + </div> + </a-tab-pane> + </a-tabs> + </a-card> + </a-col> + </a-row> + <!-- 头像裁剪弹窗 --> + <ele-cropper-modal + :src="form.avatar" + v-model:visible="visible" + :to-blob="true" + :options="{ autoCropArea: 1, viewMode: 1, dragMode: 'move' }" + @done="onCrop" + /> + + <ele-modal v-model:visible="qrCodeShow" title="扫码绑定微信" width="240px"> + <template #footer> + <a-button @click="handleCancel">关闭</a-button> + </template> + <ele-qr-code :value="QrText" :size="190" :margin="0" /> + <a-qrcode :value="QrText" /> + </ele-modal> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive, computed } from 'vue'; + import { UploadOutlined, WechatOutlined } from '@ant-design/icons-vue'; + import { message } from 'ant-design-vue/es'; + import type { FormInstance, Rule } from 'ant-design-vue/es/form'; + import { useUserStore } from '@/store/modules/user'; + import { storeToRefs } from 'pinia'; + import { useThemeStore } from '@/store/modules/theme'; + import { updateUser, uploadAvatar } from '@/api/user/profile'; + import { ProfileForm } from '@/api/user/profile/model'; + + // 是否开启响应式布局 + const themeStore = useThemeStore(); + const { styleResponsive } = storeToRefs(themeStore); + + const userStore = useUserStore(); + + // + const formRef = ref<FormInstance | null>(null); + + // tab 页选中 + const active = ref('info'); + + // 保存按钮 loading + const loading = ref(false); + + // 是否显示裁剪弹窗 + const visible = ref(false); + + // 登录用户信息 + const loginUser = computed(() => userStore.info ?? {}); + + // 表单数据 + const form = reactive<ProfileForm>({ + userId: loginUser.value.userId, + nickname: loginUser.value.nickname, + sex: loginUser.value.sex, + email: loginUser.value.email, + introduction: loginUser.value.introduction, + phone: loginUser.value.phone, + avatar: loginUser.value.avatar + }); + + // 表单验证规则 + const rules = reactive<Record<string, Rule[]>>({ + nickname: [ + { + required: true, + message: '请输入昵称', + type: 'string' + } + ], + sex: [ + { + required: true, + message: '请选择性别', + type: 'string' + } + ], + email: [ + { + required: true, + message: '请输入邮箱', + type: 'string' + } + ] + }); + + const QrText = ref('http://test.cqtlcm.com/api/postCode'); + const qrCodeShow = ref(false); + + const showQR = () => { + qrCodeShow.value = true; + }; + + const handleCancel = () => { + qrCodeShow.value = false; + }; + + /* 修改登录用户 */ + const updateLoginUser = (obj: Record<string, any>) => { + userStore.setInfo({ ...loginUser.value, ...obj }); + }; + + /* 保存更改 */ + const save = () => { + if (!formRef.value) { + return; + } + formRef.value + .validate() + .then(() => { + loading.value = true; + updateUser(form).then((res) => { + if (res.code === 0) { + message.success(res.message); + loading.value = false; + } else { + message.error('修改失败!'); + loading.value = false; + } + }); + }) + .catch(() => {}); + }; + + /* 头像裁剪完成回调 */ + const onCrop = (blob: Blob) => { + visible.value = false; + const formData = new FormData(); + formData.append('file', blob); + uploadAvatar(formData).then((res: any) => { + form.avatar = res.data; + }); + updateLoginUser(form); + }; + + /* 打开图片裁剪 */ + const openCropper = () => { + visible.value = true; + }; +</script> + +<script lang="ts"> + export default { + name: 'UserProfile' + }; +</script> + +<style lang="less" scoped> + /* 用户资料卡片 */ + .user-info-avatar-group { + margin: 16px 0; + display: inline-block; + position: relative; + cursor: pointer; + + .user-info-avatar-icon { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #fff; + font-size: 30px; + display: none; + z-index: 2; + } + + &:hover .user-info-avatar-icon { + display: block; + } + + &:after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 50%; + background-color: transparent; + transition: background-color 0.3s; + } + + &:hover:after { + background-color: rgba(0, 0, 0, 0.4); + } + + & + h1 { + margin-bottom: 8px; + } + } + + /* 用户信息列表 */ + .user-info-list { + margin: 47px 0 32px 0; + + .ele-cell + .ele-cell { + margin-top: 16px; + } + + & + .ant-divider { + margin-bottom: 16px; + } + } + + /* 用户标签 */ + .user-info-tags { + margin: 16px 0 4px 0; + + .ant-tag { + margin: 0 12px 8px 0; + } + } + + /* 用户账号绑定列表 */ + .user-account-list { + & > .ele-cell { + padding: 16px 8px; + } + + .user-account-icon { + color: #fff; + padding: 8px; + font-size: 26px; + border-radius: 50%; + + &.anticon-qq { + background: #3492ed; + } + + &.anticon-wechat { + background: #4daf29; + } + + &.anticon-alipay { + background: #1476fe; + } + } + } +</style> diff --git a/src/views/wechat/menu/index.vue b/src/views/wechat/menu/index.vue new file mode 100644 index 0000000..a0b2d3d --- /dev/null +++ b/src/views/wechat/menu/index.vue @@ -0,0 +1,1218 @@ +<template> + <div> + <a-page-header :ghost="false" title="自定义菜单"> + <div class="ele-text-secondary"> + 微信自定义菜单的扩展,不用登录微信平台即可修改微信菜单 + </div> + </a-page-header> + <div class="wechat-body"> + <a-card :bordered="false"> + <div class="content" style="width: 900px; margin: 0 auto"> + <a-row justify="center" :gutter="24"> + <a-col :span="10"> + <div class="weixin-preview"> + <div class="weixin-hd"> + <div class="weixin-title">{{ weixinTitle }}</div> + </div> + <div class="weixin-bd"> + <div class="weixin-menu" id="weixin-menu"> + <div + v-for="(btn, i) in menu.button" + class="menu-item" + :key="i" + :class="{ + current: + selectedMenuIndex === i && selectedMenuLevel() === 1 + }" + @click="selectedMenu(i)" + > + <div class="menu-item-title"> + <pause-circle-outlined + :rotate="90" + style="font-size: 10px" + /> + <span>{{ btn.name }}</span> + </div> + <div + class="weixin-sub-menu" + v-show="selectedMenuIndex === i" + > + <div + v-for="(sub, i2) in btn.sub_button" + class="menu-sub-item" + :key="i2" + :class="{ + current: + selectedSubMenuIndex === i2 && + selectedMenuLevel() === 2 + }" + @click.stop="selectedSubMenu(i2)" + > + <div class="menu-item-title"> + <span>{{ sub.name }}</span> + </div> + </div> + <div + v-if="btn.sub_button.length < 5" + class="menu-sub-item" + @click.stop="addMenu(2)" + > + <div class="menu-item-title"> + <plus-outlined /> + </div> + </div> + <i class="menu-arrow arrow_out"></i> + <i class="menu-arrow arrow_in"></i> + </div> + </div> + <template v-if="menu.button.length < 3"> + <div class="menu-item" @click="addMenu(1)"> + <plus-outlined /> + </div> + </template> + </div> + </div> + </div> + </a-col> + <a-col :span="14"> + <div class="weixin-menu-detail" v-if="selectedMenuLevel() === 1"> + <div + class="menu-input-group" + style="border-bottom: 2px #e8e8e8 solid" + > + <div class="menu-name"> + {{ menu.button[selectedMenuIndex].name }} + </div> + <div class="menu-del" @click="delMenu">删除菜单</div> + </div> + <div class="menu-input-group"> + <div class="menu-label">菜单名称</div> + <div class="menu-input"> + <a-input + placeholder="请输入菜单名称" + class="menu-input-text" + v-model:value="menu.button[selectedMenuIndex].name" + @change=" + checkMenuName(menu.button[selectedMenuIndex].name) + " + /> + <p + class="menu-tips" + style="color: #e15f63" + v-show="menuNameBounds" + >字数超过上限</p + > + <p class="menu-tips">字数不超过4个汉字或8个字母</p> + </div> + </div> + <template + v-if="menu.button[selectedMenuIndex].sub_button.length === 0" + > + <div class="menu-input-group"> + <div class="menu-label">菜单内容</div> + <div class="menu-input"> + <a-select + v-model:value="menu.button[selectedMenuIndex].type" + name="type" + class="menu-input-text" + > + <a-select-option value="view" + >跳转网页(view)</a-select-option + > + <a-select-option value="media_id" + >发送消息(media_id)</a-select-option + > + <a-select-option value="miniprogram" + >打开指定小程序(miniprogram)</a-select-option + > + <a-select-option value="click" + >自定义点击事件(click)</a-select-option + > + <a-select-option value="scancode_push" + >扫码上传消息(scancode_push)</a-select-option + > + <a-select-option value="scancode_waitmsg" + >扫码提示下发(scancode_waitmsg)</a-select-option + > + <a-select-option value="pic_sysphoto" + >系统相机拍照(pic_sysphoto)</a-select-option + > + <a-select-option value="pic_photo_or_album" + >弹出拍照或者相册(pic_photo_or_album)</a-select-option + > + <a-select-option value="pic_weixin" + >弹出微信相册(pic_weixin)</a-select-option + > + <a-select-option value="location_select" + >弹出地理位置选择器(location_select)</a-select-option + > + </a-select> + </div> + </div> + <a-card + class="menu-content" + v-if="selectedMenuType() === 1" + style="padding: 0" + > + <div class="menu-input-group"> + <p class="menu-tips">订阅者点击该子菜单会跳到以下链接</p> + <div> + <div class="menu-label">页面地址</div> + <div class="menu-input"> + <a-input + placeholder="请输入页面地址" + class="menu-input-text" + v-model:value="menu.button[selectedMenuIndex].url" + /> + <p class="menu-tips cursor" @click="selectNewsUrl" + >从公众号图文消息中选择</p + > + </div> + </div> + </div> + </a-card> + <div + class="menu-msg-content" + v-else-if="selectedMenuType() === 2" + > + <div class="menu-msg-head"> + <i class="icon_msg_sender"></i> + 图文消息 + </div> + <div + class="menu-msg-panel" + v-if="menu.button[selectedMenuIndex].media_id" + > + <div class="menu-msg-select"> + <div class="menu-msg-title"> + <i class="icon_msg_sender"></i> + {{ material.title }} + </div> + <a + :href="material.url" + target="_blank" + class="el-button el-button--mini" + >查看</a + > + <a-button + size="small" + type="primary" + danger + @click="delMaterialId" + >删除</a-button + > + </div> + </div> + <div class="menu-msg-panel" v-else> + <div class="menu-msg-select" @click="selectMaterialId"> + <i class="icon36_common add_gray"></i> + <strong>从素材库中选择</strong> + </div> + </div> + </div> + <div + class="menu-content" + v-else-if="selectedMenuType() === 3" + > + <div class="menu-input-group"> + <p class="menu-tips">用于消息接口推送,不超过128字节</p> + <div class="menu-label">菜单KEY值</div> + <div class="menu-input"> + <a-input + placeholder="" + class="menu-input-text" + v-model:value="menu.button[selectedMenuIndex].key" + /> + </div> + </div> + </div> + <div + class="menu-content" + v-else-if="selectedMenuType() === 4" + > + <div class="menu-input-group"> + <p class="menu-tips" + >订阅者点击该子菜单会跳到以下小程序</p + > + <div class="menu-label">小程序APPID</div> + <div class="menu-input"> + <a-input + placeholder="小程序的appid(仅认证公众号可配置)" + class="menu-input-text" + v-model:value="menu.button[selectedMenuIndex].appid" + /> + </div> + </div> + <div class="menu-input-group"> + <div class="menu-label">小程序路径</div> + <div class="menu-input"> + <a-input + placeholder="小程序的页面路径 pages/Index/index" + class="menu-input-text" + v-model="menu.button[selectedMenuIndex].pagepath" + /> + </div> + </div> + <div class="menu-input-group"> + <div class="menu-label">备用网页</div> + <div class="menu-input"> + <a-input + placeholder="" + class="menu-input-text" + v-model="menu.button[selectedMenuIndex].url" + /> + <p class="menu-tips"> + 旧版微信客户端无法支持小程序,用户点击菜单时将会打开备用网页。 + </p> + </div> + </div> + </div> + </template> + </div> + <!-- 子菜单 --> + <div class="weixin-menu-detail" v-if="selectedMenuLevel() === 2"> + <div + class="menu-input-group" + style="border-bottom: 2px #e8e8e8 solid" + > + <div class="menu-name"> + {{ + menu.button[selectedMenuIndex].sub_button[ + selectedSubMenuIndex + ].name + }} + </div> + <div class="menu-del" @click="delMenu">删除子菜单</div> + </div> + <div class="menu-input-group"> + <div class="menu-label">子菜单名称</div> + <div class="menu-input"> + <a-input + placeholder="请输入子菜单名称" + class="menu-input-text" + v-model:value=" + menu.button[selectedMenuIndex].sub_button[ + selectedSubMenuIndex + ].name + " + @change=" + checkMenuName( + menu.button[selectedMenuIndex].sub_button[ + selectedSubMenuIndex + ].name + ) + " + /> + <p + class="menu-tips" + style="color: #e15f63" + v-show="menuNameBounds" + > + 字数超过上限 + </p> + <p class="menu-tips">字数不超过8个汉字或16个字母</p> + </div> + </div> + <div class="menu-input-group"> + <div class="menu-label">子菜单内容</div> + <div class="menu-input"> + <a-select + v-model:value=" + menu.button[selectedMenuIndex].sub_button[ + selectedSubMenuIndex + ].type + " + name="type" + class="menu-input-text" + > + <a-select-option value="view" + >跳转网页(view)</a-select-option + > + <a-select-option value="media_id" + >发送消息(media_id)</a-select-option + > + <a-select-option value="miniprogram" + >打开指定小程序(miniprogram)</a-select-option + > + <a-select-option value="click" + >自定义点击事件(click)</a-select-option + > + <a-select-option value="scancode_push" + >扫码上传消息(scancode_push)</a-select-option + > + <a-select-option value="scancode_waitmsg" + >扫码提示下发(scancode_waitmsg)</a-select-option + > + <a-select-option value="pic_sysphoto" + >系统相机拍照(pic_sysphoto)</a-select-option + > + <a-select-option value="pic_photo_or_album" + >弹出拍照或者相册(pic_photo_or_album)</a-select-option + > + <a-select-option value="pic_weixin" + >弹出微信相册(pic_weixin)</a-select-option + > + <a-select-option value="location_select" + >弹出地理位置选择器(location_select)</a-select-option + > + </a-select> + </div> + </div> + <div class="menu-content" v-if="selectedMenuType() === 1"> + <div class="menu-input-group"> + <p class="menu-tips">订阅者点击该子菜单会跳到以下链接</p> + <div class="menu-label">页面地址</div> + <div class="menu-input"> + <a-input + placeholder="请输入页面地址" + class="menu-input-text" + v-model:value=" + menu.button[selectedMenuIndex].sub_button[ + selectedSubMenuIndex + ].url + " + /> + <p class="menu-tips cursor" @click="selectNewsUrl" + >从公众号图文消息中选择</p + > + </div> + </div> + </div> + <div + class="menu-msg-content" + v-else-if="selectedMenuType() === 2" + > + <div class="menu-msg-head"> + <i class="icon_msg_sender"></i> + 图文消息 + </div> + <div + class="menu-msg-panel" + v-if=" + menu.button[selectedMenuIndex].sub_button[ + selectedSubMenuIndex + ].media_id + " + > + <div class="menu-msg-select"> + <i class="icon_msg_sender"></i> + <span>{{ material.title }}</span> + <a + :href="material.url" + target="_blank" + class="el-button el-button--mini" + >查看</a + > + <a-button + size="small" + type="primary" + danger + @click="delMaterialId" + >删除</a-button + > + </div> + </div> + <div class="menu-msg-panel" v-else> + <div class="menu-msg-select" @click="selectMaterialId"> + <i class="icon36_common add_gray"></i> + <strong>从素材库中选择</strong> + </div> + </div> + </div> + <div class="menu-content" v-else-if="selectedMenuType() === 3"> + <div class="menu-input-group"> + <p class="menu-tips">用于消息接口推送,不超过128字节</p> + <div class="menu-label">菜单KEY值</div> + <div class="menu-input"> + <a-input + placeholder="" + class="menu-input-text" + v-model:value=" + menu.button[selectedMenuIndex].sub_button[ + selectedSubMenuIndex + ].key + " + /> + </div> + </div> + </div> + <div class="menu-content" v-else-if="selectedMenuType() === 4"> + <div class="menu-input-group"> + <p class="menu-tips">订阅者点击该子菜单会跳到以下小程序</p> + <div class="menu-label">小程序APPID</div> + <div class="menu-input"> + <a-input + placeholder="小程序的appid(仅认证公众号可配置)" + class="menu-input-text" + v-model:value=" + menu.button[selectedMenuIndex].sub_button[ + selectedSubMenuIndex + ].appid + " + /> + </div> + </div> + <div class="menu-input-group"> + <div class="menu-label">小程序路径</div> + <div class="menu-input"> + <a-input + placeholder="小程序的页面路径 pages/Index/index" + class="menu-input-text" + v-model:value=" + menu.button[selectedMenuIndex].sub_button[ + selectedSubMenuIndex + ].pagepath + " + /> + </div> + </div> + <div class="menu-input-group"> + <div class="menu-label">备用网页</div> + <div class="menu-input"> + <a-input + placeholder="" + class="menu-input-text" + v-model:value=" + menu.button[selectedMenuIndex].sub_button[ + selectedSubMenuIndex + ].url + " + /> + <p class="menu-tips" + >旧版微信客户端无法支持小程序,用户点击菜单时将会打开备用网页。</p + > + </div> + </div> + </div> + </div> + </a-col> + </a-row> + + <div class="weixin-btn-group"> + <a-button type="primary" @click="onMenuSubmit">发布</a-button> + </div> + <a-modal title="选择图文" v-model:visible="newsDialog"> + <a-table :data-source="newsList" size="small" :pagination="{total:newsListTotal,pageSize:10}" :columns="newsTableHeader"> + <template #bodyCell="{ column ,row }"> + <a-button + v-if="column.key === 'action'" + type="primary" size="small" + @click="setNewsUrl(row)" + >选择</a-button> + </template> + </a-table> + </a-modal> + <a-modal title="选择素材" v-model:visible="materialDialog"> + <a-table :data-source="materialList" :columns="materialListColumns"> + <template #bodyCell="{ row }"> + <div + v-for="(item, index) in row.content.news_item" + :key="index" + > + ({{ index + 1 }}).{{ item.title }} + </div> + </template> + <template #default="{ row }"> + <a-button + type="primary" + size="small" + @click="setMaterialId(row)" + >选择</a-button + > + </template> + </a-table> + </a-modal> + </div> + </a-card> + </div> + </div> +</template> + +<script lang="ts" setup> + import { ref, reactive, createVNode } from 'vue'; + import { WxMenuForm } from '@/api/wechat/model'; + import { + addWxMenu, + getWxMaterial, + getWxMaterialList, + getWxMenu + } from '@/api/wechat'; + import { message, Modal } from 'ant-design-vue'; + import { + ExclamationCircleOutlined, + PauseCircleOutlined, + PlusOutlined + } from '@ant-design/icons-vue'; + + const weixinTitle = ref('公众号'); + const selectedMenuIndex = ref<any>(0); //当前选中菜单索引 + const selectedSubMenuIndex = ref<any>(''); //当前选中子菜单索引 + //当前菜单 + const menu = reactive<WxMenuForm>({ + button: [ + { + name: '菜单名称', + type: '', + url: '', + sub_button: [{ name: '子菜单名称', type: '', url: '' }] + } + ] + }); + + const menuNameBounds = ref<boolean>(false); //菜单长度是否过长 + const material = reactive({ + title: '', + url: '', + thumb_url: '' + }); + const materialLoading = ref<boolean>(false); + const materialDialog = ref<boolean>(false); + const materialList = ref([]); + const materialListOffset = ref(0); + const materialListTotal = ref(0); + const newsDialog = ref<boolean>(false); + const newsList = ref([]); + const newsListOffset = ref(0); + const newsListTotal = ref(0); + const newsTableHeader = [ + { + title: '图文标题', + dataIndex: 'content.news_item[0].title' + }, + { + title: '日期', + dataIndex: 'update_time' + }, + { + title: '操作', + key:'action' + } + ]; + const materialListColumns = [ + { + title: '姓名', + dataIndex: 'name', + key: 'name', + }, + { + title: '年龄', + dataIndex: 'age', + key: 'age', + }, + { + title: '住址', + dataIndex: 'address', + key: 'address', + }, + ]; + const queryMenu = () => { + getWxMenu().then((res) => { + Object.assign(menu.button, res.menu.button); + }); + }; + //选中主菜单 + const selectedMenu = (i) => { + selectedSubMenuIndex.value = ''; + selectedMenuIndex.value = i; + let selectedMenu = menu.button[selectedMenuIndex.value]; + //清空选中media_id 防止再次请求 + if (selectedMenu.media_id && selectedMenuType() == 2) { + getMaterial(selectedMenu.media_id); + } + //检查名称长度 + checkMenuName(selectedMenu.name); + }; + //选中子菜单 + const selectedSubMenu = (i) => { + selectedSubMenuIndex.value = i; + let selectedSubMenu = + menu.button[selectedMenuIndex.value].sub_button[ + selectedSubMenuIndex.value + ]; + if (selectedSubMenu.media_id && selectedMenuType() == 2) { + getMaterial(selectedSubMenu.media_id); + } + checkMenuName(selectedSubMenu.name); + }; + //选中菜单级别 + const selectedMenuLevel = () => { + if (selectedMenuIndex.value !== '' && selectedSubMenuIndex.value === '') { + //主菜单 + return 1; + } else if ( + selectedMenuIndex.value !== '' && + selectedSubMenuIndex.value !== '' + ) { + //子菜单 + return 2; + } else { + //未选中任何菜单 + return 0; + } + }; + //获取菜单类型 1. view网页类型,2. media_id类型和view_limited类型 3. click点击类型,4.miniprogram表示小程序类型 + + const selectedMenuType = () => { + if ( + selectedMenuLevel() == 1 && + menu.button[selectedMenuIndex.value].sub_button.length == 0 + ) { + //主菜单 + switch (menu.button[selectedMenuIndex.value].type) { + case 'view': + return 1; + case 'media_id': + return 2; + case 'view_limited': + return 2; + case 'click': + return 3; + case 'scancode_push': + return 3; + case 'scancode_waitmsg': + return 3; + case 'pic_sysphoto': + return 3; + case 'pic_photo_or_album': + return 3; + case 'pic_weixin': + return 3; + case 'location_select': + return 3; + case 'miniprogram': + return 4; + } + } else if (selectedMenuLevel() == 2) { + //子菜单 + switch ( + menu.button[selectedMenuIndex.value].sub_button[ + selectedSubMenuIndex.value + ].type + ) { + case 'view': + return 1; + case 'media_id': + return 2; + case 'view_limited': + return 2; + case 'click': + return 3; + case 'scancode_push': + return 3; + case 'scancode_waitmsg': + return 3; + case 'pic_sysphoto': + return 3; + case 'pic_photo_or_album': + return 3; + case 'pic_weixin': + return 3; + case 'location_select': + return 3; + case 'miniprogram': + return 4; + } + } else { + return 1; + } + }; + //添加菜单 + const addMenu = (level) => { + if (level == 1 && menu.button.length < 3) { + menu.button.push({ + type: 'view', + name: '菜单名称', + sub_button: [], + url: '' + }); + selectedMenuIndex.value = menu.button.length - 1; + selectedSubMenuIndex.value = ''; + } + if ( + level == 2 && + menu.button[selectedMenuIndex.value].sub_button.length < 5 + ) { + menu.button[selectedMenuIndex.value].sub_button.push({ + type: 'view', + name: '子菜单名称', + url: '' + }); + selectedSubMenuIndex.value = + menu.button[selectedMenuIndex.value].sub_button.length - 1; + } + }; + //删除菜单 + const delMenu = () => { + if (selectedMenuLevel() == 1) { + Modal.confirm({ + title: '警告', + content: '删除后菜单下设置的内容将被删除', + icon: createVNode(ExclamationCircleOutlined), + maskClosable: true, + onOk: () => { + if (selectedMenuIndex.value === 0) { + menu.button.splice(selectedMenuIndex.value, 1); + selectedMenuIndex.value = 0; + } else { + menu.button.splice(selectedMenuIndex.value, 1); + selectedMenuIndex.value -= 1; + } + if (menu.button.length == 0) { + selectedMenuIndex.value = ''; + } + }, + onCancel: () => { + return; + } + }); + } else if (selectedMenuLevel() == 2) { + if (selectedSubMenuIndex.value === 0) { + menu.button[selectedMenuIndex.value].sub_button.splice( + selectedSubMenuIndex.value, + 1 + ); + selectedSubMenuIndex.value = 0; + } else { + menu.button[selectedMenuIndex.value].sub_button.splice( + selectedSubMenuIndex.value, + 1 + ); + selectedSubMenuIndex.value -= 1; + } + if (menu.button[selectedMenuIndex.value].sub_button.length == 0) { + selectedSubMenuIndex.value = ''; + } + } + }; + //检查菜单名称长度 + const checkMenuName = (val) => { + if (selectedMenuLevel() == 1 && getMenuNameLen(val) <= 16) { + menuNameBounds.value = false; + } else + menuNameBounds.value = !( + selectedMenuLevel() == 2 && getMenuNameLen(val) <= 32 + ); + }; + //获取菜单名称长度 + const getMenuNameLen = (val) => { + let len = 0; + for (let i = 0; i < val.length; i++) { + const a = val.charAt(i); + a.match(/[^\x00-\xff]/gi) != null ? (len += 2) : (len += 1); + } + return len; + }; + //选择公众号素材库素材 + const selectMaterialId = () => { + materialDialog.value = true; + getMaterialList(); + }; + //选择公众号图文链接 + const selectNewsUrl = () => { + newsDialog.value = true; + getNewsList(); + }; + //设置选择的素材id + // const setMaterialId = (row) => { + // let { media_id, content } = row; + // if (selectedMenuLevel() == 1) { + // menu.button[selectedMenuIndex.value].media_id = media_id; + // } else if (selectedMenuLevel() == 2) { + // menu.button[selectedMenuIndex.value].sub_button[ + // selectedSubMenuIndex.value + // ].media_id = media_id; + // } + // let { news_item } = content; + // let item = news_item[0]; + // material.title = item.title; + // material.url = item.url; + // materialDialog.value = false; + // }; + //删除选择的素材id + const delMaterialId = () => { + if (selectedMenuLevel() == 1) { + menu.button[selectedMenuIndex.value].media_id = ''; + } else if (selectedMenuLevel() == 2) { + menu.button[selectedMenuIndex.value].sub_button[ + selectedSubMenuIndex.value + ].media_id = ''; + } + }; + //设置选择的图文链接 + const setNewsUrl = (row) => { + let { url } = row; + if (selectedMenuLevel() == 1) { + menu.button[selectedMenuIndex.value].url = url; + } else if (selectedMenuLevel() == 2) { + menu.button[selectedMenuIndex.value].sub_button[ + selectedSubMenuIndex.value + ].url = url; + } + newsDialog.value = false; + }; + //获取永久素材信息 + const getMaterial = (id) => { + materialLoading.value = true; + getWxMaterial(id).then((res) => { + material.title = res.data.news_item[0].title; + material.url = res.data.news_item[0].url; + }); + materialLoading.value = false; + }; + //获取永久图文列表 + const getNewsList = () => { + if ( + newsListOffset.value > 0 && + newsListOffset.value >= newsListOffset.value + ) { + return; + } + let params = { + type: 'news', + offset: newsListOffset.value, + count: 10 + }; + getWxMaterialList(params).then((res: any) => { + newsList.value = newsList.value.concat(res.item); + newsListOffset.value += res.item_count; + newsListTotal.value = res.total_count; + }); + }; + //获取永久素材列表 + const getMaterialList = () => { + if ( + materialListOffset.value > 0 && + materialListOffset.value >= materialListTotal.value + ) { + return; + } + let params = { + type: 'image', + offset: newsListOffset.value, + count: 10 + }; + getWxMaterialList(params).then((res:any) => { + console.log(res.item) + materialList.value = materialList.value.concat(res.item); + materialListOffset.value += res.item_count; + materialListTotal.value = res.total_count; + }); + }; + //提交自定义菜单 + const onMenuSubmit = () => { + addWxMenu(menu).then((res) => { + if (res.errcode === 0) { + message.success('修改成功!'); + } else { + message.error('修改失败!'); + } + }); + }; + queryMenu(); +</script> + +<script lang="ts"> + export default { + name: 'WeixinMenu' + }; +</script> + +<style lang="less" scoped> + .weixin-preview { + position: relative; + width: 320px; + height: 540px; + float: left; + margin-right: 10px; + border: 1px solid #e7e7eb; + .weixin-hd { + color: #fff; + text-align: center; + position: relative; + top: 0; + left: 0; + width: 320px; + height: 64px; + background: transparent url('@/assets/menu_head.png') no-repeat 0 0; + .weixin-title { + color: #fff; + font-size: 15px; + width: 100%; + text-align: center; + position: absolute; + top: 33px; + left: 0; + } + } + .weixin-menu { + position: absolute; + bottom: 0; + left: 0; + right: 0; + border-top: 1px solid #e7e7e7; + background: transparent url('@/assets/menu_foot.png') no-repeat 0 0; + padding-left: 43px; + margin-bottom: 0; + .menu-item { + position: relative; + float: left; + line-height: 50px; + height: 50px; + text-align: center; + width: 33.33%; + padding: 0; + border-left: 1px solid #e7e7e7; + border-right: 1px solid #e7e7e7; + cursor: pointer; + color: #616161; + .menu-item-title { + width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + box-sizing: border-box; + } + .current { + border: 1px solid #44b549; + background: #fff; + color: #44b549; + } + } + } + .weixin-sub-menu { + position: absolute; + bottom: 60px; + left: 0; + right: 0; + border-top: 1px solid #d0d0d0; + margin-bottom: 0; + background: #fafafa; + display: block; + padding: 0; + .menu-sub-item { + line-height: 50px; + height: 50px; + text-align: center; + width: 100%; + padding: 0; + border: 1px solid #d0d0d0; + border-top-width: 0; + cursor: pointer; + position: relative; + color: #616161; + .menu-item-title { + width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + box-sizing: border-box; + } + .current { + border: 1px solid #44b549; + background: #fff; + color: #44b549; + } + } + .show { + display: block; + } + } + .menu-arrow { + position: absolute; + left: 50%; + margin-left: -6px; + } + .arrow_in { + bottom: -4px; + display: inline-block; + width: 0; + height: 0; + border-width: 6px 6px 0; + border-style: solid dashed dashed; + border-color: #fafafa transparent transparent; + } + .arrow_out { + bottom: -5px; + display: inline-block; + width: 0; + height: 0; + border-width: 6px 6px 0; + border-style: solid dashed dashed; + border-color: #d0d0d0 transparent transparent; + } + a { + text-decoration: none; + color: #616161; + } + } + //} + + .weixin-preview .menu-item:hover { + color: #000; + } + .weixin-preview .menu-sub-item:hover { + background: #eee; + } + + .weixin-preview li .current:hover { + background: #fff; + color: #44b549; + } + + /*菜单内容*/ + .weixin-menu-detail { + width: 600px; + padding: 0 20px 5px; + background-color: #f4f5f9; + border: 1px solid #e7e7eb; + float: left; + min-height: 540px; + .menu-name { + float: left; + height: 40px; + line-height: 40px; + font-size: 18px; + } + .menu-del { + float: right; + height: 40px; + line-height: 40px; + color: #459ae9; + cursor: pointer; + } + .menu-input-group { + //width: 540px; + margin: 10px 0 30px 0; + overflow: hidden; + } + .menu-label { + float: left; + height: 30px; + line-height: 30px; + width: 80px; + text-align: right; + } + .menu-input { + float: left; + width: 380px; + .menu-tips { + margin: 0 0 0 10px; + } + } + .menu-input-text { + width: 300px; + margin-left: 10px; + } + .menu-tips { + color: #8d8d8d; + padding-top: 4px; + margin: 0; + } + .menu-tips.cursor { + color: #459ae9; + cursor: pointer; + } + .menu-content { + padding: 16px 20px; + border: 1px solid #e7e7eb; + background-color: #fff; + .menu-input-group { + margin: 0 0 10px 0; + } + .menu-label { + text-align: left; + width: 100px; + } + .menu-input-text { + border: 1px solid #e7e7eb; + } + .menu-tips { + padding-bottom: 10px; + } + } + .menu-msg-content { + padding: 0; + border: 1px solid #e7e7eb; + background-color: #fff; + .menu-msg-head { + overflow: hidden; + border-bottom: 1px solid #e7e7eb; + line-height: 38px; + height: 38px; + padding: 0 20px; + } + .menu-msg-panel { + padding: 30px 50px; + } + .menu-msg-select { + padding: 40px 20px; + border: 2px dotted #d9dadc; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + } + .menu-msg-select:hover { + border-color: #b3b3b3; + } + strong { + display: block; + padding-top: 3px; + font-weight: 400; + font-style: normal; + } + .menu-msg-title { + float: left; + width: 310px; + height: 30px; + line-height: 30px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } + .icon36_common { + width: 36px; + height: 36px; + vertical-align: middle; + display: inline-block; + .add_gray { + background: url('@/assets/base.png') 0 -2548px no-repeat; + } + } + .icon_msg_sender { + margin-right: 3px; + margin-top: -2px; + width: 20px; + height: 20px; + vertical-align: middle; + display: inline-block; + background: url('@/assets/msg_tab.png') 0 -270px no-repeat; + } + + .weixin-btn-group { + text-align: center; + width: 950px; + margin-top: 30px; + overflow: hidden; + .btn { + width: 100px; + border-radius: 0; + } + } + + #material-list { + padding: 20px; + overflow-y: scroll; + height: 558px; + table { + width: 100%; + } + } + #news-list { + padding: 20px; + overflow-y: scroll; + height: 558px; + } +</style> diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// <reference types="vite/client" /> diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..dd32d4b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "forceConsistentCasingInFileNames": true, + "allowSyntheticDefaultImports": true, + "strictFunctionTypes": false, + "jsx": "preserve", + "baseUrl": "./", + "allowJs": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "experimentalDecorators": true, + "lib": ["esnext", "dom"], + "types": ["vite/client"], + "typeRoots": ["./node_modules/@types/"], + "noImplicitAny": false, + "skipLibCheck": true, + "paths": { + "@/*": ["src/*"] + } + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue", + "components.d.ts", + "vite.config.ts" + ], + "exclude": ["node_modules", "dist", "**/*.js"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..7137502 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,72 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import ViteCompression from 'vite-plugin-compression'; +import ViteComponents from 'unplugin-vue-components/vite'; +import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'; +import { EleAdminResolver } from 'ele-admin-pro/lib/utils/resolvers'; +import { DynamicAntdLess } from 'ele-admin-pro/lib/utils/dynamic-theme'; +import { resolve } from 'path'; + +export default defineConfig(({ command }) => { + const isBuild = command === 'build'; + return { + resolve: { + alias: { + '@/': resolve('src') + '/', + 'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js' + } + }, + base: './', + plugins: [ + vue(), + // 组件按需引入 + ViteComponents({ + dts: false, + resolvers: [ + AntDesignVueResolver({ + importStyle: isBuild ? 'less' : false + }), + EleAdminResolver({ + importStyle: isBuild ? 'less' : false + }) + ] + }), + // gzip 压缩 + ViteCompression({ + disable: !isBuild, + threshold: 10240, + algorithm: 'gzip', + ext: '.gz' + }) + ], + css: { + preprocessorOptions: { + less: { + javascriptEnabled: true, + plugins: [new DynamicAntdLess()], + modifyVars: { + // 组件样式开发环境全局引入生产环境按需引入 + 'style-entry-file': isBuild ? 'as-needed' : 'global-import' + } + } + } + }, + optimizeDeps: { + include: [ + 'sortablejs', + 'vuedraggable', + 'echarts/core', + 'echarts/charts', + 'echarts/renderers', + 'echarts/components', + 'vue-echarts', + 'echarts-wordcloud', + 'xlsx' + ] + }, + build: { + target: 'chrome63', + chunkSizeWarningLimit: 2000 + } + }; +});