commit e974bf361d2c7ae81d2cc3738ff71887cfe6e2e7 Author: Jason Date: Thu Dec 18 16:37:33 2025 +0800 init diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..dc3bc09 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,4 @@ +> 1% +last 2 versions +not dead +not ie 11 diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..5654e89 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,5 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..f954fb4 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", + "changelog": [ + "@changesets/changelog-github", + { "repo": "vbenjs/vue-vben-admin" } + ], + "commit": false, + "fixed": [["@vben-core/*", "@vben/*"]], + "snapshot": { + "prereleaseTemplate": "{tag}-{datetime}" + }, + "privatePackages": { "version": true, "tag": true }, + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 0000000..02e33fa --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1 @@ +export { default } from '@vben/commitlint-config'; diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..52b833a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +node_modules +.git +.gitignore +*.md +dist +.turbo +dist.zip diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..179aec6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset=utf-8 +end_of_line=lf +insert_final_newline=true +indent_style=space +indent_size=2 +max_line_length = 100 +trim_trailing_whitespace = true +quote_type = single + +[*.{yml,yaml,json}] +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d4e5bd3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings + +# Automatically normalize line endings (to LF) for all text-based files. +* text=auto eol=lf + +# Declare files that will always have CRLF line endings on checkout. +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary \ No newline at end of file diff --git a/.gitconfig b/.gitconfig new file mode 100644 index 0000000..4b28a69 --- /dev/null +++ b/.gitconfig @@ -0,0 +1,2 @@ +[core] + ignorecase = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fc4832 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +node_modules +.DS_Store +dist +dist-ssr +dist.zip +dist.tar +dist.war +.nitro +.output +*-dist.zip +*-dist.tar +*-dist.war +coverage +*.local +**/.vitepress/cache +.cache +.turbo +.temp +dev-dist +.stylelintcache +yarn.lock +package-lock.json +.VSCodeCounter +**/backend-mock/data + +# local env files +.env.local +.env.*.local +.eslintcache + +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +vite.config.mts.* +vite.config.mjs.* +vite.config.js.* +vite.config.ts.* + +# Editor directories and files +.idea +# .vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.history +**/ele-docs diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..5fda2cf --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,6 @@ +ports: + - port: 5555 + onOpen: open-preview +tasks: + - init: npm i -g corepack && pnpm install + command: pnpm run dev:play diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..ee5c244 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.1.0 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..aeac1ae --- /dev/null +++ b/.npmrc @@ -0,0 +1,13 @@ +registry=https://registry.npmmirror.com +public-hoist-pattern[]=lefthook +public-hoist-pattern[]=eslint +public-hoist-pattern[]=prettier +public-hoist-pattern[]=prettier-plugin-tailwindcss +public-hoist-pattern[]=stylelint +public-hoist-pattern[]=*postcss* +public-hoist-pattern[]=@commitlint/* +public-hoist-pattern[]=czg + +strict-peer-dependencies=false +auto-install-peers=true +dedupe-peer-dependents=true diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..d0b0ca1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,18 @@ +dist +dev-dist +.local +.output.js +node_modules +.nvmrc +coverage +CODEOWNERS +.nitro +.output + + +**/*.svg +**/*.sh + +public +.npmrc +*-lock.yaml diff --git a/.prettierrc.mjs b/.prettierrc.mjs new file mode 100644 index 0000000..3e25d2c --- /dev/null +++ b/.prettierrc.mjs @@ -0,0 +1 @@ +export { default } from '@vben/prettier-config'; diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000..f4b2db2 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,4 @@ +dist +public +__tests__ +coverage diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cec5b42 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2024-present, Vben + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..06fca11 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# 易慧用计价系统 +## 服务端-web +pnpm run build:ele +## 客户端-web +pnpm run build:antd + +# 前置准备 +## 环境要求 https://doc.vben.pro/guide/introduction/quick-start.html#%E5%89%8D%E7%BD%AE%E5%87%86%E5%A4%87 +在启动项目前,你需要确保你的环境满足以下要求: + +Node.js 20.15.0 及以上版本,推荐使用 fnm 、 nvm 或者直接使用pnpm 进行版本管理。 +Git 任意版本。 +验证你的环境是否满足以上要求,你可以通过以下命令查看版本: + +# 安装依赖 +在你的代码目录内打开终端,并执行以下命令: + + +## 进入项目目录 +cd vue-vben-admin + +## 使用项目指定的pnpm版本进行依赖安装 +npm i -g corepack + +## 安装依赖 +pnpm install + +# 运行项目 +## 选择项目 +执行以下命令运行项目: + + +# 启动项目 +pnpm dev + diff --git a/apps/web-antd/.env b/apps/web-antd/.env new file mode 100644 index 0000000..f84adf3 --- /dev/null +++ b/apps/web-antd/.env @@ -0,0 +1,35 @@ +# 应用标题 +VITE_APP_TITLE=易慧用计价系统 + +# 应用命名空间,用于缓存、store等功能的前缀,确保隔离 +VITE_APP_NAMESPACE=yudao-vben-antd + +# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密 +VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key + +# 是否开启模拟数据 +VITE_NITRO_MOCK=false + +# 租户开关 +VITE_APP_TENANT_ENABLE=true + +# 验证码的开关 +VITE_APP_CAPTCHA_ENABLE=false + +# 文档地址的开关 +VITE_APP_DOCALERT_ENABLE=true + +# 百度统计 +#VITE_APP_BAIDU_CODE = e98f2eab6ceb8688bc6d8fc5332ff093 + +# GoView域名 +VITE_GOVIEW_URL='http://127.0.0.1:3000' + +# API 加解密 +VITE_APP_API_ENCRYPT_ENABLE = true +VITE_APP_API_ENCRYPT_HEADER = X-Api-Encrypt +VITE_APP_API_ENCRYPT_ALGORITHM = AES +VITE_APP_API_ENCRYPT_REQUEST_KEY = 52549111389893486934626385991395 +VITE_APP_API_ENCRYPT_RESPONSE_KEY = 96103715984234343991809655248883 +# VITE_APP_API_ENCRYPT_REQUEST_KEY = MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCls2rIpnGdYnLFgz1XU13GbNQ5DloyPpvW00FPGjqn5Z6JpK+kDtVlnkhwR87iRrE5Vf2WNqRX6vzbLSgveIQY8e8oqGCb829myjf1MuI+ZzN4ghf/7tEYhZJGPI9AbfxFqBUzm+kR3/HByAI22GLT96WM26QiMK8n3tIP/yiLswIDAQAB +# VITE_APP_API_ENCRYPT_RESPONSE_KEY = MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAOH8IfIFxL/MR9XIg1UDv5z1fGXQI93E8wrU4iPFovL/sEt9uSgSkjyidC2O7N+m7EKtoN6b1u7cEwXSkwf3kfK0jdWLSQaNpX5YshqXCBzbDfugDaxuyYrNA4/tIMs7mzZAk0APuRXB35Dmupou7Yw7TFW/7QhQmGfzeEKULQvnAgMBAAECgYAw8LqlQGyQoPv5p3gRxEMOCfgL0JzD3XBJKztiRd35RDh40Nx1ejgjW4dPioFwGiVWd2W8cAGHLzALdcQT2KDJh+T/tsd4SPmI6uSBBK6Ff2DkO+kFFcuYvfclQQKqxma5CaZOSqhgenacmgTMFeg2eKlY3symV6JlFNu/IKU42QJBAOhxAK/Eq3e61aYQV2JSguhMR3b8NXJJRroRs/QHEanksJtl+M+2qhkC9nQVXBmBkndnkU/l2tYcHfSBlAyFySMCQQD445tgm/J2b6qMQmuUGQAYDN8FIkHjeKmha+l/fv0igWm8NDlBAem91lNDIPBUzHL1X1+pcts5bjmq99YdOnhtAkAg2J8dN3B3idpZDiQbC8fd5bGPmdI/pSUudAP27uzLEjr2qrE/QPPGdwm2m7IZFJtK7kK1hKio6u48t/bg0iL7AkEAuUUs94h+v702Fnym+jJ2CHEkXvz2US8UDs52nWrZYiM1o1y4tfSHm8H8bv8JCAa9GHyriEawfBraILOmllFdLQJAQSRZy4wmlaG48MhVXodB85X+VZ9krGXZ2TLhz7kz9iuToy53l9jTkESt6L5BfBDCVdIwcXLYgK+8KFdHN5W7HQ== diff --git a/apps/web-antd/.env.analyze b/apps/web-antd/.env.analyze new file mode 100644 index 0000000..ffafa8d --- /dev/null +++ b/apps/web-antd/.env.analyze @@ -0,0 +1,7 @@ +# public path +VITE_BASE=/ + +# Basic interface address SPA +VITE_GLOB_API_URL=/api + +VITE_VISUALIZER=true diff --git a/apps/web-antd/.env.development b/apps/web-antd/.env.development new file mode 100644 index 0000000..cff2557 --- /dev/null +++ b/apps/web-antd/.env.development @@ -0,0 +1,21 @@ +# 端口号 +VITE_PORT=5666 + +VITE_BASE=/ + +# 请求路径 +VITE_BASE_URL=http://127.0.0.1:48080 +# 接口地址 +VITE_GLOB_API_URL=/admin-api +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 +VITE_UPLOAD_TYPE=server +# 是否打开 devtools,true 为打开,false 为关闭 +VITE_DEVTOOLS=false + +# 是否注入全局loading +VITE_INJECT_APP_LOADING=true + +# 默认登录用户名 +VITE_APP_DEFAULT_USERNAME=admin +# 默认登录密码 +VITE_APP_DEFAULT_PASSWORD=admin123 diff --git a/apps/web-antd/.env.production b/apps/web-antd/.env.production new file mode 100644 index 0000000..910fd64 --- /dev/null +++ b/apps/web-antd/.env.production @@ -0,0 +1,23 @@ +VITE_BASE=/ + +# 请求路径 +VITE_BASE_URL=http://127.0.0.1:48080 +# 接口地址 +VITE_GLOB_API_URL=http://127.0.0.1:48080/admin-api +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 +VITE_UPLOAD_TYPE=server + +# 是否开启压缩,可以设置为 none, brotli, gzip +VITE_COMPRESS=none + +# 是否开启 PWA +VITE_PWA=false + +# vue-router 的模式 +VITE_ROUTER_HISTORY=hash + +# 是否注入全局loading +VITE_INJECT_APP_LOADING=true + +# 打包后是否生成dist.zip +VITE_ARCHIVER=true diff --git a/apps/web-antd/index.html b/apps/web-antd/index.html new file mode 100644 index 0000000..56dc53c --- /dev/null +++ b/apps/web-antd/index.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + <%= VITE_APP_TITLE %> + + + + +
+ + + diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json new file mode 100644 index 0000000..601f7d1 --- /dev/null +++ b/apps/web-antd/package.json @@ -0,0 +1,73 @@ +{ + "name": "@vben/web-antd", + "version": "5.5.9", + "homepage": "https://vben.pro", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "apps/web-antd" + }, + "license": "MIT", + "author": { + "name": "vben", + "email": "ann.vben@gmail.com", + "url": "https://github.com/anncwb" + }, + "type": "module", + "scripts": { + "build": "pnpm vite build --mode production", + "build:analyze": "pnpm vite build --mode analyze", + "dev": "pnpm vite --mode development", + "preview": "vite preview", + "typecheck": "vue-tsc --noEmit --skipLibCheck" + }, + "imports": { + "#/*": "./src/*" + }, + "dependencies": { + "@form-create/ant-design-vue": "catalog:", + "@form-create/antd-designer": "catalog:", + "@handsontable/vue3": "catalog:", + "@tinymce/tinymce-vue": "catalog:", + "@vben/access": "workspace:*", + "@vben/common-ui": "workspace:*", + "@vben/constants": "workspace:*", + "@vben/hooks": "workspace:*", + "@vben/icons": "workspace:*", + "@vben/layouts": "workspace:*", + "@vben/locales": "workspace:*", + "@vben/plugins": "workspace:*", + "@vben/preferences": "workspace:*", + "@vben/request": "workspace:*", + "@vben/stores": "workspace:*", + "@vben/styles": "workspace:*", + "@vben/types": "workspace:*", + "@vben/utils": "workspace:*", + "@videojs-player/vue": "catalog:", + "@vueuse/core": "catalog:", + "@vueuse/integrations": "catalog:", + "ant-design-vue": "catalog:", + "benz-amr-recorder": "catalog:", + "bpmn-js": "catalog:", + "bpmn-js-properties-panel": "catalog:", + "bpmn-js-token-simulation": "catalog:", + "camunda-bpmn-moddle": "catalog:", + "cropperjs": "catalog:", + "dayjs": "catalog:", + "diagram-js": "catalog:", + "fast-xml-parser": "catalog:", + "handsontable": "catalog:", + "highlight.js": "catalog:", + "pinia": "catalog:", + "steady-xml": "catalog:", + "tinymce": "catalog:", + "video.js": "catalog:", + "vue": "catalog:", + "vue-dompurify-html": "catalog:", + "vue-router": "catalog:", + "vue3-print-nb": "catalog:", + "vue3-signature": "catalog:", + "vuedraggable": "catalog:" + } +} diff --git a/apps/web-antd/postcss.config.mjs b/apps/web-antd/postcss.config.mjs new file mode 100644 index 0000000..3d80704 --- /dev/null +++ b/apps/web-antd/postcss.config.mjs @@ -0,0 +1 @@ +export { default } from '@vben/tailwind-config/postcss'; diff --git a/apps/web-antd/public/favicon.ico b/apps/web-antd/public/favicon.ico new file mode 100644 index 0000000..fcf9818 Binary files /dev/null and b/apps/web-antd/public/favicon.ico differ diff --git a/apps/web-antd/public/static/imgs/ai/dall2.jpg b/apps/web-antd/public/static/imgs/ai/dall2.jpg new file mode 100644 index 0000000..c07374d Binary files /dev/null and b/apps/web-antd/public/static/imgs/ai/dall2.jpg differ diff --git a/apps/web-antd/public/static/imgs/ai/dall3.jpg b/apps/web-antd/public/static/imgs/ai/dall3.jpg new file mode 100644 index 0000000..7f45803 Binary files /dev/null and b/apps/web-antd/public/static/imgs/ai/dall3.jpg differ diff --git a/apps/web-antd/public/static/imgs/ai/qingxi.jpg b/apps/web-antd/public/static/imgs/ai/qingxi.jpg new file mode 100644 index 0000000..d76b815 Binary files /dev/null and b/apps/web-antd/public/static/imgs/ai/qingxi.jpg differ diff --git a/apps/web-antd/public/static/imgs/ai/ziran.jpg b/apps/web-antd/public/static/imgs/ai/ziran.jpg new file mode 100644 index 0000000..6290724 Binary files /dev/null and b/apps/web-antd/public/static/imgs/ai/ziran.jpg differ diff --git a/apps/web-antd/public/tinymce/icons/default/icons.min.js b/apps/web-antd/public/tinymce/icons/default/icons.min.js new file mode 100644 index 0000000..620f554 --- /dev/null +++ b/apps/web-antd/public/tinymce/icons/default/icons.min.js @@ -0,0 +1 @@ +tinymce.IconManager.add("default",{icons:{"accessibility-check":'',"accordion-toggle":'',accordion:'',"action-next":'',"action-prev":'',addtag:'',"ai-prompt":'',ai:'',"align-center":'',"align-justify":'',"align-left":'',"align-none":'',"align-right":'',"arrow-left":'',"arrow-right":'',bold:'',bookmark:'',"border-style":'',"border-width":'',brightness:'',browse:'',cancel:'',"cell-background-color":'',"cell-border-color":'',"change-case":'',"character-count":'',"checklist-rtl":'',checklist:'',checkmark:'',"chevron-down":'',"chevron-left":'',"chevron-right":'',"chevron-up":'',close:'',"code-sample":'',"color-levels":'',"color-picker":'',"color-swatch-remove-color":'',"color-swatch":'',"comment-add":'',comment:'',contrast:'',copy:'',crop:'',"cut-column":'',"cut-row":'',cut:'',"document-properties":'',drag:'',"duplicate-column":'',"duplicate-row":'',duplicate:'',"edit-block":'',"edit-image":'',"embed-page":'',embed:'',emoji:'',export:'',fill:'',"flip-horizontally":'',"flip-vertically":'',footnote:'',"format-code":'',"format-painter":'',format:'',fullscreen:'',gallery:'',gamma:'',help:'',"highlight-bg-color":'',home:'',"horizontal-rule":'',"image-options":'',image:'',indent:'',info:'',"insert-character":'',"insert-time":'',invert:'',italic:'',language:'',"line-height":'',line:'',link:'',"list-bull-circle":'',"list-bull-default":'',"list-bull-square":'',"list-num-default-rtl":'',"list-num-default":'',"list-num-lower-alpha-rtl":'',"list-num-lower-alpha":'',"list-num-lower-greek-rtl":'',"list-num-lower-greek":'',"list-num-lower-roman-rtl":'',"list-num-lower-roman":'',"list-num-upper-alpha-rtl":'',"list-num-upper-alpha":'',"list-num-upper-roman-rtl":'',"list-num-upper-roman":'',lock:'',ltr:'',"math-equation":'',mentions:'',minus:'',"more-drawer":'',"new-document":'',"new-tab":'',"non-breaking":'',notice:'',"ordered-list-rtl":'',"ordered-list":'',orientation:'',outdent:'',"export-pdf":'',"export-word":'',"import-word":'',"page-break":'',paragraph:'',"paste-column-after":'',"paste-column-before":'',"paste-row-after":'',"paste-row-before":'',"paste-text":'',paste:'',"permanent-pen":'',plus:'',preferences:'',preview:'',print:'',quote:'',redo:'',reload:'',"remove-formatting":'',remove:'',"resize-handle":'',resize:'',"restore-draft":'',"revision-history":'',"rotate-left":'',"rotate-right":'',rtl:'',save:'',search:'',"select-all":'',selected:'',send:'',settings:'',sharpen:'',sourcecode:'',"spell-check":'',"strike-through":'',subscript:'',superscript:'',"table-caption":'',"table-cell-classes":'',"table-cell-properties":'',"table-cell-select-all":'',"table-cell-select-inner":'',"table-classes":'',"table-delete-column":'',"table-delete-row":'',"table-delete-table":'',"table-insert-column-after":'',"table-insert-column-before":'',"table-insert-row-above":'',"table-insert-row-after":'',"table-left-header":'',"table-merge-cells":'',"table-row-numbering-rtl":'',"table-row-numbering":'',"table-row-properties":'',"table-split-cells":'',"table-top-header":'',table:'',"template-add":'',template:'',"temporary-placeholder":'',"text-color":'',"text-size-decrease":'',"text-size-increase":'',toc:'',translate:'',typography:'',underline:'',undo:'',unlink:'',unlock:'',"unordered-list":'',unselected:'',upload:'',"add-file":'',adjustments:'',"alt-text":'',"auto-image-enhancement":'',blur:'',box:'',camera:'',caption:'',dropbox:'',evernote:'',exposure:'',fb:'',flickr:'',folder:'',"google-drive":'',"google-photos":'',grayscale:'',huddle:'',"image-decorative":'',"image-enhancements":'',instagram:'',onedrive:'',"photo-filter":'',"revert-changes":'',saturation:'',"transform-image":'',vibrance:'',vk:'',warmth:'',user:'',"vertical-align":'',visualblocks:'',visualchars:'',warning:'',"zoom-in":'',"zoom-out":''}}); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/langs/README.md b/apps/web-antd/public/tinymce/langs/README.md new file mode 100644 index 0000000..cd93d8c --- /dev/null +++ b/apps/web-antd/public/tinymce/langs/README.md @@ -0,0 +1,3 @@ +This is where language files should be placed. + +Please DO NOT translate these directly, use this service instead: https://crowdin.com/project/tinymce diff --git a/apps/web-antd/public/tinymce/langs/zh_CN.js b/apps/web-antd/public/tinymce/langs/zh_CN.js new file mode 100644 index 0000000..58ed188 --- /dev/null +++ b/apps/web-antd/public/tinymce/langs/zh_CN.js @@ -0,0 +1 @@ +tinymce.addI18n("zh_CN",{"#":"#","Accessibility":"\u8f85\u52a9\u529f\u80fd","Accordion":"\u6298\u53e0\u9762\u677f","Accordion body...":"\u6298\u53e0\u9762\u677f\u6b63\u6587...","Accordion summary...":"\u6298\u53e0\u9762\u677f\u6458\u8981...","Action":"\u52a8\u4f5c","Activity":"\u6d3b\u52a8","Address":"\u5730\u5740","Advanced":"\u9ad8\u7ea7","Align":"\u5bf9\u9f50","Align center":"\u5c45\u4e2d\u5bf9\u9f50","Align left":"\u5de6\u5bf9\u9f50","Align right":"\u53f3\u5bf9\u9f50","Alignment":"\u5bf9\u9f50","Alignment {0}":"\u5bf9\u9f50{0}","All":"\u5168\u90e8","Alternative description":"\u66ff\u4ee3\u63cf\u8ff0","Alternative source":"\u955c\u50cf","Alternative source URL":"\u66ff\u4ee3\u6765\u6e90\u7f51\u5740","Anchor":"\u951a\u70b9","Anchor...":"\u951a\u70b9...","Anchors":"\u951a\u70b9","Animals and Nature":"\u52a8\u7269\u548c\u81ea\u7136","Arrows":"\u7bad\u5934","B":"B","Background color":"\u80cc\u666f\u989c\u8272","Background color {0}":"\u80cc\u666f\u989c\u8272 {0}","Black":"\u9ed1\u8272","Block":"\u5757","Block {0}":"\u6587\u672c\u5757{0}","Blockquote":"Blockquote","Blocks":"\u6837\u5f0f","Blue":"\u84dd\u8272","Blue component":"\u767d\u8272\u90e8\u5206","Body":"\u8868\u4f53","Bold":"\u7c97\u4f53","Border":"\u6846\u7ebf","Border color":"\u6846\u7ebf\u989c\u8272","Border style":"\u8fb9\u6846\u6837\u5f0f","Border width":"\u8fb9\u6846\u5bbd\u5ea6","Bottom":"\u4e0b\u65b9\u5bf9\u9f50","Browse files":"\u6d4f\u89c8\u6587\u4ef6","Browse for an image":"\u6d4f\u89c8\u56fe\u7247","Browse links":"\u6d4f\u89c8\u94fe\u63a5","Bullet list":"\u65e0\u5e8f\u5217\u8868","Cancel":"\u53d6\u6d88","Caption":"\u6807\u9898","Cell":"\u5355\u5143\u683c","Cell padding":"\u5355\u5143\u683c\u5185\u8fb9\u8ddd","Cell properties":"\u5355\u5143\u683c\u5c5e\u6027","Cell spacing":"\u5355\u5143\u683c\u5916\u95f4\u8ddd","Cell styles":"\u5355\u5143\u683c\u6837\u5f0f","Cell type":"\u50a8\u5b58\u683c\u522b","Center":"\u5c45\u4e2d","Characters":"\u5b57\u7b26","Characters (no spaces)":"\u5b57\u7b26(\u65e0\u7a7a\u683c)","Circle":"\u7a7a\u5fc3\u5706","Class":"\u7c7b\u578b","Clear formatting":"\u6e05\u9664\u683c\u5f0f","Close":"\u5173\u95ed","Code":"\u4ee3\u7801","Code sample...":"\u793a\u4f8b\u4ee3\u7801...","Code view":"\u4ee3\u7801\u89c6\u56fe","Color Picker":"\u9009\u8272\u5668","Color swatch":"\u989c\u8272\u6837\u672c","Cols":"\u5217","Column":"\u5217","Column clipboard actions":"\u5217\u526a\u8d34\u677f\u64cd\u4f5c","Column group":"\u5217\u7ec4","Column header":"\u5217\u6807\u9898","Constrain proportions":"\u4fdd\u6301\u6bd4\u4f8b","Copy":"\u590d\u5236","Copy column":"\u590d\u5236\u5217","Copy row":"\u590d\u5236\u884c","Could not find the specified string.":"\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9\u3002","Could not load emojis":"\u65e0\u6cd5\u52a0\u8f7dEmojis","Count":"\u8ba1\u6570","Currency":"\u8d27\u5e01","Current window":"\u5f53\u524d\u7a97\u53e3","Custom color":"\u81ea\u5b9a\u4e49\u989c\u8272","Custom...":"\u81ea\u5b9a\u4e49......","Cut":"\u526a\u5207","Cut column":"\u526a\u5207\u5217","Cut row":"\u526a\u5207\u884c","Dark Blue":"\u6df1\u84dd\u8272","Dark Gray":"\u6df1\u7070\u8272","Dark Green":"\u6df1\u7eff\u8272","Dark Orange":"\u6df1\u6a59\u8272","Dark Purple":"\u6df1\u7d2b\u8272","Dark Red":"\u6df1\u7ea2\u8272","Dark Turquoise":"\u6df1\u84dd\u7eff\u8272","Dark Yellow":"\u6697\u9ec4\u8272","Dashed":"\u865a\u7ebf","Date/time":"\u65e5\u671f/\u65f6\u95f4","Decrease indent":"\u51cf\u5c11\u7f29\u8fdb","Default":"\u9884\u8bbe","Delete accordion":"\u5220\u9664\u6298\u53e0\u9762\u677f","Delete column":"\u5220\u9664\u5217","Delete row":"\u5220\u9664\u884c","Delete table":"\u5220\u9664\u8868\u683c","Dimensions":"\u5c3a\u5bf8","Disc":"\u5b9e\u5fc3\u5706","Div":"Div","Document":"\u6587\u6863","Dotted":"\u865a\u7ebf","Double":"\u53cc\u7cbe\u5ea6","Drop an image here":"\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64","Dropped file type is not supported":"\u6b64\u6587\u4ef6\u7c7b\u578b\u4e0d\u652f\u6301\u62d6\u653e","Edit":"\u7f16\u8f91","Embed":"\u5185\u5d4c","Emojis":"Emojis","Emojis...":"Emojis...","Error":"\u9519\u8bef","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","Extended Latin":"\u62c9\u4e01\u8bed\u6269\u5145","Failed to initialize plugin: {0}":"\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}","Failed to load plugin url: {0}":"\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}","Failed to load plugin: {0} from url {1}":"\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}","Failed to upload image: {0}":"\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}","File":"\u6587\u4ef6","Find":"\u5bfb\u627e","Find (if searchreplace plugin activated)":"\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)","Find and Replace":"\u67e5\u627e\u548c\u66ff\u6362","Find and replace...":"\u67e5\u627e\u5e76\u66ff\u6362...","Find in selection":"\u5728\u9009\u533a\u4e2d\u67e5\u627e","Find whole words only":"\u5168\u5b57\u5339\u914d","Flags":"\u65d7\u5e1c","Focus to contextual toolbar":"\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355","Focus to element path":"\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84","Focus to menubar":"\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f","Focus to toolbar":"\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f","Font":"\u5b57\u4f53","Font size {0}":"\u5b57\u4f53\u5927\u5c0f{0}","Font sizes":"\u5b57\u4f53\u5927\u5c0f","Font {0}":"\u5b57\u4f53{0}","Fonts":"\u5b57\u4f53","Food and Drink":"\u98df\u7269\u548c\u996e\u54c1","Footer":"\u8868\u5c3e","Format":"\u683c\u5f0f","Format {0}":"\u683c\u5f0f{0}","Formats":"\u683c\u5f0f","Fullscreen":"\u5168\u5c4f","G":"G","General":"\u4e00\u822c","Gray":"\u7070\u8272","Green":"\u7eff\u8272","Green component":"\u7eff\u8272\u90e8\u5206","Groove":"\u51f9\u69fd","Handy Shortcuts":"\u5feb\u6377\u952e","Header":"\u8868\u5934","Header cell":"\u8868\u5934\u5355\u5143\u683c","Heading 1":"\u4e00\u7ea7\u6807\u9898","Heading 2":"\u4e8c\u7ea7\u6807\u9898","Heading 3":"\u4e09\u7ea7\u6807\u9898","Heading 4":"\u56db\u7ea7\u6807\u9898","Heading 5":"\u4e94\u7ea7\u6807\u9898","Heading 6":"\u516d\u7ea7\u6807\u9898","Headings":"\u6807\u9898","Height":"\u9ad8\u5ea6","Help":"\u5e2e\u52a9","Hex color code":"\u5341\u516d\u8fdb\u5236\u989c\u8272\u4ee3\u7801","Hidden":"\u9690\u85cf","Horizontal align":"\u6c34\u5e73\u5bf9\u9f50","Horizontal line":"\u6c34\u5e73\u5206\u5272\u7ebf","Horizontal space":"\u6c34\u5e73\u95f4\u8ddd","ID":"ID","ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.":"ID\u5e94\u8be5\u4ee5\u82f1\u6587\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u9762\u53ea\u80fd\u6709\u82f1\u6587\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002","Image is decorative":"\u56fe\u50cf\u662f\u88c5\u9970\u6027\u7684","Image list":"\u56fe\u7247\u6e05\u5355","Image title":"\u56fe\u7247\u6807\u9898","Image...":"\u56fe\u7247...","ImageProxy HTTP error: Could not find Image Proxy":"\u56fe\u7247\u4ee3\u7406\u8bf7\u6c42\u9519\u8bef\uff1a\u65e0\u6cd5\u627e\u5230\u56fe\u7247\u4ee3\u7406","ImageProxy HTTP error: Incorrect Image Proxy URL":"\u56fe\u7247\u4ee3\u7406\u8bf7\u6c42\u9519\u8bef\uff1a\u56fe\u7247\u4ee3\u7406\u5730\u5740\u9519\u8bef","ImageProxy HTTP error: Rejected request":"\u56fe\u7247\u4ee3\u7406\u8bf7\u6c42\u9519\u8bef\uff1a\u8bf7\u6c42\u88ab\u62d2\u7edd","ImageProxy HTTP error: Unknown ImageProxy error":"\u56fe\u7247\u4ee3\u7406\u8bf7\u6c42\u9519\u8bef\uff1a\u672a\u77e5\u7684\u56fe\u7247\u4ee3\u7406\u9519\u8bef","Increase indent":"\u589e\u52a0\u7f29\u8fdb","Inline":"\u6587\u672c","Insert":"\u63d2\u5165","Insert Template":"\u63d2\u5165\u6a21\u677f","Insert accordion":"\u63d2\u5165\u6298\u53e0\u9762\u677f","Insert column after":"\u5728\u53f3\u4fa7\u63d2\u5165\u5217","Insert column before":"\u5728\u5de6\u4fa7\u63d2\u5165\u5217","Insert date/time":"\u63d2\u5165\u65e5\u671f/\u65f6\u95f4","Insert image":"\u63d2\u5165\u56fe\u7247","Insert link (if link plugin activated)":"\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)","Insert row after":"\u5728\u4e0b\u65b9\u63d2\u5165\u884c","Insert row before":"\u5728\u4e0a\u65b9\u63d2\u5165\u884c","Insert table":"\u63d2\u5165\u8868\u683c","Insert template...":"\u63d2\u5165\u6a21\u677f...","Insert video":"\u63d2\u5165\u89c6\u9891","Insert/Edit code sample":"\u63d2\u5165/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b","Insert/edit image":"\u63d2\u5165/\u7f16\u8f91\u56fe\u7247","Insert/edit link":"\u63d2\u5165/\u7f16\u8f91\u94fe\u63a5","Insert/edit media":"\u63d2\u5165/\u7f16\u8f91\u5a92\u4f53","Insert/edit video":"\u63d2\u5165/\u7f16\u8f91\u89c6\u9891","Inset":"\u5d4c\u5165","Invalid hex color code: {0}":"\u5341\u516d\u8fdb\u5236\u989c\u8272\u4ee3\u7801\u65e0\u6548\uff1a {0}","Invalid input":"\u65e0\u6548\u8f93\u5165","Italic":"\u659c\u4f53","Justify":"\u4e24\u7aef\u5bf9\u9f50","Keyboard Navigation":"\u952e\u76d8\u6307\u5f15","Language":"\u8bed\u8a00","Learn more...":"\u4e86\u89e3\u66f4\u591a...","Left":"\u5de6","Left to right":"\u7531\u5de6\u5230\u53f3","Light Blue":"\u6d45\u84dd\u8272","Light Gray":"\u6d45\u7070\u8272","Light Green":"\u6d45\u7eff\u8272","Light Purple":"\u6d45\u7d2b\u8272","Light Red":"\u6d45\u7ea2\u8272","Light Yellow":"\u6d45\u9ec4\u8272","Line height":"Line height","Link list":"\u94fe\u63a5\u6e05\u5355","Link...":"\u94fe\u63a5...","List Properties":"\u5217\u8868\u5c5e\u6027","List properties...":"\u6807\u9898\u5b57\u4f53\u5c5e\u6027","Loading emojis...":"\u6b63\u5728\u52a0\u8f7dEmojis...","Loading...":"\u52a0\u8f7d\u4e2d...","Lower Alpha":"\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd","Lower Greek":"\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd","Lower Roman":"\u5c0f\u5199\u7f57\u9a6c\u6570\u5b57","Match case":"\u5927\u5c0f\u5199\u5339\u914d","Mathematical":"\u6570\u5b66","Media poster (Image URL)":"\u5c01\u9762(\u56fe\u7247\u5730\u5740)","Media...":"\u591a\u5a92\u4f53...","Medium Blue":"\u4e2d\u84dd\u8272","Medium Gray":"\u4e2d\u7070\u8272","Medium Purple":"\u4e2d\u7d2b\u8272","Merge cells":"\u5408\u5e76\u5355\u5143\u683c","Middle":"\u5c45\u4e2d\u5bf9\u9f50","Midnight Blue":"\u6df1\u84dd\u8272","More...":"\u66f4\u591a...","Name":"\u540d\u79f0","Navy Blue":"\u6d77\u519b\u84dd","New document":"\u65b0\u5efa\u6587\u6863","New window":"\u65b0\u7a97\u53e3","Next":"\u4e0b\u4e00\u4e2a","No":"\u5426","No alignment":"\u672a\u5bf9\u9f50","No color":"\u65e0","Nonbreaking space":"\u4e0d\u95f4\u65ad\u7a7a\u683c","None":"\u65e0","Numbered list":"\u6709\u5e8f\u5217\u8868","OR":"\u6216","Objects":"\u7269\u4ef6","Ok":"\u786e\u5b9a","Open help dialog":"\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846","Open link":"\u6253\u5f00\u94fe\u63a5","Open link in...":"\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...","Open popup menu for split buttons":"\u6253\u5f00\u5f39\u51fa\u5f0f\u83dc\u5355\uff0c\u7528\u4e8e\u62c6\u5206\u6309\u94ae","Orange":"\u6a59\u8272","Outset":"\u5916\u7f6e","Page break":"\u5206\u9875\u7b26","Paragraph":"\u6bb5\u843d","Paste":"\u7c98\u8d34","Paste as text":"\u7c98\u8d34\u4e3a\u6587\u672c","Paste column after":"\u7c98\u8d34\u540e\u9762\u7684\u5217","Paste column before":"\u7c98\u8d34\u6b64\u5217\u524d","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","Paste or type a link":"\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5","Paste row after":"\u7c98\u8d34\u884c\u5230\u4e0b\u65b9","Paste row before":"\u7c98\u8d34\u884c\u5230\u4e0a\u65b9","Paste your embed code below:":"\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:","People":"\u4eba\u7c7b","Plugins":"\u63d2\u4ef6","Plugins installed ({0}):":"\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):","Powered by {0}":"\u7531{0}\u9a71\u52a8","Pre":"\u524d\u8a00","Preferences":"\u9996\u9009\u9879","Preformatted":"\u9884\u5148\u683c\u5f0f\u5316\u7684","Premium plugins:":"\u4f18\u79c0\u63d2\u4ef6\uff1a","Press the Up and Down arrow keys to resize the editor.":"\u6309\u4e0a\u4e0b\u7bad\u5934\u952e\u4ee5\u8c03\u6574\u7f16\u8f91\u5668\u5927\u5c0f\u3002","Press the arrow keys to resize the editor.":"\u6309\u7bad\u5934\u952e\u4ee5\u8c03\u6574\u7f16\u8f91\u5668\u5927\u5c0f\u3002","Press {0} for help":"\u6309 {0} \u83b7\u5f97\u5e2e\u52a9","Preview":"\u9884\u89c8","Previous":"\u4e0a\u4e00\u4e2a","Print":"\u6253\u5370","Print...":"\u6253\u5370...","Purple":"\u7d2b\u8272","Quotations":"\u5f15\u7528","R":"R","Range 0 to 255":"\u8303\u56f40\u81f3255","Red":"\u7ea2\u8272","Red component":"\u7ea2\u8272\u90e8\u5206","Redo":"\u91cd\u505a","Remove":"\u79fb\u9664","Remove color":"\u79fb\u9664\u989c\u8272","Remove link":"\u79fb\u9664\u94fe\u63a5","Replace":"\u66ff\u6362","Replace all":"\u66ff\u6362\u5168\u90e8","Replace with":"\u66ff\u6362\u4e3a","Resize":"\u8c03\u6574\u5927\u5c0f","Restore last draft":"\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f","Reveal or hide additional toolbar items":"\u663e\u793a\u6216\u9690\u85cf\u5176\u4ed6\u5de5\u5177\u680f\u9879","Rich Text Area":"\u5bcc\u6587\u672c\u533a\u57df","Rich Text Area. Press ALT-0 for help.":"\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002","Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help":"\u7f16\u8f91\u533a\u3002\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9","Ridge":"\u6d77\u810a\u5ea7","Right":"\u53f3","Right to left":"\u7531\u53f3\u5230\u5de6","Row":"\u884c","Row clipboard actions":"\u884c\u526a\u8d34\u677f\u64cd\u4f5c","Row group":"\u884c\u7ec4","Row header":"\u884c\u5934","Row properties":"\u884c\u5c5e\u6027","Row type":"\u884c\u7c7b\u578b","Rows":"\u884c\u6570","Save":"\u4fdd\u5b58","Save (if save plugin activated)":"\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)","Scope":"\u8303\u56f4","Search":"\u641c\u7d22","Select all":"\u5168\u9009","Select...":"\u9009\u62e9...","Selection":"\u9009\u62e9","Shortcut":"\u5feb\u6377\u65b9\u5f0f","Show blocks":"\u663e\u793a\u533a\u5757\u8fb9\u6846","Show caption":"\u663e\u793a\u6807\u9898","Show invisible characters":"\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26","Size":"\u5b57\u53f7","Solid":"\u5b9e\u7ebf","Source":"\u5730\u5740","Source code":"\u6e90\u4ee3\u7801","Special Character":"\u7279\u6b8a\u5b57\u7b26","Special character...":"\u7279\u6b8a\u5b57\u7b26...","Split cell":"\u62c6\u5206\u5355\u5143\u683c","Square":"\u5b9e\u5fc3\u65b9\u5757","Start list at number":"\u4ee5\u6570\u5b57\u5f00\u59cb\u5217\u8868","Strikethrough":"\u5220\u9664\u7ebf","Style":"\u6837\u5f0f","Subscript":"\u4e0b\u6807","Superscript":"\u4e0a\u6807","Switch to or from fullscreen mode":"\u5207\u6362\u5168\u5c4f\u6a21\u5f0f","Symbols":"\u7b26\u53f7","System Font":"\u7cfb\u7edf\u5b57\u4f53","Table":"\u8868\u683c","Table caption":"\u8868\u683c\u6807\u9898","Table properties":"\u8868\u683c\u5c5e\u6027","Table styles":"\u8868\u683c\u6837\u5f0f","Template":"\u6a21\u677f","Templates":"\u6a21\u677f","Text":"\u6587\u5b57","Text color":"\u5b57\u4f53\u989c\u8272","Text color {0}":"\u5b57\u4f53\u989c\u8272 {0}","Text to display":"\u8981\u663e\u793a\u7684\u6587\u672c","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","Title":"\u6807\u9898","To open the popup, press Shift+Enter":"\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846","Toggle accordion":"\u5207\u6362\u6298\u53e0\u9762\u677f","Tools":"\u5de5\u5177","Top":"\u4e0a\u65b9\u5bf9\u9f50","Travel and Places":"\u65c5\u6e38\u548c\u5730\u70b9","Turquoise":"\u9752\u7eff\u8272","Underline":"\u4e0b\u5212\u7ebf","Undo":"\u64a4\u9500","Upload":"\u4e0a\u4f20","Uploading image":"\u4e0a\u4f20\u56fe\u7247","Upper Alpha":"\u5927\u5199\u82f1\u6587\u5b57\u6bcd","Upper Roman":"\u5927\u5199\u7f57\u9a6c\u6570\u5b57","Url":"\u5730\u5740","User Defined":"\u81ea\u5b9a\u4e49","Valid":"\u6709\u6548","Version":"\u7248\u672c","Vertical align":"\u5782\u76f4\u5bf9\u9f50","Vertical space":"\u5782\u76f4\u95f4\u8ddd","View":"\u67e5\u770b","Visual aids":"\u7f51\u683c\u7ebf","Warn":"\u8b66\u544a","White":"\u767d\u8272","Width":"\u5bbd\u5ea6","Word count":"\u5b57\u6570","Words":"\u5355\u8bcd","Words: {0}":"\u5b57\u6570\uff1a{0}","Yellow":"\u9ec4\u8272","Yes":"\u662f","You are using {0}":"\u4f60\u6b63\u5728\u4f7f\u7528 {0}","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","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","alignment":"\u5bf9\u9f50","austral sign":"\u6fb3\u5143\u7b26\u53f7","cedi sign":"\u585e\u5730\u7b26\u53f7","colon sign":"\u5192\u53f7","cruzeiro sign":"\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7","currency sign":"\u8d27\u5e01\u7b26\u53f7","dollar sign":"\u7f8e\u5143\u7b26\u53f7","dong sign":"\u8d8a\u5357\u76fe\u7b26\u53f7","drachma sign":"\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7","euro-currency sign":"\u6b27\u5143\u7b26\u53f7","example":"\u793a\u4f8b","formatting":"\u683c\u5f0f\u5316","french franc sign":"\u6cd5\u90ce\u7b26\u53f7","german penny symbol":"\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7","guarani sign":"\u74dc\u62c9\u5c3c\u7b26\u53f7","history":"\u5386\u53f2","hryvnia sign":"\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7","indentation":"\u7f29\u8fdb","indian rupee sign":"\u5370\u5ea6\u5362\u6bd4","kip sign":"\u8001\u631d\u57fa\u666e\u7b26\u53f7","lira sign":"\u91cc\u62c9\u7b26\u53f7","livre tournois sign":"\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7","manat sign":"\u9a6c\u7eb3\u7279\u7b26\u53f7","mill sign":"\u5bc6\u5c14\u7b26\u53f7","naira sign":"\u5948\u62c9\u7b26\u53f7","new sheqel sign":"\u65b0\u8c22\u514b\u5c14\u7b26\u53f7","nordic mark sign":"\u5317\u6b27\u9a6c\u514b","peseta sign":"\u6bd4\u585e\u5854\u7b26\u53f7","peso sign":"\u6bd4\u7d22\u7b26\u53f7","ruble sign":"\u5362\u5e03\u7b26\u53f7","rupee sign":"\u5362\u6bd4\u7b26\u53f7","spesmilo sign":"spesmilo\u7b26\u53f7","styles":"\u6837\u5f0f","tenge sign":"\u575a\u6208\u7b26\u53f7","tugrik sign":"\u56fe\u683c\u91cc\u514b\u7b26\u53f7","turkish lira sign":"\u571f\u8033\u5176\u91cc\u62c9","won sign":"\u97e9\u5143\u7b26\u53f7","yen character":"\u65e5\u5143\u5b57\u6837","yen/yuan character variant one":"\u5143\u5b57\u6837\uff08\u5927\u5199\uff09","yuan character":"\u4eba\u6c11\u5e01\u5143\u5b57\u6837","yuan character, in hong kong and taiwan":"\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09","{0} characters":"{0} \u4e2a\u5b57\u7b26","{0} columns, {1} rows":"{0} \u5217\uff0c{1} \u884c","{0} words":"{0} \u5b57"}); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/license.md b/apps/web-antd/public/tinymce/license.md new file mode 100644 index 0000000..70454a6 --- /dev/null +++ b/apps/web-antd/public/tinymce/license.md @@ -0,0 +1,6 @@ +# Software License Agreement + +**TinyMCE** – [](https://github.com/tinymce/tinymce) +Copyright (c) 2024, Ephox Corporation DBA Tiny Technologies, Inc. + +Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). diff --git a/apps/web-antd/public/tinymce/models/dom/model.min.js b/apps/web-antd/public/tinymce/models/dom/model.min.js new file mode 100644 index 0000000..bce8e62 --- /dev/null +++ b/apps/web-antd/public/tinymce/models/dom/model.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.ModelManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(o=n=e,(r=String).prototype.isPrototypeOf(o)||(null===(s=n.constructor)||void 0===s?void 0:s.name)===r.name)?"string":t;var o,n,r,s})(t)===e,o=e=>t=>typeof t===e,n=e=>t=>e===t,r=t("string"),s=t("object"),l=t("array"),a=n(null),c=o("boolean"),i=n(void 0),m=e=>!(e=>null==e)(e),d=o("function"),u=o("number"),f=()=>{},g=e=>()=>e,h=e=>e,p=(e,t)=>e===t;function b(e,...t){return(...o)=>{const n=t.concat(o);return e.apply(null,n)}}const w=e=>t=>!e(t),v=e=>e(),y=g(!1),x=g(!0);class C{constructor(e,t){this.tag=e,this.value=t}static some(e){return new C(!0,e)}static none(){return C.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?C.some(e(this.value)):C.none()}bind(e){return this.tag?e(this.value):C.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:C.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return m(e)?C.some(e):C.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}C.singletonNone=new C(!1);const S=Array.prototype.slice,T=Array.prototype.indexOf,R=Array.prototype.push,D=(e,t)=>{return o=e,n=t,T.call(o,n)>-1;var o,n},O=(e,t)=>{for(let o=0,n=e.length;o{const o=[];for(let n=0;n{const o=e.length,n=new Array(o);for(let r=0;r{for(let o=0,n=e.length;o{const o=[],n=[];for(let r=0,s=e.length;r{const o=[];for(let n=0,r=e.length;n(((e,t)=>{for(let o=e.length-1;o>=0;o--)t(e[o],o)})(e,((e,n)=>{o=t(o,e,n)})),o),A=(e,t,o)=>(N(e,((e,n)=>{o=t(o,e,n)})),o),L=(e,t)=>((e,t,o)=>{for(let n=0,r=e.length;n{for(let o=0,n=e.length;o{const t=[];for(let o=0,n=e.length;oM(E(e,t)),P=(e,t)=>{for(let o=0,n=e.length;o{const o={};for(let n=0,r=e.length;nt>=0&&tF(e,0),$=e=>F(e,e.length-1),V=(e,t)=>{for(let o=0;o{const o=q(e);for(let n=0,r=o.length;nY(e,((e,o)=>({k:o,v:t(e,o)}))),Y=(e,t)=>{const o={};return G(e,((e,n)=>{const r=t(e,n);o[r.k]=r.v})),o},J=(e,t)=>{const o=[];return G(e,((e,n)=>{o.push(t(e,n))})),o},Q=e=>J(e,h),X=(e,t)=>U.call(e,t),Z="undefined"!=typeof window?window:Function("return this;")(),ee=(e,t)=>((e,t)=>{let o=null!=t?t:Z;for(let t=0;t{const t=ee("ownerDocument.defaultView",e);return s(e)&&((e=>((e,t)=>{const o=((e,t)=>ee(e,t))(e,t);if(null==o)throw new Error(e+" not available on this browser");return o})("HTMLElement",e))(t).prototype.isPrototypeOf(e)||/^HTML\w*Element$/.test(te(e).constructor.name))},ne=e=>e.dom.nodeName.toLowerCase(),re=e=>e.dom.nodeType,se=e=>t=>re(t)===e,le=e=>8===re(e)||"#comment"===ne(e),ae=e=>ce(e)&&oe(e.dom),ce=se(1),ie=se(3),me=se(9),de=se(11),ue=e=>t=>ce(t)&&ne(t)===e,fe=(e,t,o)=>{if(!(r(o)||c(o)||u(o)))throw console.error("Invalid call to Attribute.set. Key ",t,":: Value ",o,":: Element ",e),new Error("Attribute value was not simple");e.setAttribute(t,o+"")},ge=(e,t,o)=>{fe(e.dom,t,o)},he=(e,t)=>{const o=e.dom;G(t,((e,t)=>{fe(o,t,e)}))},pe=(e,t)=>{const o=e.dom.getAttribute(t);return null===o?void 0:o},be=(e,t)=>C.from(pe(e,t)),we=(e,t)=>{e.dom.removeAttribute(t)},ve=e=>A(e.dom.attributes,((e,t)=>(e[t.name]=t.value,e)),{}),ye=e=>{if(null==e)throw new Error("Node cannot be null or undefined");return{dom:e}},xe={fromHtml:(e,t)=>{const o=(t||document).createElement("div");if(o.innerHTML=e,!o.hasChildNodes()||o.childNodes.length>1){const t="HTML does not have a single root node";throw console.error(t,e),new Error(t)}return ye(o.childNodes[0])},fromTag:(e,t)=>{const o=(t||document).createElement(e);return ye(o)},fromText:(e,t)=>{const o=(t||document).createTextNode(e);return ye(o)},fromDom:ye,fromPoint:(e,t,o)=>C.from(e.dom.elementFromPoint(t,o)).map(ye)},Ce=(e,t)=>{const o=e.dom;if(1!==o.nodeType)return!1;{const e=o;if(void 0!==e.matches)return e.matches(t);if(void 0!==e.msMatchesSelector)return e.msMatchesSelector(t);if(void 0!==e.webkitMatchesSelector)return e.webkitMatchesSelector(t);if(void 0!==e.mozMatchesSelector)return e.mozMatchesSelector(t);throw new Error("Browser lacks native selectors")}},Se=e=>1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType||0===e.childElementCount,Te=(e,t)=>{const o=void 0===t?document:t.dom;return Se(o)?C.none():C.from(o.querySelector(e)).map(xe.fromDom)},Re=(e,t)=>e.dom===t.dom,De=(e,t)=>{const o=e.dom,n=t.dom;return o!==n&&o.contains(n)},Oe=Ce,ke=e=>xe.fromDom(e.dom.ownerDocument),Ee=e=>me(e)?e:ke(e),Ne=e=>C.from(e.dom.parentNode).map(xe.fromDom),_e=e=>C.from(e.dom.parentElement).map(xe.fromDom),Be=(e,t)=>{const o=d(t)?t:y;let n=e.dom;const r=[];for(;null!==n.parentNode&&void 0!==n.parentNode;){const e=n.parentNode,t=xe.fromDom(e);if(r.push(t),!0===o(t))break;n=e}return r},ze=e=>C.from(e.dom.previousSibling).map(xe.fromDom),Ae=e=>C.from(e.dom.nextSibling).map(xe.fromDom),Le=e=>E(e.dom.childNodes,xe.fromDom),We=(e,t)=>{const o=e.dom.childNodes;return C.from(o[t]).map(xe.fromDom)},Me=(e,t)=>{Ne(e).each((o=>{o.dom.insertBefore(t.dom,e.dom)}))},je=(e,t)=>{Ae(e).fold((()=>{Ne(e).each((e=>{Ie(e,t)}))}),(e=>{Me(e,t)}))},Pe=(e,t)=>{const o=(e=>We(e,0))(e);o.fold((()=>{Ie(e,t)}),(o=>{e.dom.insertBefore(t.dom,o.dom)}))},Ie=(e,t)=>{e.dom.appendChild(t.dom)},Fe=(e,t)=>{Me(e,t),Ie(t,e)},He=(e,t)=>{N(t,((o,n)=>{const r=0===n?e:t[n-1];je(r,o)}))},$e=(e,t)=>{N(t,(t=>{Ie(e,t)}))},Ve=e=>{e.dom.textContent="",N(Le(e),(e=>{qe(e)}))},qe=e=>{const t=e.dom;null!==t.parentNode&&t.parentNode.removeChild(t)},Ue=e=>{const t=Le(e);t.length>0&&He(e,t),qe(e)},Ge=(e,t)=>xe.fromDom(e.dom.cloneNode(t)),Ke=e=>Ge(e,!1),Ye=e=>Ge(e,!0),Je=(e,t)=>{const o=xe.fromTag(t),n=ve(e);return he(o,n),o},Qe=["tfoot","thead","tbody","colgroup"],Xe=(e,t,o)=>({element:e,rowspan:t,colspan:o}),Ze=(e,t,o)=>({element:e,cells:t,section:o}),et=(e,t,o)=>({element:e,isNew:t,isLocked:o}),tt=(e,t,o,n)=>({element:e,cells:t,section:o,isNew:n}),ot=e=>de(e)&&m(e.dom.host),nt=e=>xe.fromDom(e.dom.getRootNode()),rt=e=>xe.fromDom(e.dom.host),st=e=>{const t=ie(e)?e.dom.parentNode:e.dom;if(null==t||null===t.ownerDocument)return!1;const o=t.ownerDocument;return(e=>{const t=nt(e);return ot(t)?C.some(t):C.none()})(xe.fromDom(t)).fold((()=>o.body.contains(t)),(n=st,r=rt,e=>n(r(e))));var n,r},lt=()=>at(xe.fromDom(document)),at=e=>{const t=e.dom.body;if(null==t)throw new Error("Body is not available yet");return xe.fromDom(t)},ct=(e,t)=>{let o=[];return N(Le(e),(e=>{t(e)&&(o=o.concat([e])),o=o.concat(ct(e,t))})),o},it=(e,t,o)=>((e,o,n)=>B(Be(e,n),(e=>Ce(e,t))))(e,0,o),mt=(e,t)=>(e=>B(Le(e),(e=>Ce(e,t))))(e),dt=(e,t)=>((e,t)=>{const o=void 0===t?document:t.dom;return Se(o)?[]:E(o.querySelectorAll(e),xe.fromDom)})(t,e);var ut=(e,t,o,n,r)=>e(o,n)?C.some(o):d(r)&&r(o)?C.none():t(o,n,r);const ft=(e,t,o)=>{let n=e.dom;const r=d(o)?o:y;for(;n.parentNode;){n=n.parentNode;const e=xe.fromDom(n);if(t(e))return C.some(e);if(r(e))break}return C.none()},gt=(e,t,o)=>ut(((e,t)=>t(e)),ft,e,t,o),ht=(e,t,o)=>ft(e,(e=>Ce(e,t)),o),pt=(e,t)=>(e=>L(e.dom.childNodes,(e=>{return o=xe.fromDom(e),Ce(o,t);var o})).map(xe.fromDom))(e),bt=(e,t)=>Te(t,e),wt=(e,t,o)=>ut(((e,t)=>Ce(e,t)),ht,e,t,o),vt=(e,t,o=p)=>e.exists((e=>o(e,t))),yt=e=>{const t=[],o=e=>{t.push(e)};for(let t=0;te?C.some(t):C.none(),Ct=(e,t,o)=>""===t||e.length>=t.length&&e.substr(o,o+t.length)===t,St=(e,t,o=0,n)=>{const r=e.indexOf(t,o);return-1!==r&&(!!i(n)||r+t.length<=n)},Tt=(e,t)=>Ct(e,t,0),Rt=(e,t)=>Ct(e,t,e.length-t.length),Dt=(e=>t=>t.replace(e,""))(/^\s+|\s+$/g),Ot=e=>e.length>0,kt=e=>void 0!==e.style&&d(e.style.getPropertyValue),Et=(e,t,o)=>{if(!r(o))throw console.error("Invalid call to CSS.set. Property ",t,":: Value ",o,":: Element ",e),new Error("CSS value must be a string: "+o);kt(e)&&e.style.setProperty(t,o)},Nt=(e,t,o)=>{const n=e.dom;Et(n,t,o)},_t=(e,t)=>{const o=e.dom;G(t,((e,t)=>{Et(o,t,e)}))},Bt=(e,t)=>{const o=e.dom,n=window.getComputedStyle(o).getPropertyValue(t);return""!==n||st(e)?n:zt(o,t)},zt=(e,t)=>kt(e)?e.style.getPropertyValue(t):"",At=(e,t)=>{const o=e.dom,n=zt(o,t);return C.from(n).filter((e=>e.length>0))},Lt=(e,t)=>{((e,t)=>{kt(e)&&e.style.removeProperty(t)})(e.dom,t),vt(be(e,"style").map(Dt),"")&&we(e,"style")},Wt=(e,t,o=0)=>be(e,t).map((e=>parseInt(e,10))).getOr(o),Mt=(e,t)=>Wt(e,t,1),jt=e=>ue("col")(e)?Wt(e,"span",1)>1:Mt(e,"colspan")>1,Pt=(e,t)=>parseInt(Bt(e,t),10),It=g(10),Ft=g(10),Ht=(e,t)=>$t(e,t,x),$t=(e,t,o)=>j(Le(e),(e=>Ce(e,t)?o(e)?[e]:[]:$t(e,t,o))),Vt=(e,t)=>((e,t,o=y)=>o(t)?C.none():D(e,ne(t))?C.some(t):ht(t,e.join(","),(e=>Ce(e,"table")||o(e))))(["td","th"],e,t),qt=e=>Ht(e,"th,td"),Ut=e=>Ce(e,"colgroup")?mt(e,"col"):j(Yt(e),(e=>mt(e,"col"))),Gt=(e,t)=>wt(e,"table",t),Kt=e=>Ht(e,"tr"),Yt=e=>Gt(e).fold(g([]),(e=>mt(e,"colgroup"))),Jt=(e,t)=>E(e,(e=>{if("colgroup"===ne(e)){const t=E(Ut(e),(e=>{const t=Wt(e,"span",1);return Xe(e,1,t)}));return Ze(e,t,"colgroup")}{const o=E(qt(e),(e=>{const t=Wt(e,"rowspan",1),o=Wt(e,"colspan",1);return Xe(e,t,o)}));return Ze(e,o,t(e))}})),Qt=e=>Ne(e).map((e=>{const t=ne(e);return(e=>D(Qe,e))(t)?t:"tbody"})).getOr("tbody"),Xt=e=>{const t=Kt(e),o=[...Yt(e),...t];return Jt(o,Qt)},Zt=e=>{let t,o=!1;return(...n)=>(o||(o=!0,t=e.apply(null,n)),t)},eo=()=>to(0,0),to=(e,t)=>({major:e,minor:t}),oo={nu:to,detect:(e,t)=>{const o=String(t).toLowerCase();return 0===e.length?eo():((e,t)=>{const o=((e,t)=>{for(let o=0;oNumber(t.replace(o,"$"+e));return to(n(1),n(2))})(e,o)},unknown:eo},no=(e,t)=>{const o=String(t).toLowerCase();return L(e,(e=>e.search(o)))},ro=/.*?version\/\ ?([0-9]+)\.([0-9]+).*/,so=e=>t=>St(t,e),lo=[{name:"Edge",versionRegexes:[/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],search:e=>St(e,"edge/")&&St(e,"chrome")&&St(e,"safari")&&St(e,"applewebkit")},{name:"Chromium",brand:"Chromium",versionRegexes:[/.*?chrome\/([0-9]+)\.([0-9]+).*/,ro],search:e=>St(e,"chrome")&&!St(e,"chromeframe")},{name:"IE",versionRegexes:[/.*?msie\ ?([0-9]+)\.([0-9]+).*/,/.*?rv:([0-9]+)\.([0-9]+).*/],search:e=>St(e,"msie")||St(e,"trident")},{name:"Opera",versionRegexes:[ro,/.*?opera\/([0-9]+)\.([0-9]+).*/],search:so("opera")},{name:"Firefox",versionRegexes:[/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],search:so("firefox")},{name:"Safari",versionRegexes:[ro,/.*?cpu os ([0-9]+)_([0-9]+).*/],search:e=>(St(e,"safari")||St(e,"mobile/"))&&St(e,"applewebkit")}],ao=[{name:"Windows",search:so("win"),versionRegexes:[/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]},{name:"iOS",search:e=>St(e,"iphone")||St(e,"ipad"),versionRegexes:[/.*?version\/\ ?([0-9]+)\.([0-9]+).*/,/.*cpu os ([0-9]+)_([0-9]+).*/,/.*cpu iphone os ([0-9]+)_([0-9]+).*/]},{name:"Android",search:so("android"),versionRegexes:[/.*?android\ ?([0-9]+)\.([0-9]+).*/]},{name:"macOS",search:so("mac os x"),versionRegexes:[/.*?mac\ os\ x\ ?([0-9]+)_([0-9]+).*/]},{name:"Linux",search:so("linux"),versionRegexes:[]},{name:"Solaris",search:so("sunos"),versionRegexes:[]},{name:"FreeBSD",search:so("freebsd"),versionRegexes:[]},{name:"ChromeOS",search:so("cros"),versionRegexes:[/.*?chrome\/([0-9]+)\.([0-9]+).*/]}],co={browsers:g(lo),oses:g(ao)},io="Edge",mo="Chromium",uo="Opera",fo="Firefox",go="Safari",ho=e=>{const t=e.current,o=e.version,n=e=>()=>t===e;return{current:t,version:o,isEdge:n(io),isChromium:n(mo),isIE:n("IE"),isOpera:n(uo),isFirefox:n(fo),isSafari:n(go)}},po=()=>ho({current:void 0,version:oo.unknown()}),bo=ho,wo=(g(io),g(mo),g("IE"),g(uo),g(fo),g(go),"Windows"),vo="Android",yo="Linux",xo="macOS",Co="Solaris",So="FreeBSD",To="ChromeOS",Ro=e=>{const t=e.current,o=e.version,n=e=>()=>t===e;return{current:t,version:o,isWindows:n(wo),isiOS:n("iOS"),isAndroid:n(vo),isMacOS:n(xo),isLinux:n(yo),isSolaris:n(Co),isFreeBSD:n(So),isChromeOS:n(To)}},Do=()=>Ro({current:void 0,version:oo.unknown()}),Oo=Ro,ko=(g(wo),g("iOS"),g(vo),g(yo),g(xo),g(Co),g(So),g(To),e=>window.matchMedia(e).matches);let Eo=Zt((()=>((e,t,o)=>{const n=co.browsers(),r=co.oses(),s=t.bind((e=>((e,t)=>V(t.brands,(t=>{const o=t.brand.toLowerCase();return L(e,(e=>{var t;return o===(null===(t=e.brand)||void 0===t?void 0:t.toLowerCase())})).map((e=>({current:e.name,version:oo.nu(parseInt(t.version,10),0)})))})))(n,e))).orThunk((()=>((e,t)=>no(e,t).map((e=>{const o=oo.detect(e.versionRegexes,t);return{current:e.name,version:o}})))(n,e))).fold(po,bo),l=((e,t)=>no(e,t).map((e=>{const o=oo.detect(e.versionRegexes,t);return{current:e.name,version:o}})))(r,e).fold(Do,Oo),a=((e,t,o,n)=>{const r=e.isiOS()&&!0===/ipad/i.test(o),s=e.isiOS()&&!r,l=e.isiOS()||e.isAndroid(),a=l||n("(pointer:coarse)"),c=r||!s&&l&&n("(min-device-width:768px)"),i=s||l&&!c,m=t.isSafari()&&e.isiOS()&&!1===/safari/i.test(o),d=!i&&!c&&!m;return{isiPad:g(r),isiPhone:g(s),isTablet:g(c),isPhone:g(i),isTouch:g(a),isAndroid:e.isAndroid,isiOS:e.isiOS,isWebView:g(m),isDesktop:g(d)}})(l,s,e,o);return{browser:s,os:l,deviceType:a}})(window.navigator.userAgent,C.from(window.navigator.userAgentData),ko)));const No=()=>Eo(),_o=(e,t)=>{const o=o=>{const n=t(o);if(n<=0||null===n){const t=Bt(o,e);return parseFloat(t)||0}return n},n=(e,t)=>A(t,((t,o)=>{const n=Bt(e,o),r=void 0===n?0:parseInt(n,10);return isNaN(r)?t:t+r}),0);return{set:(t,o)=>{if(!u(o)&&!o.match(/^[0-9]+$/))throw new Error(e+".set accepts only positive integer values. Value was "+o);const n=t.dom;kt(n)&&(n.style[e]=o+"px")},get:o,getOuter:o,aggregate:n,max:(e,t,o)=>{const r=n(e,o);return t>r?t-r:0}}},Bo=(e,t,o)=>((e,t)=>(e=>{const t=parseFloat(e);return isNaN(t)?C.none():C.some(t)})(e).getOr(t))(Bt(e,t),o),zo=_o("width",(e=>e.dom.offsetWidth)),Ao=e=>zo.get(e),Lo=e=>zo.getOuter(e),Wo=e=>((e,t)=>{const o=e.dom,n=o.getBoundingClientRect().width||o.offsetWidth;return"border-box"===t?n:((e,t,o,n)=>t-Bo(e,`padding-${o}`,0)-Bo(e,`padding-${n}`,0)-Bo(e,`border-${o}-width`,0)-Bo(e,`border-${n}-width`,0))(e,n,"left","right")})(e,"content-box"),Mo=(e,t,o)=>{const n=e.cells,r=n.slice(0,t),s=n.slice(t),l=r.concat(o).concat(s);return Io(e,l)},jo=(e,t,o)=>Mo(e,t,[o]),Po=(e,t,o)=>{e.cells[t]=o},Io=(e,t)=>tt(e.element,t,e.section,e.isNew),Fo=(e,t)=>e.cells[t],Ho=(e,t)=>Fo(e,t).element,$o=e=>e.cells.length,Vo=e=>{const t=_(e,(e=>"colgroup"===e.section));return{rows:t.fail,cols:t.pass}},qo=(e,t,o)=>{const n=E(e.cells,o);return tt(t(e.element),n,e.section,!0)},Uo="data-snooker-locked-cols",Go=e=>be(e,Uo).bind((e=>C.from(e.match(/\d+/g)))).map((e=>I(e,x))),Ko=e=>{const t=A(Vo(e).rows,((e,t)=>(N(t.cells,((t,o)=>{t.isLocked&&(e[o]=!0)})),e)),{}),o=J(t,((e,t)=>parseInt(t,10)));return(e=>{const t=S.call(e,0);return t.sort(void 0),t})(o)},Yo=(e,t)=>e+","+t,Jo=(e,t)=>{const o=j(e.all,(e=>e.cells));return B(o,t)},Qo=e=>{const t={},o=[],n=H(e).map((e=>e.element)).bind(Gt).bind(Go).getOr({});let r=0,s=0,l=0;const{pass:a,fail:c}=_(e,(e=>"colgroup"===e.section));N(c,(e=>{const a=[];N(e.cells,(e=>{let o=0;for(;void 0!==t[Yo(l,o)];)o++;const r=((e,t)=>X(e,t)&&void 0!==e[t]&&null!==e[t])(n,o.toString()),c=((e,t,o,n,r,s)=>({element:e,rowspan:t,colspan:o,row:n,column:r,isLocked:s}))(e.element,e.rowspan,e.colspan,l,o,r);for(let n=0;n{const t=(e=>{const t={};let o=0;return N(e.cells,(e=>{const n=e.colspan;k(n,(r=>{const s=o+r;t[s]=((e,t,o)=>({element:e,colspan:t,column:o}))(e.element,n,s)})),o+=n})),t})(e),o=((e,t)=>({element:e,columns:t}))(e.element,Q(t));return{colgroups:[o],columns:t}})).getOrThunk((()=>({colgroups:[],columns:{}}))),d=((e,t)=>({rows:e,columns:t}))(r,s);return{grid:d,access:t,all:o,columns:i,colgroups:m}},Xo=e=>{const t=Xt(e);return Qo(t)},Zo=Qo,en=(e,t,o)=>C.from(e.access[Yo(t,o)]),tn=(e,t,o)=>{const n=Jo(e,(e=>o(t,e.element)));return n.length>0?C.some(n[0]):C.none()},on=Jo,nn=e=>j(e.all,(e=>e.cells)),rn=e=>Q(e.columns),sn=e=>q(e.columns).length>0,ln=(e,t)=>C.from(e.columns[t]),an=(e,t=x)=>{const o=e.grid,n=k(o.columns,h),r=k(o.rows,h);return E(n,(o=>cn((()=>j(r,(t=>en(e,t,o).filter((e=>e.column===o)).toArray()))),(e=>1===e.colspan&&t(e.element)),(()=>en(e,0,o)))))},cn=(e,t,o)=>{const n=e();return L(n,t).orThunk((()=>C.from(n[0]).orThunk(o))).map((e=>e.element))},mn=e=>{const t=e.grid,o=k(t.rows,h),n=k(t.columns,h);return E(o,(t=>cn((()=>j(n,(o=>en(e,t,o).filter((e=>e.row===t)).fold(g([]),(e=>[e]))))),(e=>1===e.rowspan),(()=>en(e,t,0)))))},dn=(e,t)=>o=>"rtl"===un(o)?t:e,un=e=>"rtl"===Bt(e,"direction")?"rtl":"ltr",fn=_o("height",(e=>{const t=e.dom;return st(e)?t.getBoundingClientRect().height:t.offsetHeight})),gn=e=>fn.get(e),hn=e=>fn.getOuter(e),pn=(e,t)=>({left:e,top:t,translate:(o,n)=>pn(e+o,t+n)}),bn=pn,wn=(e,t)=>void 0!==e?e:void 0!==t?t:0,vn=e=>{const t=e.dom.ownerDocument,o=t.body,n=t.defaultView,r=t.documentElement;if(o===e.dom)return bn(o.offsetLeft,o.offsetTop);const s=wn(null==n?void 0:n.pageYOffset,r.scrollTop),l=wn(null==n?void 0:n.pageXOffset,r.scrollLeft),a=wn(r.clientTop,o.clientTop),c=wn(r.clientLeft,o.clientLeft);return yn(e).translate(l-c,s-a)},yn=e=>{const t=e.dom,o=t.ownerDocument.body;return o===t?bn(o.offsetLeft,o.offsetTop):st(e)?(e=>{const t=e.getBoundingClientRect();return bn(t.left,t.top)})(t):bn(0,0)},xn=(e,t)=>({row:e,y:t}),Cn=(e,t)=>({col:e,x:t}),Sn=e=>vn(e).left+Lo(e),Tn=e=>vn(e).left,Rn=(e,t)=>Cn(e,Tn(t)),Dn=(e,t)=>Cn(e,Sn(t)),On=e=>vn(e).top,kn=(e,t)=>xn(e,On(t)),En=(e,t)=>xn(e,On(t)+hn(t)),Nn=(e,t,o)=>{if(0===o.length)return[];const n=E(o.slice(1),((t,o)=>t.map((t=>e(o,t))))),r=o[o.length-1].map((e=>t(o.length-1,e)));return n.concat([r])},_n={delta:h,positions:e=>Nn(kn,En,e),edge:On},Bn=dn({delta:h,edge:Tn,positions:e=>Nn(Rn,Dn,e)},{delta:e=>-e,edge:Sn,positions:e=>Nn(Dn,Rn,e)}),zn={delta:(e,t)=>Bn(t).delta(e,t),positions:(e,t)=>Bn(t).positions(e,t),edge:e=>Bn(e).edge(e)},An={unsupportedLength:["em","ex","cap","ch","ic","rem","lh","rlh","vw","vh","vi","vb","vmin","vmax","cm","mm","Q","in","pc","pt","px"],fixed:["px","pt"],relative:["%"],empty:[""]},Ln=(()=>{const e="[0-9]+",t="[eE][+-]?"+e,o=e=>`(?:${e})?`,n=["Infinity",e+"\\."+o(e)+o(t),"\\."+e+o(t),e+o(t)].join("|");return new RegExp(`^([+-]?(?:${n}))(.*)$`)})(),Wn=/(\d+(\.\d+)?)%/,Mn=/(\d+(\.\d+)?)px|em/,jn=ue("col"),Pn=ue("tr"),In=(e,t,o)=>{const n=_e(e).getOrThunk((()=>at(ke(e))));return t(e)/o(n)*100},Fn=(e,t)=>{Nt(e,"width",t+"px")},Hn=(e,t)=>{Nt(e,"width",t+"%")},$n=(e,t)=>{Nt(e,"height",t+"px")},Vn=e=>{const t=(e=>{return Bo(t=e,"height",t.dom.offsetHeight)+"px";var t})(e);return t?((e,t,o,n)=>{const r=parseFloat(e);return Rt(e,"%")&&"table"!==ne(t)?((e,t,o,n)=>{const r=Gt(e).map((e=>{const n=o(e);return Math.floor(t/100*n)})).getOr(t);return n(e,r),r})(t,r,o,n):r})(t,e,gn,$n):gn(e)},qn=(e,t)=>At(e,t).orThunk((()=>be(e,t).map((e=>e+"px")))),Un=e=>qn(e,"width"),Gn=e=>In(e,Ao,Wo),Kn=e=>{return jn(e)?Ao(e):Bo(t=e,"width",t.dom.offsetWidth);var t},Yn=e=>Pn(e)?gn(e):((e,t,o)=>o(e)/Mt(e,"rowspan"))(e,0,Vn),Jn=(e,t,o)=>{Nt(e,"width",t+o)},Qn=e=>In(e,Ao,Wo)+"%",Xn=g(Wn),Zn=ue("col"),er=e=>Un(e).getOrThunk((()=>Kn(e)+"px")),tr=e=>{return(t=e,qn(t,"height")).getOrThunk((()=>Yn(e)+"px"));var t},or=(e,t,o,n,r,s)=>e.filter(n).fold((()=>s(((e,t)=>{if(t<0||t>=e.length-1)return C.none();const o=e[t].fold((()=>{const o=(e=>{const t=S.call(e,0);return t.reverse(),t})(e.slice(0,t));return V(o,((e,t)=>e.map((e=>({value:e,delta:t+1})))))}),(e=>C.some({value:e,delta:0}))),n=e[t+1].fold((()=>{const o=e.slice(t+1);return V(o,((e,t)=>e.map((e=>({value:e,delta:t+1})))))}),(e=>C.some({value:e,delta:1})));return o.bind((e=>n.map((t=>{const o=t.delta+e.delta;return Math.abs(t.value-e.value)/o}))))})(o,t))),(e=>r(e))),nr=(e,t,o,n)=>{const r=an(e),s=sn(e)?(e=>E(rn(e),(e=>C.from(e.element))))(e):r,l=[C.some(zn.edge(t))].concat(E(zn.positions(r,t),(e=>e.map((e=>e.x))))),a=w(jt);return E(s,((e,t)=>or(e,t,l,a,(e=>{if((e=>{const t=No().browser,o=t.isChromium()||t.isFirefox();return!Zn(e)||o})(e))return o(e);{const e=null!=(s=r[t])?h(s):C.none();return or(e,t,l,a,(e=>n(C.some(Ao(e)))),n)}var s}),n)))},rr=e=>e.map((e=>e+"px")).getOr(""),sr=(e,t,o)=>nr(e,t,Kn,(e=>e.getOrThunk(o.minCellWidth))),lr=(e,t,o,n)=>{const r=mn(e),s=E(e.all,(e=>C.some(e.element))),l=[C.some(_n.edge(t))].concat(E(_n.positions(r,t),(e=>e.map((e=>e.y)))));return E(s,((e,t)=>or(e,t,l,x,o,n)))},ar=(e,t)=>()=>st(e)?t(e):parseFloat(At(e,"width").getOr("0")),cr=e=>{const t=ar(e,(e=>parseFloat(Qn(e)))),o=ar(e,Ao);return{width:t,pixelWidth:o,getWidths:(t,o)=>((e,t,o)=>nr(e,t,Gn,(e=>e.fold((()=>o.minCellWidth()),(e=>e/o.pixelWidth()*100)))))(t,e,o),getCellDelta:e=>e/o()*100,singleColumnWidth:(e,t)=>[100-e],minCellWidth:()=>It()/o()*100,setElementWidth:Hn,adjustTableWidth:o=>{const n=t();Hn(e,n+o/100*n)},isRelative:!0,label:"percent"}},ir=e=>{const t=ar(e,Ao);return{width:t,pixelWidth:t,getWidths:(t,o)=>sr(t,e,o),getCellDelta:h,singleColumnWidth:(e,t)=>[Math.max(It(),e+t)-e],minCellWidth:It,setElementWidth:Fn,adjustTableWidth:o=>{const n=t()+o;Fn(e,n)},isRelative:!1,label:"pixel"}},mr=e=>Un(e).fold((()=>(e=>{const t=ar(e,Ao),o=g(0);return{width:t,pixelWidth:t,getWidths:(t,o)=>sr(t,e,o),getCellDelta:o,singleColumnWidth:g([0]),minCellWidth:o,setElementWidth:f,adjustTableWidth:f,isRelative:!0,label:"none"}})(e)),(t=>((e,t)=>null!==Xn().exec(t)?cr(e):ir(e))(e,t))),dr=ir,ur=cr,fr=(e,t,o)=>{const n=e[o].element,r=xe.fromTag("td");Ie(r,xe.fromTag("br")),(t?Ie:Pe)(n,r)},gr=(e=>{const t=t=>e(t)?C.from(t.dom.nodeValue):C.none();return{get:o=>{if(!e(o))throw new Error("Can only get text value of a text node");return t(o).getOr("")},getOption:t,set:(t,o)=>{if(!e(t))throw new Error("Can only set raw text value of a text node");t.dom.nodeValue=o}}})(ie),hr=e=>gr.get(e),pr=e=>gr.getOption(e),br=(e,t)=>gr.set(e,t),wr=e=>"img"===ne(e)?1:pr(e).fold((()=>Le(e).length),(e=>e.length)),vr=["img","br"],yr=e=>pr(e).filter((e=>0!==e.trim().length||e.indexOf("\xa0")>-1)).isSome()||D(vr,ne(e))||(e=>ae(e)&&"false"===pe(e,"contenteditable"))(e),xr=e=>((e,t)=>{const o=e=>{for(let n=0;nSr(e,yr),Sr=(e,t)=>{const o=e=>{const n=Le(e);for(let e=n.length-1;e>=0;e--){const r=n[e];if(t(r))return C.some(r);const s=o(r);if(s.isSome())return s}return C.none()};return o(e)},Tr={scope:["row","col"]},Rr=e=>()=>{const t=xe.fromTag("td",e.dom);return Ie(t,xe.fromTag("br",e.dom)),t},Dr=e=>()=>xe.fromTag("col",e.dom),Or=e=>()=>xe.fromTag("colgroup",e.dom),kr=e=>()=>xe.fromTag("tr",e.dom),Er=(e,t,o)=>{const n=((e,t)=>{const o=Je(e,t),n=Le(Ye(e));return $e(o,n),o})(e,t);return G(o,((e,t)=>{null===e?we(n,t):ge(n,t,e)})),n},Nr=e=>e,_r=(e,t,o)=>{const n=(e,t)=>{((e,t)=>{const o=e.dom,n=t.dom;kt(o)&&kt(n)&&(n.style.cssText=o.style.cssText)})(e.element,t),Lt(t,"height"),1!==e.colspan&&Lt(t,"width")};return{col:o=>{const r=xe.fromTag(ne(o.element),t.dom);return n(o,r),e(o.element,r),r},colgroup:Or(t),row:kr(t),cell:r=>{const s=xe.fromTag(ne(r.element),t.dom),l=o.getOr(["strong","em","b","i","span","font","h1","h2","h3","h4","h5","h6","p","div"]),a=l.length>0?((e,t,o)=>xr(e).map((n=>{const r=o.join(","),s=it(n,r,(t=>Re(t,e)));return z(s,((e,t)=>{const o=Ke(t);return Ie(e,o),o}),t)})).getOr(t))(r.element,s,l):s;return Ie(a,xe.fromTag("br")),n(r,s),((e,t)=>{G(Tr,((o,n)=>be(e,n).filter((e=>D(o,e))).each((e=>ge(t,n,e)))))})(r.element,s),e(r.element,s),s},replace:Er,colGap:Dr(t),gap:Rr(t)}},Br=e=>({col:Dr(e),colgroup:Or(e),row:kr(e),cell:Rr(e),replace:Nr,colGap:Dr(e),gap:Rr(e)}),zr=e=>t=>t.options.get(e),Ar="100%",Lr=e=>{var t;const o=e.dom,n=null!==(t=o.getParent(e.selection.getStart(),o.isBlock))&&void 0!==t?t:e.getBody();return Wo(xe.fromDom(n))+"px"},Wr=e=>C.from(e.options.get("table_clone_elements")),Mr=zr("table_header_type"),jr=zr("table_column_resizing"),Pr=e=>"preservetable"===jr(e),Ir=e=>"resizetable"===jr(e),Fr=zr("table_sizing_mode"),Hr=e=>"relative"===Fr(e),$r=e=>"fixed"===Fr(e),Vr=e=>"responsive"===Fr(e),qr=zr("table_resize_bars"),Ur=zr("table_style_by_css"),Gr=zr("table_merge_content_on_paste"),Kr=e=>{const t=e.options,o=t.get("table_default_attributes");return t.isSet("table_default_attributes")?o:((e,t)=>Vr(e)||Ur(e)?t:$r(e)?{...t,width:Lr(e)}:{...t,width:Ar})(e,o)},Yr=zr("table_use_colgroups"),Jr=zr("fixed_toolbar_container"),Qr=zr("fixed_toolbar_container_target"),Xr=zr("ui_mode"),Zr=e=>wt(e,"[contenteditable]"),es=(e,t=!1)=>st(e)?e.dom.isContentEditable:Zr(e).fold(g(t),(e=>"true"===ts(e))),ts=e=>e.dom.contentEditable,os=e=>xe.fromDom(e.getBody()),ns=e=>t=>Re(t,os(e)),rs=e=>{we(e,"data-mce-style");const t=e=>we(e,"data-mce-style");N(qt(e),t),N(Ut(e),t),N(Kt(e),t)},ss=e=>xe.fromDom(e.selection.getStart()),ls=e=>e.getBoundingClientRect().width,as=e=>e.getBoundingClientRect().height,cs=e=>(t,o)=>{const n=t.dom.getStyle(o,e)||t.dom.getAttrib(o,e);return C.from(n).filter(Ot)},is=cs("width"),ms=cs("height"),ds=e=>gt(e,ue("table")).exists(es),us=(e,t)=>{const o=t.column,n=t.column+t.colspan-1,r=t.row,s=t.row+t.rowspan-1;return o<=e.finishCol&&n>=e.startCol&&r<=e.finishRow&&s>=e.startRow},fs=(e,t)=>t.column>=e.startCol&&t.column+t.colspan-1<=e.finishCol&&t.row>=e.startRow&&t.row+t.rowspan-1<=e.finishRow,gs=(e,t,o)=>{const n=tn(e,t,Re),r=tn(e,o,Re);return n.bind((e=>r.map((t=>{return o=e,n=t,{startRow:Math.min(o.row,n.row),startCol:Math.min(o.column,n.column),finishRow:Math.max(o.row+o.rowspan-1,n.row+n.rowspan-1),finishCol:Math.max(o.column+o.colspan-1,n.column+n.colspan-1)};var o,n}))))},hs=(e,t,o)=>gs(e,t,o).map((t=>{const o=on(e,b(us,t));return E(o,(e=>e.element))})),ps=(e,t)=>tn(e,t,((e,t)=>De(t,e))).map((e=>e.element)),bs=(e,t,o)=>{const n=vs(e);return hs(n,t,o)},ws=(e,t,o,n,r)=>{const s=vs(e),l=Re(e,o)?C.some(t):ps(s,t),a=Re(e,r)?C.some(n):ps(s,n);return l.bind((e=>a.bind((t=>hs(s,e,t)))))},vs=Xo;var ys=["body","p","div","article","aside","figcaption","figure","footer","header","nav","section","ol","ul","li","table","thead","tbody","tfoot","caption","tr","td","th","h1","h2","h3","h4","h5","h6","blockquote","pre","address"],xs=()=>({up:g({selector:ht,closest:wt,predicate:ft,all:Be}),down:g({selector:dt,predicate:ct}),styles:g({get:Bt,getRaw:At,set:Nt,remove:Lt}),attrs:g({get:pe,set:ge,remove:we,copyTo:(e,t)=>{const o=ve(e);he(t,o)}}),insert:g({before:Me,after:je,afterAll:He,append:Ie,appendAll:$e,prepend:Pe,wrap:Fe}),remove:g({unwrap:Ue,remove:qe}),create:g({nu:xe.fromTag,clone:e=>xe.fromDom(e.dom.cloneNode(!1)),text:xe.fromText}),query:g({comparePosition:(e,t)=>e.dom.compareDocumentPosition(t.dom),prevSibling:ze,nextSibling:Ae}),property:g({children:Le,name:ne,parent:Ne,document:e=>Ee(e).dom,isText:ie,isComment:le,isElement:ce,isSpecial:e=>{const t=ne(e);return D(["script","noscript","iframe","noframes","noembed","title","style","textarea","xmp"],t)},getLanguage:e=>ce(e)?be(e,"lang"):C.none(),getText:hr,setText:br,isBoundary:e=>!!ce(e)&&("body"===ne(e)||D(ys,ne(e))),isEmptyTag:e=>!!ce(e)&&D(["br","img","hr","input"],ne(e)),isNonEditable:e=>ce(e)&&"false"===pe(e,"contenteditable")}),eq:Re,is:Oe});const Cs=(e,t,o,n)=>{const r=t(e,o);return z(n,((o,n)=>{const r=t(e,n);return Ss(e,o,r)}),r)},Ss=(e,t,o)=>t.bind((t=>o.filter(b(e.eq,t)))),Ts=xs(),Rs=(e,t)=>((e,t,o)=>o.length>0?((e,t,o,n)=>n(e,t,o[0],o.slice(1)))(e,t,o,Cs):C.none())(Ts,((t,o)=>e(o)),t),Ds=e=>ht(e,"table"),Os=(e,t,o)=>{const n=e=>t=>void 0!==o&&o(t)||Re(t,e);return Re(e,t)?C.some({boxes:C.some([e]),start:e,finish:t}):Ds(e).bind((r=>Ds(t).bind((s=>{if(Re(r,s))return C.some({boxes:bs(r,e,t),start:e,finish:t});if(De(r,s)){const o=it(t,"td,th",n(r)),l=o.length>0?o[o.length-1]:t;return C.some({boxes:ws(r,e,r,t,s),start:e,finish:l})}if(De(s,r)){const o=it(e,"td,th",n(s)),l=o.length>0?o[o.length-1]:e;return C.some({boxes:ws(s,e,r,t,s),start:e,finish:l})}return((e,t)=>((e,t,o,n=y)=>{const r=[t].concat(e.up().all(t)),s=[o].concat(e.up().all(o)),l=e=>W(e,n).fold((()=>e),(t=>e.slice(0,t+1))),a=l(r),c=l(s),i=L(a,(t=>O(c,((e,t)=>b(e.eq,t))(e,t))));return{firstpath:a,secondpath:c,shared:i}})(Ts,e,t,void 0))(e,t).shared.bind((l=>wt(l,"table",o).bind((o=>{const l=it(t,"td,th",n(o)),a=l.length>0?l[l.length-1]:t,c=it(e,"td,th",n(o)),i=c.length>0?c[c.length-1]:e;return C.some({boxes:ws(o,e,r,t,s),start:i,finish:a})}))))}))))},ks=(e,t)=>{const o=dt(e,t);return o.length>0?C.some(o):C.none()},Es=(e,t,o)=>bt(e,t).bind((t=>bt(e,o).bind((e=>Rs(Ds,[t,e]).map((o=>({first:t,last:e,table:o}))))))),Ns=(e,t,o,n,r)=>((e,t)=>L(e,(e=>Ce(e,t))))(e,r).bind((e=>((e,t,o)=>Gt(e).bind((n=>((e,t,o,n)=>tn(e,t,Re).bind((t=>{const r=o>0?t.row+t.rowspan-1:t.row,s=n>0?t.column+t.colspan-1:t.column;return en(e,r+o,s+n).map((e=>e.element))})))(vs(n),e,t,o))))(e,t,o).bind((e=>((e,t)=>ht(e,"table").bind((o=>bt(o,t).bind((t=>Os(t,e).bind((e=>e.boxes.map((t=>({boxes:t,start:e.start,finish:e.finish}))))))))))(e,n))))),_s=(e,t)=>ks(e,t),Bs=(e,t,o)=>Es(e,t,o).bind((t=>{const o=t=>Re(e,t),n="thead,tfoot,tbody,table",r=ht(t.first,n,o),s=ht(t.last,n,o);return r.bind((e=>s.bind((o=>Re(e,o)?((e,t,o)=>((e,t,o)=>gs(e,t,o).bind((t=>((e,t)=>{let o=!0;const n=b(fs,t);for(let r=t.startRow;r<=t.finishRow;r++)for(let s=t.startCol;s<=t.finishCol;s++)o=o&&en(e,r,s).exists(n);return o?C.some(t):C.none()})(e,t))))(vs(e),t,o))(t.table,t.first,t.last):C.none()))))})),zs=h,As=e=>{const t=(e,t)=>be(e,t).exists((e=>parseInt(e,10)>1));return e.length>0&&P(e,(e=>t(e,"rowspan")||t(e,"colspan")))?C.some(e):C.none()},Ls=(e,t,o)=>t.length<=1?C.none():Bs(e,o.firstSelectedSelector,o.lastSelectedSelector).map((e=>({bounds:e,cells:t}))),Ws="data-mce-selected",Ms="data-mce-first-selected",js="data-mce-last-selected",Ps="["+Ws+"]",Is={selected:Ws,selectedSelector:"td["+Ws+"],th["+Ws+"]",firstSelected:Ms,firstSelectedSelector:"td["+Ms+"],th["+Ms+"]",lastSelected:js,lastSelectedSelector:"td["+js+"],th["+js+"]"},Fs=(e,t,o)=>({element:o,mergable:Ls(t,e,Is),unmergable:As(e),selection:zs(e)}),Hs=e=>(t,o)=>{const n=ne(t),r="col"===n||"colgroup"===n?Gt(s=t).bind((e=>_s(e,Is.firstSelectedSelector))).fold(g(s),(e=>e[0])):t;var s;return wt(r,e,o)},$s=Hs("th,td,caption"),Vs=Hs("th,td"),qs=e=>{return t=e.model.table.getSelectedCells(),E(t,xe.fromDom);var t},Us=(e,t)=>{e.on("BeforeGetContent",(t=>{const o=o=>{t.preventDefault(),(e=>Gt(e[0]).map((e=>{const t=((e,t)=>{const o=e=>Ce(e.element,t),n=Ye(e),r=Xt(n),s=mr(e),l=Zo(r),a=((e,t)=>{const o=e.grid.columns;let n=e.grid.rows,r=o,s=0,l=0;const a=[],c=[];return G(e.access,(e=>{if(a.push(e),t(e)){c.push(e);const t=e.row,o=t+e.rowspan-1,a=e.column,i=a+e.colspan-1;ts&&(s=o),al&&(l=i)}})),((e,t,o,n,r,s)=>({minRow:e,minCol:t,maxRow:o,maxCol:n,allCells:r,selectedCells:s}))(n,r,s,l,a,c)})(l,o),c="th:not("+t+"),td:not("+t+")",i=$t(n,"th,td",(e=>Ce(e,c)));N(i,qe),((e,t,o,n)=>{const r=B(e,(e=>"colgroup"!==e.section)),s=t.grid.columns,l=t.grid.rows;for(let e=0;eo.maxRow||ao.maxCol||(en(t,e,a).filter(n).isNone()?fr(r,l,e):l=!0)}})(r,l,a,o);const m=((e,t,o,n)=>{if(0===n.minCol&&t.grid.columns===n.maxCol+1)return 0;const r=sr(t,e,o),s=A(r,((e,t)=>e+t),0),l=A(r.slice(n.minCol,n.maxCol+1),((e,t)=>e+t),0),a=l/s*o.pixelWidth()-o.pixelWidth();return o.getCellDelta(a)})(e,Xo(e),s,a);return((e,t,o,n)=>{G(o.columns,(e=>{(e.columnt.maxCol)&&qe(e.element)}));const r=B(Ht(e,"tr"),(e=>0===e.dom.childElementCount));N(r,qe),t.minCol!==t.maxCol&&t.minRow!==t.maxRow||N(Ht(e,"th,td"),(e=>{we(e,"rowspan"),we(e,"colspan")})),we(e,Uo),we(e,"data-snooker-col-series"),mr(e).adjustTableWidth(n)})(n,a,l,m),n})(e,Ps);return rs(t),[t]})))(o).each((o=>{const n="text"===t.format?((e,t)=>{const o=e.getDoc(),n=nt(xe.fromDom(e.getBody())),r=xe.fromTag("div",o);ge(r,"data-mce-bogus","all"),_t(r,{position:"fixed",left:"-9999999px",top:"0",overflow:"hidden",opacity:"0"});const s=(e=>ot(e)?e:xe.fromDom(Ee(e).dom.body))(n);$e(r,t),Ie(s,r);const l=r.dom.innerText;return qe(r),l})(e,o):((e,t)=>E(t,(t=>e.selection.serializer.serialize(t.dom,{}))).join(""))(e,o);t.content=n}))};if(!0===t.selection){const t=(e=>B(qs(e),(e=>Ce(e,Is.selectedSelector))))(e);t.length>=1&&o(t)}})),e.on("BeforeSetContent",(o=>{if(!0===o.selection&&!0===o.paste){const n=qs(e);H(n).each((n=>{Gt(n).each((r=>{const s=B((e=>{const t=document.createElement("div");return t.innerHTML=e,Le(xe.fromDom(t))})(o.content),(e=>"meta"!==ne(e))),l=ue("table");if(Gr(e)&&1===s.length&&l(s[0])){o.preventDefault();const l=xe.fromDom(e.getDoc()),a=Br(l),c=((e,t,o)=>({element:e,clipboard:t,generators:o}))(n,s[0],a);t.pasteCells(r,c).each((()=>{e.focus()}))}}))}))}}))},Gs=(e,t)=>({element:e,offset:t}),Ks=(e,t,o)=>e.property().isText(t)&&0===e.property().getText(t).trim().length||e.property().isComment(t)?o(t).bind((t=>Ks(e,t,o).orThunk((()=>C.some(t))))):C.none(),Ys=(e,t)=>e.property().isText(t)?e.property().getText(t).length:e.property().children(t).length,Js=(e,t)=>{const o=Ks(e,t,e.query().prevSibling).getOr(t);if(e.property().isText(o))return Gs(o,Ys(e,o));const n=e.property().children(o);return n.length>0?Js(e,n[n.length-1]):Gs(o,Ys(e,o))},Qs=Js,Xs=xs(),Zs=(e,t)=>{if(!jt(e)){const o=(e=>Un(e).bind((e=>{return t=e,o=["fixed","relative","empty"],C.from(Ln.exec(t)).bind((e=>{const t=Number(e[1]),n=e[2];return((e,t)=>O(t,(t=>O(An[t],(t=>e===t)))))(n,o)?C.some({value:t,unit:n}):C.none()}));var t,o})))(e);o.each((o=>{const n=o.value/2;Jn(e,n,o.unit),Jn(t,n,o.unit)}))}},el=e=>E(e,g(0)),tl=(e,t,o,n,r)=>r(e.slice(0,t)).concat(n).concat(r(e.slice(o))),ol=e=>(t,o,n,r)=>{if(e(n)){const e=Math.max(r,t[o]-Math.abs(n)),s=Math.abs(e-t[o]);return n>=0?s:-s}return n},nl=ol((e=>e<0)),rl=ol(x),sl=()=>{const e=(e,t,o,n)=>{const r=(100+o)/100,s=Math.max(n,(e[t]+o)/r);return E(e,((e,o)=>(o===t?s:e/r)-e))},t=(t,o,n,r,s,l)=>l?e(t,o,r,s):((e,t,o,n,r)=>{const s=nl(e,t,n,r);return tl(e,t,o+1,[s,0],el)})(t,o,n,r,s);return{resizeTable:(e,t)=>e(t),clampTableDelta:nl,calcLeftEdgeDeltas:t,calcMiddleDeltas:(e,o,n,r,s,l,a)=>t(e,n,r,s,l,a),calcRightEdgeDeltas:(t,o,n,r,s,l)=>{if(l)return e(t,n,r,s);{const e=nl(t,n,r,s);return el(t.slice(0,n)).concat([e])}},calcRedestributedWidths:(e,t,o,n)=>{if(n){const n=(t+o)/t,r=E(e,(e=>e/n));return{delta:100*n-100,newSizes:r}}return{delta:o,newSizes:e}}}},ll=()=>{const e=(e,t,o,n,r)=>{const s=rl(e,n>=0?o:t,n,r);return tl(e,t,o+1,[s,-s],el)};return{resizeTable:(e,t,o)=>{o&&e(t)},clampTableDelta:(e,t,o,n,r)=>{if(r){if(o>=0)return o;{const t=A(e,((e,t)=>e+t-n),0);return Math.max(-t,o)}}return nl(e,t,o,n)},calcLeftEdgeDeltas:e,calcMiddleDeltas:(t,o,n,r,s,l)=>e(t,n,r,s,l),calcRightEdgeDeltas:(e,t,o,n,r,s)=>{if(s)return el(e);{const t=n/e.length;return E(e,g(t))}},calcRedestributedWidths:(e,t,o,n)=>({delta:0,newSizes:e})}},al=e=>Xo(e).grid,cl=ue("th"),il=e=>P(e,(e=>cl(e.element))),ml=(e,t)=>e&&t?"sectionCells":e?"section":"cells",dl=e=>{const t="thead"===e.section,o=vt(ul(e.cells),"th");return"tfoot"===e.section?{type:"footer"}:t||o?{type:"header",subType:ml(t,o)}:{type:"body"}},ul=e=>{const t=B(e,(e=>cl(e.element)));return 0===t.length?C.some("td"):t.length===e.length?C.some("th"):C.none()},fl=(e,t,o)=>et(o(e.element,t),!0,e.isLocked),gl=(e,t)=>e.section!==t?tt(e.element,e.cells,t,e.isNew):e,hl=()=>({transformRow:gl,transformCell:(e,t,o)=>{const n=o(e.element,t),r="td"!==ne(n)?(e=>{const t=Je(e,"td");je(e,t);const o=Le(e);return $e(t,o),qe(e),t})(n):n;return et(r,e.isNew,e.isLocked)}}),pl=()=>({transformRow:gl,transformCell:fl}),bl=()=>({transformRow:(e,t)=>gl(e,"thead"===t?"tbody":t),transformCell:fl}),wl=hl,vl=pl,yl=bl,xl=()=>({transformRow:h,transformCell:fl}),Cl=(e,t,o,n)=>{o===n?we(e,t):ge(e,t,o)},Sl=(e,t,o)=>{$(mt(e,t)).fold((()=>Pe(e,o)),(e=>je(e,o)))},Tl=(e,t)=>{const o=[],n=[],r=e=>E(e,(e=>{e.isNew&&o.push(e.element);const t=e.element;return Ve(t),N(e.cells,(e=>{e.isNew&&n.push(e.element),Cl(e.element,"colspan",e.colspan,1),Cl(e.element,"rowspan",e.rowspan,1),Ie(t,e.element)})),t})),s=e=>j(e,(e=>E(e.cells,(e=>(Cl(e.element,"span",e.colspan,1),e.element))))),l=(t,o)=>{const n=((e,t)=>{const o=pt(e,t).getOrThunk((()=>{const o=xe.fromTag(t,ke(e).dom);return"thead"===t?Sl(e,"caption,colgroup",o):"colgroup"===t?Sl(e,"caption",o):Ie(e,o),o}));return Ve(o),o})(e,o),l=("colgroup"===o?s:r)(t);$e(n,l)},a=(t,o)=>{t.length>0?l(t,o):(t=>{pt(e,t).each(qe)})(o)},c=[],i=[],m=[],d=[];return N(t,(e=>{switch(e.section){case"thead":c.push(e);break;case"tbody":i.push(e);break;case"tfoot":m.push(e);break;case"colgroup":d.push(e)}})),a(d,"colgroup"),a(c,"thead"),a(i,"tbody"),a(m,"tfoot"),{newRows:o,newCells:n}},Rl=(e,t)=>{if(0===e.length)return 0;const o=e[0];return W(e,(e=>!t(o.element,e.element))).getOr(e.length)},Dl=(e,t)=>{const o=E(e,(e=>E(e.cells,y)));return E(e,((n,r)=>{const s=j(n.cells,((n,s)=>{if(!1===o[r][s]){const m=((e,t,o,n)=>{const r=((e,t)=>e[t])(e,t),s="colgroup"===r.section,l=Rl(r.cells.slice(o),n),a=s?1:Rl(((e,t)=>E(e,(e=>Fo(e,t))))(e.slice(t),o),n);return{colspan:l,rowspan:a}})(e,r,s,t);return((e,t,n,r)=>{for(let s=e;s({element:e,cells:t,section:o,isNew:n}))(n.element,s,n.section,n.isNew)}))},Ol=(e,t,o)=>{const n=[];N(e.colgroups,(r=>{const s=[];for(let n=0;net(e.element,o,!1))).getOrThunk((()=>et(t.colGap(),!0,!1)));s.push(r)}n.push(tt(r.element,s,"colgroup",o))}));for(let r=0;ret(e.element,o,e.isLocked))).getOrThunk((()=>et(t.gap(),!0,!1)));s.push(l)}const l=e.all[r],a=tt(l.element,s,l.section,o);n.push(a)}return n},kl=e=>Dl(e,Re),El=(e,t)=>V(e.all,(e=>L(e.cells,(e=>Re(t,e.element))))),Nl=(e,t,o)=>{const n=E(t.selection,(t=>Vt(t).bind((t=>El(e,t))).filter(o))),r=yt(n);return xt(r.length>0,r)},_l=(e,t,o,n,r)=>(s,l,a,c)=>{const i=Xo(s),m=C.from(null==c?void 0:c.section).getOrThunk(xl);return t(i,l).map((t=>{const o=((e,t)=>Ol(e,t,!1))(i,a),n=e(o,t,Re,r(a),m),s=Ko(n.grid);return{info:t,grid:kl(n.grid),cursor:n.cursor,lockedColumns:s}})).bind((e=>{const t=Tl(s,e.grid),r=C.from(null==c?void 0:c.sizing).getOrThunk((()=>mr(s))),l=C.from(null==c?void 0:c.resize).getOrThunk(ll);return o(s,e.grid,e.info,{sizing:r,resize:l,section:m}),n(s),we(s,Uo),e.lockedColumns.length>0&&ge(s,Uo,e.lockedColumns.join(",")),C.some({cursor:e.cursor,newRows:t.newRows,newCells:t.newCells})}))},Bl=(e,t)=>Nl(e,t,x).map((e=>({cells:e,generators:t.generators,clipboard:t.clipboard}))),zl=(e,t)=>Nl(e,t,x),Al=(e,t)=>Nl(e,t,(e=>!e.isLocked)),Ll=(e,t)=>P(t,(t=>((e,t)=>El(e,t).exists((e=>!e.isLocked)))(e,t))),Wl=(e,t,o,n)=>{const r=Vo(e).rows;let s=!0;for(let e=0;e{const t=t=>t(e),o=g(e),n=()=>r,r={tag:!0,inner:e,fold:(t,o)=>o(e),isValue:x,isError:y,map:t=>Pl.value(t(e)),mapError:n,bind:t,exists:t,forall:t,getOr:o,or:n,getOrThunk:o,orThunk:n,getOrDie:o,each:t=>{t(e)},toOptional:()=>C.some(e)};return r},jl=e=>{const t=()=>o,o={tag:!1,inner:e,fold:(t,o)=>t(e),isValue:y,isError:x,map:t,mapError:t=>Pl.error(t(e)),bind:t,exists:y,forall:x,getOr:h,or:h,getOrThunk:v,orThunk:v,getOrDie:(n=String(e),()=>{throw new Error(n)}),each:f,toOptional:C.none};var n;return o},Pl={value:Ml,error:jl,fromOption:(e,t)=>e.fold((()=>jl(t)),Ml)},Il=(e,t)=>({rowDelta:0,colDelta:$o(e[0])-$o(t[0])}),Fl=(e,t)=>({rowDelta:e.length-t.length,colDelta:0}),Hl=(e,t,o,n)=>{const r="colgroup"===t.section?o.col:o.cell;return k(e,(e=>et(r(),!0,n(e))))},$l=(e,t,o,n)=>{const r=e[e.length-1];return e.concat(k(t,(()=>{const e="colgroup"===r.section?o.colgroup:o.row,t=qo(r,e,h),s=Hl(t.cells.length,t,o,(e=>X(n,e.toString())));return Io(t,s)})))},Vl=(e,t,o,n)=>E(e,(e=>{const r=Hl(t,e,o,y);return Mo(e,n,r)})),ql=(e,t,o)=>{const n=t.colDelta<0?Vl:h,r=t.rowDelta<0?$l:h,s=Ko(e),l=$o(e[0]),a=O(s,(e=>e===l-1)),c=n(e,Math.abs(t.colDelta),o,a?l-1:l),i=Ko(c);return r(c,Math.abs(t.rowDelta),o,I(i,x))},Ul=(e,t,o,n)=>{const r=b(n,Fo(e[t],o).element),s=e[t];return e.length>1&&$o(s)>1&&(o>0&&r(Ho(s,o-1))||o0&&r(Ho(e[t-1],o))||tB(o,(o=>o>=e.column&&o<=$o(t[0])+e.column)),Kl=(e,t,o,n,r)=>{((e,t,o,n)=>{t>0&&t{const r=e.cells[t-1];let s=0;const l=n();for(;e.cells.length>t+s&&o(r.element,e.cells[t+s].element);)Po(e,t+s,et(l,!0,e.cells[t+s].isLocked)),s++}))})(t,e,r,n.cell);const s=Fl(o,t),l=ql(o,s,n),a=Fl(t,l),c=ql(t,a,n);return E(c,((t,o)=>Mo(t,e,l[o].cells)))},Yl=(e,t,o,n,r)=>{((e,t,o,n)=>{const r=Vo(e).rows;if(t>0&&tA(e,((e,o)=>O(e,(e=>t(e.element,o.element)))?e:e.concat([o])),[]))(r[t-1].cells,o);N(e,(e=>{let s=C.none();for(let l=t;l{Po(a,t,et(e,!0,c.isLocked))})))}}))}})(t,e,r,n.cell);const s=Ko(t),l=Il(t,o),a={...l,colDelta:l.colDelta-s.length},c=ql(t,a,n),{cols:i,rows:m}=Vo(c),d=Ko(c),u=Il(o,t),f={...u,colDelta:u.colDelta+d.length},g=(p=n,b=d,E(o,(e=>A(b,((t,o)=>{const n=Hl(1,e,p,x)[0];return jo(t,o,n)}),e)))),h=ql(g,f,n);var p,b;return[...i,...m.slice(0,e),...h,...m.slice(e,m.length)]},Jl=(e,t,o,n,r)=>{const{rows:s,cols:l}=Vo(e),a=s.slice(0,t),c=s.slice(t);return[...l,...a,((e,t,o,n)=>qo(e,(e=>n(e,o)),t))(s[o],((e,o)=>t>0&&tE(e,(e=>{const s=t>0&&t<$o(e)&&n(Ho(e,t-1),Ho(e,t)),l=((e,t,o,n,r,s,l)=>{if("colgroup"!==o&&n)return Fo(e,t);{const t=Fo(e,r);return et(l(t.element,s),!0,!1)}})(e,t,e.section,s,o,n,r);return jo(e,t,l)})),Xl=(e,t,o,n)=>((e,t,o,n)=>void 0!==Ho(e[t],o)&&t>0&&n(Ho(e[t-1],o),Ho(e[t],o)))(e,t,o,n)||((e,t,o)=>t>0&&o(Ho(e,t-1),Ho(e,t)))(e[t],o,n),Zl=(e,t,o,n)=>{const r=e=>(e=>"row"===e?(e=>Mt(e,"rowspan")>1)(t):jt(t))(e)?`${e}group`:e;return e?cl(t)?r(o):null:n&&cl(t)?r("row"===o?"col":"row"):null},ea=(e,t,o)=>et(o(e.element,t),!0,e.isLocked),ta=(e,t,o,n,r,s,l)=>E(e,((e,a)=>(e=>{const c=e.cells,i=E(c,((e,c)=>{if((e=>O(t,(t=>o(e.element,t.element))))(e)){const t=l(e,a,c)?r(e,o,n):e;return s(t,a,c).each((e=>{var o,n;o=t.element,n={scope:C.from(e)},G(n,((e,t)=>{e.fold((()=>{we(o,t)}),(e=>{fe(o.dom,t,e)}))}))})),t}return e}));return tt(e.element,i,e.section,e.isNew)})(e))),oa=(e,t,o)=>j(e,((n,r)=>Xl(e,r,t,o)?[]:[Fo(n,t)])),na=(e,t,o,n,r)=>{const s=Vo(e).rows,l=j(t,(e=>oa(s,e,n))),a=E(s,(e=>il(e.cells))),c=((e,t)=>P(t,h)&&il(e)?x:(e,o,n)=>!("th"===ne(e.element)&&t[o]))(l,a),i=((e,t)=>(o,n)=>C.some(Zl(e,o.element,"row",t[n])))(o,a);return ta(e,l,n,r,ea,i,c)},ra=(e,t,o,n)=>{const r=Vo(e).rows,s=E(t,(e=>Fo(r[e.row],e.column)));return ta(e,s,o,n,ea,C.none,x)},sa=e=>{if(!l(e))throw new Error("cases must be an array");if(0===e.length)throw new Error("there must be at least one case");const t=[],o={};return N(e,((n,r)=>{const s=q(n);if(1!==s.length)throw new Error("one and only one name per case");const a=s[0],c=n[a];if(void 0!==o[a])throw new Error("duplicate key detected:"+a);if("cata"===a)throw new Error("cannot have a case named cata (sorry)");if(!l(c))throw new Error("case arguments must be an array");t.push(a),o[a]=(...o)=>{const n=o.length;if(n!==c.length)throw new Error("Wrong number of arguments to case "+a+". Expected "+c.length+" ("+c+"), got "+n);return{fold:(...t)=>{if(t.length!==e.length)throw new Error("Wrong number of arguments to fold. Expected "+e.length+", got "+t.length);return t[r].apply(null,o)},match:e=>{const n=q(e);if(t.length!==n.length)throw new Error("Wrong number of arguments to match. Expected: "+t.join(",")+"\nActual: "+n.join(","));if(!P(t,(e=>D(n,e))))throw new Error("Not all branches were specified when using match. Specified: "+n.join(", ")+"\nRequired: "+t.join(", "));return e[a].apply(null,o)},log:e=>{console.log(e,{constructors:t,constructor:a,params:o})}}}})),o},la={...sa([{none:[]},{only:["index"]},{left:["index","next"]},{middle:["prev","index","next"]},{right:["prev","index"]}])},aa=(e,t,o)=>{const n=((e,t)=>sn(e)?((e,t)=>{const o=rn(e);return E(o,((e,o)=>({element:e.element,width:t[o],colspan:e.colspan})))})(e,t):((e,t)=>{const o=nn(e);return E(o,(e=>{const o=((e,t,o)=>{let n=0;for(let r=e;r{o.setElementWidth(e.element,e.width)}))},ca=(e,t,o,n,r)=>{const s=Xo(e),l=r.getCellDelta(t),a=r.getWidths(s,r),c=o===s.grid.columns-1,i=n.clampTableDelta(a,o,l,r.minCellWidth(),c),m=((e,t,o,n,r)=>{const s=e.slice(0),l=((e,t)=>0===e.length?la.none():1===e.length?la.only(0):0===t?la.left(0,1):t===e.length-1?la.right(t-1,t):t>0&&tn.singleColumnWidth(s[e],o)),((e,t)=>r.calcLeftEdgeDeltas(s,e,t,o,n.minCellWidth(),n.isRelative)),((e,t,l)=>r.calcMiddleDeltas(s,e,t,l,o,n.minCellWidth(),n.isRelative)),((e,t)=>r.calcRightEdgeDeltas(s,e,t,o,n.minCellWidth(),n.isRelative)))})(a,o,i,r,n),d=E(m,((e,t)=>e+a[t]));aa(s,d,r),n.resizeTable(r.adjustTableWidth,i,c)},ia=(e,t,o)=>{const n=Xo(e),r=((e,t)=>lr(e,t,Yn,(e=>e.getOrThunk(Ft))))(n,e),s=E(r,((e,n)=>o===n?Math.max(t+e,Ft()):e)),l=((e,t)=>E(e.all,((e,o)=>({element:e.element,height:t[o]}))))(n,s);N(l,(e=>{$n(e.element,e.height)})),N(nn(n),(e=>{(e=>{Lt(e,"height")})(e.element)}));const a=z(s,((e,t)=>e+t),0);$n(e,a)},ma=e=>A(e,((e,t)=>O(e,(e=>e.column===t.column))?e:e.concat([t])),[]).sort(((e,t)=>e.column-t.column)),da=ue("col"),ua=ue("colgroup"),fa=e=>"tr"===ne(e)||ua(e),ga=e=>({element:e,colspan:Wt(e,"colspan",1),rowspan:Wt(e,"rowspan",1)}),ha=e=>be(e,"scope").map((e=>e.substr(0,3))),pa=(e,t=ga)=>{const o=o=>{if(fa(o))return ua((r={element:o}).element)?e.colgroup(r):e.row(r);{const r=o,s=(t=>da(t.element)?e.col(t):e.cell(t))(t(r));return n=C.some({item:r,replacement:s}),s}var r};let n=C.none();return{getOrInit:(e,t)=>n.fold((()=>o(e)),(n=>t(e,n.item)?n.replacement:o(e)))}},ba=e=>t=>{const o=[],n=n=>{const r="td"===e?{scope:null}:{},s=t.replace(n,e,r);return o.push({item:n,sub:s}),s};return{replaceOrInit:(e,t)=>{if(fa(e)||da(e))return e;{const r=e;return((e,t)=>L(o,(o=>t(o.item,e))))(r,t).fold((()=>n(r)),(o=>t(e,o.item)?o.sub:n(r)))}}}},wa=e=>({unmerge:t=>{const o=ha(t);return o.each((e=>ge(t,"scope",e))),()=>{const n=e.cell({element:t,colspan:1,rowspan:1});return Lt(n,"width"),Lt(t,"width"),o.each((e=>ge(n,"scope",e))),n}},merge:e=>(Lt(e[0],"width"),(()=>{const t=yt(E(e,ha));if(0===t.length)return C.none();{const e=t[0],o=["row","col"];return O(t,(t=>t!==e&&D(o,t)))?C.none():C.from(e)}})().fold((()=>we(e[0],"scope")),(t=>ge(e[0],"scope",t+"group"))),g(e[0]))}),va=["body","p","div","article","aside","figcaption","figure","footer","header","nav","section","ol","ul","table","thead","tfoot","tbody","caption","tr","td","th","h1","h2","h3","h4","h5","h6","blockquote","pre","address"],ya=xs(),xa=e=>((e,t)=>{const o=e.property().name(t);return D(va,o)})(ya,e),Ca=e=>((e,t)=>{const o=e.property().name(t);return D(["ol","ul"],o)})(ya,e),Sa=e=>{const t=ue("br"),o=e=>Cr(e).bind((o=>{const n=Ae(o).map((e=>!!xa(e)||!!((e,t)=>D(["br","img","hr","input"],e.property().name(t)))(ya,e)&&"img"!==ne(e))).getOr(!1);return Ne(o).map((r=>{return!0===n||("li"===ne(s=r)||ft(s,Ca).isSome())||t(o)||xa(r)&&!Re(e,r)?[]:[xe.fromTag("br")];var s}))})).getOr([]),n=(()=>{const n=j(e,(e=>{const n=Le(e);return(e=>P(e,(e=>t(e)||ie(e)&&0===hr(e).trim().length)))(n)?[]:n.concat(o(e))}));return 0===n.length?[xe.fromTag("br")]:n})();Ve(e[0]),$e(e[0],n)},Ta=e=>es(e,!0),Ra=e=>{0===qt(e).length&&qe(e)},Da=(e,t)=>({grid:e,cursor:t}),Oa=(e,t,o)=>{const n=((e,t,o)=>{var n,r;const s=Vo(e).rows;return C.from(null===(r=null===(n=s[t])||void 0===n?void 0:n.cells[o])||void 0===r?void 0:r.element).filter(Ta).orThunk((()=>(e=>V(e,(e=>V(e.cells,(e=>{const t=e.element;return xt(Ta(t),t)})))))(s)))})(e,t,o);return Da(e,n)},ka=e=>A(e,((e,t)=>O(e,(e=>e.row===t.row))?e:e.concat([t])),[]).sort(((e,t)=>e.row-t.row)),Ea=(e,t)=>(o,n,r,s,l)=>{const a=ka(n),c=E(a,(e=>e.row)),i=((e,t,o,n,r,s,l)=>{const{cols:a,rows:c}=Vo(e),i=c[t[0]],m=j(t,(e=>((e,t,o)=>{const n=e[t];return j(n.cells,((n,r)=>Xl(e,t,r,o)?[]:[n]))})(c,e,r))),d=E(i.cells,((e,t)=>il(oa(c,t,r)))),u=[...c];N(t,(e=>{u[e]=l.transformRow(c[e],o)}));const f=[...a,...u],g=((e,t)=>P(t,h)&&il(e.cells)?x:(e,o,n)=>!("th"===ne(e.element)&&t[n]))(i,d),p=((e,t)=>(o,n,r)=>C.some(Zl(e,o.element,"col",t[r])))(n,d);return ta(f,m,r,s,l.transformCell,p,g)})(o,c,e,t,r,s.replaceOrInit,l);return Oa(i,n[0].row,n[0].column)},Na=Ea("thead",!0),_a=Ea("tbody",!1),Ba=Ea("tfoot",!1),za=(e,t,o)=>{const n=((e,t)=>Jt(e,(()=>t)))(e,o.section),r=Zo(n);return Ol(r,t,!0)},Aa=(e,t,o,n)=>((e,t,o,n)=>{const r=Zo(t),s=n.getWidths(r,n);aa(r,s,n)})(0,t,0,n.sizing),La=(e,t,o,n)=>((e,t,o,n,r)=>{const s=Zo(t),l=n.getWidths(s,n),a=n.pixelWidth(),{newSizes:c,delta:i}=r.calcRedestributedWidths(l,a,o.pixelDelta,n.isRelative);aa(s,c,n),n.adjustTableWidth(i)})(0,t,o,n.sizing,n.resize),Wa=(e,t)=>O(t,(e=>0===e.column&&e.isLocked)),Ma=(e,t)=>O(t,(t=>t.column+t.colspan>=e.grid.columns&&t.isLocked)),ja=(e,t)=>{const o=an(e),n=ma(t);return A(n,((e,t)=>e+o[t.column].map(Lo).getOr(0)),0)},Pa=e=>(t,o)=>zl(t,o).filter((o=>!(e?Wa:Ma)(t,o))).map((e=>({details:e,pixelDelta:ja(t,e)}))),Ia=e=>(t,o)=>Bl(t,o).filter((o=>!(e?Wa:Ma)(t,o.cells))),Fa=ba("th"),Ha=ba("td"),$a=_l(((e,t,o,n)=>{const r=t[0].row,s=ka(t),l=z(s,((e,t)=>({grid:Jl(e.grid,r,t.row+e.delta,o,n.getOrInit),delta:e.delta+1})),{grid:e,delta:0}).grid;return Oa(l,r,t[0].column)}),zl,f,f,pa),Va=_l(((e,t,o,n)=>{const r=ka(t),s=r[r.length-1],l=s.row+s.rowspan,a=z(r,((e,t)=>Jl(e,l,t.row,o,n.getOrInit)),e);return Oa(a,l,t[0].column)}),zl,f,f,pa),qa=_l(((e,t,o,n)=>{const r=t.details,s=ma(r),l=s[0].column,a=z(s,((e,t)=>({grid:Ql(e.grid,l,t.column+e.delta,o,n.getOrInit),delta:e.delta+1})),{grid:e,delta:0}).grid;return Oa(a,r[0].row,l)}),Pa(!0),La,f,pa),Ua=_l(((e,t,o,n)=>{const r=t.details,s=r[r.length-1],l=s.column+s.colspan,a=ma(r),c=z(a,((e,t)=>Ql(e,l,t.column,o,n.getOrInit)),e);return Oa(c,r[0].row,l)}),Pa(!1),La,f,pa),Ga=_l(((e,t,o,n)=>{const r=ma(t.details),s=((e,t)=>j(e,(e=>{const o=e.cells,n=z(t,((e,t)=>t>=0&&t0?[tt(e.element,n,e.section,e.isNew)]:[]})))(e,E(r,(e=>e.column))),l=s.length>0?s[0].cells.length-1:0;return Oa(s,r[0].row,Math.min(r[0].column,l))}),((e,t)=>Al(e,t).map((t=>({details:t,pixelDelta:-ja(e,t)})))),La,Ra,pa),Ka=_l(((e,t,o,n)=>{const r=ka(t),s=((e,t,o)=>{const{rows:n,cols:r}=Vo(e);return[...r,...n.slice(0,t),...n.slice(o+1)]})(e,r[0].row,r[r.length-1].row),l=Math.max(Vo(s).rows.length-1,0);return Oa(s,Math.min(t[0].row,l),t[0].column)}),zl,f,Ra,pa),Ya=_l(((e,t,o,n)=>{const r=ma(t),s=E(r,(e=>e.column)),l=na(e,s,!0,o,n.replaceOrInit);return Oa(l,t[0].row,t[0].column)}),Al,f,f,Fa),Ja=_l(((e,t,o,n)=>{const r=ma(t),s=E(r,(e=>e.column)),l=na(e,s,!1,o,n.replaceOrInit);return Oa(l,t[0].row,t[0].column)}),Al,f,f,Ha),Qa=_l(Na,zl,f,f,Fa),Xa=_l(_a,zl,f,f,Ha),Za=_l(Ba,zl,f,f,Ha),ec=_l(((e,t,o,n)=>{const r=ra(e,t,o,n.replaceOrInit);return Oa(r,t[0].row,t[0].column)}),Al,f,f,Fa),tc=_l(((e,t,o,n)=>{const r=ra(e,t,o,n.replaceOrInit);return Oa(r,t[0].row,t[0].column)}),Al,f,f,Ha),oc=_l(((e,t,o,n)=>{const r=t.cells;Sa(r);const s=((e,t,o,n)=>{const r=Vo(e).rows;if(0===r.length)return e;for(let e=t.startRow;e<=t.finishRow;e++)for(let o=t.startCol;o<=t.finishCol;o++){const t=r[e],s=Fo(t,o).isLocked;Po(t,o,et(n(),!1,s))}return e})(e,t.bounds,0,n.merge(r));return Da(s,C.from(r[0]))}),((e,t)=>((e,t)=>t.mergable)(0,t).filter((t=>Ll(e,t.cells)))),Aa,f,wa),nc=_l(((e,t,o,n)=>{const r=z(t,((e,t)=>Wl(e,t,o,n.unmerge(t))),e);return Da(r,C.from(t[0]))}),((e,t)=>((e,t)=>t.unmergable)(0,t).filter((t=>Ll(e,t)))),Aa,f,wa),rc=_l(((e,t,o,n)=>{const r=((e,t)=>{const o=Xo(e);return Ol(o,t,!0)})(t.clipboard,t.generators);var s,l;return((e,t,o,n,r)=>{const s=Ko(t),l=((e,t,o)=>{const n=$o(t[0]),r=Vo(t).cols.length+e.row,s=k(n-e.column,(t=>t+e.column));return{row:r,column:L(s,(e=>P(o,(t=>t!==e)))).getOr(n-1)}})(e,t,s),a=Vo(o).rows,c=Gl(l,a,s),i=((e,t,o)=>{if(e.row>=t.length||e.column>$o(t[0]))return Pl.error("invalid start address out of table bounds, row: "+e.row+", column: "+e.column);const n=t.slice(e.row),r=n[0].cells.slice(e.column),s=$o(o[0]),l=o.length;return Pl.value({rowDelta:n.length-l,colDelta:r.length-s})})(l,t,a);return i.map((e=>{const o={...e,colDelta:e.colDelta-c.length},s=ql(t,o,n),i=Ko(s),m=Gl(l,a,i);return((e,t,o,n,r,s)=>{const l=e.row,a=e.column,c=l+o.length,i=a+$o(o[0])+s.length,m=I(s,x);for(let e=l;eDa(e,C.some(t.element))),(e=>Oa(e,t.row,t.column)))}),((e,t)=>Vt(t.element).bind((o=>El(e,o).map((e=>({...e,generators:t.generators,clipboard:t.clipboard})))))),Aa,f,pa),sc=_l(((e,t,o,n)=>{const r=Vo(e).rows,s=t.cells[0].column,l=r[t.cells[0].row],a=za(t.clipboard,t.generators,l),c=Kl(s,e,a,t.generators,o);return Oa(c,t.cells[0].row,t.cells[0].column)}),Ia(!0),f,f,pa),lc=_l(((e,t,o,n)=>{const r=Vo(e).rows,s=t.cells[t.cells.length-1].column+t.cells[t.cells.length-1].colspan,l=r[t.cells[0].row],a=za(t.clipboard,t.generators,l),c=Kl(s,e,a,t.generators,o);return Oa(c,t.cells[0].row,s)}),Ia(!1),f,f,pa),ac=_l(((e,t,o,n)=>{const r=Vo(e).rows,s=t.cells[0].row,l=r[s],a=za(t.clipboard,t.generators,l),c=Yl(s,e,a,t.generators,o);return Oa(c,t.cells[0].row,t.cells[0].column)}),Bl,f,f,pa),cc=_l(((e,t,o,n)=>{const r=Vo(e).rows,s=t.cells[t.cells.length-1].row+t.cells[t.cells.length-1].rowspan,l=r[t.cells[0].row],a=za(t.clipboard,t.generators,l),c=Yl(s,e,a,t.generators,o);return Oa(c,s,t.cells[0].column)}),Bl,f,f,pa),ic=(e,t)=>{const o=Xo(e);return zl(o,t).bind((e=>{const t=e[e.length-1],n=e[0].column,r=t.column+t.colspan,s=M(E(o.all,(e=>B(e.cells,(e=>e.column>=n&&e.column{const o=Xo(e);return zl(o,t).bind(ul).getOr("")},dc=(e,t)=>{const o=Xo(e);return zl(o,t).bind((e=>{const t=e[e.length-1],n=e[0].row,r=t.row+t.rowspan;return(e=>{const t=E(e,(e=>dl(e).type)),o=D(t,"header"),n=D(t,"footer");if(o||n){const e=D(t,"body");return!o||e||n?o||e||!n?C.none():C.some("footer"):C.some("header")}return C.some("body")})(o.all.slice(n,r))})).getOr("")},uc=(e,t)=>e.dispatch("NewRow",{node:t}),fc=(e,t)=>e.dispatch("NewCell",{node:t}),gc=(e,t,o)=>{e.dispatch("TableModified",{...o,table:t})},hc={structure:!1,style:!0},pc={structure:!0,style:!1},bc={structure:!0,style:!0},wc=(e,t)=>Hr(e)?ur(t):$r(e)?dr(t):mr(t),vc=(e,t,o)=>{const n=e=>"table"===ne(os(e)),r=Wr(e),s=Ir(e)?f:Zs,l=t=>{switch(Mr(e)){case"section":return wl();case"sectionCells":return vl();case"cells":return yl();default:return((e,t)=>{var o;switch((o=Xo(e),V(o.all,(e=>{const t=dl(e);return"header"===t.type?C.from(t.subType):C.none()}))).getOr(t)){case"section":return hl();case"sectionCells":return pl();case"cells":return bl()}})(t,"section")}},a=(n,s,a,c)=>(i,m,d=!1)=>{rs(i);const u=xe.fromDom(e.getDoc()),f=_r(a,u,r),g={sizing:wc(e,i),resize:Ir(e)?sl():ll(),section:l(i)};return s(i)?n(i,m,f,g).bind((n=>{t.refresh(i.dom),N(n.newRows,(t=>{uc(e,t.dom)})),N(n.newCells,(t=>{fc(e,t.dom)}));const r=((t,n)=>n.cursor.fold((()=>{const n=qt(t);return H(n).filter(st).map((n=>{o.clearSelectedCells(t.dom);const r=e.dom.createRng();return r.selectNode(n.dom),e.selection.setRng(r),ge(n,"data-mce-selected","1"),r}))}),(n=>{const r=Qs(Xs,n),s=e.dom.createRng();return s.setStart(r.element.dom,r.offset),s.setEnd(r.element.dom,r.offset),e.selection.setRng(s),o.clearSelectedCells(t.dom),C.some(s)})))(i,n);return st(i)&&(rs(i),d||gc(e,i.dom,c)),r.map((e=>({rng:e,effect:c})))})):C.none()},c=a(Ka,(t=>!n(e)||al(t).rows>1),f,pc),i=a(Ga,(t=>!n(e)||al(t).columns>1),f,pc);return{deleteRow:c,deleteColumn:i,insertRowsBefore:a($a,x,f,pc),insertRowsAfter:a(Va,x,f,pc),insertColumnsBefore:a(qa,x,s,pc),insertColumnsAfter:a(Ua,x,s,pc),mergeCells:a(oc,x,f,pc),unmergeCells:a(nc,x,f,pc),pasteColsBefore:a(sc,x,f,pc),pasteColsAfter:a(lc,x,f,pc),pasteRowsBefore:a(ac,x,f,pc),pasteRowsAfter:a(cc,x,f,pc),pasteCells:a(rc,x,f,bc),makeCellsHeader:a(ec,x,f,pc),unmakeCellsHeader:a(tc,x,f,pc),makeColumnsHeader:a(Ya,x,f,pc),unmakeColumnsHeader:a(Ja,x,f,pc),makeRowsHeader:a(Qa,x,f,pc),makeRowsBody:a(Xa,x,f,pc),makeRowsFooter:a(Za,x,f,pc),getTableRowType:dc,getTableCellType:mc,getTableColType:ic}},yc=(e,t,o)=>{const n=Wt(e,t,1);1===o||n<=1?we(e,t):ge(e,t,Math.min(o,n))},xc=(e,t)=>o=>{const n=o.column+o.colspan-1,r=o.column;return n>=e&&r{const n=o.substring(0,o.length-e.length),r=parseFloat(n);return n===r.toString()?t(r):Cc.invalid(o)},Tc={...Cc,from:e=>Rt(e,"%")?Sc("%",Cc.percent,e):Rt(e,"px")?Sc("px",Cc.pixels,e):Cc.invalid(e)},Rc=(e,t,o)=>{const n=Tc.from(o),r=P(e,(e=>"0px"===e))?((e,t)=>{const o=e.fold((()=>g("")),(e=>g(e/t+"px")),(()=>g(100/t+"%")));return k(t,o)})(n,e.length):((e,t,o)=>e.fold((()=>t),(e=>((e,t,o)=>{const n=o/t;return E(e,(e=>Tc.from(e).fold((()=>e),(e=>e*n+"px"),(e=>e/100*o+"px"))))})(t,o,e)),(e=>((e,t)=>E(e,(e=>Tc.from(e).fold((()=>e),(e=>e/t*100+"%"),(e=>e+"%")))))(t,o))))(n,e,t);return kc(r)},Dc=(e,t)=>0===e.length?t:z(e,((e,t)=>Tc.from(t).fold(g(0),h,h)+e),0),Oc=(e,t)=>Tc.from(e).fold(g(e),(e=>e+t+"px"),(e=>e+t+"%")),kc=e=>{if(0===e.length)return e;const t=z(e,((e,t)=>{const o=Tc.from(t).fold((()=>({value:t,remainder:0})),(e=>(e=>{const t=Math.floor(e);return{value:t+"px",remainder:e-t}})(e)),(e=>({value:e+"%",remainder:0})));return{output:[o.value].concat(e.output),remainder:e.remainder+o.remainder}}),{output:[],remainder:0}),o=t.output;return o.slice(0,o.length-1).concat([Oc(o[o.length-1],Math.round(t.remainder))])},Ec=Tc.from,Nc=(e,t,o)=>{const n=Xo(e),r=n.all,s=nn(n),l=rn(n);t.each((t=>{const o=Ec(t).fold(g("px"),g("px"),g("%")),r=Ao(e),a=((e,t)=>nr(e,t,er,rr))(n,e),c=Rc(a,r,t);sn(n)?((e,t,o)=>{N(t,((t,n)=>{const r=Dc([e[n]],It());Nt(t.element,"width",r+o)}))})(c,l,o):((e,t,o)=>{N(t,(t=>{const n=e.slice(t.column,t.colspan+t.column),r=Dc(n,It());Nt(t.element,"width",r+o)}))})(c,s,o),Nt(e,"width",t)})),o.each((t=>{const o=gn(e),l=((e,t)=>lr(e,t,tr,rr))(n,e);((e,t,o)=>{N(o,(e=>{Lt(e.element,"height")})),N(t,((t,o)=>{Nt(t.element,"height",e[o])}))})(Rc(l,o,t),r,s),Nt(e,"height",t)}))},_c=e=>Un(e).exists((e=>Wn.test(e))),Bc=e=>Un(e).exists((e=>Mn.test(e))),zc=e=>Un(e).isNone(),Ac=e=>{we(e,"width"),we(e,"height")},Lc=e=>{const t=Qn(e);Nc(e,C.some(t),C.none()),Ac(e)},Wc=e=>{const t=(e=>Ao(e)+"px")(e);Nc(e,C.some(t),C.none()),Ac(e)},Mc=e=>{Lt(e,"width");const t=Ut(e),o=t.length>0?t:qt(e);N(o,(e=>{Lt(e,"width"),Ac(e)})),Ac(e)},jc={styles:{"border-collapse":"collapse",width:"100%"},attributes:{border:"1"},colGroups:!1},Pc=(e,t,o,n)=>k(e,(e=>((e,t,o,n)=>{const r=xe.fromTag("tr");for(let s=0;s{e.selection.select(t.dom,!0),e.selection.collapse(!0)},Fc=(e,t,o,n,s)=>{const l=(e=>{const t=e.options,o=t.get("table_default_styles");return t.isSet("table_default_styles")?o:((e,t)=>Vr(e)||!Ur(e)?t:$r(e)?{...t,width:Lr(e)}:{...t,width:Ar})(e,o)})(e),a={styles:l,attributes:Kr(e),colGroups:Yr(e)};return e.undoManager.ignore((()=>{const r=((e,t,o,n,r,s=jc)=>{const l=xe.fromTag("table"),a="cells"!==r;_t(l,s.styles),he(l,s.attributes),s.colGroups&&Ie(l,(e=>{const t=xe.fromTag("colgroup");return k(e,(()=>Ie(t,xe.fromTag("col")))),t})(t));const c=Math.min(e,o);if(a&&o>0){const e=xe.fromTag("thead");Ie(l,e);const s=Pc(o,t,"sectionCells"===r?c:0,n);$e(e,s)}const i=xe.fromTag("tbody");Ie(l,i);const m=Pc(a?e-c:e,t,a?0:o,n);return $e(i,m),l})(o,t,s,n,Mr(e),a);ge(r,"data-mce-id","__mce");const l=(e=>{const t=xe.fromTag("div"),o=xe.fromDom(e.dom.cloneNode(!0));return Ie(t,o),(e=>e.dom.innerHTML)(t)})(r);e.insertContent(l),e.addVisual()})),bt(os(e),'table[data-mce-id="__mce"]').map((t=>($r(e)?Wc(t):Vr(e)?Mc(t):(Hr(e)||(e=>r(e)&&-1!==e.indexOf("%"))(l.width))&&Lc(t),rs(t),we(t,"data-mce-id"),((e,t)=>{N(dt(t,"tr"),(t=>{uc(e,t.dom),N(dt(t,"th,td"),(t=>{fc(e,t.dom)}))}))})(e,t),((e,t)=>{bt(t,"td,th").each(b(Ic,e))})(e,t),t.dom))).getOrNull()};var Hc=tinymce.util.Tools.resolve("tinymce.FakeClipboard");const $c="x-tinymce/dom-table-",Vc=$c+"rows",qc=$c+"columns",Uc=e=>{const t=Hc.FakeClipboardItem(e);Hc.write([t])},Gc=e=>{var t;const o=null!==(t=Hc.read())&&void 0!==t?t:[];return V(o,(t=>C.from(t.getType(e))))},Kc=e=>{Gc(e).isSome()&&Hc.clear()},Yc=e=>{e.fold(Qc,(e=>Uc({[Vc]:e})))},Jc=()=>Gc(Vc),Qc=()=>Kc(Vc),Xc=e=>{e.fold(ei,(e=>Uc({[qc]:e})))},Zc=()=>Gc(qc),ei=()=>Kc(qc),ti=e=>$s(ss(e),ns(e)).filter(ds),oi=(e,t)=>{const o=ns(e),n=e=>Gt(e,o),l=t=>(e=>Vs(ss(e),ns(e)).filter(ds))(e).bind((e=>n(e).map((o=>t(o,e))))),a=t=>{e.focus()},c=(t,o=!1)=>l(((n,r)=>{const s=Fs(qs(e),n,r);t(n,s,o).each(a)})),i=()=>l(((t,o)=>((e,t,o)=>{const n=Xo(e);return zl(n,t).bind((e=>{const t=Ol(n,o,!1),r=Vo(t).rows.slice(e[0].row,e[e.length-1].row+e[e.length-1].rowspan),s=j(r,(e=>{const t=B(e.cells,(e=>!e.isLocked));return t.length>0?[{...e,cells:t}]:[]})),l=kl(s);return xt(l.length>0,l)})).map((e=>E(e,(e=>{const t=Ke(e.element);return N(e.cells,(e=>{const o=Ye(e.element);Cl(o,"colspan",e.colspan,1),Cl(o,"rowspan",e.rowspan,1),Ie(t,o)})),t}))))})(t,Fs(qs(e),t,o),_r(f,xe.fromDom(e.getDoc()),C.none())))),m=()=>l(((t,o)=>((e,t)=>{const o=Xo(e);return Al(o,t).map((e=>{const t=e[e.length-1],n=e[0].column,r=t.column+t.colspan,s=((e,t,o)=>{if(sn(e)){const n=B(rn(e),xc(t,o)),r=E(n,(e=>{const n=Ye(e.element);return yc(n,"span",o-t),n})),s=xe.fromTag("colgroup");return $e(s,r),[s]}return[]})(o,n,r),l=((e,t,o)=>E(e.all,(e=>{const n=B(e.cells,xc(t,o)),r=E(n,(e=>{const n=Ye(e.element);return yc(n,"colspan",o-t),n})),s=xe.fromTag("tr");return $e(s,r),s})))(o,n,r);return[...s,...l]}))})(t,Fs(qs(e),t,o)))),d=(t,o)=>o().each((o=>{const n=E(o,(e=>Ye(e)));l(((o,r)=>{const s=Br(xe.fromDom(e.getDoc())),l=((e,t,o,n)=>({selection:zs(e),clipboard:o,generators:n}))(qs(e),0,n,s);t(o,l).each(a)}))})),g=e=>(t,o)=>((e,t)=>X(e,t)?C.from(e[t]):C.none())(o,"type").each((t=>{c(e(t),o.no_events)}));G({mceTableSplitCells:()=>c(t.unmergeCells),mceTableMergeCells:()=>c(t.mergeCells),mceTableInsertRowBefore:()=>c(t.insertRowsBefore),mceTableInsertRowAfter:()=>c(t.insertRowsAfter),mceTableInsertColBefore:()=>c(t.insertColumnsBefore),mceTableInsertColAfter:()=>c(t.insertColumnsAfter),mceTableDeleteCol:()=>c(t.deleteColumn),mceTableDeleteRow:()=>c(t.deleteRow),mceTableCutCol:()=>m().each((e=>{Xc(e),c(t.deleteColumn)})),mceTableCutRow:()=>i().each((e=>{Yc(e),c(t.deleteRow)})),mceTableCopyCol:()=>m().each((e=>Xc(e))),mceTableCopyRow:()=>i().each((e=>Yc(e))),mceTablePasteColBefore:()=>d(t.pasteColsBefore,Zc),mceTablePasteColAfter:()=>d(t.pasteColsAfter,Zc),mceTablePasteRowBefore:()=>d(t.pasteRowsBefore,Jc),mceTablePasteRowAfter:()=>d(t.pasteRowsAfter,Jc),mceTableDelete:()=>ti(e).each((t=>{Gt(t,o).filter(w(o)).each((t=>{const o=xe.fromText("");if(je(t,o),qe(t),e.dom.isEmpty(e.getBody()))e.setContent(""),e.selection.setCursorLocation();else{const t=e.dom.createRng();t.setStart(o.dom,0),t.setEnd(o.dom,0),e.selection.setRng(t),e.nodeChanged()}}))})),mceTableCellToggleClass:(t,o)=>{l((t=>{const n=qs(e),r=P(n,(t=>e.formatter.match("tablecellclass",{value:o},t.dom))),s=r?e.formatter.remove:e.formatter.apply;N(n,(e=>s("tablecellclass",{value:o},e.dom))),gc(e,t.dom,hc)}))},mceTableToggleClass:(t,o)=>{l((t=>{e.formatter.toggle("tableclass",{value:o},t.dom),gc(e,t.dom,hc)}))},mceTableToggleCaption:()=>{ti(e).each((t=>{Gt(t,o).each((o=>{pt(o,"caption").fold((()=>{const t=xe.fromTag("caption");Ie(t,xe.fromText("Caption")),((e,t)=>{We(e,0).fold((()=>{Ie(e,t)}),(e=>{Me(e,t)}))})(o,t),e.selection.setCursorLocation(t.dom,0)}),(n=>{ue("caption")(t)&&Te("td",o).each((t=>e.selection.setCursorLocation(t.dom,0))),qe(n)})),gc(e,o.dom,pc)}))}))},mceTableSizingMode:(t,n)=>(t=>ti(e).each((n=>{Vr(e)||$r(e)||Hr(e)||Gt(n,o).each((o=>{"relative"!==t||_c(o)?"fixed"!==t||Bc(o)?"responsive"!==t||zc(o)||Mc(o):Wc(o):Lc(o),rs(o),gc(e,o.dom,pc)}))})))(n),mceTableCellType:g((e=>"th"===e?t.makeCellsHeader:t.unmakeCellsHeader)),mceTableColType:g((e=>"th"===e?t.makeColumnsHeader:t.unmakeColumnsHeader)),mceTableRowType:g((e=>{switch(e){case"header":return t.makeRowsHeader;case"footer":return t.makeRowsFooter;default:return t.makeRowsBody}}))},((t,o)=>e.addCommand(o,t))),e.addCommand("mceInsertTable",((t,o)=>{((e,t,o,n={})=>{const r=e=>u(e)&&e>0;if(r(t)&&r(o)){const r=n.headerRows||0,s=n.headerColumns||0;return Fc(e,o,t,s,r)}console.error("Invalid values for mceInsertTable - rows and columns values are required to insert a table.")})(e,o.rows,o.columns,o.options)})),e.addCommand("mceTableApplyCellStyle",((t,o)=>{const l=e=>"tablecell"+e.toLowerCase().replace("-","");if(!s(o))return;const a=B(qs(e),ds);if(0===a.length)return;const c=((e,t)=>{const o={};return((e,t,o,n)=>{G(e,((e,r)=>{(t(e,r)?o:n)(e,r)}))})(e,t,(e=>(t,o)=>{e[o]=t})(o),f),o})(o,((t,o)=>e.formatter.has(l(o))&&r(t)));(e=>{for(const t in e)if(U.call(e,t))return!1;return!0})(c)||(G(c,((t,o)=>{const n=l(o);N(a,(o=>{""===t?e.formatter.remove(n,{value:null},o.dom,!0):e.formatter.apply(n,{value:t},o.dom)}))})),n(a[0]).each((t=>gc(e,t.dom,hc))))}))},ni=sa([{before:["element"]},{on:["element","offset"]},{after:["element"]}]),ri={before:ni.before,on:ni.on,after:ni.after,cata:(e,t,o,n)=>e.fold(t,o,n),getStart:e=>e.fold(h,h,h)},si=(e,t)=>({selection:e,kill:t}),li=(e,t)=>{const o=e.document.createRange();return o.selectNode(t.dom),o},ai=(e,t)=>{const o=e.document.createRange();return ci(o,t),o},ci=(e,t)=>e.selectNodeContents(t.dom),ii=(e,t,o)=>{const n=e.document.createRange();var r;return r=n,t.fold((e=>{r.setStartBefore(e.dom)}),((e,t)=>{r.setStart(e.dom,t)}),(e=>{r.setStartAfter(e.dom)})),((e,t)=>{t.fold((t=>{e.setEndBefore(t.dom)}),((t,o)=>{e.setEnd(t.dom,o)}),(t=>{e.setEndAfter(t.dom)}))})(n,o),n},mi=(e,t,o,n,r)=>{const s=e.document.createRange();return s.setStart(t.dom,o),s.setEnd(n.dom,r),s},di=e=>({left:e.left,top:e.top,right:e.right,bottom:e.bottom,width:e.width,height:e.height}),ui=sa([{ltr:["start","soffset","finish","foffset"]},{rtl:["start","soffset","finish","foffset"]}]),fi=(e,t,o)=>t(xe.fromDom(o.startContainer),o.startOffset,xe.fromDom(o.endContainer),o.endOffset),gi=(e,t)=>{const o=((e,t)=>t.match({domRange:e=>({ltr:g(e),rtl:C.none}),relative:(t,o)=>({ltr:Zt((()=>ii(e,t,o))),rtl:Zt((()=>C.some(ii(e,o,t))))}),exact:(t,o,n,r)=>({ltr:Zt((()=>mi(e,t,o,n,r))),rtl:Zt((()=>C.some(mi(e,n,r,t,o))))})}))(e,t);return((e,t)=>{const o=t.ltr();return o.collapsed?t.rtl().filter((e=>!1===e.collapsed)).map((e=>ui.rtl(xe.fromDom(e.endContainer),e.endOffset,xe.fromDom(e.startContainer),e.startOffset))).getOrThunk((()=>fi(0,ui.ltr,o))):fi(0,ui.ltr,o)})(0,o)},hi=(e,t)=>gi(e,t).match({ltr:(t,o,n,r)=>{const s=e.document.createRange();return s.setStart(t.dom,o),s.setEnd(n.dom,r),s},rtl:(t,o,n,r)=>{const s=e.document.createRange();return s.setStart(n.dom,r),s.setEnd(t.dom,o),s}});ui.ltr,ui.rtl;const pi=(e,t,o,n)=>({start:e,soffset:t,finish:o,foffset:n}),bi=(e,t,o,n)=>({start:ri.on(e,t),finish:ri.on(o,n)}),wi=(e,t)=>{const o=hi(e,t);return pi(xe.fromDom(o.startContainer),o.startOffset,xe.fromDom(o.endContainer),o.endOffset)},vi=bi,yi=(e,t,o,n,r)=>Re(o,n)?C.none():Os(o,n,t).bind((t=>{const n=t.boxes.getOr([]);return n.length>1?(r(e,n,t.start,t.finish),C.some(si(C.some(vi(o,0,o,wr(o))),!0))):C.none()})),xi=(e,t)=>({item:e,mode:t}),Ci=(e,t,o,n=Si)=>e.property().parent(t).map((e=>xi(e,n))),Si=(e,t,o,n=Ti)=>o.sibling(e,t).map((e=>xi(e,n))),Ti=(e,t,o,n=Ti)=>{const r=e.property().children(t);return o.first(r).map((e=>xi(e,n)))},Ri=[{current:Ci,next:Si,fallback:C.none()},{current:Si,next:Ti,fallback:C.some(Ci)},{current:Ti,next:Ti,fallback:C.some(Si)}],Di=(e,t,o,n,r=Ri)=>L(r,(e=>e.current===o)).bind((o=>o.current(e,t,n,o.next).orThunk((()=>o.fallback.bind((o=>Di(e,t,o,n))))))),Oi=(e,t,o,n,r,s)=>Di(e,t,n,r).bind((t=>s(t.item)?C.none():o(t.item)?C.some(t.item):Oi(e,t.item,o,t.mode,r,s))),ki=e=>t=>0===e.property().children(t).length,Ei=(e,t,o,n)=>Oi(e,t,o,Si,{sibling:(e,t)=>e.query().prevSibling(t),first:e=>e.length>0?C.some(e[e.length-1]):C.none()},n),Ni=(e,t,o,n)=>Oi(e,t,o,Si,{sibling:(e,t)=>e.query().nextSibling(t),first:e=>e.length>0?C.some(e[0]):C.none()},n),_i=xs(),Bi=(e,t)=>((e,t,o)=>Ei(e,t,ki(e),o))(_i,e,t),zi=(e,t)=>((e,t,o)=>Ni(e,t,ki(e),o))(_i,e,t),Ai=sa([{none:["message"]},{success:[]},{failedUp:["cell"]},{failedDown:["cell"]}]),Li=e=>wt(e,"tr"),Wi={...Ai,verify:(e,t,o,n,r,s,l)=>wt(n,"td,th",l).bind((o=>wt(t,"td,th",l).map((t=>Re(o,t)?Re(n,o)&&wr(o)===r?s(t):Ai.none("in same cell"):Rs(Li,[o,t]).fold((()=>((e,t,o)=>{const n=e.getRect(t),r=e.getRect(o);return r.right>n.left&&r.lefts(t))))))).getOr(Ai.none("default")),cata:(e,t,o,n,r)=>e.fold(t,o,n,r)},Mi=ue("br"),ji=(e,t,o)=>t(e,o).bind((e=>ie(e)&&0===hr(e).trim().length?ji(e,t,o):C.some(e))),Pi=(e,t,o,n)=>((e,t)=>We(e,t).filter(Mi).orThunk((()=>We(e,t-1).filter(Mi))))(t,o).bind((t=>n.traverse(t).fold((()=>ji(t,n.gather,e).map(n.relative)),(e=>(e=>Ne(e).bind((t=>{const o=Le(t);return((e,t)=>W(e,b(Re,t)))(o,e).map((n=>((e,t,o,n)=>({parent:e,children:t,element:o,index:n}))(t,o,e,n)))})))(e).map((e=>ri.on(e.parent,e.index))))))),Ii=(e,t)=>({left:e.left,top:e.top+t,right:e.right,bottom:e.bottom+t}),Fi=(e,t)=>({left:e.left,top:e.top-t,right:e.right,bottom:e.bottom-t}),Hi=(e,t,o)=>({left:e.left+t,top:e.top+o,right:e.right+t,bottom:e.bottom+o}),$i=e=>({left:e.left,top:e.top,right:e.right,bottom:e.bottom}),Vi=(e,t)=>C.some(e.getRect(t)),qi=(e,t,o)=>ce(t)?Vi(e,t).map($i):ie(t)?((e,t,o)=>o>=0&&o0?e.getRangedRect(t,o-1,t,o):C.none())(e,t,o).map($i):C.none(),Ui=(e,t)=>ce(t)?Vi(e,t).map($i):ie(t)?e.getRangedRect(t,0,t,wr(t)).map($i):C.none(),Gi=sa([{none:[]},{retry:["caret"]}]),Ki=(e,t,o)=>gt(t,xa).fold(y,(t=>Ui(e,t).exists((e=>((e,t)=>e.leftt.right)(o,e))))),Yi={point:e=>e.bottom,adjuster:(e,t,o,n,r)=>{const s=Ii(r,5);return Math.abs(o.bottom-n.bottom)<1||o.top>r.bottom?Gi.retry(s):o.top===r.bottom?Gi.retry(Ii(r,1)):Ki(e,t,r)?Gi.retry(Hi(s,5,0)):Gi.none()},move:Ii,gather:zi},Ji=(e,t,o,n,r)=>0===r?C.some(n):((e,t,o)=>e.elementFromPoint(t,o).filter((e=>"table"===ne(e))).isSome())(e,n.left,t.point(n))?((e,t,o,n,r)=>Ji(e,t,o,t.move(n,5),r))(e,t,o,n,r-1):e.situsFromPoint(n.left,t.point(n)).bind((s=>s.start.fold(C.none,(s=>Ui(e,s).bind((l=>t.adjuster(e,s,l,o,n).fold(C.none,(n=>Ji(e,t,o,n,r-1))))).orThunk((()=>C.some(n)))),C.none))),Qi=(e,t,o)=>{const n=e.move(o,5),r=Ji(t,e,o,n,100).getOr(n);return((e,t,o)=>e.point(t)>o.getInnerHeight()?C.some(e.point(t)-o.getInnerHeight()):e.point(t)<0?C.some(-e.point(t)):C.none())(e,r,t).fold((()=>t.situsFromPoint(r.left,e.point(r))),(o=>(t.scrollBy(0,o),t.situsFromPoint(r.left,e.point(r)-o))))},Xi={tryUp:b(Qi,{point:e=>e.top,adjuster:(e,t,o,n,r)=>{const s=Fi(r,5);return Math.abs(o.top-n.top)<1||o.bottome.getSelection().bind((n=>((e,t,o,n)=>{const r=Mi(t)?((e,t,o)=>o.traverse(t).orThunk((()=>ji(t,o.gather,e))).map(o.relative))(e,t,n):Pi(e,t,o,n);return r.map((e=>({start:e,finish:e})))})(t,n.finish,n.foffset,o).fold((()=>C.some(Gs(n.finish,n.foffset))),(r=>{const s=e.fromSitus(r);return l=Wi.verify(e,n.finish,n.foffset,s.finish,s.foffset,o.failure,t),Wi.cata(l,(e=>C.none()),(()=>C.none()),(e=>C.some(Gs(e,0))),(e=>C.some(Gs(e,wr(e)))));var l})))),em=(e,t,o,n,r,s)=>0===s?C.none():nm(e,t,o,n,r).bind((l=>{const a=e.fromSitus(l),c=Wi.verify(e,o,n,a.finish,a.foffset,r.failure,t);return Wi.cata(c,(()=>C.none()),(()=>C.some(l)),(l=>Re(o,l)&&0===n?tm(e,o,n,Fi,r):em(e,t,l,0,r,s-1)),(l=>Re(o,l)&&n===wr(l)?tm(e,o,n,Ii,r):em(e,t,l,wr(l),r,s-1)))})),tm=(e,t,o,n,r)=>qi(e,t,o).bind((t=>om(e,r,n(t,Xi.getJumpSize())))),om=(e,t,o)=>{const n=No().browser;return n.isChromium()||n.isSafari()||n.isFirefox()?t.retry(e,o):C.none()},nm=(e,t,o,n,r)=>qi(e,o,n).bind((t=>om(e,r,t))),rm=(e,t,o,n,r)=>wt(n,"td,th",t).bind((n=>wt(n,"table",t).bind((s=>((e,t)=>ft(e,(e=>Ne(e).exists((e=>Re(e,t)))),void 0).isSome())(r,s)?((e,t,o)=>Zi(e,t,o).bind((n=>em(e,t,n.element,n.offset,o,20).map(e.fromSitus))))(e,t,o).bind((e=>wt(e.finish,"td,th",t).map((t=>({start:n,finish:t,range:e}))))):C.none())))),sm=(e,t,o,n,r,s)=>s(n,t).orThunk((()=>rm(e,t,o,n,r).map((e=>{const t=e.range;return si(C.some(vi(t.start,t.soffset,t.finish,t.foffset)),!0)})))),lm=(e,t)=>wt(e,"tr",t).bind((e=>wt(e,"table",t).bind((o=>{const n=dt(o,"tr");return Re(e,n[0])?((e,t,o)=>Ei(_i,e,(e=>Cr(e).isSome()),o))(o,0,t).map((e=>{const t=wr(e);return si(C.some(vi(e,t,e,t)),!0)})):C.none()})))),am=(e,t)=>wt(e,"tr",t).bind((e=>wt(e,"table",t).bind((o=>{const n=dt(o,"tr");return Re(e,n[n.length-1])?((e,t,o)=>Ni(_i,e,(e=>xr(e).isSome()),o))(o,0,t).map((e=>si(C.some(vi(e,0,e,0)),!0))):C.none()})))),cm=(e,t,o,n,r,s,l)=>rm(e,o,n,r,s).bind((e=>yi(t,o,e.start,e.finish,l))),im=e=>{let t=e;return{get:()=>t,set:e=>{t=e}}},mm=()=>{const e=(e=>{const t=im(C.none()),o=()=>t.get().each(e);return{clear:()=>{o(),t.set(C.none())},isSet:()=>t.get().isSome(),get:()=>t.get(),set:e=>{o(),t.set(C.some(e))}}})(f);return{...e,on:t=>e.get().each(t)}},dm=(e,t)=>wt(e,"td,th",t),um=e=>_e(e).exists(es),fm={traverse:Ae,gather:zi,relative:ri.before,retry:Xi.tryDown,failure:Wi.failedDown},gm={traverse:ze,gather:Bi,relative:ri.before,retry:Xi.tryUp,failure:Wi.failedUp},hm=e=>t=>t===e,pm=hm(38),bm=hm(40),wm=e=>e>=37&&e<=40,vm={isBackward:hm(37),isForward:hm(39)},ym={isBackward:hm(39),isForward:hm(37)},xm=sa([{domRange:["rng"]},{relative:["startSitu","finishSitu"]},{exact:["start","soffset","finish","foffset"]}]),Cm={domRange:xm.domRange,relative:xm.relative,exact:xm.exact,exactFromRange:e=>xm.exact(e.start,e.soffset,e.finish,e.foffset),getWin:e=>{const t=(e=>e.match({domRange:e=>xe.fromDom(e.startContainer),relative:(e,t)=>ri.getStart(e),exact:(e,t,o,n)=>e}))(e);return xe.fromDom(Ee(t).dom.defaultView)},range:pi},Sm=(e,t)=>{const o=ne(e);return"input"===o?ri.after(e):D(["br","img"],o)?0===t?ri.before(e):ri.after(e):ri.on(e,t)},Tm=e=>C.from(e.getSelection()),Rm=(e,t)=>{Tm(e).each((e=>{e.removeAllRanges(),e.addRange(t)}))},Dm=(e,t,o,n,r)=>{const s=mi(e,t,o,n,r);Rm(e,s)},Om=(e,t)=>gi(e,t).match({ltr:(t,o,n,r)=>{Dm(e,t,o,n,r)},rtl:(t,o,n,r)=>{Tm(e).each((s=>{if(s.setBaseAndExtent)s.setBaseAndExtent(t.dom,o,n.dom,r);else if(s.extend)try{((e,t,o,n,r,s)=>{t.collapse(o.dom,n),t.extend(r.dom,s)})(0,s,t,o,n,r)}catch(s){Dm(e,n,r,t,o)}else Dm(e,n,r,t,o)}))}}),km=(e,t,o,n,r)=>{const s=((e,t,o,n)=>{const r=Sm(e,t),s=Sm(o,n);return Cm.relative(r,s)})(t,o,n,r);Om(e,s)},Em=(e,t,o)=>{const n=((e,t)=>{const o=e.fold(ri.before,Sm,ri.after),n=t.fold(ri.before,Sm,ri.after);return Cm.relative(o,n)})(t,o);Om(e,n)},Nm=e=>{if(e.rangeCount>0){const t=e.getRangeAt(0),o=e.getRangeAt(e.rangeCount-1);return C.some(pi(xe.fromDom(t.startContainer),t.startOffset,xe.fromDom(o.endContainer),o.endOffset))}return C.none()},_m=e=>{if(null===e.anchorNode||null===e.focusNode)return Nm(e);{const t=xe.fromDom(e.anchorNode),o=xe.fromDom(e.focusNode);return((e,t,o,n)=>{const r=((e,t,o,n)=>{const r=ke(e).dom.createRange();return r.setStart(e.dom,t),r.setEnd(o.dom,n),r})(e,t,o,n),s=Re(e,o)&&t===n;return r.collapsed&&!s})(t,e.anchorOffset,o,e.focusOffset)?C.some(pi(t,e.anchorOffset,o,e.focusOffset)):Nm(e)}},Bm=(e,t,o=!0)=>{const n=(o?ai:li)(e,t);Rm(e,n)},zm=e=>(e=>Tm(e).filter((e=>e.rangeCount>0)).bind(_m))(e).map((e=>Cm.exact(e.start,e.soffset,e.finish,e.foffset))),Am=(e,t,o)=>((e,t,o)=>((e,t,o)=>e.caretPositionFromPoint?((e,t,o)=>{var n;return C.from(null===(n=e.caretPositionFromPoint)||void 0===n?void 0:n.call(e,t,o)).bind((t=>{if(null===t.offsetNode)return C.none();const o=e.createRange();return o.setStart(t.offsetNode,t.offset),o.collapse(),C.some(o)}))})(e,t,o):e.caretRangeFromPoint?((e,t,o)=>{var n;return C.from(null===(n=e.caretRangeFromPoint)||void 0===n?void 0:n.call(e,t,o))})(e,t,o):C.none())(e.document,t,o).map((e=>pi(xe.fromDom(e.startContainer),e.startOffset,xe.fromDom(e.endContainer),e.endOffset))))(e,t,o),Lm=e=>({elementFromPoint:(t,o)=>xe.fromPoint(xe.fromDom(e.document),t,o),getRect:e=>e.dom.getBoundingClientRect(),getRangedRect:(t,o,n,r)=>{const s=Cm.exact(t,o,n,r);return((e,t)=>(e=>{const t=e.getClientRects(),o=t.length>0?t[0]:e.getBoundingClientRect();return o.width>0||o.height>0?C.some(o).map(di):C.none()})(hi(e,t)))(e,s)},getSelection:()=>zm(e).map((t=>wi(e,t))),fromSitus:t=>{const o=Cm.relative(t.start,t.finish);return wi(e,o)},situsFromPoint:(t,o)=>Am(e,t,o).map((e=>bi(e.start,e.soffset,e.finish,e.foffset))),clearSelection:()=>{(e=>{Tm(e).each((e=>e.removeAllRanges()))})(e)},collapseSelection:(t=!1)=>{zm(e).each((o=>o.fold((e=>e.collapse(t)),((o,n)=>{const r=t?o:n;Em(e,r,r)}),((o,n,r,s)=>{const l=t?o:r,a=t?n:s;km(e,l,a,l,a)}))))},setSelection:t=>{km(e,t.start,t.soffset,t.finish,t.foffset)},setRelativeSelection:(t,o)=>{Em(e,t,o)},selectNode:t=>{Bm(e,t,!1)},selectContents:t=>{Bm(e,t)},getInnerHeight:()=>e.innerHeight,getScrollY:()=>(e=>{const t=void 0!==e?e.dom:document,o=t.body.scrollLeft||t.documentElement.scrollLeft,n=t.body.scrollTop||t.documentElement.scrollTop;return bn(o,n)})(xe.fromDom(e.document)).top,scrollBy:(t,o)=>{((e,t,o)=>{const n=(void 0!==o?o.dom:document).defaultView;n&&n.scrollBy(e,t)})(t,o,xe.fromDom(e.document))}}),Wm=(e,t)=>({rows:e,cols:t}),Mm=e=>gt(e,ae).exists(es),jm=(e,t)=>Mm(e)||Mm(t),Pm=e=>void 0!==e.dom.classList,Im=(e,t)=>((e,t,o)=>{const n=((e,t)=>{const o=pe(e,t);return void 0===o||""===o?[]:o.split(" ")})(e,t).concat([o]);return ge(e,t,n.join(" ")),!0})(e,"class",t),Fm=(e,t)=>{Pm(e)?e.dom.classList.add(t):Im(e,t)},Hm=(e,t)=>Pm(e)&&e.dom.classList.contains(t),$m=()=>({tag:"none"}),Vm=e=>({tag:"multiple",elements:e}),qm=e=>({tag:"single",element:e}),Um=e=>{const t=xe.fromDom((e=>{if(m(e.target)){const t=xe.fromDom(e.target);if(ce(t)&&m(t.dom.shadowRoot)&&e.composed&&e.composedPath){const t=e.composedPath();if(t)return H(t)}}return C.from(e.target)})(e).getOr(e.target)),o=()=>e.stopPropagation(),n=()=>e.preventDefault(),r=(s=n,l=o,(...e)=>s(l.apply(null,e)));var s,l;return((e,t,o,n,r,s,l)=>({target:e,x:t,y:o,stop:n,prevent:r,kill:s,raw:l}))(t,e.clientX,e.clientY,o,n,r,e)},Gm=(e,t,o,n)=>{e.dom.removeEventListener(t,o,n)},Km=x,Ym=(e,t,o)=>((e,t,o,n)=>((e,t,o,n,r)=>{const s=((e,t)=>o=>{e(o)&&t(Um(o))})(o,n);return e.dom.addEventListener(t,s,r),{unbind:b(Gm,e,t,s,r)}})(e,t,o,n,!1))(e,t,Km,o),Jm=Um,Qm=e=>!Hm(xe.fromDom(e.target),"ephox-snooker-resizer-bar"),Xm=(e,t)=>{const o=(r=Is.selectedSelector,{get:()=>_s(xe.fromDom(e.getBody()),r).fold((()=>Vs(ss(e),ns(e)).fold($m,qm)),Vm)}),n=((e,t,o)=>{const n=t=>{we(t,e.selected),we(t,e.firstSelected),we(t,e.lastSelected)},r=t=>{ge(t,e.selected,"1")},s=e=>{l(e),o()},l=t=>{const o=dt(t,`${e.selectedSelector},${e.firstSelectedSelector},${e.lastSelectedSelector}`);N(o,n)};return{clearBeforeUpdate:l,clear:s,selectRange:(o,n,l,a)=>{s(o),N(n,r),ge(l,e.firstSelected,"1"),ge(a,e.lastSelected,"1"),t(n,l,a)},selectedSelector:e.selectedSelector,firstSelectedSelector:e.firstSelectedSelector,lastSelectedSelector:e.lastSelectedSelector}})(Is,((t,o,n)=>{Gt(o).each((r=>{const s=E(t,(e=>e.dom)),l=Wr(e),a=_r(f,xe.fromDom(e.getDoc()),l),c=((e,t,o)=>{const n=Xo(e);return zl(n,t).map((e=>{const t=Ol(n,o,!1),{rows:r}=Vo(t),s=((e,t)=>{const o=e.slice(0,t[t.length-1].row+1),n=kl(o);return j(n,(e=>{const o=e.cells.slice(0,t[t.length-1].column+1);return E(o,(e=>e.element))}))})(r,e),l=((e,t)=>{const o=e.slice(t[0].row+t[0].rowspan-1,e.length),n=kl(o);return j(n,(e=>{const o=e.cells.slice(t[0].column+t[0].colspan-1,e.cells.length);return E(o,(e=>e.element))}))})(r,e);return{upOrLeftCells:s,downOrRightCells:l}}))})(r,{selection:qs(e)},a).map((e=>K(e,(e=>E(e,(e=>e.dom)))))).getOrUndefined();((e,t,o,n,r)=>{e.dispatch("TableSelectionChange",{cells:t,start:o,finish:n,otherCells:r})})(e,s,o.dom,n.dom,c)}))}),(()=>(e=>{e.dispatch("TableSelectionClear")})(e)));var r;return e.on("init",(o=>{const r=e.getWin(),s=os(e),l=ns(e),a=((e,t,o,n)=>{const r=((e,t,o,n)=>{const r=mm(),s=r.clear,l=s=>{r.on((r=>{n.clearBeforeUpdate(t),dm(s.target,o).each((l=>{Os(r,l,o).each((o=>{const r=o.boxes.getOr([]);if(1===r.length){const e=r[0],o="false"===ts(e),l=vt(Zr(s.target),e,Re);o&&l&&n.selectRange(t,r,e,e)}else r.length>1&&(n.selectRange(t,r,o.start,o.finish),e.selectContents(l))}))}))}))};return{clearstate:s,mousedown:e=>{n.clear(t),dm(e.target,o).filter(um).each(r.set)},mouseover:e=>{l(e)},mouseup:e=>{l(e),s()}}})(Lm(e),t,o,n);return{clearstate:r.clearstate,mousedown:r.mousedown,mouseover:r.mouseover,mouseup:r.mouseup}})(r,s,l,n),c=((e,t,o,n)=>{const r=Lm(e),s=()=>(n.clear(t),C.none());return{keydown:(e,l,a,c,i,m)=>{const d=e.raw,u=d.which,f=!0===d.shiftKey,g=ks(t,n.selectedSelector).fold((()=>(wm(u)&&!f&&n.clearBeforeUpdate(t),wm(u)&&f&&!jm(l,c)?C.none:bm(u)&&f?b(cm,r,t,o,fm,c,l,n.selectRange):pm(u)&&f?b(cm,r,t,o,gm,c,l,n.selectRange):bm(u)?b(sm,r,o,fm,c,l,am):pm(u)?b(sm,r,o,gm,c,l,lm):C.none)),(e=>{const o=o=>()=>{const s=V(o,(o=>((e,t,o,n,r)=>Ns(n,e,t,r.firstSelectedSelector,r.lastSelectedSelector).map((e=>(r.clearBeforeUpdate(o),r.selectRange(o,e.boxes,e.start,e.finish),e.boxes))))(o.rows,o.cols,t,e,n)));return s.fold((()=>Es(t,n.firstSelectedSelector,n.lastSelectedSelector).map((e=>{const o=bm(u)||m.isForward(u)?ri.after:ri.before;return r.setRelativeSelection(ri.on(e.first,0),o(e.table)),n.clear(t),si(C.none(),!0)}))),(e=>C.some(si(C.none(),!0))))};return wm(u)&&f&&!jm(l,c)?C.none:bm(u)&&f?o([Wm(1,0)]):pm(u)&&f?o([Wm(-1,0)]):m.isBackward(u)&&f?o([Wm(0,-1),Wm(-1,0)]):m.isForward(u)&&f?o([Wm(0,1),Wm(1,0)]):wm(u)&&!f?s:C.none}));return g()},keyup:(e,r,s,l,a)=>ks(t,n.selectedSelector).fold((()=>{const c=e.raw,i=c.which;return!0===c.shiftKey&&wm(i)&&jm(r,l)?((e,t,o,n,r,s,l)=>Re(o,r)&&n===s?C.none():wt(o,"td,th",t).bind((o=>wt(r,"td,th",t).bind((n=>yi(e,t,o,n,l))))))(t,o,r,s,l,a,n.selectRange):C.none()}),C.none)}})(r,s,l,n),i=((e,t,o,n)=>{const r=Lm(e);return(e,s)=>{n.clearBeforeUpdate(t),Os(e,s,o).each((e=>{const o=e.boxes.getOr([]);n.selectRange(t,o,e.start,e.finish),r.selectContents(s),r.collapseSelection()}))}})(r,s,l,n);e.on("TableSelectorChange",(e=>i(e.start,e.finish)));const m=(t,o)=>{(e=>!0===e.raw.shiftKey)(t)&&(o.kill&&t.kill(),o.selection.each((t=>{const o=Cm.relative(t.start,t.finish),n=hi(r,o);e.selection.setRng(n)})))},d=e=>0===e.button,u=(()=>{const e=im(xe.fromDom(s)),t=im(0);return{touchEnd:o=>{const n=xe.fromDom(o.target);if(ue("td")(n)||ue("th")(n)){const r=e.get(),s=t.get();Re(r,n)&&o.timeStamp-s<300&&(o.preventDefault(),i(n,n))}e.set(n),t.set(o.timeStamp)}}})();e.on("dragstart",(e=>{a.clearstate()})),e.on("mousedown",(e=>{d(e)&&Qm(e)&&a.mousedown(Jm(e))})),e.on("mouseover",(e=>{var t;(void 0===(t=e).buttons||1&t.buttons)&&Qm(e)&&a.mouseover(Jm(e))})),e.on("mouseup",(e=>{d(e)&&Qm(e)&&a.mouseup(Jm(e))})),e.on("touchend",u.touchEnd),e.on("keyup",(t=>{const o=Jm(t);if(o.raw.shiftKey&&wm(o.raw.which)){const t=e.selection.getRng(),n=xe.fromDom(t.startContainer),r=xe.fromDom(t.endContainer);c.keyup(o,n,t.startOffset,r,t.endOffset).each((e=>{m(o,e)}))}})),e.on("keydown",(o=>{const n=Jm(o);t.hide();const r=e.selection.getRng(),s=xe.fromDom(r.startContainer),l=xe.fromDom(r.endContainer),a=dn(vm,ym)(xe.fromDom(e.selection.getStart()));c.keydown(n,s,r.startOffset,l,r.endOffset,a).each((e=>{m(n,e)})),t.show()})),e.on("NodeChange",(()=>{const t=e.selection,o=xe.fromDom(t.getStart()),r=xe.fromDom(t.getEnd());Rs(Gt,[o,r]).fold((()=>n.clear(s)),f)}))})),e.on("PreInit",(()=>{e.serializer.addTempAttr(Is.firstSelected),e.serializer.addTempAttr(Is.lastSelected)})),{getSelectedCells:()=>((e,t)=>{switch(e.tag){case"none":return t();case"single":return(e=>[e.dom])(e.element);case"multiple":return(e=>E(e,(e=>e.dom)))(e.elements)}})(o.get(),g([])),clearSelectedCells:e=>n.clear(xe.fromDom(e))}},Zm=e=>{let t=[];return{bind:e=>{if(void 0===e)throw new Error("Event bind error: undefined handler");t.push(e)},unbind:e=>{t=B(t,(t=>t!==e))},trigger:(...o)=>{const n={};N(e,((e,t)=>{n[e]=o[t]})),N(t,(e=>{e(n)}))}}},ed=e=>({registry:K(e,(e=>({bind:e.bind,unbind:e.unbind}))),trigger:K(e,(e=>e.trigger))}),td=e=>e.slice(0).sort(),od=(e,t)=>{const o=B(t,(t=>!D(e,t)));o.length>0&&(e=>{throw new Error("Unsupported keys for object: "+td(e).join(", "))})(o)},nd=e=>((e,t)=>((e,t,o)=>{if(0===t.length)throw new Error("You must specify at least one required field.");return((e,t)=>{if(!l(t))throw new Error("The "+e+" fields must be an array. Was: "+t+".");N(t,(t=>{if(!r(t))throw new Error("The value "+t+" in the "+e+" fields was not a string.")}))})("required",t),(e=>{const t=td(e);L(t,((e,o)=>o{throw new Error("The field: "+e+" occurs more than once in the combined fields: ["+t.join(", ")+"].")}))})(t),n=>{const r=q(n);P(t,(e=>D(r,e)))||((e,t)=>{throw new Error("All required keys ("+td(e).join(", ")+") were not specified. Specified keys were: "+td(t).join(", ")+".")})(t,r),e(t,r);const s=B(t,(e=>!o.validate(n[e],e)));return s.length>0&&((e,t)=>{throw new Error("All values need to be of type: "+t+". Keys ("+td(e).join(", ")+") were not.")})(s,o.label),n}})(e,t,{validate:d,label:"function"}))(od,e),rd=nd(["compare","extract","mutate","sink"]),sd=nd(["element","start","stop","destroy"]),ld=nd(["forceDrop","drop","move","delayDrop"]),ad=()=>{const e=(()=>{const e=ed({move:Zm(["info"])});return{onEvent:f,reset:f,events:e.registry}})(),t=(()=>{let e=C.none();const t=ed({move:Zm(["info"])});return{onEvent:(o,n)=>{n.extract(o).each((o=>{const r=((t,o)=>{const n=e.map((e=>t.compare(e,o)));return e=C.some(o),n})(n,o);r.each((e=>{t.trigger.move(e)}))}))},reset:()=>{e=C.none()},events:t.registry}})();let o=e;return{on:()=>{o.reset(),o=t},off:()=>{o.reset(),o=e},isOn:()=>o===t,onEvent:(e,t)=>{o.onEvent(e,t)},events:t.events}},cd=e=>{const t=e.replace(/\./g,"-");return{resolve:e=>t+"-"+e}},id=cd("ephox-dragster").resolve;var md=rd({compare:(e,t)=>bn(t.left-e.left,t.top-e.top),extract:e=>C.some(bn(e.x,e.y)),sink:(e,t)=>{const o=(e=>{const t={layerClass:id("blocker"),...e},o=xe.fromTag("div");return ge(o,"role","presentation"),_t(o,{position:"fixed",left:"0px",top:"0px",width:"100%",height:"100%"}),Fm(o,id("blocker")),Fm(o,t.layerClass),{element:g(o),destroy:()=>{qe(o)}}})(t),n=Ym(o.element(),"mousedown",e.forceDrop),r=Ym(o.element(),"mouseup",e.drop),s=Ym(o.element(),"mousemove",e.move),l=Ym(o.element(),"mouseout",e.delayDrop);return sd({element:o.element,start:e=>{Ie(e,o.element())},stop:()=>{qe(o.element())},destroy:()=>{o.destroy(),r.unbind(),s.unbind(),l.unbind(),n.unbind()}})},mutate:(e,t)=>{e.mutate(t.left,t.top)}});const dd=cd("ephox-snooker").resolve,ud=dd("resizer-bar"),fd=dd("resizer-rows"),gd=dd("resizer-cols"),hd=e=>{const t=dt(e.parent(),"."+ud);N(t,qe)},pd=(e,t,o)=>{const n=e.origin();N(t,(t=>{t.each((t=>{const r=o(n,t);Fm(r,ud),Ie(e.parent(),r)}))}))},bd=(e,t,o,n,r)=>{const s=vn(o),l=t.isResizable,a=n.length>0?_n.positions(n,o):[],c=a.length>0?((e,t)=>j(e.all,((e,o)=>t(e.element)?[o]:[])))(e,l):[];((e,t,o,n)=>{pd(e,t,((e,t)=>{const r=((e,t,o,n)=>{const r=xe.fromTag("div");return _t(r,{position:"absolute",left:t+"px",top:o-3.5+"px",height:"7px",width:n+"px"}),he(r,{"data-row":e,role:"presentation"}),r})(t.row,o.left-e.left,t.y-e.top,n);return Fm(r,fd),r}))})(t,B(a,((e,t)=>O(c,(e=>t===e)))),s,Lo(o));const i=r.length>0?zn.positions(r,o):[],m=i.length>0?((e,t)=>{const o=[];return k(e.grid.columns,(n=>{ln(e,n).map((e=>e.element)).forall(t)&&o.push(n)})),B(o,(o=>{const n=on(e,(e=>e.column===o));return P(n,(e=>t(e.element)))}))})(e,l):[];((e,t,o,n)=>{pd(e,t,((e,t)=>{const r=((e,t,o,n,r)=>{const s=xe.fromTag("div");return _t(s,{position:"absolute",left:t-3.5+"px",top:o+"px",height:r+"px",width:"7px"}),he(s,{"data-column":e,role:"presentation"}),s})(t.col,t.x-e.left,o.top-e.top,0,n);return Fm(r,gd),r}))})(t,B(i,((e,t)=>O(m,(e=>t===e)))),s,hn(o))},wd=(e,t)=>{if(hd(e),e.isResizable(t)){const o=Xo(t),n=mn(o),r=an(o);bd(o,e,t,n,r)}},vd=(e,t)=>{const o=dt(e.parent(),"."+ud);N(o,t)},yd=e=>{vd(e,(e=>{Nt(e,"display","none")}))},xd=e=>{vd(e,(e=>{Nt(e,"display","block")}))},Cd=dd("resizer-bar-dragging"),Sd=e=>{const t=(()=>{const e=ed({drag:Zm(["xDelta","yDelta","target"])});let t=C.none();const o=(()=>{const e=ed({drag:Zm(["xDelta","yDelta"])});return{mutate:(t,o)=>{e.trigger.drag(t,o)},events:e.registry}})();return o.events.drag.bind((o=>{t.each((t=>{e.trigger.drag(o.xDelta,o.yDelta,t)}))})),{assign:e=>{t=C.some(e)},get:()=>t,mutate:o.mutate,events:e.registry}})(),o=((e,t={})=>{var o;return((e,t,o)=>{let n=!1;const r=ed({start:Zm([]),stop:Zm([])}),s=ad(),l=()=>{m.stop(),s.isOn()&&(s.off(),r.trigger.stop())},c=(e=>{let t=null;const o=()=>{a(t)||(clearTimeout(t),t=null)};return{cancel:o,throttle:(...n)=>{o(),t=setTimeout((()=>{t=null,e.apply(null,n)}),200)}}})(l);s.events.move.bind((o=>{t.mutate(e,o.info)}));const i=e=>(...t)=>{n&&e.apply(null,t)},m=t.sink(ld({forceDrop:l,drop:i(l),move:i((e=>{c.cancel(),s.onEvent(e,t)})),delayDrop:i(c.throttle)}),o);return{element:m.element,go:e=>{m.start(e),s.on(),r.trigger.start()},on:()=>{n=!0},off:()=>{n=!1},isActive:()=>n,destroy:()=>{m.destroy()},events:r.registry}})(e,null!==(o=t.mode)&&void 0!==o?o:md,t)})(t,{});let n=C.none();const r=(e,t)=>C.from(pe(e,t));t.events.drag.bind((e=>{r(e.target,"data-row").each((t=>{const o=Pt(e.target,"top");Nt(e.target,"top",o+e.yDelta+"px")})),r(e.target,"data-column").each((t=>{const o=Pt(e.target,"left");Nt(e.target,"left",o+e.xDelta+"px")}))}));const s=(e,t)=>Pt(e,t)-Wt(e,"data-initial-"+t,0);o.events.stop.bind((()=>{t.get().each((t=>{n.each((o=>{r(t,"data-row").each((e=>{const n=s(t,"top");we(t,"data-initial-top"),d.trigger.adjustHeight(o,n,parseInt(e,10))})),r(t,"data-column").each((e=>{const n=s(t,"left");we(t,"data-initial-left"),d.trigger.adjustWidth(o,n,parseInt(e,10))})),wd(e,o)}))}))}));const l=(n,r)=>{d.trigger.startAdjust(),t.assign(n),ge(n,"data-initial-"+r,Pt(n,r)),Fm(n,Cd),Nt(n,"opacity","0.2"),o.go(e.dragContainer())},c=Ym(e.parent(),"mousedown",(e=>{var t;t=e.target,Hm(t,fd)&&l(e.target,"top"),(e=>Hm(e,gd))(e.target)&&l(e.target,"left")})),i=t=>Re(t,e.view()),m=Ym(e.view(),"mouseover",(t=>{var r;(r=t.target,wt(r,"table",i).filter(es)).fold((()=>{st(t.target)&&hd(e)}),(t=>{o.isActive()&&(n=C.some(t),wd(e,t))}))})),d=ed({adjustHeight:Zm(["table","delta","row"]),adjustWidth:Zm(["table","delta","column"]),startAdjust:Zm([])});return{destroy:()=>{c.unbind(),m.unbind(),o.destroy(),hd(e)},refresh:t=>{wd(e,t)},on:o.on,off:o.off,hideBars:b(yd,e),showBars:b(xd,e),events:d.registry}};let Td=0;const Rd=(e,t)=>{const o=(e=>!(e=>e.inline&&(e=>{var t;if(!e.inline)return C.none();const o=null!==(t=Jr(e))&&void 0!==t?t:"";if(o.length>0)return bt(lt(),o);const n=Qr(e);return m(n)?C.some(xe.fromDom(n)):C.none()})(e).isSome())(e)&&"split"===Xr(e))(e),n=xe.fromDom(e.getBody()),r=(e=>{const t=(e=>{const t=(new Date).getTime(),o=Math.floor(window.crypto.getRandomValues(new Uint32Array(1))[0]/4294967295*1e9);return Td++,e+"_"+o+Td+String(t)})("resizer-container"),o=xe.fromTag("div");return ge(o,"id",t),_t(o,{position:e,height:"0",width:"0",padding:"0",margin:"0",border:"0"}),o})(o?"relative":"static"),s=lt();return o?(je(n,r),((e,t,o,n)=>({parent:g(t),view:g(e),dragContainer:g(o),origin:()=>vn(t),isResizable:n}))(n,r,s,t)):(Ie(s,r),((e,t,o)=>({parent:g(t),view:g(e),dragContainer:g(t),origin:g(bn(0,0)),isResizable:o}))(n,r,t))},Dd=e=>m(e)&&"TABLE"===e.nodeName,Od="bar-",kd=e=>"false"!==pe(e,"data-mce-resize"),Ed=e=>{const t=mm(),o=mm(),n=mm();let r,s,l,a;const c=t=>wc(e,t),i=()=>Pr(e)?ll():sl(),m=(t,o,n,m)=>{const d=(e=>{return Tt(t=e,"corner-")?(e=>e.substring(7))(t):t;var t})(o),u=Rt(d,"e"),f=Tt(d,"n");if(""===s&&Lc(t),""===a&&(e=>{const t=(e=>gn(e)+"px")(e);Nc(e,C.none(),C.some(t)),Ac(e)})(t),n!==r&&""!==s){Nt(t,"width",s);const o=i(),l=c(t),a=Pr(e)||u?(e=>al(e).columns)(t)-1:0;ca(t,n-r,a,o,l)}else if((e=>/^(\d+(\.\d+)?)%$/.test(e))(s)){const e=parseFloat(s.replace("%",""));Nt(t,"width",n*e/r+"%")}if((e=>/^(\d+(\.\d+)?)px$/.test(e))(s)&&(e=>{const t=Xo(e);sn(t)||N(qt(e),(e=>{const t=Bt(e,"width");Nt(e,"width",t),we(e,"width")}))})(t),m!==l&&""!==a){Nt(t,"height",a);const e=f?0:(e=>al(e).rows)(t)-1;ia(t,m-l,e)}};e.on("init",(()=>{const r=((e,t)=>e.inline?Rd(e,t):((e,t)=>{const o=me(e)?(e=>xe.fromDom(Ee(e).dom.documentElement))(e):e;return{parent:g(o),view:g(e),dragContainer:g(o),origin:g(bn(0,0)),isResizable:t}})(xe.fromDom(e.getDoc()),t))(e,kd);if(n.set(r),(e=>{const t=e.options.get("object_resizing");return D(t.split(","),"table")})(e)&&qr(e)){const n=((e,t,o)=>{const n=_n,r=zn,s=Sd(e),l=ed({beforeResize:Zm(["table","type"]),afterResize:Zm(["table","type"]),startDrag:Zm([])});return s.events.adjustHeight.bind((e=>{const t=e.table;l.trigger.beforeResize(t,"row");const o=n.delta(e.delta,t);ia(t,o,e.row),l.trigger.afterResize(t,"row")})),s.events.startAdjust.bind((e=>{l.trigger.startDrag()})),s.events.adjustWidth.bind((e=>{const n=e.table;l.trigger.beforeResize(n,"col");const s=r.delta(e.delta,n),a=o(n);ca(n,s,e.column,t,a),l.trigger.afterResize(n,"col")})),{on:s.on,off:s.off,refreshBars:s.refresh,hideBars:s.hideBars,showBars:s.showBars,destroy:s.destroy,events:l.registry}})(r,i(),c);e.mode.isReadOnly()||n.on(),n.events.startDrag.bind((o=>{t.set(e.selection.getRng())})),n.events.beforeResize.bind((t=>{const o=t.table.dom;((e,t,o,n,r)=>{e.dispatch("ObjectResizeStart",{target:t,width:o,height:n,origin:r})})(e,o,ls(o),as(o),Od+t.type)})),n.events.afterResize.bind((o=>{const n=o.table,r=n.dom;rs(n),t.on((t=>{e.selection.setRng(t),e.focus()})),((e,t,o,n,r)=>{e.dispatch("ObjectResized",{target:t,width:o,height:n,origin:r})})(e,r,ls(r),as(r),Od+o.type),e.undoManager.add()})),o.set(n)}})),e.on("ObjectResizeStart",(t=>{const o=t.target;if(Dd(o)&&!e.mode.isReadOnly()){const n=xe.fromDom(o);N(e.dom.select(".mce-clonedresizable"),(t=>{e.dom.addClass(t,"mce-"+jr(e)+"-columns")})),!Bc(n)&&$r(e)?Wc(n):!_c(n)&&Hr(e)&&Lc(n),zc(n)&&Tt(t.origin,Od)&&Lc(n),r=t.width,s=Vr(e)?"":is(e,o).getOr(""),l=t.height,a=ms(e,o).getOr("")}})),e.on("ObjectResized",(t=>{const o=t.target;if(Dd(o)){const n=xe.fromDom(o),r=t.origin;(e=>Tt(e,"corner-"))(r)&&m(n,r,t.width,t.height),rs(n),gc(e,n.dom,hc)}}));const d=()=>{o.on((e=>{e.on(),e.showBars()}))},u=()=>{o.on((e=>{e.off(),e.hideBars()}))};return e.on("DisabledStateChange",(e=>{e.state?u():d()})),e.on("SwitchMode",(()=>{e.mode.isReadOnly()?u():d()})),e.on("dragstart dragend",(e=>{"dragstart"===e.type?u():d()})),e.on("remove",(()=>{o.on((e=>{e.destroy()})),n.on((t=>{((e,t)=>{e.inline&&qe(t.parent())})(e,t)}))})),{refresh:e=>{o.on((t=>t.refreshBars(xe.fromDom(e))))},hide:()=>{o.on((e=>e.hideBars()))},show:()=>{o.on((e=>e.showBars()))}}},Nd=e=>{(e=>{const t=e.options.register;t("table_clone_elements",{processor:"string[]"}),t("table_use_colgroups",{processor:"boolean",default:!0}),t("table_header_type",{processor:e=>{const t=D(["section","cells","sectionCells","auto"],e);return t?{value:e,valid:t}:{valid:!1,message:"Must be one of: section, cells, sectionCells or auto."}},default:"section"}),t("table_sizing_mode",{processor:"string",default:"auto"}),t("table_default_attributes",{processor:"object",default:{border:"1"}}),t("table_default_styles",{processor:"object",default:{"border-collapse":"collapse"}}),t("table_column_resizing",{processor:e=>{const t=D(["preservetable","resizetable"],e);return t?{value:e,valid:t}:{valid:!1,message:"Must be preservetable, or resizetable."}},default:"preservetable"}),t("table_resize_bars",{processor:"boolean",default:!0}),t("table_style_by_css",{processor:"boolean",default:!0}),t("table_merge_content_on_paste",{processor:"boolean",default:!0})})(e);const t=Ed(e),o=Xm(e,t),n=vc(e,t,o);return oi(e,n),((e,t)=>{const o=ns(e),n=t=>Vs(ss(e)).bind((n=>Gt(n,o).map((o=>{const r=Fs(qs(e),o,n);return t(o,r)})))).getOr("");G({mceTableRowType:()=>n(t.getTableRowType),mceTableCellType:()=>n(t.getTableCellType),mceTableColType:()=>n(t.getTableColType)},((t,o)=>e.addQueryValueHandler(o,t)))})(e,n),Us(e,n),{getSelectedCells:o.getSelectedCells,clearSelectedCells:o.clearSelectedCells}};e.add("dom",(e=>({table:Nd(e)})))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/accordion/plugin.min.js b/apps/web-antd/public/tinymce/plugins/accordion/plugin.min.js new file mode 100644 index 0000000..3c88604 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/accordion/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");let t=0;const o=e=>t=>typeof t===e,n=e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(o=n=e,(r=String).prototype.isPrototypeOf(o)||(null===(s=n.constructor)||void 0===s?void 0:s.name)===r.name)?"string":t;var o,n,r,s})(e),r=o("boolean"),s=e=>null==e,i=e=>!s(e),a=o("function"),d=o("number"),l=e=>()=>e,c=(e,t)=>e===t,m=l(!1);class u{constructor(e,t){this.tag=e,this.value=t}static some(e){return new u(!0,e)}static none(){return u.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?u.some(e(this.value)):u.none()}bind(e){return this.tag?e(this.value):u.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:u.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return i(e)?u.some(e):u.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}u.singletonNone=new u(!1);const g=Array.prototype.indexOf,p=(e,t)=>{return o=e,n=t,g.call(o,n)>-1;var o,n},h=(e,t)=>{const o=e.length,n=new Array(o);for(let r=0;r{for(let o=0,n=e.length;oe.dom.nodeName.toLowerCase(),w=e=>e.dom.nodeType,b=e=>t=>w(t)===e,N=b(1),T=b(3),A=b(9),C=b(11),S=(e,t,o)=>{if(!(n(o)||r(o)||d(o)))throw console.error("Invalid call to Attribute.set. Key ",t,":: Value ",o,":: Element ",e),new Error("Attribute value was not simple");e.setAttribute(t,o+"")},x=(e,t)=>{const o=e.dom.getAttribute(t);return null===o?void 0:o},D=(e,t)=>u.from(x(e,t)),E=(e,t)=>{e.dom.removeAttribute(t)},O=e=>{if(null==e)throw new Error("Node cannot be null or undefined");return{dom:e}},M={fromHtml:(e,t)=>{const o=(t||document).createElement("div");if(o.innerHTML=e,!o.hasChildNodes()||o.childNodes.length>1){const t="HTML does not have a single root node";throw console.error(t,e),new Error(t)}return O(o.childNodes[0])},fromTag:(e,t)=>{const o=(t||document).createElement(e);return O(o)},fromText:(e,t)=>{const o=(t||document).createTextNode(e);return O(o)},fromDom:O,fromPoint:(e,t,o)=>u.from(e.dom.elementFromPoint(t,o)).map(O)},P=(e,t)=>{const o=e.dom;if(1!==o.nodeType)return!1;{const e=o;if(void 0!==e.matches)return e.matches(t);if(void 0!==e.msMatchesSelector)return e.msMatchesSelector(t);if(void 0!==e.webkitMatchesSelector)return e.webkitMatchesSelector(t);if(void 0!==e.mozMatchesSelector)return e.mozMatchesSelector(t);throw new Error("Browser lacks native selectors")}},R=e=>1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType||0===e.childElementCount,k=P,B=(L=/^\s+|\s+$/g,e=>e.replace(L,""));var L;const $=e=>void 0!==e.style&&a(e.style.getPropertyValue),V=e=>u.from(e.dom.parentNode).map(M.fromDom),I=e=>u.from(e.dom.nextSibling).map(M.fromDom),j=e=>h(e.dom.childNodes,M.fromDom),q=e=>M.fromDom(e.dom.host),F=e=>{const t=T(e)?e.dom.parentNode:e.dom;if(null==t||null===t.ownerDocument)return!1;const o=t.ownerDocument;return(e=>{const t=(e=>M.fromDom(e.dom.getRootNode()))(e);return C(o=t)&&i(o.dom.host)?u.some(t):u.none();var o})(M.fromDom(t)).fold((()=>o.body.contains(t)),(n=F,r=q,e=>n(r(e))));var n,r},H=(e,t)=>$(e)?e.style.getPropertyValue(t):"",z=(e,t)=>{V(e).each((o=>{o.dom.insertBefore(t.dom,e.dom)}))},K=(e,t)=>{I(e).fold((()=>{V(e).each((e=>{U(e,t)}))}),(e=>{z(e,t)}))},U=(e,t)=>{e.dom.appendChild(t.dom)},Y=(e,t)=>{f(t,((o,n)=>{const r=0===n?e:t[n-1];K(r,o)}))},_=(e,t)=>{let o=[];return f(j(e),(e=>{t(e)&&(o=o.concat([e])),o=o.concat(_(e,t))})),o},G=(e,t,o)=>{let n=e.dom;const r=a(o)?o:m;for(;n.parentNode;){n=n.parentNode;const e=M.fromDom(n);if(t(e))return u.some(e);if(r(e))break}return u.none()},J=e=>{const t=e.dom;null!==t.parentNode&&t.parentNode.removeChild(t)},Q=(e,t,o)=>G(e,(e=>P(e,t)),o),W=(e,t)=>((e,t)=>{const o=void 0===t?document:t.dom;return R(o)?u.none():u.from(o.querySelector(e)).map(M.fromDom)})(t,e),X=(e=>{const t=t=>e(t)?u.from(t.dom.nodeValue):u.none();return{get:o=>{if(!e(o))throw new Error("Can only get text value of a text node");return t(o).getOr("")},getOption:t,set:(t,o)=>{if(!e(t))throw new Error("Can only set raw text value of a text node");t.dom.nodeValue=o}}})(T);var Z=["body","p","div","article","aside","figcaption","figure","footer","header","nav","section","ol","ul","li","table","thead","tbody","tfoot","caption","tr","td","th","h1","h2","h3","h4","h5","h6","blockquote","pre","address"];const ee=(e,t)=>({element:e,offset:t}),te=(e,t,o)=>e.property().isText(t)&&0===e.property().getText(t).trim().length||e.property().isComment(t)?o(t).bind((t=>te(e,t,o).orThunk((()=>u.some(t))))):u.none(),oe=(e,t)=>e.property().isText(t)?e.property().getText(t).length:e.property().children(t).length,ne=(e,t)=>{const o=te(e,t,e.query().prevSibling).getOr(t);if(e.property().isText(o))return ee(o,oe(e,o));const n=e.property().children(o);return n.length>0?ne(e,n[n.length-1]):ee(o,oe(e,o))},re=ne,se={up:l({selector:Q,closest:(e,t,o)=>((e,t,o,n,r)=>((e,t)=>P(e,t))(o,n)?u.some(o):a(r)&&r(o)?u.none():t(o,n,r))(0,Q,e,t,o),predicate:G,all:(e,t)=>{const o=a(t)?t:m;let n=e.dom;const r=[];for(;null!==n.parentNode&&void 0!==n.parentNode;){const e=n.parentNode,t=M.fromDom(e);if(r.push(t),!0===o(t))break;n=e}return r}}),down:l({selector:(e,t)=>((e,t)=>{const o=void 0===t?document:t.dom;return R(o)?[]:h(o.querySelectorAll(e),M.fromDom)})(t,e),predicate:_}),styles:l({get:(e,t)=>{const o=e.dom,n=window.getComputedStyle(o).getPropertyValue(t);return""!==n||F(e)?n:H(o,t)},getRaw:(e,t)=>{const o=e.dom,n=H(o,t);return u.from(n).filter((e=>e.length>0))},set:(e,t,o)=>{((e,t,o)=>{if(!n(o))throw console.error("Invalid call to CSS.set. Property ",t,":: Value ",o,":: Element ",e),new Error("CSS value must be a string: "+o);$(e)&&e.style.setProperty(t,o)})(e.dom,t,o)},remove:(e,t)=>{((e,t)=>{$(e)&&e.style.removeProperty(t)})(e.dom,t),((e,t,o=c)=>e.exists((e=>o(e,t))))(D(e,"style").map(B),"")&&E(e,"style")}}),attrs:l({get:x,set:(e,t,o)=>{S(e.dom,t,o)},remove:E,copyTo:(e,t)=>{const o=(n=e.dom.attributes,r=(e,t)=>(e[t.name]=t.value,e),s={},f(n,((e,t)=>{s=r(s,e)})),s);var n,r,s;((e,t)=>{const o=e.dom;((e,t)=>{const o=y(e);for(let n=0,r=o.length;n{S(o,t,e)}))})(t,o)}}),insert:l({before:z,after:K,afterAll:Y,append:U,appendAll:(e,t)=>{f(t,(t=>{U(e,t)}))},prepend:(e,t)=>{(e=>(e=>{const t=e.dom.childNodes;return u.from(t[0]).map(M.fromDom)})(e))(e).fold((()=>{U(e,t)}),(o=>{e.dom.insertBefore(t.dom,o.dom)}))},wrap:(e,t)=>{z(e,t),U(t,e)}}),remove:l({unwrap:e=>{const t=j(e);t.length>0&&Y(e,t),J(e)},remove:J}),create:l({nu:M.fromTag,clone:e=>M.fromDom(e.dom.cloneNode(!1)),text:M.fromText}),query:l({comparePosition:(e,t)=>e.dom.compareDocumentPosition(t.dom),prevSibling:e=>u.from(e.dom.previousSibling).map(M.fromDom),nextSibling:I}),property:l({children:j,name:v,parent:V,document:e=>{return(t=e,A(t)?t:M.fromDom(t.dom.ownerDocument)).dom;var t},isText:T,isComment:e=>8===w(e)||"#comment"===v(e),isElement:N,isSpecial:e=>{const t=v(e);return p(["script","noscript","iframe","noframes","noembed","title","style","textarea","xmp"],t)},getLanguage:e=>N(e)?D(e,"lang"):u.none(),getText:e=>X.get(e),setText:(e,t)=>X.set(e,t),isBoundary:e=>!!N(e)&&("body"===v(e)||p(Z,v(e))),isEmptyTag:e=>!!N(e)&&p(["br","img","hr","input"],v(e)),isNonEditable:e=>N(e)&&"false"===x(e,"contenteditable")}),eq:(e,t)=>e.dom===t.dom,is:k},ie="details",ae="mce-accordion",de="mce-accordion-summary",le="mce-accordion-body",ce="div";var me=tinymce.util.Tools.resolve("tinymce.util.Tools");const ue=e=>"SUMMARY"===(null==e?void 0:e.nodeName),ge=e=>"DETAILS"===(null==e?void 0:e.nodeName),pe=e=>e.hasAttribute("open"),he=e=>{const t=e.selection.getNode();return ue(t)||Boolean(e.dom.getParent(t,ue))},fe=e=>!he(e)&&e.dom.isEditable(e.selection.getNode())&&!e.mode.isReadOnly(),ye=e=>u.from(e.dom.getParent(e.selection.getNode(),ge)),ve=e=>(e.innerHTML='
',e),we=e=>ve(e.dom.create("p")),be=e=>t=>{((e,t)=>{if(ue(null==t?void 0:t.lastChild)){const o=we(e);t.appendChild(o),e.selection.setCursorLocation(o,0)}})(e,t),((e,t)=>{if(!ue(null==t?void 0:t.firstChild)){const o=(e=>ve(e.dom.create("summary")))(e);t.prepend(o),e.selection.setCursorLocation(o,0)}})(e,t)},Ne=e=>{if(!fe(e))return;const o=M.fromDom(e.getBody()),n=(e=>{const o=(new Date).getTime(),n=Math.floor(window.crypto.getRandomValues(new Uint32Array(1))[0]/4294967295*1e9);return t++,e+"_"+n+t+String(o)})("acc"),r=e.dom.encode(e.selection.getRng().toString()||e.translate("Accordion summary...")),s=e.dom.encode(e.translate("Accordion body...")),i=`${r}`,a=`<${ce} class="${le}">

${s}

`;e.undoManager.transact((()=>{e.insertContent([`
`,i,a,"
"].join("")),W(o,`[data-mce-id="${n}"]`).each((t=>{E(t,"data-mce-id"),W(t,"summary").each((t=>{const o=e.dom.createRng(),n=re(se,t);o.setStart(n.element.dom,n.offset),o.setEnd(n.element.dom,n.offset),e.selection.setRng(o)}))}))}))},Te=(e,t)=>{const o=null!=t?t:!pe(e);return o?e.setAttribute("open","open"):e.removeAttribute("open"),o},Ae=e=>{e.addCommand("InsertAccordion",(()=>Ne(e))),e.addCommand("ToggleAccordion",((t,o)=>((e,t)=>{ye(e).each((o=>{((e,t,o)=>{e.dispatch("ToggledAccordion",{element:t,state:o})})(e,o,Te(o,t))}))})(e,o))),e.addCommand("ToggleAllAccordions",((t,o)=>((e,t)=>{const o=Array.from(e.getBody().querySelectorAll("details"));0!==o.length&&(f(o,(e=>Te(e,null!=t?t:!pe(e)))),((e,t,o)=>{e.dispatch("ToggledAllAccordions",{elements:t,state:o})})(e,o,t))})(e,o))),e.addCommand("RemoveAccordion",(()=>(e=>{e.mode.isReadOnly()||ye(e).each((t=>{const{nextSibling:o}=t;o?(e.selection.select(o,!0),e.selection.collapse(!0)):((e,t)=>{const o=we(e);t.insertAdjacentElement("afterend",o),e.selection.setCursorLocation(o,0)})(e,t),t.remove()}))})(e)))};var Ce=tinymce.util.Tools.resolve("tinymce.html.Node");const Se=e=>{var t,o;return null!==(o=null===(t=e.attr("class"))||void 0===t?void 0:t.split(" "))&&void 0!==o?o:[]},xe=(e,t)=>{const o=new Set([...Se(e),...t]),n=Array.from(o);n.length>0&&e.attr("class",n.join(" "))},De=(e,t)=>{const o=(e=>{const o=[];for(let r=0,s=e.length;r0?o.join(" "):null)},Ee=e=>e.name===ie&&p(Se(e),ae),Oe=e=>{const t=e.children();let o,n;const r=[];for(let e=0;e{const t=new Ce("br",1);t.attr("data-mce-bogus","1"),e.empty(),e.append(t)};var Pe=tinymce.util.Tools.resolve("tinymce.util.VK");const Re=e=>{(e=>{e.on("keydown",(t=>{(!t.shiftKey&&t.keyCode===Pe.ENTER&&he(e)||(e=>{const t=e.selection.getRng();return ge(t.startContainer)&&t.collapsed&&0===t.startOffset})(e))&&(t.preventDefault(),e.execCommand("ToggleAccordion"))}))})(e),e.on("ExecCommand",(t=>{const o=t.command.toLowerCase();"delete"!==o&&"forwarddelete"!==o||!(e=>ye(e).isSome())(e)||(e=>{me.each(me.grep(e.dom.select("details",e.getBody())),be(e))})(e)}))};var ke=tinymce.util.Tools.resolve("tinymce.Env");const Be=e=>t=>{const o=()=>t.setEnabled(fe(e));return e.on("NodeChange",o),()=>e.off("NodeChange",o)};e.add("accordion",(e=>{(e=>{const t=()=>e.execCommand("InsertAccordion");e.ui.registry.addButton("accordion",{icon:"accordion",tooltip:"Insert accordion",onSetup:Be(e),onAction:t}),e.ui.registry.addMenuItem("accordion",{icon:"accordion",text:"Accordion",onSetup:Be(e),onAction:t}),e.ui.registry.addToggleButton("accordiontoggle",{icon:"accordion-toggle",tooltip:"Toggle accordion",onAction:()=>e.execCommand("ToggleAccordion")}),e.ui.registry.addToggleButton("accordionremove",{icon:"remove",tooltip:"Delete accordion",onAction:()=>e.execCommand("RemoveAccordion")}),e.ui.registry.addContextToolbar("accordion",{predicate:t=>e.dom.is(t,"details")&&e.getBody().contains(t)&&e.dom.isEditable(t.parentNode),items:"accordiontoggle accordionremove",scope:"node",position:"node"})})(e),Ae(e),Re(e),(e=>{e.on("PreInit",(()=>{const{serializer:t,parser:o}=e;o.addNodeFilter(ie,(e=>{for(let t=0;t0)for(let e=0;e{const t=new Set([de]);for(let o=0;o{ke.browser.isSafari()&&e.on("click",(t=>{if(ue(t.target)){const o=t.target,n=e.selection.getRng();n.collapsed&&n.startContainer===o.parentNode&&0===n.startOffset&&e.selection.setCursorLocation(o,0)}}))})(e)}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/advlist/plugin.min.js b/apps/web-antd/public/tinymce/plugins/advlist/plugin.min.js new file mode 100644 index 0000000..781711d --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/advlist/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=(t,e,s)=>{const r="UL"===e?"InsertUnorderedList":"InsertOrderedList";t.execCommand(r,!1,!1===s?null:{"list-style-type":s})},s=t=>e=>e.options.get(t),r=s("advlist_number_styles"),n=s("advlist_bullet_styles"),l=t=>null==t,i=t=>!l(t);class o{constructor(t,e){this.tag=t,this.value=e}static some(t){return new o(!0,t)}static none(){return o.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?o.some(t(this.value)):o.none()}bind(t){return this.tag?t(this.value):o.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:o.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return i(t)?o.some(t):o.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}o.singletonNone=new o(!1);const a=Array.prototype.indexOf,u=Object.keys;var d=tinymce.util.Tools.resolve("tinymce.util.Tools");const c=t=>e=>i(e)&&t.test(e.nodeName),h=c(/^(OL|UL|DL)$/),g=c(/^(TH|TD)$/),p=t=>l(t)||"default"===t?"":t,m=(t,e)=>s=>((t,e)=>{const s=t.selection.getNode();return e({parents:t.dom.getParents(s),element:s}),t.on("NodeChange",e),()=>t.off("NodeChange",e)})(t,(r=>((t,r)=>{const n=t.selection.getStart(!0);s.setActive(((t,e,s)=>((t,e,s)=>{for(let e=0,n=t.length;ee.nodeName===s&&((t,e)=>t.dom.isChildOf(e,t.getBody()))(t,e))))(t,r,e)),s.setEnabled(!((t,e)=>{const s=t.dom.getParent(e,"ol,ul,dl");return((t,e)=>null!==e&&!t.dom.isEditable(e))(t,s)||!t.selection.isEditable()})(t,n))})(t,r.parents))),v=(t,s,r,n,l,i)=>{const c={"lower-latin":"lower-alpha","upper-latin":"upper-alpha","lower-alpha":"lower-latin","upper-alpha":"upper-latin"},h=(g=t=>{return e=i,s=t,a.call(e,s)>-1;var e,s},((t,e)=>{const s={};return((t,e)=>{const s=u(t);for(let r=0,n=s.length;r{const n=e(t,r);s[n.k]=n.v})),s})(c,((t,e)=>({k:e,v:g(t)}))));var g;t.ui.registry.addSplitButton(s,{tooltip:r,icon:"OL"===l?"ordered-list":"unordered-list",presets:"listpreview",columns:3,fetch:t=>{t(d.map(i,(t=>{const e="OL"===l?"num":"bull",s="disc"===t||"decimal"===t?"default":t,r=p(t),n=(t=>t.replace(/\-/g," ").replace(/\b\w/g,(t=>t.toUpperCase())))(t);return{type:"choiceitem",value:r,icon:"list-"+e+"-"+s,text:n}})))},onAction:()=>t.execCommand(n),onItemAction:(s,r)=>{e(t,l,r)},select:e=>{const s=(t=>{const e=t.dom.getParent(t.selection.getNode(),"ol,ul"),s=t.dom.getStyle(e,"listStyleType");return o.from(s)})(t);return s.exists((t=>e===t||c[t]===e&&!h[e]))},onSetup:m(t,l)})},y=(t,s,r,n,l,i)=>{i.length>1?v(t,s,r,n,l,i):((t,s,r,n,l,i)=>{t.ui.registry.addToggleButton(s,{active:!1,tooltip:r,icon:"OL"===l?"ordered-list":"unordered-list",onSetup:m(t,l),onAction:()=>t.queryCommandState(n)||""===i?t.execCommand(n):e(t,l,i)})})(t,s,r,n,l,p(i[0]))};t.add("advlist",(t=>{t.hasPlugin("lists")?((t=>{const e=t.options.register;e("advlist_number_styles",{processor:"string[]",default:"default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman".split(",")}),e("advlist_bullet_styles",{processor:"string[]",default:"default,circle,square".split(",")})})(t),(t=>{y(t,"numlist","Numbered list","InsertOrderedList","OL",r(t)),y(t,"bullist","Bullet list","InsertUnorderedList","UL",n(t))})(t),(t=>{t.addCommand("ApplyUnorderedListStyle",((s,r)=>{e(t,"UL",r["list-style-type"])})),t.addCommand("ApplyOrderedListStyle",((s,r)=>{e(t,"OL",r["list-style-type"])}))})(t)):console.error("Please use the Lists plugin together with the List Styles plugin.")}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/anchor/plugin.min.js b/apps/web-antd/public/tinymce/plugins/anchor/plugin.min.js new file mode 100644 index 0000000..3c944ed --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/anchor/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=e=>e.options.get("allow_html_in_named_anchor");const a="a:not([href])",r=e=>!e,i=e=>e.getAttribute("id")||e.getAttribute("name")||"",l=e=>(e=>"a"===e.nodeName.toLowerCase())(e)&&!e.getAttribute("href")&&""!==i(e),s=e=>e.dom.getParent(e.selection.getStart(),a),d=(e,a)=>{const r=s(e);r?((e,t,o)=>{o.removeAttribute("name"),o.id=t,e.addVisual(),e.undoManager.add()})(e,a,r):((e,a)=>{e.undoManager.transact((()=>{n(e)||e.selection.collapse(!0),e.selection.isCollapsed()?e.insertContent(e.dom.createHTML("a",{id:a})):((e=>{const n=e.dom;t(n).walk(e.selection.getRng(),(e=>{o.each(e,(e=>{var t;l(t=e)&&!t.firstChild&&n.remove(e,!1)}))}))})(e),e.formatter.remove("namedAnchor",void 0,void 0,!0),e.formatter.apply("namedAnchor",{value:a}),e.addVisual())}))})(e,a),e.focus()},c=e=>(e=>r(e.attr("href"))&&!r(e.attr("id")||e.attr("name")))(e)&&!e.firstChild,m=e=>t=>{for(let o=0;ot=>{const o=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",o),o(),()=>{e.off("NodeChange",o)}};e.add("anchor",(e=>{(e=>{(0,e.options.register)("allow_html_in_named_anchor",{processor:"boolean",default:!1})})(e),(e=>{e.on("PreInit",(()=>{e.parser.addNodeFilter("a",m("false")),e.serializer.addNodeFilter("a",m(null))}))})(e),(e=>{e.addCommand("mceAnchor",(()=>{(e=>{const t=(e=>{const t=s(e);return t?i(t):""})(e);e.windowManager.open({title:"Anchor",size:"normal",body:{type:"panel",items:[{name:"id",type:"input",label:"ID",placeholder:"example"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{id:t},onSubmit:t=>{((e,t)=>/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(t)?(d(e,t),!0):(e.windowManager.alert("ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),!1))(e,t.getData().id)&&t.close()}})})(e)}))})(e),(e=>{const t=()=>e.execCommand("mceAnchor");e.ui.registry.addToggleButton("anchor",{icon:"bookmark",tooltip:"Anchor",onAction:t,onSetup:t=>{const o=e.selection.selectorChangedWithUnbind("a:not([href])",t.setActive).unbind,n=u(e)(t);return()=>{o(),n()}}}),e.ui.registry.addMenuItem("anchor",{icon:"bookmark",text:"Anchor...",onAction:t,onSetup:u(e)})})(e),e.on("PreInit",(()=>{(e=>{e.formatter.register("namedAnchor",{inline:"a",selector:a,remove:"all",split:!0,deep:!0,attributes:{id:"%value"},onmatch:(e,t,o)=>l(e)})})(e)}))}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/autolink/plugin.min.js b/apps/web-antd/public/tinymce/plugins/autolink/plugin.min.js new file mode 100644 index 0000000..ba87c86 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/autolink/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),n=t("autolink_pattern"),o=t("link_default_target"),r=t("link_default_protocol"),a=t("allow_unsafe_link_target"),s=e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=o=e,(r=String).prototype.isPrototypeOf(n)||(null===(a=o.constructor)||void 0===a?void 0:a.name)===r.name)?"string":t;var n,o,r,a})(e);const l=e=>undefined===e;const i=e=>!(e=>null==e)(e),c=Object.hasOwnProperty,d=e=>"\ufeff"===e;var u=tinymce.util.Tools.resolve("tinymce.dom.TextSeeker");const f=e=>/^[(\[{ \u00a0]$/.test(e),g=(e,t,n)=>{for(let o=t-1;o>=0;o--){const t=e.charAt(o);if(!d(t)&&n(t))return o}return-1},m=(e,t)=>{var o;const a=e.schema.getVoidElements(),s=n(e),{dom:i,selection:d}=e;if(null!==i.getParent(d.getNode(),"a[href]")||e.mode.isReadOnly())return null;const m=d.getRng(),k=u(i,(e=>{return i.isBlock(e)||(t=a,n=e.nodeName.toLowerCase(),c.call(t,n))||"false"===i.getContentEditable(e);var t,n})),{container:p,offset:y}=((e,t)=>{let n=e,o=t;for(;1===n.nodeType&&n.childNodes[o];)n=n.childNodes[o],o=3===n.nodeType?n.data.length:n.childNodes.length;return{container:n,offset:o}})(m.endContainer,m.endOffset),w=null!==(o=i.getParent(p,i.isBlock))&&void 0!==o?o:i.getRoot(),h=k.backwards(p,y+t,((e,t)=>{const n=e.data,o=g(n,t,(r=f,e=>!r(e)));var r,a;return-1===o||(a=n[o],/[?!,.;:]/.test(a))?o:o+1}),w);if(!h)return null;let v=h.container;const _=k.backwards(h.container,h.offset,((e,t)=>{v=e;const n=g(e.data,t,f);return-1===n?n:n+1}),w),A=i.createRng();_?A.setStart(_.container,_.offset):A.setStart(v,0),A.setEnd(h.container,h.offset);const C=A.toString().replace(/\uFEFF/g,"").match(s);if(C){let t=C[0];return $="www.",(b=t).length>=4&&b.substr(0,4)===$?t=r(e)+"://"+t:((e,t,n=0,o)=>{const r=e.indexOf(t,n);return-1!==r&&(!!l(o)||r+t.length<=o)})(t,"@")&&!(e=>/^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(e))(t)&&(t="mailto:"+t),{rng:A,url:t}}var b,$;return null},k=(e,t)=>{const{dom:n,selection:r}=e,{rng:l,url:i}=t,c=r.getBookmark();r.setRng(l);const d="createlink",u={command:d,ui:!1,value:i};if(!e.dispatch("BeforeExecCommand",u).isDefaultPrevented()){e.getDoc().execCommand(d,!1,i),e.dispatch("ExecCommand",u);const t=o(e);if(s(t)){const o=r.getNode();n.setAttrib(o,"target",t),"_blank"!==t||a(e)||n.setAttrib(o,"rel","noopener")}}r.moveToBookmark(c),e.nodeChanged()},p=e=>{const t=m(e,-1);i(t)&&k(e,t)},y=p;e.add("autolink",(e=>{(e=>{const t=e.options.register;t("autolink_pattern",{processor:"regexp",default:new RegExp("^"+/(?:[A-Za-z][A-Za-z\d.+-]{0,14}:\/\/(?:[-.~*+=!&;:'%@?^${}(),\w]+@)?|www\.|[-;:&=+$,.\w]+@)[A-Za-z\d-]+(?:\.[A-Za-z\d-]+)*(?::\d+)?(?:\/(?:[-.~*+=!;:'%@$(),\/\w]*[-~*+=%@$()\/\w])?)?(?:\?(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?(?:#(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?/g.source+"$","i")}),t("link_default_target",{processor:"string"}),t("link_default_protocol",{processor:"string",default:"https"})})(e),(e=>{e.on("keydown",(t=>{13!==t.keyCode||t.isDefaultPrevented()||(e=>{const t=m(e,0);i(t)&&k(e,t)})(e)})),e.on("keyup",(t=>{32===t.keyCode?p(e):(48===t.keyCode&&t.shiftKey||221===t.keyCode)&&y(e)}))})(e)}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/autoresize/plugin.min.js b/apps/web-antd/public/tinymce/plugins/autoresize/plugin.min.js new file mode 100644 index 0000000..1f5e37f --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/autoresize/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env");const o=e=>t=>t.options.get(e),n=o("min_height"),s=o("max_height"),i=o("autoresize_overflow_padding"),r=o("autoresize_bottom_margin"),g=(e,t)=>{const o=e.getBody();o&&(o.style.overflowY=t?"":"hidden",t||(o.scrollTop=0))},l=(e,t,o,n)=>{var s;const i=parseInt(null!==(s=e.getStyle(t,o,n))&&void 0!==s?s:"",10);return isNaN(i)?0:i},a=(e,o,r,c)=>{var d;const u=e.dom,h=e.getDoc();if(!h)return;if((e=>e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen())(e))return void g(e,!0);const m=h.documentElement,f=c?c():i(e),p=null!==(d=n(e))&&void 0!==d?d:e.getElement().offsetHeight;let y=p;const S=l(u,m,"margin-top",!0),v=l(u,m,"margin-bottom",!0);let C=m.offsetHeight+S+v+f;C<0&&(C=0);const H=e.getContainer().offsetHeight-e.getContentAreaContainer().offsetHeight;C+H>p&&(y=C+H);const b=s(e);b&&y>b?(y=b,g(e,!0)):g(e,!1);const w=o.get();if(w.set&&(e.dom.setStyles(e.getDoc().documentElement,{"min-height":0}),e.dom.setStyles(e.getBody(),{"min-height":"inherit"})),y!==w.totalHeight&&(C-f!==w.contentHeight||!w.set)){const n=y-w.totalHeight;if(u.setStyle(e.getContainer(),"height",y+"px"),o.set({totalHeight:y,contentHeight:C,set:!0}),(e=>{e.dispatch("ResizeEditor")})(e),t.browser.isSafari()&&(t.os.isMacOS()||t.os.isiOS())){const t=e.getWin();t.scrollTo(t.pageXOffset,t.pageYOffset)}e.hasFocus()&&(e=>{if("setcontent"===(null==e?void 0:e.type.toLowerCase())){const t=e;return!0===t.selection||!0===t.paste}return!1})(r)&&e.selection.scrollIntoView(),(t.browser.isSafari()||t.browser.isChromium())&&n<0&&a(e,o,r,c)}};e.add("autoresize",(e=>{if((e=>{const t=e.options.register;t("autoresize_overflow_padding",{processor:"number",default:1}),t("autoresize_bottom_margin",{processor:"number",default:50})})(e),e.options.isSet("resize")||e.options.set("resize",!1),!e.inline){const o=(()=>{let e={totalHeight:0,contentHeight:0,set:!1};return{get:()=>e,set:t=>{e=t}}})();((e,t)=>{e.addCommand("mceAutoResize",(()=>{a(e,t)}))})(e,o),((e,o)=>{const n=()=>r(e);e.on("init",(s=>{const r=i(e),g=e.dom;g.setStyles(e.getDoc().documentElement,{height:"auto"}),t.browser.isEdge()||t.browser.isIE()?g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r,"min-height":0}):g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r}),a(e,o,s,n)})),e.on("NodeChange SetContent keyup FullscreenStateChanged ResizeContent",(t=>{a(e,o,t,n)}))})(e,o)}}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/autosave/plugin.min.js b/apps/web-antd/public/tinymce/plugins/autosave/plugin.min.js new file mode 100644 index 0000000..354d42e --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/autosave/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(r=o=t,(a=String).prototype.isPrototypeOf(r)||(null===(s=o.constructor)||void 0===s?void 0:s.name)===a.name)?"string":e;var r,o,a,s})(t);const r=t=>undefined===t;var o=tinymce.util.Tools.resolve("tinymce.util.Delay"),a=tinymce.util.Tools.resolve("tinymce.util.LocalStorage"),s=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=t=>{const e=/^(\d+)([ms]?)$/.exec(t);return(e&&e[2]?{s:1e3,m:6e4}[e[2]]:1)*parseInt(t,10)},i=t=>e=>e.options.get(t),u=i("autosave_ask_before_unload"),l=i("autosave_restore_when_empty"),c=i("autosave_interval"),d=i("autosave_retention"),m=t=>{const e=document.location;return t.options.get("autosave_prefix").replace(/{path}/g,e.pathname).replace(/{query}/g,e.search).replace(/{hash}/g,e.hash).replace(/{id}/g,t.id)},v=(t,e)=>{if(r(e))return t.dom.isEmpty(t.getBody());{const r=s.trim(e);if(""===r)return!0;{const e=(new DOMParser).parseFromString(r,"text/html");return t.dom.isEmpty(e)}}},f=t=>{var e;const r=parseInt(null!==(e=a.getItem(m(t)+"time"))&&void 0!==e?e:"0",10)||0;return!((new Date).getTime()-r>d(t)&&(p(t,!1),1))},p=(t,e)=>{const r=m(t);a.removeItem(r+"draft"),a.removeItem(r+"time"),!1!==e&&(t=>{t.dispatch("RemoveDraft")})(t)},y=t=>{const e=m(t);!v(t)&&t.isDirty()&&(a.setItem(e+"draft",t.getContent({format:"raw",no_events:!0})),a.setItem(e+"time",(new Date).getTime().toString()),(t=>{t.dispatch("StoreDraft")})(t))},g=t=>{var e;const r=m(t);f(t)&&(t.setContent(null!==(e=a.getItem(r+"draft"))&&void 0!==e?e:"",{format:"raw"}),(t=>{t.dispatch("RestoreDraft")})(t))};var D=tinymce.util.Tools.resolve("tinymce.EditorManager");const h=t=>e=>{const r=()=>f(t)&&!t.mode.isReadOnly();e.setEnabled(r());const o=()=>e.setEnabled(r());return t.on("StoreDraft RestoreDraft RemoveDraft",o),()=>t.off("StoreDraft RestoreDraft RemoveDraft",o)};t.add("autosave",(t=>((t=>{const r=t.options.register,o=t=>{const r=e(t);return r?{value:n(t),valid:r}:{valid:!1,message:"Must be a string."}};r("autosave_ask_before_unload",{processor:"boolean",default:!0}),r("autosave_prefix",{processor:"string",default:"tinymce-autosave-{path}{query}{hash}-{id}-"}),r("autosave_restore_when_empty",{processor:"boolean",default:!1}),r("autosave_interval",{processor:o,default:"30s"}),r("autosave_retention",{processor:o,default:"20m"})})(t),(t=>{t.editorManager.on("BeforeUnload",(t=>{let e;s.each(D.get(),(t=>{t.plugins.autosave&&t.plugins.autosave.storeDraft(),!e&&t.isDirty()&&u(t)&&(e=t.translate("You have unsaved changes are you sure you want to navigate away?"))})),e&&(t.preventDefault(),t.returnValue=e)}))})(t),(t=>{(t=>{const e=c(t);o.setEditorInterval(t,(()=>{y(t)}),e)})(t);const e=()=>{(t=>{t.undoManager.transact((()=>{g(t),p(t)})),t.focus()})(t)};t.ui.registry.addButton("restoredraft",{tooltip:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)}),t.ui.registry.addMenuItem("restoredraft",{text:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)})})(t),t.on("init",(()=>{l(t)&&t.dom.isEmpty(t.getBody())&&g(t)})),(t=>({hasDraft:()=>f(t),storeDraft:()=>y(t),restoreDraft:()=>g(t),removeDraft:e=>p(t,e),isEmpty:e=>v(t,e)}))(t))))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/charmap/plugin.min.js b/apps/web-antd/public/tinymce/plugins/charmap/plugin.min.js new file mode 100644 index 0000000..cd905e6 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/charmap/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=(e,t)=>{const r=((e,t)=>e.dispatch("insertCustomChar",{chr:t}))(e,t).chr;e.execCommand("mceInsertContent",!1,r)},r=e=>t=>e===t,a=e=>"array"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(r=a=e,(n=String).prototype.isPrototypeOf(r)||(null===(i=a.constructor)||void 0===i?void 0:i.name)===n.name)?"string":t;var r,a,n,i})(e);const n=r(null),i=r(void 0),o=e=>"function"==typeof e,s=()=>false;class l{constructor(e,t){this.tag=e,this.value=t}static some(e){return new l(!0,e)}static none(){return l.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?l.some(e(this.value)):l.none()}bind(e){return this.tag?e(this.value):l.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:l.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return null==e?l.none():l.some(e)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}l.singletonNone=new l(!1);const c=Array.prototype.push,u=(e,t)=>{const r=e.length,a=new Array(r);for(let n=0;nt=>t.options.get(e),m=h("charmap"),p=h("charmap_append"),d=g.isArray,f="User Defined",y=e=>{return d(e)?(t=e,g.grep(t,(e=>d(e)&&2===e.length))):"function"==typeof e?e():[];var t},b=e=>{const t=((e,t)=>{const r=m(e);r&&(t=[{name:f,characters:y(r)}]);const a=p(e);if(a){const e=g.grep(t,(e=>e.name===f));return e.length?(e[0].characters=[...e[0].characters,...y(a)],t):t.concat({name:f,characters:y(a)})}return t})(e,[{name:"Currency",characters:[[36,"dollar sign"],[162,"cent sign"],[8364,"euro sign"],[163,"pound sign"],[165,"yen sign"],[164,"currency sign"],[8352,"euro-currency sign"],[8353,"colon sign"],[8354,"cruzeiro sign"],[8355,"french franc sign"],[8356,"lira sign"],[8357,"mill sign"],[8358,"naira sign"],[8359,"peseta sign"],[8360,"rupee sign"],[8361,"won sign"],[8362,"new sheqel sign"],[8363,"dong sign"],[8365,"kip sign"],[8366,"tugrik sign"],[8367,"drachma sign"],[8368,"german penny symbol"],[8369,"peso sign"],[8370,"guarani sign"],[8371,"austral sign"],[8372,"hryvnia sign"],[8373,"cedi sign"],[8374,"livre tournois sign"],[8375,"spesmilo sign"],[8376,"tenge sign"],[8377,"indian rupee sign"],[8378,"turkish lira sign"],[8379,"nordic mark sign"],[8380,"manat sign"],[8381,"ruble sign"],[20870,"yen character"],[20803,"yuan character"],[22291,"yuan character, in hong kong and taiwan"],[22278,"yen/yuan character variant one"]]},{name:"Text",characters:[[169,"copyright sign"],[174,"registered sign"],[8482,"trade mark sign"],[8240,"per mille sign"],[181,"micro sign"],[183,"middle dot"],[8226,"bullet"],[8230,"three dot leader"],[8242,"minutes / feet"],[8243,"seconds / inches"],[167,"section sign"],[182,"paragraph sign"],[223,"sharp s / ess-zed"]]},{name:"Quotations",characters:[[8249,"single left-pointing angle quotation mark"],[8250,"single right-pointing angle quotation mark"],[171,"left pointing guillemet"],[187,"right pointing guillemet"],[8216,"left single quotation mark"],[8217,"right single quotation mark"],[8220,"left double quotation mark"],[8221,"right double quotation mark"],[8218,"single low-9 quotation mark"],[8222,"double low-9 quotation mark"],[60,"less-than sign"],[62,"greater-than sign"],[8804,"less-than or equal to"],[8805,"greater-than or equal to"],[8211,"en dash"],[8212,"em dash"],[175,"macron"],[8254,"overline"],[164,"currency sign"],[166,"broken bar"],[168,"diaeresis"],[161,"inverted exclamation mark"],[191,"turned question mark"],[710,"circumflex accent"],[732,"small tilde"],[176,"degree sign"],[8722,"minus sign"],[177,"plus-minus sign"],[247,"division sign"],[8260,"fraction slash"],[215,"multiplication sign"],[185,"superscript one"],[178,"superscript two"],[179,"superscript three"],[188,"fraction one quarter"],[189,"fraction one half"],[190,"fraction three quarters"]]},{name:"Mathematical",characters:[[402,"function / florin"],[8747,"integral"],[8721,"n-ary sumation"],[8734,"infinity"],[8730,"square root"],[8764,"similar to"],[8773,"approximately equal to"],[8776,"almost equal to"],[8800,"not equal to"],[8801,"identical to"],[8712,"element of"],[8713,"not an element of"],[8715,"contains as member"],[8719,"n-ary product"],[8743,"logical and"],[8744,"logical or"],[172,"not sign"],[8745,"intersection"],[8746,"union"],[8706,"partial differential"],[8704,"for all"],[8707,"there exists"],[8709,"diameter"],[8711,"backward difference"],[8727,"asterisk operator"],[8733,"proportional to"],[8736,"angle"]]},{name:"Extended Latin",characters:[[192,"A - grave"],[193,"A - acute"],[194,"A - circumflex"],[195,"A - tilde"],[196,"A - diaeresis"],[197,"A - ring above"],[256,"A - macron"],[198,"ligature AE"],[199,"C - cedilla"],[200,"E - grave"],[201,"E - acute"],[202,"E - circumflex"],[203,"E - diaeresis"],[274,"E - macron"],[204,"I - grave"],[205,"I - acute"],[206,"I - circumflex"],[207,"I - diaeresis"],[298,"I - macron"],[208,"ETH"],[209,"N - tilde"],[210,"O - grave"],[211,"O - acute"],[212,"O - circumflex"],[213,"O - tilde"],[214,"O - diaeresis"],[216,"O - slash"],[332,"O - macron"],[338,"ligature OE"],[352,"S - caron"],[217,"U - grave"],[218,"U - acute"],[219,"U - circumflex"],[220,"U - diaeresis"],[362,"U - macron"],[221,"Y - acute"],[376,"Y - diaeresis"],[562,"Y - macron"],[222,"THORN"],[224,"a - grave"],[225,"a - acute"],[226,"a - circumflex"],[227,"a - tilde"],[228,"a - diaeresis"],[229,"a - ring above"],[257,"a - macron"],[230,"ligature ae"],[231,"c - cedilla"],[232,"e - grave"],[233,"e - acute"],[234,"e - circumflex"],[235,"e - diaeresis"],[275,"e - macron"],[236,"i - grave"],[237,"i - acute"],[238,"i - circumflex"],[239,"i - diaeresis"],[299,"i - macron"],[240,"eth"],[241,"n - tilde"],[242,"o - grave"],[243,"o - acute"],[244,"o - circumflex"],[245,"o - tilde"],[246,"o - diaeresis"],[248,"o slash"],[333,"o macron"],[339,"ligature oe"],[353,"s - caron"],[249,"u - grave"],[250,"u - acute"],[251,"u - circumflex"],[252,"u - diaeresis"],[363,"u - macron"],[253,"y - acute"],[254,"thorn"],[255,"y - diaeresis"],[563,"y - macron"],[913,"Alpha"],[914,"Beta"],[915,"Gamma"],[916,"Delta"],[917,"Epsilon"],[918,"Zeta"],[919,"Eta"],[920,"Theta"],[921,"Iota"],[922,"Kappa"],[923,"Lambda"],[924,"Mu"],[925,"Nu"],[926,"Xi"],[927,"Omicron"],[928,"Pi"],[929,"Rho"],[931,"Sigma"],[932,"Tau"],[933,"Upsilon"],[934,"Phi"],[935,"Chi"],[936,"Psi"],[937,"Omega"],[945,"alpha"],[946,"beta"],[947,"gamma"],[948,"delta"],[949,"epsilon"],[950,"zeta"],[951,"eta"],[952,"theta"],[953,"iota"],[954,"kappa"],[955,"lambda"],[956,"mu"],[957,"nu"],[958,"xi"],[959,"omicron"],[960,"pi"],[961,"rho"],[962,"final sigma"],[963,"sigma"],[964,"tau"],[965,"upsilon"],[966,"phi"],[967,"chi"],[968,"psi"],[969,"omega"]]},{name:"Symbols",characters:[[8501,"alef symbol"],[982,"pi symbol"],[8476,"real part symbol"],[978,"upsilon - hook symbol"],[8472,"Weierstrass p"],[8465,"imaginary part"]]},{name:"Arrows",characters:[[8592,"leftwards arrow"],[8593,"upwards arrow"],[8594,"rightwards arrow"],[8595,"downwards arrow"],[8596,"left right arrow"],[8629,"carriage return"],[8656,"leftwards double arrow"],[8657,"upwards double arrow"],[8658,"rightwards double arrow"],[8659,"downwards double arrow"],[8660,"left right double arrow"],[8756,"therefore"],[8834,"subset of"],[8835,"superset of"],[8836,"not a subset of"],[8838,"subset of or equal to"],[8839,"superset of or equal to"],[8853,"circled plus"],[8855,"circled times"],[8869,"perpendicular"],[8901,"dot operator"],[8968,"left ceiling"],[8969,"right ceiling"],[8970,"left floor"],[8971,"right floor"],[9001,"left-pointing angle bracket"],[9002,"right-pointing angle bracket"],[9674,"lozenge"],[9824,"black spade suit"],[9827,"black club suit"],[9829,"black heart suit"],[9830,"black diamond suit"],[8194,"en space"],[8195,"em space"],[8201,"thin space"],[8204,"zero width non-joiner"],[8205,"zero width joiner"],[8206,"left-to-right mark"],[8207,"right-to-left mark"]]}]);return t.length>1?[{name:"All",characters:(r=t,n=e=>e.characters,(e=>{const t=[];for(let r=0,n=e.length;r{let t=e;return{get:()=>t,set:e=>{t=e}}},v=(e,t,r=0,a)=>{const n=e.indexOf(t,r);return-1!==n&&(!!i(a)||n+t.length<=a)},k=String.fromCodePoint,C=(e,t)=>{const r=[],a=t.toLowerCase();return(e=>{for(let n=0,i=e.length;n!!v(k(e).toLowerCase(),r)||v(t.toLowerCase(),r)||v(t.toLowerCase().replace(/\s+/g,""),r))((t=e[n])[0],t[1],a)&&r.push(t);var t})(e.characters),u(r,(e=>({text:e[1],value:k(e[0]),icon:k(e[0])})))},x="pattern",A=(e,r)=>{const a=()=>[{label:"Search",type:"input",name:x},{type:"collection",name:"results"}],i=1===r.length?w(f):w("All"),o=(e=>{let t=null;const r=()=>{n(t)||(clearTimeout(t),t=null)};return{cancel:r,throttle:(...a)=>{r(),t=setTimeout((()=>{t=null,e.apply(null,a)}),40)}}})((e=>{const t=e.getData().pattern;((e,t)=>{var a,n;(a=r,n=e=>e.name===i.get(),((e,t,r)=>{for(let a=0,n=e.length;a{const a=C(r,t);e.setData({results:a})}))})(e,t)})),c={title:"Special Character",size:"normal",body:1===r.length?{type:"panel",items:a()}:{type:"tabpanel",tabs:u(r,(e=>({title:e.name,name:e.name,items:a()})))},buttons:[{type:"cancel",name:"close",text:"Close",primary:!0}],initialData:{pattern:"",results:C(r[0],"")},onAction:(r,a)=>{"results"===a.name&&(t(e,a.value),r.close())},onTabChange:(e,t)=>{i.set(t.newTabName),o.throttle(e)},onChange:(e,t)=>{t.name===x&&o.throttle(e)}};e.windowManager.open(c).focus(x)},q=e=>t=>{const r=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",r),r(),()=>{e.off("NodeChange",r)}};e.add("charmap",(e=>{(e=>{const t=e.options.register,r=e=>o(e)||a(e);t("charmap",{processor:r}),t("charmap_append",{processor:r})})(e);const r=b(e);return((e,t)=>{e.addCommand("mceShowCharmap",(()=>{A(e,t)}))})(e,r),(e=>{const t=()=>e.execCommand("mceShowCharmap");e.ui.registry.addButton("charmap",{icon:"insert-character",tooltip:"Special character",onAction:t,onSetup:q(e)}),e.ui.registry.addMenuItem("charmap",{icon:"insert-character",text:"Special character...",onAction:t,onSetup:q(e)})})(e),((e,t)=>{e.ui.registry.addAutocompleter("charmap",{trigger:":",columns:"auto",minChars:2,fetch:(e,r)=>new Promise(((r,a)=>{r(C(t,e))})),onAction:(t,r,a)=>{e.selection.setRng(r),e.insertContent(a),t.hide()}})})(e,r[0]),(e=>({getCharMap:()=>b(e),insertChar:r=>{t(e,r)}}))(e)}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/code/plugin.min.js b/apps/web-antd/public/tinymce/plugins/code/plugin.min.js new file mode 100644 index 0000000..54a780c --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/code/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/codesample/plugin.min.js b/apps/web-antd/public/tinymce/plugins/codesample/plugin.min.js new file mode 100644 index 0000000..2283bb9 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/codesample/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>!(e=>null==e)(e),n=()=>{};class a{constructor(e,t){this.tag=e,this.value=t}static some(e){return new a(!0,e)}static none(){return a.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?a.some(e(this.value)):a.none()}bind(e){return this.tag?e(this.value):a.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:a.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return t(e)?a.some(e):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);var s=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils");const r="undefined"!=typeof window?window:Function("return this;")(),i=function(){const e=window.Prism;window.Prism={manual:!0};var t=function(e){var t=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,n=0,a={},s={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(t){return t instanceof r?new r(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=d.reach);x+=_.value.length,_=_.next){var F=_.value;if(t.length>e.length)return;if(!(F instanceof r)){var A,S=1;if(y){if(!(A=i(v,x,e,m))||A.index>=e.length)break;var $=A.index,z=A.index+A[0].length,E=x;for(E+=_.value.length;$>=E;)E+=(_=_.next).value.length;if(x=E-=_.value.length,_.value instanceof r)continue;for(var C=_;C!==t.tail&&(Ed.reach&&(d.reach=O);var P=_.prev;if(B&&(P=u(t,P,B),x+=B.length),c(t,P,S),_=u(t,P,new r(g,f?s.tokenize(j,f):j,w,j)),T&&u(t,_,T),S>1){var N={cause:g+","+b,reach:O};o(e,t,n,_.prev,x,N),d&&N.reach>d.reach&&(d.reach=N.reach)}}}}}}function l(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function u(e,t,n){var a=t.next,s={value:n,prev:t,next:a};return t.next=s,a.prev=s,e.length++,s}function c(e,t,n){for(var a=t.next,s=0;s"+r.content+""},!e.document)return e.addEventListener?(s.disableWorkerMessageHandler||e.addEventListener("message",(function(t){var n=JSON.parse(t.data),a=n.language,r=n.code,i=n.immediateClose;e.postMessage(s.highlight(r,s.languages[a],a)),i&&e.close()}),!1),s):s;var d=s.util.currentScript();function g(){s.manual||s.highlightAll()}if(d&&(s.filename=d.src,d.hasAttribute("data-manual")&&(s.manual=!0)),!s.manual){var p=document.readyState;"loading"===p||"interactive"===p&&d&&d.defer?document.addEventListener("DOMContentLoaded",g):window.requestAnimationFrame?window.requestAnimationFrame(g):window.setTimeout(g,16)}return s}("undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{});return t.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,a,s,r){if(n.language===a){var i=n.tokenStack=[];n.code=n.code.replace(s,(function(e){if("function"==typeof r&&!r(e))return e;for(var s,o=i.length;-1!==n.code.indexOf(s=t(a,o));)++o;return i[o]=e,s})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,a){if(n.language===a&&n.tokenStack){n.grammar=e.languages[a];var s=0,r=Object.keys(n.tokenStack);!function i(o){for(var l=0;l=r.length);l++){var u=o[l];if("string"==typeof u||u.content&&"string"==typeof u.content){var c=r[s],d=n.tokenStack[c],g="string"==typeof u?u:u.content,p=t(a,c),b=g.indexOf(p);if(b>-1){++s;var h=g.substring(0,b),f=new e.Token(a,e.tokenize(d,n.grammar),"language-"+a,d),m=g.substring(b+p.length),y=[];h&&y.push.apply(y,i([h])),y.push(f),m&&y.push.apply(y,i([m])),"string"==typeof u?o.splice.apply(o,[l,1].concat(y)):u.content=y}}else u.content&&i(u.content)}return o}(n.tokens)}}}})}(t),t.languages.c=t.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),t.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),t.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},t.languages.c.string],char:t.languages.c.char,comment:t.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:t.languages.c}}}}),t.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete t.languages.c.boolean,function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n=/\b(?!)\w+(?:\s*\.\s*\w+)*\b/.source.replace(//g,(function(){return t.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!)\w+/.source.replace(//g,(function(){return t.source}))),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/(?:\s*:\s*)?|:\s*/.source.replace(//g,(function(){return n}))+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(t),function(e){function t(e,t){return e.replace(/<<(\d+)>>/g,(function(e,n){return"(?:"+t[+n]+")"}))}function n(e,n,a){return RegExp(t(e,n),a||"")}function a(e,t){for(var n=0;n>/g,(function(){return"(?:"+e+")"}));return e.replace(/<>/g,"[^\\s\\S]")}var s="bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",r="class enum interface record struct",i="add alias and ascending async await by descending from(?=\\s*(?:\\w|$)) get global group into init(?=\\s*;) join let nameof not notnull on or orderby partial remove select set unmanaged value when where with(?=\\s*{)",o="abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield";function l(e){return"\\b(?:"+e.trim().replace(/ /g,"|")+")\\b"}var u=l(r),c=RegExp(l(s+" "+r+" "+i+" "+o)),d=l(r+" "+i+" "+o),g=l(s+" "+r+" "+o),p=a(/<(?:[^<>;=+\-*/%&|^]|<>)*>/.source,2),b=a(/\((?:[^()]|<>)*\)/.source,2),h=/@?\b[A-Za-z_]\w*\b/.source,f=t(/<<0>>(?:\s*<<1>>)?/.source,[h,p]),m=t(/(?!<<0>>)<<1>>(?:\s*\.\s*<<1>>)*/.source,[d,f]),y=/\[\s*(?:,\s*)*\]/.source,w=t(/<<0>>(?:\s*(?:\?\s*)?<<1>>)*(?:\s*\?)?/.source,[m,y]),k=t(/[^,()<>[\];=+\-*/%&|^]|<<0>>|<<1>>|<<2>>/.source,[p,b,y]),v=t(/\(<<0>>+(?:,<<0>>+)+\)/.source,[k]),_=t(/(?:<<0>>|<<1>>)(?:\s*(?:\?\s*)?<<2>>)*(?:\s*\?)?/.source,[v,m,y]),x={keyword:c,punctuation:/[<>()?,.:[\]]/},F=/'(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'/.source,A=/"(?:\\.|[^\\"\r\n])*"/.source,S=/@"(?:""|\\[\s\S]|[^\\"])*"(?!")/.source;e.languages.csharp=e.languages.extend("clike",{string:[{pattern:n(/(^|[^$\\])<<0>>/.source,[S]),lookbehind:!0,greedy:!0},{pattern:n(/(^|[^@$\\])<<0>>/.source,[A]),lookbehind:!0,greedy:!0}],"class-name":[{pattern:n(/(\busing\s+static\s+)<<0>>(?=\s*;)/.source,[m]),lookbehind:!0,inside:x},{pattern:n(/(\busing\s+<<0>>\s*=\s*)<<1>>(?=\s*;)/.source,[h,_]),lookbehind:!0,inside:x},{pattern:n(/(\busing\s+)<<0>>(?=\s*=)/.source,[h]),lookbehind:!0},{pattern:n(/(\b<<0>>\s+)<<1>>/.source,[u,f]),lookbehind:!0,inside:x},{pattern:n(/(\bcatch\s*\(\s*)<<0>>/.source,[m]),lookbehind:!0,inside:x},{pattern:n(/(\bwhere\s+)<<0>>/.source,[h]),lookbehind:!0},{pattern:n(/(\b(?:is(?:\s+not)?|as)\s+)<<0>>/.source,[w]),lookbehind:!0,inside:x},{pattern:n(/\b<<0>>(?=\s+(?!<<1>>|with\s*\{)<<2>>(?:\s*[=,;:{)\]]|\s+(?:in|when)\b))/.source,[_,g,h]),inside:x}],keyword:c,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:[dflmu]|lu|ul)?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),e.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),e.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:n(/([(,]\s*)<<0>>(?=\s*:)/.source,[h]),lookbehind:!0,alias:"punctuation"}}),e.languages.insertBefore("csharp","class-name",{namespace:{pattern:n(/(\b(?:namespace|using)\s+)<<0>>(?:\s*\.\s*<<0>>)*(?=\s*[;{])/.source,[h]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:n(/(\b(?:default|sizeof|typeof)\s*\(\s*(?!\s))(?:[^()\s]|\s(?!\s)|<<0>>)*(?=\s*\))/.source,[b]),lookbehind:!0,alias:"class-name",inside:x},"return-type":{pattern:n(/<<0>>(?=\s+(?:<<1>>\s*(?:=>|[({]|\.\s*this\s*\[)|this\s*\[))/.source,[_,m]),inside:x,alias:"class-name"},"constructor-invocation":{pattern:n(/(\bnew\s+)<<0>>(?=\s*[[({])/.source,[_]),lookbehind:!0,inside:x,alias:"class-name"},"generic-method":{pattern:n(/<<0>>\s*<<1>>(?=\s*\()/.source,[h,p]),inside:{function:n(/^<<0>>/.source,[h]),generic:{pattern:RegExp(p),alias:"class-name",inside:x}}},"type-list":{pattern:n(/\b((?:<<0>>\s+<<1>>|record\s+<<1>>\s*<<5>>|where\s+<<2>>)\s*:\s*)(?:<<3>>|<<4>>|<<1>>\s*<<5>>|<<6>>)(?:\s*,\s*(?:<<3>>|<<4>>|<<6>>))*(?=\s*(?:where|[{;]|=>|$))/.source,[u,f,h,_,c.source,b,/\bnew\s*\(\s*\)/.source]),lookbehind:!0,inside:{"record-arguments":{pattern:n(/(^(?!new\s*\()<<0>>\s*)<<1>>/.source,[f,b]),lookbehind:!0,greedy:!0,inside:e.languages.csharp},keyword:c,"class-name":{pattern:RegExp(_),greedy:!0,inside:x},punctuation:/[,()]/}},preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(#)\b(?:define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var $=A+"|"+F,z=t(/\/(?![*/])|\/\/[^\r\n]*[\r\n]|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>/.source,[$]),E=a(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[z]),2),C=/\b(?:assembly|event|field|method|module|param|property|return|type)\b/.source,j=t(/<<0>>(?:\s*\(<<1>>*\))?/.source,[m,E]);e.languages.insertBefore("csharp","class-name",{attribute:{pattern:n(/((?:^|[^\s\w>)?])\s*\[\s*)(?:<<0>>\s*:\s*)?<<1>>(?:\s*,\s*<<1>>)*(?=\s*\])/.source,[C,j]),lookbehind:!0,greedy:!0,inside:{target:{pattern:n(/^<<0>>(?=\s*:)/.source,[C]),alias:"keyword"},"attribute-arguments":{pattern:n(/\(<<0>>*\)/.source,[E]),inside:e.languages.csharp},"class-name":{pattern:RegExp(m),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var B=/:[^}\r\n]+/.source,T=a(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[z]),2),O=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[T,B]),P=a(t(/[^"'/()]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>|\(<>*\)/.source,[$]),2),N=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[P,B]);function R(t,a){return{interpolation:{pattern:n(/((?:^|[^{])(?:\{\{)*)<<0>>/.source,[t]),lookbehind:!0,inside:{"format-string":{pattern:n(/(^\{(?:(?![}:])<<0>>)*)<<1>>(?=\}$)/.source,[a,B]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:e.languages.csharp}}},string:/[\s\S]+/}}e.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:n(/(^|[^\\])(?:\$@|@\$)"(?:""|\\[\s\S]|\{\{|<<0>>|[^\\{"])*"/.source,[O]),lookbehind:!0,greedy:!0,inside:R(O,T)},{pattern:n(/(^|[^@\\])\$"(?:\\.|\{\{|<<0>>|[^\\"{])*"/.source,[N]),lookbehind:!0,greedy:!0,inside:R(N,P)}],char:{pattern:RegExp(F),greedy:!0}}),e.languages.dotnet=e.languages.cs=e.languages.csharp}(t),function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+t.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),n.tag.addAttribute("style","css"))}(t),function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,n=/(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source,a={pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};e.languages.java=e.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[a,{pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z]\w*(?=\s+\w+\s*[;,=()]|\s*(?:\[[\s,]*\]\s*)?::\s*new\b)/.source),lookbehind:!0,inside:a.inside},{pattern:RegExp(/(\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\s+)/.source+n+/[A-Z]\w*\b/.source),lookbehind:!0,inside:a.inside}],keyword:t,function:[e.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),e.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":a,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp(/(\bimport\s+)/.source+n+/(?:[A-Z]\w*|\*)(?=\s*;)/.source),lookbehind:!0,inside:{namespace:a.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp(/(\bimport\s+static\s+)/.source+n+/(?:\w+|\*)(?=\s*;)/.source),lookbehind:!0,alias:"static",inside:{namespace:a.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp(/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)(?!)[a-z]\w*(?:\.[a-z]\w*)*\.?/.source.replace(//g,(function(){return t.source}))),lookbehind:!0,inside:{punctuation:/\./}}})}(t),t.languages.javascript=t.languages.extend("clike",{"class-name":[t.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),t.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,t.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:t.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:t.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:t.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:t.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:t.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),t.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:t.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),t.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),t.languages.markup&&(t.languages.markup.tag.addInlined("script","javascript"),t.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),t.languages.js=t.languages.javascript,t.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},t.languages.markup.tag.inside["attr-value"].inside.entity=t.languages.markup.entity,t.languages.markup.doctype.inside["internal-subset"].inside=t.languages.markup,t.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(t.languages.markup.tag,"addInlined",{value:function(e,n){var a={};a["language-"+n]={pattern:/(^$)/i,lookbehind:!0,inside:t.languages[n]},a.cdata=/^$/i;var s={"included-cdata":{pattern://i,inside:a}};s["language-"+n]={pattern:/[\s\S]+/,inside:t.languages[n]};var r={};r[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:s},t.languages.insertBefore("markup","cdata",r)}}),Object.defineProperty(t.languages.markup.tag,"addAttribute",{value:function(e,n){t.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[n,"language-"+n],inside:t.languages[n]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),t.languages.html=t.languages.markup,t.languages.mathml=t.languages.markup,t.languages.svg=t.languages.markup,t.languages.xml=t.languages.extend("markup",{}),t.languages.ssml=t.languages.xml,t.languages.atom=t.languages.xml,t.languages.rss=t.languages.xml,function(e){var t=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,n=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],a=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,s=/|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,r=/[{}\[\](),:;]/;e.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:t,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:a,operator:s,punctuation:r};var i={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:e.languages.php},o=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:i}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:i}}];e.languages.insertBefore("php","variable",{string:o,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:t,string:o,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,number:a,operator:s,punctuation:r}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),e.hooks.add("before-tokenize",(function(t){/<\?/.test(t.code)&&e.languages["markup-templating"].buildPlaceholders(t,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g)})),e.hooks.add("after-tokenize",(function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"php")}))}(t),t.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},t.languages.python["string-interpolation"].inside.interpolation.inside.rest=t.languages.python,t.languages.py=t.languages.python,function(e){e.languages.ruby=e.languages.extend("clike",{comment:{pattern:/#.*|^=begin\s[\s\S]*?^=end/m,greedy:!0},"class-name":{pattern:/(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,operator:/\.{2,3}|&\.|===||[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,punctuation:/[(){}[\].,;]/}),e.languages.insertBefore("ruby","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}});var t={pattern:/((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,lookbehind:!0,inside:{content:{pattern:/^(#\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:e.languages.ruby},delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"}}};delete e.languages.ruby.function;var n="(?:"+[/([^a-zA-Z0-9\s{(\[<=])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/\((?:[^()\\]|\\[\s\S]|\((?:[^()\\]|\\[\s\S])*\))*\)/.source,/\{(?:[^{}\\]|\\[\s\S]|\{(?:[^{}\\]|\\[\s\S])*\})*\}/.source,/\[(?:[^\[\]\\]|\\[\s\S]|\[(?:[^\[\]\\]|\\[\s\S])*\])*\]/.source,/<(?:[^<>\\]|\\[\s\S]|<(?:[^<>\\]|\\[\s\S])*>)*>/.source].join("|")+")",a=/(?:"(?:\\.|[^"\\\r\n])*"|(?:\b[a-zA-Z_]\w*|[^\s\0-\x7F]+)[?!]?|\$.)/.source;e.languages.insertBefore("ruby","keyword",{"regex-literal":[{pattern:RegExp(/%r/.source+n+/[egimnosux]{0,6}/.source),greedy:!0,inside:{interpolation:t,regex:/[\s\S]+/}},{pattern:/(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,lookbehind:!0,greedy:!0,inside:{interpolation:t,regex:/[\s\S]+/}}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:[{pattern:RegExp(/(^|[^:]):/.source+a),lookbehind:!0,greedy:!0},{pattern:RegExp(/([\r\n{(,][ \t]*)/.source+a+/(?=:(?!:))/.source),lookbehind:!0,greedy:!0}],"method-definition":{pattern:/(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,lookbehind:!0,inside:{function:/\b\w+$/,keyword:/^self\b/,"class-name":/^\w+/,punctuation:/\./}}}),e.languages.insertBefore("ruby","string",{"string-literal":[{pattern:RegExp(/%[qQiIwWs]?/.source+n),greedy:!0,inside:{interpolation:t,string:/[\s\S]+/}},{pattern:/("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,greedy:!0,inside:{interpolation:t,string:/[\s\S]+/}},{pattern:/<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?/}},interpolation:t,string:/[\s\S]+/}},{pattern:/<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?'|'$/}},string:/[\s\S]+/}}],"command-literal":[{pattern:RegExp(/%x/.source+n),greedy:!0,inside:{interpolation:t,command:{pattern:/[\s\S]+/,alias:"string"}}},{pattern:/`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,greedy:!0,inside:{interpolation:t,command:{pattern:/[\s\S]+/,alias:"string"}}}]}),delete e.languages.ruby.string,e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,constant:/\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/}),e.languages.rb=e.languages.ruby}(t),window.Prism=e,t}(),o=e=>t=>t.options.get(e),l=o("codesample_languages"),u=o("codesample_global_prismjs"),c=e=>r.Prism&&u(e)?r.Prism:i,d=e=>t(e)&&"PRE"===e.nodeName&&-1!==e.className.indexOf("language-"),g=e=>{const t=e.selection?e.selection.getNode():null;return d(t)?a.some(t):a.none()},p=e=>{const t=(e=>l(e)||[{text:"HTML/XML",value:"markup"},{text:"JavaScript",value:"javascript"},{text:"CSS",value:"css"},{text:"PHP",value:"php"},{text:"Ruby",value:"ruby"},{text:"Python",value:"python"},{text:"Java",value:"java"},{text:"C",value:"c"},{text:"C#",value:"csharp"},{text:"C++",value:"cpp"}])(e),n=(r=t,(e=>0""),(e=>e.value));var r;const i=((e,t)=>g(e).fold((()=>t),(e=>{const n=e.className.match(/language-(\w+)/);return n?n[1]:t})))(e,n),o=(e=>g(e).bind((e=>a.from(e.textContent))).getOr(""))(e);e.windowManager.open({title:"Insert/Edit Code Sample",size:"large",body:{type:"panel",items:[{type:"listbox",name:"language",label:"Language",items:t},{type:"textarea",name:"code",label:"Code view"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{language:i,code:o},onSubmit:t=>{const n=t.getData();((e,t,n)=>{const a=e.dom;e.undoManager.transact((()=>{const r=g(e);return n=s.DOM.encode(n),r.fold((()=>{e.insertContent('
'+n+"
");const s=a.select("#__new")[0];a.setAttrib(s,"id",null),e.selection.select(s)}),(s=>{a.setAttrib(s,"class","language-"+t),s.innerHTML=n,c(e).highlightElement(s),e.selection.select(s)}))}))})(e,n.language,n.code),t.close()}})},b=(h=/^\s+|\s+$/g,e=>e.replace(h,""));var h,f=tinymce.util.Tools.resolve("tinymce.util.Tools");const m=(e,t=n)=>n=>{const a=()=>{n.setEnabled(e.selection.isEditable()),t(n)};return e.on("NodeChange",a),a(),()=>{e.off("NodeChange",a)}};e.add("codesample",(e=>{(e=>{const t=e.options.register;t("codesample_languages",{processor:"object[]"}),t("codesample_global_prismjs",{processor:"boolean",default:!1})})(e),(e=>{e.on("PreProcess",(t=>{const n=e.dom,a=n.select("pre[contenteditable=false]",t.node);f.each(f.grep(a,d),(e=>{const t=e.textContent;let a;for(n.setAttrib(e,"class",b(n.getAttrib(e,"class"))),n.setAttrib(e,"contentEditable",null),n.setAttrib(e,"data-mce-highlighted",null);a=e.firstChild;)e.removeChild(a);n.add(e,"code").textContent=t}))})),e.on("SetContent",(()=>{const t=e.dom,n=f.grep(t.select("pre"),(e=>d(e)&&"true"!==t.getAttrib(e,"data-mce-highlighted")));n.length&&e.undoManager.transact((()=>{f.each(n,(n=>{var a;f.each(t.select("br",n),(n=>{t.replace(e.getDoc().createTextNode("\n"),n)})),n.innerHTML=t.encode(null!==(a=n.textContent)&&void 0!==a?a:""),c(e).highlightElement(n),t.setAttrib(n,"data-mce-highlighted",!0),n.className=b(n.className)}))}))})),e.on("PreInit",(()=>{e.parser.addNodeFilter("pre",(e=>{var t;for(let n=0,a=e.length;n{const t=()=>e.execCommand("codesample");e.ui.registry.addToggleButton("codesample",{icon:"code-sample",tooltip:"Insert/edit code sample",onAction:t,onSetup:m(e,(t=>{t.setActive((e=>{const t=e.selection.getStart();return e.dom.is(t,'pre[class*="language-"]')})(e))}))}),e.ui.registry.addMenuItem("codesample",{text:"Code sample...",icon:"code-sample",onAction:t,onSetup:m(e)})})(e),(e=>{e.addCommand("codesample",(()=>{const t=e.selection.getNode();e.selection.isCollapsed()||d(t)?p(e):e.formatter.toggle("code")}))})(e),e.on("dblclick",(t=>{d(t.target)&&p(e)}))}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/directionality/plugin.min.js b/apps/web-antd/public/tinymce/plugins/directionality/plugin.min.js new file mode 100644 index 0000000..a4f3780 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/directionality/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>typeof e===t,o=t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(o=r=t,(n=String).prototype.isPrototypeOf(o)||(null===(i=r.constructor)||void 0===i?void 0:i.name)===n.name)?"string":e;var o,r,n,i})(t),r=e("boolean"),n=t=>!(t=>null==t)(t),i=e("function"),s=e("number"),l=()=>false;class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return n(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=(t,e)=>{for(let o=0,r=t.length;o{if(null==t)throw new Error("Node cannot be null or undefined");return{dom:t}},d=c,h=(t,e)=>{const o=t.dom;if(1!==o.nodeType)return!1;{const t=o;if(void 0!==t.matches)return t.matches(e);if(void 0!==t.msMatchesSelector)return t.msMatchesSelector(e);if(void 0!==t.webkitMatchesSelector)return t.webkitMatchesSelector(e);if(void 0!==t.mozMatchesSelector)return t.mozMatchesSelector(e);throw new Error("Browser lacks native selectors")}};"undefined"!=typeof window?window:Function("return this;")();const m=t=>e=>(t=>t.dom.nodeType)(e)===t,g=m(1),f=m(3),v=m(11),y=(t,e)=>{t.dom.removeAttribute(e)},p=t=>d(t.dom.host),w=t=>{const e=f(t)?t.dom.parentNode:t.dom;if(null==e||null===e.ownerDocument)return!1;const o=e.ownerDocument;return(t=>{const e=(t=>d(t.dom.getRootNode()))(t);return v(o=e)&&n(o.dom.host)?a.some(e):a.none();var o})(d(e)).fold((()=>o.body.contains(e)),(r=w,i=p,t=>r(i(t))));var r,i},b=t=>"rtl"===((t,e)=>{const o=t.dom,r=window.getComputedStyle(o).getPropertyValue(e);return""!==r||w(t)?r:((t,e)=>(t=>void 0!==t.style&&i(t.style.getPropertyValue))(t)?t.style.getPropertyValue(e):"")(o,e)})(t,"direction")?"rtl":"ltr",S=(t,e)=>(t=>((t,e)=>{const o=[];for(let r=0,n=t.length;r{const o=t.length,r=new Array(o);for(let n=0;nh(t,e))))(t),N=t=>g(t)&&"li"===t.dom.nodeName.toLowerCase();const A=(t,e,n)=>{u(e,(e=>{const c=d(e),m=N(c),f=((t,e)=>{return(e?(o=t,r="ol,ul",((t,e,o)=>{let n=t.dom;const s=i(o)?o:l;for(;n.parentNode;){n=n.parentNode;const t=d(n);if(h(t,r))return a.some(t);if(s(t))break}return a.none()})(o,0,n)):a.some(t)).getOr(t);var o,r,n})(c,m);var v;(v=f,(t=>a.from(t.dom.parentNode).map(d))(v).filter(g)).each((e=>{if(t.setStyle(f.dom,"direction",null),b(e)===n?y(f,"dir"):((t,e,n)=>{((t,e,n)=>{if(!(o(n)||r(n)||s(n)))throw console.error("Invalid call to Attribute.set. Key ",e,":: Value ",n,":: Element ",t),new Error("Attribute value was not simple");t.setAttribute(e,n+"")})(t.dom,e,n)})(f,"dir",n),b(f)!==n&&t.setStyle(f.dom,"direction",n),m){const e=S(f,"li[dir],li[style]");u(e,(e=>{y(e,"dir"),t.setStyle(e.dom,"direction",null)}))}}))}))},T=(t,e)=>{t.selection.isEditable()&&(A(t.dom,t.selection.getSelectedBlocks(),e),t.nodeChanged())},C=(t,e)=>o=>{const r=r=>{const n=d(r.element);o.setActive(b(n)===e),o.setEnabled(t.selection.isEditable())};return t.on("NodeChange",r),o.setEnabled(t.selection.isEditable()),()=>t.off("NodeChange",r)};t.add("directionality",(t=>{(t=>{t.addCommand("mceDirectionLTR",(()=>{T(t,"ltr")})),t.addCommand("mceDirectionRTL",(()=>{T(t,"rtl")}))})(t),(t=>{t.ui.registry.addToggleButton("ltr",{tooltip:"Left to right",icon:"ltr",onAction:()=>t.execCommand("mceDirectionLTR"),onSetup:C(t,"ltr")}),t.ui.registry.addToggleButton("rtl",{tooltip:"Right to left",icon:"rtl",onAction:()=>t.execCommand("mceDirectionRTL"),onSetup:C(t,"rtl")})})(t)}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/emoticons/js/emojiimages.js b/apps/web-antd/public/tinymce/plugins/emoticons/js/emojiimages.js new file mode 100644 index 0000000..6fcec71 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/emoticons/js/emojiimages.js @@ -0,0 +1 @@ +window.tinymce.Resource.add("tinymce.plugins.emoticons",{100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:'💯',fitzpatrick_scale:false,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:'🔢',fitzpatrick_scale:false,category:"symbols"},grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:'😀',fitzpatrick_scale:false,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:'😬',fitzpatrick_scale:false,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:'😁',fitzpatrick_scale:false,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:'😂',fitzpatrick_scale:false,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:'🤣',fitzpatrick_scale:false,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:'🥳',fitzpatrick_scale:false,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:'😃',fitzpatrick_scale:false,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:'😄',fitzpatrick_scale:false,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:'😅',fitzpatrick_scale:false,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:'😆',fitzpatrick_scale:false,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:'😇',fitzpatrick_scale:false,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:'😉',fitzpatrick_scale:false,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:'😊',fitzpatrick_scale:false,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:'🙂',fitzpatrick_scale:false,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:'🙃',fitzpatrick_scale:false,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:'☺️',fitzpatrick_scale:false,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:'😋',fitzpatrick_scale:false,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:'😌',fitzpatrick_scale:false,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:'😍',fitzpatrick_scale:false,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:'🥰',fitzpatrick_scale:false,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'😘',fitzpatrick_scale:false,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:'😗',fitzpatrick_scale:false,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:'😙',fitzpatrick_scale:false,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'😚',fitzpatrick_scale:false,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:'😜',fitzpatrick_scale:false,category:"people"},zany:{keywords:["face","goofy","crazy"],char:'🤪',fitzpatrick_scale:false,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:'🤨',fitzpatrick_scale:false,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:'🧐',fitzpatrick_scale:false,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:'😝',fitzpatrick_scale:false,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:'😛',fitzpatrick_scale:false,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:'🤑',fitzpatrick_scale:false,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:'🤓',fitzpatrick_scale:false,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:'😎',fitzpatrick_scale:false,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:'🤩',fitzpatrick_scale:false,category:"people"},clown_face:{keywords:["face"],char:'🤡',fitzpatrick_scale:false,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:'🤠',fitzpatrick_scale:false,category:"people"},hugs:{keywords:["face","smile","hug"],char:'🤗',fitzpatrick_scale:false,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:'😏',fitzpatrick_scale:false,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:'😶',fitzpatrick_scale:false,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:'😐',fitzpatrick_scale:false,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:'😑',fitzpatrick_scale:false,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:'😒',fitzpatrick_scale:false,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:'🙄',fitzpatrick_scale:false,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:'🤔',fitzpatrick_scale:false,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:'🤥',fitzpatrick_scale:false,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:'🤭',fitzpatrick_scale:false,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:'🤫',fitzpatrick_scale:false,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:'🤬',fitzpatrick_scale:false,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:'🤯',fitzpatrick_scale:false,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:'😳',fitzpatrick_scale:false,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:'😞',fitzpatrick_scale:false,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:'😟',fitzpatrick_scale:false,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:'😠',fitzpatrick_scale:false,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:'😡',fitzpatrick_scale:false,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:'😔',fitzpatrick_scale:false,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:'😕',fitzpatrick_scale:false,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:'🙁',fitzpatrick_scale:false,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:'☹',fitzpatrick_scale:false,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:'😣',fitzpatrick_scale:false,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:'😖',fitzpatrick_scale:false,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:'😫',fitzpatrick_scale:false,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:'😩',fitzpatrick_scale:false,category:"people"},pleading:{keywords:["face","begging","mercy"],char:'🥺',fitzpatrick_scale:false,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:'😤',fitzpatrick_scale:false,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:'😮',fitzpatrick_scale:false,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:'😱',fitzpatrick_scale:false,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:'😨',fitzpatrick_scale:false,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:'😰',fitzpatrick_scale:false,category:"people"},hushed:{keywords:["face","woo","shh"],char:'😯',fitzpatrick_scale:false,category:"people"},frowning:{keywords:["face","aw","what"],char:'😦',fitzpatrick_scale:false,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:'😧',fitzpatrick_scale:false,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:'😢',fitzpatrick_scale:false,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:'😥',fitzpatrick_scale:false,category:"people"},drooling_face:{keywords:["face"],char:'🤤',fitzpatrick_scale:false,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:'😪',fitzpatrick_scale:false,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:'😓',fitzpatrick_scale:false,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:'🥵',fitzpatrick_scale:false,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:'🥶',fitzpatrick_scale:false,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:'😭',fitzpatrick_scale:false,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:'😵',fitzpatrick_scale:false,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:'😲',fitzpatrick_scale:false,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:'🤐',fitzpatrick_scale:false,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:'🤢',fitzpatrick_scale:false,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:'🤧',fitzpatrick_scale:false,category:"people"},vomiting:{keywords:["face","sick"],char:'🤮',fitzpatrick_scale:false,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:'😷',fitzpatrick_scale:false,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:'🤒',fitzpatrick_scale:false,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:'🤕',fitzpatrick_scale:false,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:'🥴',fitzpatrick_scale:false,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:'😴',fitzpatrick_scale:false,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:'💤',fitzpatrick_scale:false,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:'💩',fitzpatrick_scale:false,category:"people"},smiling_imp:{keywords:["devil","horns"],char:'😈',fitzpatrick_scale:false,category:"people"},imp:{keywords:["devil","angry","horns"],char:'👿',fitzpatrick_scale:false,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:'👹',fitzpatrick_scale:false,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:'👺',fitzpatrick_scale:false,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:'💀',fitzpatrick_scale:false,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:'👻',fitzpatrick_scale:false,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:'👽',fitzpatrick_scale:false,category:"people"},robot:{keywords:["computer","machine","bot"],char:'🤖',fitzpatrick_scale:false,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:'😺',fitzpatrick_scale:false,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:'😸',fitzpatrick_scale:false,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:'😹',fitzpatrick_scale:false,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:'😻',fitzpatrick_scale:false,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:'😼',fitzpatrick_scale:false,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:'😽',fitzpatrick_scale:false,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:'🙀',fitzpatrick_scale:false,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:'😿',fitzpatrick_scale:false,category:"people"},pouting_cat:{keywords:["animal","cats"],char:'😾',fitzpatrick_scale:false,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:'🤲',fitzpatrick_scale:true,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:'🙌',fitzpatrick_scale:true,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:'👏',fitzpatrick_scale:true,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:'👋',fitzpatrick_scale:true,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:'🤙',fitzpatrick_scale:true,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:'👍',fitzpatrick_scale:true,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:'👎',fitzpatrick_scale:true,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:'👊',fitzpatrick_scale:true,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:'✊',fitzpatrick_scale:true,category:"people"},fist_left:{keywords:["hand","fistbump"],char:'🤛',fitzpatrick_scale:true,category:"people"},fist_right:{keywords:["hand","fistbump"],char:'🤜',fitzpatrick_scale:true,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:'✌',fitzpatrick_scale:true,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:'👌',fitzpatrick_scale:true,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:'✋',fitzpatrick_scale:true,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:'🤚',fitzpatrick_scale:true,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:'👐',fitzpatrick_scale:true,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:'💪',fitzpatrick_scale:true,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:'🙏',fitzpatrick_scale:true,category:"people"},foot:{keywords:["kick","stomp"],char:'🦶',fitzpatrick_scale:true,category:"people"},leg:{keywords:["kick","limb"],char:'🦵',fitzpatrick_scale:true,category:"people"},handshake:{keywords:["agreement","shake"],char:'🤝',fitzpatrick_scale:false,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:'☝',fitzpatrick_scale:true,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:'👆',fitzpatrick_scale:true,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:'👇',fitzpatrick_scale:true,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:'👈',fitzpatrick_scale:true,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:'👉',fitzpatrick_scale:true,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:'🖕',fitzpatrick_scale:true,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:'🖐',fitzpatrick_scale:true,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:'🤟',fitzpatrick_scale:true,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:'🤘',fitzpatrick_scale:true,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:'🤞',fitzpatrick_scale:true,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:'🖖',fitzpatrick_scale:true,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:'✍',fitzpatrick_scale:true,category:"people"},selfie:{keywords:["camera","phone"],char:'🤳',fitzpatrick_scale:true,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:'💅',fitzpatrick_scale:true,category:"people"},lips:{keywords:["mouth","kiss"],char:'👄',fitzpatrick_scale:false,category:"people"},tooth:{keywords:["teeth","dentist"],char:'🦷',fitzpatrick_scale:false,category:"people"},tongue:{keywords:["mouth","playful"],char:'👅',fitzpatrick_scale:false,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:'👂',fitzpatrick_scale:true,category:"people"},nose:{keywords:["smell","sniff"],char:'👃',fitzpatrick_scale:true,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:'👁',fitzpatrick_scale:false,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:'👀',fitzpatrick_scale:false,category:"people"},brain:{keywords:["smart","intelligent"],char:'🧠',fitzpatrick_scale:false,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:'👤',fitzpatrick_scale:false,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:'👥',fitzpatrick_scale:false,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:'🗣',fitzpatrick_scale:false,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:'👶',fitzpatrick_scale:true,category:"people"},child:{keywords:["gender-neutral","young"],char:'🧒',fitzpatrick_scale:true,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:'👦',fitzpatrick_scale:true,category:"people"},girl:{keywords:["female","woman","teenager"],char:'👧',fitzpatrick_scale:true,category:"people"},adult:{keywords:["gender-neutral","person"],char:'🧑',fitzpatrick_scale:true,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:'👨',fitzpatrick_scale:true,category:"people"},woman:{keywords:["female","girls","lady"],char:'👩',fitzpatrick_scale:true,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:'👱‍♀️',fitzpatrick_scale:true,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:'👱',fitzpatrick_scale:true,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:'🧔',fitzpatrick_scale:true,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:'🧓',fitzpatrick_scale:true,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:'👴',fitzpatrick_scale:true,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:'👵',fitzpatrick_scale:true,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:'👲',fitzpatrick_scale:true,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:'🧕',fitzpatrick_scale:true,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:'👳‍♀️',fitzpatrick_scale:true,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:'👳',fitzpatrick_scale:true,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:'👮‍♀️',fitzpatrick_scale:true,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:'👮',fitzpatrick_scale:true,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:'👷‍♀️',fitzpatrick_scale:true,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:'👷',fitzpatrick_scale:true,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:'💂‍♀️',fitzpatrick_scale:true,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:'💂',fitzpatrick_scale:true,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:'🕵️‍♀️',fitzpatrick_scale:true,category:"people"},male_detective:{keywords:["human","spy","detective"],char:'🕵',fitzpatrick_scale:true,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:'👩‍⚕️',fitzpatrick_scale:true,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:'👨‍⚕️',fitzpatrick_scale:true,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:'👩‍🌾',fitzpatrick_scale:true,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:'👨‍🌾',fitzpatrick_scale:true,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:'👩‍🍳',fitzpatrick_scale:true,category:"people"},man_cook:{keywords:["chef","man","human"],char:'👨‍🍳',fitzpatrick_scale:true,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:'👩‍🎓',fitzpatrick_scale:true,category:"people"},man_student:{keywords:["graduate","man","human"],char:'👨‍🎓',fitzpatrick_scale:true,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:'👩‍🎤',fitzpatrick_scale:true,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:'👨‍🎤',fitzpatrick_scale:true,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:'👩‍🏫',fitzpatrick_scale:true,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:'👨‍🏫',fitzpatrick_scale:true,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:'👩‍🏭',fitzpatrick_scale:true,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:'👨‍🏭',fitzpatrick_scale:true,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:'👩‍💻',fitzpatrick_scale:true,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:'👨‍💻',fitzpatrick_scale:true,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:'👩‍💼',fitzpatrick_scale:true,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:'👨‍💼',fitzpatrick_scale:true,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:'👩‍🔧',fitzpatrick_scale:true,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:'👨‍🔧',fitzpatrick_scale:true,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:'👩‍🔬',fitzpatrick_scale:true,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:'👨‍🔬',fitzpatrick_scale:true,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:'👩‍🎨',fitzpatrick_scale:true,category:"people"},man_artist:{keywords:["painter","man","human"],char:'👨‍🎨',fitzpatrick_scale:true,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:'👩‍🚒',fitzpatrick_scale:true,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:'👨‍🚒',fitzpatrick_scale:true,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:'👩‍✈️',fitzpatrick_scale:true,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:'👨‍✈️',fitzpatrick_scale:true,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:'👩‍🚀',fitzpatrick_scale:true,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:'👨‍🚀',fitzpatrick_scale:true,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:'👩‍⚖️',fitzpatrick_scale:true,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:'👨‍⚖️',fitzpatrick_scale:true,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:'🦸‍♀️',fitzpatrick_scale:true,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:'🦸‍♂️',fitzpatrick_scale:true,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:'🦹‍♀️',fitzpatrick_scale:true,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:'🦹‍♂️',fitzpatrick_scale:true,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:'🤶',fitzpatrick_scale:true,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:'🎅',fitzpatrick_scale:true,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:'🧙‍♀️',fitzpatrick_scale:true,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:'🧙‍♂️',fitzpatrick_scale:true,category:"people"},woman_elf:{keywords:["woman","female"],char:'🧝‍♀️',fitzpatrick_scale:true,category:"people"},man_elf:{keywords:["man","male"],char:'🧝‍♂️',fitzpatrick_scale:true,category:"people"},woman_vampire:{keywords:["woman","female"],char:'🧛‍♀️',fitzpatrick_scale:true,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:'🧛‍♂️',fitzpatrick_scale:true,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:'🧟‍♀️',fitzpatrick_scale:false,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:'🧟‍♂️',fitzpatrick_scale:false,category:"people"},woman_genie:{keywords:["woman","female"],char:'🧞‍♀️',fitzpatrick_scale:false,category:"people"},man_genie:{keywords:["man","male"],char:'🧞‍♂️',fitzpatrick_scale:false,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:'🧜‍♀️',fitzpatrick_scale:true,category:"people"},merman:{keywords:["man","male","triton"],char:'🧜‍♂️',fitzpatrick_scale:true,category:"people"},woman_fairy:{keywords:["woman","female"],char:'🧚‍♀️',fitzpatrick_scale:true,category:"people"},man_fairy:{keywords:["man","male"],char:'🧚‍♂️',fitzpatrick_scale:true,category:"people"},angel:{keywords:["heaven","wings","halo"],char:'👼',fitzpatrick_scale:true,category:"people"},pregnant_woman:{keywords:["baby"],char:'🤰',fitzpatrick_scale:true,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:'🤱',fitzpatrick_scale:true,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:'👸',fitzpatrick_scale:true,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:'🤴',fitzpatrick_scale:true,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:'👰',fitzpatrick_scale:true,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:'🤵',fitzpatrick_scale:true,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:'🏃‍♀️',fitzpatrick_scale:true,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:'🏃',fitzpatrick_scale:true,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:'🚶‍♀️',fitzpatrick_scale:true,category:"people"},walking_man:{keywords:["human","feet","steps"],char:'🚶',fitzpatrick_scale:true,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:'💃',fitzpatrick_scale:true,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:'🕺',fitzpatrick_scale:true,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:'👯',fitzpatrick_scale:false,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:'👯‍♂️',fitzpatrick_scale:false,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:'👫',fitzpatrick_scale:false,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:'👬',fitzpatrick_scale:false,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:'👭',fitzpatrick_scale:false,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:'🙇‍♀️',fitzpatrick_scale:true,category:"people"},bowing_man:{keywords:["man","male","boy"],char:'🙇',fitzpatrick_scale:true,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:'🤦‍♂️',fitzpatrick_scale:true,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:'🤦‍♀️',fitzpatrick_scale:true,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:'🤷',fitzpatrick_scale:true,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:'🤷‍♂️',fitzpatrick_scale:true,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:'💁',fitzpatrick_scale:true,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:'💁‍♂️',fitzpatrick_scale:true,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:'🙅',fitzpatrick_scale:true,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:'🙅‍♂️',fitzpatrick_scale:true,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:'🙆',fitzpatrick_scale:true,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:'🙆‍♂️',fitzpatrick_scale:true,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:'🙋',fitzpatrick_scale:true,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:'🙋‍♂️',fitzpatrick_scale:true,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:'🙎',fitzpatrick_scale:true,category:"people"},pouting_man:{keywords:["male","boy","man"],char:'🙎‍♂️',fitzpatrick_scale:true,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:'🙍',fitzpatrick_scale:true,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:'🙍‍♂️',fitzpatrick_scale:true,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:'💇',fitzpatrick_scale:true,category:"people"},haircut_man:{keywords:["male","boy","man"],char:'💇‍♂️',fitzpatrick_scale:true,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:'💆',fitzpatrick_scale:true,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:'💆‍♂️',fitzpatrick_scale:true,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:'🧖‍♀️',fitzpatrick_scale:true,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:'🧖‍♂️',fitzpatrick_scale:true,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'💑',fitzpatrick_scale:false,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'👩‍❤️‍👩',fitzpatrick_scale:false,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'👨‍❤️‍👨',fitzpatrick_scale:false,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'💏',fitzpatrick_scale:false,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'👩‍❤️‍💋‍👩',fitzpatrick_scale:false,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:'👨‍❤️‍💋‍👨',fitzpatrick_scale:false,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:'👪',fitzpatrick_scale:false,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:'👨‍👩‍👧',fitzpatrick_scale:false,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'👨‍👩‍👧‍👦',fitzpatrick_scale:false,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'👨‍👩‍👦‍👦',fitzpatrick_scale:false,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'👨‍👩‍👧‍👧',fitzpatrick_scale:false,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:'👩‍👩‍👦',fitzpatrick_scale:false,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:'👩‍👩‍👧',fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'👩‍👩‍👧‍👦',fitzpatrick_scale:false,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'👩‍👩‍👦‍👦',fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'👩‍👩‍👧‍👧',fitzpatrick_scale:false,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:'👨‍👨‍👦',fitzpatrick_scale:false,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:'👨‍👨‍👧',fitzpatrick_scale:false,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:'👨‍👨‍👧‍👦',fitzpatrick_scale:false,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:'👨‍👨‍👦‍👦',fitzpatrick_scale:false,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:'👨‍👨‍👧‍👧',fitzpatrick_scale:false,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:'👩‍👦',fitzpatrick_scale:false,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:'👩‍👧',fitzpatrick_scale:false,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:'👩‍👧‍👦',fitzpatrick_scale:false,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:'👩‍👦‍👦',fitzpatrick_scale:false,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:'👩‍👧‍👧',fitzpatrick_scale:false,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:'👨‍👦',fitzpatrick_scale:false,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:'👨‍👧',fitzpatrick_scale:false,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:'👨‍👧‍👦',fitzpatrick_scale:false,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:'👨‍👦‍👦',fitzpatrick_scale:false,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:'👨‍👧‍👧',fitzpatrick_scale:false,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:'🧶',fitzpatrick_scale:false,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:'🧵',fitzpatrick_scale:false,category:"people"},coat:{keywords:["jacket"],char:'🧥',fitzpatrick_scale:false,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:'🥼',fitzpatrick_scale:false,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:'👚',fitzpatrick_scale:false,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:'👕',fitzpatrick_scale:false,category:"people"},jeans:{keywords:["fashion","shopping"],char:'👖',fitzpatrick_scale:false,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:'👔',fitzpatrick_scale:false,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:'👗',fitzpatrick_scale:false,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:'👙',fitzpatrick_scale:false,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:'👘',fitzpatrick_scale:false,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:'💄',fitzpatrick_scale:false,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:'💋',fitzpatrick_scale:false,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:'👣',fitzpatrick_scale:false,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:'🥿',fitzpatrick_scale:false,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:'👠',fitzpatrick_scale:false,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:'👡',fitzpatrick_scale:false,category:"people"},boot:{keywords:["shoes","fashion"],char:'👢',fitzpatrick_scale:false,category:"people"},mans_shoe:{keywords:["fashion","male"],char:'👞',fitzpatrick_scale:false,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:'👟',fitzpatrick_scale:false,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:'🥾',fitzpatrick_scale:false,category:"people"},socks:{keywords:["stockings","clothes"],char:'🧦',fitzpatrick_scale:false,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:'🧤',fitzpatrick_scale:false,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:'🧣',fitzpatrick_scale:false,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:'👒',fitzpatrick_scale:false,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:'🎩',fitzpatrick_scale:false,category:"people"},billed_hat:{keywords:["cap","baseball"],char:'🧢',fitzpatrick_scale:false,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:'⛑',fitzpatrick_scale:false,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:'🎓',fitzpatrick_scale:false,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:'👑',fitzpatrick_scale:false,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:'🎒',fitzpatrick_scale:false,category:"people"},luggage:{keywords:["packing","travel"],char:'🧳',fitzpatrick_scale:false,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:'👝',fitzpatrick_scale:false,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:'👛',fitzpatrick_scale:false,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:'👜',fitzpatrick_scale:false,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:'💼',fitzpatrick_scale:false,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:'👓',fitzpatrick_scale:false,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:'🕶',fitzpatrick_scale:false,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:'🥽',fitzpatrick_scale:false,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:'💍',fitzpatrick_scale:false,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:'🌂',fitzpatrick_scale:false,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:'🐶',fitzpatrick_scale:false,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:'🐱',fitzpatrick_scale:false,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:'🐭',fitzpatrick_scale:false,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:'🐹',fitzpatrick_scale:false,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:'🐰',fitzpatrick_scale:false,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:'🦊',fitzpatrick_scale:false,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:'🐻',fitzpatrick_scale:false,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:'🐼',fitzpatrick_scale:false,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:'🐨',fitzpatrick_scale:false,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:'🐯',fitzpatrick_scale:false,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:'🦁',fitzpatrick_scale:false,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:'🐮',fitzpatrick_scale:false,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:'🐷',fitzpatrick_scale:false,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:'🐽',fitzpatrick_scale:false,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:'🐸',fitzpatrick_scale:false,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:'🦑',fitzpatrick_scale:false,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:'🐙',fitzpatrick_scale:false,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:'🦐',fitzpatrick_scale:false,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:'🐵',fitzpatrick_scale:false,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:'🦍',fitzpatrick_scale:false,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:'🙈',fitzpatrick_scale:false,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:'🙉',fitzpatrick_scale:false,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:'🙊',fitzpatrick_scale:false,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:'🐒',fitzpatrick_scale:false,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:'🐔',fitzpatrick_scale:false,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:'🐧',fitzpatrick_scale:false,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:'🐦',fitzpatrick_scale:false,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:'🐤',fitzpatrick_scale:false,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:'🐣',fitzpatrick_scale:false,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:'🐥',fitzpatrick_scale:false,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:'🦆',fitzpatrick_scale:false,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:'🦅',fitzpatrick_scale:false,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:'🦉',fitzpatrick_scale:false,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:'🦇',fitzpatrick_scale:false,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:'🐺',fitzpatrick_scale:false,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:'🐗',fitzpatrick_scale:false,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:'🐴',fitzpatrick_scale:false,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:'🦄',fitzpatrick_scale:false,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:'🐝',fitzpatrick_scale:false,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:'🐛',fitzpatrick_scale:false,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:'🦋',fitzpatrick_scale:false,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:'🐌',fitzpatrick_scale:false,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:'🐞',fitzpatrick_scale:false,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:'🐜',fitzpatrick_scale:false,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:'🦗',fitzpatrick_scale:false,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:'🕷',fitzpatrick_scale:false,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:'🦂',fitzpatrick_scale:false,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:'🦀',fitzpatrick_scale:false,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:'🐍',fitzpatrick_scale:false,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:'🦎',fitzpatrick_scale:false,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:'🦖',fitzpatrick_scale:false,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:'🦕',fitzpatrick_scale:false,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:'🐢',fitzpatrick_scale:false,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:'🐠',fitzpatrick_scale:false,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:'🐟',fitzpatrick_scale:false,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:'🐡',fitzpatrick_scale:false,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:'🐬',fitzpatrick_scale:false,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:'🦈',fitzpatrick_scale:false,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:'🐳',fitzpatrick_scale:false,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:'🐋',fitzpatrick_scale:false,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:'🐊',fitzpatrick_scale:false,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:'🐆',fitzpatrick_scale:false,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:'🦓',fitzpatrick_scale:false,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:'🐅',fitzpatrick_scale:false,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:'🐃',fitzpatrick_scale:false,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:'🐂',fitzpatrick_scale:false,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:'🐄',fitzpatrick_scale:false,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:'🦌',fitzpatrick_scale:false,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:'🐪',fitzpatrick_scale:false,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:'🐫',fitzpatrick_scale:false,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:'🦒',fitzpatrick_scale:false,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:'🐘',fitzpatrick_scale:false,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:'🦏',fitzpatrick_scale:false,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:'🐐',fitzpatrick_scale:false,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:'🐏',fitzpatrick_scale:false,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:'🐑',fitzpatrick_scale:false,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:'🐎',fitzpatrick_scale:false,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:'🐖',fitzpatrick_scale:false,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:'🐀',fitzpatrick_scale:false,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:'🐁',fitzpatrick_scale:false,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:'🐓',fitzpatrick_scale:false,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:'🦃',fitzpatrick_scale:false,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:'🕊',fitzpatrick_scale:false,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:'🐕',fitzpatrick_scale:false,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:'🐩',fitzpatrick_scale:false,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:'🐈',fitzpatrick_scale:false,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:'🐇',fitzpatrick_scale:false,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:'🐿',fitzpatrick_scale:false,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:'🦔',fitzpatrick_scale:false,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:'🦝',fitzpatrick_scale:false,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:'🦙',fitzpatrick_scale:false,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:'🦛',fitzpatrick_scale:false,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:'🦘',fitzpatrick_scale:false,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:'🦡',fitzpatrick_scale:false,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:'🦢',fitzpatrick_scale:false,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:'🦚',fitzpatrick_scale:false,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:'🦜',fitzpatrick_scale:false,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:'🦞',fitzpatrick_scale:false,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:'🦟',fitzpatrick_scale:false,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:'🐾',fitzpatrick_scale:false,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:'🐉',fitzpatrick_scale:false,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:'🐲',fitzpatrick_scale:false,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:'🌵',fitzpatrick_scale:false,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:'🎄',fitzpatrick_scale:false,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:'🌲',fitzpatrick_scale:false,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:'🌳',fitzpatrick_scale:false,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:'🌴',fitzpatrick_scale:false,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:'🌱',fitzpatrick_scale:false,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:'🌿',fitzpatrick_scale:false,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:'☘',fitzpatrick_scale:false,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:'🍀',fitzpatrick_scale:false,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:'🎍',fitzpatrick_scale:false,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:'🎋',fitzpatrick_scale:false,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:'🍃',fitzpatrick_scale:false,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:'🍂',fitzpatrick_scale:false,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:'🍁',fitzpatrick_scale:false,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:'🌾',fitzpatrick_scale:false,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:'🌺',fitzpatrick_scale:false,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:'🌻',fitzpatrick_scale:false,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:'🌹',fitzpatrick_scale:false,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:'🥀',fitzpatrick_scale:false,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:'🌷',fitzpatrick_scale:false,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:'🌼',fitzpatrick_scale:false,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:'🌸',fitzpatrick_scale:false,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:'💐',fitzpatrick_scale:false,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:'🍄',fitzpatrick_scale:false,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:'🌰',fitzpatrick_scale:false,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:'🎃',fitzpatrick_scale:false,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:'🐚',fitzpatrick_scale:false,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:'🕸',fitzpatrick_scale:false,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:'🌎',fitzpatrick_scale:false,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:'🌍',fitzpatrick_scale:false,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:'🌏',fitzpatrick_scale:false,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:'🌕',fitzpatrick_scale:false,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:'🌖',fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌗',fitzpatrick_scale:false,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌘',fitzpatrick_scale:false,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌑',fitzpatrick_scale:false,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌒',fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌓',fitzpatrick_scale:false,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:'🌔',fitzpatrick_scale:false,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌚',fitzpatrick_scale:false,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌝',fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌛',fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌜',fitzpatrick_scale:false,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:'🌞',fitzpatrick_scale:false,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:'🌙',fitzpatrick_scale:false,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:'⭐',fitzpatrick_scale:false,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:'🌟',fitzpatrick_scale:false,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:'💫',fitzpatrick_scale:false,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:'✨',fitzpatrick_scale:false,category:"animals_and_nature"},comet:{keywords:["space"],char:'☄',fitzpatrick_scale:false,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:'☀️',fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:'🌤',fitzpatrick_scale:false,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:'⛅',fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:'🌥',fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:'🌦',fitzpatrick_scale:false,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:'☁️',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:'🌧',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:'⛈',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:'🌩',fitzpatrick_scale:false,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:'⚡',fitzpatrick_scale:false,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:'🔥',fitzpatrick_scale:false,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:'💥',fitzpatrick_scale:false,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:'❄️',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:'🌨',fitzpatrick_scale:false,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:'⛄',fitzpatrick_scale:false,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:'☃',fitzpatrick_scale:false,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:'🌬',fitzpatrick_scale:false,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:'💨',fitzpatrick_scale:false,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:'🌪',fitzpatrick_scale:false,category:"animals_and_nature"},fog:{keywords:["weather"],char:'🌫',fitzpatrick_scale:false,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:'☂',fitzpatrick_scale:false,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:'☔',fitzpatrick_scale:false,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:'💧',fitzpatrick_scale:false,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:'💦',fitzpatrick_scale:false,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:'🌊',fitzpatrick_scale:false,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:'🍏',fitzpatrick_scale:false,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:'🍎',fitzpatrick_scale:false,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:'🍐',fitzpatrick_scale:false,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:'🍊',fitzpatrick_scale:false,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:'🍋',fitzpatrick_scale:false,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:'🍌',fitzpatrick_scale:false,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:'🍉',fitzpatrick_scale:false,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:'🍇',fitzpatrick_scale:false,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:'🍓',fitzpatrick_scale:false,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:'🍈',fitzpatrick_scale:false,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:'🍒',fitzpatrick_scale:false,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:'🍑',fitzpatrick_scale:false,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:'🍍',fitzpatrick_scale:false,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:'🥥',fitzpatrick_scale:false,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:'🥝',fitzpatrick_scale:false,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:'🥭',fitzpatrick_scale:false,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:'🥑',fitzpatrick_scale:false,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:'🥦',fitzpatrick_scale:false,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:'🍅',fitzpatrick_scale:false,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:'🍆',fitzpatrick_scale:false,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:'🥒',fitzpatrick_scale:false,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:'🥕',fitzpatrick_scale:false,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:'🌶',fitzpatrick_scale:false,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:'🥔',fitzpatrick_scale:false,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:'🌽',fitzpatrick_scale:false,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:'🥬',fitzpatrick_scale:false,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:'🍠',fitzpatrick_scale:false,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:'🥜',fitzpatrick_scale:false,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:'🍯',fitzpatrick_scale:false,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:'🥐',fitzpatrick_scale:false,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:'🍞',fitzpatrick_scale:false,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:'🥖',fitzpatrick_scale:false,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:'🥯',fitzpatrick_scale:false,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:'🥨',fitzpatrick_scale:false,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:'🧀',fitzpatrick_scale:false,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:'🥚',fitzpatrick_scale:false,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:'🥓',fitzpatrick_scale:false,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:'🥩',fitzpatrick_scale:false,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:'🥞',fitzpatrick_scale:false,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:'🍗',fitzpatrick_scale:false,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:'🍖',fitzpatrick_scale:false,category:"food_and_drink"},bone:{keywords:["skeleton"],char:'🦴',fitzpatrick_scale:false,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:'🍤',fitzpatrick_scale:false,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:'🍳',fitzpatrick_scale:false,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:'🍔',fitzpatrick_scale:false,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:'🍟',fitzpatrick_scale:false,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:'🥙',fitzpatrick_scale:false,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:'🌭',fitzpatrick_scale:false,category:"food_and_drink"},pizza:{keywords:["food","party"],char:'🍕',fitzpatrick_scale:false,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:'🥪',fitzpatrick_scale:false,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:'🥫',fitzpatrick_scale:false,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:'🍝',fitzpatrick_scale:false,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:'🌮',fitzpatrick_scale:false,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:'🌯',fitzpatrick_scale:false,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:'🥗',fitzpatrick_scale:false,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:'🥘',fitzpatrick_scale:false,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:'🍜',fitzpatrick_scale:false,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:'🍲',fitzpatrick_scale:false,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:'🍥',fitzpatrick_scale:false,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:'🥠',fitzpatrick_scale:false,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:'🍣',fitzpatrick_scale:false,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:'🍱',fitzpatrick_scale:false,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:'🍛',fitzpatrick_scale:false,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:'🍙',fitzpatrick_scale:false,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:'🍚',fitzpatrick_scale:false,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:'🍘',fitzpatrick_scale:false,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:'🍢',fitzpatrick_scale:false,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:'🍡',fitzpatrick_scale:false,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:'🍧',fitzpatrick_scale:false,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:'🍨',fitzpatrick_scale:false,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:'🍦',fitzpatrick_scale:false,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:'🥧',fitzpatrick_scale:false,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:'🍰',fitzpatrick_scale:false,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:'🧁',fitzpatrick_scale:false,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:'🥮',fitzpatrick_scale:false,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:'🎂',fitzpatrick_scale:false,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:'🍮',fitzpatrick_scale:false,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:'🍬',fitzpatrick_scale:false,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:'🍭',fitzpatrick_scale:false,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:'🍫',fitzpatrick_scale:false,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:'🍿',fitzpatrick_scale:false,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:'🥟',fitzpatrick_scale:false,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:'🍩',fitzpatrick_scale:false,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:'🍪',fitzpatrick_scale:false,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:'🥛',fitzpatrick_scale:false,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'🍺',fitzpatrick_scale:false,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'🍻',fitzpatrick_scale:false,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:'🥂',fitzpatrick_scale:false,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:'🍷',fitzpatrick_scale:false,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:'🥃',fitzpatrick_scale:false,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:'🍸',fitzpatrick_scale:false,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:'🍹',fitzpatrick_scale:false,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:'🍾',fitzpatrick_scale:false,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:'🍶',fitzpatrick_scale:false,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:'🍵',fitzpatrick_scale:false,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:'🥤',fitzpatrick_scale:false,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:'☕',fitzpatrick_scale:false,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:'🍼',fitzpatrick_scale:false,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:'🧂',fitzpatrick_scale:false,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:'🥄',fitzpatrick_scale:false,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:'🍴',fitzpatrick_scale:false,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:'🍽',fitzpatrick_scale:false,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:'🥣',fitzpatrick_scale:false,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:'🥡',fitzpatrick_scale:false,category:"food_and_drink"},chopsticks:{keywords:["food"],char:'🥢',fitzpatrick_scale:false,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:'⚽',fitzpatrick_scale:false,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:'🏀',fitzpatrick_scale:false,category:"activity"},football:{keywords:["sports","balls","NFL"],char:'🏈',fitzpatrick_scale:false,category:"activity"},baseball:{keywords:["sports","balls"],char:'⚾',fitzpatrick_scale:false,category:"activity"},softball:{keywords:["sports","balls"],char:'🥎',fitzpatrick_scale:false,category:"activity"},tennis:{keywords:["sports","balls","green"],char:'🎾',fitzpatrick_scale:false,category:"activity"},volleyball:{keywords:["sports","balls"],char:'🏐',fitzpatrick_scale:false,category:"activity"},rugby_football:{keywords:["sports","team"],char:'🏉',fitzpatrick_scale:false,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:'🥏',fitzpatrick_scale:false,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:'🎱',fitzpatrick_scale:false,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:'⛳',fitzpatrick_scale:false,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:'🏌️‍♀️',fitzpatrick_scale:false,category:"activity"},golfing_man:{keywords:["sports","business"],char:'🏌',fitzpatrick_scale:true,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:'🏓',fitzpatrick_scale:false,category:"activity"},badminton:{keywords:["sports"],char:'🏸',fitzpatrick_scale:false,category:"activity"},goal_net:{keywords:["sports"],char:'🥅',fitzpatrick_scale:false,category:"activity"},ice_hockey:{keywords:["sports"],char:'🏒',fitzpatrick_scale:false,category:"activity"},field_hockey:{keywords:["sports"],char:'🏑',fitzpatrick_scale:false,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:'🥍',fitzpatrick_scale:false,category:"activity"},cricket:{keywords:["sports"],char:'🏏',fitzpatrick_scale:false,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:'🎿',fitzpatrick_scale:false,category:"activity"},skier:{keywords:["sports","winter","snow"],char:'⛷',fitzpatrick_scale:false,category:"activity"},snowboarder:{keywords:["sports","winter"],char:'🏂',fitzpatrick_scale:true,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:'🤺',fitzpatrick_scale:false,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:'🤼‍♀️',fitzpatrick_scale:false,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:'🤼‍♂️',fitzpatrick_scale:false,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:'🤸‍♀️',fitzpatrick_scale:true,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:'🤸‍♂️',fitzpatrick_scale:true,category:"activity"},woman_playing_handball:{keywords:["sports"],char:'🤾‍♀️',fitzpatrick_scale:true,category:"activity"},man_playing_handball:{keywords:["sports"],char:'🤾‍♂️',fitzpatrick_scale:true,category:"activity"},ice_skate:{keywords:["sports"],char:'⛸',fitzpatrick_scale:false,category:"activity"},curling_stone:{keywords:["sports"],char:'🥌',fitzpatrick_scale:false,category:"activity"},skateboard:{keywords:["board"],char:'🛹',fitzpatrick_scale:false,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:'🛷',fitzpatrick_scale:false,category:"activity"},bow_and_arrow:{keywords:["sports"],char:'🏹',fitzpatrick_scale:false,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:'🎣',fitzpatrick_scale:false,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:'🥊',fitzpatrick_scale:false,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:'🥋',fitzpatrick_scale:false,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:'🚣‍♀️',fitzpatrick_scale:true,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:'🚣',fitzpatrick_scale:true,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:'🧗‍♀️',fitzpatrick_scale:true,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:'🧗‍♂️',fitzpatrick_scale:true,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:'🏊‍♀️',fitzpatrick_scale:true,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:'🏊',fitzpatrick_scale:true,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:'🤽‍♀️',fitzpatrick_scale:true,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:'🤽‍♂️',fitzpatrick_scale:true,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:'🧘‍♀️',fitzpatrick_scale:true,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:'🧘‍♂️',fitzpatrick_scale:true,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:'🏄‍♀️',fitzpatrick_scale:true,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:'🏄',fitzpatrick_scale:true,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:'🛀',fitzpatrick_scale:true,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:'⛹️‍♀️',fitzpatrick_scale:true,category:"activity"},basketball_man:{keywords:["sports","human"],char:'⛹',fitzpatrick_scale:true,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:'🏋️‍♀️',fitzpatrick_scale:true,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:'🏋',fitzpatrick_scale:true,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:'🚴‍♀️',fitzpatrick_scale:true,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:'🚴',fitzpatrick_scale:true,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:'🚵‍♀️',fitzpatrick_scale:true,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:'🚵',fitzpatrick_scale:true,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:'🏇',fitzpatrick_scale:true,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:'🕴',fitzpatrick_scale:true,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:'🏆',fitzpatrick_scale:false,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:'🎽',fitzpatrick_scale:false,category:"activity"},medal_sports:{keywords:["award","winning"],char:'🏅',fitzpatrick_scale:false,category:"activity"},medal_military:{keywords:["award","winning","army"],char:'🎖',fitzpatrick_scale:false,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:'🥇',fitzpatrick_scale:false,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:'🥈',fitzpatrick_scale:false,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:'🥉',fitzpatrick_scale:false,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:'🎗',fitzpatrick_scale:false,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:'🏵',fitzpatrick_scale:false,category:"activity"},ticket:{keywords:["event","concert","pass"],char:'🎫',fitzpatrick_scale:false,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:'🎟',fitzpatrick_scale:false,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:'🎭',fitzpatrick_scale:false,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:'🎨',fitzpatrick_scale:false,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:'🎪',fitzpatrick_scale:false,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:'🤹‍♀️',fitzpatrick_scale:true,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:'🤹‍♂️',fitzpatrick_scale:true,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:'🎤',fitzpatrick_scale:false,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:'🎧',fitzpatrick_scale:false,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:'🎼',fitzpatrick_scale:false,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:'🎹',fitzpatrick_scale:false,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:'🥁',fitzpatrick_scale:false,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:'🎷',fitzpatrick_scale:false,category:"activity"},trumpet:{keywords:["music","brass"],char:'🎺',fitzpatrick_scale:false,category:"activity"},guitar:{keywords:["music","instrument"],char:'🎸',fitzpatrick_scale:false,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:'🎻',fitzpatrick_scale:false,category:"activity"},clapper:{keywords:["movie","film","record"],char:'🎬',fitzpatrick_scale:false,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:'🎮',fitzpatrick_scale:false,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:'👾',fitzpatrick_scale:false,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:'🎯',fitzpatrick_scale:false,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:'🎲',fitzpatrick_scale:false,category:"activity"},chess_pawn:{keywords:["expendable"],char:"♟",fitzpatrick_scale:false,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:'🎰',fitzpatrick_scale:false,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:'🧩',fitzpatrick_scale:false,category:"activity"},bowling:{keywords:["sports","fun","play"],char:'🎳',fitzpatrick_scale:false,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:'🚗',fitzpatrick_scale:false,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:'🚕',fitzpatrick_scale:false,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:'🚙',fitzpatrick_scale:false,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:'🚌',fitzpatrick_scale:false,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:'🚎',fitzpatrick_scale:false,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:'🏎',fitzpatrick_scale:false,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:'🚓',fitzpatrick_scale:false,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:'🚑',fitzpatrick_scale:false,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:'🚒',fitzpatrick_scale:false,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:'🚐',fitzpatrick_scale:false,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:'🚚',fitzpatrick_scale:false,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:'🚛',fitzpatrick_scale:false,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:'🚜',fitzpatrick_scale:false,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:'🛴',fitzpatrick_scale:false,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:'🏍',fitzpatrick_scale:false,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:'🚲',fitzpatrick_scale:false,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:'🛵',fitzpatrick_scale:false,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:'🚨',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:'🚔',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:'🚍',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:'🚘',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:'🚖',fitzpatrick_scale:false,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:'🚡',fitzpatrick_scale:false,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:'🚠',fitzpatrick_scale:false,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:'🚟',fitzpatrick_scale:false,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:'🚃',fitzpatrick_scale:false,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:'🚋',fitzpatrick_scale:false,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:'🚝',fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:'🚄',fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:'🚅',fitzpatrick_scale:false,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:'🚈',fitzpatrick_scale:false,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:'🚞',fitzpatrick_scale:false,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:'🚂',fitzpatrick_scale:false,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:'🚆',fitzpatrick_scale:false,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:'🚇',fitzpatrick_scale:false,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:'🚊',fitzpatrick_scale:false,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:'🚉',fitzpatrick_scale:false,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:'🛸',fitzpatrick_scale:false,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:'🚁',fitzpatrick_scale:false,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:'🛩',fitzpatrick_scale:false,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:'✈️',fitzpatrick_scale:false,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:'🛫',fitzpatrick_scale:false,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:'🛬',fitzpatrick_scale:false,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:'⛵',fitzpatrick_scale:false,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:'🛥',fitzpatrick_scale:false,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:'🚤',fitzpatrick_scale:false,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:'⛴',fitzpatrick_scale:false,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:'🛳',fitzpatrick_scale:false,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:'🚀',fitzpatrick_scale:false,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:'🛰',fitzpatrick_scale:false,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:'💺',fitzpatrick_scale:false,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:'🛶',fitzpatrick_scale:false,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:'⚓',fitzpatrick_scale:false,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:'🚧',fitzpatrick_scale:false,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:'⛽',fitzpatrick_scale:false,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:'🚏',fitzpatrick_scale:false,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:'🚦',fitzpatrick_scale:false,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:'🚥',fitzpatrick_scale:false,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:'🏁',fitzpatrick_scale:false,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:'🚢',fitzpatrick_scale:false,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:'🎡',fitzpatrick_scale:false,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:'🎢',fitzpatrick_scale:false,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:'🎠',fitzpatrick_scale:false,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:'🏗',fitzpatrick_scale:false,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:'🌁',fitzpatrick_scale:false,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:'🗼',fitzpatrick_scale:false,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:'🏭',fitzpatrick_scale:false,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:'⛲',fitzpatrick_scale:false,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:'🎑',fitzpatrick_scale:false,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:'⛰',fitzpatrick_scale:false,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:'🏔',fitzpatrick_scale:false,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:'🗻',fitzpatrick_scale:false,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:'🌋',fitzpatrick_scale:false,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:'🗾',fitzpatrick_scale:false,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:'🏕',fitzpatrick_scale:false,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:'⛺',fitzpatrick_scale:false,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:'🏞',fitzpatrick_scale:false,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:'🛣',fitzpatrick_scale:false,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:'🛤',fitzpatrick_scale:false,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:'🌅',fitzpatrick_scale:false,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:'🌄',fitzpatrick_scale:false,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:'🏜',fitzpatrick_scale:false,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:'🏖',fitzpatrick_scale:false,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:'🏝',fitzpatrick_scale:false,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:'🌇',fitzpatrick_scale:false,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:'🌆',fitzpatrick_scale:false,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:'🏙',fitzpatrick_scale:false,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:'🌃',fitzpatrick_scale:false,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:'🌉',fitzpatrick_scale:false,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:'🌌',fitzpatrick_scale:false,category:"travel_and_places"},stars:{keywords:["night","photo"],char:'🌠',fitzpatrick_scale:false,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:'🎇',fitzpatrick_scale:false,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:'🎆',fitzpatrick_scale:false,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:'🌈',fitzpatrick_scale:false,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:'🏘',fitzpatrick_scale:false,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:'🏰',fitzpatrick_scale:false,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:'🏯',fitzpatrick_scale:false,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:'🏟',fitzpatrick_scale:false,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:'🗽',fitzpatrick_scale:false,category:"travel_and_places"},house:{keywords:["building","home"],char:'🏠',fitzpatrick_scale:false,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:'🏡',fitzpatrick_scale:false,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:'🏚',fitzpatrick_scale:false,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:'🏢',fitzpatrick_scale:false,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:'🏬',fitzpatrick_scale:false,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:'🏣',fitzpatrick_scale:false,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:'🏤',fitzpatrick_scale:false,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:'🏥',fitzpatrick_scale:false,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:'🏦',fitzpatrick_scale:false,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:'🏨',fitzpatrick_scale:false,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:'🏪',fitzpatrick_scale:false,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:'🏫',fitzpatrick_scale:false,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:'🏩',fitzpatrick_scale:false,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:'💒',fitzpatrick_scale:false,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:'🏛',fitzpatrick_scale:false,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:'⛪',fitzpatrick_scale:false,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:'🕌',fitzpatrick_scale:false,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:'🕍',fitzpatrick_scale:false,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:'🕋',fitzpatrick_scale:false,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:'⛩',fitzpatrick_scale:false,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:'⌚',fitzpatrick_scale:false,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:'📱',fitzpatrick_scale:false,category:"objects"},calling:{keywords:["iphone","incoming"],char:'📲',fitzpatrick_scale:false,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:'💻',fitzpatrick_scale:false,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:'⌨',fitzpatrick_scale:false,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:'🖥',fitzpatrick_scale:false,category:"objects"},printer:{keywords:["paper","ink"],char:'🖨',fitzpatrick_scale:false,category:"objects"},computer_mouse:{keywords:["click"],char:'🖱',fitzpatrick_scale:false,category:"objects"},trackball:{keywords:["technology","trackpad"],char:'🖲',fitzpatrick_scale:false,category:"objects"},joystick:{keywords:["game","play"],char:'🕹',fitzpatrick_scale:false,category:"objects"},clamp:{keywords:["tool"],char:'🗜',fitzpatrick_scale:false,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:'💽',fitzpatrick_scale:false,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:'💾',fitzpatrick_scale:false,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:'💿',fitzpatrick_scale:false,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:'📀',fitzpatrick_scale:false,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:'📼',fitzpatrick_scale:false,category:"objects"},camera:{keywords:["gadgets","photography"],char:'📷',fitzpatrick_scale:false,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:'📸',fitzpatrick_scale:false,category:"objects"},video_camera:{keywords:["film","record"],char:'📹',fitzpatrick_scale:false,category:"objects"},movie_camera:{keywords:["film","record"],char:'🎥',fitzpatrick_scale:false,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:'📽',fitzpatrick_scale:false,category:"objects"},film_strip:{keywords:["movie"],char:'🎞',fitzpatrick_scale:false,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:'📞',fitzpatrick_scale:false,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:'☎️',fitzpatrick_scale:false,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:'📟',fitzpatrick_scale:false,category:"objects"},fax:{keywords:["communication","technology"],char:'📠',fitzpatrick_scale:false,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:'📺',fitzpatrick_scale:false,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:'📻',fitzpatrick_scale:false,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:'🎙',fitzpatrick_scale:false,category:"objects"},level_slider:{keywords:["scale"],char:'🎚',fitzpatrick_scale:false,category:"objects"},control_knobs:{keywords:["dial"],char:'🎛',fitzpatrick_scale:false,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:'🧭',fitzpatrick_scale:false,category:"objects"},stopwatch:{keywords:["time","deadline"],char:'⏱',fitzpatrick_scale:false,category:"objects"},timer_clock:{keywords:["alarm"],char:'⏲',fitzpatrick_scale:false,category:"objects"},alarm_clock:{keywords:["time","wake"],char:'⏰',fitzpatrick_scale:false,category:"objects"},mantelpiece_clock:{keywords:["time"],char:'🕰',fitzpatrick_scale:false,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:'⏳',fitzpatrick_scale:false,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:'⌛',fitzpatrick_scale:false,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:'📡',fitzpatrick_scale:false,category:"objects"},battery:{keywords:["power","energy","sustain"],char:'🔋',fitzpatrick_scale:false,category:"objects"},electric_plug:{keywords:["charger","power"],char:'🔌',fitzpatrick_scale:false,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:'💡',fitzpatrick_scale:false,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:'🔦',fitzpatrick_scale:false,category:"objects"},candle:{keywords:["fire","wax"],char:'🕯',fitzpatrick_scale:false,category:"objects"},fire_extinguisher:{keywords:["quench"],char:'🧯',fitzpatrick_scale:false,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:'🗑',fitzpatrick_scale:false,category:"objects"},oil_drum:{keywords:["barrell"],char:'🛢',fitzpatrick_scale:false,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:'💸',fitzpatrick_scale:false,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:'💵',fitzpatrick_scale:false,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:'💴',fitzpatrick_scale:false,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:'💶',fitzpatrick_scale:false,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:'💷',fitzpatrick_scale:false,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:'💰',fitzpatrick_scale:false,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:'💳',fitzpatrick_scale:false,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:'💎',fitzpatrick_scale:false,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:'⚖',fitzpatrick_scale:false,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:'🧰',fitzpatrick_scale:false,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:'🔧',fitzpatrick_scale:false,category:"objects"},hammer:{keywords:["tools","build","create"],char:'🔨',fitzpatrick_scale:false,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:'⚒',fitzpatrick_scale:false,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:'🛠',fitzpatrick_scale:false,category:"objects"},pick:{keywords:["tools","dig"],char:'⛏',fitzpatrick_scale:false,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:'🔩',fitzpatrick_scale:false,category:"objects"},gear:{keywords:["cog"],char:'⚙',fitzpatrick_scale:false,category:"objects"},brick:{keywords:["bricks"],char:'🧱',fitzpatrick_scale:false,category:"objects"},chains:{keywords:["lock","arrest"],char:'⛓',fitzpatrick_scale:false,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:'🧲',fitzpatrick_scale:false,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:'🔫',fitzpatrick_scale:false,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:'💣',fitzpatrick_scale:false,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:'🧨',fitzpatrick_scale:false,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:'🔪',fitzpatrick_scale:false,category:"objects"},dagger:{keywords:["weapon"],char:'🗡',fitzpatrick_scale:false,category:"objects"},crossed_swords:{keywords:["weapon"],char:'⚔',fitzpatrick_scale:false,category:"objects"},shield:{keywords:["protection","security"],char:'🛡',fitzpatrick_scale:false,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:'🚬',fitzpatrick_scale:false,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:'☠',fitzpatrick_scale:false,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:'⚰',fitzpatrick_scale:false,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:'⚱',fitzpatrick_scale:false,category:"objects"},amphora:{keywords:["vase","jar"],char:'🏺',fitzpatrick_scale:false,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:'🔮',fitzpatrick_scale:false,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:'📿',fitzpatrick_scale:false,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:'🧿',fitzpatrick_scale:false,category:"objects"},barber:{keywords:["hair","salon","style"],char:'💈',fitzpatrick_scale:false,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:'⚗',fitzpatrick_scale:false,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:'🔭',fitzpatrick_scale:false,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:'🔬',fitzpatrick_scale:false,category:"objects"},hole:{keywords:["embarrassing"],char:'🕳',fitzpatrick_scale:false,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:'💊',fitzpatrick_scale:false,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:'💉',fitzpatrick_scale:false,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:'🧬',fitzpatrick_scale:false,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:'🦠',fitzpatrick_scale:false,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:'🧫',fitzpatrick_scale:false,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:'🧪',fitzpatrick_scale:false,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:'🌡',fitzpatrick_scale:false,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:'🧹',fitzpatrick_scale:false,category:"objects"},basket:{keywords:["laundry"],char:'🧺',fitzpatrick_scale:false,category:"objects"},toilet_paper:{keywords:["roll"],char:'🧻',fitzpatrick_scale:false,category:"objects"},label:{keywords:["sale","tag"],char:'🏷',fitzpatrick_scale:false,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:'🔖',fitzpatrick_scale:false,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:'🚽',fitzpatrick_scale:false,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:'🚿',fitzpatrick_scale:false,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:'🛁',fitzpatrick_scale:false,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:'🧼',fitzpatrick_scale:false,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:'🧽',fitzpatrick_scale:false,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:'🧴',fitzpatrick_scale:false,category:"objects"},key:{keywords:["lock","door","password"],char:'🔑',fitzpatrick_scale:false,category:"objects"},old_key:{keywords:["lock","door","password"],char:'🗝',fitzpatrick_scale:false,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:'🛋',fitzpatrick_scale:false,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:'🛌',fitzpatrick_scale:true,category:"objects"},bed:{keywords:["sleep","rest"],char:'🛏',fitzpatrick_scale:false,category:"objects"},door:{keywords:["house","entry","exit"],char:'🚪',fitzpatrick_scale:false,category:"objects"},bellhop_bell:{keywords:["service"],char:'🛎',fitzpatrick_scale:false,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:'🧸',fitzpatrick_scale:false,category:"objects"},framed_picture:{keywords:["photography"],char:'🖼',fitzpatrick_scale:false,category:"objects"},world_map:{keywords:["location","direction"],char:'🗺',fitzpatrick_scale:false,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:'⛱',fitzpatrick_scale:false,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:'🗿',fitzpatrick_scale:false,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:'🛍',fitzpatrick_scale:false,category:"objects"},shopping_cart:{keywords:["trolley"],char:'🛒',fitzpatrick_scale:false,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:'🎈',fitzpatrick_scale:false,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:'🎏',fitzpatrick_scale:false,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:'🎀',fitzpatrick_scale:false,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:'🎁',fitzpatrick_scale:false,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:'🎊',fitzpatrick_scale:false,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:'🎉',fitzpatrick_scale:false,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:'🎎',fitzpatrick_scale:false,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:'🎐',fitzpatrick_scale:false,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:'🎌',fitzpatrick_scale:false,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:'🏮',fitzpatrick_scale:false,category:"objects"},red_envelope:{keywords:["gift"],char:'🧧',fitzpatrick_scale:false,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:'✉️',fitzpatrick_scale:false,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:'📩',fitzpatrick_scale:false,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:'📨',fitzpatrick_scale:false,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:'📧',fitzpatrick_scale:false,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:'💌',fitzpatrick_scale:false,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:'📮',fitzpatrick_scale:false,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:'📪',fitzpatrick_scale:false,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:'📫',fitzpatrick_scale:false,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:'📬',fitzpatrick_scale:false,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:'📭',fitzpatrick_scale:false,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:'📦',fitzpatrick_scale:false,category:"objects"},postal_horn:{keywords:["instrument","music"],char:'📯',fitzpatrick_scale:false,category:"objects"},inbox_tray:{keywords:["email","documents"],char:'📥',fitzpatrick_scale:false,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:'📤',fitzpatrick_scale:false,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:'📜',fitzpatrick_scale:false,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:'📃',fitzpatrick_scale:false,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:'📑',fitzpatrick_scale:false,category:"objects"},receipt:{keywords:["accounting","expenses"],char:'🧾',fitzpatrick_scale:false,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:'📊',fitzpatrick_scale:false,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:'📈',fitzpatrick_scale:false,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:'📉',fitzpatrick_scale:false,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:'📄',fitzpatrick_scale:false,category:"objects"},date:{keywords:["calendar","schedule"],char:'📅',fitzpatrick_scale:false,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:'📆',fitzpatrick_scale:false,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:'🗓',fitzpatrick_scale:false,category:"objects"},card_index:{keywords:["business","stationery"],char:'📇',fitzpatrick_scale:false,category:"objects"},card_file_box:{keywords:["business","stationery"],char:'🗃',fitzpatrick_scale:false,category:"objects"},ballot_box:{keywords:["election","vote"],char:'🗳',fitzpatrick_scale:false,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:'🗄',fitzpatrick_scale:false,category:"objects"},clipboard:{keywords:["stationery","documents"],char:'📋',fitzpatrick_scale:false,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:'🗒',fitzpatrick_scale:false,category:"objects"},file_folder:{keywords:["documents","business","office"],char:'📁',fitzpatrick_scale:false,category:"objects"},open_file_folder:{keywords:["documents","load"],char:'📂',fitzpatrick_scale:false,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:'🗂',fitzpatrick_scale:false,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:'🗞',fitzpatrick_scale:false,category:"objects"},newspaper:{keywords:["press","headline"],char:'📰',fitzpatrick_scale:false,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:'📓',fitzpatrick_scale:false,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:'📕',fitzpatrick_scale:false,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:'📗',fitzpatrick_scale:false,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:'📘',fitzpatrick_scale:false,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:'📙',fitzpatrick_scale:false,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:'📔',fitzpatrick_scale:false,category:"objects"},ledger:{keywords:["notes","paper"],char:'📒',fitzpatrick_scale:false,category:"objects"},books:{keywords:["literature","library","study"],char:'📚',fitzpatrick_scale:false,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:'📖',fitzpatrick_scale:false,category:"objects"},safety_pin:{keywords:["diaper"],char:'🧷',fitzpatrick_scale:false,category:"objects"},link:{keywords:["rings","url"],char:'🔗',fitzpatrick_scale:false,category:"objects"},paperclip:{keywords:["documents","stationery"],char:'📎',fitzpatrick_scale:false,category:"objects"},paperclips:{keywords:["documents","stationery"],char:'🖇',fitzpatrick_scale:false,category:"objects"},scissors:{keywords:["stationery","cut"],char:'✂️',fitzpatrick_scale:false,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:'📐',fitzpatrick_scale:false,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:'📏',fitzpatrick_scale:false,category:"objects"},abacus:{keywords:["calculation"],char:'🧮',fitzpatrick_scale:false,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:'📌',fitzpatrick_scale:false,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:'📍',fitzpatrick_scale:false,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:'🚩',fitzpatrick_scale:false,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:'🏳',fitzpatrick_scale:false,category:"objects"},black_flag:{keywords:["pirate"],char:'🏴',fitzpatrick_scale:false,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:'🏳️‍🌈',fitzpatrick_scale:false,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:'🔐',fitzpatrick_scale:false,category:"objects"},lock:{keywords:["security","password","padlock"],char:'🔒',fitzpatrick_scale:false,category:"objects"},unlock:{keywords:["privacy","security"],char:'🔓',fitzpatrick_scale:false,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:'🔏',fitzpatrick_scale:false,category:"objects"},pen:{keywords:["stationery","writing","write"],char:'🖊',fitzpatrick_scale:false,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:'🖋',fitzpatrick_scale:false,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:'✒️',fitzpatrick_scale:false,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:'📝',fitzpatrick_scale:false,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:'✏️',fitzpatrick_scale:false,category:"objects"},crayon:{keywords:["drawing","creativity"],char:'🖍',fitzpatrick_scale:false,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:'🖌',fitzpatrick_scale:false,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:'🔍',fitzpatrick_scale:false,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:'🔎',fitzpatrick_scale:false,category:"objects"},heart:{keywords:["love","like","valentines"],char:'❤️',fitzpatrick_scale:false,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:'🧡',fitzpatrick_scale:false,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:'💛',fitzpatrick_scale:false,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:'💚',fitzpatrick_scale:false,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:'💙',fitzpatrick_scale:false,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:'💜',fitzpatrick_scale:false,category:"symbols"},black_heart:{keywords:["evil"],char:'🖤',fitzpatrick_scale:false,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:'💔',fitzpatrick_scale:false,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:'❣',fitzpatrick_scale:false,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:'💕',fitzpatrick_scale:false,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:'💞',fitzpatrick_scale:false,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:'💓',fitzpatrick_scale:false,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:'💗',fitzpatrick_scale:false,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:'💖',fitzpatrick_scale:false,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:'💘',fitzpatrick_scale:false,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:'💝',fitzpatrick_scale:false,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:'💟',fitzpatrick_scale:false,category:"symbols"},peace_symbol:{keywords:["hippie"],char:'☮',fitzpatrick_scale:false,category:"symbols"},latin_cross:{keywords:["christianity"],char:'✝',fitzpatrick_scale:false,category:"symbols"},star_and_crescent:{keywords:["islam"],char:'☪',fitzpatrick_scale:false,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'🕉',fitzpatrick_scale:false,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'☸',fitzpatrick_scale:false,category:"symbols"},star_of_david:{keywords:["judaism"],char:'✡',fitzpatrick_scale:false,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:'🔯',fitzpatrick_scale:false,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:'🕎',fitzpatrick_scale:false,category:"symbols"},yin_yang:{keywords:["balance"],char:'☯',fitzpatrick_scale:false,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:'☦',fitzpatrick_scale:false,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:'🛐',fitzpatrick_scale:false,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:'⛎',fitzpatrick_scale:false,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:'♈',fitzpatrick_scale:false,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:'♉',fitzpatrick_scale:false,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:'♊',fitzpatrick_scale:false,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:'♋',fitzpatrick_scale:false,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:'♌',fitzpatrick_scale:false,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:'♍',fitzpatrick_scale:false,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:'♎',fitzpatrick_scale:false,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:'♏',fitzpatrick_scale:false,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:'♐',fitzpatrick_scale:false,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:'♑',fitzpatrick_scale:false,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:'♒',fitzpatrick_scale:false,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:'♓',fitzpatrick_scale:false,category:"symbols"},id:{keywords:["purple-square","words"],char:'🆔',fitzpatrick_scale:false,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:'⚛',fitzpatrick_scale:false,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:'🈳',fitzpatrick_scale:false,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:'🈹',fitzpatrick_scale:false,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:'☢',fitzpatrick_scale:false,category:"symbols"},biohazard:{keywords:["danger"],char:'☣',fitzpatrick_scale:false,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:'📴',fitzpatrick_scale:false,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:'📳',fitzpatrick_scale:false,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:'🈶',fitzpatrick_scale:false,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:'🈚',fitzpatrick_scale:false,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:'🈸',fitzpatrick_scale:false,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:'🈺',fitzpatrick_scale:false,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:'🈷️',fitzpatrick_scale:false,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:'✴️',fitzpatrick_scale:false,category:"symbols"},vs:{keywords:["words","orange-square"],char:'🆚',fitzpatrick_scale:false,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:'🉑',fitzpatrick_scale:false,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:'💮',fitzpatrick_scale:false,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:'🉐',fitzpatrick_scale:false,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:'㊙️',fitzpatrick_scale:false,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:'㊗️',fitzpatrick_scale:false,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:'🈴',fitzpatrick_scale:false,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:'🈵',fitzpatrick_scale:false,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:'🈲',fitzpatrick_scale:false,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:'🅰️',fitzpatrick_scale:false,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:'🅱️',fitzpatrick_scale:false,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:'🆎',fitzpatrick_scale:false,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:'🆑',fitzpatrick_scale:false,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:'🅾️',fitzpatrick_scale:false,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:'🆘',fitzpatrick_scale:false,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:'⛔',fitzpatrick_scale:false,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:'📛',fitzpatrick_scale:false,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:'🚫',fitzpatrick_scale:false,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:'❌',fitzpatrick_scale:false,category:"symbols"},o:{keywords:["circle","round"],char:'⭕',fitzpatrick_scale:false,category:"symbols"},stop_sign:{keywords:["stop"],char:'🛑',fitzpatrick_scale:false,category:"symbols"},anger:{keywords:["angry","mad"],char:'💢',fitzpatrick_scale:false,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:'♨️',fitzpatrick_scale:false,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:'🚷',fitzpatrick_scale:false,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:'🚯',fitzpatrick_scale:false,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:'🚳',fitzpatrick_scale:false,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:'🚱',fitzpatrick_scale:false,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:'🔞',fitzpatrick_scale:false,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:'📵',fitzpatrick_scale:false,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:'❗',fitzpatrick_scale:false,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:'❕',fitzpatrick_scale:false,category:"symbols"},question:{keywords:["doubt","confused"],char:'❓',fitzpatrick_scale:false,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:'❔',fitzpatrick_scale:false,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:'‼️',fitzpatrick_scale:false,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:'⁉️',fitzpatrick_scale:false,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:'🔅',fitzpatrick_scale:false,category:"symbols"},high_brightness:{keywords:["sun","light"],char:'🔆',fitzpatrick_scale:false,category:"symbols"},trident:{keywords:["weapon","spear"],char:'🔱',fitzpatrick_scale:false,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:'⚜',fitzpatrick_scale:false,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:'〽️',fitzpatrick_scale:false,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:'⚠️',fitzpatrick_scale:false,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:'🚸',fitzpatrick_scale:false,category:"symbols"},beginner:{keywords:["badge","shield"],char:'🔰',fitzpatrick_scale:false,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:'♻️',fitzpatrick_scale:false,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:'🈯',fitzpatrick_scale:false,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:'💹',fitzpatrick_scale:false,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:'❇️',fitzpatrick_scale:false,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:'✳️',fitzpatrick_scale:false,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:'❎',fitzpatrick_scale:false,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:'✅',fitzpatrick_scale:false,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:'💠',fitzpatrick_scale:false,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:'🌀',fitzpatrick_scale:false,category:"symbols"},loop:{keywords:["tape","cassette"],char:'➿',fitzpatrick_scale:false,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:'🌐',fitzpatrick_scale:false,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:'Ⓜ️',fitzpatrick_scale:false,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:'🏧',fitzpatrick_scale:false,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:'🈂️',fitzpatrick_scale:false,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:'🛂',fitzpatrick_scale:false,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:'🛃',fitzpatrick_scale:false,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:'🛄',fitzpatrick_scale:false,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:'🛅',fitzpatrick_scale:false,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:'♿',fitzpatrick_scale:false,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:'🚭',fitzpatrick_scale:false,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:'🚾',fitzpatrick_scale:false,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:'🅿️',fitzpatrick_scale:false,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:'🚰',fitzpatrick_scale:false,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:'🚹',fitzpatrick_scale:false,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:'🚺',fitzpatrick_scale:false,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:'🚼',fitzpatrick_scale:false,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:'🚻',fitzpatrick_scale:false,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:'🚮',fitzpatrick_scale:false,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:'🎦',fitzpatrick_scale:false,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:'📶',fitzpatrick_scale:false,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:'🈁',fitzpatrick_scale:false,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:'🆖',fitzpatrick_scale:false,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:'🆗',fitzpatrick_scale:false,category:"symbols"},up:{keywords:["blue-square","above","high"],char:'🆙',fitzpatrick_scale:false,category:"symbols"},cool:{keywords:["words","blue-square"],char:'🆒',fitzpatrick_scale:false,category:"symbols"},new:{keywords:["blue-square","words","start"],char:'🆕',fitzpatrick_scale:false,category:"symbols"},free:{keywords:["blue-square","words"],char:'🆓',fitzpatrick_scale:false,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:'0️⃣',fitzpatrick_scale:false,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:'1️⃣',fitzpatrick_scale:false,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:'2️⃣',fitzpatrick_scale:false,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:'3️⃣',fitzpatrick_scale:false,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:'4️⃣',fitzpatrick_scale:false,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:'5️⃣',fitzpatrick_scale:false,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:'6️⃣',fitzpatrick_scale:false,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:'7️⃣',fitzpatrick_scale:false,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:'8️⃣',fitzpatrick_scale:false,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:'9️⃣',fitzpatrick_scale:false,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:'🔟',fitzpatrick_scale:false,category:"symbols"},asterisk:{keywords:["star","keycap"],char:'*⃣',fitzpatrick_scale:false,category:"symbols"},eject_button:{keywords:["blue-square"],char:'⏏️',fitzpatrick_scale:false,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:'▶️',fitzpatrick_scale:false,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:'⏸',fitzpatrick_scale:false,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:'⏭',fitzpatrick_scale:false,category:"symbols"},stop_button:{keywords:["blue-square"],char:'⏹',fitzpatrick_scale:false,category:"symbols"},record_button:{keywords:["blue-square"],char:'⏺',fitzpatrick_scale:false,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:'⏯',fitzpatrick_scale:false,category:"symbols"},previous_track_button:{keywords:["backward"],char:'⏮',fitzpatrick_scale:false,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:'⏩',fitzpatrick_scale:false,category:"symbols"},rewind:{keywords:["play","blue-square"],char:'⏪',fitzpatrick_scale:false,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:'🔀',fitzpatrick_scale:false,category:"symbols"},repeat:{keywords:["loop","record"],char:'🔁',fitzpatrick_scale:false,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:'🔂',fitzpatrick_scale:false,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:'◀️',fitzpatrick_scale:false,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:'🔼',fitzpatrick_scale:false,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:'🔽',fitzpatrick_scale:false,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:'⏫',fitzpatrick_scale:false,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:'⏬',fitzpatrick_scale:false,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:'➡️',fitzpatrick_scale:false,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:'⬅️',fitzpatrick_scale:false,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:'⬆️',fitzpatrick_scale:false,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:'⬇️',fitzpatrick_scale:false,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:'↗️',fitzpatrick_scale:false,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:'↘️',fitzpatrick_scale:false,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:'↙️',fitzpatrick_scale:false,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:'↖️',fitzpatrick_scale:false,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:'↕️',fitzpatrick_scale:false,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:'↔️',fitzpatrick_scale:false,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:'🔄',fitzpatrick_scale:false,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:'↪️',fitzpatrick_scale:false,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:'↩️',fitzpatrick_scale:false,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:'⤴️',fitzpatrick_scale:false,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:'⤵️',fitzpatrick_scale:false,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:'#️⃣',fitzpatrick_scale:false,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:'ℹ️',fitzpatrick_scale:false,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:'🔤',fitzpatrick_scale:false,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:'🔡',fitzpatrick_scale:false,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:'🔠',fitzpatrick_scale:false,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:'🔣',fitzpatrick_scale:false,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:'🎵',fitzpatrick_scale:false,category:"symbols"},notes:{keywords:["music","score"],char:'🎶',fitzpatrick_scale:false,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:'〰️',fitzpatrick_scale:false,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:'➰',fitzpatrick_scale:false,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:'✔️',fitzpatrick_scale:false,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:'🔃',fitzpatrick_scale:false,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:'➕',fitzpatrick_scale:false,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:'➖',fitzpatrick_scale:false,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:'➗',fitzpatrick_scale:false,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:'✖️',fitzpatrick_scale:false,category:"symbols"},infinity:{keywords:["forever"],char:'♾',fitzpatrick_scale:false,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:'💲',fitzpatrick_scale:false,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:'💱',fitzpatrick_scale:false,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:'©️',fitzpatrick_scale:false,category:"symbols"},registered:{keywords:["alphabet","circle"],char:'®️',fitzpatrick_scale:false,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:'™️',fitzpatrick_scale:false,category:"symbols"},end:{keywords:["words","arrow"],char:'🔚',fitzpatrick_scale:false,category:"symbols"},back:{keywords:["arrow","words","return"],char:'🔙',fitzpatrick_scale:false,category:"symbols"},on:{keywords:["arrow","words"],char:'🔛',fitzpatrick_scale:false,category:"symbols"},top:{keywords:["words","blue-square"],char:'🔝',fitzpatrick_scale:false,category:"symbols"},soon:{keywords:["arrow","words"],char:'🔜',fitzpatrick_scale:false,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:'☑️',fitzpatrick_scale:false,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:'🔘',fitzpatrick_scale:false,category:"symbols"},white_circle:{keywords:["shape","round"],char:'⚪',fitzpatrick_scale:false,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:'⚫',fitzpatrick_scale:false,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:'🔴',fitzpatrick_scale:false,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:'🔵',fitzpatrick_scale:false,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:'🔸',fitzpatrick_scale:false,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:'🔹',fitzpatrick_scale:false,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:'🔶',fitzpatrick_scale:false,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:'🔷',fitzpatrick_scale:false,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:'🔺',fitzpatrick_scale:false,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:'▪️',fitzpatrick_scale:false,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:'▫️',fitzpatrick_scale:false,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:'⬛',fitzpatrick_scale:false,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:'⬜',fitzpatrick_scale:false,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:'🔻',fitzpatrick_scale:false,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:'◼️',fitzpatrick_scale:false,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:'◻️',fitzpatrick_scale:false,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:'◾',fitzpatrick_scale:false,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:'◽',fitzpatrick_scale:false,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:'🔲',fitzpatrick_scale:false,category:"symbols"},white_square_button:{keywords:["shape","input"],char:'🔳',fitzpatrick_scale:false,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:'🔈',fitzpatrick_scale:false,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:'🔉',fitzpatrick_scale:false,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:'🔊',fitzpatrick_scale:false,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:'🔇',fitzpatrick_scale:false,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:'📣',fitzpatrick_scale:false,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:'📢',fitzpatrick_scale:false,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:'🔔',fitzpatrick_scale:false,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:'🔕',fitzpatrick_scale:false,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:'🃏',fitzpatrick_scale:false,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:'🀄',fitzpatrick_scale:false,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:'♠️',fitzpatrick_scale:false,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:'♣️',fitzpatrick_scale:false,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:'♥️',fitzpatrick_scale:false,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:'♦️',fitzpatrick_scale:false,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:'🎴',fitzpatrick_scale:false,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:'💭',fitzpatrick_scale:false,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:'🗯',fitzpatrick_scale:false,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:'💬',fitzpatrick_scale:false,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:'🗨',fitzpatrick_scale:false,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:'🕐',fitzpatrick_scale:false,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:'🕑',fitzpatrick_scale:false,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:'🕒',fitzpatrick_scale:false,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:'🕓',fitzpatrick_scale:false,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:'🕔',fitzpatrick_scale:false,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:'🕕',fitzpatrick_scale:false,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:'🕖',fitzpatrick_scale:false,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:'🕗',fitzpatrick_scale:false,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:'🕘',fitzpatrick_scale:false,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:'🕙',fitzpatrick_scale:false,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:'🕚',fitzpatrick_scale:false,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:'🕛',fitzpatrick_scale:false,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:'🕜',fitzpatrick_scale:false,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:'🕝',fitzpatrick_scale:false,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:'🕞',fitzpatrick_scale:false,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:'🕟',fitzpatrick_scale:false,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:'🕠',fitzpatrick_scale:false,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:'🕡',fitzpatrick_scale:false,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:'🕢',fitzpatrick_scale:false,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:'🕣',fitzpatrick_scale:false,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:'🕤',fitzpatrick_scale:false,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:'🕥',fitzpatrick_scale:false,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:'🕦',fitzpatrick_scale:false,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:'🕧',fitzpatrick_scale:false,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:'🇦🇫',fitzpatrick_scale:false,category:"flags"},aland_islands:{keywords:["Åland","islands","flag","nation","country","banner"],char:'🇦🇽',fitzpatrick_scale:false,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:'🇦🇱',fitzpatrick_scale:false,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:'🇩🇿',fitzpatrick_scale:false,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:'🇦🇸',fitzpatrick_scale:false,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:'🇦🇩',fitzpatrick_scale:false,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:'🇦🇴',fitzpatrick_scale:false,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:'🇦🇮',fitzpatrick_scale:false,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:'🇦🇶',fitzpatrick_scale:false,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:'🇦🇬',fitzpatrick_scale:false,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:'🇦🇷',fitzpatrick_scale:false,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:'🇦🇲',fitzpatrick_scale:false,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:'🇦🇼',fitzpatrick_scale:false,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:'🇦🇺',fitzpatrick_scale:false,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:'🇦🇹',fitzpatrick_scale:false,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:'🇦🇿',fitzpatrick_scale:false,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:'🇧🇸',fitzpatrick_scale:false,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:'🇧🇭',fitzpatrick_scale:false,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:'🇧🇩',fitzpatrick_scale:false,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:'🇧🇧',fitzpatrick_scale:false,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:'🇧🇾',fitzpatrick_scale:false,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:'🇧🇪',fitzpatrick_scale:false,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:'🇧🇿',fitzpatrick_scale:false,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:'🇧🇯',fitzpatrick_scale:false,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:'🇧🇲',fitzpatrick_scale:false,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:'🇧🇹',fitzpatrick_scale:false,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:'🇧🇴',fitzpatrick_scale:false,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:'🇧🇶',fitzpatrick_scale:false,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:'🇧🇦',fitzpatrick_scale:false,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:'🇧🇼',fitzpatrick_scale:false,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:'🇧🇷',fitzpatrick_scale:false,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:'🇮🇴',fitzpatrick_scale:false,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:'🇻🇬',fitzpatrick_scale:false,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:'🇧🇳',fitzpatrick_scale:false,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:'🇧🇬',fitzpatrick_scale:false,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:'🇧🇫',fitzpatrick_scale:false,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:'🇧🇮',fitzpatrick_scale:false,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:'🇨🇻',fitzpatrick_scale:false,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:'🇰🇭',fitzpatrick_scale:false,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:'🇨🇲',fitzpatrick_scale:false,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:'🇨🇦',fitzpatrick_scale:false,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:'🇮🇨',fitzpatrick_scale:false,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:'🇰🇾',fitzpatrick_scale:false,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:'🇨🇫',fitzpatrick_scale:false,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:'🇹🇩',fitzpatrick_scale:false,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:'🇨🇱',fitzpatrick_scale:false,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:'🇨🇳',fitzpatrick_scale:false,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:'🇨🇽',fitzpatrick_scale:false,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:'🇨🇨',fitzpatrick_scale:false,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:'🇨🇴',fitzpatrick_scale:false,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:'🇰🇲',fitzpatrick_scale:false,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:'🇨🇬',fitzpatrick_scale:false,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:'🇨🇩',fitzpatrick_scale:false,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:'🇨🇰',fitzpatrick_scale:false,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:'🇨🇷',fitzpatrick_scale:false,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:'🇭🇷',fitzpatrick_scale:false,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:'🇨🇺',fitzpatrick_scale:false,category:"flags"},curacao:{keywords:["curaçao","flag","nation","country","banner"],char:'🇨🇼',fitzpatrick_scale:false,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:'🇨🇾',fitzpatrick_scale:false,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:'🇨🇿',fitzpatrick_scale:false,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:'🇩🇰',fitzpatrick_scale:false,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:'🇩🇯',fitzpatrick_scale:false,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:'🇩🇲',fitzpatrick_scale:false,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:'🇩🇴',fitzpatrick_scale:false,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:'🇪🇨',fitzpatrick_scale:false,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:'🇪🇬',fitzpatrick_scale:false,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:'🇸🇻',fitzpatrick_scale:false,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:'🇬🇶',fitzpatrick_scale:false,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:'🇪🇷',fitzpatrick_scale:false,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:'🇪🇪',fitzpatrick_scale:false,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:'🇪🇹',fitzpatrick_scale:false,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:'🇪🇺',fitzpatrick_scale:false,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:'🇫🇰',fitzpatrick_scale:false,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:'🇫🇴',fitzpatrick_scale:false,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:'🇫🇯',fitzpatrick_scale:false,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:'🇫🇮',fitzpatrick_scale:false,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:'🇫🇷',fitzpatrick_scale:false,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:'🇬🇫',fitzpatrick_scale:false,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:'🇵🇫',fitzpatrick_scale:false,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:'🇹🇫',fitzpatrick_scale:false,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:'🇬🇦',fitzpatrick_scale:false,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:'🇬🇲',fitzpatrick_scale:false,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:'🇬🇪',fitzpatrick_scale:false,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:'🇩🇪',fitzpatrick_scale:false,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:'🇬🇭',fitzpatrick_scale:false,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:'🇬🇮',fitzpatrick_scale:false,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:'🇬🇷',fitzpatrick_scale:false,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:'🇬🇱',fitzpatrick_scale:false,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:'🇬🇩',fitzpatrick_scale:false,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:'🇬🇵',fitzpatrick_scale:false,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:'🇬🇺',fitzpatrick_scale:false,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:'🇬🇹',fitzpatrick_scale:false,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:'🇬🇬',fitzpatrick_scale:false,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:'🇬🇳',fitzpatrick_scale:false,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:'🇬🇼',fitzpatrick_scale:false,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:'🇬🇾',fitzpatrick_scale:false,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:'🇭🇹',fitzpatrick_scale:false,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:'🇭🇳',fitzpatrick_scale:false,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:'🇭🇰',fitzpatrick_scale:false,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:'🇭🇺',fitzpatrick_scale:false,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:'🇮🇸',fitzpatrick_scale:false,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:'🇮🇳',fitzpatrick_scale:false,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:'🇮🇩',fitzpatrick_scale:false,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:'🇮🇷',fitzpatrick_scale:false,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:'🇮🇶',fitzpatrick_scale:false,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:'🇮🇪',fitzpatrick_scale:false,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:'🇮🇲',fitzpatrick_scale:false,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:'🇮🇱',fitzpatrick_scale:false,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:'🇮🇹',fitzpatrick_scale:false,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:'🇨🇮',fitzpatrick_scale:false,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:'🇯🇲',fitzpatrick_scale:false,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:'🇯🇵',fitzpatrick_scale:false,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:'🇯🇪',fitzpatrick_scale:false,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:'🇯🇴',fitzpatrick_scale:false,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:'🇰🇿',fitzpatrick_scale:false,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:'🇰🇪',fitzpatrick_scale:false,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:'🇰🇮',fitzpatrick_scale:false,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:'🇽🇰',fitzpatrick_scale:false,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:'🇰🇼',fitzpatrick_scale:false,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:'🇰🇬',fitzpatrick_scale:false,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:'🇱🇦',fitzpatrick_scale:false,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:'🇱🇻',fitzpatrick_scale:false,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:'🇱🇧',fitzpatrick_scale:false,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:'🇱🇸',fitzpatrick_scale:false,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:'🇱🇷',fitzpatrick_scale:false,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:'🇱🇾',fitzpatrick_scale:false,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:'🇱🇮',fitzpatrick_scale:false,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:'🇱🇹',fitzpatrick_scale:false,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:'🇱🇺',fitzpatrick_scale:false,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:'🇲🇴',fitzpatrick_scale:false,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:'🇲🇰',fitzpatrick_scale:false,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:'🇲🇬',fitzpatrick_scale:false,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:'🇲🇼',fitzpatrick_scale:false,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:'🇲🇾',fitzpatrick_scale:false,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:'🇲🇻',fitzpatrick_scale:false,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:'🇲🇱',fitzpatrick_scale:false,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:'🇲🇹',fitzpatrick_scale:false,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:'🇲🇭',fitzpatrick_scale:false,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:'🇲🇶',fitzpatrick_scale:false,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:'🇲🇷',fitzpatrick_scale:false,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:'🇲🇺',fitzpatrick_scale:false,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:'🇾🇹',fitzpatrick_scale:false,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:'🇲🇽',fitzpatrick_scale:false,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:'🇫🇲',fitzpatrick_scale:false,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:'🇲🇩',fitzpatrick_scale:false,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:'🇲🇨',fitzpatrick_scale:false,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:'🇲🇳',fitzpatrick_scale:false,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:'🇲🇪',fitzpatrick_scale:false,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:'🇲🇸',fitzpatrick_scale:false,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:'🇲🇦',fitzpatrick_scale:false,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:'🇲🇿',fitzpatrick_scale:false,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:'🇲🇲',fitzpatrick_scale:false,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:'🇳🇦',fitzpatrick_scale:false,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:'🇳🇷',fitzpatrick_scale:false,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:'🇳🇵',fitzpatrick_scale:false,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:'🇳🇱',fitzpatrick_scale:false,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:'🇳🇨',fitzpatrick_scale:false,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:'🇳🇿',fitzpatrick_scale:false,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:'🇳🇮',fitzpatrick_scale:false,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:'🇳🇪',fitzpatrick_scale:false,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:'🇳🇬',fitzpatrick_scale:false,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:'🇳🇺',fitzpatrick_scale:false,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:'🇳🇫',fitzpatrick_scale:false,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:'🇲🇵',fitzpatrick_scale:false,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:'🇰🇵',fitzpatrick_scale:false,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:'🇳🇴',fitzpatrick_scale:false,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:'🇴🇲',fitzpatrick_scale:false,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:'🇵🇰',fitzpatrick_scale:false,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:'🇵🇼',fitzpatrick_scale:false,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:'🇵🇸',fitzpatrick_scale:false,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:'🇵🇦',fitzpatrick_scale:false,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:'🇵🇬',fitzpatrick_scale:false,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:'🇵🇾',fitzpatrick_scale:false,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:'🇵🇪',fitzpatrick_scale:false,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:'🇵🇭',fitzpatrick_scale:false,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:'🇵🇳',fitzpatrick_scale:false,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:'🇵🇱',fitzpatrick_scale:false,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:'🇵🇹',fitzpatrick_scale:false,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:'🇵🇷',fitzpatrick_scale:false,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:'🇶🇦',fitzpatrick_scale:false,category:"flags"},reunion:{keywords:["réunion","flag","nation","country","banner"],char:'🇷🇪',fitzpatrick_scale:false,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:'🇷🇴',fitzpatrick_scale:false,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:'🇷🇺',fitzpatrick_scale:false,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:'🇷🇼',fitzpatrick_scale:false,category:"flags"},st_barthelemy:{keywords:["saint","barthélemy","flag","nation","country","banner"],char:'🇧🇱',fitzpatrick_scale:false,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:'🇸🇭',fitzpatrick_scale:false,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:'🇰🇳',fitzpatrick_scale:false,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:'🇱🇨',fitzpatrick_scale:false,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:'🇵🇲',fitzpatrick_scale:false,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:'🇻🇨',fitzpatrick_scale:false,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:'🇼🇸',fitzpatrick_scale:false,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:'🇸🇲',fitzpatrick_scale:false,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:'🇸🇹',fitzpatrick_scale:false,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:'🇸🇦',fitzpatrick_scale:false,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:'🇸🇳',fitzpatrick_scale:false,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:'🇷🇸',fitzpatrick_scale:false,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:'🇸🇨',fitzpatrick_scale:false,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:'🇸🇱',fitzpatrick_scale:false,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:'🇸🇬',fitzpatrick_scale:false,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:'🇸🇽',fitzpatrick_scale:false,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:'🇸🇰',fitzpatrick_scale:false,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:'🇸🇮',fitzpatrick_scale:false,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:'🇸🇧',fitzpatrick_scale:false,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:'🇸🇴',fitzpatrick_scale:false,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:'🇿🇦',fitzpatrick_scale:false,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:'🇬🇸',fitzpatrick_scale:false,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:'🇰🇷',fitzpatrick_scale:false,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:'🇸🇸',fitzpatrick_scale:false,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:'🇪🇸',fitzpatrick_scale:false,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:'🇱🇰',fitzpatrick_scale:false,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:'🇸🇩',fitzpatrick_scale:false,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:'🇸🇷',fitzpatrick_scale:false,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:'🇸🇿',fitzpatrick_scale:false,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:'🇸🇪',fitzpatrick_scale:false,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:'🇨🇭',fitzpatrick_scale:false,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:'🇸🇾',fitzpatrick_scale:false,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:'🇹🇼',fitzpatrick_scale:false,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:'🇹🇯',fitzpatrick_scale:false,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:'🇹🇿',fitzpatrick_scale:false,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:'🇹🇭',fitzpatrick_scale:false,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:'🇹🇱',fitzpatrick_scale:false,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:'🇹🇬',fitzpatrick_scale:false,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:'🇹🇰',fitzpatrick_scale:false,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:'🇹🇴',fitzpatrick_scale:false,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:'🇹🇹',fitzpatrick_scale:false,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:'🇹🇳',fitzpatrick_scale:false,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:'🇹🇷',fitzpatrick_scale:false,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:'🇹🇲',fitzpatrick_scale:false,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:'🇹🇨',fitzpatrick_scale:false,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:'🇹🇻',fitzpatrick_scale:false,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:'🇺🇬',fitzpatrick_scale:false,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:'🇺🇦',fitzpatrick_scale:false,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:'🇦🇪',fitzpatrick_scale:false,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:'🇬🇧',fitzpatrick_scale:false,category:"flags"},england:{keywords:["flag","english"],char:'🏴󠁧󠁢󠁥󠁮󠁧󠁿',fitzpatrick_scale:false,category:"flags"},scotland:{keywords:["flag","scottish"],char:'🏴󠁧󠁢󠁳󠁣󠁴󠁿',fitzpatrick_scale:false,category:"flags"},wales:{keywords:["flag","welsh"],char:'🏴󠁧󠁢󠁷󠁬󠁳󠁿',fitzpatrick_scale:false,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:'🇺🇸',fitzpatrick_scale:false,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:'🇻🇮',fitzpatrick_scale:false,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:'🇺🇾',fitzpatrick_scale:false,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:'🇺🇿',fitzpatrick_scale:false,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:'🇻🇺',fitzpatrick_scale:false,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:'🇻🇦',fitzpatrick_scale:false,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:'🇻🇪',fitzpatrick_scale:false,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:'🇻🇳',fitzpatrick_scale:false,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:'🇼🇫',fitzpatrick_scale:false,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:'🇪🇭',fitzpatrick_scale:false,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:'🇾🇪',fitzpatrick_scale:false,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:'🇿🇲',fitzpatrick_scale:false,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:'🇿🇼',fitzpatrick_scale:false,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:'🇺🇳',fitzpatrick_scale:false,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:'🏴‍☠️',fitzpatrick_scale:false,category:"flags"}}); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/emoticons/js/emojiimages.min.js b/apps/web-antd/public/tinymce/plugins/emoticons/js/emojiimages.min.js new file mode 100644 index 0000000..37f3bcf --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/emoticons/js/emojiimages.min.js @@ -0,0 +1,3 @@ +// Source: npm package: emojilib +// Images provided by twemoji: https://github.com/twitter/twemoji +window.tinymce.Resource.add("tinymce.plugins.emoticons",{100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:'\u{1f4af}',fitzpatrick_scale:!1,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:'\u{1f522}',fitzpatrick_scale:!1,category:"symbols"},grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:'\u{1f600}',fitzpatrick_scale:!1,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:'\u{1f62c}',fitzpatrick_scale:!1,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:'\u{1f601}',fitzpatrick_scale:!1,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:'\u{1f602}',fitzpatrick_scale:!1,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:'\u{1f923}',fitzpatrick_scale:!1,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:'\u{1f973}',fitzpatrick_scale:!1,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:'\u{1f603}',fitzpatrick_scale:!1,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:'\u{1f604}',fitzpatrick_scale:!1,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:'\u{1f605}',fitzpatrick_scale:!1,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:'\u{1f606}',fitzpatrick_scale:!1,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:'\u{1f607}',fitzpatrick_scale:!1,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:'\u{1f609}',fitzpatrick_scale:!1,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:'\u{1f60a}',fitzpatrick_scale:!1,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:'\u{1f642}',fitzpatrick_scale:!1,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:'\u{1f643}',fitzpatrick_scale:!1,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:'\u263a\ufe0f',fitzpatrick_scale:!1,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:'\u{1f60b}',fitzpatrick_scale:!1,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:'\u{1f60c}',fitzpatrick_scale:!1,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:'\u{1f60d}',fitzpatrick_scale:!1,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:'\u{1f970}',fitzpatrick_scale:!1,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'\u{1f618}',fitzpatrick_scale:!1,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:'\u{1f617}',fitzpatrick_scale:!1,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:'\u{1f619}',fitzpatrick_scale:!1,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'\u{1f61a}',fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:'\u{1f61c}',fitzpatrick_scale:!1,category:"people"},zany:{keywords:["face","goofy","crazy"],char:'\u{1f92a}',fitzpatrick_scale:!1,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:'\u{1f928}',fitzpatrick_scale:!1,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:'\u{1f9d0}',fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:'\u{1f61d}',fitzpatrick_scale:!1,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:'\u{1f61b}',fitzpatrick_scale:!1,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:'\u{1f911}',fitzpatrick_scale:!1,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:'\u{1f913}',fitzpatrick_scale:!1,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:'\u{1f60e}',fitzpatrick_scale:!1,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:'\u{1f929}',fitzpatrick_scale:!1,category:"people"},clown_face:{keywords:["face"],char:'\u{1f921}',fitzpatrick_scale:!1,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:'\u{1f920}',fitzpatrick_scale:!1,category:"people"},hugs:{keywords:["face","smile","hug"],char:'\u{1f917}',fitzpatrick_scale:!1,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:'\u{1f60f}',fitzpatrick_scale:!1,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:'\u{1f636}',fitzpatrick_scale:!1,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:'\u{1f610}',fitzpatrick_scale:!1,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:'\u{1f611}',fitzpatrick_scale:!1,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:'\u{1f612}',fitzpatrick_scale:!1,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:'\u{1f644}',fitzpatrick_scale:!1,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:'\u{1f914}',fitzpatrick_scale:!1,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:'\u{1f925}',fitzpatrick_scale:!1,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:'\u{1f92d}',fitzpatrick_scale:!1,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:'\u{1f92b}',fitzpatrick_scale:!1,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:'\u{1f92c}',fitzpatrick_scale:!1,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:'\u{1f92f}',fitzpatrick_scale:!1,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:'\u{1f633}',fitzpatrick_scale:!1,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:'\u{1f61e}',fitzpatrick_scale:!1,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:'\u{1f61f}',fitzpatrick_scale:!1,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:'\u{1f620}',fitzpatrick_scale:!1,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:'\u{1f621}',fitzpatrick_scale:!1,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:'\u{1f614}',fitzpatrick_scale:!1,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:'\u{1f615}',fitzpatrick_scale:!1,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:'\u{1f641}',fitzpatrick_scale:!1,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:'\u2639',fitzpatrick_scale:!1,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:'\u{1f623}',fitzpatrick_scale:!1,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:'\u{1f616}',fitzpatrick_scale:!1,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:'\u{1f62b}',fitzpatrick_scale:!1,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:'\u{1f629}',fitzpatrick_scale:!1,category:"people"},pleading:{keywords:["face","begging","mercy"],char:'\u{1f97a}',fitzpatrick_scale:!1,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:'\u{1f624}',fitzpatrick_scale:!1,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:'\u{1f62e}',fitzpatrick_scale:!1,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:'\u{1f631}',fitzpatrick_scale:!1,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:'\u{1f628}',fitzpatrick_scale:!1,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:'\u{1f630}',fitzpatrick_scale:!1,category:"people"},hushed:{keywords:["face","woo","shh"],char:'\u{1f62f}',fitzpatrick_scale:!1,category:"people"},frowning:{keywords:["face","aw","what"],char:'\u{1f626}',fitzpatrick_scale:!1,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:'\u{1f627}',fitzpatrick_scale:!1,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:'\u{1f622}',fitzpatrick_scale:!1,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:'\u{1f625}',fitzpatrick_scale:!1,category:"people"},drooling_face:{keywords:["face"],char:'\u{1f924}',fitzpatrick_scale:!1,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:'\u{1f62a}',fitzpatrick_scale:!1,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:'\u{1f613}',fitzpatrick_scale:!1,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:'\u{1f975}',fitzpatrick_scale:!1,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:'\u{1f976}',fitzpatrick_scale:!1,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:'\u{1f62d}',fitzpatrick_scale:!1,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:'\u{1f635}',fitzpatrick_scale:!1,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:'\u{1f632}',fitzpatrick_scale:!1,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:'\u{1f910}',fitzpatrick_scale:!1,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:'\u{1f922}',fitzpatrick_scale:!1,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:'\u{1f927}',fitzpatrick_scale:!1,category:"people"},vomiting:{keywords:["face","sick"],char:'\u{1f92e}',fitzpatrick_scale:!1,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:'\u{1f637}',fitzpatrick_scale:!1,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:'\u{1f912}',fitzpatrick_scale:!1,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:'\u{1f915}',fitzpatrick_scale:!1,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:'\u{1f974}',fitzpatrick_scale:!1,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:'\u{1f634}',fitzpatrick_scale:!1,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:'\u{1f4a4}',fitzpatrick_scale:!1,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:'\u{1f4a9}',fitzpatrick_scale:!1,category:"people"},smiling_imp:{keywords:["devil","horns"],char:'\u{1f608}',fitzpatrick_scale:!1,category:"people"},imp:{keywords:["devil","angry","horns"],char:'\u{1f47f}',fitzpatrick_scale:!1,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:'\u{1f479}',fitzpatrick_scale:!1,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:'\u{1f47a}',fitzpatrick_scale:!1,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:'\u{1f480}',fitzpatrick_scale:!1,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:'\u{1f47b}',fitzpatrick_scale:!1,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:'\u{1f47d}',fitzpatrick_scale:!1,category:"people"},robot:{keywords:["computer","machine","bot"],char:'\u{1f916}',fitzpatrick_scale:!1,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:'\u{1f63a}',fitzpatrick_scale:!1,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:'\u{1f638}',fitzpatrick_scale:!1,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:'\u{1f639}',fitzpatrick_scale:!1,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:'\u{1f63b}',fitzpatrick_scale:!1,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:'\u{1f63c}',fitzpatrick_scale:!1,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:'\u{1f63d}',fitzpatrick_scale:!1,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:'\u{1f640}',fitzpatrick_scale:!1,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:'\u{1f63f}',fitzpatrick_scale:!1,category:"people"},pouting_cat:{keywords:["animal","cats"],char:'\u{1f63e}',fitzpatrick_scale:!1,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:'\u{1f932}',fitzpatrick_scale:!0,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:'\u{1f64c}',fitzpatrick_scale:!0,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:'\u{1f44f}',fitzpatrick_scale:!0,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:'\u{1f44b}',fitzpatrick_scale:!0,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:'\u{1f919}',fitzpatrick_scale:!0,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:'\u{1f44d}',fitzpatrick_scale:!0,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:'\u{1f44e}',fitzpatrick_scale:!0,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:'\u{1f44a}',fitzpatrick_scale:!0,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:'\u270a',fitzpatrick_scale:!0,category:"people"},fist_left:{keywords:["hand","fistbump"],char:'\u{1f91b}',fitzpatrick_scale:!0,category:"people"},fist_right:{keywords:["hand","fistbump"],char:'\u{1f91c}',fitzpatrick_scale:!0,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:'\u270c',fitzpatrick_scale:!0,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:'\u{1f44c}',fitzpatrick_scale:!0,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:'\u270b',fitzpatrick_scale:!0,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:'\u{1f91a}',fitzpatrick_scale:!0,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:'\u{1f450}',fitzpatrick_scale:!0,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:'\u{1f4aa}',fitzpatrick_scale:!0,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:'\u{1f64f}',fitzpatrick_scale:!0,category:"people"},foot:{keywords:["kick","stomp"],char:'\u{1f9b6}',fitzpatrick_scale:!0,category:"people"},leg:{keywords:["kick","limb"],char:'\u{1f9b5}',fitzpatrick_scale:!0,category:"people"},handshake:{keywords:["agreement","shake"],char:'\u{1f91d}',fitzpatrick_scale:!1,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:'\u261d',fitzpatrick_scale:!0,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:'\u{1f446}',fitzpatrick_scale:!0,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:'\u{1f447}',fitzpatrick_scale:!0,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:'\u{1f448}',fitzpatrick_scale:!0,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:'\u{1f449}',fitzpatrick_scale:!0,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:'\u{1f595}',fitzpatrick_scale:!0,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:'\u{1f590}',fitzpatrick_scale:!0,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:'\u{1f91f}',fitzpatrick_scale:!0,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:'\u{1f918}',fitzpatrick_scale:!0,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:'\u{1f91e}',fitzpatrick_scale:!0,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:'\u{1f596}',fitzpatrick_scale:!0,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:'\u270d',fitzpatrick_scale:!0,category:"people"},selfie:{keywords:["camera","phone"],char:'\u{1f933}',fitzpatrick_scale:!0,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:'\u{1f485}',fitzpatrick_scale:!0,category:"people"},lips:{keywords:["mouth","kiss"],char:'\u{1f444}',fitzpatrick_scale:!1,category:"people"},tooth:{keywords:["teeth","dentist"],char:'\u{1f9b7}',fitzpatrick_scale:!1,category:"people"},tongue:{keywords:["mouth","playful"],char:'\u{1f445}',fitzpatrick_scale:!1,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:'\u{1f442}',fitzpatrick_scale:!0,category:"people"},nose:{keywords:["smell","sniff"],char:'\u{1f443}',fitzpatrick_scale:!0,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:'\u{1f441}',fitzpatrick_scale:!1,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:'\u{1f440}',fitzpatrick_scale:!1,category:"people"},brain:{keywords:["smart","intelligent"],char:'\u{1f9e0}',fitzpatrick_scale:!1,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:'\u{1f464}',fitzpatrick_scale:!1,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:'\u{1f465}',fitzpatrick_scale:!1,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:'\u{1f5e3}',fitzpatrick_scale:!1,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:'\u{1f476}',fitzpatrick_scale:!0,category:"people"},child:{keywords:["gender-neutral","young"],char:'\u{1f9d2}',fitzpatrick_scale:!0,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:'\u{1f466}',fitzpatrick_scale:!0,category:"people"},girl:{keywords:["female","woman","teenager"],char:'\u{1f467}',fitzpatrick_scale:!0,category:"people"},adult:{keywords:["gender-neutral","person"],char:'\u{1f9d1}',fitzpatrick_scale:!0,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:'\u{1f468}',fitzpatrick_scale:!0,category:"people"},woman:{keywords:["female","girls","lady"],char:'\u{1f469}',fitzpatrick_scale:!0,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:'\u{1f471}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:'\u{1f471}',fitzpatrick_scale:!0,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:'\u{1f9d4}',fitzpatrick_scale:!0,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:'\u{1f9d3}',fitzpatrick_scale:!0,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:'\u{1f474}',fitzpatrick_scale:!0,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:'\u{1f475}',fitzpatrick_scale:!0,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:'\u{1f472}',fitzpatrick_scale:!0,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:'\u{1f9d5}',fitzpatrick_scale:!0,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:'\u{1f473}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:'\u{1f473}',fitzpatrick_scale:!0,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:'\u{1f46e}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:'\u{1f46e}',fitzpatrick_scale:!0,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:'\u{1f477}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:'\u{1f477}',fitzpatrick_scale:!0,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:'\u{1f482}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:'\u{1f482}',fitzpatrick_scale:!0,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:'\u{1f575}\ufe0f\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},male_detective:{keywords:["human","spy","detective"],char:'\u{1f575}',fitzpatrick_scale:!0,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:'\u{1f469}\u200d\u2695\ufe0f',fitzpatrick_scale:!0,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:'\u{1f468}\u200d\u2695\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:'\u{1f469}\u200d\u{1f33e}',fitzpatrick_scale:!0,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:'\u{1f468}\u200d\u{1f33e}',fitzpatrick_scale:!0,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:'\u{1f469}\u200d\u{1f373}',fitzpatrick_scale:!0,category:"people"},man_cook:{keywords:["chef","man","human"],char:'\u{1f468}\u200d\u{1f373}',fitzpatrick_scale:!0,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:'\u{1f469}\u200d\u{1f393}',fitzpatrick_scale:!0,category:"people"},man_student:{keywords:["graduate","man","human"],char:'\u{1f468}\u200d\u{1f393}',fitzpatrick_scale:!0,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:'\u{1f469}\u200d\u{1f3a4}',fitzpatrick_scale:!0,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:'\u{1f468}\u200d\u{1f3a4}',fitzpatrick_scale:!0,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:'\u{1f469}\u200d\u{1f3eb}',fitzpatrick_scale:!0,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:'\u{1f468}\u200d\u{1f3eb}',fitzpatrick_scale:!0,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:'\u{1f469}\u200d\u{1f3ed}',fitzpatrick_scale:!0,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:'\u{1f468}\u200d\u{1f3ed}',fitzpatrick_scale:!0,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:'\u{1f469}\u200d\u{1f4bb}',fitzpatrick_scale:!0,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:'\u{1f468}\u200d\u{1f4bb}',fitzpatrick_scale:!0,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:'\u{1f469}\u200d\u{1f4bc}',fitzpatrick_scale:!0,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:'\u{1f468}\u200d\u{1f4bc}',fitzpatrick_scale:!0,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:'\u{1f469}\u200d\u{1f527}',fitzpatrick_scale:!0,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:'\u{1f468}\u200d\u{1f527}',fitzpatrick_scale:!0,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:'\u{1f469}\u200d\u{1f52c}',fitzpatrick_scale:!0,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:'\u{1f468}\u200d\u{1f52c}',fitzpatrick_scale:!0,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:'\u{1f469}\u200d\u{1f3a8}',fitzpatrick_scale:!0,category:"people"},man_artist:{keywords:["painter","man","human"],char:'\u{1f468}\u200d\u{1f3a8}',fitzpatrick_scale:!0,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:'\u{1f469}\u200d\u{1f692}',fitzpatrick_scale:!0,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:'\u{1f468}\u200d\u{1f692}',fitzpatrick_scale:!0,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:'\u{1f469}\u200d\u2708\ufe0f',fitzpatrick_scale:!0,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:'\u{1f468}\u200d\u2708\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:'\u{1f469}\u200d\u{1f680}',fitzpatrick_scale:!0,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:'\u{1f468}\u200d\u{1f680}',fitzpatrick_scale:!0,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:'\u{1f469}\u200d\u2696\ufe0f',fitzpatrick_scale:!0,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:'\u{1f468}\u200d\u2696\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:'\u{1f9b8}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:'\u{1f9b8}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:'\u{1f9b9}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:'\u{1f9b9}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:'\u{1f936}',fitzpatrick_scale:!0,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:'\u{1f385}',fitzpatrick_scale:!0,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:'\u{1f9d9}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:'\u{1f9d9}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_elf:{keywords:["woman","female"],char:'\u{1f9dd}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_elf:{keywords:["man","male"],char:'\u{1f9dd}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_vampire:{keywords:["woman","female"],char:'\u{1f9db}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:'\u{1f9db}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:'\u{1f9df}\u200d\u2640\ufe0f',fitzpatrick_scale:!1,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:'\u{1f9df}\u200d\u2642\ufe0f',fitzpatrick_scale:!1,category:"people"},woman_genie:{keywords:["woman","female"],char:'\u{1f9de}\u200d\u2640\ufe0f',fitzpatrick_scale:!1,category:"people"},man_genie:{keywords:["man","male"],char:'\u{1f9de}\u200d\u2642\ufe0f',fitzpatrick_scale:!1,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:'\u{1f9dc}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},merman:{keywords:["man","male","triton"],char:'\u{1f9dc}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_fairy:{keywords:["woman","female"],char:'\u{1f9da}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_fairy:{keywords:["man","male"],char:'\u{1f9da}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},angel:{keywords:["heaven","wings","halo"],char:'\u{1f47c}',fitzpatrick_scale:!0,category:"people"},pregnant_woman:{keywords:["baby"],char:'\u{1f930}',fitzpatrick_scale:!0,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:'\u{1f931}',fitzpatrick_scale:!0,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:'\u{1f478}',fitzpatrick_scale:!0,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:'\u{1f934}',fitzpatrick_scale:!0,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:'\u{1f470}',fitzpatrick_scale:!0,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:'\u{1f935}',fitzpatrick_scale:!0,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:'\u{1f3c3}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:'\u{1f3c3}',fitzpatrick_scale:!0,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:'\u{1f6b6}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},walking_man:{keywords:["human","feet","steps"],char:'\u{1f6b6}',fitzpatrick_scale:!0,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:'\u{1f483}',fitzpatrick_scale:!0,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:'\u{1f57a}',fitzpatrick_scale:!0,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:'\u{1f46f}',fitzpatrick_scale:!1,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:'\u{1f46f}\u200d\u2642\ufe0f',fitzpatrick_scale:!1,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:'\u{1f46b}',fitzpatrick_scale:!1,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:'\u{1f46c}',fitzpatrick_scale:!1,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:'\u{1f46d}',fitzpatrick_scale:!1,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:'\u{1f647}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},bowing_man:{keywords:["man","male","boy"],char:'\u{1f647}',fitzpatrick_scale:!0,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:'\u{1f926}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:'\u{1f926}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:'\u{1f937}',fitzpatrick_scale:!0,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:'\u{1f937}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:'\u{1f481}',fitzpatrick_scale:!0,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:'\u{1f481}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:'\u{1f645}',fitzpatrick_scale:!0,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:'\u{1f645}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:'\u{1f646}',fitzpatrick_scale:!0,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:'\u{1f646}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:'\u{1f64b}',fitzpatrick_scale:!0,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:'\u{1f64b}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:'\u{1f64e}',fitzpatrick_scale:!0,category:"people"},pouting_man:{keywords:["male","boy","man"],char:'\u{1f64e}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:'\u{1f64d}',fitzpatrick_scale:!0,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:'\u{1f64d}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:'\u{1f487}',fitzpatrick_scale:!0,category:"people"},haircut_man:{keywords:["male","boy","man"],char:'\u{1f487}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:'\u{1f486}',fitzpatrick_scale:!0,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:'\u{1f486}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:'\u{1f9d6}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:'\u{1f9d6}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'\u{1f491}',fitzpatrick_scale:!1,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'\u{1f469}\u200d\u2764\ufe0f\u200d\u{1f469}',fitzpatrick_scale:!1,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'\u{1f468}\u200d\u2764\ufe0f\u200d\u{1f468}',fitzpatrick_scale:!1,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'\u{1f48f}',fitzpatrick_scale:!1,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'\u{1f469}\u200d\u2764\ufe0f\u200d\u{1f48b}\u200d\u{1f469}',fitzpatrick_scale:!1,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:'\u{1f468}\u200d\u2764\ufe0f\u200d\u{1f48b}\u200d\u{1f468}',fitzpatrick_scale:!1,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:'\u{1f46a}',fitzpatrick_scale:!1,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:'\u{1f468}\u200d\u{1f469}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f469}\u200d\u{1f466}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f469}\u200d\u{1f469}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:'\u{1f469}\u200d\u{1f469}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f469}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f469}\u200d\u{1f469}\u200d\u{1f466}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'\u{1f469}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f468}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f468}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f468}\u200d\u{1f467}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f468}\u200d\u{1f466}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:'\u{1f468}\u200d\u{1f468}\u200d\u{1f467}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:'\u{1f469}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:'\u{1f469}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:'\u{1f469}\u200d\u{1f467}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:'\u{1f469}\u200d\u{1f466}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:'\u{1f469}\u200d\u{1f467}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:'\u{1f468}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:'\u{1f468}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:'\u{1f468}\u200d\u{1f467}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:'\u{1f468}\u200d\u{1f466}\u200d\u{1f466}',fitzpatrick_scale:!1,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:'\u{1f468}\u200d\u{1f467}\u200d\u{1f467}',fitzpatrick_scale:!1,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:'\u{1f9f6}',fitzpatrick_scale:!1,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:'\u{1f9f5}',fitzpatrick_scale:!1,category:"people"},coat:{keywords:["jacket"],char:'\u{1f9e5}',fitzpatrick_scale:!1,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:'\u{1f97c}',fitzpatrick_scale:!1,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:'\u{1f45a}',fitzpatrick_scale:!1,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:'\u{1f455}',fitzpatrick_scale:!1,category:"people"},jeans:{keywords:["fashion","shopping"],char:'\u{1f456}',fitzpatrick_scale:!1,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:'\u{1f454}',fitzpatrick_scale:!1,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:'\u{1f457}',fitzpatrick_scale:!1,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:'\u{1f459}',fitzpatrick_scale:!1,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:'\u{1f458}',fitzpatrick_scale:!1,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:'\u{1f484}',fitzpatrick_scale:!1,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:'\u{1f48b}',fitzpatrick_scale:!1,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:'\u{1f463}',fitzpatrick_scale:!1,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:'\u{1f97f}',fitzpatrick_scale:!1,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:'\u{1f460}',fitzpatrick_scale:!1,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:'\u{1f461}',fitzpatrick_scale:!1,category:"people"},boot:{keywords:["shoes","fashion"],char:'\u{1f462}',fitzpatrick_scale:!1,category:"people"},mans_shoe:{keywords:["fashion","male"],char:'\u{1f45e}',fitzpatrick_scale:!1,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:'\u{1f45f}',fitzpatrick_scale:!1,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:'\u{1f97e}',fitzpatrick_scale:!1,category:"people"},socks:{keywords:["stockings","clothes"],char:'\u{1f9e6}',fitzpatrick_scale:!1,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:'\u{1f9e4}',fitzpatrick_scale:!1,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:'\u{1f9e3}',fitzpatrick_scale:!1,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:'\u{1f452}',fitzpatrick_scale:!1,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:'\u{1f3a9}',fitzpatrick_scale:!1,category:"people"},billed_hat:{keywords:["cap","baseball"],char:'\u{1f9e2}',fitzpatrick_scale:!1,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:'\u26d1',fitzpatrick_scale:!1,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:'\u{1f393}',fitzpatrick_scale:!1,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:'\u{1f451}',fitzpatrick_scale:!1,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:'\u{1f392}',fitzpatrick_scale:!1,category:"people"},luggage:{keywords:["packing","travel"],char:'\u{1f9f3}',fitzpatrick_scale:!1,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:'\u{1f45d}',fitzpatrick_scale:!1,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:'\u{1f45b}',fitzpatrick_scale:!1,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:'\u{1f45c}',fitzpatrick_scale:!1,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:'\u{1f4bc}',fitzpatrick_scale:!1,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:'\u{1f453}',fitzpatrick_scale:!1,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:'\u{1f576}',fitzpatrick_scale:!1,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:'\u{1f97d}',fitzpatrick_scale:!1,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:'\u{1f48d}',fitzpatrick_scale:!1,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:'\u{1f302}',fitzpatrick_scale:!1,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:'\u{1f436}',fitzpatrick_scale:!1,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:'\u{1f431}',fitzpatrick_scale:!1,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:'\u{1f42d}',fitzpatrick_scale:!1,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:'\u{1f439}',fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:'\u{1f430}',fitzpatrick_scale:!1,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:'\u{1f98a}',fitzpatrick_scale:!1,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:'\u{1f43b}',fitzpatrick_scale:!1,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:'\u{1f43c}',fitzpatrick_scale:!1,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:'\u{1f428}',fitzpatrick_scale:!1,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:'\u{1f42f}',fitzpatrick_scale:!1,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:'\u{1f981}',fitzpatrick_scale:!1,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:'\u{1f42e}',fitzpatrick_scale:!1,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:'\u{1f437}',fitzpatrick_scale:!1,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:'\u{1f43d}',fitzpatrick_scale:!1,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:'\u{1f438}',fitzpatrick_scale:!1,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:'\u{1f991}',fitzpatrick_scale:!1,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:'\u{1f419}',fitzpatrick_scale:!1,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:'\u{1f990}',fitzpatrick_scale:!1,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:'\u{1f435}',fitzpatrick_scale:!1,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:'\u{1f98d}',fitzpatrick_scale:!1,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:'\u{1f648}',fitzpatrick_scale:!1,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:'\u{1f649}',fitzpatrick_scale:!1,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:'\u{1f64a}',fitzpatrick_scale:!1,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:'\u{1f412}',fitzpatrick_scale:!1,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:'\u{1f414}',fitzpatrick_scale:!1,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:'\u{1f427}',fitzpatrick_scale:!1,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:'\u{1f426}',fitzpatrick_scale:!1,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:'\u{1f424}',fitzpatrick_scale:!1,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:'\u{1f423}',fitzpatrick_scale:!1,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:'\u{1f425}',fitzpatrick_scale:!1,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:'\u{1f986}',fitzpatrick_scale:!1,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:'\u{1f985}',fitzpatrick_scale:!1,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:'\u{1f989}',fitzpatrick_scale:!1,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:'\u{1f987}',fitzpatrick_scale:!1,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:'\u{1f43a}',fitzpatrick_scale:!1,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:'\u{1f417}',fitzpatrick_scale:!1,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:'\u{1f434}',fitzpatrick_scale:!1,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:'\u{1f984}',fitzpatrick_scale:!1,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:'\u{1f41d}',fitzpatrick_scale:!1,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:'\u{1f41b}',fitzpatrick_scale:!1,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:'\u{1f98b}',fitzpatrick_scale:!1,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:'\u{1f40c}',fitzpatrick_scale:!1,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:'\u{1f41e}',fitzpatrick_scale:!1,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:'\u{1f41c}',fitzpatrick_scale:!1,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:'\u{1f997}',fitzpatrick_scale:!1,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:'\u{1f577}',fitzpatrick_scale:!1,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:'\u{1f982}',fitzpatrick_scale:!1,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:'\u{1f980}',fitzpatrick_scale:!1,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:'\u{1f40d}',fitzpatrick_scale:!1,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:'\u{1f98e}',fitzpatrick_scale:!1,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:'\u{1f996}',fitzpatrick_scale:!1,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:'\u{1f995}',fitzpatrick_scale:!1,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:'\u{1f422}',fitzpatrick_scale:!1,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:'\u{1f420}',fitzpatrick_scale:!1,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:'\u{1f41f}',fitzpatrick_scale:!1,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:'\u{1f421}',fitzpatrick_scale:!1,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:'\u{1f42c}',fitzpatrick_scale:!1,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:'\u{1f988}',fitzpatrick_scale:!1,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:'\u{1f433}',fitzpatrick_scale:!1,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:'\u{1f40b}',fitzpatrick_scale:!1,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:'\u{1f40a}',fitzpatrick_scale:!1,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:'\u{1f406}',fitzpatrick_scale:!1,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:'\u{1f993}',fitzpatrick_scale:!1,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:'\u{1f405}',fitzpatrick_scale:!1,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:'\u{1f403}',fitzpatrick_scale:!1,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:'\u{1f402}',fitzpatrick_scale:!1,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:'\u{1f404}',fitzpatrick_scale:!1,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:'\u{1f98c}',fitzpatrick_scale:!1,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:'\u{1f42a}',fitzpatrick_scale:!1,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:'\u{1f42b}',fitzpatrick_scale:!1,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:'\u{1f992}',fitzpatrick_scale:!1,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:'\u{1f418}',fitzpatrick_scale:!1,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:'\u{1f98f}',fitzpatrick_scale:!1,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:'\u{1f410}',fitzpatrick_scale:!1,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:'\u{1f40f}',fitzpatrick_scale:!1,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:'\u{1f411}',fitzpatrick_scale:!1,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:'\u{1f40e}',fitzpatrick_scale:!1,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:'\u{1f416}',fitzpatrick_scale:!1,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:'\u{1f400}',fitzpatrick_scale:!1,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:'\u{1f401}',fitzpatrick_scale:!1,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:'\u{1f413}',fitzpatrick_scale:!1,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:'\u{1f983}',fitzpatrick_scale:!1,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:'\u{1f54a}',fitzpatrick_scale:!1,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:'\u{1f415}',fitzpatrick_scale:!1,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:'\u{1f429}',fitzpatrick_scale:!1,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:'\u{1f408}',fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:'\u{1f407}',fitzpatrick_scale:!1,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:'\u{1f43f}',fitzpatrick_scale:!1,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:'\u{1f994}',fitzpatrick_scale:!1,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:'\u{1f99d}',fitzpatrick_scale:!1,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:'\u{1f999}',fitzpatrick_scale:!1,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:'\u{1f99b}',fitzpatrick_scale:!1,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:'\u{1f998}',fitzpatrick_scale:!1,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:'\u{1f9a1}',fitzpatrick_scale:!1,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:'\u{1f9a2}',fitzpatrick_scale:!1,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:'\u{1f99a}',fitzpatrick_scale:!1,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:'\u{1f99c}',fitzpatrick_scale:!1,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:'\u{1f99e}',fitzpatrick_scale:!1,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:'\u{1f99f}',fitzpatrick_scale:!1,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:'\u{1f43e}',fitzpatrick_scale:!1,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:'\u{1f409}',fitzpatrick_scale:!1,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:'\u{1f432}',fitzpatrick_scale:!1,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:'\u{1f335}',fitzpatrick_scale:!1,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:'\u{1f384}',fitzpatrick_scale:!1,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:'\u{1f332}',fitzpatrick_scale:!1,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:'\u{1f333}',fitzpatrick_scale:!1,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:'\u{1f334}',fitzpatrick_scale:!1,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:'\u{1f331}',fitzpatrick_scale:!1,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:'\u{1f33f}',fitzpatrick_scale:!1,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:'\u2618',fitzpatrick_scale:!1,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:'\u{1f340}',fitzpatrick_scale:!1,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:'\u{1f38d}',fitzpatrick_scale:!1,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:'\u{1f38b}',fitzpatrick_scale:!1,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:'\u{1f343}',fitzpatrick_scale:!1,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:'\u{1f342}',fitzpatrick_scale:!1,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:'\u{1f341}',fitzpatrick_scale:!1,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:'\u{1f33e}',fitzpatrick_scale:!1,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:'\u{1f33a}',fitzpatrick_scale:!1,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:'\u{1f33b}',fitzpatrick_scale:!1,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:'\u{1f339}',fitzpatrick_scale:!1,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:'\u{1f940}',fitzpatrick_scale:!1,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:'\u{1f337}',fitzpatrick_scale:!1,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:'\u{1f33c}',fitzpatrick_scale:!1,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:'\u{1f338}',fitzpatrick_scale:!1,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:'\u{1f490}',fitzpatrick_scale:!1,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:'\u{1f344}',fitzpatrick_scale:!1,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:'\u{1f330}',fitzpatrick_scale:!1,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:'\u{1f383}',fitzpatrick_scale:!1,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:'\u{1f41a}',fitzpatrick_scale:!1,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:'\u{1f578}',fitzpatrick_scale:!1,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:'\u{1f30e}',fitzpatrick_scale:!1,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:'\u{1f30d}',fitzpatrick_scale:!1,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:'\u{1f30f}',fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:'\u{1f315}',fitzpatrick_scale:!1,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:'\u{1f316}',fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f317}',fitzpatrick_scale:!1,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f318}',fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f311}',fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f312}',fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f313}',fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:'\u{1f314}',fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f31a}',fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f31d}',fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f31b}',fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'\u{1f31c}',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:'\u{1f31e}',fitzpatrick_scale:!1,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:'\u{1f319}',fitzpatrick_scale:!1,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:'\u2b50',fitzpatrick_scale:!1,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:'\u{1f31f}',fitzpatrick_scale:!1,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:'\u{1f4ab}',fitzpatrick_scale:!1,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:'\u2728',fitzpatrick_scale:!1,category:"animals_and_nature"},comet:{keywords:["space"],char:'\u2604',fitzpatrick_scale:!1,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:'\u2600\ufe0f',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:'\u{1f324}',fitzpatrick_scale:!1,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:'\u26c5',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:'\u{1f325}',fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:'\u{1f326}',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:'\u2601\ufe0f',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:'\u{1f327}',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:'\u26c8',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:'\u{1f329}',fitzpatrick_scale:!1,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:'\u26a1',fitzpatrick_scale:!1,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:'\u{1f525}',fitzpatrick_scale:!1,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:'\u{1f4a5}',fitzpatrick_scale:!1,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:'\u2744\ufe0f',fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:'\u{1f328}',fitzpatrick_scale:!1,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:'\u26c4',fitzpatrick_scale:!1,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:'\u2603',fitzpatrick_scale:!1,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:'\u{1f32c}',fitzpatrick_scale:!1,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:'\u{1f4a8}',fitzpatrick_scale:!1,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:'\u{1f32a}',fitzpatrick_scale:!1,category:"animals_and_nature"},fog:{keywords:["weather"],char:'\u{1f32b}',fitzpatrick_scale:!1,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:'\u2602',fitzpatrick_scale:!1,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:'\u2614',fitzpatrick_scale:!1,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:'\u{1f4a7}',fitzpatrick_scale:!1,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:'\u{1f4a6}',fitzpatrick_scale:!1,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:'\u{1f30a}',fitzpatrick_scale:!1,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:'\u{1f34f}',fitzpatrick_scale:!1,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:'\u{1f34e}',fitzpatrick_scale:!1,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:'\u{1f350}',fitzpatrick_scale:!1,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:'\u{1f34a}',fitzpatrick_scale:!1,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:'\u{1f34b}',fitzpatrick_scale:!1,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:'\u{1f34c}',fitzpatrick_scale:!1,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:'\u{1f349}',fitzpatrick_scale:!1,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:'\u{1f347}',fitzpatrick_scale:!1,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:'\u{1f353}',fitzpatrick_scale:!1,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:'\u{1f348}',fitzpatrick_scale:!1,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:'\u{1f352}',fitzpatrick_scale:!1,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:'\u{1f351}',fitzpatrick_scale:!1,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:'\u{1f34d}',fitzpatrick_scale:!1,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:'\u{1f965}',fitzpatrick_scale:!1,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:'\u{1f95d}',fitzpatrick_scale:!1,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:'\u{1f96d}',fitzpatrick_scale:!1,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:'\u{1f951}',fitzpatrick_scale:!1,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:'\u{1f966}',fitzpatrick_scale:!1,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:'\u{1f345}',fitzpatrick_scale:!1,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:'\u{1f346}',fitzpatrick_scale:!1,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:'\u{1f952}',fitzpatrick_scale:!1,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:'\u{1f955}',fitzpatrick_scale:!1,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:'\u{1f336}',fitzpatrick_scale:!1,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:'\u{1f954}',fitzpatrick_scale:!1,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:'\u{1f33d}',fitzpatrick_scale:!1,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:'\u{1f96c}',fitzpatrick_scale:!1,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:'\u{1f360}',fitzpatrick_scale:!1,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:'\u{1f95c}',fitzpatrick_scale:!1,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:'\u{1f36f}',fitzpatrick_scale:!1,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:'\u{1f950}',fitzpatrick_scale:!1,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:'\u{1f35e}',fitzpatrick_scale:!1,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:'\u{1f956}',fitzpatrick_scale:!1,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:'\u{1f96f}',fitzpatrick_scale:!1,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:'\u{1f968}',fitzpatrick_scale:!1,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:'\u{1f9c0}',fitzpatrick_scale:!1,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:'\u{1f95a}',fitzpatrick_scale:!1,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:'\u{1f953}',fitzpatrick_scale:!1,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:'\u{1f969}',fitzpatrick_scale:!1,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:'\u{1f95e}',fitzpatrick_scale:!1,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:'\u{1f357}',fitzpatrick_scale:!1,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:'\u{1f356}',fitzpatrick_scale:!1,category:"food_and_drink"},bone:{keywords:["skeleton"],char:'\u{1f9b4}',fitzpatrick_scale:!1,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:'\u{1f364}',fitzpatrick_scale:!1,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:'\u{1f373}',fitzpatrick_scale:!1,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:'\u{1f354}',fitzpatrick_scale:!1,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:'\u{1f35f}',fitzpatrick_scale:!1,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:'\u{1f959}',fitzpatrick_scale:!1,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:'\u{1f32d}',fitzpatrick_scale:!1,category:"food_and_drink"},pizza:{keywords:["food","party"],char:'\u{1f355}',fitzpatrick_scale:!1,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:'\u{1f96a}',fitzpatrick_scale:!1,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:'\u{1f96b}',fitzpatrick_scale:!1,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:'\u{1f35d}',fitzpatrick_scale:!1,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:'\u{1f32e}',fitzpatrick_scale:!1,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:'\u{1f32f}',fitzpatrick_scale:!1,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:'\u{1f957}',fitzpatrick_scale:!1,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:'\u{1f958}',fitzpatrick_scale:!1,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:'\u{1f35c}',fitzpatrick_scale:!1,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:'\u{1f372}',fitzpatrick_scale:!1,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:'\u{1f365}',fitzpatrick_scale:!1,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:'\u{1f960}',fitzpatrick_scale:!1,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:'\u{1f363}',fitzpatrick_scale:!1,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:'\u{1f371}',fitzpatrick_scale:!1,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:'\u{1f35b}',fitzpatrick_scale:!1,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:'\u{1f359}',fitzpatrick_scale:!1,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:'\u{1f35a}',fitzpatrick_scale:!1,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:'\u{1f358}',fitzpatrick_scale:!1,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:'\u{1f362}',fitzpatrick_scale:!1,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:'\u{1f361}',fitzpatrick_scale:!1,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:'\u{1f367}',fitzpatrick_scale:!1,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:'\u{1f368}',fitzpatrick_scale:!1,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:'\u{1f366}',fitzpatrick_scale:!1,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:'\u{1f967}',fitzpatrick_scale:!1,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:'\u{1f370}',fitzpatrick_scale:!1,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:'\u{1f9c1}',fitzpatrick_scale:!1,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:'\u{1f96e}',fitzpatrick_scale:!1,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:'\u{1f382}',fitzpatrick_scale:!1,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:'\u{1f36e}',fitzpatrick_scale:!1,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:'\u{1f36c}',fitzpatrick_scale:!1,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:'\u{1f36d}',fitzpatrick_scale:!1,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:'\u{1f36b}',fitzpatrick_scale:!1,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:'\u{1f37f}',fitzpatrick_scale:!1,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:'\u{1f95f}',fitzpatrick_scale:!1,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:'\u{1f369}',fitzpatrick_scale:!1,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:'\u{1f36a}',fitzpatrick_scale:!1,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:'\u{1f95b}',fitzpatrick_scale:!1,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'\u{1f37a}',fitzpatrick_scale:!1,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'\u{1f37b}',fitzpatrick_scale:!1,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:'\u{1f942}',fitzpatrick_scale:!1,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:'\u{1f377}',fitzpatrick_scale:!1,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:'\u{1f943}',fitzpatrick_scale:!1,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:'\u{1f378}',fitzpatrick_scale:!1,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:'\u{1f379}',fitzpatrick_scale:!1,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:'\u{1f37e}',fitzpatrick_scale:!1,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:'\u{1f376}',fitzpatrick_scale:!1,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:'\u{1f375}',fitzpatrick_scale:!1,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:'\u{1f964}',fitzpatrick_scale:!1,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:'\u2615',fitzpatrick_scale:!1,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:'\u{1f37c}',fitzpatrick_scale:!1,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:'\u{1f9c2}',fitzpatrick_scale:!1,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:'\u{1f944}',fitzpatrick_scale:!1,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:'\u{1f374}',fitzpatrick_scale:!1,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:'\u{1f37d}',fitzpatrick_scale:!1,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:'\u{1f963}',fitzpatrick_scale:!1,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:'\u{1f961}',fitzpatrick_scale:!1,category:"food_and_drink"},chopsticks:{keywords:["food"],char:'\u{1f962}',fitzpatrick_scale:!1,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:'\u26bd',fitzpatrick_scale:!1,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:'\u{1f3c0}',fitzpatrick_scale:!1,category:"activity"},football:{keywords:["sports","balls","NFL"],char:'\u{1f3c8}',fitzpatrick_scale:!1,category:"activity"},baseball:{keywords:["sports","balls"],char:'\u26be',fitzpatrick_scale:!1,category:"activity"},softball:{keywords:["sports","balls"],char:'\u{1f94e}',fitzpatrick_scale:!1,category:"activity"},tennis:{keywords:["sports","balls","green"],char:'\u{1f3be}',fitzpatrick_scale:!1,category:"activity"},volleyball:{keywords:["sports","balls"],char:'\u{1f3d0}',fitzpatrick_scale:!1,category:"activity"},rugby_football:{keywords:["sports","team"],char:'\u{1f3c9}',fitzpatrick_scale:!1,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:'\u{1f94f}',fitzpatrick_scale:!1,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:'\u{1f3b1}',fitzpatrick_scale:!1,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:'\u26f3',fitzpatrick_scale:!1,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:'\u{1f3cc}\ufe0f\u200d\u2640\ufe0f',fitzpatrick_scale:!1,category:"activity"},golfing_man:{keywords:["sports","business"],char:'\u{1f3cc}',fitzpatrick_scale:!0,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:'\u{1f3d3}',fitzpatrick_scale:!1,category:"activity"},badminton:{keywords:["sports"],char:'\u{1f3f8}',fitzpatrick_scale:!1,category:"activity"},goal_net:{keywords:["sports"],char:'\u{1f945}',fitzpatrick_scale:!1,category:"activity"},ice_hockey:{keywords:["sports"],char:'\u{1f3d2}',fitzpatrick_scale:!1,category:"activity"},field_hockey:{keywords:["sports"],char:'\u{1f3d1}',fitzpatrick_scale:!1,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:'\u{1f94d}',fitzpatrick_scale:!1,category:"activity"},cricket:{keywords:["sports"],char:'\u{1f3cf}',fitzpatrick_scale:!1,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:'\u{1f3bf}',fitzpatrick_scale:!1,category:"activity"},skier:{keywords:["sports","winter","snow"],char:'\u26f7',fitzpatrick_scale:!1,category:"activity"},snowboarder:{keywords:["sports","winter"],char:'\u{1f3c2}',fitzpatrick_scale:!0,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:'\u{1f93a}',fitzpatrick_scale:!1,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:'\u{1f93c}\u200d\u2640\ufe0f',fitzpatrick_scale:!1,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:'\u{1f93c}\u200d\u2642\ufe0f',fitzpatrick_scale:!1,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:'\u{1f938}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:'\u{1f938}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},woman_playing_handball:{keywords:["sports"],char:'\u{1f93e}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},man_playing_handball:{keywords:["sports"],char:'\u{1f93e}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},ice_skate:{keywords:["sports"],char:'\u26f8',fitzpatrick_scale:!1,category:"activity"},curling_stone:{keywords:["sports"],char:'\u{1f94c}',fitzpatrick_scale:!1,category:"activity"},skateboard:{keywords:["board"],char:'\u{1f6f9}',fitzpatrick_scale:!1,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:'\u{1f6f7}',fitzpatrick_scale:!1,category:"activity"},bow_and_arrow:{keywords:["sports"],char:'\u{1f3f9}',fitzpatrick_scale:!1,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:'\u{1f3a3}',fitzpatrick_scale:!1,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:'\u{1f94a}',fitzpatrick_scale:!1,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:'\u{1f94b}',fitzpatrick_scale:!1,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:'\u{1f6a3}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:'\u{1f6a3}',fitzpatrick_scale:!0,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:'\u{1f9d7}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:'\u{1f9d7}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:'\u{1f3ca}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:'\u{1f3ca}',fitzpatrick_scale:!0,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:'\u{1f93d}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:'\u{1f93d}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:'\u{1f9d8}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:'\u{1f9d8}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:'\u{1f3c4}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:'\u{1f3c4}',fitzpatrick_scale:!0,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:'\u{1f6c0}',fitzpatrick_scale:!0,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:'\u26f9\ufe0f\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},basketball_man:{keywords:["sports","human"],char:'\u26f9',fitzpatrick_scale:!0,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:'\u{1f3cb}\ufe0f\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:'\u{1f3cb}',fitzpatrick_scale:!0,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:'\u{1f6b4}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:'\u{1f6b4}',fitzpatrick_scale:!0,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:'\u{1f6b5}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:'\u{1f6b5}',fitzpatrick_scale:!0,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:'\u{1f3c7}',fitzpatrick_scale:!0,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:'\u{1f574}',fitzpatrick_scale:!0,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:'\u{1f3c6}',fitzpatrick_scale:!1,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:'\u{1f3bd}',fitzpatrick_scale:!1,category:"activity"},medal_sports:{keywords:["award","winning"],char:'\u{1f3c5}',fitzpatrick_scale:!1,category:"activity"},medal_military:{keywords:["award","winning","army"],char:'\u{1f396}',fitzpatrick_scale:!1,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:'\u{1f947}',fitzpatrick_scale:!1,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:'\u{1f948}',fitzpatrick_scale:!1,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:'\u{1f949}',fitzpatrick_scale:!1,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:'\u{1f397}',fitzpatrick_scale:!1,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:'\u{1f3f5}',fitzpatrick_scale:!1,category:"activity"},ticket:{keywords:["event","concert","pass"],char:'\u{1f3ab}',fitzpatrick_scale:!1,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:'\u{1f39f}',fitzpatrick_scale:!1,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:'\u{1f3ad}',fitzpatrick_scale:!1,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:'\u{1f3a8}',fitzpatrick_scale:!1,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:'\u{1f3aa}',fitzpatrick_scale:!1,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:'\u{1f939}\u200d\u2640\ufe0f',fitzpatrick_scale:!0,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:'\u{1f939}\u200d\u2642\ufe0f',fitzpatrick_scale:!0,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:'\u{1f3a4}',fitzpatrick_scale:!1,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:'\u{1f3a7}',fitzpatrick_scale:!1,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:'\u{1f3bc}',fitzpatrick_scale:!1,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:'\u{1f3b9}',fitzpatrick_scale:!1,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:'\u{1f941}',fitzpatrick_scale:!1,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:'\u{1f3b7}',fitzpatrick_scale:!1,category:"activity"},trumpet:{keywords:["music","brass"],char:'\u{1f3ba}',fitzpatrick_scale:!1,category:"activity"},guitar:{keywords:["music","instrument"],char:'\u{1f3b8}',fitzpatrick_scale:!1,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:'\u{1f3bb}',fitzpatrick_scale:!1,category:"activity"},clapper:{keywords:["movie","film","record"],char:'\u{1f3ac}',fitzpatrick_scale:!1,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:'\u{1f3ae}',fitzpatrick_scale:!1,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:'\u{1f47e}',fitzpatrick_scale:!1,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:'\u{1f3af}',fitzpatrick_scale:!1,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:'\u{1f3b2}',fitzpatrick_scale:!1,category:"activity"},chess_pawn:{keywords:["expendable"],char:"\u265f",fitzpatrick_scale:!1,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:'\u{1f3b0}',fitzpatrick_scale:!1,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:'\u{1f9e9}',fitzpatrick_scale:!1,category:"activity"},bowling:{keywords:["sports","fun","play"],char:'\u{1f3b3}',fitzpatrick_scale:!1,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:'\u{1f697}',fitzpatrick_scale:!1,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:'\u{1f695}',fitzpatrick_scale:!1,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:'\u{1f699}',fitzpatrick_scale:!1,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:'\u{1f68c}',fitzpatrick_scale:!1,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:'\u{1f68e}',fitzpatrick_scale:!1,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:'\u{1f3ce}',fitzpatrick_scale:!1,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:'\u{1f693}',fitzpatrick_scale:!1,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:'\u{1f691}',fitzpatrick_scale:!1,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:'\u{1f692}',fitzpatrick_scale:!1,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:'\u{1f690}',fitzpatrick_scale:!1,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:'\u{1f69a}',fitzpatrick_scale:!1,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:'\u{1f69b}',fitzpatrick_scale:!1,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:'\u{1f69c}',fitzpatrick_scale:!1,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:'\u{1f6f4}',fitzpatrick_scale:!1,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:'\u{1f3cd}',fitzpatrick_scale:!1,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:'\u{1f6b2}',fitzpatrick_scale:!1,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:'\u{1f6f5}',fitzpatrick_scale:!1,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:'\u{1f6a8}',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:'\u{1f694}',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:'\u{1f68d}',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:'\u{1f698}',fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:'\u{1f696}',fitzpatrick_scale:!1,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:'\u{1f6a1}',fitzpatrick_scale:!1,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:'\u{1f6a0}',fitzpatrick_scale:!1,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:'\u{1f69f}',fitzpatrick_scale:!1,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:'\u{1f683}',fitzpatrick_scale:!1,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:'\u{1f68b}',fitzpatrick_scale:!1,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:'\u{1f69d}',fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:'\u{1f684}',fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:'\u{1f685}',fitzpatrick_scale:!1,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:'\u{1f688}',fitzpatrick_scale:!1,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:'\u{1f69e}',fitzpatrick_scale:!1,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:'\u{1f682}',fitzpatrick_scale:!1,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:'\u{1f686}',fitzpatrick_scale:!1,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:'\u{1f687}',fitzpatrick_scale:!1,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:'\u{1f68a}',fitzpatrick_scale:!1,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:'\u{1f689}',fitzpatrick_scale:!1,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:'\u{1f6f8}',fitzpatrick_scale:!1,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:'\u{1f681}',fitzpatrick_scale:!1,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:'\u{1f6e9}',fitzpatrick_scale:!1,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:'\u2708\ufe0f',fitzpatrick_scale:!1,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:'\u{1f6eb}',fitzpatrick_scale:!1,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:'\u{1f6ec}',fitzpatrick_scale:!1,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:'\u26f5',fitzpatrick_scale:!1,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:'\u{1f6e5}',fitzpatrick_scale:!1,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:'\u{1f6a4}',fitzpatrick_scale:!1,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:'\u26f4',fitzpatrick_scale:!1,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:'\u{1f6f3}',fitzpatrick_scale:!1,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:'\u{1f680}',fitzpatrick_scale:!1,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:'\u{1f6f0}',fitzpatrick_scale:!1,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:'\u{1f4ba}',fitzpatrick_scale:!1,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:'\u{1f6f6}',fitzpatrick_scale:!1,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:'\u2693',fitzpatrick_scale:!1,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:'\u{1f6a7}',fitzpatrick_scale:!1,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:'\u26fd',fitzpatrick_scale:!1,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:'\u{1f68f}',fitzpatrick_scale:!1,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:'\u{1f6a6}',fitzpatrick_scale:!1,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:'\u{1f6a5}',fitzpatrick_scale:!1,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:'\u{1f3c1}',fitzpatrick_scale:!1,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:'\u{1f6a2}',fitzpatrick_scale:!1,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:'\u{1f3a1}',fitzpatrick_scale:!1,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:'\u{1f3a2}',fitzpatrick_scale:!1,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:'\u{1f3a0}',fitzpatrick_scale:!1,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:'\u{1f3d7}',fitzpatrick_scale:!1,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:'\u{1f301}',fitzpatrick_scale:!1,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:'\u{1f5fc}',fitzpatrick_scale:!1,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:'\u{1f3ed}',fitzpatrick_scale:!1,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:'\u26f2',fitzpatrick_scale:!1,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:'\u{1f391}',fitzpatrick_scale:!1,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:'\u26f0',fitzpatrick_scale:!1,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:'\u{1f3d4}',fitzpatrick_scale:!1,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:'\u{1f5fb}',fitzpatrick_scale:!1,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:'\u{1f30b}',fitzpatrick_scale:!1,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:'\u{1f5fe}',fitzpatrick_scale:!1,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:'\u{1f3d5}',fitzpatrick_scale:!1,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:'\u26fa',fitzpatrick_scale:!1,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:'\u{1f3de}',fitzpatrick_scale:!1,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:'\u{1f6e3}',fitzpatrick_scale:!1,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:'\u{1f6e4}',fitzpatrick_scale:!1,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:'\u{1f305}',fitzpatrick_scale:!1,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:'\u{1f304}',fitzpatrick_scale:!1,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:'\u{1f3dc}',fitzpatrick_scale:!1,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:'\u{1f3d6}',fitzpatrick_scale:!1,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:'\u{1f3dd}',fitzpatrick_scale:!1,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:'\u{1f307}',fitzpatrick_scale:!1,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:'\u{1f306}',fitzpatrick_scale:!1,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:'\u{1f3d9}',fitzpatrick_scale:!1,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:'\u{1f303}',fitzpatrick_scale:!1,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:'\u{1f309}',fitzpatrick_scale:!1,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:'\u{1f30c}',fitzpatrick_scale:!1,category:"travel_and_places"},stars:{keywords:["night","photo"],char:'\u{1f320}',fitzpatrick_scale:!1,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:'\u{1f387}',fitzpatrick_scale:!1,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:'\u{1f386}',fitzpatrick_scale:!1,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:'\u{1f308}',fitzpatrick_scale:!1,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:'\u{1f3d8}',fitzpatrick_scale:!1,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:'\u{1f3f0}',fitzpatrick_scale:!1,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:'\u{1f3ef}',fitzpatrick_scale:!1,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:'\u{1f3df}',fitzpatrick_scale:!1,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:'\u{1f5fd}',fitzpatrick_scale:!1,category:"travel_and_places"},house:{keywords:["building","home"],char:'\u{1f3e0}',fitzpatrick_scale:!1,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:'\u{1f3e1}',fitzpatrick_scale:!1,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:'\u{1f3da}',fitzpatrick_scale:!1,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:'\u{1f3e2}',fitzpatrick_scale:!1,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:'\u{1f3ec}',fitzpatrick_scale:!1,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:'\u{1f3e3}',fitzpatrick_scale:!1,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:'\u{1f3e4}',fitzpatrick_scale:!1,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:'\u{1f3e5}',fitzpatrick_scale:!1,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:'\u{1f3e6}',fitzpatrick_scale:!1,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:'\u{1f3e8}',fitzpatrick_scale:!1,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:'\u{1f3ea}',fitzpatrick_scale:!1,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:'\u{1f3eb}',fitzpatrick_scale:!1,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:'\u{1f3e9}',fitzpatrick_scale:!1,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:'\u{1f492}',fitzpatrick_scale:!1,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:'\u{1f3db}',fitzpatrick_scale:!1,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:'\u26ea',fitzpatrick_scale:!1,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:'\u{1f54c}',fitzpatrick_scale:!1,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:'\u{1f54d}',fitzpatrick_scale:!1,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:'\u{1f54b}',fitzpatrick_scale:!1,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:'\u26e9',fitzpatrick_scale:!1,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:'\u231a',fitzpatrick_scale:!1,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:'\u{1f4f1}',fitzpatrick_scale:!1,category:"objects"},calling:{keywords:["iphone","incoming"],char:'\u{1f4f2}',fitzpatrick_scale:!1,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:'\u{1f4bb}',fitzpatrick_scale:!1,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:'\u2328',fitzpatrick_scale:!1,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:'\u{1f5a5}',fitzpatrick_scale:!1,category:"objects"},printer:{keywords:["paper","ink"],char:'\u{1f5a8}',fitzpatrick_scale:!1,category:"objects"},computer_mouse:{keywords:["click"],char:'\u{1f5b1}',fitzpatrick_scale:!1,category:"objects"},trackball:{keywords:["technology","trackpad"],char:'\u{1f5b2}',fitzpatrick_scale:!1,category:"objects"},joystick:{keywords:["game","play"],char:'\u{1f579}',fitzpatrick_scale:!1,category:"objects"},clamp:{keywords:["tool"],char:'\u{1f5dc}',fitzpatrick_scale:!1,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:'\u{1f4bd}',fitzpatrick_scale:!1,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:'\u{1f4be}',fitzpatrick_scale:!1,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:'\u{1f4bf}',fitzpatrick_scale:!1,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:'\u{1f4c0}',fitzpatrick_scale:!1,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:'\u{1f4fc}',fitzpatrick_scale:!1,category:"objects"},camera:{keywords:["gadgets","photography"],char:'\u{1f4f7}',fitzpatrick_scale:!1,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:'\u{1f4f8}',fitzpatrick_scale:!1,category:"objects"},video_camera:{keywords:["film","record"],char:'\u{1f4f9}',fitzpatrick_scale:!1,category:"objects"},movie_camera:{keywords:["film","record"],char:'\u{1f3a5}',fitzpatrick_scale:!1,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:'\u{1f4fd}',fitzpatrick_scale:!1,category:"objects"},film_strip:{keywords:["movie"],char:'\u{1f39e}',fitzpatrick_scale:!1,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:'\u{1f4de}',fitzpatrick_scale:!1,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:'\u260e\ufe0f',fitzpatrick_scale:!1,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:'\u{1f4df}',fitzpatrick_scale:!1,category:"objects"},fax:{keywords:["communication","technology"],char:'\u{1f4e0}',fitzpatrick_scale:!1,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:'\u{1f4fa}',fitzpatrick_scale:!1,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:'\u{1f4fb}',fitzpatrick_scale:!1,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:'\u{1f399}',fitzpatrick_scale:!1,category:"objects"},level_slider:{keywords:["scale"],char:'\u{1f39a}',fitzpatrick_scale:!1,category:"objects"},control_knobs:{keywords:["dial"],char:'\u{1f39b}',fitzpatrick_scale:!1,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:'\u{1f9ed}',fitzpatrick_scale:!1,category:"objects"},stopwatch:{keywords:["time","deadline"],char:'\u23f1',fitzpatrick_scale:!1,category:"objects"},timer_clock:{keywords:["alarm"],char:'\u23f2',fitzpatrick_scale:!1,category:"objects"},alarm_clock:{keywords:["time","wake"],char:'\u23f0',fitzpatrick_scale:!1,category:"objects"},mantelpiece_clock:{keywords:["time"],char:'\u{1f570}',fitzpatrick_scale:!1,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:'\u23f3',fitzpatrick_scale:!1,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:'\u231b',fitzpatrick_scale:!1,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:'\u{1f4e1}',fitzpatrick_scale:!1,category:"objects"},battery:{keywords:["power","energy","sustain"],char:'\u{1f50b}',fitzpatrick_scale:!1,category:"objects"},electric_plug:{keywords:["charger","power"],char:'\u{1f50c}',fitzpatrick_scale:!1,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:'\u{1f4a1}',fitzpatrick_scale:!1,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:'\u{1f526}',fitzpatrick_scale:!1,category:"objects"},candle:{keywords:["fire","wax"],char:'\u{1f56f}',fitzpatrick_scale:!1,category:"objects"},fire_extinguisher:{keywords:["quench"],char:'\u{1f9ef}',fitzpatrick_scale:!1,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:'\u{1f5d1}',fitzpatrick_scale:!1,category:"objects"},oil_drum:{keywords:["barrell"],char:'\u{1f6e2}',fitzpatrick_scale:!1,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:'\u{1f4b8}',fitzpatrick_scale:!1,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:'\u{1f4b5}',fitzpatrick_scale:!1,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:'\u{1f4b4}',fitzpatrick_scale:!1,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:'\u{1f4b6}',fitzpatrick_scale:!1,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:'\u{1f4b7}',fitzpatrick_scale:!1,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:'\u{1f4b0}',fitzpatrick_scale:!1,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:'\u{1f4b3}',fitzpatrick_scale:!1,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:'\u{1f48e}',fitzpatrick_scale:!1,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:'\u2696',fitzpatrick_scale:!1,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:'\u{1f9f0}',fitzpatrick_scale:!1,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:'\u{1f527}',fitzpatrick_scale:!1,category:"objects"},hammer:{keywords:["tools","build","create"],char:'\u{1f528}',fitzpatrick_scale:!1,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:'\u2692',fitzpatrick_scale:!1,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:'\u{1f6e0}',fitzpatrick_scale:!1,category:"objects"},pick:{keywords:["tools","dig"],char:'\u26cf',fitzpatrick_scale:!1,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:'\u{1f529}',fitzpatrick_scale:!1,category:"objects"},gear:{keywords:["cog"],char:'\u2699',fitzpatrick_scale:!1,category:"objects"},brick:{keywords:["bricks"],char:'\u{1f9f1}',fitzpatrick_scale:!1,category:"objects"},chains:{keywords:["lock","arrest"],char:'\u26d3',fitzpatrick_scale:!1,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:'\u{1f9f2}',fitzpatrick_scale:!1,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:'\u{1f52b}',fitzpatrick_scale:!1,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:'\u{1f4a3}',fitzpatrick_scale:!1,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:'\u{1f9e8}',fitzpatrick_scale:!1,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:'\u{1f52a}',fitzpatrick_scale:!1,category:"objects"},dagger:{keywords:["weapon"],char:'\u{1f5e1}',fitzpatrick_scale:!1,category:"objects"},crossed_swords:{keywords:["weapon"],char:'\u2694',fitzpatrick_scale:!1,category:"objects"},shield:{keywords:["protection","security"],char:'\u{1f6e1}',fitzpatrick_scale:!1,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:'\u{1f6ac}',fitzpatrick_scale:!1,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:'\u2620',fitzpatrick_scale:!1,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:'\u26b0',fitzpatrick_scale:!1,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:'\u26b1',fitzpatrick_scale:!1,category:"objects"},amphora:{keywords:["vase","jar"],char:'\u{1f3fa}',fitzpatrick_scale:!1,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:'\u{1f52e}',fitzpatrick_scale:!1,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:'\u{1f4ff}',fitzpatrick_scale:!1,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:'\u{1f9ff}',fitzpatrick_scale:!1,category:"objects"},barber:{keywords:["hair","salon","style"],char:'\u{1f488}',fitzpatrick_scale:!1,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:'\u2697',fitzpatrick_scale:!1,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:'\u{1f52d}',fitzpatrick_scale:!1,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:'\u{1f52c}',fitzpatrick_scale:!1,category:"objects"},hole:{keywords:["embarrassing"],char:'\u{1f573}',fitzpatrick_scale:!1,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:'\u{1f48a}',fitzpatrick_scale:!1,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:'\u{1f489}',fitzpatrick_scale:!1,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:'\u{1f9ec}',fitzpatrick_scale:!1,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:'\u{1f9a0}',fitzpatrick_scale:!1,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:'\u{1f9eb}',fitzpatrick_scale:!1,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:'\u{1f9ea}',fitzpatrick_scale:!1,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:'\u{1f321}',fitzpatrick_scale:!1,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:'\u{1f9f9}',fitzpatrick_scale:!1,category:"objects"},basket:{keywords:["laundry"],char:'\u{1f9fa}',fitzpatrick_scale:!1,category:"objects"},toilet_paper:{keywords:["roll"],char:'\u{1f9fb}',fitzpatrick_scale:!1,category:"objects"},label:{keywords:["sale","tag"],char:'\u{1f3f7}',fitzpatrick_scale:!1,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:'\u{1f516}',fitzpatrick_scale:!1,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:'\u{1f6bd}',fitzpatrick_scale:!1,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:'\u{1f6bf}',fitzpatrick_scale:!1,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:'\u{1f6c1}',fitzpatrick_scale:!1,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:'\u{1f9fc}',fitzpatrick_scale:!1,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:'\u{1f9fd}',fitzpatrick_scale:!1,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:'\u{1f9f4}',fitzpatrick_scale:!1,category:"objects"},key:{keywords:["lock","door","password"],char:'\u{1f511}',fitzpatrick_scale:!1,category:"objects"},old_key:{keywords:["lock","door","password"],char:'\u{1f5dd}',fitzpatrick_scale:!1,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:'\u{1f6cb}',fitzpatrick_scale:!1,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:'\u{1f6cc}',fitzpatrick_scale:!0,category:"objects"},bed:{keywords:["sleep","rest"],char:'\u{1f6cf}',fitzpatrick_scale:!1,category:"objects"},door:{keywords:["house","entry","exit"],char:'\u{1f6aa}',fitzpatrick_scale:!1,category:"objects"},bellhop_bell:{keywords:["service"],char:'\u{1f6ce}',fitzpatrick_scale:!1,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:'\u{1f9f8}',fitzpatrick_scale:!1,category:"objects"},framed_picture:{keywords:["photography"],char:'\u{1f5bc}',fitzpatrick_scale:!1,category:"objects"},world_map:{keywords:["location","direction"],char:'\u{1f5fa}',fitzpatrick_scale:!1,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:'\u26f1',fitzpatrick_scale:!1,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:'\u{1f5ff}',fitzpatrick_scale:!1,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:'\u{1f6cd}',fitzpatrick_scale:!1,category:"objects"},shopping_cart:{keywords:["trolley"],char:'\u{1f6d2}',fitzpatrick_scale:!1,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:'\u{1f388}',fitzpatrick_scale:!1,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:'\u{1f38f}',fitzpatrick_scale:!1,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:'\u{1f380}',fitzpatrick_scale:!1,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:'\u{1f381}',fitzpatrick_scale:!1,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:'\u{1f38a}',fitzpatrick_scale:!1,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:'\u{1f389}',fitzpatrick_scale:!1,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:'\u{1f38e}',fitzpatrick_scale:!1,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:'\u{1f390}',fitzpatrick_scale:!1,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:'\u{1f38c}',fitzpatrick_scale:!1,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:'\u{1f3ee}',fitzpatrick_scale:!1,category:"objects"},red_envelope:{keywords:["gift"],char:'\u{1f9e7}',fitzpatrick_scale:!1,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:'\u2709\ufe0f',fitzpatrick_scale:!1,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:'\u{1f4e9}',fitzpatrick_scale:!1,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:'\u{1f4e8}',fitzpatrick_scale:!1,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:'\u{1f4e7}',fitzpatrick_scale:!1,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:'\u{1f48c}',fitzpatrick_scale:!1,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:'\u{1f4ee}',fitzpatrick_scale:!1,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:'\u{1f4ea}',fitzpatrick_scale:!1,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:'\u{1f4eb}',fitzpatrick_scale:!1,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:'\u{1f4ec}',fitzpatrick_scale:!1,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:'\u{1f4ed}',fitzpatrick_scale:!1,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:'\u{1f4e6}',fitzpatrick_scale:!1,category:"objects"},postal_horn:{keywords:["instrument","music"],char:'\u{1f4ef}',fitzpatrick_scale:!1,category:"objects"},inbox_tray:{keywords:["email","documents"],char:'\u{1f4e5}',fitzpatrick_scale:!1,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:'\u{1f4e4}',fitzpatrick_scale:!1,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:'\u{1f4dc}',fitzpatrick_scale:!1,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:'\u{1f4c3}',fitzpatrick_scale:!1,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:'\u{1f4d1}',fitzpatrick_scale:!1,category:"objects"},receipt:{keywords:["accounting","expenses"],char:'\u{1f9fe}',fitzpatrick_scale:!1,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:'\u{1f4ca}',fitzpatrick_scale:!1,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:'\u{1f4c8}',fitzpatrick_scale:!1,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:'\u{1f4c9}',fitzpatrick_scale:!1,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:'\u{1f4c4}',fitzpatrick_scale:!1,category:"objects"},date:{keywords:["calendar","schedule"],char:'\u{1f4c5}',fitzpatrick_scale:!1,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:'\u{1f4c6}',fitzpatrick_scale:!1,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:'\u{1f5d3}',fitzpatrick_scale:!1,category:"objects"},card_index:{keywords:["business","stationery"],char:'\u{1f4c7}',fitzpatrick_scale:!1,category:"objects"},card_file_box:{keywords:["business","stationery"],char:'\u{1f5c3}',fitzpatrick_scale:!1,category:"objects"},ballot_box:{keywords:["election","vote"],char:'\u{1f5f3}',fitzpatrick_scale:!1,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:'\u{1f5c4}',fitzpatrick_scale:!1,category:"objects"},clipboard:{keywords:["stationery","documents"],char:'\u{1f4cb}',fitzpatrick_scale:!1,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:'\u{1f5d2}',fitzpatrick_scale:!1,category:"objects"},file_folder:{keywords:["documents","business","office"],char:'\u{1f4c1}',fitzpatrick_scale:!1,category:"objects"},open_file_folder:{keywords:["documents","load"],char:'\u{1f4c2}',fitzpatrick_scale:!1,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:'\u{1f5c2}',fitzpatrick_scale:!1,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:'\u{1f5de}',fitzpatrick_scale:!1,category:"objects"},newspaper:{keywords:["press","headline"],char:'\u{1f4f0}',fitzpatrick_scale:!1,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:'\u{1f4d3}',fitzpatrick_scale:!1,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:'\u{1f4d5}',fitzpatrick_scale:!1,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:'\u{1f4d7}',fitzpatrick_scale:!1,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:'\u{1f4d8}',fitzpatrick_scale:!1,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:'\u{1f4d9}',fitzpatrick_scale:!1,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:'\u{1f4d4}',fitzpatrick_scale:!1,category:"objects"},ledger:{keywords:["notes","paper"],char:'\u{1f4d2}',fitzpatrick_scale:!1,category:"objects"},books:{keywords:["literature","library","study"],char:'\u{1f4da}',fitzpatrick_scale:!1,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:'\u{1f4d6}',fitzpatrick_scale:!1,category:"objects"},safety_pin:{keywords:["diaper"],char:'\u{1f9f7}',fitzpatrick_scale:!1,category:"objects"},link:{keywords:["rings","url"],char:'\u{1f517}',fitzpatrick_scale:!1,category:"objects"},paperclip:{keywords:["documents","stationery"],char:'\u{1f4ce}',fitzpatrick_scale:!1,category:"objects"},paperclips:{keywords:["documents","stationery"],char:'\u{1f587}',fitzpatrick_scale:!1,category:"objects"},scissors:{keywords:["stationery","cut"],char:'\u2702\ufe0f',fitzpatrick_scale:!1,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:'\u{1f4d0}',fitzpatrick_scale:!1,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:'\u{1f4cf}',fitzpatrick_scale:!1,category:"objects"},abacus:{keywords:["calculation"],char:'\u{1f9ee}',fitzpatrick_scale:!1,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:'\u{1f4cc}',fitzpatrick_scale:!1,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:'\u{1f4cd}',fitzpatrick_scale:!1,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:'\u{1f6a9}',fitzpatrick_scale:!1,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:'\u{1f3f3}',fitzpatrick_scale:!1,category:"objects"},black_flag:{keywords:["pirate"],char:'\u{1f3f4}',fitzpatrick_scale:!1,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:'\u{1f3f3}\ufe0f\u200d\u{1f308}',fitzpatrick_scale:!1,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:'\u{1f510}',fitzpatrick_scale:!1,category:"objects"},lock:{keywords:["security","password","padlock"],char:'\u{1f512}',fitzpatrick_scale:!1,category:"objects"},unlock:{keywords:["privacy","security"],char:'\u{1f513}',fitzpatrick_scale:!1,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:'\u{1f50f}',fitzpatrick_scale:!1,category:"objects"},pen:{keywords:["stationery","writing","write"],char:'\u{1f58a}',fitzpatrick_scale:!1,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:'\u{1f58b}',fitzpatrick_scale:!1,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:'\u2712\ufe0f',fitzpatrick_scale:!1,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:'\u{1f4dd}',fitzpatrick_scale:!1,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:'\u270f\ufe0f',fitzpatrick_scale:!1,category:"objects"},crayon:{keywords:["drawing","creativity"],char:'\u{1f58d}',fitzpatrick_scale:!1,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:'\u{1f58c}',fitzpatrick_scale:!1,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:'\u{1f50d}',fitzpatrick_scale:!1,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:'\u{1f50e}',fitzpatrick_scale:!1,category:"objects"},heart:{keywords:["love","like","valentines"],char:'\u2764\ufe0f',fitzpatrick_scale:!1,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f9e1}',fitzpatrick_scale:!1,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f49b}',fitzpatrick_scale:!1,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f49a}',fitzpatrick_scale:!1,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f499}',fitzpatrick_scale:!1,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f49c}',fitzpatrick_scale:!1,category:"symbols"},black_heart:{keywords:["evil"],char:'\u{1f5a4}',fitzpatrick_scale:!1,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:'\u{1f494}',fitzpatrick_scale:!1,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:'\u2763',fitzpatrick_scale:!1,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:'\u{1f495}',fitzpatrick_scale:!1,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:'\u{1f49e}',fitzpatrick_scale:!1,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:'\u{1f493}',fitzpatrick_scale:!1,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:'\u{1f497}',fitzpatrick_scale:!1,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:'\u{1f496}',fitzpatrick_scale:!1,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:'\u{1f498}',fitzpatrick_scale:!1,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:'\u{1f49d}',fitzpatrick_scale:!1,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:'\u{1f49f}',fitzpatrick_scale:!1,category:"symbols"},peace_symbol:{keywords:["hippie"],char:'\u262e',fitzpatrick_scale:!1,category:"symbols"},latin_cross:{keywords:["christianity"],char:'\u271d',fitzpatrick_scale:!1,category:"symbols"},star_and_crescent:{keywords:["islam"],char:'\u262a',fitzpatrick_scale:!1,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'\u{1f549}',fitzpatrick_scale:!1,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'\u2638',fitzpatrick_scale:!1,category:"symbols"},star_of_david:{keywords:["judaism"],char:'\u2721',fitzpatrick_scale:!1,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:'\u{1f52f}',fitzpatrick_scale:!1,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:'\u{1f54e}',fitzpatrick_scale:!1,category:"symbols"},yin_yang:{keywords:["balance"],char:'\u262f',fitzpatrick_scale:!1,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:'\u2626',fitzpatrick_scale:!1,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:'\u{1f6d0}',fitzpatrick_scale:!1,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:'\u26ce',fitzpatrick_scale:!1,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:'\u2648',fitzpatrick_scale:!1,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:'\u2649',fitzpatrick_scale:!1,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:'\u264a',fitzpatrick_scale:!1,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:'\u264b',fitzpatrick_scale:!1,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:'\u264c',fitzpatrick_scale:!1,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:'\u264d',fitzpatrick_scale:!1,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:'\u264e',fitzpatrick_scale:!1,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:'\u264f',fitzpatrick_scale:!1,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:'\u2650',fitzpatrick_scale:!1,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:'\u2651',fitzpatrick_scale:!1,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:'\u2652',fitzpatrick_scale:!1,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:'\u2653',fitzpatrick_scale:!1,category:"symbols"},id:{keywords:["purple-square","words"],char:'\u{1f194}',fitzpatrick_scale:!1,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:'\u269b',fitzpatrick_scale:!1,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:'\u{1f233}',fitzpatrick_scale:!1,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:'\u{1f239}',fitzpatrick_scale:!1,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:'\u2622',fitzpatrick_scale:!1,category:"symbols"},biohazard:{keywords:["danger"],char:'\u2623',fitzpatrick_scale:!1,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:'\u{1f4f4}',fitzpatrick_scale:!1,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:'\u{1f4f3}',fitzpatrick_scale:!1,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:'\u{1f236}',fitzpatrick_scale:!1,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:'\u{1f21a}',fitzpatrick_scale:!1,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:'\u{1f238}',fitzpatrick_scale:!1,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:'\u{1f23a}',fitzpatrick_scale:!1,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:'\u{1f237}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:'\u2734\ufe0f',fitzpatrick_scale:!1,category:"symbols"},vs:{keywords:["words","orange-square"],char:'\u{1f19a}',fitzpatrick_scale:!1,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:'\u{1f251}',fitzpatrick_scale:!1,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:'\u{1f4ae}',fitzpatrick_scale:!1,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:'\u{1f250}',fitzpatrick_scale:!1,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:'\u3299\ufe0f',fitzpatrick_scale:!1,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:'\u3297\ufe0f',fitzpatrick_scale:!1,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:'\u{1f234}',fitzpatrick_scale:!1,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:'\u{1f235}',fitzpatrick_scale:!1,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:'\u{1f232}',fitzpatrick_scale:!1,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:'\u{1f170}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:'\u{1f171}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:'\u{1f18e}',fitzpatrick_scale:!1,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:'\u{1f191}',fitzpatrick_scale:!1,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:'\u{1f17e}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:'\u{1f198}',fitzpatrick_scale:!1,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:'\u26d4',fitzpatrick_scale:!1,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:'\u{1f4db}',fitzpatrick_scale:!1,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:'\u{1f6ab}',fitzpatrick_scale:!1,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:'\u274c',fitzpatrick_scale:!1,category:"symbols"},o:{keywords:["circle","round"],char:'\u2b55',fitzpatrick_scale:!1,category:"symbols"},stop_sign:{keywords:["stop"],char:'\u{1f6d1}',fitzpatrick_scale:!1,category:"symbols"},anger:{keywords:["angry","mad"],char:'\u{1f4a2}',fitzpatrick_scale:!1,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:'\u2668\ufe0f',fitzpatrick_scale:!1,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:'\u{1f6b7}',fitzpatrick_scale:!1,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:'\u{1f6af}',fitzpatrick_scale:!1,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:'\u{1f6b3}',fitzpatrick_scale:!1,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:'\u{1f6b1}',fitzpatrick_scale:!1,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:'\u{1f51e}',fitzpatrick_scale:!1,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:'\u{1f4f5}',fitzpatrick_scale:!1,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:'\u2757',fitzpatrick_scale:!1,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:'\u2755',fitzpatrick_scale:!1,category:"symbols"},question:{keywords:["doubt","confused"],char:'\u2753',fitzpatrick_scale:!1,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:'\u2754',fitzpatrick_scale:!1,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:'\u203c\ufe0f',fitzpatrick_scale:!1,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:'\u2049\ufe0f',fitzpatrick_scale:!1,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:'\u{1f505}',fitzpatrick_scale:!1,category:"symbols"},high_brightness:{keywords:["sun","light"],char:'\u{1f506}',fitzpatrick_scale:!1,category:"symbols"},trident:{keywords:["weapon","spear"],char:'\u{1f531}',fitzpatrick_scale:!1,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:'\u269c',fitzpatrick_scale:!1,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:'\u303d\ufe0f',fitzpatrick_scale:!1,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:'\u26a0\ufe0f',fitzpatrick_scale:!1,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:'\u{1f6b8}',fitzpatrick_scale:!1,category:"symbols"},beginner:{keywords:["badge","shield"],char:'\u{1f530}',fitzpatrick_scale:!1,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:'\u267b\ufe0f',fitzpatrick_scale:!1,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:'\u{1f22f}',fitzpatrick_scale:!1,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:'\u{1f4b9}',fitzpatrick_scale:!1,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:'\u2747\ufe0f',fitzpatrick_scale:!1,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:'\u2733\ufe0f',fitzpatrick_scale:!1,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:'\u274e',fitzpatrick_scale:!1,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:'\u2705',fitzpatrick_scale:!1,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:'\u{1f4a0}',fitzpatrick_scale:!1,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:'\u{1f300}',fitzpatrick_scale:!1,category:"symbols"},loop:{keywords:["tape","cassette"],char:'\u27bf',fitzpatrick_scale:!1,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:'\u{1f310}',fitzpatrick_scale:!1,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:'\u24c2\ufe0f',fitzpatrick_scale:!1,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:'\u{1f3e7}',fitzpatrick_scale:!1,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:'\u{1f202}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:'\u{1f6c2}',fitzpatrick_scale:!1,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:'\u{1f6c3}',fitzpatrick_scale:!1,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:'\u{1f6c4}',fitzpatrick_scale:!1,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:'\u{1f6c5}',fitzpatrick_scale:!1,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:'\u267f',fitzpatrick_scale:!1,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:'\u{1f6ad}',fitzpatrick_scale:!1,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:'\u{1f6be}',fitzpatrick_scale:!1,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:'\u{1f17f}\ufe0f',fitzpatrick_scale:!1,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:'\u{1f6b0}',fitzpatrick_scale:!1,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:'\u{1f6b9}',fitzpatrick_scale:!1,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:'\u{1f6ba}',fitzpatrick_scale:!1,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:'\u{1f6bc}',fitzpatrick_scale:!1,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:'\u{1f6bb}',fitzpatrick_scale:!1,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:'\u{1f6ae}',fitzpatrick_scale:!1,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:'\u{1f3a6}',fitzpatrick_scale:!1,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:'\u{1f4f6}',fitzpatrick_scale:!1,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:'\u{1f201}',fitzpatrick_scale:!1,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:'\u{1f196}',fitzpatrick_scale:!1,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:'\u{1f197}',fitzpatrick_scale:!1,category:"symbols"},up:{keywords:["blue-square","above","high"],char:'\u{1f199}',fitzpatrick_scale:!1,category:"symbols"},cool:{keywords:["words","blue-square"],char:'\u{1f192}',fitzpatrick_scale:!1,category:"symbols"},new:{keywords:["blue-square","words","start"],char:'\u{1f195}',fitzpatrick_scale:!1,category:"symbols"},free:{keywords:["blue-square","words"],char:'\u{1f193}',fitzpatrick_scale:!1,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:'0\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:'1\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:'2\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:'3\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:'4\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:'5\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:'6\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:'7\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:'8\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:'9\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:'\u{1f51f}',fitzpatrick_scale:!1,category:"symbols"},asterisk:{keywords:["star","keycap"],char:'*\u20e3',fitzpatrick_scale:!1,category:"symbols"},eject_button:{keywords:["blue-square"],char:'\u23cf\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:'\u25b6\ufe0f',fitzpatrick_scale:!1,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:'\u23f8',fitzpatrick_scale:!1,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:'\u23ed',fitzpatrick_scale:!1,category:"symbols"},stop_button:{keywords:["blue-square"],char:'\u23f9',fitzpatrick_scale:!1,category:"symbols"},record_button:{keywords:["blue-square"],char:'\u23fa',fitzpatrick_scale:!1,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:'\u23ef',fitzpatrick_scale:!1,category:"symbols"},previous_track_button:{keywords:["backward"],char:'\u23ee',fitzpatrick_scale:!1,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:'\u23e9',fitzpatrick_scale:!1,category:"symbols"},rewind:{keywords:["play","blue-square"],char:'\u23ea',fitzpatrick_scale:!1,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:'\u{1f500}',fitzpatrick_scale:!1,category:"symbols"},repeat:{keywords:["loop","record"],char:'\u{1f501}',fitzpatrick_scale:!1,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:'\u{1f502}',fitzpatrick_scale:!1,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:'\u25c0\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:'\u{1f53c}',fitzpatrick_scale:!1,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:'\u{1f53d}',fitzpatrick_scale:!1,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:'\u23eb',fitzpatrick_scale:!1,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:'\u23ec',fitzpatrick_scale:!1,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:'\u27a1\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:'\u2b05\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:'\u2b06\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:'\u2b07\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:'\u2197\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:'\u2198\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:'\u2199\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:'\u2196\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:'\u2195\ufe0f',fitzpatrick_scale:!1,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:'\u2194\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:'\u{1f504}',fitzpatrick_scale:!1,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:'\u21aa\ufe0f',fitzpatrick_scale:!1,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:'\u21a9\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:'\u2934\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:'\u2935\ufe0f',fitzpatrick_scale:!1,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:'#\ufe0f\u20e3',fitzpatrick_scale:!1,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:'\u2139\ufe0f',fitzpatrick_scale:!1,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:'\u{1f524}',fitzpatrick_scale:!1,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:'\u{1f521}',fitzpatrick_scale:!1,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:'\u{1f520}',fitzpatrick_scale:!1,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:'\u{1f523}',fitzpatrick_scale:!1,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:'\u{1f3b5}',fitzpatrick_scale:!1,category:"symbols"},notes:{keywords:["music","score"],char:'\u{1f3b6}',fitzpatrick_scale:!1,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:'\u3030\ufe0f',fitzpatrick_scale:!1,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:'\u27b0',fitzpatrick_scale:!1,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:'\u2714\ufe0f',fitzpatrick_scale:!1,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:'\u{1f503}',fitzpatrick_scale:!1,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:'\u2795',fitzpatrick_scale:!1,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:'\u2796',fitzpatrick_scale:!1,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:'\u2797',fitzpatrick_scale:!1,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:'\u2716\ufe0f',fitzpatrick_scale:!1,category:"symbols"},infinity:{keywords:["forever"],char:'\u267e',fitzpatrick_scale:!1,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:'\u{1f4b2}',fitzpatrick_scale:!1,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:'\u{1f4b1}',fitzpatrick_scale:!1,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:'\xa9\ufe0f',fitzpatrick_scale:!1,category:"symbols"},registered:{keywords:["alphabet","circle"],char:'\xae\ufe0f',fitzpatrick_scale:!1,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:'\u2122\ufe0f',fitzpatrick_scale:!1,category:"symbols"},end:{keywords:["words","arrow"],char:'\u{1f51a}',fitzpatrick_scale:!1,category:"symbols"},back:{keywords:["arrow","words","return"],char:'\u{1f519}',fitzpatrick_scale:!1,category:"symbols"},on:{keywords:["arrow","words"],char:'\u{1f51b}',fitzpatrick_scale:!1,category:"symbols"},top:{keywords:["words","blue-square"],char:'\u{1f51d}',fitzpatrick_scale:!1,category:"symbols"},soon:{keywords:["arrow","words"],char:'\u{1f51c}',fitzpatrick_scale:!1,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:'\u2611\ufe0f',fitzpatrick_scale:!1,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:'\u{1f518}',fitzpatrick_scale:!1,category:"symbols"},white_circle:{keywords:["shape","round"],char:'\u26aa',fitzpatrick_scale:!1,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:'\u26ab',fitzpatrick_scale:!1,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:'\u{1f534}',fitzpatrick_scale:!1,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:'\u{1f535}',fitzpatrick_scale:!1,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:'\u{1f538}',fitzpatrick_scale:!1,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:'\u{1f539}',fitzpatrick_scale:!1,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:'\u{1f536}',fitzpatrick_scale:!1,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:'\u{1f537}',fitzpatrick_scale:!1,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:'\u{1f53a}',fitzpatrick_scale:!1,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:'\u25aa\ufe0f',fitzpatrick_scale:!1,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:'\u25ab\ufe0f',fitzpatrick_scale:!1,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:'\u2b1b',fitzpatrick_scale:!1,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:'\u2b1c',fitzpatrick_scale:!1,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:'\u{1f53b}',fitzpatrick_scale:!1,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:'\u25fc\ufe0f',fitzpatrick_scale:!1,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:'\u25fb\ufe0f',fitzpatrick_scale:!1,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:'\u25fe',fitzpatrick_scale:!1,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:'\u25fd',fitzpatrick_scale:!1,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:'\u{1f532}',fitzpatrick_scale:!1,category:"symbols"},white_square_button:{keywords:["shape","input"],char:'\u{1f533}',fitzpatrick_scale:!1,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:'\u{1f508}',fitzpatrick_scale:!1,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:'\u{1f509}',fitzpatrick_scale:!1,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:'\u{1f50a}',fitzpatrick_scale:!1,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:'\u{1f507}',fitzpatrick_scale:!1,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:'\u{1f4e3}',fitzpatrick_scale:!1,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:'\u{1f4e2}',fitzpatrick_scale:!1,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:'\u{1f514}',fitzpatrick_scale:!1,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:'\u{1f515}',fitzpatrick_scale:!1,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:'\u{1f0cf}',fitzpatrick_scale:!1,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:'\u{1f004}',fitzpatrick_scale:!1,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:'\u2660\ufe0f',fitzpatrick_scale:!1,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:'\u2663\ufe0f',fitzpatrick_scale:!1,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:'\u2665\ufe0f',fitzpatrick_scale:!1,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:'\u2666\ufe0f',fitzpatrick_scale:!1,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:'\u{1f3b4}',fitzpatrick_scale:!1,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:'\u{1f4ad}',fitzpatrick_scale:!1,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:'\u{1f5ef}',fitzpatrick_scale:!1,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:'\u{1f4ac}',fitzpatrick_scale:!1,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:'\u{1f5e8}',fitzpatrick_scale:!1,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:'\u{1f550}',fitzpatrick_scale:!1,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:'\u{1f551}',fitzpatrick_scale:!1,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:'\u{1f552}',fitzpatrick_scale:!1,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:'\u{1f553}',fitzpatrick_scale:!1,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:'\u{1f554}',fitzpatrick_scale:!1,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:'\u{1f555}',fitzpatrick_scale:!1,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:'\u{1f556}',fitzpatrick_scale:!1,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:'\u{1f557}',fitzpatrick_scale:!1,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:'\u{1f558}',fitzpatrick_scale:!1,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:'\u{1f559}',fitzpatrick_scale:!1,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:'\u{1f55a}',fitzpatrick_scale:!1,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:'\u{1f55b}',fitzpatrick_scale:!1,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:'\u{1f55c}',fitzpatrick_scale:!1,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:'\u{1f55d}',fitzpatrick_scale:!1,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:'\u{1f55e}',fitzpatrick_scale:!1,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:'\u{1f55f}',fitzpatrick_scale:!1,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:'\u{1f560}',fitzpatrick_scale:!1,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:'\u{1f561}',fitzpatrick_scale:!1,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:'\u{1f562}',fitzpatrick_scale:!1,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:'\u{1f563}',fitzpatrick_scale:!1,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:'\u{1f564}',fitzpatrick_scale:!1,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:'\u{1f565}',fitzpatrick_scale:!1,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:'\u{1f566}',fitzpatrick_scale:!1,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:'\u{1f567}',fitzpatrick_scale:!1,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},aland_islands:{keywords:["\xc5land","islands","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1fd}',fitzpatrick_scale:!1,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:'\u{1f1e9}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f6}',fitzpatrick_scale:!1,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1e7}',fitzpatrick_scale:!1,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ef}',fitzpatrick_scale:!1,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f6}',fitzpatrick_scale:!1,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1fb}',fitzpatrick_scale:!1,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:'\u{1f1e8}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1fd}',fitzpatrick_scale:!1,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:'\u{1f1ed}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},curacao:{keywords:["cura\xe7ao","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:'\u{1f1e9}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:'\u{1f1e9}\u{1f1ef}',fitzpatrick_scale:!1,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:'\u{1f1e9}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:'\u{1f1e9}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1fb}',fitzpatrick_scale:!1,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f6}',fitzpatrick_scale:!1,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:'\u{1f1ea}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:'\u{1f1eb}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:'\u{1f1eb}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:'\u{1f1eb}\u{1f1ef}',fitzpatrick_scale:!1,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:'\u{1f1eb}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:'\u{1f1eb}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:'\u{1f1e9}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f5}',fitzpatrick_scale:!1,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:'\u{1f1ed}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:'\u{1f1ed}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:'\u{1f1ed}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:'\u{1f1ed}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f6}',fitzpatrick_scale:!1,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:'\u{1f1ee}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:'\u{1f1ef}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:'\u{1f1ef}\u{1f1f5}',fitzpatrick_scale:!1,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:'\u{1f1ef}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:'\u{1f1ef}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:'\u{1f1fd}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1fb}',fitzpatrick_scale:!1,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1e7}',fitzpatrick_scale:!1,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1fb}',fitzpatrick_scale:!1,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f6}',fitzpatrick_scale:!1,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:'\u{1f1fe}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1fd}',fitzpatrick_scale:!1,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:'\u{1f1eb}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1f5}',fitzpatrick_scale:!1,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:'\u{1f1f2}\u{1f1f5}',fitzpatrick_scale:!1,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:'\u{1f1f0}\u{1f1f5}',fitzpatrick_scale:!1,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:'\u{1f1f3}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:'\u{1f1f4}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:'\u{1f1f6}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},reunion:{keywords:["r\xe9union","flag","nation","country","banner"],char:'\u{1f1f7}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:'\u{1f1f7}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:'\u{1f1f7}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:'\u{1f1f7}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},st_barthelemy:{keywords:["saint","barth\xe9lemy","flag","nation","country","banner"],char:'\u{1f1e7}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:'\u{1f1f0}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:'\u{1f1f5}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:'\u{1f1fc}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:'\u{1f1f7}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1fd}',fitzpatrick_scale:!1,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1e7}',fitzpatrick_scale:!1,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:'\u{1f1ff}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:'\u{1f1ec}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:'\u{1f1f0}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:'\u{1f1f1}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1e9}',fitzpatrick_scale:!1,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:'\u{1f1e8}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:'\u{1f1f8}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1ef}',fitzpatrick_scale:!1,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f1}',fitzpatrick_scale:!1,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f0}',fitzpatrick_scale:!1,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f4}',fitzpatrick_scale:!1,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f9}',fitzpatrick_scale:!1,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f7}',fitzpatrick_scale:!1,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1e8}',fitzpatrick_scale:!1,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:'\u{1f1f9}\u{1f1fb}',fitzpatrick_scale:!1,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:'\u{1f1fa}\u{1f1ec}',fitzpatrick_scale:!1,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:'\u{1f1fa}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:'\u{1f1e6}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:'\u{1f1ec}\u{1f1e7}',fitzpatrick_scale:!1,category:"flags"},england:{keywords:["flag","english"],char:'\u{1f3f4}\u{e0067}\u{e0062}\u{e0065}\u{e006e}\u{e0067}\u{e007f}',fitzpatrick_scale:!1,category:"flags"},scotland:{keywords:["flag","scottish"],char:'\u{1f3f4}\u{e0067}\u{e0062}\u{e0073}\u{e0063}\u{e0074}\u{e007f}',fitzpatrick_scale:!1,category:"flags"},wales:{keywords:["flag","welsh"],char:'\u{1f3f4}\u{e0067}\u{e0062}\u{e0077}\u{e006c}\u{e0073}\u{e007f}',fitzpatrick_scale:!1,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:'\u{1f1fa}\u{1f1f8}',fitzpatrick_scale:!1,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1ee}',fitzpatrick_scale:!1,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:'\u{1f1fa}\u{1f1fe}',fitzpatrick_scale:!1,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:'\u{1f1fa}\u{1f1ff}',fitzpatrick_scale:!1,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1fa}',fitzpatrick_scale:!1,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1e6}',fitzpatrick_scale:!1,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:'\u{1f1fb}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:'\u{1f1fc}\u{1f1eb}',fitzpatrick_scale:!1,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:'\u{1f1ea}\u{1f1ed}',fitzpatrick_scale:!1,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:'\u{1f1fe}\u{1f1ea}',fitzpatrick_scale:!1,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:'\u{1f1ff}\u{1f1f2}',fitzpatrick_scale:!1,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:'\u{1f1ff}\u{1f1fc}',fitzpatrick_scale:!1,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:'\u{1f1fa}\u{1f1f3}',fitzpatrick_scale:!1,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:'\u{1f3f4}\u200d\u2620\ufe0f',fitzpatrick_scale:!1,category:"flags"}}); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/emoticons/js/emojis.js b/apps/web-antd/public/tinymce/plugins/emoticons/js/emojis.js new file mode 100644 index 0000000..88455e9 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/emoticons/js/emojis.js @@ -0,0 +1 @@ +window.tinymce.Resource.add("tinymce.plugins.emoticons",{grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:"😀",fitzpatrick_scale:false,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:"😬",fitzpatrick_scale:false,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:"😁",fitzpatrick_scale:false,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:"😂",fitzpatrick_scale:false,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:"🤣",fitzpatrick_scale:false,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:"🥳",fitzpatrick_scale:false,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:"😃",fitzpatrick_scale:false,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:"😄",fitzpatrick_scale:false,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:"😅",fitzpatrick_scale:false,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:"😆",fitzpatrick_scale:false,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:"😇",fitzpatrick_scale:false,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:"😉",fitzpatrick_scale:false,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:"😊",fitzpatrick_scale:false,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:"🙂",fitzpatrick_scale:false,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:"🙃",fitzpatrick_scale:false,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:"☺️",fitzpatrick_scale:false,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:"😋",fitzpatrick_scale:false,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:"😌",fitzpatrick_scale:false,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:"😍",fitzpatrick_scale:false,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:"🥰",fitzpatrick_scale:false,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"😘",fitzpatrick_scale:false,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:"😗",fitzpatrick_scale:false,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:"😙",fitzpatrick_scale:false,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"😚",fitzpatrick_scale:false,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:"😜",fitzpatrick_scale:false,category:"people"},zany:{keywords:["face","goofy","crazy"],char:"🤪",fitzpatrick_scale:false,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:"🤨",fitzpatrick_scale:false,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:"🧐",fitzpatrick_scale:false,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:"😝",fitzpatrick_scale:false,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:"😛",fitzpatrick_scale:false,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:"🤑",fitzpatrick_scale:false,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:"🤓",fitzpatrick_scale:false,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:"😎",fitzpatrick_scale:false,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:"🤩",fitzpatrick_scale:false,category:"people"},clown_face:{keywords:["face"],char:"🤡",fitzpatrick_scale:false,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:"🤠",fitzpatrick_scale:false,category:"people"},hugs:{keywords:["face","smile","hug"],char:"🤗",fitzpatrick_scale:false,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:"😏",fitzpatrick_scale:false,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:"😶",fitzpatrick_scale:false,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:"😐",fitzpatrick_scale:false,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:"😑",fitzpatrick_scale:false,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:"😒",fitzpatrick_scale:false,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:"🙄",fitzpatrick_scale:false,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:"🤔",fitzpatrick_scale:false,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:"🤥",fitzpatrick_scale:false,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:"🤭",fitzpatrick_scale:false,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:"🤫",fitzpatrick_scale:false,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:"🤬",fitzpatrick_scale:false,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:"🤯",fitzpatrick_scale:false,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:"😳",fitzpatrick_scale:false,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:"😞",fitzpatrick_scale:false,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:"😟",fitzpatrick_scale:false,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:"😠",fitzpatrick_scale:false,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:"😡",fitzpatrick_scale:false,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:"😔",fitzpatrick_scale:false,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:"😕",fitzpatrick_scale:false,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:"🙁",fitzpatrick_scale:false,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:"☹",fitzpatrick_scale:false,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:"😣",fitzpatrick_scale:false,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:"😖",fitzpatrick_scale:false,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:"😫",fitzpatrick_scale:false,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:"😩",fitzpatrick_scale:false,category:"people"},pleading:{keywords:["face","begging","mercy"],char:"🥺",fitzpatrick_scale:false,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:"😤",fitzpatrick_scale:false,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:"😮",fitzpatrick_scale:false,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:"😱",fitzpatrick_scale:false,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:"😨",fitzpatrick_scale:false,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:"😰",fitzpatrick_scale:false,category:"people"},hushed:{keywords:["face","woo","shh"],char:"😯",fitzpatrick_scale:false,category:"people"},frowning:{keywords:["face","aw","what"],char:"😦",fitzpatrick_scale:false,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:"😧",fitzpatrick_scale:false,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:"😢",fitzpatrick_scale:false,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:"😥",fitzpatrick_scale:false,category:"people"},drooling_face:{keywords:["face"],char:"🤤",fitzpatrick_scale:false,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:"😪",fitzpatrick_scale:false,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:"😓",fitzpatrick_scale:false,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:"🥵",fitzpatrick_scale:false,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:"🥶",fitzpatrick_scale:false,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:"😭",fitzpatrick_scale:false,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:"😵",fitzpatrick_scale:false,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:"😲",fitzpatrick_scale:false,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:"🤐",fitzpatrick_scale:false,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:"🤢",fitzpatrick_scale:false,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:"🤧",fitzpatrick_scale:false,category:"people"},vomiting:{keywords:["face","sick"],char:"🤮",fitzpatrick_scale:false,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:"😷",fitzpatrick_scale:false,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:"🤒",fitzpatrick_scale:false,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:"🤕",fitzpatrick_scale:false,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:"🥴",fitzpatrick_scale:false,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:"😴",fitzpatrick_scale:false,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:"💤",fitzpatrick_scale:false,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:"💩",fitzpatrick_scale:false,category:"people"},smiling_imp:{keywords:["devil","horns"],char:"😈",fitzpatrick_scale:false,category:"people"},imp:{keywords:["devil","angry","horns"],char:"👿",fitzpatrick_scale:false,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:"👹",fitzpatrick_scale:false,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:"👺",fitzpatrick_scale:false,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:"💀",fitzpatrick_scale:false,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:"👻",fitzpatrick_scale:false,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:"👽",fitzpatrick_scale:false,category:"people"},robot:{keywords:["computer","machine","bot"],char:"🤖",fitzpatrick_scale:false,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:"😺",fitzpatrick_scale:false,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:"😸",fitzpatrick_scale:false,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:"😹",fitzpatrick_scale:false,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:"😻",fitzpatrick_scale:false,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:"😼",fitzpatrick_scale:false,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:"😽",fitzpatrick_scale:false,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:"🙀",fitzpatrick_scale:false,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:"😿",fitzpatrick_scale:false,category:"people"},pouting_cat:{keywords:["animal","cats"],char:"😾",fitzpatrick_scale:false,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:"🤲",fitzpatrick_scale:true,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:"🙌",fitzpatrick_scale:true,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:"👏",fitzpatrick_scale:true,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:"👋",fitzpatrick_scale:true,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:"🤙",fitzpatrick_scale:true,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:"👍",fitzpatrick_scale:true,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:"👎",fitzpatrick_scale:true,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:"👊",fitzpatrick_scale:true,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:"✊",fitzpatrick_scale:true,category:"people"},fist_left:{keywords:["hand","fistbump"],char:"🤛",fitzpatrick_scale:true,category:"people"},fist_right:{keywords:["hand","fistbump"],char:"🤜",fitzpatrick_scale:true,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:"✌",fitzpatrick_scale:true,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:"👌",fitzpatrick_scale:true,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:"✋",fitzpatrick_scale:true,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:"🤚",fitzpatrick_scale:true,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:"👐",fitzpatrick_scale:true,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:"💪",fitzpatrick_scale:true,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:"🙏",fitzpatrick_scale:true,category:"people"},foot:{keywords:["kick","stomp"],char:"🦶",fitzpatrick_scale:true,category:"people"},leg:{keywords:["kick","limb"],char:"🦵",fitzpatrick_scale:true,category:"people"},handshake:{keywords:["agreement","shake"],char:"🤝",fitzpatrick_scale:false,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:"☝",fitzpatrick_scale:true,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:"👆",fitzpatrick_scale:true,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:"👇",fitzpatrick_scale:true,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:"👈",fitzpatrick_scale:true,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:"👉",fitzpatrick_scale:true,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:"🖕",fitzpatrick_scale:true,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:"🖐",fitzpatrick_scale:true,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:"🤟",fitzpatrick_scale:true,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:"🤘",fitzpatrick_scale:true,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:"🤞",fitzpatrick_scale:true,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:"🖖",fitzpatrick_scale:true,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:"✍",fitzpatrick_scale:true,category:"people"},selfie:{keywords:["camera","phone"],char:"🤳",fitzpatrick_scale:true,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:"💅",fitzpatrick_scale:true,category:"people"},lips:{keywords:["mouth","kiss"],char:"👄",fitzpatrick_scale:false,category:"people"},tooth:{keywords:["teeth","dentist"],char:"🦷",fitzpatrick_scale:false,category:"people"},tongue:{keywords:["mouth","playful"],char:"👅",fitzpatrick_scale:false,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:"👂",fitzpatrick_scale:true,category:"people"},nose:{keywords:["smell","sniff"],char:"👃",fitzpatrick_scale:true,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:"👁",fitzpatrick_scale:false,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:"👀",fitzpatrick_scale:false,category:"people"},brain:{keywords:["smart","intelligent"],char:"🧠",fitzpatrick_scale:false,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:"👤",fitzpatrick_scale:false,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:"👥",fitzpatrick_scale:false,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:"🗣",fitzpatrick_scale:false,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:"👶",fitzpatrick_scale:true,category:"people"},child:{keywords:["gender-neutral","young"],char:"🧒",fitzpatrick_scale:true,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:"👦",fitzpatrick_scale:true,category:"people"},girl:{keywords:["female","woman","teenager"],char:"👧",fitzpatrick_scale:true,category:"people"},adult:{keywords:["gender-neutral","person"],char:"🧑",fitzpatrick_scale:true,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:"👨",fitzpatrick_scale:true,category:"people"},woman:{keywords:["female","girls","lady"],char:"👩",fitzpatrick_scale:true,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:"👱‍♀️",fitzpatrick_scale:true,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:"👱",fitzpatrick_scale:true,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:"🧔",fitzpatrick_scale:true,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:"🧓",fitzpatrick_scale:true,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:"👴",fitzpatrick_scale:true,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:"👵",fitzpatrick_scale:true,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:"👲",fitzpatrick_scale:true,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:"🧕",fitzpatrick_scale:true,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:"👳‍♀️",fitzpatrick_scale:true,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:"👳",fitzpatrick_scale:true,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:"👮‍♀️",fitzpatrick_scale:true,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:"👮",fitzpatrick_scale:true,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:"👷‍♀️",fitzpatrick_scale:true,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:"👷",fitzpatrick_scale:true,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:"💂‍♀️",fitzpatrick_scale:true,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:"💂",fitzpatrick_scale:true,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:"🕵️‍♀️",fitzpatrick_scale:true,category:"people"},male_detective:{keywords:["human","spy","detective"],char:"🕵",fitzpatrick_scale:true,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:"👩‍⚕️",fitzpatrick_scale:true,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:"👨‍⚕️",fitzpatrick_scale:true,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:"👩‍🌾",fitzpatrick_scale:true,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:"👨‍🌾",fitzpatrick_scale:true,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:"👩‍🍳",fitzpatrick_scale:true,category:"people"},man_cook:{keywords:["chef","man","human"],char:"👨‍🍳",fitzpatrick_scale:true,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:"👩‍🎓",fitzpatrick_scale:true,category:"people"},man_student:{keywords:["graduate","man","human"],char:"👨‍🎓",fitzpatrick_scale:true,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:"👩‍🎤",fitzpatrick_scale:true,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:"👨‍🎤",fitzpatrick_scale:true,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:"👩‍🏫",fitzpatrick_scale:true,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:"👨‍🏫",fitzpatrick_scale:true,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:"👩‍🏭",fitzpatrick_scale:true,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:"👨‍🏭",fitzpatrick_scale:true,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:"👩‍💻",fitzpatrick_scale:true,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:"👨‍💻",fitzpatrick_scale:true,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:"👩‍💼",fitzpatrick_scale:true,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:"👨‍💼",fitzpatrick_scale:true,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:"👩‍🔧",fitzpatrick_scale:true,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:"👨‍🔧",fitzpatrick_scale:true,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:"👩‍🔬",fitzpatrick_scale:true,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:"👨‍🔬",fitzpatrick_scale:true,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:"👩‍🎨",fitzpatrick_scale:true,category:"people"},man_artist:{keywords:["painter","man","human"],char:"👨‍🎨",fitzpatrick_scale:true,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:"👩‍🚒",fitzpatrick_scale:true,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:"👨‍🚒",fitzpatrick_scale:true,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:"👩‍✈️",fitzpatrick_scale:true,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:"👨‍✈️",fitzpatrick_scale:true,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:"👩‍🚀",fitzpatrick_scale:true,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:"👨‍🚀",fitzpatrick_scale:true,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:"👩‍⚖️",fitzpatrick_scale:true,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:"👨‍⚖️",fitzpatrick_scale:true,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:"🦸‍♀️",fitzpatrick_scale:true,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:"🦸‍♂️",fitzpatrick_scale:true,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:"🦹‍♀️",fitzpatrick_scale:true,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:"🦹‍♂️",fitzpatrick_scale:true,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:"🤶",fitzpatrick_scale:true,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:"🎅",fitzpatrick_scale:true,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:"🧙‍♀️",fitzpatrick_scale:true,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:"🧙‍♂️",fitzpatrick_scale:true,category:"people"},woman_elf:{keywords:["woman","female"],char:"🧝‍♀️",fitzpatrick_scale:true,category:"people"},man_elf:{keywords:["man","male"],char:"🧝‍♂️",fitzpatrick_scale:true,category:"people"},woman_vampire:{keywords:["woman","female"],char:"🧛‍♀️",fitzpatrick_scale:true,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:"🧛‍♂️",fitzpatrick_scale:true,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:"🧟‍♀️",fitzpatrick_scale:false,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:"🧟‍♂️",fitzpatrick_scale:false,category:"people"},woman_genie:{keywords:["woman","female"],char:"🧞‍♀️",fitzpatrick_scale:false,category:"people"},man_genie:{keywords:["man","male"],char:"🧞‍♂️",fitzpatrick_scale:false,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:"🧜‍♀️",fitzpatrick_scale:true,category:"people"},merman:{keywords:["man","male","triton"],char:"🧜‍♂️",fitzpatrick_scale:true,category:"people"},woman_fairy:{keywords:["woman","female"],char:"🧚‍♀️",fitzpatrick_scale:true,category:"people"},man_fairy:{keywords:["man","male"],char:"🧚‍♂️",fitzpatrick_scale:true,category:"people"},angel:{keywords:["heaven","wings","halo"],char:"👼",fitzpatrick_scale:true,category:"people"},pregnant_woman:{keywords:["baby"],char:"🤰",fitzpatrick_scale:true,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:"🤱",fitzpatrick_scale:true,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:"👸",fitzpatrick_scale:true,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:"🤴",fitzpatrick_scale:true,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:"👰",fitzpatrick_scale:true,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:"🤵",fitzpatrick_scale:true,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:"🏃‍♀️",fitzpatrick_scale:true,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:"🏃",fitzpatrick_scale:true,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:"🚶‍♀️",fitzpatrick_scale:true,category:"people"},walking_man:{keywords:["human","feet","steps"],char:"🚶",fitzpatrick_scale:true,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:"💃",fitzpatrick_scale:true,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:"🕺",fitzpatrick_scale:true,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:"👯",fitzpatrick_scale:false,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:"👯‍♂️",fitzpatrick_scale:false,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:"👫",fitzpatrick_scale:false,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:"👬",fitzpatrick_scale:false,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:"👭",fitzpatrick_scale:false,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:"🙇‍♀️",fitzpatrick_scale:true,category:"people"},bowing_man:{keywords:["man","male","boy"],char:"🙇",fitzpatrick_scale:true,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:"🤦‍♂️",fitzpatrick_scale:true,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:"🤦‍♀️",fitzpatrick_scale:true,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:"🤷",fitzpatrick_scale:true,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:"🤷‍♂️",fitzpatrick_scale:true,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:"💁",fitzpatrick_scale:true,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:"💁‍♂️",fitzpatrick_scale:true,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:"🙅",fitzpatrick_scale:true,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:"🙅‍♂️",fitzpatrick_scale:true,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:"🙆",fitzpatrick_scale:true,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:"🙆‍♂️",fitzpatrick_scale:true,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:"🙋",fitzpatrick_scale:true,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:"🙋‍♂️",fitzpatrick_scale:true,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:"🙎",fitzpatrick_scale:true,category:"people"},pouting_man:{keywords:["male","boy","man"],char:"🙎‍♂️",fitzpatrick_scale:true,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:"🙍",fitzpatrick_scale:true,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:"🙍‍♂️",fitzpatrick_scale:true,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:"💇",fitzpatrick_scale:true,category:"people"},haircut_man:{keywords:["male","boy","man"],char:"💇‍♂️",fitzpatrick_scale:true,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:"💆",fitzpatrick_scale:true,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:"💆‍♂️",fitzpatrick_scale:true,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:"🧖‍♀️",fitzpatrick_scale:true,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:"🧖‍♂️",fitzpatrick_scale:true,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"💑",fitzpatrick_scale:false,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"👩‍❤️‍👩",fitzpatrick_scale:false,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"👨‍❤️‍👨",fitzpatrick_scale:false,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"💏",fitzpatrick_scale:false,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"👩‍❤️‍💋‍👩",fitzpatrick_scale:false,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:"👨‍❤️‍💋‍👨",fitzpatrick_scale:false,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:"👪",fitzpatrick_scale:false,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:"👨‍👩‍👧",fitzpatrick_scale:false,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"👨‍👩‍👧‍👦",fitzpatrick_scale:false,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"👨‍👩‍👦‍👦",fitzpatrick_scale:false,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"👨‍👩‍👧‍👧",fitzpatrick_scale:false,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:"👩‍👩‍👦",fitzpatrick_scale:false,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:"👩‍👩‍👧",fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"👩‍👩‍👧‍👦",fitzpatrick_scale:false,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"👩‍👩‍👦‍👦",fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"👩‍👩‍👧‍👧",fitzpatrick_scale:false,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:"👨‍👨‍👦",fitzpatrick_scale:false,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:"👨‍👨‍👧",fitzpatrick_scale:false,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:"👨‍👨‍👧‍👦",fitzpatrick_scale:false,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:"👨‍👨‍👦‍👦",fitzpatrick_scale:false,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:"👨‍👨‍👧‍👧",fitzpatrick_scale:false,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:"👩‍👦",fitzpatrick_scale:false,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:"👩‍👧",fitzpatrick_scale:false,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:"👩‍👧‍👦",fitzpatrick_scale:false,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:"👩‍👦‍👦",fitzpatrick_scale:false,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:"👩‍👧‍👧",fitzpatrick_scale:false,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:"👨‍👦",fitzpatrick_scale:false,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:"👨‍👧",fitzpatrick_scale:false,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:"👨‍👧‍👦",fitzpatrick_scale:false,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:"👨‍👦‍👦",fitzpatrick_scale:false,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:"👨‍👧‍👧",fitzpatrick_scale:false,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:"🧶",fitzpatrick_scale:false,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:"🧵",fitzpatrick_scale:false,category:"people"},coat:{keywords:["jacket"],char:"🧥",fitzpatrick_scale:false,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:"🥼",fitzpatrick_scale:false,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:"👚",fitzpatrick_scale:false,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:"👕",fitzpatrick_scale:false,category:"people"},jeans:{keywords:["fashion","shopping"],char:"👖",fitzpatrick_scale:false,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:"👔",fitzpatrick_scale:false,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:"👗",fitzpatrick_scale:false,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:"👙",fitzpatrick_scale:false,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:"👘",fitzpatrick_scale:false,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:"💄",fitzpatrick_scale:false,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:"💋",fitzpatrick_scale:false,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:"👣",fitzpatrick_scale:false,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:"🥿",fitzpatrick_scale:false,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:"👠",fitzpatrick_scale:false,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:"👡",fitzpatrick_scale:false,category:"people"},boot:{keywords:["shoes","fashion"],char:"👢",fitzpatrick_scale:false,category:"people"},mans_shoe:{keywords:["fashion","male"],char:"👞",fitzpatrick_scale:false,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:"👟",fitzpatrick_scale:false,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:"🥾",fitzpatrick_scale:false,category:"people"},socks:{keywords:["stockings","clothes"],char:"🧦",fitzpatrick_scale:false,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:"🧤",fitzpatrick_scale:false,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:"🧣",fitzpatrick_scale:false,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:"👒",fitzpatrick_scale:false,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:"🎩",fitzpatrick_scale:false,category:"people"},billed_hat:{keywords:["cap","baseball"],char:"🧢",fitzpatrick_scale:false,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:"⛑",fitzpatrick_scale:false,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:"🎓",fitzpatrick_scale:false,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:"👑",fitzpatrick_scale:false,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:"🎒",fitzpatrick_scale:false,category:"people"},luggage:{keywords:["packing","travel"],char:"🧳",fitzpatrick_scale:false,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:"👝",fitzpatrick_scale:false,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:"👛",fitzpatrick_scale:false,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:"👜",fitzpatrick_scale:false,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:"💼",fitzpatrick_scale:false,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:"👓",fitzpatrick_scale:false,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:"🕶",fitzpatrick_scale:false,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:"🥽",fitzpatrick_scale:false,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:"💍",fitzpatrick_scale:false,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:"🌂",fitzpatrick_scale:false,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:"🐶",fitzpatrick_scale:false,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:"🐱",fitzpatrick_scale:false,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:"🐭",fitzpatrick_scale:false,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:"🐹",fitzpatrick_scale:false,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:"🐰",fitzpatrick_scale:false,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:"🦊",fitzpatrick_scale:false,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:"🐻",fitzpatrick_scale:false,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:"🐼",fitzpatrick_scale:false,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:"🐨",fitzpatrick_scale:false,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:"🐯",fitzpatrick_scale:false,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:"🦁",fitzpatrick_scale:false,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:"🐮",fitzpatrick_scale:false,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:"🐷",fitzpatrick_scale:false,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:"🐽",fitzpatrick_scale:false,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:"🐸",fitzpatrick_scale:false,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:"🦑",fitzpatrick_scale:false,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:"🐙",fitzpatrick_scale:false,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:"🦐",fitzpatrick_scale:false,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:"🐵",fitzpatrick_scale:false,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:"🦍",fitzpatrick_scale:false,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:"🙈",fitzpatrick_scale:false,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:"🙉",fitzpatrick_scale:false,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:"🙊",fitzpatrick_scale:false,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:"🐒",fitzpatrick_scale:false,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:"🐔",fitzpatrick_scale:false,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:"🐧",fitzpatrick_scale:false,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:"🐦",fitzpatrick_scale:false,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:"🐤",fitzpatrick_scale:false,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:"🐣",fitzpatrick_scale:false,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:"🐥",fitzpatrick_scale:false,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:"🦆",fitzpatrick_scale:false,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:"🦅",fitzpatrick_scale:false,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:"🦉",fitzpatrick_scale:false,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:"🦇",fitzpatrick_scale:false,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:"🐺",fitzpatrick_scale:false,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:"🐗",fitzpatrick_scale:false,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:"🐴",fitzpatrick_scale:false,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:"🦄",fitzpatrick_scale:false,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:"🐝",fitzpatrick_scale:false,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:"🐛",fitzpatrick_scale:false,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:"🦋",fitzpatrick_scale:false,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:"🐌",fitzpatrick_scale:false,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:"🐞",fitzpatrick_scale:false,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:"🐜",fitzpatrick_scale:false,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:"🦗",fitzpatrick_scale:false,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:"🕷",fitzpatrick_scale:false,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:"🦂",fitzpatrick_scale:false,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:"🦀",fitzpatrick_scale:false,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:"🐍",fitzpatrick_scale:false,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:"🦎",fitzpatrick_scale:false,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:"🦖",fitzpatrick_scale:false,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:"🦕",fitzpatrick_scale:false,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:"🐢",fitzpatrick_scale:false,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:"🐠",fitzpatrick_scale:false,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:"🐟",fitzpatrick_scale:false,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:"🐡",fitzpatrick_scale:false,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:"🐬",fitzpatrick_scale:false,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:"🦈",fitzpatrick_scale:false,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:"🐳",fitzpatrick_scale:false,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:"🐋",fitzpatrick_scale:false,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:"🐊",fitzpatrick_scale:false,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:"🐆",fitzpatrick_scale:false,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:"🦓",fitzpatrick_scale:false,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:"🐅",fitzpatrick_scale:false,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:"🐃",fitzpatrick_scale:false,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:"🐂",fitzpatrick_scale:false,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:"🐄",fitzpatrick_scale:false,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:"🦌",fitzpatrick_scale:false,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:"🐪",fitzpatrick_scale:false,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:"🐫",fitzpatrick_scale:false,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:"🦒",fitzpatrick_scale:false,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:"🐘",fitzpatrick_scale:false,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:"🦏",fitzpatrick_scale:false,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:"🐐",fitzpatrick_scale:false,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:"🐏",fitzpatrick_scale:false,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:"🐑",fitzpatrick_scale:false,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:"🐎",fitzpatrick_scale:false,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:"🐖",fitzpatrick_scale:false,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:"🐀",fitzpatrick_scale:false,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:"🐁",fitzpatrick_scale:false,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:"🐓",fitzpatrick_scale:false,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:"🦃",fitzpatrick_scale:false,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:"🕊",fitzpatrick_scale:false,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:"🐕",fitzpatrick_scale:false,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:"🐩",fitzpatrick_scale:false,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:"🐈",fitzpatrick_scale:false,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:"🐇",fitzpatrick_scale:false,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:"🐿",fitzpatrick_scale:false,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:"🦔",fitzpatrick_scale:false,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:"🦝",fitzpatrick_scale:false,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:"🦙",fitzpatrick_scale:false,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:"🦛",fitzpatrick_scale:false,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:"🦘",fitzpatrick_scale:false,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:"🦡",fitzpatrick_scale:false,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:"🦢",fitzpatrick_scale:false,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:"🦚",fitzpatrick_scale:false,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:"🦜",fitzpatrick_scale:false,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:"🦞",fitzpatrick_scale:false,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:"🦟",fitzpatrick_scale:false,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:"🐾",fitzpatrick_scale:false,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:"🐉",fitzpatrick_scale:false,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:"🐲",fitzpatrick_scale:false,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:"🌵",fitzpatrick_scale:false,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:"🎄",fitzpatrick_scale:false,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:"🌲",fitzpatrick_scale:false,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:"🌳",fitzpatrick_scale:false,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:"🌴",fitzpatrick_scale:false,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:"🌱",fitzpatrick_scale:false,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:"🌿",fitzpatrick_scale:false,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:"☘",fitzpatrick_scale:false,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:"🍀",fitzpatrick_scale:false,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:"🎍",fitzpatrick_scale:false,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:"🎋",fitzpatrick_scale:false,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:"🍃",fitzpatrick_scale:false,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:"🍂",fitzpatrick_scale:false,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:"🍁",fitzpatrick_scale:false,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:"🌾",fitzpatrick_scale:false,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:"🌺",fitzpatrick_scale:false,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:"🌻",fitzpatrick_scale:false,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:"🌹",fitzpatrick_scale:false,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:"🥀",fitzpatrick_scale:false,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:"🌷",fitzpatrick_scale:false,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:"🌼",fitzpatrick_scale:false,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:"🌸",fitzpatrick_scale:false,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:"💐",fitzpatrick_scale:false,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:"🍄",fitzpatrick_scale:false,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:"🌰",fitzpatrick_scale:false,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:"🎃",fitzpatrick_scale:false,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:"🐚",fitzpatrick_scale:false,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:"🕸",fitzpatrick_scale:false,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:"🌎",fitzpatrick_scale:false,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:"🌍",fitzpatrick_scale:false,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:"🌏",fitzpatrick_scale:false,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:"🌕",fitzpatrick_scale:false,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:"🌖",fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌗",fitzpatrick_scale:false,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌘",fitzpatrick_scale:false,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌑",fitzpatrick_scale:false,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌒",fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌓",fitzpatrick_scale:false,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:"🌔",fitzpatrick_scale:false,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌚",fitzpatrick_scale:false,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌝",fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌛",fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌜",fitzpatrick_scale:false,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:"🌞",fitzpatrick_scale:false,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:"🌙",fitzpatrick_scale:false,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:"⭐",fitzpatrick_scale:false,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:"🌟",fitzpatrick_scale:false,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:"💫",fitzpatrick_scale:false,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:"✨",fitzpatrick_scale:false,category:"animals_and_nature"},comet:{keywords:["space"],char:"☄",fitzpatrick_scale:false,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:"☀️",fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:"🌤",fitzpatrick_scale:false,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:"⛅",fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:"🌥",fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:"🌦",fitzpatrick_scale:false,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:"☁️",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:"🌧",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:"⛈",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:"🌩",fitzpatrick_scale:false,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:"⚡",fitzpatrick_scale:false,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:"🔥",fitzpatrick_scale:false,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:"💥",fitzpatrick_scale:false,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:"❄️",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:"🌨",fitzpatrick_scale:false,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:"⛄",fitzpatrick_scale:false,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:"☃",fitzpatrick_scale:false,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:"🌬",fitzpatrick_scale:false,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:"💨",fitzpatrick_scale:false,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:"🌪",fitzpatrick_scale:false,category:"animals_and_nature"},fog:{keywords:["weather"],char:"🌫",fitzpatrick_scale:false,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:"☂",fitzpatrick_scale:false,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:"☔",fitzpatrick_scale:false,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:"💧",fitzpatrick_scale:false,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:"💦",fitzpatrick_scale:false,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:"🌊",fitzpatrick_scale:false,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:"🍏",fitzpatrick_scale:false,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:"🍎",fitzpatrick_scale:false,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:"🍐",fitzpatrick_scale:false,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:"🍊",fitzpatrick_scale:false,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:"🍋",fitzpatrick_scale:false,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:"🍌",fitzpatrick_scale:false,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:"🍉",fitzpatrick_scale:false,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:"🍇",fitzpatrick_scale:false,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:"🍓",fitzpatrick_scale:false,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:"🍈",fitzpatrick_scale:false,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:"🍒",fitzpatrick_scale:false,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:"🍑",fitzpatrick_scale:false,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:"🍍",fitzpatrick_scale:false,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:"🥥",fitzpatrick_scale:false,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:"🥝",fitzpatrick_scale:false,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:"🥭",fitzpatrick_scale:false,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:"🥑",fitzpatrick_scale:false,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:"🥦",fitzpatrick_scale:false,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:"🍅",fitzpatrick_scale:false,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:"🍆",fitzpatrick_scale:false,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:"🥒",fitzpatrick_scale:false,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:"🥕",fitzpatrick_scale:false,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:"🌶",fitzpatrick_scale:false,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:"🥔",fitzpatrick_scale:false,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:"🌽",fitzpatrick_scale:false,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:"🥬",fitzpatrick_scale:false,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:"🍠",fitzpatrick_scale:false,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:"🥜",fitzpatrick_scale:false,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:"🍯",fitzpatrick_scale:false,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:"🥐",fitzpatrick_scale:false,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:"🍞",fitzpatrick_scale:false,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:"🥖",fitzpatrick_scale:false,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:"🥯",fitzpatrick_scale:false,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:"🥨",fitzpatrick_scale:false,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:"🧀",fitzpatrick_scale:false,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:"🥚",fitzpatrick_scale:false,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:"🥓",fitzpatrick_scale:false,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:"🥩",fitzpatrick_scale:false,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:"🥞",fitzpatrick_scale:false,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:"🍗",fitzpatrick_scale:false,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:"🍖",fitzpatrick_scale:false,category:"food_and_drink"},bone:{keywords:["skeleton"],char:"🦴",fitzpatrick_scale:false,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:"🍤",fitzpatrick_scale:false,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:"🍳",fitzpatrick_scale:false,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:"🍔",fitzpatrick_scale:false,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:"🍟",fitzpatrick_scale:false,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:"🥙",fitzpatrick_scale:false,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:"🌭",fitzpatrick_scale:false,category:"food_and_drink"},pizza:{keywords:["food","party"],char:"🍕",fitzpatrick_scale:false,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:"🥪",fitzpatrick_scale:false,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:"🥫",fitzpatrick_scale:false,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:"🍝",fitzpatrick_scale:false,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:"🌮",fitzpatrick_scale:false,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:"🌯",fitzpatrick_scale:false,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:"🥗",fitzpatrick_scale:false,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:"🥘",fitzpatrick_scale:false,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:"🍜",fitzpatrick_scale:false,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:"🍲",fitzpatrick_scale:false,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:"🍥",fitzpatrick_scale:false,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:"🥠",fitzpatrick_scale:false,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:"🍣",fitzpatrick_scale:false,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:"🍱",fitzpatrick_scale:false,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:"🍛",fitzpatrick_scale:false,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:"🍙",fitzpatrick_scale:false,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:"🍚",fitzpatrick_scale:false,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:"🍘",fitzpatrick_scale:false,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:"🍢",fitzpatrick_scale:false,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:"🍡",fitzpatrick_scale:false,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:"🍧",fitzpatrick_scale:false,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:"🍨",fitzpatrick_scale:false,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:"🍦",fitzpatrick_scale:false,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:"🥧",fitzpatrick_scale:false,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:"🍰",fitzpatrick_scale:false,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:"🧁",fitzpatrick_scale:false,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:"🥮",fitzpatrick_scale:false,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:"🎂",fitzpatrick_scale:false,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:"🍮",fitzpatrick_scale:false,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:"🍬",fitzpatrick_scale:false,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:"🍭",fitzpatrick_scale:false,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:"🍫",fitzpatrick_scale:false,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:"🍿",fitzpatrick_scale:false,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:"🥟",fitzpatrick_scale:false,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:"🍩",fitzpatrick_scale:false,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:"🍪",fitzpatrick_scale:false,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:"🥛",fitzpatrick_scale:false,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"🍺",fitzpatrick_scale:false,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"🍻",fitzpatrick_scale:false,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:"🥂",fitzpatrick_scale:false,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:"🍷",fitzpatrick_scale:false,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:"🥃",fitzpatrick_scale:false,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:"🍸",fitzpatrick_scale:false,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:"🍹",fitzpatrick_scale:false,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:"🍾",fitzpatrick_scale:false,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:"🍶",fitzpatrick_scale:false,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:"🍵",fitzpatrick_scale:false,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:"🥤",fitzpatrick_scale:false,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:"☕",fitzpatrick_scale:false,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:"🍼",fitzpatrick_scale:false,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:"🧂",fitzpatrick_scale:false,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:"🥄",fitzpatrick_scale:false,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:"🍴",fitzpatrick_scale:false,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:"🍽",fitzpatrick_scale:false,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:"🥣",fitzpatrick_scale:false,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:"🥡",fitzpatrick_scale:false,category:"food_and_drink"},chopsticks:{keywords:["food"],char:"🥢",fitzpatrick_scale:false,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:"⚽",fitzpatrick_scale:false,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:"🏀",fitzpatrick_scale:false,category:"activity"},football:{keywords:["sports","balls","NFL"],char:"🏈",fitzpatrick_scale:false,category:"activity"},baseball:{keywords:["sports","balls"],char:"⚾",fitzpatrick_scale:false,category:"activity"},softball:{keywords:["sports","balls"],char:"🥎",fitzpatrick_scale:false,category:"activity"},tennis:{keywords:["sports","balls","green"],char:"🎾",fitzpatrick_scale:false,category:"activity"},volleyball:{keywords:["sports","balls"],char:"🏐",fitzpatrick_scale:false,category:"activity"},rugby_football:{keywords:["sports","team"],char:"🏉",fitzpatrick_scale:false,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:"🥏",fitzpatrick_scale:false,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:"🎱",fitzpatrick_scale:false,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:"⛳",fitzpatrick_scale:false,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:"🏌️‍♀️",fitzpatrick_scale:false,category:"activity"},golfing_man:{keywords:["sports","business"],char:"🏌",fitzpatrick_scale:true,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:"🏓",fitzpatrick_scale:false,category:"activity"},badminton:{keywords:["sports"],char:"🏸",fitzpatrick_scale:false,category:"activity"},goal_net:{keywords:["sports"],char:"🥅",fitzpatrick_scale:false,category:"activity"},ice_hockey:{keywords:["sports"],char:"🏒",fitzpatrick_scale:false,category:"activity"},field_hockey:{keywords:["sports"],char:"🏑",fitzpatrick_scale:false,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:"🥍",fitzpatrick_scale:false,category:"activity"},cricket:{keywords:["sports"],char:"🏏",fitzpatrick_scale:false,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:"🎿",fitzpatrick_scale:false,category:"activity"},skier:{keywords:["sports","winter","snow"],char:"⛷",fitzpatrick_scale:false,category:"activity"},snowboarder:{keywords:["sports","winter"],char:"🏂",fitzpatrick_scale:true,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:"🤺",fitzpatrick_scale:false,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:"🤼‍♀️",fitzpatrick_scale:false,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:"🤼‍♂️",fitzpatrick_scale:false,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:"🤸‍♀️",fitzpatrick_scale:true,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:"🤸‍♂️",fitzpatrick_scale:true,category:"activity"},woman_playing_handball:{keywords:["sports"],char:"🤾‍♀️",fitzpatrick_scale:true,category:"activity"},man_playing_handball:{keywords:["sports"],char:"🤾‍♂️",fitzpatrick_scale:true,category:"activity"},ice_skate:{keywords:["sports"],char:"⛸",fitzpatrick_scale:false,category:"activity"},curling_stone:{keywords:["sports"],char:"🥌",fitzpatrick_scale:false,category:"activity"},skateboard:{keywords:["board"],char:"🛹",fitzpatrick_scale:false,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:"🛷",fitzpatrick_scale:false,category:"activity"},bow_and_arrow:{keywords:["sports"],char:"🏹",fitzpatrick_scale:false,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:"🎣",fitzpatrick_scale:false,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:"🥊",fitzpatrick_scale:false,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:"🥋",fitzpatrick_scale:false,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:"🚣‍♀️",fitzpatrick_scale:true,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:"🚣",fitzpatrick_scale:true,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:"🧗‍♀️",fitzpatrick_scale:true,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:"🧗‍♂️",fitzpatrick_scale:true,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:"🏊‍♀️",fitzpatrick_scale:true,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:"🏊",fitzpatrick_scale:true,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:"🤽‍♀️",fitzpatrick_scale:true,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:"🤽‍♂️",fitzpatrick_scale:true,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:"🧘‍♀️",fitzpatrick_scale:true,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:"🧘‍♂️",fitzpatrick_scale:true,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:"🏄‍♀️",fitzpatrick_scale:true,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:"🏄",fitzpatrick_scale:true,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:"🛀",fitzpatrick_scale:true,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:"⛹️‍♀️",fitzpatrick_scale:true,category:"activity"},basketball_man:{keywords:["sports","human"],char:"⛹",fitzpatrick_scale:true,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:"🏋️‍♀️",fitzpatrick_scale:true,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:"🏋",fitzpatrick_scale:true,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:"🚴‍♀️",fitzpatrick_scale:true,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:"🚴",fitzpatrick_scale:true,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:"🚵‍♀️",fitzpatrick_scale:true,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:"🚵",fitzpatrick_scale:true,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:"🏇",fitzpatrick_scale:true,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:"🕴",fitzpatrick_scale:true,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:"🏆",fitzpatrick_scale:false,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:"🎽",fitzpatrick_scale:false,category:"activity"},medal_sports:{keywords:["award","winning"],char:"🏅",fitzpatrick_scale:false,category:"activity"},medal_military:{keywords:["award","winning","army"],char:"🎖",fitzpatrick_scale:false,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:"🥇",fitzpatrick_scale:false,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:"🥈",fitzpatrick_scale:false,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:"🥉",fitzpatrick_scale:false,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:"🎗",fitzpatrick_scale:false,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:"🏵",fitzpatrick_scale:false,category:"activity"},ticket:{keywords:["event","concert","pass"],char:"🎫",fitzpatrick_scale:false,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:"🎟",fitzpatrick_scale:false,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:"🎭",fitzpatrick_scale:false,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:"🎨",fitzpatrick_scale:false,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:"🎪",fitzpatrick_scale:false,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:"🤹‍♀️",fitzpatrick_scale:true,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:"🤹‍♂️",fitzpatrick_scale:true,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:"🎤",fitzpatrick_scale:false,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:"🎧",fitzpatrick_scale:false,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:"🎼",fitzpatrick_scale:false,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:"🎹",fitzpatrick_scale:false,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:"🥁",fitzpatrick_scale:false,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:"🎷",fitzpatrick_scale:false,category:"activity"},trumpet:{keywords:["music","brass"],char:"🎺",fitzpatrick_scale:false,category:"activity"},guitar:{keywords:["music","instrument"],char:"🎸",fitzpatrick_scale:false,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:"🎻",fitzpatrick_scale:false,category:"activity"},clapper:{keywords:["movie","film","record"],char:"🎬",fitzpatrick_scale:false,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:"🎮",fitzpatrick_scale:false,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:"👾",fitzpatrick_scale:false,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:"🎯",fitzpatrick_scale:false,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:"🎲",fitzpatrick_scale:false,category:"activity"},chess_pawn:{keywords:["expendable"],char:"♟",fitzpatrick_scale:false,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:"🎰",fitzpatrick_scale:false,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:"🧩",fitzpatrick_scale:false,category:"activity"},bowling:{keywords:["sports","fun","play"],char:"🎳",fitzpatrick_scale:false,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:"🚗",fitzpatrick_scale:false,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:"🚕",fitzpatrick_scale:false,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:"🚙",fitzpatrick_scale:false,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:"🚌",fitzpatrick_scale:false,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:"🚎",fitzpatrick_scale:false,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:"🏎",fitzpatrick_scale:false,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:"🚓",fitzpatrick_scale:false,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:"🚑",fitzpatrick_scale:false,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:"🚒",fitzpatrick_scale:false,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:"🚐",fitzpatrick_scale:false,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:"🚚",fitzpatrick_scale:false,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:"🚛",fitzpatrick_scale:false,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:"🚜",fitzpatrick_scale:false,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:"🛴",fitzpatrick_scale:false,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:"🏍",fitzpatrick_scale:false,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:"🚲",fitzpatrick_scale:false,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:"🛵",fitzpatrick_scale:false,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:"🚨",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:"🚔",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:"🚍",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:"🚘",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:"🚖",fitzpatrick_scale:false,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:"🚡",fitzpatrick_scale:false,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:"🚠",fitzpatrick_scale:false,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:"🚟",fitzpatrick_scale:false,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:"🚃",fitzpatrick_scale:false,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:"🚋",fitzpatrick_scale:false,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:"🚝",fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:"🚄",fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:"🚅",fitzpatrick_scale:false,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:"🚈",fitzpatrick_scale:false,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:"🚞",fitzpatrick_scale:false,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:"🚂",fitzpatrick_scale:false,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:"🚆",fitzpatrick_scale:false,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:"🚇",fitzpatrick_scale:false,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:"🚊",fitzpatrick_scale:false,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:"🚉",fitzpatrick_scale:false,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:"🛸",fitzpatrick_scale:false,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:"🚁",fitzpatrick_scale:false,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:"🛩",fitzpatrick_scale:false,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:"✈️",fitzpatrick_scale:false,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:"🛫",fitzpatrick_scale:false,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:"🛬",fitzpatrick_scale:false,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:"⛵",fitzpatrick_scale:false,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:"🛥",fitzpatrick_scale:false,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:"🚤",fitzpatrick_scale:false,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:"⛴",fitzpatrick_scale:false,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:"🛳",fitzpatrick_scale:false,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:"🚀",fitzpatrick_scale:false,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:"🛰",fitzpatrick_scale:false,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:"💺",fitzpatrick_scale:false,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:"🛶",fitzpatrick_scale:false,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:"⚓",fitzpatrick_scale:false,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:"🚧",fitzpatrick_scale:false,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:"⛽",fitzpatrick_scale:false,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:"🚏",fitzpatrick_scale:false,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:"🚦",fitzpatrick_scale:false,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:"🚥",fitzpatrick_scale:false,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:"🏁",fitzpatrick_scale:false,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:"🚢",fitzpatrick_scale:false,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:"🎡",fitzpatrick_scale:false,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:"🎢",fitzpatrick_scale:false,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:"🎠",fitzpatrick_scale:false,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:"🏗",fitzpatrick_scale:false,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:"🌁",fitzpatrick_scale:false,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:"🗼",fitzpatrick_scale:false,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:"🏭",fitzpatrick_scale:false,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:"⛲",fitzpatrick_scale:false,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:"🎑",fitzpatrick_scale:false,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:"⛰",fitzpatrick_scale:false,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:"🏔",fitzpatrick_scale:false,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:"🗻",fitzpatrick_scale:false,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:"🌋",fitzpatrick_scale:false,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:"🗾",fitzpatrick_scale:false,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:"🏕",fitzpatrick_scale:false,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:"⛺",fitzpatrick_scale:false,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:"🏞",fitzpatrick_scale:false,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:"🛣",fitzpatrick_scale:false,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:"🛤",fitzpatrick_scale:false,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:"🌅",fitzpatrick_scale:false,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:"🌄",fitzpatrick_scale:false,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:"🏜",fitzpatrick_scale:false,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:"🏖",fitzpatrick_scale:false,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:"🏝",fitzpatrick_scale:false,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:"🌇",fitzpatrick_scale:false,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:"🌆",fitzpatrick_scale:false,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:"🏙",fitzpatrick_scale:false,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:"🌃",fitzpatrick_scale:false,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:"🌉",fitzpatrick_scale:false,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:"🌌",fitzpatrick_scale:false,category:"travel_and_places"},stars:{keywords:["night","photo"],char:"🌠",fitzpatrick_scale:false,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:"🎇",fitzpatrick_scale:false,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:"🎆",fitzpatrick_scale:false,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:"🌈",fitzpatrick_scale:false,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:"🏘",fitzpatrick_scale:false,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:"🏰",fitzpatrick_scale:false,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:"🏯",fitzpatrick_scale:false,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:"🏟",fitzpatrick_scale:false,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:"🗽",fitzpatrick_scale:false,category:"travel_and_places"},house:{keywords:["building","home"],char:"🏠",fitzpatrick_scale:false,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:"🏡",fitzpatrick_scale:false,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:"🏚",fitzpatrick_scale:false,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:"🏢",fitzpatrick_scale:false,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:"🏬",fitzpatrick_scale:false,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:"🏣",fitzpatrick_scale:false,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:"🏤",fitzpatrick_scale:false,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:"🏥",fitzpatrick_scale:false,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:"🏦",fitzpatrick_scale:false,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:"🏨",fitzpatrick_scale:false,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:"🏪",fitzpatrick_scale:false,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:"🏫",fitzpatrick_scale:false,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:"🏩",fitzpatrick_scale:false,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:"💒",fitzpatrick_scale:false,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:"🏛",fitzpatrick_scale:false,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:"⛪",fitzpatrick_scale:false,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:"🕌",fitzpatrick_scale:false,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:"🕍",fitzpatrick_scale:false,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:"🕋",fitzpatrick_scale:false,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:"⛩",fitzpatrick_scale:false,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:"⌚",fitzpatrick_scale:false,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:"📱",fitzpatrick_scale:false,category:"objects"},calling:{keywords:["iphone","incoming"],char:"📲",fitzpatrick_scale:false,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:"💻",fitzpatrick_scale:false,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:"⌨",fitzpatrick_scale:false,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:"🖥",fitzpatrick_scale:false,category:"objects"},printer:{keywords:["paper","ink"],char:"🖨",fitzpatrick_scale:false,category:"objects"},computer_mouse:{keywords:["click"],char:"🖱",fitzpatrick_scale:false,category:"objects"},trackball:{keywords:["technology","trackpad"],char:"🖲",fitzpatrick_scale:false,category:"objects"},joystick:{keywords:["game","play"],char:"🕹",fitzpatrick_scale:false,category:"objects"},clamp:{keywords:["tool"],char:"🗜",fitzpatrick_scale:false,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:"💽",fitzpatrick_scale:false,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:"💾",fitzpatrick_scale:false,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:"💿",fitzpatrick_scale:false,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:"📀",fitzpatrick_scale:false,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:"📼",fitzpatrick_scale:false,category:"objects"},camera:{keywords:["gadgets","photography"],char:"📷",fitzpatrick_scale:false,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:"📸",fitzpatrick_scale:false,category:"objects"},video_camera:{keywords:["film","record"],char:"📹",fitzpatrick_scale:false,category:"objects"},movie_camera:{keywords:["film","record"],char:"🎥",fitzpatrick_scale:false,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:"📽",fitzpatrick_scale:false,category:"objects"},film_strip:{keywords:["movie"],char:"🎞",fitzpatrick_scale:false,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:"📞",fitzpatrick_scale:false,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:"☎️",fitzpatrick_scale:false,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:"📟",fitzpatrick_scale:false,category:"objects"},fax:{keywords:["communication","technology"],char:"📠",fitzpatrick_scale:false,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:"📺",fitzpatrick_scale:false,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:"📻",fitzpatrick_scale:false,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:"🎙",fitzpatrick_scale:false,category:"objects"},level_slider:{keywords:["scale"],char:"🎚",fitzpatrick_scale:false,category:"objects"},control_knobs:{keywords:["dial"],char:"🎛",fitzpatrick_scale:false,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:"🧭",fitzpatrick_scale:false,category:"objects"},stopwatch:{keywords:["time","deadline"],char:"⏱",fitzpatrick_scale:false,category:"objects"},timer_clock:{keywords:["alarm"],char:"⏲",fitzpatrick_scale:false,category:"objects"},alarm_clock:{keywords:["time","wake"],char:"⏰",fitzpatrick_scale:false,category:"objects"},mantelpiece_clock:{keywords:["time"],char:"🕰",fitzpatrick_scale:false,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:"⏳",fitzpatrick_scale:false,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:"⌛",fitzpatrick_scale:false,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:"📡",fitzpatrick_scale:false,category:"objects"},battery:{keywords:["power","energy","sustain"],char:"🔋",fitzpatrick_scale:false,category:"objects"},electric_plug:{keywords:["charger","power"],char:"🔌",fitzpatrick_scale:false,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:"💡",fitzpatrick_scale:false,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:"🔦",fitzpatrick_scale:false,category:"objects"},candle:{keywords:["fire","wax"],char:"🕯",fitzpatrick_scale:false,category:"objects"},fire_extinguisher:{keywords:["quench"],char:"🧯",fitzpatrick_scale:false,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:"🗑",fitzpatrick_scale:false,category:"objects"},oil_drum:{keywords:["barrell"],char:"🛢",fitzpatrick_scale:false,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:"💸",fitzpatrick_scale:false,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:"💵",fitzpatrick_scale:false,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:"💴",fitzpatrick_scale:false,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:"💶",fitzpatrick_scale:false,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:"💷",fitzpatrick_scale:false,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:"💰",fitzpatrick_scale:false,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:"💳",fitzpatrick_scale:false,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:"💎",fitzpatrick_scale:false,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:"⚖",fitzpatrick_scale:false,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:"🧰",fitzpatrick_scale:false,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:"🔧",fitzpatrick_scale:false,category:"objects"},hammer:{keywords:["tools","build","create"],char:"🔨",fitzpatrick_scale:false,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:"⚒",fitzpatrick_scale:false,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:"🛠",fitzpatrick_scale:false,category:"objects"},pick:{keywords:["tools","dig"],char:"⛏",fitzpatrick_scale:false,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:"🔩",fitzpatrick_scale:false,category:"objects"},gear:{keywords:["cog"],char:"⚙",fitzpatrick_scale:false,category:"objects"},brick:{keywords:["bricks"],char:"🧱",fitzpatrick_scale:false,category:"objects"},chains:{keywords:["lock","arrest"],char:"⛓",fitzpatrick_scale:false,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:"🧲",fitzpatrick_scale:false,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:"🔫",fitzpatrick_scale:false,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:"💣",fitzpatrick_scale:false,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:"🧨",fitzpatrick_scale:false,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:"🔪",fitzpatrick_scale:false,category:"objects"},dagger:{keywords:["weapon"],char:"🗡",fitzpatrick_scale:false,category:"objects"},crossed_swords:{keywords:["weapon"],char:"⚔",fitzpatrick_scale:false,category:"objects"},shield:{keywords:["protection","security"],char:"🛡",fitzpatrick_scale:false,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:"🚬",fitzpatrick_scale:false,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:"☠",fitzpatrick_scale:false,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:"⚰",fitzpatrick_scale:false,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:"⚱",fitzpatrick_scale:false,category:"objects"},amphora:{keywords:["vase","jar"],char:"🏺",fitzpatrick_scale:false,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:"🔮",fitzpatrick_scale:false,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:"📿",fitzpatrick_scale:false,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:"🧿",fitzpatrick_scale:false,category:"objects"},barber:{keywords:["hair","salon","style"],char:"💈",fitzpatrick_scale:false,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:"⚗",fitzpatrick_scale:false,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:"🔭",fitzpatrick_scale:false,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:"🔬",fitzpatrick_scale:false,category:"objects"},hole:{keywords:["embarrassing"],char:"🕳",fitzpatrick_scale:false,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:"💊",fitzpatrick_scale:false,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:"💉",fitzpatrick_scale:false,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:"🧬",fitzpatrick_scale:false,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:"🦠",fitzpatrick_scale:false,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:"🧫",fitzpatrick_scale:false,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:"🧪",fitzpatrick_scale:false,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:"🌡",fitzpatrick_scale:false,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:"🧹",fitzpatrick_scale:false,category:"objects"},basket:{keywords:["laundry"],char:"🧺",fitzpatrick_scale:false,category:"objects"},toilet_paper:{keywords:["roll"],char:"🧻",fitzpatrick_scale:false,category:"objects"},label:{keywords:["sale","tag"],char:"🏷",fitzpatrick_scale:false,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:"🔖",fitzpatrick_scale:false,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:"🚽",fitzpatrick_scale:false,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:"🚿",fitzpatrick_scale:false,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:"🛁",fitzpatrick_scale:false,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:"🧼",fitzpatrick_scale:false,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:"🧽",fitzpatrick_scale:false,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:"🧴",fitzpatrick_scale:false,category:"objects"},key:{keywords:["lock","door","password"],char:"🔑",fitzpatrick_scale:false,category:"objects"},old_key:{keywords:["lock","door","password"],char:"🗝",fitzpatrick_scale:false,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:"🛋",fitzpatrick_scale:false,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:"🛌",fitzpatrick_scale:true,category:"objects"},bed:{keywords:["sleep","rest"],char:"🛏",fitzpatrick_scale:false,category:"objects"},door:{keywords:["house","entry","exit"],char:"🚪",fitzpatrick_scale:false,category:"objects"},bellhop_bell:{keywords:["service"],char:"🛎",fitzpatrick_scale:false,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:"🧸",fitzpatrick_scale:false,category:"objects"},framed_picture:{keywords:["photography"],char:"🖼",fitzpatrick_scale:false,category:"objects"},world_map:{keywords:["location","direction"],char:"🗺",fitzpatrick_scale:false,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:"⛱",fitzpatrick_scale:false,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:"🗿",fitzpatrick_scale:false,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:"🛍",fitzpatrick_scale:false,category:"objects"},shopping_cart:{keywords:["trolley"],char:"🛒",fitzpatrick_scale:false,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:"🎈",fitzpatrick_scale:false,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:"🎏",fitzpatrick_scale:false,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:"🎀",fitzpatrick_scale:false,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:"🎁",fitzpatrick_scale:false,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:"🎊",fitzpatrick_scale:false,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:"🎉",fitzpatrick_scale:false,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:"🎎",fitzpatrick_scale:false,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:"🎐",fitzpatrick_scale:false,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:"🎌",fitzpatrick_scale:false,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:"🏮",fitzpatrick_scale:false,category:"objects"},red_envelope:{keywords:["gift"],char:"🧧",fitzpatrick_scale:false,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:"✉️",fitzpatrick_scale:false,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:"📩",fitzpatrick_scale:false,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:"📨",fitzpatrick_scale:false,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:"📧",fitzpatrick_scale:false,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:"💌",fitzpatrick_scale:false,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:"📮",fitzpatrick_scale:false,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:"📪",fitzpatrick_scale:false,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:"📫",fitzpatrick_scale:false,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:"📬",fitzpatrick_scale:false,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:"📭",fitzpatrick_scale:false,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:"📦",fitzpatrick_scale:false,category:"objects"},postal_horn:{keywords:["instrument","music"],char:"📯",fitzpatrick_scale:false,category:"objects"},inbox_tray:{keywords:["email","documents"],char:"📥",fitzpatrick_scale:false,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:"📤",fitzpatrick_scale:false,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:"📜",fitzpatrick_scale:false,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:"📃",fitzpatrick_scale:false,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:"📑",fitzpatrick_scale:false,category:"objects"},receipt:{keywords:["accounting","expenses"],char:"🧾",fitzpatrick_scale:false,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:"📊",fitzpatrick_scale:false,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:"📈",fitzpatrick_scale:false,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:"📉",fitzpatrick_scale:false,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:"📄",fitzpatrick_scale:false,category:"objects"},date:{keywords:["calendar","schedule"],char:"📅",fitzpatrick_scale:false,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:"📆",fitzpatrick_scale:false,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:"🗓",fitzpatrick_scale:false,category:"objects"},card_index:{keywords:["business","stationery"],char:"📇",fitzpatrick_scale:false,category:"objects"},card_file_box:{keywords:["business","stationery"],char:"🗃",fitzpatrick_scale:false,category:"objects"},ballot_box:{keywords:["election","vote"],char:"🗳",fitzpatrick_scale:false,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:"🗄",fitzpatrick_scale:false,category:"objects"},clipboard:{keywords:["stationery","documents"],char:"📋",fitzpatrick_scale:false,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:"🗒",fitzpatrick_scale:false,category:"objects"},file_folder:{keywords:["documents","business","office"],char:"📁",fitzpatrick_scale:false,category:"objects"},open_file_folder:{keywords:["documents","load"],char:"📂",fitzpatrick_scale:false,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:"🗂",fitzpatrick_scale:false,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:"🗞",fitzpatrick_scale:false,category:"objects"},newspaper:{keywords:["press","headline"],char:"📰",fitzpatrick_scale:false,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:"📓",fitzpatrick_scale:false,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:"📕",fitzpatrick_scale:false,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:"📗",fitzpatrick_scale:false,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:"📘",fitzpatrick_scale:false,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:"📙",fitzpatrick_scale:false,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:"📔",fitzpatrick_scale:false,category:"objects"},ledger:{keywords:["notes","paper"],char:"📒",fitzpatrick_scale:false,category:"objects"},books:{keywords:["literature","library","study"],char:"📚",fitzpatrick_scale:false,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:"📖",fitzpatrick_scale:false,category:"objects"},safety_pin:{keywords:["diaper"],char:"🧷",fitzpatrick_scale:false,category:"objects"},link:{keywords:["rings","url"],char:"🔗",fitzpatrick_scale:false,category:"objects"},paperclip:{keywords:["documents","stationery"],char:"📎",fitzpatrick_scale:false,category:"objects"},paperclips:{keywords:["documents","stationery"],char:"🖇",fitzpatrick_scale:false,category:"objects"},scissors:{keywords:["stationery","cut"],char:"✂️",fitzpatrick_scale:false,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:"📐",fitzpatrick_scale:false,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:"📏",fitzpatrick_scale:false,category:"objects"},abacus:{keywords:["calculation"],char:"🧮",fitzpatrick_scale:false,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:"📌",fitzpatrick_scale:false,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:"📍",fitzpatrick_scale:false,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:"🚩",fitzpatrick_scale:false,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:"🏳",fitzpatrick_scale:false,category:"objects"},black_flag:{keywords:["pirate"],char:"🏴",fitzpatrick_scale:false,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:"🏳️‍🌈",fitzpatrick_scale:false,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:"🔐",fitzpatrick_scale:false,category:"objects"},lock:{keywords:["security","password","padlock"],char:"🔒",fitzpatrick_scale:false,category:"objects"},unlock:{keywords:["privacy","security"],char:"🔓",fitzpatrick_scale:false,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:"🔏",fitzpatrick_scale:false,category:"objects"},pen:{keywords:["stationery","writing","write"],char:"🖊",fitzpatrick_scale:false,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:"🖋",fitzpatrick_scale:false,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:"✒️",fitzpatrick_scale:false,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:"📝",fitzpatrick_scale:false,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:"✏️",fitzpatrick_scale:false,category:"objects"},crayon:{keywords:["drawing","creativity"],char:"🖍",fitzpatrick_scale:false,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:"🖌",fitzpatrick_scale:false,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:"🔍",fitzpatrick_scale:false,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:"🔎",fitzpatrick_scale:false,category:"objects"},heart:{keywords:["love","like","valentines"],char:"❤️",fitzpatrick_scale:false,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:"🧡",fitzpatrick_scale:false,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:"💛",fitzpatrick_scale:false,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:"💚",fitzpatrick_scale:false,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:"💙",fitzpatrick_scale:false,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:"💜",fitzpatrick_scale:false,category:"symbols"},black_heart:{keywords:["evil"],char:"🖤",fitzpatrick_scale:false,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:"💔",fitzpatrick_scale:false,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:"❣",fitzpatrick_scale:false,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:"💕",fitzpatrick_scale:false,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:"💞",fitzpatrick_scale:false,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:"💓",fitzpatrick_scale:false,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:"💗",fitzpatrick_scale:false,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:"💖",fitzpatrick_scale:false,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:"💘",fitzpatrick_scale:false,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:"💝",fitzpatrick_scale:false,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:"💟",fitzpatrick_scale:false,category:"symbols"},peace_symbol:{keywords:["hippie"],char:"☮",fitzpatrick_scale:false,category:"symbols"},latin_cross:{keywords:["christianity"],char:"✝",fitzpatrick_scale:false,category:"symbols"},star_and_crescent:{keywords:["islam"],char:"☪",fitzpatrick_scale:false,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"🕉",fitzpatrick_scale:false,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"☸",fitzpatrick_scale:false,category:"symbols"},star_of_david:{keywords:["judaism"],char:"✡",fitzpatrick_scale:false,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:"🔯",fitzpatrick_scale:false,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:"🕎",fitzpatrick_scale:false,category:"symbols"},yin_yang:{keywords:["balance"],char:"☯",fitzpatrick_scale:false,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:"☦",fitzpatrick_scale:false,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:"🛐",fitzpatrick_scale:false,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:"⛎",fitzpatrick_scale:false,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:"♈",fitzpatrick_scale:false,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:"♉",fitzpatrick_scale:false,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:"♊",fitzpatrick_scale:false,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:"♋",fitzpatrick_scale:false,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:"♌",fitzpatrick_scale:false,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:"♍",fitzpatrick_scale:false,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:"♎",fitzpatrick_scale:false,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:"♏",fitzpatrick_scale:false,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:"♐",fitzpatrick_scale:false,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:"♑",fitzpatrick_scale:false,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:"♒",fitzpatrick_scale:false,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:"♓",fitzpatrick_scale:false,category:"symbols"},id:{keywords:["purple-square","words"],char:"🆔",fitzpatrick_scale:false,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:"⚛",fitzpatrick_scale:false,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:"🈳",fitzpatrick_scale:false,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:"🈹",fitzpatrick_scale:false,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:"☢",fitzpatrick_scale:false,category:"symbols"},biohazard:{keywords:["danger"],char:"☣",fitzpatrick_scale:false,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:"📴",fitzpatrick_scale:false,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:"📳",fitzpatrick_scale:false,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:"🈶",fitzpatrick_scale:false,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:"🈚",fitzpatrick_scale:false,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:"🈸",fitzpatrick_scale:false,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:"🈺",fitzpatrick_scale:false,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:"🈷️",fitzpatrick_scale:false,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:"✴️",fitzpatrick_scale:false,category:"symbols"},vs:{keywords:["words","orange-square"],char:"🆚",fitzpatrick_scale:false,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:"🉑",fitzpatrick_scale:false,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:"💮",fitzpatrick_scale:false,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:"🉐",fitzpatrick_scale:false,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:"㊙️",fitzpatrick_scale:false,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:"㊗️",fitzpatrick_scale:false,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:"🈴",fitzpatrick_scale:false,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:"🈵",fitzpatrick_scale:false,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:"🈲",fitzpatrick_scale:false,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:"🅰️",fitzpatrick_scale:false,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:"🅱️",fitzpatrick_scale:false,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:"🆎",fitzpatrick_scale:false,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:"🆑",fitzpatrick_scale:false,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:"🅾️",fitzpatrick_scale:false,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:"🆘",fitzpatrick_scale:false,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:"⛔",fitzpatrick_scale:false,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:"📛",fitzpatrick_scale:false,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:"🚫",fitzpatrick_scale:false,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:"❌",fitzpatrick_scale:false,category:"symbols"},o:{keywords:["circle","round"],char:"⭕",fitzpatrick_scale:false,category:"symbols"},stop_sign:{keywords:["stop"],char:"🛑",fitzpatrick_scale:false,category:"symbols"},anger:{keywords:["angry","mad"],char:"💢",fitzpatrick_scale:false,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:"♨️",fitzpatrick_scale:false,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:"🚷",fitzpatrick_scale:false,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:"🚯",fitzpatrick_scale:false,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:"🚳",fitzpatrick_scale:false,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:"🚱",fitzpatrick_scale:false,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:"🔞",fitzpatrick_scale:false,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:"📵",fitzpatrick_scale:false,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:"❗",fitzpatrick_scale:false,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:"❕",fitzpatrick_scale:false,category:"symbols"},question:{keywords:["doubt","confused"],char:"❓",fitzpatrick_scale:false,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:"❔",fitzpatrick_scale:false,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:"‼️",fitzpatrick_scale:false,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:"⁉️",fitzpatrick_scale:false,category:"symbols"},100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:"💯",fitzpatrick_scale:false,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:"🔅",fitzpatrick_scale:false,category:"symbols"},high_brightness:{keywords:["sun","light"],char:"🔆",fitzpatrick_scale:false,category:"symbols"},trident:{keywords:["weapon","spear"],char:"🔱",fitzpatrick_scale:false,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:"⚜",fitzpatrick_scale:false,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:"〽️",fitzpatrick_scale:false,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:"⚠️",fitzpatrick_scale:false,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:"🚸",fitzpatrick_scale:false,category:"symbols"},beginner:{keywords:["badge","shield"],char:"🔰",fitzpatrick_scale:false,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:"♻️",fitzpatrick_scale:false,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:"🈯",fitzpatrick_scale:false,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:"💹",fitzpatrick_scale:false,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:"❇️",fitzpatrick_scale:false,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:"✳️",fitzpatrick_scale:false,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:"❎",fitzpatrick_scale:false,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:"✅",fitzpatrick_scale:false,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:"💠",fitzpatrick_scale:false,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:"🌀",fitzpatrick_scale:false,category:"symbols"},loop:{keywords:["tape","cassette"],char:"➿",fitzpatrick_scale:false,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:"🌐",fitzpatrick_scale:false,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:"Ⓜ️",fitzpatrick_scale:false,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:"🏧",fitzpatrick_scale:false,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:"🈂️",fitzpatrick_scale:false,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:"🛂",fitzpatrick_scale:false,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:"🛃",fitzpatrick_scale:false,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:"🛄",fitzpatrick_scale:false,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:"🛅",fitzpatrick_scale:false,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:"♿",fitzpatrick_scale:false,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:"🚭",fitzpatrick_scale:false,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:"🚾",fitzpatrick_scale:false,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:"🅿️",fitzpatrick_scale:false,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:"🚰",fitzpatrick_scale:false,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:"🚹",fitzpatrick_scale:false,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:"🚺",fitzpatrick_scale:false,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:"🚼",fitzpatrick_scale:false,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:"🚻",fitzpatrick_scale:false,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:"🚮",fitzpatrick_scale:false,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:"🎦",fitzpatrick_scale:false,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:"📶",fitzpatrick_scale:false,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:"🈁",fitzpatrick_scale:false,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:"🆖",fitzpatrick_scale:false,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:"🆗",fitzpatrick_scale:false,category:"symbols"},up:{keywords:["blue-square","above","high"],char:"🆙",fitzpatrick_scale:false,category:"symbols"},cool:{keywords:["words","blue-square"],char:"🆒",fitzpatrick_scale:false,category:"symbols"},new:{keywords:["blue-square","words","start"],char:"🆕",fitzpatrick_scale:false,category:"symbols"},free:{keywords:["blue-square","words"],char:"🆓",fitzpatrick_scale:false,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:"0️⃣",fitzpatrick_scale:false,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:"1️⃣",fitzpatrick_scale:false,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:"2️⃣",fitzpatrick_scale:false,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:"3️⃣",fitzpatrick_scale:false,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:"4️⃣",fitzpatrick_scale:false,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:"5️⃣",fitzpatrick_scale:false,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:"6️⃣",fitzpatrick_scale:false,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:"7️⃣",fitzpatrick_scale:false,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:"8️⃣",fitzpatrick_scale:false,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:"9️⃣",fitzpatrick_scale:false,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:"🔟",fitzpatrick_scale:false,category:"symbols"},asterisk:{keywords:["star","keycap"],char:"*⃣",fitzpatrick_scale:false,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:"🔢",fitzpatrick_scale:false,category:"symbols"},eject_button:{keywords:["blue-square"],char:"⏏️",fitzpatrick_scale:false,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:"▶️",fitzpatrick_scale:false,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:"⏸",fitzpatrick_scale:false,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:"⏭",fitzpatrick_scale:false,category:"symbols"},stop_button:{keywords:["blue-square"],char:"⏹",fitzpatrick_scale:false,category:"symbols"},record_button:{keywords:["blue-square"],char:"⏺",fitzpatrick_scale:false,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:"⏯",fitzpatrick_scale:false,category:"symbols"},previous_track_button:{keywords:["backward"],char:"⏮",fitzpatrick_scale:false,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:"⏩",fitzpatrick_scale:false,category:"symbols"},rewind:{keywords:["play","blue-square"],char:"⏪",fitzpatrick_scale:false,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:"🔀",fitzpatrick_scale:false,category:"symbols"},repeat:{keywords:["loop","record"],char:"🔁",fitzpatrick_scale:false,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:"🔂",fitzpatrick_scale:false,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:"◀️",fitzpatrick_scale:false,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:"🔼",fitzpatrick_scale:false,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:"🔽",fitzpatrick_scale:false,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:"⏫",fitzpatrick_scale:false,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:"⏬",fitzpatrick_scale:false,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:"➡️",fitzpatrick_scale:false,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:"⬅️",fitzpatrick_scale:false,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:"⬆️",fitzpatrick_scale:false,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:"⬇️",fitzpatrick_scale:false,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:"↗️",fitzpatrick_scale:false,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:"↘️",fitzpatrick_scale:false,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:"↙️",fitzpatrick_scale:false,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:"↖️",fitzpatrick_scale:false,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:"↕️",fitzpatrick_scale:false,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:"↔️",fitzpatrick_scale:false,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:"🔄",fitzpatrick_scale:false,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:"↪️",fitzpatrick_scale:false,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:"↩️",fitzpatrick_scale:false,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:"⤴️",fitzpatrick_scale:false,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:"⤵️",fitzpatrick_scale:false,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:"#️⃣",fitzpatrick_scale:false,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:"ℹ️",fitzpatrick_scale:false,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:"🔤",fitzpatrick_scale:false,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:"🔡",fitzpatrick_scale:false,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:"🔠",fitzpatrick_scale:false,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:"🔣",fitzpatrick_scale:false,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:"🎵",fitzpatrick_scale:false,category:"symbols"},notes:{keywords:["music","score"],char:"🎶",fitzpatrick_scale:false,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:"〰️",fitzpatrick_scale:false,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:"➰",fitzpatrick_scale:false,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:"✔️",fitzpatrick_scale:false,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:"🔃",fitzpatrick_scale:false,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:"➕",fitzpatrick_scale:false,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:"➖",fitzpatrick_scale:false,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:"➗",fitzpatrick_scale:false,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:"✖️",fitzpatrick_scale:false,category:"symbols"},infinity:{keywords:["forever"],char:"♾",fitzpatrick_scale:false,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:"💲",fitzpatrick_scale:false,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:"💱",fitzpatrick_scale:false,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:"©️",fitzpatrick_scale:false,category:"symbols"},registered:{keywords:["alphabet","circle"],char:"®️",fitzpatrick_scale:false,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:"™️",fitzpatrick_scale:false,category:"symbols"},end:{keywords:["words","arrow"],char:"🔚",fitzpatrick_scale:false,category:"symbols"},back:{keywords:["arrow","words","return"],char:"🔙",fitzpatrick_scale:false,category:"symbols"},on:{keywords:["arrow","words"],char:"🔛",fitzpatrick_scale:false,category:"symbols"},top:{keywords:["words","blue-square"],char:"🔝",fitzpatrick_scale:false,category:"symbols"},soon:{keywords:["arrow","words"],char:"🔜",fitzpatrick_scale:false,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:"☑️",fitzpatrick_scale:false,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:"🔘",fitzpatrick_scale:false,category:"symbols"},white_circle:{keywords:["shape","round"],char:"⚪",fitzpatrick_scale:false,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:"⚫",fitzpatrick_scale:false,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:"🔴",fitzpatrick_scale:false,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:"🔵",fitzpatrick_scale:false,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:"🔸",fitzpatrick_scale:false,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:"🔹",fitzpatrick_scale:false,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:"🔶",fitzpatrick_scale:false,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:"🔷",fitzpatrick_scale:false,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:"🔺",fitzpatrick_scale:false,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:"▪️",fitzpatrick_scale:false,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:"▫️",fitzpatrick_scale:false,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:"⬛",fitzpatrick_scale:false,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:"⬜",fitzpatrick_scale:false,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:"🔻",fitzpatrick_scale:false,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:"◼️",fitzpatrick_scale:false,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:"◻️",fitzpatrick_scale:false,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:"◾",fitzpatrick_scale:false,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:"◽",fitzpatrick_scale:false,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:"🔲",fitzpatrick_scale:false,category:"symbols"},white_square_button:{keywords:["shape","input"],char:"🔳",fitzpatrick_scale:false,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:"🔈",fitzpatrick_scale:false,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:"🔉",fitzpatrick_scale:false,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:"🔊",fitzpatrick_scale:false,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:"🔇",fitzpatrick_scale:false,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:"📣",fitzpatrick_scale:false,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:"📢",fitzpatrick_scale:false,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:"🔔",fitzpatrick_scale:false,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:"🔕",fitzpatrick_scale:false,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:"🃏",fitzpatrick_scale:false,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:"🀄",fitzpatrick_scale:false,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:"♠️",fitzpatrick_scale:false,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:"♣️",fitzpatrick_scale:false,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:"♥️",fitzpatrick_scale:false,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:"♦️",fitzpatrick_scale:false,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:"🎴",fitzpatrick_scale:false,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:"💭",fitzpatrick_scale:false,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:"🗯",fitzpatrick_scale:false,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:"💬",fitzpatrick_scale:false,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:"🗨",fitzpatrick_scale:false,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:"🕐",fitzpatrick_scale:false,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:"🕑",fitzpatrick_scale:false,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:"🕒",fitzpatrick_scale:false,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:"🕓",fitzpatrick_scale:false,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:"🕔",fitzpatrick_scale:false,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:"🕕",fitzpatrick_scale:false,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:"🕖",fitzpatrick_scale:false,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:"🕗",fitzpatrick_scale:false,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:"🕘",fitzpatrick_scale:false,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:"🕙",fitzpatrick_scale:false,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:"🕚",fitzpatrick_scale:false,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:"🕛",fitzpatrick_scale:false,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:"🕜",fitzpatrick_scale:false,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:"🕝",fitzpatrick_scale:false,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:"🕞",fitzpatrick_scale:false,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:"🕟",fitzpatrick_scale:false,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:"🕠",fitzpatrick_scale:false,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:"🕡",fitzpatrick_scale:false,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:"🕢",fitzpatrick_scale:false,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:"🕣",fitzpatrick_scale:false,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:"🕤",fitzpatrick_scale:false,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:"🕥",fitzpatrick_scale:false,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:"🕦",fitzpatrick_scale:false,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:"🕧",fitzpatrick_scale:false,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:"🇦🇫",fitzpatrick_scale:false,category:"flags"},aland_islands:{keywords:["Åland","islands","flag","nation","country","banner"],char:"🇦🇽",fitzpatrick_scale:false,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:"🇦🇱",fitzpatrick_scale:false,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:"🇩🇿",fitzpatrick_scale:false,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:"🇦🇸",fitzpatrick_scale:false,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:"🇦🇩",fitzpatrick_scale:false,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:"🇦🇴",fitzpatrick_scale:false,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:"🇦🇮",fitzpatrick_scale:false,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:"🇦🇶",fitzpatrick_scale:false,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:"🇦🇬",fitzpatrick_scale:false,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:"🇦🇷",fitzpatrick_scale:false,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:"🇦🇲",fitzpatrick_scale:false,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:"🇦🇼",fitzpatrick_scale:false,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:"🇦🇺",fitzpatrick_scale:false,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:"🇦🇹",fitzpatrick_scale:false,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:"🇦🇿",fitzpatrick_scale:false,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:"🇧🇸",fitzpatrick_scale:false,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:"🇧🇭",fitzpatrick_scale:false,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:"🇧🇩",fitzpatrick_scale:false,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:"🇧🇧",fitzpatrick_scale:false,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:"🇧🇾",fitzpatrick_scale:false,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:"🇧🇪",fitzpatrick_scale:false,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:"🇧🇿",fitzpatrick_scale:false,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:"🇧🇯",fitzpatrick_scale:false,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:"🇧🇲",fitzpatrick_scale:false,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:"🇧🇹",fitzpatrick_scale:false,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:"🇧🇴",fitzpatrick_scale:false,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:"🇧🇶",fitzpatrick_scale:false,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:"🇧🇦",fitzpatrick_scale:false,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:"🇧🇼",fitzpatrick_scale:false,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:"🇧🇷",fitzpatrick_scale:false,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:"🇮🇴",fitzpatrick_scale:false,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:"🇻🇬",fitzpatrick_scale:false,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:"🇧🇳",fitzpatrick_scale:false,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:"🇧🇬",fitzpatrick_scale:false,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:"🇧🇫",fitzpatrick_scale:false,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:"🇧🇮",fitzpatrick_scale:false,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:"🇨🇻",fitzpatrick_scale:false,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:"🇰🇭",fitzpatrick_scale:false,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:"🇨🇲",fitzpatrick_scale:false,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:"🇨🇦",fitzpatrick_scale:false,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:"🇮🇨",fitzpatrick_scale:false,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:"🇰🇾",fitzpatrick_scale:false,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:"🇨🇫",fitzpatrick_scale:false,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:"🇹🇩",fitzpatrick_scale:false,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:"🇨🇱",fitzpatrick_scale:false,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:"🇨🇳",fitzpatrick_scale:false,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:"🇨🇽",fitzpatrick_scale:false,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:"🇨🇨",fitzpatrick_scale:false,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:"🇨🇴",fitzpatrick_scale:false,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:"🇰🇲",fitzpatrick_scale:false,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:"🇨🇬",fitzpatrick_scale:false,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:"🇨🇩",fitzpatrick_scale:false,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:"🇨🇰",fitzpatrick_scale:false,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:"🇨🇷",fitzpatrick_scale:false,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:"🇭🇷",fitzpatrick_scale:false,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:"🇨🇺",fitzpatrick_scale:false,category:"flags"},curacao:{keywords:["curaçao","flag","nation","country","banner"],char:"🇨🇼",fitzpatrick_scale:false,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:"🇨🇾",fitzpatrick_scale:false,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:"🇨🇿",fitzpatrick_scale:false,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:"🇩🇰",fitzpatrick_scale:false,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:"🇩🇯",fitzpatrick_scale:false,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:"🇩🇲",fitzpatrick_scale:false,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:"🇩🇴",fitzpatrick_scale:false,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:"🇪🇨",fitzpatrick_scale:false,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:"🇪🇬",fitzpatrick_scale:false,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:"🇸🇻",fitzpatrick_scale:false,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:"🇬🇶",fitzpatrick_scale:false,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:"🇪🇷",fitzpatrick_scale:false,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:"🇪🇪",fitzpatrick_scale:false,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:"🇪🇹",fitzpatrick_scale:false,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:"🇪🇺",fitzpatrick_scale:false,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:"🇫🇰",fitzpatrick_scale:false,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:"🇫🇴",fitzpatrick_scale:false,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:"🇫🇯",fitzpatrick_scale:false,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:"🇫🇮",fitzpatrick_scale:false,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:"🇫🇷",fitzpatrick_scale:false,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:"🇬🇫",fitzpatrick_scale:false,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:"🇵🇫",fitzpatrick_scale:false,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:"🇹🇫",fitzpatrick_scale:false,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:"🇬🇦",fitzpatrick_scale:false,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:"🇬🇲",fitzpatrick_scale:false,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:"🇬🇪",fitzpatrick_scale:false,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:"🇩🇪",fitzpatrick_scale:false,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:"🇬🇭",fitzpatrick_scale:false,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:"🇬🇮",fitzpatrick_scale:false,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:"🇬🇷",fitzpatrick_scale:false,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:"🇬🇱",fitzpatrick_scale:false,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:"🇬🇩",fitzpatrick_scale:false,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:"🇬🇵",fitzpatrick_scale:false,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:"🇬🇺",fitzpatrick_scale:false,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:"🇬🇹",fitzpatrick_scale:false,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:"🇬🇬",fitzpatrick_scale:false,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:"🇬🇳",fitzpatrick_scale:false,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:"🇬🇼",fitzpatrick_scale:false,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:"🇬🇾",fitzpatrick_scale:false,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:"🇭🇹",fitzpatrick_scale:false,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:"🇭🇳",fitzpatrick_scale:false,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:"🇭🇰",fitzpatrick_scale:false,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:"🇭🇺",fitzpatrick_scale:false,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:"🇮🇸",fitzpatrick_scale:false,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:"🇮🇳",fitzpatrick_scale:false,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:"🇮🇩",fitzpatrick_scale:false,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:"🇮🇷",fitzpatrick_scale:false,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:"🇮🇶",fitzpatrick_scale:false,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:"🇮🇪",fitzpatrick_scale:false,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:"🇮🇲",fitzpatrick_scale:false,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:"🇮🇱",fitzpatrick_scale:false,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:"🇮🇹",fitzpatrick_scale:false,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:"🇨🇮",fitzpatrick_scale:false,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:"🇯🇲",fitzpatrick_scale:false,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:"🇯🇵",fitzpatrick_scale:false,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:"🇯🇪",fitzpatrick_scale:false,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:"🇯🇴",fitzpatrick_scale:false,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:"🇰🇿",fitzpatrick_scale:false,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:"🇰🇪",fitzpatrick_scale:false,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:"🇰🇮",fitzpatrick_scale:false,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:"🇽🇰",fitzpatrick_scale:false,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:"🇰🇼",fitzpatrick_scale:false,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:"🇰🇬",fitzpatrick_scale:false,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:"🇱🇦",fitzpatrick_scale:false,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:"🇱🇻",fitzpatrick_scale:false,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:"🇱🇧",fitzpatrick_scale:false,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:"🇱🇸",fitzpatrick_scale:false,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:"🇱🇷",fitzpatrick_scale:false,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:"🇱🇾",fitzpatrick_scale:false,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:"🇱🇮",fitzpatrick_scale:false,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:"🇱🇹",fitzpatrick_scale:false,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:"🇱🇺",fitzpatrick_scale:false,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:"🇲🇴",fitzpatrick_scale:false,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:"🇲🇰",fitzpatrick_scale:false,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:"🇲🇬",fitzpatrick_scale:false,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:"🇲🇼",fitzpatrick_scale:false,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:"🇲🇾",fitzpatrick_scale:false,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:"🇲🇻",fitzpatrick_scale:false,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:"🇲🇱",fitzpatrick_scale:false,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:"🇲🇹",fitzpatrick_scale:false,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:"🇲🇭",fitzpatrick_scale:false,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:"🇲🇶",fitzpatrick_scale:false,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:"🇲🇷",fitzpatrick_scale:false,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:"🇲🇺",fitzpatrick_scale:false,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:"🇾🇹",fitzpatrick_scale:false,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:"🇲🇽",fitzpatrick_scale:false,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:"🇫🇲",fitzpatrick_scale:false,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:"🇲🇩",fitzpatrick_scale:false,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:"🇲🇨",fitzpatrick_scale:false,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:"🇲🇳",fitzpatrick_scale:false,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:"🇲🇪",fitzpatrick_scale:false,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:"🇲🇸",fitzpatrick_scale:false,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:"🇲🇦",fitzpatrick_scale:false,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:"🇲🇿",fitzpatrick_scale:false,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:"🇲🇲",fitzpatrick_scale:false,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:"🇳🇦",fitzpatrick_scale:false,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:"🇳🇷",fitzpatrick_scale:false,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:"🇳🇵",fitzpatrick_scale:false,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:"🇳🇱",fitzpatrick_scale:false,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:"🇳🇨",fitzpatrick_scale:false,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:"🇳🇿",fitzpatrick_scale:false,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:"🇳🇮",fitzpatrick_scale:false,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:"🇳🇪",fitzpatrick_scale:false,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:"🇳🇬",fitzpatrick_scale:false,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:"🇳🇺",fitzpatrick_scale:false,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:"🇳🇫",fitzpatrick_scale:false,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:"🇲🇵",fitzpatrick_scale:false,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:"🇰🇵",fitzpatrick_scale:false,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:"🇳🇴",fitzpatrick_scale:false,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:"🇴🇲",fitzpatrick_scale:false,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:"🇵🇰",fitzpatrick_scale:false,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:"🇵🇼",fitzpatrick_scale:false,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:"🇵🇸",fitzpatrick_scale:false,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:"🇵🇦",fitzpatrick_scale:false,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:"🇵🇬",fitzpatrick_scale:false,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:"🇵🇾",fitzpatrick_scale:false,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:"🇵🇪",fitzpatrick_scale:false,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:"🇵🇭",fitzpatrick_scale:false,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:"🇵🇳",fitzpatrick_scale:false,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:"🇵🇱",fitzpatrick_scale:false,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:"🇵🇹",fitzpatrick_scale:false,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:"🇵🇷",fitzpatrick_scale:false,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:"🇶🇦",fitzpatrick_scale:false,category:"flags"},reunion:{keywords:["réunion","flag","nation","country","banner"],char:"🇷🇪",fitzpatrick_scale:false,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:"🇷🇴",fitzpatrick_scale:false,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:"🇷🇺",fitzpatrick_scale:false,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:"🇷🇼",fitzpatrick_scale:false,category:"flags"},st_barthelemy:{keywords:["saint","barthélemy","flag","nation","country","banner"],char:"🇧🇱",fitzpatrick_scale:false,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:"🇸🇭",fitzpatrick_scale:false,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:"🇰🇳",fitzpatrick_scale:false,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:"🇱🇨",fitzpatrick_scale:false,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:"🇵🇲",fitzpatrick_scale:false,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:"🇻🇨",fitzpatrick_scale:false,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:"🇼🇸",fitzpatrick_scale:false,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:"🇸🇲",fitzpatrick_scale:false,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:"🇸🇹",fitzpatrick_scale:false,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:"🇸🇦",fitzpatrick_scale:false,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:"🇸🇳",fitzpatrick_scale:false,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:"🇷🇸",fitzpatrick_scale:false,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:"🇸🇨",fitzpatrick_scale:false,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:"🇸🇱",fitzpatrick_scale:false,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:"🇸🇬",fitzpatrick_scale:false,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:"🇸🇽",fitzpatrick_scale:false,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:"🇸🇰",fitzpatrick_scale:false,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:"🇸🇮",fitzpatrick_scale:false,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:"🇸🇧",fitzpatrick_scale:false,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:"🇸🇴",fitzpatrick_scale:false,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:"🇿🇦",fitzpatrick_scale:false,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:"🇬🇸",fitzpatrick_scale:false,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:"🇰🇷",fitzpatrick_scale:false,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:"🇸🇸",fitzpatrick_scale:false,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:"🇪🇸",fitzpatrick_scale:false,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:"🇱🇰",fitzpatrick_scale:false,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:"🇸🇩",fitzpatrick_scale:false,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:"🇸🇷",fitzpatrick_scale:false,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:"🇸🇿",fitzpatrick_scale:false,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:"🇸🇪",fitzpatrick_scale:false,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:"🇨🇭",fitzpatrick_scale:false,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:"🇸🇾",fitzpatrick_scale:false,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:"🇹🇼",fitzpatrick_scale:false,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:"🇹🇯",fitzpatrick_scale:false,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:"🇹🇿",fitzpatrick_scale:false,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:"🇹🇭",fitzpatrick_scale:false,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:"🇹🇱",fitzpatrick_scale:false,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:"🇹🇬",fitzpatrick_scale:false,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:"🇹🇰",fitzpatrick_scale:false,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:"🇹🇴",fitzpatrick_scale:false,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:"🇹🇹",fitzpatrick_scale:false,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:"🇹🇳",fitzpatrick_scale:false,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:"🇹🇷",fitzpatrick_scale:false,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:"🇹🇲",fitzpatrick_scale:false,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:"🇹🇨",fitzpatrick_scale:false,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:"🇹🇻",fitzpatrick_scale:false,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:"🇺🇬",fitzpatrick_scale:false,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:"🇺🇦",fitzpatrick_scale:false,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:"🇦🇪",fitzpatrick_scale:false,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:"🇬🇧",fitzpatrick_scale:false,category:"flags"},england:{keywords:["flag","english"],char:"🏴󠁧󠁢󠁥󠁮󠁧󠁿",fitzpatrick_scale:false,category:"flags"},scotland:{keywords:["flag","scottish"],char:"🏴󠁧󠁢󠁳󠁣󠁴󠁿",fitzpatrick_scale:false,category:"flags"},wales:{keywords:["flag","welsh"],char:"🏴󠁧󠁢󠁷󠁬󠁳󠁿",fitzpatrick_scale:false,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:"🇺🇸",fitzpatrick_scale:false,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:"🇻🇮",fitzpatrick_scale:false,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:"🇺🇾",fitzpatrick_scale:false,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:"🇺🇿",fitzpatrick_scale:false,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:"🇻🇺",fitzpatrick_scale:false,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:"🇻🇦",fitzpatrick_scale:false,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:"🇻🇪",fitzpatrick_scale:false,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:"🇻🇳",fitzpatrick_scale:false,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:"🇼🇫",fitzpatrick_scale:false,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:"🇪🇭",fitzpatrick_scale:false,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:"🇾🇪",fitzpatrick_scale:false,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:"🇿🇲",fitzpatrick_scale:false,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:"🇿🇼",fitzpatrick_scale:false,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:"🇺🇳",fitzpatrick_scale:false,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:"🏴‍☠️",fitzpatrick_scale:false,category:"flags"}}); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/emoticons/js/emojis.min.js b/apps/web-antd/public/tinymce/plugins/emoticons/js/emojis.min.js new file mode 100644 index 0000000..5a1c491 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/emoticons/js/emojis.min.js @@ -0,0 +1,2 @@ +// Source: npm package: emojilib, file:emojis.json +window.tinymce.Resource.add("tinymce.plugins.emoticons",{grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:"\u{1f600}",fitzpatrick_scale:!1,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:"\u{1f62c}",fitzpatrick_scale:!1,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:"\u{1f601}",fitzpatrick_scale:!1,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:"\u{1f602}",fitzpatrick_scale:!1,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:"\u{1f923}",fitzpatrick_scale:!1,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:"\u{1f973}",fitzpatrick_scale:!1,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:"\u{1f603}",fitzpatrick_scale:!1,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:"\u{1f604}",fitzpatrick_scale:!1,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:"\u{1f605}",fitzpatrick_scale:!1,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:"\u{1f606}",fitzpatrick_scale:!1,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:"\u{1f607}",fitzpatrick_scale:!1,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:"\u{1f609}",fitzpatrick_scale:!1,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:"\u{1f60a}",fitzpatrick_scale:!1,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:"\u{1f642}",fitzpatrick_scale:!1,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:"\u{1f643}",fitzpatrick_scale:!1,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:"\u263a\ufe0f",fitzpatrick_scale:!1,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:"\u{1f60b}",fitzpatrick_scale:!1,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:"\u{1f60c}",fitzpatrick_scale:!1,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:"\u{1f60d}",fitzpatrick_scale:!1,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:"\u{1f970}",fitzpatrick_scale:!1,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"\u{1f618}",fitzpatrick_scale:!1,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:"\u{1f617}",fitzpatrick_scale:!1,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:"\u{1f619}",fitzpatrick_scale:!1,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"\u{1f61a}",fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:"\u{1f61c}",fitzpatrick_scale:!1,category:"people"},zany:{keywords:["face","goofy","crazy"],char:"\u{1f92a}",fitzpatrick_scale:!1,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:"\u{1f928}",fitzpatrick_scale:!1,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:"\u{1f9d0}",fitzpatrick_scale:!1,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:"\u{1f61d}",fitzpatrick_scale:!1,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:"\u{1f61b}",fitzpatrick_scale:!1,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:"\u{1f911}",fitzpatrick_scale:!1,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:"\u{1f913}",fitzpatrick_scale:!1,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:"\u{1f60e}",fitzpatrick_scale:!1,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:"\u{1f929}",fitzpatrick_scale:!1,category:"people"},clown_face:{keywords:["face"],char:"\u{1f921}",fitzpatrick_scale:!1,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:"\u{1f920}",fitzpatrick_scale:!1,category:"people"},hugs:{keywords:["face","smile","hug"],char:"\u{1f917}",fitzpatrick_scale:!1,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:"\u{1f60f}",fitzpatrick_scale:!1,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:"\u{1f636}",fitzpatrick_scale:!1,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:"\u{1f610}",fitzpatrick_scale:!1,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:"\u{1f611}",fitzpatrick_scale:!1,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:"\u{1f612}",fitzpatrick_scale:!1,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:"\u{1f644}",fitzpatrick_scale:!1,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:"\u{1f914}",fitzpatrick_scale:!1,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:"\u{1f925}",fitzpatrick_scale:!1,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:"\u{1f92d}",fitzpatrick_scale:!1,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:"\u{1f92b}",fitzpatrick_scale:!1,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:"\u{1f92c}",fitzpatrick_scale:!1,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:"\u{1f92f}",fitzpatrick_scale:!1,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:"\u{1f633}",fitzpatrick_scale:!1,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:"\u{1f61e}",fitzpatrick_scale:!1,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:"\u{1f61f}",fitzpatrick_scale:!1,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:"\u{1f620}",fitzpatrick_scale:!1,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:"\u{1f621}",fitzpatrick_scale:!1,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:"\u{1f614}",fitzpatrick_scale:!1,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:"\u{1f615}",fitzpatrick_scale:!1,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:"\u{1f641}",fitzpatrick_scale:!1,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:"\u2639",fitzpatrick_scale:!1,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:"\u{1f623}",fitzpatrick_scale:!1,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:"\u{1f616}",fitzpatrick_scale:!1,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:"\u{1f62b}",fitzpatrick_scale:!1,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:"\u{1f629}",fitzpatrick_scale:!1,category:"people"},pleading:{keywords:["face","begging","mercy"],char:"\u{1f97a}",fitzpatrick_scale:!1,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:"\u{1f624}",fitzpatrick_scale:!1,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:"\u{1f62e}",fitzpatrick_scale:!1,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:"\u{1f631}",fitzpatrick_scale:!1,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:"\u{1f628}",fitzpatrick_scale:!1,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:"\u{1f630}",fitzpatrick_scale:!1,category:"people"},hushed:{keywords:["face","woo","shh"],char:"\u{1f62f}",fitzpatrick_scale:!1,category:"people"},frowning:{keywords:["face","aw","what"],char:"\u{1f626}",fitzpatrick_scale:!1,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:"\u{1f627}",fitzpatrick_scale:!1,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:"\u{1f622}",fitzpatrick_scale:!1,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:"\u{1f625}",fitzpatrick_scale:!1,category:"people"},drooling_face:{keywords:["face"],char:"\u{1f924}",fitzpatrick_scale:!1,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:"\u{1f62a}",fitzpatrick_scale:!1,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:"\u{1f613}",fitzpatrick_scale:!1,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:"\u{1f975}",fitzpatrick_scale:!1,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:"\u{1f976}",fitzpatrick_scale:!1,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:"\u{1f62d}",fitzpatrick_scale:!1,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:"\u{1f635}",fitzpatrick_scale:!1,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:"\u{1f632}",fitzpatrick_scale:!1,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:"\u{1f910}",fitzpatrick_scale:!1,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:"\u{1f922}",fitzpatrick_scale:!1,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:"\u{1f927}",fitzpatrick_scale:!1,category:"people"},vomiting:{keywords:["face","sick"],char:"\u{1f92e}",fitzpatrick_scale:!1,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:"\u{1f637}",fitzpatrick_scale:!1,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:"\u{1f912}",fitzpatrick_scale:!1,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:"\u{1f915}",fitzpatrick_scale:!1,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:"\u{1f974}",fitzpatrick_scale:!1,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:"\u{1f634}",fitzpatrick_scale:!1,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:"\u{1f4a4}",fitzpatrick_scale:!1,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:"\u{1f4a9}",fitzpatrick_scale:!1,category:"people"},smiling_imp:{keywords:["devil","horns"],char:"\u{1f608}",fitzpatrick_scale:!1,category:"people"},imp:{keywords:["devil","angry","horns"],char:"\u{1f47f}",fitzpatrick_scale:!1,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:"\u{1f479}",fitzpatrick_scale:!1,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:"\u{1f47a}",fitzpatrick_scale:!1,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:"\u{1f480}",fitzpatrick_scale:!1,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:"\u{1f47b}",fitzpatrick_scale:!1,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:"\u{1f47d}",fitzpatrick_scale:!1,category:"people"},robot:{keywords:["computer","machine","bot"],char:"\u{1f916}",fitzpatrick_scale:!1,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:"\u{1f63a}",fitzpatrick_scale:!1,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:"\u{1f638}",fitzpatrick_scale:!1,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:"\u{1f639}",fitzpatrick_scale:!1,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:"\u{1f63b}",fitzpatrick_scale:!1,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:"\u{1f63c}",fitzpatrick_scale:!1,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:"\u{1f63d}",fitzpatrick_scale:!1,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:"\u{1f640}",fitzpatrick_scale:!1,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:"\u{1f63f}",fitzpatrick_scale:!1,category:"people"},pouting_cat:{keywords:["animal","cats"],char:"\u{1f63e}",fitzpatrick_scale:!1,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:"\u{1f932}",fitzpatrick_scale:!0,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:"\u{1f64c}",fitzpatrick_scale:!0,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:"\u{1f44f}",fitzpatrick_scale:!0,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:"\u{1f44b}",fitzpatrick_scale:!0,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:"\u{1f919}",fitzpatrick_scale:!0,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:"\u{1f44d}",fitzpatrick_scale:!0,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:"\u{1f44e}",fitzpatrick_scale:!0,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:"\u{1f44a}",fitzpatrick_scale:!0,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:"\u270a",fitzpatrick_scale:!0,category:"people"},fist_left:{keywords:["hand","fistbump"],char:"\u{1f91b}",fitzpatrick_scale:!0,category:"people"},fist_right:{keywords:["hand","fistbump"],char:"\u{1f91c}",fitzpatrick_scale:!0,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:"\u270c",fitzpatrick_scale:!0,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:"\u{1f44c}",fitzpatrick_scale:!0,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:"\u270b",fitzpatrick_scale:!0,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:"\u{1f91a}",fitzpatrick_scale:!0,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:"\u{1f450}",fitzpatrick_scale:!0,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:"\u{1f4aa}",fitzpatrick_scale:!0,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:"\u{1f64f}",fitzpatrick_scale:!0,category:"people"},foot:{keywords:["kick","stomp"],char:"\u{1f9b6}",fitzpatrick_scale:!0,category:"people"},leg:{keywords:["kick","limb"],char:"\u{1f9b5}",fitzpatrick_scale:!0,category:"people"},handshake:{keywords:["agreement","shake"],char:"\u{1f91d}",fitzpatrick_scale:!1,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:"\u261d",fitzpatrick_scale:!0,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:"\u{1f446}",fitzpatrick_scale:!0,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:"\u{1f447}",fitzpatrick_scale:!0,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:"\u{1f448}",fitzpatrick_scale:!0,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:"\u{1f449}",fitzpatrick_scale:!0,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:"\u{1f595}",fitzpatrick_scale:!0,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:"\u{1f590}",fitzpatrick_scale:!0,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:"\u{1f91f}",fitzpatrick_scale:!0,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:"\u{1f918}",fitzpatrick_scale:!0,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:"\u{1f91e}",fitzpatrick_scale:!0,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:"\u{1f596}",fitzpatrick_scale:!0,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:"\u270d",fitzpatrick_scale:!0,category:"people"},selfie:{keywords:["camera","phone"],char:"\u{1f933}",fitzpatrick_scale:!0,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:"\u{1f485}",fitzpatrick_scale:!0,category:"people"},lips:{keywords:["mouth","kiss"],char:"\u{1f444}",fitzpatrick_scale:!1,category:"people"},tooth:{keywords:["teeth","dentist"],char:"\u{1f9b7}",fitzpatrick_scale:!1,category:"people"},tongue:{keywords:["mouth","playful"],char:"\u{1f445}",fitzpatrick_scale:!1,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:"\u{1f442}",fitzpatrick_scale:!0,category:"people"},nose:{keywords:["smell","sniff"],char:"\u{1f443}",fitzpatrick_scale:!0,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:"\u{1f441}",fitzpatrick_scale:!1,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:"\u{1f440}",fitzpatrick_scale:!1,category:"people"},brain:{keywords:["smart","intelligent"],char:"\u{1f9e0}",fitzpatrick_scale:!1,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:"\u{1f464}",fitzpatrick_scale:!1,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:"\u{1f465}",fitzpatrick_scale:!1,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:"\u{1f5e3}",fitzpatrick_scale:!1,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:"\u{1f476}",fitzpatrick_scale:!0,category:"people"},child:{keywords:["gender-neutral","young"],char:"\u{1f9d2}",fitzpatrick_scale:!0,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:"\u{1f466}",fitzpatrick_scale:!0,category:"people"},girl:{keywords:["female","woman","teenager"],char:"\u{1f467}",fitzpatrick_scale:!0,category:"people"},adult:{keywords:["gender-neutral","person"],char:"\u{1f9d1}",fitzpatrick_scale:!0,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:"\u{1f468}",fitzpatrick_scale:!0,category:"people"},woman:{keywords:["female","girls","lady"],char:"\u{1f469}",fitzpatrick_scale:!0,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:"\u{1f471}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:"\u{1f471}",fitzpatrick_scale:!0,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:"\u{1f9d4}",fitzpatrick_scale:!0,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:"\u{1f9d3}",fitzpatrick_scale:!0,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:"\u{1f474}",fitzpatrick_scale:!0,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:"\u{1f475}",fitzpatrick_scale:!0,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:"\u{1f472}",fitzpatrick_scale:!0,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:"\u{1f9d5}",fitzpatrick_scale:!0,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:"\u{1f473}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:"\u{1f473}",fitzpatrick_scale:!0,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:"\u{1f46e}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:"\u{1f46e}",fitzpatrick_scale:!0,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:"\u{1f477}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:"\u{1f477}",fitzpatrick_scale:!0,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:"\u{1f482}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:"\u{1f482}",fitzpatrick_scale:!0,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:"\u{1f575}\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},male_detective:{keywords:["human","spy","detective"],char:"\u{1f575}",fitzpatrick_scale:!0,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:"\u{1f469}\u200d\u2695\ufe0f",fitzpatrick_scale:!0,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:"\u{1f468}\u200d\u2695\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:"\u{1f469}\u200d\u{1f33e}",fitzpatrick_scale:!0,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:"\u{1f468}\u200d\u{1f33e}",fitzpatrick_scale:!0,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:"\u{1f469}\u200d\u{1f373}",fitzpatrick_scale:!0,category:"people"},man_cook:{keywords:["chef","man","human"],char:"\u{1f468}\u200d\u{1f373}",fitzpatrick_scale:!0,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:"\u{1f469}\u200d\u{1f393}",fitzpatrick_scale:!0,category:"people"},man_student:{keywords:["graduate","man","human"],char:"\u{1f468}\u200d\u{1f393}",fitzpatrick_scale:!0,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:"\u{1f469}\u200d\u{1f3a4}",fitzpatrick_scale:!0,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:"\u{1f468}\u200d\u{1f3a4}",fitzpatrick_scale:!0,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:"\u{1f469}\u200d\u{1f3eb}",fitzpatrick_scale:!0,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:"\u{1f468}\u200d\u{1f3eb}",fitzpatrick_scale:!0,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:"\u{1f469}\u200d\u{1f3ed}",fitzpatrick_scale:!0,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:"\u{1f468}\u200d\u{1f3ed}",fitzpatrick_scale:!0,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:"\u{1f469}\u200d\u{1f4bb}",fitzpatrick_scale:!0,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:"\u{1f468}\u200d\u{1f4bb}",fitzpatrick_scale:!0,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:"\u{1f469}\u200d\u{1f4bc}",fitzpatrick_scale:!0,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:"\u{1f468}\u200d\u{1f4bc}",fitzpatrick_scale:!0,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:"\u{1f469}\u200d\u{1f527}",fitzpatrick_scale:!0,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:"\u{1f468}\u200d\u{1f527}",fitzpatrick_scale:!0,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:"\u{1f469}\u200d\u{1f52c}",fitzpatrick_scale:!0,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:"\u{1f468}\u200d\u{1f52c}",fitzpatrick_scale:!0,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:"\u{1f469}\u200d\u{1f3a8}",fitzpatrick_scale:!0,category:"people"},man_artist:{keywords:["painter","man","human"],char:"\u{1f468}\u200d\u{1f3a8}",fitzpatrick_scale:!0,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:"\u{1f469}\u200d\u{1f692}",fitzpatrick_scale:!0,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:"\u{1f468}\u200d\u{1f692}",fitzpatrick_scale:!0,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:"\u{1f469}\u200d\u2708\ufe0f",fitzpatrick_scale:!0,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:"\u{1f468}\u200d\u2708\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:"\u{1f469}\u200d\u{1f680}",fitzpatrick_scale:!0,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:"\u{1f468}\u200d\u{1f680}",fitzpatrick_scale:!0,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:"\u{1f469}\u200d\u2696\ufe0f",fitzpatrick_scale:!0,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:"\u{1f468}\u200d\u2696\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:"\u{1f9b8}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:"\u{1f9b8}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:"\u{1f9b9}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:"\u{1f9b9}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:"\u{1f936}",fitzpatrick_scale:!0,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:"\u{1f385}",fitzpatrick_scale:!0,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:"\u{1f9d9}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:"\u{1f9d9}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_elf:{keywords:["woman","female"],char:"\u{1f9dd}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_elf:{keywords:["man","male"],char:"\u{1f9dd}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_vampire:{keywords:["woman","female"],char:"\u{1f9db}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:"\u{1f9db}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:"\u{1f9df}\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:"\u{1f9df}\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"people"},woman_genie:{keywords:["woman","female"],char:"\u{1f9de}\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"people"},man_genie:{keywords:["man","male"],char:"\u{1f9de}\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:"\u{1f9dc}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},merman:{keywords:["man","male","triton"],char:"\u{1f9dc}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_fairy:{keywords:["woman","female"],char:"\u{1f9da}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_fairy:{keywords:["man","male"],char:"\u{1f9da}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},angel:{keywords:["heaven","wings","halo"],char:"\u{1f47c}",fitzpatrick_scale:!0,category:"people"},pregnant_woman:{keywords:["baby"],char:"\u{1f930}",fitzpatrick_scale:!0,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:"\u{1f931}",fitzpatrick_scale:!0,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:"\u{1f478}",fitzpatrick_scale:!0,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:"\u{1f934}",fitzpatrick_scale:!0,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:"\u{1f470}",fitzpatrick_scale:!0,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:"\u{1f935}",fitzpatrick_scale:!0,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:"\u{1f3c3}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:"\u{1f3c3}",fitzpatrick_scale:!0,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:"\u{1f6b6}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},walking_man:{keywords:["human","feet","steps"],char:"\u{1f6b6}",fitzpatrick_scale:!0,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:"\u{1f483}",fitzpatrick_scale:!0,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:"\u{1f57a}",fitzpatrick_scale:!0,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:"\u{1f46f}",fitzpatrick_scale:!1,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:"\u{1f46f}\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:"\u{1f46b}",fitzpatrick_scale:!1,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:"\u{1f46c}",fitzpatrick_scale:!1,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:"\u{1f46d}",fitzpatrick_scale:!1,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:"\u{1f647}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},bowing_man:{keywords:["man","male","boy"],char:"\u{1f647}",fitzpatrick_scale:!0,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:"\u{1f926}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:"\u{1f926}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:"\u{1f937}",fitzpatrick_scale:!0,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:"\u{1f937}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:"\u{1f481}",fitzpatrick_scale:!0,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:"\u{1f481}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:"\u{1f645}",fitzpatrick_scale:!0,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:"\u{1f645}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:"\u{1f646}",fitzpatrick_scale:!0,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:"\u{1f646}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:"\u{1f64b}",fitzpatrick_scale:!0,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:"\u{1f64b}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:"\u{1f64e}",fitzpatrick_scale:!0,category:"people"},pouting_man:{keywords:["male","boy","man"],char:"\u{1f64e}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:"\u{1f64d}",fitzpatrick_scale:!0,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:"\u{1f64d}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:"\u{1f487}",fitzpatrick_scale:!0,category:"people"},haircut_man:{keywords:["male","boy","man"],char:"\u{1f487}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:"\u{1f486}",fitzpatrick_scale:!0,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:"\u{1f486}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:"\u{1f9d6}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:"\u{1f9d6}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"\u{1f491}",fitzpatrick_scale:!1,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"\u{1f469}\u200d\u2764\ufe0f\u200d\u{1f469}",fitzpatrick_scale:!1,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"\u{1f468}\u200d\u2764\ufe0f\u200d\u{1f468}",fitzpatrick_scale:!1,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"\u{1f48f}",fitzpatrick_scale:!1,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"\u{1f469}\u200d\u2764\ufe0f\u200d\u{1f48b}\u200d\u{1f469}",fitzpatrick_scale:!1,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:"\u{1f468}\u200d\u2764\ufe0f\u200d\u{1f48b}\u200d\u{1f468}",fitzpatrick_scale:!1,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:"\u{1f46a}",fitzpatrick_scale:!1,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:"\u{1f468}\u200d\u{1f469}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f469}\u200d\u{1f466}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f469}\u200d\u{1f469}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:"\u{1f469}\u200d\u{1f469}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f469}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f469}\u200d\u{1f469}\u200d\u{1f466}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"\u{1f469}\u200d\u{1f469}\u200d\u{1f467}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f468}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f468}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f468}\u200d\u{1f467}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f468}\u200d\u{1f466}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:"\u{1f468}\u200d\u{1f468}\u200d\u{1f467}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:"\u{1f469}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:"\u{1f469}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:"\u{1f469}\u200d\u{1f467}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:"\u{1f469}\u200d\u{1f466}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:"\u{1f469}\u200d\u{1f467}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:"\u{1f468}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:"\u{1f468}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:"\u{1f468}\u200d\u{1f467}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:"\u{1f468}\u200d\u{1f466}\u200d\u{1f466}",fitzpatrick_scale:!1,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:"\u{1f468}\u200d\u{1f467}\u200d\u{1f467}",fitzpatrick_scale:!1,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:"\u{1f9f6}",fitzpatrick_scale:!1,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:"\u{1f9f5}",fitzpatrick_scale:!1,category:"people"},coat:{keywords:["jacket"],char:"\u{1f9e5}",fitzpatrick_scale:!1,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:"\u{1f97c}",fitzpatrick_scale:!1,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:"\u{1f45a}",fitzpatrick_scale:!1,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:"\u{1f455}",fitzpatrick_scale:!1,category:"people"},jeans:{keywords:["fashion","shopping"],char:"\u{1f456}",fitzpatrick_scale:!1,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:"\u{1f454}",fitzpatrick_scale:!1,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:"\u{1f457}",fitzpatrick_scale:!1,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:"\u{1f459}",fitzpatrick_scale:!1,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:"\u{1f458}",fitzpatrick_scale:!1,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:"\u{1f484}",fitzpatrick_scale:!1,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:"\u{1f48b}",fitzpatrick_scale:!1,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:"\u{1f463}",fitzpatrick_scale:!1,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:"\u{1f97f}",fitzpatrick_scale:!1,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:"\u{1f460}",fitzpatrick_scale:!1,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:"\u{1f461}",fitzpatrick_scale:!1,category:"people"},boot:{keywords:["shoes","fashion"],char:"\u{1f462}",fitzpatrick_scale:!1,category:"people"},mans_shoe:{keywords:["fashion","male"],char:"\u{1f45e}",fitzpatrick_scale:!1,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:"\u{1f45f}",fitzpatrick_scale:!1,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:"\u{1f97e}",fitzpatrick_scale:!1,category:"people"},socks:{keywords:["stockings","clothes"],char:"\u{1f9e6}",fitzpatrick_scale:!1,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:"\u{1f9e4}",fitzpatrick_scale:!1,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:"\u{1f9e3}",fitzpatrick_scale:!1,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:"\u{1f452}",fitzpatrick_scale:!1,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:"\u{1f3a9}",fitzpatrick_scale:!1,category:"people"},billed_hat:{keywords:["cap","baseball"],char:"\u{1f9e2}",fitzpatrick_scale:!1,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:"\u26d1",fitzpatrick_scale:!1,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:"\u{1f393}",fitzpatrick_scale:!1,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:"\u{1f451}",fitzpatrick_scale:!1,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:"\u{1f392}",fitzpatrick_scale:!1,category:"people"},luggage:{keywords:["packing","travel"],char:"\u{1f9f3}",fitzpatrick_scale:!1,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:"\u{1f45d}",fitzpatrick_scale:!1,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:"\u{1f45b}",fitzpatrick_scale:!1,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:"\u{1f45c}",fitzpatrick_scale:!1,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:"\u{1f4bc}",fitzpatrick_scale:!1,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:"\u{1f453}",fitzpatrick_scale:!1,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:"\u{1f576}",fitzpatrick_scale:!1,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:"\u{1f97d}",fitzpatrick_scale:!1,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:"\u{1f48d}",fitzpatrick_scale:!1,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:"\u{1f302}",fitzpatrick_scale:!1,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:"\u{1f436}",fitzpatrick_scale:!1,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:"\u{1f431}",fitzpatrick_scale:!1,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:"\u{1f42d}",fitzpatrick_scale:!1,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:"\u{1f439}",fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:"\u{1f430}",fitzpatrick_scale:!1,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:"\u{1f98a}",fitzpatrick_scale:!1,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:"\u{1f43b}",fitzpatrick_scale:!1,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:"\u{1f43c}",fitzpatrick_scale:!1,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:"\u{1f428}",fitzpatrick_scale:!1,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:"\u{1f42f}",fitzpatrick_scale:!1,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:"\u{1f981}",fitzpatrick_scale:!1,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:"\u{1f42e}",fitzpatrick_scale:!1,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:"\u{1f437}",fitzpatrick_scale:!1,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:"\u{1f43d}",fitzpatrick_scale:!1,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:"\u{1f438}",fitzpatrick_scale:!1,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:"\u{1f991}",fitzpatrick_scale:!1,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:"\u{1f419}",fitzpatrick_scale:!1,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:"\u{1f990}",fitzpatrick_scale:!1,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:"\u{1f435}",fitzpatrick_scale:!1,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:"\u{1f98d}",fitzpatrick_scale:!1,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:"\u{1f648}",fitzpatrick_scale:!1,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:"\u{1f649}",fitzpatrick_scale:!1,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:"\u{1f64a}",fitzpatrick_scale:!1,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:"\u{1f412}",fitzpatrick_scale:!1,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:"\u{1f414}",fitzpatrick_scale:!1,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:"\u{1f427}",fitzpatrick_scale:!1,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:"\u{1f426}",fitzpatrick_scale:!1,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:"\u{1f424}",fitzpatrick_scale:!1,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:"\u{1f423}",fitzpatrick_scale:!1,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:"\u{1f425}",fitzpatrick_scale:!1,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:"\u{1f986}",fitzpatrick_scale:!1,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:"\u{1f985}",fitzpatrick_scale:!1,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:"\u{1f989}",fitzpatrick_scale:!1,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:"\u{1f987}",fitzpatrick_scale:!1,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:"\u{1f43a}",fitzpatrick_scale:!1,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:"\u{1f417}",fitzpatrick_scale:!1,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:"\u{1f434}",fitzpatrick_scale:!1,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:"\u{1f984}",fitzpatrick_scale:!1,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:"\u{1f41d}",fitzpatrick_scale:!1,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:"\u{1f41b}",fitzpatrick_scale:!1,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:"\u{1f98b}",fitzpatrick_scale:!1,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:"\u{1f40c}",fitzpatrick_scale:!1,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:"\u{1f41e}",fitzpatrick_scale:!1,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:"\u{1f41c}",fitzpatrick_scale:!1,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:"\u{1f997}",fitzpatrick_scale:!1,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:"\u{1f577}",fitzpatrick_scale:!1,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:"\u{1f982}",fitzpatrick_scale:!1,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:"\u{1f980}",fitzpatrick_scale:!1,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:"\u{1f40d}",fitzpatrick_scale:!1,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:"\u{1f98e}",fitzpatrick_scale:!1,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:"\u{1f996}",fitzpatrick_scale:!1,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:"\u{1f995}",fitzpatrick_scale:!1,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:"\u{1f422}",fitzpatrick_scale:!1,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:"\u{1f420}",fitzpatrick_scale:!1,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:"\u{1f41f}",fitzpatrick_scale:!1,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:"\u{1f421}",fitzpatrick_scale:!1,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:"\u{1f42c}",fitzpatrick_scale:!1,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:"\u{1f988}",fitzpatrick_scale:!1,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:"\u{1f433}",fitzpatrick_scale:!1,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:"\u{1f40b}",fitzpatrick_scale:!1,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:"\u{1f40a}",fitzpatrick_scale:!1,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:"\u{1f406}",fitzpatrick_scale:!1,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:"\u{1f993}",fitzpatrick_scale:!1,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:"\u{1f405}",fitzpatrick_scale:!1,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:"\u{1f403}",fitzpatrick_scale:!1,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:"\u{1f402}",fitzpatrick_scale:!1,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:"\u{1f404}",fitzpatrick_scale:!1,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:"\u{1f98c}",fitzpatrick_scale:!1,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:"\u{1f42a}",fitzpatrick_scale:!1,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:"\u{1f42b}",fitzpatrick_scale:!1,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:"\u{1f992}",fitzpatrick_scale:!1,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:"\u{1f418}",fitzpatrick_scale:!1,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:"\u{1f98f}",fitzpatrick_scale:!1,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:"\u{1f410}",fitzpatrick_scale:!1,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:"\u{1f40f}",fitzpatrick_scale:!1,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:"\u{1f411}",fitzpatrick_scale:!1,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:"\u{1f40e}",fitzpatrick_scale:!1,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:"\u{1f416}",fitzpatrick_scale:!1,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:"\u{1f400}",fitzpatrick_scale:!1,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:"\u{1f401}",fitzpatrick_scale:!1,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:"\u{1f413}",fitzpatrick_scale:!1,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:"\u{1f983}",fitzpatrick_scale:!1,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:"\u{1f54a}",fitzpatrick_scale:!1,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:"\u{1f415}",fitzpatrick_scale:!1,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:"\u{1f429}",fitzpatrick_scale:!1,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:"\u{1f408}",fitzpatrick_scale:!1,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:"\u{1f407}",fitzpatrick_scale:!1,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:"\u{1f43f}",fitzpatrick_scale:!1,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:"\u{1f994}",fitzpatrick_scale:!1,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:"\u{1f99d}",fitzpatrick_scale:!1,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:"\u{1f999}",fitzpatrick_scale:!1,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:"\u{1f99b}",fitzpatrick_scale:!1,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:"\u{1f998}",fitzpatrick_scale:!1,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:"\u{1f9a1}",fitzpatrick_scale:!1,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:"\u{1f9a2}",fitzpatrick_scale:!1,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:"\u{1f99a}",fitzpatrick_scale:!1,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:"\u{1f99c}",fitzpatrick_scale:!1,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:"\u{1f99e}",fitzpatrick_scale:!1,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:"\u{1f99f}",fitzpatrick_scale:!1,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:"\u{1f43e}",fitzpatrick_scale:!1,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:"\u{1f409}",fitzpatrick_scale:!1,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:"\u{1f432}",fitzpatrick_scale:!1,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:"\u{1f335}",fitzpatrick_scale:!1,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:"\u{1f384}",fitzpatrick_scale:!1,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:"\u{1f332}",fitzpatrick_scale:!1,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:"\u{1f333}",fitzpatrick_scale:!1,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:"\u{1f334}",fitzpatrick_scale:!1,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:"\u{1f331}",fitzpatrick_scale:!1,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:"\u{1f33f}",fitzpatrick_scale:!1,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:"\u2618",fitzpatrick_scale:!1,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:"\u{1f340}",fitzpatrick_scale:!1,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:"\u{1f38d}",fitzpatrick_scale:!1,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:"\u{1f38b}",fitzpatrick_scale:!1,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:"\u{1f343}",fitzpatrick_scale:!1,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:"\u{1f342}",fitzpatrick_scale:!1,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:"\u{1f341}",fitzpatrick_scale:!1,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:"\u{1f33e}",fitzpatrick_scale:!1,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:"\u{1f33a}",fitzpatrick_scale:!1,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:"\u{1f33b}",fitzpatrick_scale:!1,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:"\u{1f339}",fitzpatrick_scale:!1,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:"\u{1f940}",fitzpatrick_scale:!1,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:"\u{1f337}",fitzpatrick_scale:!1,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:"\u{1f33c}",fitzpatrick_scale:!1,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:"\u{1f338}",fitzpatrick_scale:!1,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:"\u{1f490}",fitzpatrick_scale:!1,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:"\u{1f344}",fitzpatrick_scale:!1,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:"\u{1f330}",fitzpatrick_scale:!1,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:"\u{1f383}",fitzpatrick_scale:!1,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:"\u{1f41a}",fitzpatrick_scale:!1,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:"\u{1f578}",fitzpatrick_scale:!1,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:"\u{1f30e}",fitzpatrick_scale:!1,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:"\u{1f30d}",fitzpatrick_scale:!1,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:"\u{1f30f}",fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:"\u{1f315}",fitzpatrick_scale:!1,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:"\u{1f316}",fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f317}",fitzpatrick_scale:!1,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f318}",fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f311}",fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f312}",fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f313}",fitzpatrick_scale:!1,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:"\u{1f314}",fitzpatrick_scale:!1,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f31a}",fitzpatrick_scale:!1,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f31d}",fitzpatrick_scale:!1,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f31b}",fitzpatrick_scale:!1,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"\u{1f31c}",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:"\u{1f31e}",fitzpatrick_scale:!1,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:"\u{1f319}",fitzpatrick_scale:!1,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:"\u2b50",fitzpatrick_scale:!1,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:"\u{1f31f}",fitzpatrick_scale:!1,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:"\u{1f4ab}",fitzpatrick_scale:!1,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:"\u2728",fitzpatrick_scale:!1,category:"animals_and_nature"},comet:{keywords:["space"],char:"\u2604",fitzpatrick_scale:!1,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:"\u2600\ufe0f",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:"\u{1f324}",fitzpatrick_scale:!1,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:"\u26c5",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:"\u{1f325}",fitzpatrick_scale:!1,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:"\u{1f326}",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:"\u2601\ufe0f",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:"\u{1f327}",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:"\u26c8",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:"\u{1f329}",fitzpatrick_scale:!1,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:"\u26a1",fitzpatrick_scale:!1,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:"\u{1f525}",fitzpatrick_scale:!1,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:"\u{1f4a5}",fitzpatrick_scale:!1,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:"\u2744\ufe0f",fitzpatrick_scale:!1,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:"\u{1f328}",fitzpatrick_scale:!1,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:"\u26c4",fitzpatrick_scale:!1,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:"\u2603",fitzpatrick_scale:!1,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:"\u{1f32c}",fitzpatrick_scale:!1,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:"\u{1f4a8}",fitzpatrick_scale:!1,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:"\u{1f32a}",fitzpatrick_scale:!1,category:"animals_and_nature"},fog:{keywords:["weather"],char:"\u{1f32b}",fitzpatrick_scale:!1,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:"\u2602",fitzpatrick_scale:!1,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:"\u2614",fitzpatrick_scale:!1,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:"\u{1f4a7}",fitzpatrick_scale:!1,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:"\u{1f4a6}",fitzpatrick_scale:!1,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:"\u{1f30a}",fitzpatrick_scale:!1,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:"\u{1f34f}",fitzpatrick_scale:!1,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:"\u{1f34e}",fitzpatrick_scale:!1,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:"\u{1f350}",fitzpatrick_scale:!1,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:"\u{1f34a}",fitzpatrick_scale:!1,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:"\u{1f34b}",fitzpatrick_scale:!1,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:"\u{1f34c}",fitzpatrick_scale:!1,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:"\u{1f349}",fitzpatrick_scale:!1,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:"\u{1f347}",fitzpatrick_scale:!1,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:"\u{1f353}",fitzpatrick_scale:!1,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:"\u{1f348}",fitzpatrick_scale:!1,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:"\u{1f352}",fitzpatrick_scale:!1,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:"\u{1f351}",fitzpatrick_scale:!1,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:"\u{1f34d}",fitzpatrick_scale:!1,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:"\u{1f965}",fitzpatrick_scale:!1,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:"\u{1f95d}",fitzpatrick_scale:!1,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:"\u{1f96d}",fitzpatrick_scale:!1,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:"\u{1f951}",fitzpatrick_scale:!1,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:"\u{1f966}",fitzpatrick_scale:!1,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:"\u{1f345}",fitzpatrick_scale:!1,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:"\u{1f346}",fitzpatrick_scale:!1,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:"\u{1f952}",fitzpatrick_scale:!1,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:"\u{1f955}",fitzpatrick_scale:!1,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:"\u{1f336}",fitzpatrick_scale:!1,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:"\u{1f954}",fitzpatrick_scale:!1,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:"\u{1f33d}",fitzpatrick_scale:!1,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:"\u{1f96c}",fitzpatrick_scale:!1,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:"\u{1f360}",fitzpatrick_scale:!1,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:"\u{1f95c}",fitzpatrick_scale:!1,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:"\u{1f36f}",fitzpatrick_scale:!1,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:"\u{1f950}",fitzpatrick_scale:!1,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:"\u{1f35e}",fitzpatrick_scale:!1,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:"\u{1f956}",fitzpatrick_scale:!1,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:"\u{1f96f}",fitzpatrick_scale:!1,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:"\u{1f968}",fitzpatrick_scale:!1,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:"\u{1f9c0}",fitzpatrick_scale:!1,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:"\u{1f95a}",fitzpatrick_scale:!1,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:"\u{1f953}",fitzpatrick_scale:!1,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:"\u{1f969}",fitzpatrick_scale:!1,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:"\u{1f95e}",fitzpatrick_scale:!1,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:"\u{1f357}",fitzpatrick_scale:!1,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:"\u{1f356}",fitzpatrick_scale:!1,category:"food_and_drink"},bone:{keywords:["skeleton"],char:"\u{1f9b4}",fitzpatrick_scale:!1,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:"\u{1f364}",fitzpatrick_scale:!1,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:"\u{1f373}",fitzpatrick_scale:!1,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:"\u{1f354}",fitzpatrick_scale:!1,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:"\u{1f35f}",fitzpatrick_scale:!1,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:"\u{1f959}",fitzpatrick_scale:!1,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:"\u{1f32d}",fitzpatrick_scale:!1,category:"food_and_drink"},pizza:{keywords:["food","party"],char:"\u{1f355}",fitzpatrick_scale:!1,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:"\u{1f96a}",fitzpatrick_scale:!1,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:"\u{1f96b}",fitzpatrick_scale:!1,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:"\u{1f35d}",fitzpatrick_scale:!1,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:"\u{1f32e}",fitzpatrick_scale:!1,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:"\u{1f32f}",fitzpatrick_scale:!1,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:"\u{1f957}",fitzpatrick_scale:!1,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:"\u{1f958}",fitzpatrick_scale:!1,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:"\u{1f35c}",fitzpatrick_scale:!1,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:"\u{1f372}",fitzpatrick_scale:!1,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:"\u{1f365}",fitzpatrick_scale:!1,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:"\u{1f960}",fitzpatrick_scale:!1,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:"\u{1f363}",fitzpatrick_scale:!1,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:"\u{1f371}",fitzpatrick_scale:!1,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:"\u{1f35b}",fitzpatrick_scale:!1,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:"\u{1f359}",fitzpatrick_scale:!1,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:"\u{1f35a}",fitzpatrick_scale:!1,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:"\u{1f358}",fitzpatrick_scale:!1,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:"\u{1f362}",fitzpatrick_scale:!1,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:"\u{1f361}",fitzpatrick_scale:!1,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:"\u{1f367}",fitzpatrick_scale:!1,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:"\u{1f368}",fitzpatrick_scale:!1,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:"\u{1f366}",fitzpatrick_scale:!1,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:"\u{1f967}",fitzpatrick_scale:!1,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:"\u{1f370}",fitzpatrick_scale:!1,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:"\u{1f9c1}",fitzpatrick_scale:!1,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:"\u{1f96e}",fitzpatrick_scale:!1,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:"\u{1f382}",fitzpatrick_scale:!1,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:"\u{1f36e}",fitzpatrick_scale:!1,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:"\u{1f36c}",fitzpatrick_scale:!1,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:"\u{1f36d}",fitzpatrick_scale:!1,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:"\u{1f36b}",fitzpatrick_scale:!1,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:"\u{1f37f}",fitzpatrick_scale:!1,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:"\u{1f95f}",fitzpatrick_scale:!1,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:"\u{1f369}",fitzpatrick_scale:!1,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:"\u{1f36a}",fitzpatrick_scale:!1,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:"\u{1f95b}",fitzpatrick_scale:!1,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"\u{1f37a}",fitzpatrick_scale:!1,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"\u{1f37b}",fitzpatrick_scale:!1,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:"\u{1f942}",fitzpatrick_scale:!1,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:"\u{1f377}",fitzpatrick_scale:!1,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:"\u{1f943}",fitzpatrick_scale:!1,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:"\u{1f378}",fitzpatrick_scale:!1,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:"\u{1f379}",fitzpatrick_scale:!1,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:"\u{1f37e}",fitzpatrick_scale:!1,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:"\u{1f376}",fitzpatrick_scale:!1,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:"\u{1f375}",fitzpatrick_scale:!1,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:"\u{1f964}",fitzpatrick_scale:!1,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:"\u2615",fitzpatrick_scale:!1,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:"\u{1f37c}",fitzpatrick_scale:!1,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:"\u{1f9c2}",fitzpatrick_scale:!1,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:"\u{1f944}",fitzpatrick_scale:!1,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:"\u{1f374}",fitzpatrick_scale:!1,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:"\u{1f37d}",fitzpatrick_scale:!1,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:"\u{1f963}",fitzpatrick_scale:!1,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:"\u{1f961}",fitzpatrick_scale:!1,category:"food_and_drink"},chopsticks:{keywords:["food"],char:"\u{1f962}",fitzpatrick_scale:!1,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:"\u26bd",fitzpatrick_scale:!1,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:"\u{1f3c0}",fitzpatrick_scale:!1,category:"activity"},football:{keywords:["sports","balls","NFL"],char:"\u{1f3c8}",fitzpatrick_scale:!1,category:"activity"},baseball:{keywords:["sports","balls"],char:"\u26be",fitzpatrick_scale:!1,category:"activity"},softball:{keywords:["sports","balls"],char:"\u{1f94e}",fitzpatrick_scale:!1,category:"activity"},tennis:{keywords:["sports","balls","green"],char:"\u{1f3be}",fitzpatrick_scale:!1,category:"activity"},volleyball:{keywords:["sports","balls"],char:"\u{1f3d0}",fitzpatrick_scale:!1,category:"activity"},rugby_football:{keywords:["sports","team"],char:"\u{1f3c9}",fitzpatrick_scale:!1,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:"\u{1f94f}",fitzpatrick_scale:!1,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:"\u{1f3b1}",fitzpatrick_scale:!1,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:"\u26f3",fitzpatrick_scale:!1,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:"\u{1f3cc}\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"activity"},golfing_man:{keywords:["sports","business"],char:"\u{1f3cc}",fitzpatrick_scale:!0,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:"\u{1f3d3}",fitzpatrick_scale:!1,category:"activity"},badminton:{keywords:["sports"],char:"\u{1f3f8}",fitzpatrick_scale:!1,category:"activity"},goal_net:{keywords:["sports"],char:"\u{1f945}",fitzpatrick_scale:!1,category:"activity"},ice_hockey:{keywords:["sports"],char:"\u{1f3d2}",fitzpatrick_scale:!1,category:"activity"},field_hockey:{keywords:["sports"],char:"\u{1f3d1}",fitzpatrick_scale:!1,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:"\u{1f94d}",fitzpatrick_scale:!1,category:"activity"},cricket:{keywords:["sports"],char:"\u{1f3cf}",fitzpatrick_scale:!1,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:"\u{1f3bf}",fitzpatrick_scale:!1,category:"activity"},skier:{keywords:["sports","winter","snow"],char:"\u26f7",fitzpatrick_scale:!1,category:"activity"},snowboarder:{keywords:["sports","winter"],char:"\u{1f3c2}",fitzpatrick_scale:!0,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:"\u{1f93a}",fitzpatrick_scale:!1,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:"\u{1f93c}\u200d\u2640\ufe0f",fitzpatrick_scale:!1,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:"\u{1f93c}\u200d\u2642\ufe0f",fitzpatrick_scale:!1,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:"\u{1f938}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:"\u{1f938}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},woman_playing_handball:{keywords:["sports"],char:"\u{1f93e}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_playing_handball:{keywords:["sports"],char:"\u{1f93e}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},ice_skate:{keywords:["sports"],char:"\u26f8",fitzpatrick_scale:!1,category:"activity"},curling_stone:{keywords:["sports"],char:"\u{1f94c}",fitzpatrick_scale:!1,category:"activity"},skateboard:{keywords:["board"],char:"\u{1f6f9}",fitzpatrick_scale:!1,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:"\u{1f6f7}",fitzpatrick_scale:!1,category:"activity"},bow_and_arrow:{keywords:["sports"],char:"\u{1f3f9}",fitzpatrick_scale:!1,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:"\u{1f3a3}",fitzpatrick_scale:!1,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:"\u{1f94a}",fitzpatrick_scale:!1,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:"\u{1f94b}",fitzpatrick_scale:!1,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:"\u{1f6a3}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:"\u{1f6a3}",fitzpatrick_scale:!0,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:"\u{1f9d7}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:"\u{1f9d7}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:"\u{1f3ca}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:"\u{1f3ca}",fitzpatrick_scale:!0,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:"\u{1f93d}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:"\u{1f93d}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:"\u{1f9d8}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:"\u{1f9d8}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:"\u{1f3c4}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:"\u{1f3c4}",fitzpatrick_scale:!0,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:"\u{1f6c0}",fitzpatrick_scale:!0,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:"\u26f9\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},basketball_man:{keywords:["sports","human"],char:"\u26f9",fitzpatrick_scale:!0,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:"\u{1f3cb}\ufe0f\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:"\u{1f3cb}",fitzpatrick_scale:!0,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:"\u{1f6b4}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:"\u{1f6b4}",fitzpatrick_scale:!0,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:"\u{1f6b5}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:"\u{1f6b5}",fitzpatrick_scale:!0,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:"\u{1f3c7}",fitzpatrick_scale:!0,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:"\u{1f574}",fitzpatrick_scale:!0,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:"\u{1f3c6}",fitzpatrick_scale:!1,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:"\u{1f3bd}",fitzpatrick_scale:!1,category:"activity"},medal_sports:{keywords:["award","winning"],char:"\u{1f3c5}",fitzpatrick_scale:!1,category:"activity"},medal_military:{keywords:["award","winning","army"],char:"\u{1f396}",fitzpatrick_scale:!1,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:"\u{1f947}",fitzpatrick_scale:!1,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:"\u{1f948}",fitzpatrick_scale:!1,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:"\u{1f949}",fitzpatrick_scale:!1,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:"\u{1f397}",fitzpatrick_scale:!1,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:"\u{1f3f5}",fitzpatrick_scale:!1,category:"activity"},ticket:{keywords:["event","concert","pass"],char:"\u{1f3ab}",fitzpatrick_scale:!1,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:"\u{1f39f}",fitzpatrick_scale:!1,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:"\u{1f3ad}",fitzpatrick_scale:!1,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:"\u{1f3a8}",fitzpatrick_scale:!1,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:"\u{1f3aa}",fitzpatrick_scale:!1,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:"\u{1f939}\u200d\u2640\ufe0f",fitzpatrick_scale:!0,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:"\u{1f939}\u200d\u2642\ufe0f",fitzpatrick_scale:!0,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:"\u{1f3a4}",fitzpatrick_scale:!1,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:"\u{1f3a7}",fitzpatrick_scale:!1,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:"\u{1f3bc}",fitzpatrick_scale:!1,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:"\u{1f3b9}",fitzpatrick_scale:!1,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:"\u{1f941}",fitzpatrick_scale:!1,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:"\u{1f3b7}",fitzpatrick_scale:!1,category:"activity"},trumpet:{keywords:["music","brass"],char:"\u{1f3ba}",fitzpatrick_scale:!1,category:"activity"},guitar:{keywords:["music","instrument"],char:"\u{1f3b8}",fitzpatrick_scale:!1,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:"\u{1f3bb}",fitzpatrick_scale:!1,category:"activity"},clapper:{keywords:["movie","film","record"],char:"\u{1f3ac}",fitzpatrick_scale:!1,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:"\u{1f3ae}",fitzpatrick_scale:!1,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:"\u{1f47e}",fitzpatrick_scale:!1,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:"\u{1f3af}",fitzpatrick_scale:!1,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:"\u{1f3b2}",fitzpatrick_scale:!1,category:"activity"},chess_pawn:{keywords:["expendable"],char:"\u265f",fitzpatrick_scale:!1,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:"\u{1f3b0}",fitzpatrick_scale:!1,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:"\u{1f9e9}",fitzpatrick_scale:!1,category:"activity"},bowling:{keywords:["sports","fun","play"],char:"\u{1f3b3}",fitzpatrick_scale:!1,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:"\u{1f697}",fitzpatrick_scale:!1,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:"\u{1f695}",fitzpatrick_scale:!1,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:"\u{1f699}",fitzpatrick_scale:!1,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:"\u{1f68c}",fitzpatrick_scale:!1,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:"\u{1f68e}",fitzpatrick_scale:!1,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:"\u{1f3ce}",fitzpatrick_scale:!1,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:"\u{1f693}",fitzpatrick_scale:!1,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:"\u{1f691}",fitzpatrick_scale:!1,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:"\u{1f692}",fitzpatrick_scale:!1,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:"\u{1f690}",fitzpatrick_scale:!1,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:"\u{1f69a}",fitzpatrick_scale:!1,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:"\u{1f69b}",fitzpatrick_scale:!1,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:"\u{1f69c}",fitzpatrick_scale:!1,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:"\u{1f6f4}",fitzpatrick_scale:!1,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:"\u{1f3cd}",fitzpatrick_scale:!1,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:"\u{1f6b2}",fitzpatrick_scale:!1,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:"\u{1f6f5}",fitzpatrick_scale:!1,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:"\u{1f6a8}",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:"\u{1f694}",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:"\u{1f68d}",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:"\u{1f698}",fitzpatrick_scale:!1,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:"\u{1f696}",fitzpatrick_scale:!1,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:"\u{1f6a1}",fitzpatrick_scale:!1,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:"\u{1f6a0}",fitzpatrick_scale:!1,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:"\u{1f69f}",fitzpatrick_scale:!1,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:"\u{1f683}",fitzpatrick_scale:!1,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:"\u{1f68b}",fitzpatrick_scale:!1,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:"\u{1f69d}",fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:"\u{1f684}",fitzpatrick_scale:!1,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:"\u{1f685}",fitzpatrick_scale:!1,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:"\u{1f688}",fitzpatrick_scale:!1,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:"\u{1f69e}",fitzpatrick_scale:!1,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:"\u{1f682}",fitzpatrick_scale:!1,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:"\u{1f686}",fitzpatrick_scale:!1,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:"\u{1f687}",fitzpatrick_scale:!1,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:"\u{1f68a}",fitzpatrick_scale:!1,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:"\u{1f689}",fitzpatrick_scale:!1,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:"\u{1f6f8}",fitzpatrick_scale:!1,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:"\u{1f681}",fitzpatrick_scale:!1,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:"\u{1f6e9}",fitzpatrick_scale:!1,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:"\u2708\ufe0f",fitzpatrick_scale:!1,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:"\u{1f6eb}",fitzpatrick_scale:!1,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:"\u{1f6ec}",fitzpatrick_scale:!1,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:"\u26f5",fitzpatrick_scale:!1,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:"\u{1f6e5}",fitzpatrick_scale:!1,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:"\u{1f6a4}",fitzpatrick_scale:!1,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:"\u26f4",fitzpatrick_scale:!1,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:"\u{1f6f3}",fitzpatrick_scale:!1,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:"\u{1f680}",fitzpatrick_scale:!1,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:"\u{1f6f0}",fitzpatrick_scale:!1,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:"\u{1f4ba}",fitzpatrick_scale:!1,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:"\u{1f6f6}",fitzpatrick_scale:!1,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:"\u2693",fitzpatrick_scale:!1,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:"\u{1f6a7}",fitzpatrick_scale:!1,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:"\u26fd",fitzpatrick_scale:!1,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:"\u{1f68f}",fitzpatrick_scale:!1,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:"\u{1f6a6}",fitzpatrick_scale:!1,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:"\u{1f6a5}",fitzpatrick_scale:!1,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:"\u{1f3c1}",fitzpatrick_scale:!1,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:"\u{1f6a2}",fitzpatrick_scale:!1,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:"\u{1f3a1}",fitzpatrick_scale:!1,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:"\u{1f3a2}",fitzpatrick_scale:!1,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:"\u{1f3a0}",fitzpatrick_scale:!1,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:"\u{1f3d7}",fitzpatrick_scale:!1,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:"\u{1f301}",fitzpatrick_scale:!1,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:"\u{1f5fc}",fitzpatrick_scale:!1,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:"\u{1f3ed}",fitzpatrick_scale:!1,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:"\u26f2",fitzpatrick_scale:!1,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:"\u{1f391}",fitzpatrick_scale:!1,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:"\u26f0",fitzpatrick_scale:!1,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:"\u{1f3d4}",fitzpatrick_scale:!1,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:"\u{1f5fb}",fitzpatrick_scale:!1,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:"\u{1f30b}",fitzpatrick_scale:!1,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:"\u{1f5fe}",fitzpatrick_scale:!1,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:"\u{1f3d5}",fitzpatrick_scale:!1,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:"\u26fa",fitzpatrick_scale:!1,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:"\u{1f3de}",fitzpatrick_scale:!1,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:"\u{1f6e3}",fitzpatrick_scale:!1,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:"\u{1f6e4}",fitzpatrick_scale:!1,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:"\u{1f305}",fitzpatrick_scale:!1,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:"\u{1f304}",fitzpatrick_scale:!1,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:"\u{1f3dc}",fitzpatrick_scale:!1,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:"\u{1f3d6}",fitzpatrick_scale:!1,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:"\u{1f3dd}",fitzpatrick_scale:!1,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:"\u{1f307}",fitzpatrick_scale:!1,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:"\u{1f306}",fitzpatrick_scale:!1,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:"\u{1f3d9}",fitzpatrick_scale:!1,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:"\u{1f303}",fitzpatrick_scale:!1,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:"\u{1f309}",fitzpatrick_scale:!1,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:"\u{1f30c}",fitzpatrick_scale:!1,category:"travel_and_places"},stars:{keywords:["night","photo"],char:"\u{1f320}",fitzpatrick_scale:!1,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:"\u{1f387}",fitzpatrick_scale:!1,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:"\u{1f386}",fitzpatrick_scale:!1,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:"\u{1f308}",fitzpatrick_scale:!1,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:"\u{1f3d8}",fitzpatrick_scale:!1,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:"\u{1f3f0}",fitzpatrick_scale:!1,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:"\u{1f3ef}",fitzpatrick_scale:!1,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:"\u{1f3df}",fitzpatrick_scale:!1,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:"\u{1f5fd}",fitzpatrick_scale:!1,category:"travel_and_places"},house:{keywords:["building","home"],char:"\u{1f3e0}",fitzpatrick_scale:!1,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:"\u{1f3e1}",fitzpatrick_scale:!1,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:"\u{1f3da}",fitzpatrick_scale:!1,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:"\u{1f3e2}",fitzpatrick_scale:!1,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:"\u{1f3ec}",fitzpatrick_scale:!1,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:"\u{1f3e3}",fitzpatrick_scale:!1,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:"\u{1f3e4}",fitzpatrick_scale:!1,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:"\u{1f3e5}",fitzpatrick_scale:!1,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:"\u{1f3e6}",fitzpatrick_scale:!1,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:"\u{1f3e8}",fitzpatrick_scale:!1,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:"\u{1f3ea}",fitzpatrick_scale:!1,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:"\u{1f3eb}",fitzpatrick_scale:!1,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:"\u{1f3e9}",fitzpatrick_scale:!1,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:"\u{1f492}",fitzpatrick_scale:!1,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:"\u{1f3db}",fitzpatrick_scale:!1,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:"\u26ea",fitzpatrick_scale:!1,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:"\u{1f54c}",fitzpatrick_scale:!1,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:"\u{1f54d}",fitzpatrick_scale:!1,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:"\u{1f54b}",fitzpatrick_scale:!1,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:"\u26e9",fitzpatrick_scale:!1,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:"\u231a",fitzpatrick_scale:!1,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:"\u{1f4f1}",fitzpatrick_scale:!1,category:"objects"},calling:{keywords:["iphone","incoming"],char:"\u{1f4f2}",fitzpatrick_scale:!1,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:"\u{1f4bb}",fitzpatrick_scale:!1,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:"\u2328",fitzpatrick_scale:!1,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:"\u{1f5a5}",fitzpatrick_scale:!1,category:"objects"},printer:{keywords:["paper","ink"],char:"\u{1f5a8}",fitzpatrick_scale:!1,category:"objects"},computer_mouse:{keywords:["click"],char:"\u{1f5b1}",fitzpatrick_scale:!1,category:"objects"},trackball:{keywords:["technology","trackpad"],char:"\u{1f5b2}",fitzpatrick_scale:!1,category:"objects"},joystick:{keywords:["game","play"],char:"\u{1f579}",fitzpatrick_scale:!1,category:"objects"},clamp:{keywords:["tool"],char:"\u{1f5dc}",fitzpatrick_scale:!1,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:"\u{1f4bd}",fitzpatrick_scale:!1,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:"\u{1f4be}",fitzpatrick_scale:!1,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:"\u{1f4bf}",fitzpatrick_scale:!1,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:"\u{1f4c0}",fitzpatrick_scale:!1,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:"\u{1f4fc}",fitzpatrick_scale:!1,category:"objects"},camera:{keywords:["gadgets","photography"],char:"\u{1f4f7}",fitzpatrick_scale:!1,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:"\u{1f4f8}",fitzpatrick_scale:!1,category:"objects"},video_camera:{keywords:["film","record"],char:"\u{1f4f9}",fitzpatrick_scale:!1,category:"objects"},movie_camera:{keywords:["film","record"],char:"\u{1f3a5}",fitzpatrick_scale:!1,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:"\u{1f4fd}",fitzpatrick_scale:!1,category:"objects"},film_strip:{keywords:["movie"],char:"\u{1f39e}",fitzpatrick_scale:!1,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:"\u{1f4de}",fitzpatrick_scale:!1,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:"\u260e\ufe0f",fitzpatrick_scale:!1,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:"\u{1f4df}",fitzpatrick_scale:!1,category:"objects"},fax:{keywords:["communication","technology"],char:"\u{1f4e0}",fitzpatrick_scale:!1,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:"\u{1f4fa}",fitzpatrick_scale:!1,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:"\u{1f4fb}",fitzpatrick_scale:!1,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:"\u{1f399}",fitzpatrick_scale:!1,category:"objects"},level_slider:{keywords:["scale"],char:"\u{1f39a}",fitzpatrick_scale:!1,category:"objects"},control_knobs:{keywords:["dial"],char:"\u{1f39b}",fitzpatrick_scale:!1,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:"\u{1f9ed}",fitzpatrick_scale:!1,category:"objects"},stopwatch:{keywords:["time","deadline"],char:"\u23f1",fitzpatrick_scale:!1,category:"objects"},timer_clock:{keywords:["alarm"],char:"\u23f2",fitzpatrick_scale:!1,category:"objects"},alarm_clock:{keywords:["time","wake"],char:"\u23f0",fitzpatrick_scale:!1,category:"objects"},mantelpiece_clock:{keywords:["time"],char:"\u{1f570}",fitzpatrick_scale:!1,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:"\u23f3",fitzpatrick_scale:!1,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:"\u231b",fitzpatrick_scale:!1,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:"\u{1f4e1}",fitzpatrick_scale:!1,category:"objects"},battery:{keywords:["power","energy","sustain"],char:"\u{1f50b}",fitzpatrick_scale:!1,category:"objects"},electric_plug:{keywords:["charger","power"],char:"\u{1f50c}",fitzpatrick_scale:!1,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:"\u{1f4a1}",fitzpatrick_scale:!1,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:"\u{1f526}",fitzpatrick_scale:!1,category:"objects"},candle:{keywords:["fire","wax"],char:"\u{1f56f}",fitzpatrick_scale:!1,category:"objects"},fire_extinguisher:{keywords:["quench"],char:"\u{1f9ef}",fitzpatrick_scale:!1,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:"\u{1f5d1}",fitzpatrick_scale:!1,category:"objects"},oil_drum:{keywords:["barrell"],char:"\u{1f6e2}",fitzpatrick_scale:!1,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:"\u{1f4b8}",fitzpatrick_scale:!1,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:"\u{1f4b5}",fitzpatrick_scale:!1,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:"\u{1f4b4}",fitzpatrick_scale:!1,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:"\u{1f4b6}",fitzpatrick_scale:!1,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:"\u{1f4b7}",fitzpatrick_scale:!1,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:"\u{1f4b0}",fitzpatrick_scale:!1,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:"\u{1f4b3}",fitzpatrick_scale:!1,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:"\u{1f48e}",fitzpatrick_scale:!1,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:"\u2696",fitzpatrick_scale:!1,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:"\u{1f9f0}",fitzpatrick_scale:!1,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:"\u{1f527}",fitzpatrick_scale:!1,category:"objects"},hammer:{keywords:["tools","build","create"],char:"\u{1f528}",fitzpatrick_scale:!1,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:"\u2692",fitzpatrick_scale:!1,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:"\u{1f6e0}",fitzpatrick_scale:!1,category:"objects"},pick:{keywords:["tools","dig"],char:"\u26cf",fitzpatrick_scale:!1,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:"\u{1f529}",fitzpatrick_scale:!1,category:"objects"},gear:{keywords:["cog"],char:"\u2699",fitzpatrick_scale:!1,category:"objects"},brick:{keywords:["bricks"],char:"\u{1f9f1}",fitzpatrick_scale:!1,category:"objects"},chains:{keywords:["lock","arrest"],char:"\u26d3",fitzpatrick_scale:!1,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:"\u{1f9f2}",fitzpatrick_scale:!1,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:"\u{1f52b}",fitzpatrick_scale:!1,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:"\u{1f4a3}",fitzpatrick_scale:!1,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:"\u{1f9e8}",fitzpatrick_scale:!1,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:"\u{1f52a}",fitzpatrick_scale:!1,category:"objects"},dagger:{keywords:["weapon"],char:"\u{1f5e1}",fitzpatrick_scale:!1,category:"objects"},crossed_swords:{keywords:["weapon"],char:"\u2694",fitzpatrick_scale:!1,category:"objects"},shield:{keywords:["protection","security"],char:"\u{1f6e1}",fitzpatrick_scale:!1,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:"\u{1f6ac}",fitzpatrick_scale:!1,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:"\u2620",fitzpatrick_scale:!1,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:"\u26b0",fitzpatrick_scale:!1,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:"\u26b1",fitzpatrick_scale:!1,category:"objects"},amphora:{keywords:["vase","jar"],char:"\u{1f3fa}",fitzpatrick_scale:!1,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:"\u{1f52e}",fitzpatrick_scale:!1,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:"\u{1f4ff}",fitzpatrick_scale:!1,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:"\u{1f9ff}",fitzpatrick_scale:!1,category:"objects"},barber:{keywords:["hair","salon","style"],char:"\u{1f488}",fitzpatrick_scale:!1,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:"\u2697",fitzpatrick_scale:!1,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:"\u{1f52d}",fitzpatrick_scale:!1,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:"\u{1f52c}",fitzpatrick_scale:!1,category:"objects"},hole:{keywords:["embarrassing"],char:"\u{1f573}",fitzpatrick_scale:!1,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:"\u{1f48a}",fitzpatrick_scale:!1,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:"\u{1f489}",fitzpatrick_scale:!1,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:"\u{1f9ec}",fitzpatrick_scale:!1,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:"\u{1f9a0}",fitzpatrick_scale:!1,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:"\u{1f9eb}",fitzpatrick_scale:!1,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:"\u{1f9ea}",fitzpatrick_scale:!1,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:"\u{1f321}",fitzpatrick_scale:!1,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:"\u{1f9f9}",fitzpatrick_scale:!1,category:"objects"},basket:{keywords:["laundry"],char:"\u{1f9fa}",fitzpatrick_scale:!1,category:"objects"},toilet_paper:{keywords:["roll"],char:"\u{1f9fb}",fitzpatrick_scale:!1,category:"objects"},label:{keywords:["sale","tag"],char:"\u{1f3f7}",fitzpatrick_scale:!1,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:"\u{1f516}",fitzpatrick_scale:!1,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:"\u{1f6bd}",fitzpatrick_scale:!1,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:"\u{1f6bf}",fitzpatrick_scale:!1,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:"\u{1f6c1}",fitzpatrick_scale:!1,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:"\u{1f9fc}",fitzpatrick_scale:!1,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:"\u{1f9fd}",fitzpatrick_scale:!1,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:"\u{1f9f4}",fitzpatrick_scale:!1,category:"objects"},key:{keywords:["lock","door","password"],char:"\u{1f511}",fitzpatrick_scale:!1,category:"objects"},old_key:{keywords:["lock","door","password"],char:"\u{1f5dd}",fitzpatrick_scale:!1,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:"\u{1f6cb}",fitzpatrick_scale:!1,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:"\u{1f6cc}",fitzpatrick_scale:!0,category:"objects"},bed:{keywords:["sleep","rest"],char:"\u{1f6cf}",fitzpatrick_scale:!1,category:"objects"},door:{keywords:["house","entry","exit"],char:"\u{1f6aa}",fitzpatrick_scale:!1,category:"objects"},bellhop_bell:{keywords:["service"],char:"\u{1f6ce}",fitzpatrick_scale:!1,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:"\u{1f9f8}",fitzpatrick_scale:!1,category:"objects"},framed_picture:{keywords:["photography"],char:"\u{1f5bc}",fitzpatrick_scale:!1,category:"objects"},world_map:{keywords:["location","direction"],char:"\u{1f5fa}",fitzpatrick_scale:!1,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:"\u26f1",fitzpatrick_scale:!1,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:"\u{1f5ff}",fitzpatrick_scale:!1,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:"\u{1f6cd}",fitzpatrick_scale:!1,category:"objects"},shopping_cart:{keywords:["trolley"],char:"\u{1f6d2}",fitzpatrick_scale:!1,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:"\u{1f388}",fitzpatrick_scale:!1,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:"\u{1f38f}",fitzpatrick_scale:!1,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:"\u{1f380}",fitzpatrick_scale:!1,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:"\u{1f381}",fitzpatrick_scale:!1,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:"\u{1f38a}",fitzpatrick_scale:!1,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:"\u{1f389}",fitzpatrick_scale:!1,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:"\u{1f38e}",fitzpatrick_scale:!1,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:"\u{1f390}",fitzpatrick_scale:!1,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:"\u{1f38c}",fitzpatrick_scale:!1,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:"\u{1f3ee}",fitzpatrick_scale:!1,category:"objects"},red_envelope:{keywords:["gift"],char:"\u{1f9e7}",fitzpatrick_scale:!1,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:"\u2709\ufe0f",fitzpatrick_scale:!1,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:"\u{1f4e9}",fitzpatrick_scale:!1,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:"\u{1f4e8}",fitzpatrick_scale:!1,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:"\u{1f4e7}",fitzpatrick_scale:!1,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:"\u{1f48c}",fitzpatrick_scale:!1,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:"\u{1f4ee}",fitzpatrick_scale:!1,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:"\u{1f4ea}",fitzpatrick_scale:!1,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:"\u{1f4eb}",fitzpatrick_scale:!1,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:"\u{1f4ec}",fitzpatrick_scale:!1,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:"\u{1f4ed}",fitzpatrick_scale:!1,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:"\u{1f4e6}",fitzpatrick_scale:!1,category:"objects"},postal_horn:{keywords:["instrument","music"],char:"\u{1f4ef}",fitzpatrick_scale:!1,category:"objects"},inbox_tray:{keywords:["email","documents"],char:"\u{1f4e5}",fitzpatrick_scale:!1,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:"\u{1f4e4}",fitzpatrick_scale:!1,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:"\u{1f4dc}",fitzpatrick_scale:!1,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:"\u{1f4c3}",fitzpatrick_scale:!1,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:"\u{1f4d1}",fitzpatrick_scale:!1,category:"objects"},receipt:{keywords:["accounting","expenses"],char:"\u{1f9fe}",fitzpatrick_scale:!1,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:"\u{1f4ca}",fitzpatrick_scale:!1,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:"\u{1f4c8}",fitzpatrick_scale:!1,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:"\u{1f4c9}",fitzpatrick_scale:!1,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:"\u{1f4c4}",fitzpatrick_scale:!1,category:"objects"},date:{keywords:["calendar","schedule"],char:"\u{1f4c5}",fitzpatrick_scale:!1,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:"\u{1f4c6}",fitzpatrick_scale:!1,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:"\u{1f5d3}",fitzpatrick_scale:!1,category:"objects"},card_index:{keywords:["business","stationery"],char:"\u{1f4c7}",fitzpatrick_scale:!1,category:"objects"},card_file_box:{keywords:["business","stationery"],char:"\u{1f5c3}",fitzpatrick_scale:!1,category:"objects"},ballot_box:{keywords:["election","vote"],char:"\u{1f5f3}",fitzpatrick_scale:!1,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:"\u{1f5c4}",fitzpatrick_scale:!1,category:"objects"},clipboard:{keywords:["stationery","documents"],char:"\u{1f4cb}",fitzpatrick_scale:!1,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:"\u{1f5d2}",fitzpatrick_scale:!1,category:"objects"},file_folder:{keywords:["documents","business","office"],char:"\u{1f4c1}",fitzpatrick_scale:!1,category:"objects"},open_file_folder:{keywords:["documents","load"],char:"\u{1f4c2}",fitzpatrick_scale:!1,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:"\u{1f5c2}",fitzpatrick_scale:!1,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:"\u{1f5de}",fitzpatrick_scale:!1,category:"objects"},newspaper:{keywords:["press","headline"],char:"\u{1f4f0}",fitzpatrick_scale:!1,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:"\u{1f4d3}",fitzpatrick_scale:!1,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:"\u{1f4d5}",fitzpatrick_scale:!1,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:"\u{1f4d7}",fitzpatrick_scale:!1,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:"\u{1f4d8}",fitzpatrick_scale:!1,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:"\u{1f4d9}",fitzpatrick_scale:!1,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:"\u{1f4d4}",fitzpatrick_scale:!1,category:"objects"},ledger:{keywords:["notes","paper"],char:"\u{1f4d2}",fitzpatrick_scale:!1,category:"objects"},books:{keywords:["literature","library","study"],char:"\u{1f4da}",fitzpatrick_scale:!1,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:"\u{1f4d6}",fitzpatrick_scale:!1,category:"objects"},safety_pin:{keywords:["diaper"],char:"\u{1f9f7}",fitzpatrick_scale:!1,category:"objects"},link:{keywords:["rings","url"],char:"\u{1f517}",fitzpatrick_scale:!1,category:"objects"},paperclip:{keywords:["documents","stationery"],char:"\u{1f4ce}",fitzpatrick_scale:!1,category:"objects"},paperclips:{keywords:["documents","stationery"],char:"\u{1f587}",fitzpatrick_scale:!1,category:"objects"},scissors:{keywords:["stationery","cut"],char:"\u2702\ufe0f",fitzpatrick_scale:!1,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:"\u{1f4d0}",fitzpatrick_scale:!1,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:"\u{1f4cf}",fitzpatrick_scale:!1,category:"objects"},abacus:{keywords:["calculation"],char:"\u{1f9ee}",fitzpatrick_scale:!1,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:"\u{1f4cc}",fitzpatrick_scale:!1,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:"\u{1f4cd}",fitzpatrick_scale:!1,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:"\u{1f6a9}",fitzpatrick_scale:!1,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:"\u{1f3f3}",fitzpatrick_scale:!1,category:"objects"},black_flag:{keywords:["pirate"],char:"\u{1f3f4}",fitzpatrick_scale:!1,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:"\u{1f3f3}\ufe0f\u200d\u{1f308}",fitzpatrick_scale:!1,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:"\u{1f510}",fitzpatrick_scale:!1,category:"objects"},lock:{keywords:["security","password","padlock"],char:"\u{1f512}",fitzpatrick_scale:!1,category:"objects"},unlock:{keywords:["privacy","security"],char:"\u{1f513}",fitzpatrick_scale:!1,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:"\u{1f50f}",fitzpatrick_scale:!1,category:"objects"},pen:{keywords:["stationery","writing","write"],char:"\u{1f58a}",fitzpatrick_scale:!1,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:"\u{1f58b}",fitzpatrick_scale:!1,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:"\u2712\ufe0f",fitzpatrick_scale:!1,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:"\u{1f4dd}",fitzpatrick_scale:!1,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:"\u270f\ufe0f",fitzpatrick_scale:!1,category:"objects"},crayon:{keywords:["drawing","creativity"],char:"\u{1f58d}",fitzpatrick_scale:!1,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:"\u{1f58c}",fitzpatrick_scale:!1,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:"\u{1f50d}",fitzpatrick_scale:!1,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:"\u{1f50e}",fitzpatrick_scale:!1,category:"objects"},heart:{keywords:["love","like","valentines"],char:"\u2764\ufe0f",fitzpatrick_scale:!1,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f9e1}",fitzpatrick_scale:!1,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f49b}",fitzpatrick_scale:!1,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f49a}",fitzpatrick_scale:!1,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f499}",fitzpatrick_scale:!1,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f49c}",fitzpatrick_scale:!1,category:"symbols"},black_heart:{keywords:["evil"],char:"\u{1f5a4}",fitzpatrick_scale:!1,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:"\u{1f494}",fitzpatrick_scale:!1,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:"\u2763",fitzpatrick_scale:!1,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:"\u{1f495}",fitzpatrick_scale:!1,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:"\u{1f49e}",fitzpatrick_scale:!1,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:"\u{1f493}",fitzpatrick_scale:!1,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:"\u{1f497}",fitzpatrick_scale:!1,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:"\u{1f496}",fitzpatrick_scale:!1,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:"\u{1f498}",fitzpatrick_scale:!1,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:"\u{1f49d}",fitzpatrick_scale:!1,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:"\u{1f49f}",fitzpatrick_scale:!1,category:"symbols"},peace_symbol:{keywords:["hippie"],char:"\u262e",fitzpatrick_scale:!1,category:"symbols"},latin_cross:{keywords:["christianity"],char:"\u271d",fitzpatrick_scale:!1,category:"symbols"},star_and_crescent:{keywords:["islam"],char:"\u262a",fitzpatrick_scale:!1,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"\u{1f549}",fitzpatrick_scale:!1,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"\u2638",fitzpatrick_scale:!1,category:"symbols"},star_of_david:{keywords:["judaism"],char:"\u2721",fitzpatrick_scale:!1,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:"\u{1f52f}",fitzpatrick_scale:!1,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:"\u{1f54e}",fitzpatrick_scale:!1,category:"symbols"},yin_yang:{keywords:["balance"],char:"\u262f",fitzpatrick_scale:!1,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:"\u2626",fitzpatrick_scale:!1,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:"\u{1f6d0}",fitzpatrick_scale:!1,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:"\u26ce",fitzpatrick_scale:!1,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u2648",fitzpatrick_scale:!1,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:"\u2649",fitzpatrick_scale:!1,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u264a",fitzpatrick_scale:!1,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u264b",fitzpatrick_scale:!1,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u264c",fitzpatrick_scale:!1,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u264d",fitzpatrick_scale:!1,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u264e",fitzpatrick_scale:!1,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:"\u264f",fitzpatrick_scale:!1,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u2650",fitzpatrick_scale:!1,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:"\u2651",fitzpatrick_scale:!1,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:"\u2652",fitzpatrick_scale:!1,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:"\u2653",fitzpatrick_scale:!1,category:"symbols"},id:{keywords:["purple-square","words"],char:"\u{1f194}",fitzpatrick_scale:!1,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:"\u269b",fitzpatrick_scale:!1,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:"\u{1f233}",fitzpatrick_scale:!1,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:"\u{1f239}",fitzpatrick_scale:!1,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:"\u2622",fitzpatrick_scale:!1,category:"symbols"},biohazard:{keywords:["danger"],char:"\u2623",fitzpatrick_scale:!1,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:"\u{1f4f4}",fitzpatrick_scale:!1,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:"\u{1f4f3}",fitzpatrick_scale:!1,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:"\u{1f236}",fitzpatrick_scale:!1,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:"\u{1f21a}",fitzpatrick_scale:!1,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:"\u{1f238}",fitzpatrick_scale:!1,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:"\u{1f23a}",fitzpatrick_scale:!1,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:"\u{1f237}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:"\u2734\ufe0f",fitzpatrick_scale:!1,category:"symbols"},vs:{keywords:["words","orange-square"],char:"\u{1f19a}",fitzpatrick_scale:!1,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:"\u{1f251}",fitzpatrick_scale:!1,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:"\u{1f4ae}",fitzpatrick_scale:!1,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:"\u{1f250}",fitzpatrick_scale:!1,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:"\u3299\ufe0f",fitzpatrick_scale:!1,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:"\u3297\ufe0f",fitzpatrick_scale:!1,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:"\u{1f234}",fitzpatrick_scale:!1,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:"\u{1f235}",fitzpatrick_scale:!1,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:"\u{1f232}",fitzpatrick_scale:!1,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:"\u{1f170}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:"\u{1f171}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:"\u{1f18e}",fitzpatrick_scale:!1,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:"\u{1f191}",fitzpatrick_scale:!1,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:"\u{1f17e}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:"\u{1f198}",fitzpatrick_scale:!1,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:"\u26d4",fitzpatrick_scale:!1,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:"\u{1f4db}",fitzpatrick_scale:!1,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:"\u{1f6ab}",fitzpatrick_scale:!1,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:"\u274c",fitzpatrick_scale:!1,category:"symbols"},o:{keywords:["circle","round"],char:"\u2b55",fitzpatrick_scale:!1,category:"symbols"},stop_sign:{keywords:["stop"],char:"\u{1f6d1}",fitzpatrick_scale:!1,category:"symbols"},anger:{keywords:["angry","mad"],char:"\u{1f4a2}",fitzpatrick_scale:!1,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:"\u2668\ufe0f",fitzpatrick_scale:!1,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:"\u{1f6b7}",fitzpatrick_scale:!1,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:"\u{1f6af}",fitzpatrick_scale:!1,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:"\u{1f6b3}",fitzpatrick_scale:!1,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:"\u{1f6b1}",fitzpatrick_scale:!1,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:"\u{1f51e}",fitzpatrick_scale:!1,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:"\u{1f4f5}",fitzpatrick_scale:!1,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:"\u2757",fitzpatrick_scale:!1,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:"\u2755",fitzpatrick_scale:!1,category:"symbols"},question:{keywords:["doubt","confused"],char:"\u2753",fitzpatrick_scale:!1,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:"\u2754",fitzpatrick_scale:!1,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:"\u203c\ufe0f",fitzpatrick_scale:!1,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:"\u2049\ufe0f",fitzpatrick_scale:!1,category:"symbols"},100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:"\u{1f4af}",fitzpatrick_scale:!1,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:"\u{1f505}",fitzpatrick_scale:!1,category:"symbols"},high_brightness:{keywords:["sun","light"],char:"\u{1f506}",fitzpatrick_scale:!1,category:"symbols"},trident:{keywords:["weapon","spear"],char:"\u{1f531}",fitzpatrick_scale:!1,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:"\u269c",fitzpatrick_scale:!1,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:"\u303d\ufe0f",fitzpatrick_scale:!1,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:"\u26a0\ufe0f",fitzpatrick_scale:!1,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:"\u{1f6b8}",fitzpatrick_scale:!1,category:"symbols"},beginner:{keywords:["badge","shield"],char:"\u{1f530}",fitzpatrick_scale:!1,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:"\u267b\ufe0f",fitzpatrick_scale:!1,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:"\u{1f22f}",fitzpatrick_scale:!1,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:"\u{1f4b9}",fitzpatrick_scale:!1,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:"\u2747\ufe0f",fitzpatrick_scale:!1,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:"\u2733\ufe0f",fitzpatrick_scale:!1,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:"\u274e",fitzpatrick_scale:!1,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:"\u2705",fitzpatrick_scale:!1,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:"\u{1f4a0}",fitzpatrick_scale:!1,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:"\u{1f300}",fitzpatrick_scale:!1,category:"symbols"},loop:{keywords:["tape","cassette"],char:"\u27bf",fitzpatrick_scale:!1,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:"\u{1f310}",fitzpatrick_scale:!1,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:"\u24c2\ufe0f",fitzpatrick_scale:!1,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:"\u{1f3e7}",fitzpatrick_scale:!1,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:"\u{1f202}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:"\u{1f6c2}",fitzpatrick_scale:!1,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:"\u{1f6c3}",fitzpatrick_scale:!1,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:"\u{1f6c4}",fitzpatrick_scale:!1,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:"\u{1f6c5}",fitzpatrick_scale:!1,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:"\u267f",fitzpatrick_scale:!1,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:"\u{1f6ad}",fitzpatrick_scale:!1,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:"\u{1f6be}",fitzpatrick_scale:!1,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:"\u{1f17f}\ufe0f",fitzpatrick_scale:!1,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:"\u{1f6b0}",fitzpatrick_scale:!1,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:"\u{1f6b9}",fitzpatrick_scale:!1,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:"\u{1f6ba}",fitzpatrick_scale:!1,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:"\u{1f6bc}",fitzpatrick_scale:!1,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:"\u{1f6bb}",fitzpatrick_scale:!1,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:"\u{1f6ae}",fitzpatrick_scale:!1,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:"\u{1f3a6}",fitzpatrick_scale:!1,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:"\u{1f4f6}",fitzpatrick_scale:!1,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:"\u{1f201}",fitzpatrick_scale:!1,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:"\u{1f196}",fitzpatrick_scale:!1,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:"\u{1f197}",fitzpatrick_scale:!1,category:"symbols"},up:{keywords:["blue-square","above","high"],char:"\u{1f199}",fitzpatrick_scale:!1,category:"symbols"},cool:{keywords:["words","blue-square"],char:"\u{1f192}",fitzpatrick_scale:!1,category:"symbols"},new:{keywords:["blue-square","words","start"],char:"\u{1f195}",fitzpatrick_scale:!1,category:"symbols"},free:{keywords:["blue-square","words"],char:"\u{1f193}",fitzpatrick_scale:!1,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:"0\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:"1\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:"2\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:"3\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:"4\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:"5\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:"6\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:"7\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:"8\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:"9\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:"\u{1f51f}",fitzpatrick_scale:!1,category:"symbols"},asterisk:{keywords:["star","keycap"],char:"*\u20e3",fitzpatrick_scale:!1,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:"\u{1f522}",fitzpatrick_scale:!1,category:"symbols"},eject_button:{keywords:["blue-square"],char:"\u23cf\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:"\u25b6\ufe0f",fitzpatrick_scale:!1,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:"\u23f8",fitzpatrick_scale:!1,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:"\u23ed",fitzpatrick_scale:!1,category:"symbols"},stop_button:{keywords:["blue-square"],char:"\u23f9",fitzpatrick_scale:!1,category:"symbols"},record_button:{keywords:["blue-square"],char:"\u23fa",fitzpatrick_scale:!1,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:"\u23ef",fitzpatrick_scale:!1,category:"symbols"},previous_track_button:{keywords:["backward"],char:"\u23ee",fitzpatrick_scale:!1,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:"\u23e9",fitzpatrick_scale:!1,category:"symbols"},rewind:{keywords:["play","blue-square"],char:"\u23ea",fitzpatrick_scale:!1,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:"\u{1f500}",fitzpatrick_scale:!1,category:"symbols"},repeat:{keywords:["loop","record"],char:"\u{1f501}",fitzpatrick_scale:!1,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:"\u{1f502}",fitzpatrick_scale:!1,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:"\u25c0\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:"\u{1f53c}",fitzpatrick_scale:!1,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:"\u{1f53d}",fitzpatrick_scale:!1,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:"\u23eb",fitzpatrick_scale:!1,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:"\u23ec",fitzpatrick_scale:!1,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:"\u27a1\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:"\u2b05\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:"\u2b06\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:"\u2b07\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:"\u2197\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:"\u2198\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:"\u2199\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:"\u2196\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:"\u2195\ufe0f",fitzpatrick_scale:!1,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:"\u2194\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:"\u{1f504}",fitzpatrick_scale:!1,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:"\u21aa\ufe0f",fitzpatrick_scale:!1,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:"\u21a9\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:"\u2934\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:"\u2935\ufe0f",fitzpatrick_scale:!1,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:"#\ufe0f\u20e3",fitzpatrick_scale:!1,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:"\u2139\ufe0f",fitzpatrick_scale:!1,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:"\u{1f524}",fitzpatrick_scale:!1,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:"\u{1f521}",fitzpatrick_scale:!1,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:"\u{1f520}",fitzpatrick_scale:!1,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:"\u{1f523}",fitzpatrick_scale:!1,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:"\u{1f3b5}",fitzpatrick_scale:!1,category:"symbols"},notes:{keywords:["music","score"],char:"\u{1f3b6}",fitzpatrick_scale:!1,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:"\u3030\ufe0f",fitzpatrick_scale:!1,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:"\u27b0",fitzpatrick_scale:!1,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:"\u2714\ufe0f",fitzpatrick_scale:!1,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:"\u{1f503}",fitzpatrick_scale:!1,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:"\u2795",fitzpatrick_scale:!1,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:"\u2796",fitzpatrick_scale:!1,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:"\u2797",fitzpatrick_scale:!1,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:"\u2716\ufe0f",fitzpatrick_scale:!1,category:"symbols"},infinity:{keywords:["forever"],char:"\u267e",fitzpatrick_scale:!1,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:"\u{1f4b2}",fitzpatrick_scale:!1,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:"\u{1f4b1}",fitzpatrick_scale:!1,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:"\xa9\ufe0f",fitzpatrick_scale:!1,category:"symbols"},registered:{keywords:["alphabet","circle"],char:"\xae\ufe0f",fitzpatrick_scale:!1,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:"\u2122\ufe0f",fitzpatrick_scale:!1,category:"symbols"},end:{keywords:["words","arrow"],char:"\u{1f51a}",fitzpatrick_scale:!1,category:"symbols"},back:{keywords:["arrow","words","return"],char:"\u{1f519}",fitzpatrick_scale:!1,category:"symbols"},on:{keywords:["arrow","words"],char:"\u{1f51b}",fitzpatrick_scale:!1,category:"symbols"},top:{keywords:["words","blue-square"],char:"\u{1f51d}",fitzpatrick_scale:!1,category:"symbols"},soon:{keywords:["arrow","words"],char:"\u{1f51c}",fitzpatrick_scale:!1,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:"\u2611\ufe0f",fitzpatrick_scale:!1,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:"\u{1f518}",fitzpatrick_scale:!1,category:"symbols"},white_circle:{keywords:["shape","round"],char:"\u26aa",fitzpatrick_scale:!1,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:"\u26ab",fitzpatrick_scale:!1,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:"\u{1f534}",fitzpatrick_scale:!1,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:"\u{1f535}",fitzpatrick_scale:!1,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:"\u{1f538}",fitzpatrick_scale:!1,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:"\u{1f539}",fitzpatrick_scale:!1,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:"\u{1f536}",fitzpatrick_scale:!1,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:"\u{1f537}",fitzpatrick_scale:!1,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:"\u{1f53a}",fitzpatrick_scale:!1,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:"\u25aa\ufe0f",fitzpatrick_scale:!1,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:"\u25ab\ufe0f",fitzpatrick_scale:!1,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:"\u2b1b",fitzpatrick_scale:!1,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:"\u2b1c",fitzpatrick_scale:!1,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:"\u{1f53b}",fitzpatrick_scale:!1,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:"\u25fc\ufe0f",fitzpatrick_scale:!1,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:"\u25fb\ufe0f",fitzpatrick_scale:!1,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:"\u25fe",fitzpatrick_scale:!1,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:"\u25fd",fitzpatrick_scale:!1,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:"\u{1f532}",fitzpatrick_scale:!1,category:"symbols"},white_square_button:{keywords:["shape","input"],char:"\u{1f533}",fitzpatrick_scale:!1,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:"\u{1f508}",fitzpatrick_scale:!1,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:"\u{1f509}",fitzpatrick_scale:!1,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:"\u{1f50a}",fitzpatrick_scale:!1,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:"\u{1f507}",fitzpatrick_scale:!1,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:"\u{1f4e3}",fitzpatrick_scale:!1,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:"\u{1f4e2}",fitzpatrick_scale:!1,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:"\u{1f514}",fitzpatrick_scale:!1,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:"\u{1f515}",fitzpatrick_scale:!1,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:"\u{1f0cf}",fitzpatrick_scale:!1,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:"\u{1f004}",fitzpatrick_scale:!1,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:"\u2660\ufe0f",fitzpatrick_scale:!1,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:"\u2663\ufe0f",fitzpatrick_scale:!1,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:"\u2665\ufe0f",fitzpatrick_scale:!1,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:"\u2666\ufe0f",fitzpatrick_scale:!1,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:"\u{1f3b4}",fitzpatrick_scale:!1,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:"\u{1f4ad}",fitzpatrick_scale:!1,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:"\u{1f5ef}",fitzpatrick_scale:!1,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:"\u{1f4ac}",fitzpatrick_scale:!1,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:"\u{1f5e8}",fitzpatrick_scale:!1,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:"\u{1f550}",fitzpatrick_scale:!1,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:"\u{1f551}",fitzpatrick_scale:!1,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:"\u{1f552}",fitzpatrick_scale:!1,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:"\u{1f553}",fitzpatrick_scale:!1,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:"\u{1f554}",fitzpatrick_scale:!1,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:"\u{1f555}",fitzpatrick_scale:!1,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:"\u{1f556}",fitzpatrick_scale:!1,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:"\u{1f557}",fitzpatrick_scale:!1,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:"\u{1f558}",fitzpatrick_scale:!1,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:"\u{1f559}",fitzpatrick_scale:!1,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:"\u{1f55a}",fitzpatrick_scale:!1,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:"\u{1f55b}",fitzpatrick_scale:!1,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:"\u{1f55c}",fitzpatrick_scale:!1,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:"\u{1f55d}",fitzpatrick_scale:!1,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:"\u{1f55e}",fitzpatrick_scale:!1,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:"\u{1f55f}",fitzpatrick_scale:!1,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:"\u{1f560}",fitzpatrick_scale:!1,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:"\u{1f561}",fitzpatrick_scale:!1,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:"\u{1f562}",fitzpatrick_scale:!1,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:"\u{1f563}",fitzpatrick_scale:!1,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:"\u{1f564}",fitzpatrick_scale:!1,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:"\u{1f565}",fitzpatrick_scale:!1,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:"\u{1f566}",fitzpatrick_scale:!1,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:"\u{1f567}",fitzpatrick_scale:!1,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},aland_islands:{keywords:["\xc5land","islands","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1fd}",fitzpatrick_scale:!1,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:"\u{1f1e9}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f6}",fitzpatrick_scale:!1,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1e7}",fitzpatrick_scale:!1,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ef}",fitzpatrick_scale:!1,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f6}",fitzpatrick_scale:!1,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1fb}",fitzpatrick_scale:!1,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:"\u{1f1e8}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1fd}",fitzpatrick_scale:!1,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:"\u{1f1ed}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},curacao:{keywords:["cura\xe7ao","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:"\u{1f1e9}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:"\u{1f1e9}\u{1f1ef}",fitzpatrick_scale:!1,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:"\u{1f1e9}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:"\u{1f1e9}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1fb}",fitzpatrick_scale:!1,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f6}",fitzpatrick_scale:!1,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:"\u{1f1ea}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:"\u{1f1eb}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:"\u{1f1eb}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:"\u{1f1eb}\u{1f1ef}",fitzpatrick_scale:!1,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:"\u{1f1eb}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:"\u{1f1eb}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:"\u{1f1e9}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f5}",fitzpatrick_scale:!1,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:"\u{1f1ed}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:"\u{1f1ed}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:"\u{1f1ed}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:"\u{1f1ed}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f6}",fitzpatrick_scale:!1,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:"\u{1f1ee}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:"\u{1f1ef}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:"\u{1f1ef}\u{1f1f5}",fitzpatrick_scale:!1,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:"\u{1f1ef}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:"\u{1f1ef}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:"\u{1f1fd}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1fb}",fitzpatrick_scale:!1,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1e7}",fitzpatrick_scale:!1,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1fb}",fitzpatrick_scale:!1,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f6}",fitzpatrick_scale:!1,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:"\u{1f1fe}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1fd}",fitzpatrick_scale:!1,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:"\u{1f1eb}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1f5}",fitzpatrick_scale:!1,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:"\u{1f1f2}\u{1f1f5}",fitzpatrick_scale:!1,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:"\u{1f1f0}\u{1f1f5}",fitzpatrick_scale:!1,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:"\u{1f1f3}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:"\u{1f1f4}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:"\u{1f1f6}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},reunion:{keywords:["r\xe9union","flag","nation","country","banner"],char:"\u{1f1f7}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:"\u{1f1f7}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:"\u{1f1f7}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:"\u{1f1f7}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},st_barthelemy:{keywords:["saint","barth\xe9lemy","flag","nation","country","banner"],char:"\u{1f1e7}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:"\u{1f1f0}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:"\u{1f1f5}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:"\u{1f1fc}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:"\u{1f1f7}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1fd}",fitzpatrick_scale:!1,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1e7}",fitzpatrick_scale:!1,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:"\u{1f1ff}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:"\u{1f1ec}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:"\u{1f1f0}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:"\u{1f1f1}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1e9}",fitzpatrick_scale:!1,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:"\u{1f1e8}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:"\u{1f1f8}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1ef}",fitzpatrick_scale:!1,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f1}",fitzpatrick_scale:!1,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f0}",fitzpatrick_scale:!1,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f4}",fitzpatrick_scale:!1,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f9}",fitzpatrick_scale:!1,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f7}",fitzpatrick_scale:!1,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1e8}",fitzpatrick_scale:!1,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:"\u{1f1f9}\u{1f1fb}",fitzpatrick_scale:!1,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:"\u{1f1fa}\u{1f1ec}",fitzpatrick_scale:!1,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:"\u{1f1fa}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:"\u{1f1e6}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:"\u{1f1ec}\u{1f1e7}",fitzpatrick_scale:!1,category:"flags"},england:{keywords:["flag","english"],char:"\u{1f3f4}\u{e0067}\u{e0062}\u{e0065}\u{e006e}\u{e0067}\u{e007f}",fitzpatrick_scale:!1,category:"flags"},scotland:{keywords:["flag","scottish"],char:"\u{1f3f4}\u{e0067}\u{e0062}\u{e0073}\u{e0063}\u{e0074}\u{e007f}",fitzpatrick_scale:!1,category:"flags"},wales:{keywords:["flag","welsh"],char:"\u{1f3f4}\u{e0067}\u{e0062}\u{e0077}\u{e006c}\u{e0073}\u{e007f}",fitzpatrick_scale:!1,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:"\u{1f1fa}\u{1f1f8}",fitzpatrick_scale:!1,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1ee}",fitzpatrick_scale:!1,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:"\u{1f1fa}\u{1f1fe}",fitzpatrick_scale:!1,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:"\u{1f1fa}\u{1f1ff}",fitzpatrick_scale:!1,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1fa}",fitzpatrick_scale:!1,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1e6}",fitzpatrick_scale:!1,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:"\u{1f1fb}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:"\u{1f1fc}\u{1f1eb}",fitzpatrick_scale:!1,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:"\u{1f1ea}\u{1f1ed}",fitzpatrick_scale:!1,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:"\u{1f1fe}\u{1f1ea}",fitzpatrick_scale:!1,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:"\u{1f1ff}\u{1f1f2}",fitzpatrick_scale:!1,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:"\u{1f1ff}\u{1f1fc}",fitzpatrick_scale:!1,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:"\u{1f1fa}\u{1f1f3}",fitzpatrick_scale:!1,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:"\u{1f3f4}\u200d\u2620\ufe0f",fitzpatrick_scale:!1,category:"flags"}}); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/emoticons/plugin.min.js b/apps/web-antd/public/tinymce/plugins/emoticons/plugin.min.js new file mode 100644 index 0000000..23fa192 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/emoticons/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=t=>e=>t===e,o=e(null),n=e(void 0),s=()=>{},r=()=>!1;class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return null==t?a.none():a.some(t)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const i=(t,e)=>{const o=t.length,n=new Array(o);for(let s=0;s{let e=t;return{get:()=>e,set:t=>{e=t}}},c=Object.keys,u=Object.hasOwnProperty,g=(t,e)=>{const o=c(t);for(let n=0,s=o.length;nu.call(t,e),m=(h=(t,e)=>e,(...t)=>{if(0===t.length)throw new Error("Can't merge zero objects");const e={};for(let o=0;o{const t=(t=>{const e=l(a.none()),o=()=>e.get().each(t);return{clear:()=>{o(),e.set(a.none())},isSet:()=>e.get().isSome(),get:()=>e.get(),set:t=>{o(),e.set(a.some(t))}}})(s);return{...t,on:e=>t.get().each(e)}},y=(t,e,o=0,s)=>{const r=t.indexOf(e,o);return-1!==r&&(!!n(s)||r+e.length<=s)};var v=tinymce.util.Tools.resolve("tinymce.Resource");const f=t=>e=>e.options.get(t),b=f("emoticons_database"),w=f("emoticons_database_url"),j=f("emoticons_database_id"),C=f("emoticons_append"),_=f("emoticons_images_url"),A="All",k={symbols:"Symbols",people:"People",animals_and_nature:"Animals and Nature",food_and_drink:"Food and Drink",activity:"Activity",travel_and_places:"Travel and Places",objects:"Objects",flags:"Flags",user:"User Defined"},O=(t,e)=>d(t,e)?t[e]:e,x=t=>{const e=C(t);return o=t=>({keywords:[],category:"user",...t}),((t,e)=>{const o={};return g(t,((t,n)=>{const s=e(t,n);o[s.k]=s.v})),o})(e,((t,e)=>({k:e,v:o(t)})));var o},E=(t,e)=>y(t.title.toLowerCase(),e)||(t=>{for(let n=0,s=t.length;n{const n=[],s=e.toLowerCase(),a=o.fold((()=>r),(t=>e=>e>=t));for(let o=0;o{const n={pattern:"",results:L(e.listAll(),"",a.some(300))},s=l(A),r=(t=>{let e=null;const n=()=>{o(e)||(clearTimeout(e),e=null)};return{cancel:n,throttle:(...o)=>{n(),e=setTimeout((()=>{e=null,t.apply(null,o)}),200)}}})((t=>{(t=>{const o=t.getData(),n=s.get(),r=e.listCategory(n),i=L(r,o[S],n===A?a.some(300):a.none());t.setData({results:i})})(t)})),c={label:"Search",type:"input",name:S},u={type:"collection",name:"results"},g=()=>({title:"Emojis",size:"normal",body:{type:"tabpanel",tabs:i(e.listCategories(),(t=>({title:t,name:t,items:[c,u]})))},initialData:n,onTabChange:(t,e)=>{s.set(e.newTabName),r.throttle(t)},onChange:r.throttle,onAction:(e,o)=>{"results"===o.name&&(((t,e)=>{t.insertContent(e)})(t,o.value),e.close())},buttons:[{type:"cancel",text:"Close",primary:!0}]}),d=t.windowManager.open(g());d.focus(S),e.hasLoaded()||(d.block("Loading emojis..."),e.waitForLoad().then((()=>{d.redial(g()),r.throttle(d),d.focus(S),d.unblock()})).catch((t=>{d.redial({title:"Emojis",body:{type:"panel",items:[{type:"alertbanner",level:"error",icon:"warning",text:"Could not load emojis"}]},buttons:[{type:"cancel",text:"Close",primary:!0}],initialData:{pattern:"",results:[]}}),d.focus(S),d.unblock()})))},T=t=>e=>{const o=()=>{e.setEnabled(t.selection.isEditable())};return t.on("NodeChange",o),o(),()=>{t.off("NodeChange",o)}};t.add("emoticons",((t,e)=>{((t,e)=>{const o=t.options.register;o("emoticons_database",{processor:"string",default:"emojis"}),o("emoticons_database_url",{processor:"string",default:`${e}/js/${b(t)}${t.suffix}.js`}),o("emoticons_database_id",{processor:"string",default:"tinymce.plugins.emoticons"}),o("emoticons_append",{processor:"object",default:{}}),o("emoticons_images_url",{processor:"string",default:"https://cdnjs.cloudflare.com/ajax/libs/twemoji/15.1.0/72x72/"})})(t,e);const o=((t,e,o)=>{const n=p(),s=p(),r=_(t),i=t=>{return o="=4&&e.substr(0,4)===o?t.char.replace(/src="([^"]+)"/,((t,e)=>`src="${r}${e}"`)):t.char;var e,o};t.on("init",(()=>{v.load(o,e).then((e=>{const o=x(t);(t=>{const e={},o=[];g(t,((t,n)=>{const s={title:n,keywords:t.keywords,char:i(t),category:O(k,t.category)},r=void 0!==e[s.category]?e[s.category]:[];e[s.category]=r.concat([s]),o.push(s)})),n.set(e),s.set(o)})(m(e,o))}),(t=>{console.log(`Failed to load emojis: ${t}`),n.set({}),s.set([])}))}));const l=()=>s.get().getOr([]),u=()=>n.isSet()&&s.isSet();return{listCategories:()=>[A].concat(c(n.get().getOr({}))),hasLoaded:u,waitForLoad:()=>u()?Promise.resolve(!0):new Promise(((t,o)=>{let n=15;const s=setInterval((()=>{u()?(clearInterval(s),t(!0)):(n--,n<0&&(console.log("Could not load emojis from url: "+e),clearInterval(s),o(!1)))}),100)})),listAll:l,listCategory:t=>t===A?l():n.get().bind((e=>a.from(e[t]))).getOr([])}})(t,w(t),j(t));return((t,e)=>{t.addCommand("mceEmoticons",(()=>N(t,e)))})(t,o),(t=>{const e=()=>t.execCommand("mceEmoticons");t.ui.registry.addButton("emoticons",{tooltip:"Emojis",icon:"emoji",onAction:e,onSetup:T(t)}),t.ui.registry.addMenuItem("emoticons",{text:"Emojis...",icon:"emoji",onAction:e,onSetup:T(t)})})(t),((t,e)=>{t.ui.registry.addAutocompleter("emoticons",{trigger:":",columns:"auto",minChars:2,fetch:(t,o)=>e.waitForLoad().then((()=>{const n=e.listAll();return L(n,t,a.some(o))})),onAction:(e,o,n)=>{t.selection.setRng(o),t.insertContent(n),e.hide()}})})(t,o),(t=>{t.on("PreInit",(()=>{t.parser.addAttributeFilter("data-emoticon",(t=>{(t=>{for(let o=0,n=t.length;oo.waitForLoad().then((()=>o.listAll()))}}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/fullscreen/plugin.min.js b/apps/web-antd/public/tinymce/plugins/fullscreen/plugin.min.js new file mode 100644 index 0000000..37f4522 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/fullscreen/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";const e=e=>{let t=e;return{get:()=>t,set:e=>{t=e}}};var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const n=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=r=e,(o=String).prototype.isPrototypeOf(n)||(null===(s=r.constructor)||void 0===s?void 0:s.name)===o.name)?"string":t;var n,r,o,s})(t)===e,r=e=>t=>typeof t===e,o=e=>t=>e===t,s=n("string"),i=n("object"),l=n("array"),a=o(null),c=r("boolean"),u=o(void 0),d=e=>!(e=>null==e)(e),m=r("function"),h=r("number"),g=()=>{},p=e=>()=>e;function f(e,...t){return(...n)=>{const r=t.concat(n);return e.apply(null,r)}}const v=p(!1),w=p(!0);class y{constructor(e,t){this.tag=e,this.value=t}static some(e){return new y(!0,e)}static none(){return y.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?y.some(e(this.value)):y.none()}bind(e){return this.tag?e(this.value):y.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:y.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return d(e)?y.some(e):y.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}y.singletonNone=new y(!1);const b=Array.prototype.push,S=(e,t)=>{const n=e.length,r=new Array(n);for(let o=0;o{for(let n=0,r=e.length;n{const n=[];for(let r=0,o=e.length;r((e,t,n)=>{for(let r=0,o=e.length;r{const n=e(y.none()),r=()=>n.get().each(t);return{clear:()=>{r(),n.set(y.none())},isSet:()=>n.get().isSome(),get:()=>n.get(),set:e=>{r(),n.set(y.some(e))}}},k=()=>O((e=>e.unbind())),T=Object.keys,C="undefined"!=typeof window?window:Function("return this;")(),A=(e,t)=>((e,t)=>{let n=null!=t?t:C;for(let t=0;t{const t=A("ownerDocument.defaultView",e);return i(e)&&((e=>((e,t)=>{const n=((e,t)=>A(e,t))(e,t);if(null==n)throw new Error(e+" not available on this browser");return n})("HTMLElement",e))(t).prototype.isPrototypeOf(e)||/^HTML\w*Element$/.test(R(e).constructor.name))},M=e=>t=>(e=>e.dom.nodeType)(t)===e,P=M(1),D=M(3),N=M(11),H=(e,t)=>{const n=e.dom.getAttribute(t);return null===n?void 0:n},V=(e,t)=>{e.dom.removeAttribute(t)},W=(e,t,n=0,r)=>{const o=e.indexOf(t,n);return-1!==o&&(!!u(r)||o+t.length<=r)},q=e=>void 0!==e.style&&m(e.style.getPropertyValue),B=e=>{if(null==e)throw new Error("Node cannot be null or undefined");return{dom:e}},I=B,j=(e,t)=>{const n=e.dom;if(1!==n.nodeType)return!1;{const e=n;if(void 0!==e.matches)return e.matches(t);if(void 0!==e.msMatchesSelector)return e.msMatchesSelector(t);if(void 0!==e.webkitMatchesSelector)return e.webkitMatchesSelector(t);if(void 0!==e.mozMatchesSelector)return e.mozMatchesSelector(t);throw new Error("Browser lacks native selectors")}},_=e=>I(e.dom.ownerDocument),z=e=>S(e.dom.childNodes,I),K=e=>{const t=(e=>I(e.dom.getRootNode()))(e);return N(n=t)&&d(n.dom.host)?y.some(t):y.none();var n},$=e=>I(e.dom.host),U=e=>{const t=D(e)?e.dom.parentNode:e.dom;if(null==t||null===t.ownerDocument)return!1;const n=t.ownerDocument;return K(I(t)).fold((()=>n.body.contains(t)),(r=U,o=$,e=>r(o(e))));var r,o},X=(e,t,n)=>{if(!s(n))throw console.error("Invalid call to CSS.set. Property ",t,":: Value ",n,":: Element ",e),new Error("CSS value must be a string: "+n);q(e)&&e.style.setProperty(t,n)},Y=(e,t,n)=>{const r=e.dom;X(r,t,n)},G=(e,t)=>{const n=e.dom;((e,t)=>{const n=T(e);for(let r=0,o=n.length;r{X(n,t,e)}))},J=(e,t)=>{const n=e.dom,r=window.getComputedStyle(n).getPropertyValue(t);return""!==r||U(e)?r:Q(n,t)},Q=(e,t)=>q(e)?e.style.getPropertyValue(t):"",Z=e=>{const t=I((e=>{if(d(e.target)){const t=I(e.target);if(P(t)&&d(t.dom.shadowRoot)&&e.composed&&e.composedPath){const t=e.composedPath();if(t)return(e=>0e.stopPropagation(),r=()=>e.preventDefault(),o=(s=r,i=n,(...e)=>s(i.apply(null,e)));var s,i;return((e,t,n,r,o,s,i)=>({target:e,x:t,y:n,stop:r,prevent:o,kill:s,raw:i}))(t,e.clientX,e.clientY,n,r,o,e)},ee=(e,t,n,r)=>{e.dom.removeEventListener(t,n,r)},te=w,ne=(e,t,n)=>((e,t,n,r)=>((e,t,n,r,o)=>{const s=((e,t)=>n=>{e(n)&&t(Z(n))})(n,r);return e.dom.addEventListener(t,s,o),{unbind:f(ee,e,t,s,o)}})(e,t,n,r,!1))(e,t,te,n),re=()=>oe(0,0),oe=(e,t)=>({major:e,minor:t}),se={nu:oe,detect:(e,t)=>{const n=String(t).toLowerCase();return 0===e.length?re():((e,t)=>{const n=((e,t)=>{for(let n=0;nNumber(t.replace(n,"$"+e));return oe(r(1),r(2))})(e,n)},unknown:re},ie=(e,t)=>{const n=String(t).toLowerCase();return F(e,(e=>e.search(n)))},le=/.*?version\/\ ?([0-9]+)\.([0-9]+).*/,ae=e=>t=>W(t,e),ce=[{name:"Edge",versionRegexes:[/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],search:e=>W(e,"edge/")&&W(e,"chrome")&&W(e,"safari")&&W(e,"applewebkit")},{name:"Chromium",brand:"Chromium",versionRegexes:[/.*?chrome\/([0-9]+)\.([0-9]+).*/,le],search:e=>W(e,"chrome")&&!W(e,"chromeframe")},{name:"IE",versionRegexes:[/.*?msie\ ?([0-9]+)\.([0-9]+).*/,/.*?rv:([0-9]+)\.([0-9]+).*/],search:e=>W(e,"msie")||W(e,"trident")},{name:"Opera",versionRegexes:[le,/.*?opera\/([0-9]+)\.([0-9]+).*/],search:ae("opera")},{name:"Firefox",versionRegexes:[/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],search:ae("firefox")},{name:"Safari",versionRegexes:[le,/.*?cpu os ([0-9]+)_([0-9]+).*/],search:e=>(W(e,"safari")||W(e,"mobile/"))&&W(e,"applewebkit")}],ue=[{name:"Windows",search:ae("win"),versionRegexes:[/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]},{name:"iOS",search:e=>W(e,"iphone")||W(e,"ipad"),versionRegexes:[/.*?version\/\ ?([0-9]+)\.([0-9]+).*/,/.*cpu os ([0-9]+)_([0-9]+).*/,/.*cpu iphone os ([0-9]+)_([0-9]+).*/]},{name:"Android",search:ae("android"),versionRegexes:[/.*?android\ ?([0-9]+)\.([0-9]+).*/]},{name:"macOS",search:ae("mac os x"),versionRegexes:[/.*?mac\ os\ x\ ?([0-9]+)_([0-9]+).*/]},{name:"Linux",search:ae("linux"),versionRegexes:[]},{name:"Solaris",search:ae("sunos"),versionRegexes:[]},{name:"FreeBSD",search:ae("freebsd"),versionRegexes:[]},{name:"ChromeOS",search:ae("cros"),versionRegexes:[/.*?chrome\/([0-9]+)\.([0-9]+).*/]}],de={browsers:p(ce),oses:p(ue)},me="Edge",he="Chromium",ge="Opera",pe="Firefox",fe="Safari",ve=e=>{const t=e.current,n=e.version,r=e=>()=>t===e;return{current:t,version:n,isEdge:r(me),isChromium:r(he),isIE:r("IE"),isOpera:r(ge),isFirefox:r(pe),isSafari:r(fe)}},we=()=>ve({current:void 0,version:se.unknown()}),ye=ve,be=(p(me),p(he),p("IE"),p(ge),p(pe),p(fe),"Windows"),Se="Android",xe="Linux",Ee="macOS",Fe="Solaris",Oe="FreeBSD",ke="ChromeOS",Te=e=>{const t=e.current,n=e.version,r=e=>()=>t===e;return{current:t,version:n,isWindows:r(be),isiOS:r("iOS"),isAndroid:r(Se),isMacOS:r(Ee),isLinux:r(xe),isSolaris:r(Fe),isFreeBSD:r(Oe),isChromeOS:r(ke)}},Ce=()=>Te({current:void 0,version:se.unknown()}),Ae=Te,Re=(p(be),p("iOS"),p(Se),p(xe),p(Ee),p(Fe),p(Oe),p(ke),(e,t,n)=>{const r=de.browsers(),o=de.oses(),s=t.bind((e=>((e,t)=>((e,t)=>{for(let n=0;n{const n=t.brand.toLowerCase();return F(e,(e=>{var t;return n===(null===(t=e.brand)||void 0===t?void 0:t.toLowerCase())})).map((e=>({current:e.name,version:se.nu(parseInt(t.version,10),0)})))})))(r,e))).orThunk((()=>((e,t)=>ie(e,t).map((e=>{const n=se.detect(e.versionRegexes,t);return{current:e.name,version:n}})))(r,e))).fold(we,ye),i=((e,t)=>ie(e,t).map((e=>{const n=se.detect(e.versionRegexes,t);return{current:e.name,version:n}})))(o,e).fold(Ce,Ae),l=((e,t,n,r)=>{const o=e.isiOS()&&!0===/ipad/i.test(n),s=e.isiOS()&&!o,i=e.isiOS()||e.isAndroid(),l=i||r("(pointer:coarse)"),a=o||!s&&i&&r("(min-device-width:768px)"),c=s||i&&!a,u=t.isSafari()&&e.isiOS()&&!1===/safari/i.test(n),d=!c&&!a&&!u;return{isiPad:p(o),isiPhone:p(s),isTablet:p(a),isPhone:p(c),isTouch:p(l),isAndroid:e.isAndroid,isiOS:e.isiOS,isWebView:p(u),isDesktop:p(d)}})(i,s,e,n);return{browser:s,os:i,deviceType:l}}),Le=e=>window.matchMedia(e).matches;let Me=(e=>{let t,n=!1;return(...r)=>(n||(n=!0,t=e.apply(null,r)),t)})((()=>Re(window.navigator.userAgent,y.from(window.navigator.userAgentData),Le)));const Pe=(e,t)=>({left:e,top:t,translate:(n,r)=>Pe(e+n,t+r)}),De=Pe,Ne=e=>{const t=void 0===e?window:e;return Me().browser.isFirefox()?y.none():y.from(t.visualViewport)},He=(e,t,n,r)=>({x:e,y:t,width:n,height:r,right:e+n,bottom:t+r}),Ve=e=>{const t=void 0===e?window:e,n=t.document,r=(e=>{const t=void 0!==e?e.dom:document,n=t.body.scrollLeft||t.documentElement.scrollLeft,r=t.body.scrollTop||t.documentElement.scrollTop;return De(n,r)})(I(n));return Ne(t).fold((()=>{const e=t.document.documentElement,n=e.clientWidth,o=e.clientHeight;return He(r.left,r.top,n,o)}),(e=>He(Math.max(e.pageLeft,r.left),Math.max(e.pageTop,r.top),e.width,e.height)))},We=(e,t,n)=>Ne(n).map((n=>{const r=e=>t(Z(e));return n.addEventListener(e,r),{unbind:()=>n.removeEventListener(e,r)}})).getOrThunk((()=>({unbind:g})));var qe=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),Be=tinymce.util.Tools.resolve("tinymce.Env");const Ie=(e,t)=>{e.dispatch("FullscreenStateChanged",{state:t}),e.dispatch("ResizeEditor")},je=e=>e.options.get("fullscreen_native");const _e=e=>{return e.dom===(void 0!==(t=_(e).dom).fullscreenElement?t.fullscreenElement:void 0!==t.msFullscreenElement?t.msFullscreenElement:void 0!==t.webkitFullscreenElement?t.webkitFullscreenElement:null);var t},ze=(e,t,n)=>((e,t,n)=>E(((e,t)=>{const n=m(t)?t:v;let r=e.dom;const o=[];for(;null!==r.parentNode&&void 0!==r.parentNode;){const e=r.parentNode,t=I(e);if(o.push(t),!0===n(t))break;r=e}return o})(e,n),t))(e,(e=>j(e,t)),n),Ke=(e,t)=>(e=>{return E((e=>y.from(e.dom.parentNode).map(I))(n=e).map(z).map((e=>E(e,(e=>{return t=e,!(n.dom===t.dom);var t})))).getOr([]),(e=>j(e,t)));var n})(e),$e="data-ephox-mobile-fullscreen-style",Ue="position:absolute!important;",Xe="top:0!important;left:0!important;margin:0!important;padding:0!important;width:100%!important;height:100%!important;overflow:visible!important;",Ye=Be.os.isAndroid(),Ge=(e,t,n)=>{const r=t=>n=>{const r=H(n,"style"),o=void 0===r?"no-styles":r.trim();o!==t&&(((e,t,n)=>{((e,t,n)=>{if(!(s(n)||c(n)||h(n)))throw console.error("Invalid call to Attribute.set. Key ",t,":: Value ",n,":: Element ",e),new Error("Attribute value was not simple");e.setAttribute(t,n+"")})(e.dom,t,n)})(n,$e,o),G(n,e.parseStyle(t)))},o=ze(t,"*"),i=(e=>{const t=[];for(let n=0,r=e.length;nKe(e,"*:not(.tox-silver-sink)")))),a=(e=>{const t=J(e,"background-color");return void 0!==t&&""!==t?"background-color:"+t+"!important":"background-color:rgb(255,255,255)!important;"})(n);x(i,r("display:none!important;")),x(o,r(Ue+Xe+a)),r((!0===Ye?"":Ue)+Xe+a)(t)},Je=qe.DOM,Qe=Ne().fold((()=>({bind:g,unbind:g})),(e=>{const t=(()=>{const e=O(g);return{...e,on:t=>e.get().each(t)}})(),n=k(),r=k(),o=(e=>{let t=null;return{cancel:()=>{a(t)||(clearTimeout(t),t=null)},throttle:(...n)=>{a(t)&&(t=setTimeout((()=>{t=null,e.apply(null,n)}),50))}}})((()=>{document.body.scrollTop=0,document.documentElement.scrollTop=0,window.requestAnimationFrame((()=>{t.on((t=>G(t,{top:e.offsetTop+"px",left:e.offsetLeft+"px",height:e.height+"px",width:e.width+"px"})))}))}));return{bind:e=>{t.set(e),o.throttle(),n.set(We("resize",o.throttle)),r.set(We("scroll",o.throttle))},unbind:()=>{t.on((()=>{n.clear(),r.clear()})),t.clear()}}})),Ze=(e,t)=>{const n=document.body,r=document.documentElement,o=e.getContainer(),s=I(o),i=(l=s,y.from(l.dom.nextSibling).map(I)).filter((e=>(e=>P(e)&&L(e.dom))(e)&&(e=>(e=>void 0!==e.dom.classList)(e)&&e.dom.classList.contains("tox-silver-sink"))(e)));var l;const a=(e=>{const t=I(e.getElement());return K(t).map($).getOrThunk((()=>(e=>{const t=e.dom.body;if(null==t)throw new Error("Body is not available yet");return I(t)})(_(t))))})(e),c=t.get(),u=I(e.getBody()),d=Be.deviceType.isTouch(),m=o.style,h=e.iframeElement,g=null==h?void 0:h.style,p=e=>{e(n,"tox-fullscreen"),e(r,"tox-fullscreen"),e(o,"tox-fullscreen"),K(s).map((e=>$(e).dom)).each((t=>{e(t,"tox-fullscreen"),e(t,"tox-shadowhost")}))},f=()=>{d&&(e=>{const t=(e=>{const t=document;return 1!==(n=t).nodeType&&9!==n.nodeType&&11!==n.nodeType||0===n.childElementCount?[]:S(t.querySelectorAll(e),I);var n})("["+$e+"]");x(t,(t=>{const n=H(t,$e);n&&"no-styles"!==n?G(t,e.parseStyle(n)):V(t,"style"),V(t,$e)}))})(e.dom),p(Je.removeClass),Qe.unbind(),y.from(t.get()).each((e=>e.fullscreenChangeHandler.unbind()))};if(c)c.fullscreenChangeHandler.unbind(),je(e)&&_e(a)&&(e=>{const t=e.dom;t.exitFullscreen?t.exitFullscreen():t.msExitFullscreen?t.msExitFullscreen():t.webkitCancelFullScreen&&t.webkitCancelFullScreen()})(_(a)),g.width=c.iframeWidth,g.height=c.iframeHeight,m.width=c.containerWidth,m.height=c.containerHeight,m.top=c.containerTop,m.left=c.containerLeft,w=i,b=c.sinkCssPosition,E=(e,t)=>{Y(e,"position",t)},w.isSome()&&b.isSome()?y.some(E(w.getOrDie(),b.getOrDie())):y.none(),f(),v=c.scrollPos,window.scrollTo(v.x,v.y),t.set(null),Ie(e,!1),e.off("remove",f);else{const n=ne(_(a),void 0!==document.fullscreenElement?"fullscreenchange":void 0!==document.msFullscreenElement?"MSFullscreenChange":void 0!==document.webkitFullscreenElement?"webkitfullscreenchange":"fullscreenchange",(n=>{je(e)&&(_e(a)||null===t.get()||Ze(e,t))})),r={scrollPos:Ve(window),containerWidth:m.width,containerHeight:m.height,containerTop:m.top,containerLeft:m.left,iframeWidth:g.width,iframeHeight:g.height,fullscreenChangeHandler:n,sinkCssPosition:i.map((e=>J(e,"position")))};d&&Ge(e.dom,s,u),g.width=g.height="100%",m.width=m.height="",p(Je.addClass),i.each((e=>{Y(e,"position","fixed")})),Qe.bind(s),e.on("remove",f),t.set(r),je(e)&&(e=>{const t=e.dom;t.requestFullscreen?t.requestFullscreen():t.msRequestFullscreen?t.msRequestFullscreen():t.webkitRequestFullScreen&&t.webkitRequestFullScreen()})(a),Ie(e,!0)}var v,w,b,E};var et=tinymce.util.Tools.resolve("tinymce.util.VK");const tt=(e,t)=>n=>{n.setActive(null!==t.get());const r=e=>n.setActive(e.state);return e.on("FullscreenStateChanged",r),()=>e.off("FullscreenStateChanged",r)};t.add("fullscreen",(t=>{const n=e(null);return t.inline||((e=>{(0,e.options.register)("fullscreen_native",{processor:"boolean",default:!1})})(t),((e,t)=>{e.addCommand("mceFullScreen",(()=>{Ze(e,t)}))})(t,n),((e,t)=>{const n=()=>e.execCommand("mceFullScreen");e.ui.registry.addToggleMenuItem("fullscreen",{text:"Fullscreen",icon:"fullscreen",shortcut:"Meta+Shift+F",onAction:n,onSetup:tt(e,t),context:"any"}),e.ui.registry.addToggleButton("fullscreen",{tooltip:"Fullscreen",icon:"fullscreen",onAction:n,onSetup:tt(e,t),shortcut:"Meta+Shift+F",context:"any"})})(t,n),((e,t)=>{e.on("init",(()=>{e.on("keydown",(e=>{e.keyCode!==et.TAB||e.metaKey||e.ctrlKey||!t.get()||e.preventDefault()}))}))})(t,n),t.addShortcut("Meta+Shift+F","","mceFullScreen")),(e=>({isFullscreen:()=>null!==e.get()}))(n)}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ar.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ar.js new file mode 100644 index 0000000..e2cf02f --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ar.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ar', +'

بدء التنقل بواسطة لوحة المفاتيح

\n' + + '\n' + + '
\n' + + '
التركيز على شريط القوائم
\n' + + '
نظاما التشغيل Windows أو Linux: Alt + F9
\n' + + '
نظام التشغيل macOS: ⌥F9
\n' + + '
التركيز على شريط الأدوات
\n' + + '
نظاما التشغيل Windows أو Linux: Alt + F10
\n' + + '
نظام التشغيل macOS: ⌥F10
\n' + + '
التركيز على التذييل
\n' + + '
نظاما التشغيل Windows أو Linux: Alt + F11
\n' + + '
نظام التشغيل macOS: ⌥F11
\n' + + '
تركيز الإشعارات
\n' + + '
نظاما التشغيل Windows أو Linux: Alt + F12
\n' + + '
نظام التشغيل macOS: ⌥F12
\n' + + '
التركيز على شريط أدوات السياق
\n' + + '
أنظمة التشغيل Windows أو Linux أو macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

سيبدأ التنقل عند عنصر واجهة المستخدم الأول، والذي سيتم تمييزه أو تسطيره في حالة العنصر الأول في\n' + + ' مسار عنصر التذييل.

\n' + + '\n' + + '

التنقل بين أقسام واجهة المستخدم

\n' + + '\n' + + '

للانتقال من أحد أقسام واجهة المستخدم إلى القسم التالي، اضغط على Tab.

\n' + + '\n' + + '

للانتقال من أحد أقسام واجهة المستخدم إلى القسم السابق، اضغط على Shift+Tab.

\n' + + '\n' + + '

ترتيب علامات Tab لأقسام واجهة المستخدم هذه هو:

\n' + + '\n' + + '
    \n' + + '
  1. شريط القوائم
  2. \n' + + '
  3. كل مجموعة شريط الأدوات
  4. \n' + + '
  5. الشريط الجانبي
  6. \n' + + '
  7. مسار العنصر في التذييل
  8. \n' + + '
  9. زر تبديل عدد الكلمات في التذييل
  10. \n' + + '
  11. رابط إدراج العلامة التجارية في التذييل
  12. \n' + + '
  13. مؤشر تغيير حجم المحرر في التذييل
  14. \n' + + '
\n' + + '\n' + + '

إذا لم يكن قسم واجهة المستخدم موجودًا، فسيتم تخطيه.

\n' + + '\n' + + '

إذا كان التذييل يحتوي على التركيز على ‏‫التنقل بواسطة لوحة المفاتيح، ولا يوجد شريط جانبي مرئي، فإن الضغط على Shift+Tab\n' + + ' ينقل التركيز إلى مجموعة شريط الأدوات الأولى، وليس الأخيرة.

\n' + + '\n' + + '

التنقل بين أقسام واجهة المستخدم

\n' + + '\n' + + '

للانتقال من أحد عناصر واجهة المستخدم إلى العنصر التالي، اضغط على مفتاح السهم المناسب.

\n' + + '\n' + + '

مفتاحا السهمين اليسار‎ واليمين‎

\n' + + '\n' + + '
    \n' + + '
  • التنقل بين القوائم في شريط القوائم.
  • \n' + + '
  • فتح قائمة فرعية في القائمة.
  • \n' + + '
  • التنقل بين الأزرار في مجموعة شريط الأدوات.
  • \n' + + '
  • التنقل بين العناصر في مسار عنصر التذييل.
  • \n' + + '
\n' + + '\n' + + '

مفتاحا السهمين لأسفل‎ ولأعلى‎

\n' + + '\n' + + '
    \n' + + '
  • التنقل بين عناصر القائمة في القائمة.
  • \n' + + '
  • التنقل بين العناصر في قائمة شريط الأدوات المنبثقة.
  • \n' + + '
\n' + + '\n' + + '

دورة مفاتيح الأسهم‎ داخل قسم واجهة المستخدم التي تم التركيز عليها.

\n' + + '\n' + + '

لإغلاق قائمة مفتوحة أو قائمة فرعية مفتوحة أو قائمة منبثقة مفتوحة، اضغط على مفتاح Esc.

\n' + + '\n' + + '

إذا كان التركيز الحالي على "الجزء العلوي" من قسم معين لواجهة المستخدم، فإن الضغط على مفتاح Esc يؤدي أيضًا إلى الخروج\n' + + ' من التنقل بواسطة لوحة المفاتيح بالكامل.

\n' + + '\n' + + '

تنفيذ عنصر قائمة أو زر شريط أدوات

\n' + + '\n' + + '

عندما يتم تمييز عنصر القائمة المطلوب أو زر شريط الأدوات، اضغط على زر Return، أو Enter،\n' + + ' أو مفتاح المسافة لتنفيذ العنصر.

\n' + + '\n' + + '

التنقل في مربعات الحوار غير المبوبة

\n' + + '\n' + + '

في مربعات الحوار غير المبوبة، يتم التركيز على المكون التفاعلي الأول عند فتح مربع الحوار.

\n' + + '\n' + + '

التنقل بين مكونات الحوار التفاعلي بالضغط على زر Tab أو Shift+Tab.

\n' + + '\n' + + '

التنقل في مربعات الحوار المبوبة

\n' + + '\n' + + '

في مربعات الحوار المبوبة، يتم التركيز على الزر الأول في قائمة علامات التبويب عند فتح مربع الحوار.

\n' + + '\n' + + '

التنقل بين المكونات التفاعلية لعلامة التبويب لمربع الحوار هذه بالضغط على زر Tab أو\n' + + ' Shift+Tab.

\n' + + '\n' + + '

التبديل إلى علامة تبويب أخرى لمربع الحوار من خلال التركيز على قائمة علامة التبويب ثم الضغط على زر السهم المناسب\n' + + ' مفتاح للتنقل بين علامات التبويب المتاحة.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/bg_BG.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/bg_BG.js new file mode 100644 index 0000000..09eacf3 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/bg_BG.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.bg_BG', +'

Начало на навигацията с клавиатурата

\n' + + '\n' + + '
\n' + + '
Фокусиране върху лентата с менюта
\n' + + '
Windows или Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Фокусиране върху лентата с инструменти
\n' + + '
Windows или Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Фокусиране върху долния колонтитул
\n' + + '
Windows или Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Фокусиране на известието
\n' + + '
Windows или Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Фокусиране върху контекстуалната лента с инструменти
\n' + + '
Windows, Linux или macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Навигацията ще започне с първия елемент на ПИ, който ще бъде маркиран или подчертан в случая на първия елемент в\n' + + ' пътя до елемента в долния колонтитул.

\n' + + '\n' + + '

Навигиране между раздели на ПИ

\n' + + '\n' + + '

За да преминете от един раздел на ПИ към следващия, натиснете Tab.

\n' + + '\n' + + '

За да преминете от един раздел на ПИ към предишния, натиснете Shift+Tab.

\n' + + '\n' + + '

Редът за обхождане с табулация на тези раздели на ПИ е:

\n' + + '\n' + + '
    \n' + + '
  1. Лентата с менюта
  2. \n' + + '
  3. Всяка група на лентата с инструменти
  4. \n' + + '
  5. Страничната лента
  6. \n' + + '
  7. Пътят до елемента в долния колонтитул
  8. \n' + + '
  9. Бутонът за превключване на броя на думите в долния колонтитул
  10. \n' + + '
  11. Връзката за търговска марка в долния колонтитул
  12. \n' + + '
  13. Манипулаторът за преоразмеряване на редактора в долния колонтитул
  14. \n' + + '
\n' + + '\n' + + '

Ако някой раздел на ПИ липсва, той се пропуска.

\n' + + '\n' + + '

Ако долният колонтитул има фокус за навигация с клавиатурата и няма странична лента, натискането на Shift+Tab\n' + + ' премества фокуса към първата група на лентата с инструменти, а не към последната.

\n' + + '\n' + + '

Навигиране в разделите на ПИ

\n' + + '\n' + + '

За да преминете от един елемент на ПИ към следващия, натиснете съответния клавиш със стрелка.

\n' + + '\n' + + '

С клавишите със стрелка наляво и надясно

\n' + + '\n' + + '
    \n' + + '
  • се придвижвате между менютата в лентата с менюто;
  • \n' + + '
  • отваряте подменю в меню;
  • \n' + + '
  • се придвижвате между бутоните в група на лентата с инструменти;
  • \n' + + '
  • се придвижвате между елементи в пътя до елемент в долния колонтитул.
  • \n' + + '
\n' + + '\n' + + '

С клавишите със стрелка надолу и нагоре

\n' + + '\n' + + '
    \n' + + '
  • се придвижвате между елементите от менюто в дадено меню;
  • \n' + + '
  • се придвижвате между елементите в изскачащо меню на лентата с инструменти.
  • \n' + + '
\n' + + '\n' + + '

Клавишите със стрелки се придвижват в рамките на фокусирания раздел на ПИ.

\n' + + '\n' + + '

За да затворите отворено меню, подменю или изскачащо меню, натиснете клавиша Esc.

\n' + + '\n' + + '

Ако текущият фокус е върху „горната част“ на конкретен раздел на ПИ, натискането на клавиша Esc също излиза\n' + + ' напълно от навигацията с клавиатурата.

\n' + + '\n' + + '

Изпълнение на елемент от менюто или бутон от лентата с инструменти

\n' + + '\n' + + '

Когато желаният елемент от менюто или бутон от лентата с инструменти е маркиран, натиснете Return, Enter\n' + + ' или клавиша за интервал, за да изпълните елемента.

\n' + + '\n' + + '

Навигиране в диалогови прозорци без раздели

\n' + + '\n' + + '

В диалоговите прозорци без раздели първият интерактивен компонент се фокусира, когато се отвори диалоговият прозорец.

\n' + + '\n' + + '

Навигирайте между интерактивните компоненти на диалоговия прозорец, като натиснете Tab или Shift+Tab.

\n' + + '\n' + + '

Навигиране в диалогови прозорци с раздели

\n' + + '\n' + + '

В диалоговите прозорци с раздели първият бутон в менюто с раздели се фокусира, когато се отвори диалоговият прозорец.

\n' + + '\n' + + '

Навигирайте между интерактивните компоненти на този диалогов раздел, като натиснете Tab или\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Превключете към друг диалогов раздел, като фокусирате върху менюто с раздели и след това натиснете съответния клавиш със стрелка,\n' + + ' за да преминете през наличните раздели.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ca.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ca.js new file mode 100644 index 0000000..996e29c --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ca.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ca', +'

Inici de la navegació amb el teclat

\n' + + '\n' + + '
\n' + + '
Enfocar la barra de menús
\n' + + '
Windows o Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + "
Enfocar la barra d'eines
\n" + + '
Windows o Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Enfocar el peu de pàgina
\n' + + '
Windows o Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Enfocar la notificació
\n' + + '
Windows o Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + "
Enfocar una barra d'eines contextual
\n" + + '
Windows, Linux o macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + "

La navegació començarà en el primer element de la interfície d'usuari, que es ressaltarà o subratllarà per al primer element a\n" + + " la ruta de l'element de peu de pàgina.

\n" + + '\n' + + "

Navegació entre seccions de la interfície d'usuari

\n" + + '\n' + + "

Per desplaçar-vos des d'una secció de la interfície d'usuari a la següent, premeu la tecla Tab.

\n" + + '\n' + + "

Per desplaçar-vos des d'una secció de la interfície d'usuari a l'anterior, premeu les tecles Maj+Tab.

\n" + + '\n' + + "

L'ordre en prémer la tecla Tab d'aquestes secciones de la interfície d'usuari és:

\n" + + '\n' + + '
    \n' + + '
  1. Barra de menús
  2. \n' + + "
  3. Cada grup de la barra d'eines
  4. \n" + + '
  5. Barra lateral
  6. \n' + + "
  7. Ruta de l'element del peu de pàgina
  8. \n" + + '
  9. Botó de commutació de recompte de paraules al peu de pàgina
  10. \n' + + '
  11. Enllaç de marca del peu de pàgina
  12. \n' + + "
  13. Control de canvi de mida de l'editor al peu de pàgina
  14. \n" + + '
\n' + + '\n' + + "

Si no hi ha una secció de la interfície d'usuari, s'ometrà.

\n" + + '\n' + + '

Si el peu de pàgina té el focus de navegació del teclat i no hi ha cap barra lateral visible, en prémer Maj+Tab\n' + + " el focus es mou al primer grup de la barra d'eines, no l'últim.

\n" + + '\n' + + "

Navegació dins de les seccions de la interfície d'usuari

\n" + + '\n' + + "

Per desplaçar-vos des d'un element de la interfície d'usuari al següent, premeu la tecla de Fletxa adequada.

\n" + + '\n' + + '

Les tecles de fletxa Esquerra i Dreta

\n' + + '\n' + + '
    \n' + + '
  • us permeten desplaçar-vos entre menús de la barra de menús.
  • \n' + + '
  • obren un submenú en un menú.
  • \n' + + "
  • us permeten desplaçar-vos entre botons d'un grup de la barra d'eines.
  • \n" + + "
  • us permeten desplaçar-vos entre elements de la ruta d'elements del peu de pàgina.
  • \n" + + '
\n' + + '\n' + + '

Les tecles de fletxa Avall i Amunt

\n' + + '\n' + + '
    \n' + + "
  • us permeten desplaçar-vos entre elements de menú d'un menú.
  • \n" + + "
  • us permeten desplaçar-vos entre elements d'un menú emergent de la barra d'eines.
  • \n" + + '
\n' + + '\n' + + "

Les tecles de Fletxa us permeten desplaçar-vos dins de la secció de la interfície d'usuari que té el focus.

\n" + + '\n' + + '

Per tancar un menú, un submenú o un menú emergent oberts, premeu la tecla Esc.

\n' + + '\n' + + "

Si el focus actual es troba a la ‘part superior’ d'una secció específica de la interfície d'usuari, en prémer la tecla Esc també es tanca\n" + + ' completament la navegació amb el teclat.

\n' + + '\n' + + "

Execució d'un element de menú o d'un botó de la barra d'eines

\n" + + '\n' + + "

Quan l'element del menú o el botó de la barra d'eines que desitgeu estigui ressaltat, premeu Retorn, Intro\n" + + " o la barra d'espai per executar l'element.

\n" + + '\n' + + '

Navegació per quadres de diàleg sense pestanyes

\n' + + '\n' + + "

En els quadres de diàleg sense pestanyes, el primer component interactiu pren el focus quan s'obre el quadre diàleg.

\n" + + '\n' + + '

Premeu la tecla Tab o les tecles Maj+Tab per desplaçar-vos entre components interactius del quadre de diàleg.

\n' + + '\n' + + '

Navegació per quadres de diàleg amb pestanyes

\n' + + '\n' + + "

En els quadres de diàleg amb pestanyes, el primer botó del menú de la pestanya pren el focus quan s'obre el quadre diàleg.

\n" + + '\n' + + "

Per desplaçar-vos entre components interactius d'aquest quadre de diàleg, premeu la tecla Tab o\n" + + ' les tecles Maj+Tab.

\n' + + '\n' + + "

Canvieu a la pestanya d'un altre quadre de diàleg, tot enfocant el menú de la pestanya, i després premeu la tecla Fletxa adequada\n" + + ' per canviar entre les pestanyes disponibles.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/cs.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/cs.js new file mode 100644 index 0000000..4a5a902 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/cs.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.cs', +'

Začínáme navigovat pomocí klávesnice

\n' + + '\n' + + '
\n' + + '
Přejít na řádek nabídek
\n' + + '
Windows nebo Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Přejít na panel nástrojů
\n' + + '
Windows nebo Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Přejít na zápatí
\n' + + '
Windows nebo Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Přejít na oznámení
\n' + + '
Windows nebo Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Přejít na kontextový panel nástrojů
\n' + + '
Windows, Linux nebo macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Navigace začne u první položky uživatelského rozhraní, která bude zvýrazněna nebo v případě první položky\n' + + ' cesty k prvku zápatí podtržena.

\n' + + '\n' + + '

Navigace mezi oddíly uživatelského rozhraní

\n' + + '\n' + + '

Stisknutím klávesy Tab se posunete z jednoho oddílu uživatelského rozhraní na další.

\n' + + '\n' + + '

Stisknutím kláves Shift+Tab se posunete z jednoho oddílu uživatelského rozhraní na předchozí.

\n' + + '\n' + + '

Pořadí přepínání mezi oddíly uživatelského rozhraní pomocí klávesy Tab:

\n' + + '\n' + + '
    \n' + + '
  1. Řádek nabídek
  2. \n' + + '
  3. Každá skupina panelu nástrojů
  4. \n' + + '
  5. Boční panel
  6. \n' + + '
  7. Cesta k prvku v zápatí.
  8. \n' + + '
  9. Tlačítko přepínače počtu slov v zápatí
  10. \n' + + '
  11. Odkaz na informace o značce v zápatí
  12. \n' + + '
  13. Úchyt pro změnu velikosti editoru v zápatí
  14. \n' + + '
\n' + + '\n' + + '

Pokud nějaký oddíl uživatelského rozhraní není přítomen, je přeskočen.

\n' + + '\n' + + '

Pokud je zápatí vybrané pro navigaci pomocí klávesnice a není zobrazen žádný boční panel, stisknutím kláves Shift+Tab\n' + + ' přejdete na první skupinu panelu nástrojů, nikoli na poslední.

\n' + + '\n' + + '

Navigace v rámci oddílů uživatelského rozhraní

\n' + + '\n' + + '

Chcete-li se přesunout z jednoho prvku uživatelského rozhraní na další, stiskněte příslušnou klávesu s šipkou.

\n' + + '\n' + + '

Klávesy s šipkou vlevovpravo

\n' + + '\n' + + '
    \n' + + '
  • umožňují přesun mezi nabídkami na řádku nabídek;
  • \n' + + '
  • otevírají podnabídku nabídky;
  • \n' + + '
  • umožňují přesun mezi tlačítky ve skupině panelu nástrojů;
  • \n' + + '
  • umožňují přesun mezi položkami cesty prvku v zápatí.
  • \n' + + '
\n' + + '\n' + + '

Klávesy se šipkou dolůnahoru

\n' + + '\n' + + '
    \n' + + '
  • umožňují přesun mezi položkami nabídky;
  • \n' + + '
  • umožňují přesun mezi položkami místní nabídky panelu nástrojů.
  • \n' + + '
\n' + + '\n' + + '

Šipky provádí přepínání v rámci vybraného oddílu uživatelského rozhraní.

\n' + + '\n' + + '

Chcete-li zavřít otevřenou nabídku, podnabídku nebo místní nabídku, stiskněte klávesu Esc.

\n' + + '\n' + + '

Pokud je aktuálně vybrána horní část oddílu uživatelského rozhraní, stisknutím klávesy Esc zcela ukončíte také\n' + + ' navigaci pomocí klávesnice.

\n' + + '\n' + + '

Provedení příkazu položky nabídky nebo tlačítka panelu nástrojů

\n' + + '\n' + + '

Pokud je zvýrazněna požadovaná položka nabídky nebo tlačítko panelu nástrojů, stisknutím klávesy Return, Enter\n' + + ' nebo mezerníku provedete příslušný příkaz.

\n' + + '\n' + + '

Navigace v dialogových oknech bez záložek

\n' + + '\n' + + '

Při otevření dialogových oken bez záložek přejdete na první interaktivní komponentu.

\n' + + '\n' + + '

Přecházet mezi interaktivními komponentami dialogového okna můžete stisknutím klávesy Tab nebo kombinace Shift+Tab.

\n' + + '\n' + + '

Navigace v dialogových oknech se záložkami

\n' + + '\n' + + '

Při otevření dialogových oken se záložkami přejdete na první tlačítko v nabídce záložek.

\n' + + '\n' + + '

Přecházet mezi interaktivními komponentami této záložky dialogového okna můžete stisknutím klávesy Tab nebo\n' + + ' kombinace Shift+Tab.

\n' + + '\n' + + '

Chcete-li přepnout na další záložku dialogového okna, přejděte na nabídku záložek a poté můžete stisknutím požadované šipky\n' + + ' přepínat mezi dostupnými záložkami.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/da.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/da.js new file mode 100644 index 0000000..4d1e1d4 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/da.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.da', +'

Start tastaturnavigation

\n' + + '\n' + + '
\n' + + '
Fokuser på menulinjen
\n' + + '
Windows eller Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Fokuser på værktøjslinjen
\n' + + '
Windows eller Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Fokuser på sidefoden
\n' + + '
Windows eller Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Fokuser på meddelelsen
\n' + + '
Windows eller Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Fokuser på kontekstuel værktøjslinje
\n' + + '
Windows, Linux eller macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Navigationen starter ved det første UI-element, som fremhæves eller understreges hvad angår det første element i\n' + + ' sidefodens sti til elementet.

\n' + + '\n' + + '

Naviger mellem UI-sektioner

\n' + + '\n' + + '

Gå fra én UI-sektion til den næste ved at trykke på Tab.

\n' + + '\n' + + '

Gå fra én UI-sektion til den forrige ved at trykke på Shift+Tab.

\n' + + '\n' + + '

Tab-rækkefølgen af disse UI-sektioner er:

\n' + + '\n' + + '
    \n' + + '
  1. Menulinje
  2. \n' + + '
  3. Hver værktøjsgruppe
  4. \n' + + '
  5. Sidepanel
  6. \n' + + '
  7. Sti til elementet i sidefoden
  8. \n' + + '
  9. Til/fra-knap for ordoptælling i sidefoden
  10. \n' + + '
  11. Brandinglink i sidefoden
  12. \n' + + '
  13. Tilpasningshåndtag for editor i sidefoden
  14. \n' + + '
\n' + + '\n' + + '

Hvis en UI-sektion ikke er til stede, springes den over.

\n' + + '\n' + + '

Hvis sidefoden har fokus til tastaturnavigation, og der ikke er noget synligt sidepanel, kan der trykkes på Shift+Tab\n' + + ' for at flytte fokus til den første værktøjsgruppe, ikke den sidste.

\n' + + '\n' + + '

Naviger inden for UI-sektioner

\n' + + '\n' + + '

Gå fra ét UI-element til det næste ved at trykke på den relevante piletast.

\n' + + '\n' + + '

Venstre og højre piletast

\n' + + '\n' + + '
    \n' + + '
  • flytter mellem menuerne i menulinjen.
  • \n' + + '
  • åbner en undermenu i en menu.
  • \n' + + '
  • flytter mellem knapperne i en værktøjsgruppe.
  • \n' + + '
  • flytter mellem elementer i sidefodens sti til elementet.
  • \n' + + '
\n' + + '\n' + + '

Pil ned og op

\n' + + '\n' + + '
    \n' + + '
  • flytter mellem menupunkterne i en menu.
  • \n' + + '
  • flytter mellem punkterne i en genvejsmenu i værktøjslinjen.
  • \n' + + '
\n' + + '\n' + + '

Piletasterne kører rundt inden for UI-sektionen, der fokuseres på.

\n' + + '\n' + + '

For at lukke en åben menu, en åben undermenu eller en åben genvejsmenu trykkes der på Esc-tasten.

\n' + + '\n' + + "

Hvis det aktuelle fokus er i 'toppen' af en bestemt UI-sektion, vil tryk på Esc-tasten også afslutte\n" + + ' tastaturnavigationen helt.

\n' + + '\n' + + '

Udfør et menupunkt eller en værktøjslinjeknap

\n' + + '\n' + + '

Når det ønskede menupunkt eller den ønskede værktøjslinjeknap er fremhævet, trykkes der på Retur, Enter\n' + + ' eller mellemrumstasten for at udføre elementet.

\n' + + '\n' + + '

Naviger i ikke-faneopdelte dialogbokse

\n' + + '\n' + + '

I ikke-faneopdelte dialogbokse får den første interaktive komponent fokus, når dialogboksen åbnes.

\n' + + '\n' + + '

Naviger mellem interaktive dialogbokskomponenter ved at trykke på Tab eller Shift+Tab.

\n' + + '\n' + + '

Naviger i faneopdelte dialogbokse

\n' + + '\n' + + '

I faneopdelte dialogbokse får den første knap i fanemenuen fokus, når dialogboksen åbnes.

\n' + + '\n' + + '

Naviger mellem interaktive komponenter i denne dialogboksfane ved at trykke på Tab eller\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Skift til en anden dialogboksfane ved at fokusere på fanemenuen og derefter trykke på den relevante piletast\n' + + ' for at køre igennem de tilgængelige faner.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/de.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/de.js new file mode 100644 index 0000000..b8711ed --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/de.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.de', +'

Grundlagen der Tastaturnavigation

\n' + + '\n' + + '
\n' + + '
Fokus auf Menüleiste
\n' + + '
Windows oder Linux: ALT+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Fokus auf Symbolleiste
\n' + + '
Windows oder Linux: ALT+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Fokus auf Fußzeile
\n' + + '
Windows oder Linux: ALT+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Benachrichtigung fokussieren
\n' + + '
Windows oder Linux: ALT+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Fokus auf kontextbezogene Symbolleiste
\n' + + '
Windows, Linux oder macOS: STRG+F9
\n' + + '
\n' + + '\n' + + '

Die Navigation beginnt beim ersten Benutzeroberflächenelement, welches hervorgehoben ist. Falls sich das erste Element im Pfad der Fußzeile befindet,\n' + + ' ist es unterstrichen.

\n' + + '\n' + + '

Zwischen Abschnitten der Benutzeroberfläche navigieren

\n' + + '\n' + + '

Um von einem Abschnitt der Benutzeroberfläche zum nächsten zu wechseln, drücken Sie TAB.

\n' + + '\n' + + '

Um von einem Abschnitt der Benutzeroberfläche zum vorherigen zu wechseln, drücken Sie UMSCHALT+TAB.

\n' + + '\n' + + '

Die Abschnitte der Benutzeroberfläche haben folgende TAB-Reihenfolge:

\n' + + '\n' + + '
    \n' + + '
  1. Menüleiste
  2. \n' + + '
  3. Einzelne Gruppen der Symbolleiste
  4. \n' + + '
  5. Randleiste
  6. \n' + + '
  7. Elementpfad in der Fußzeile
  8. \n' + + '
  9. Umschaltfläche „Wörter zählen“ in der Fußzeile
  10. \n' + + '
  11. Branding-Link in der Fußzeile
  12. \n' + + '
  13. Editor-Ziehpunkt zur Größenänderung in der Fußzeile
  14. \n' + + '
\n' + + '\n' + + '

Falls ein Abschnitt der Benutzeroberflächen nicht vorhanden ist, wird er übersprungen.

\n' + + '\n' + + '

Wenn in der Fußzeile die Tastaturnavigation fokussiert ist und keine Randleiste angezeigt wird, wechselt der Fokus durch Drücken von UMSCHALT+TAB\n' + + ' zur ersten Gruppe der Symbolleiste, nicht zur letzten.

\n' + + '\n' + + '

Innerhalb von Abschnitten der Benutzeroberfläche navigieren

\n' + + '\n' + + '

Um von einem Element der Benutzeroberfläche zum nächsten zu wechseln, drücken Sie die entsprechende Pfeiltaste.

\n' + + '\n' + + '

Die Pfeiltasten Links und Rechts

\n' + + '\n' + + '
    \n' + + '
  • wechseln zwischen Menüs in der Menüleiste.
  • \n' + + '
  • öffnen das Untermenü eines Menüs.
  • \n' + + '
  • wechseln zwischen Schaltflächen in einer Gruppe der Symbolleiste.
  • \n' + + '
  • wechseln zwischen Elementen im Elementpfad der Fußzeile.
  • \n' + + '
\n' + + '\n' + + '

Die Pfeiltasten Abwärts und Aufwärts

\n' + + '\n' + + '
    \n' + + '
  • wechseln zwischen Menüelementen in einem Menü.
  • \n' + + '
  • wechseln zwischen Elementen in einem Popupmenü der Symbolleiste.
  • \n' + + '
\n' + + '\n' + + '

Die Pfeiltasten rotieren innerhalb des fokussierten Abschnitts der Benutzeroberfläche.

\n' + + '\n' + + '

Um ein geöffnetes Menü, ein geöffnetes Untermenü oder ein geöffnetes Popupmenü zu schließen, drücken Sie die ESC-Taste.

\n' + + '\n' + + '

Wenn sich der aktuelle Fokus ganz oben in einem bestimmten Abschnitt der Benutzeroberfläche befindet, wird durch Drücken der ESC-Taste auch\n' + + ' die Tastaturnavigation beendet.

\n' + + '\n' + + '

Ein Menüelement oder eine Symbolleistenschaltfläche ausführen

\n' + + '\n' + + '

Wenn das gewünschte Menüelement oder die gewünschte Symbolleistenschaltfläche hervorgehoben ist, drücken Sie Zurück, Eingabe\n' + + ' oder die Leertaste, um das Element auszuführen.

\n' + + '\n' + + '

In Dialogfeldern ohne Registerkarten navigieren

\n' + + '\n' + + '

In Dialogfeldern ohne Registerkarten ist beim Öffnen eines Dialogfelds die erste interaktive Komponente fokussiert.

\n' + + '\n' + + '

Navigieren Sie zwischen den interaktiven Komponenten eines Dialogfelds, indem Sie TAB oder UMSCHALT+TAB drücken.

\n' + + '\n' + + '

In Dialogfeldern mit Registerkarten navigieren

\n' + + '\n' + + '

In Dialogfeldern mit Registerkarten ist beim Öffnen eines Dialogfelds die erste Schaltfläche eines Registerkartenmenüs fokussiert.

\n' + + '\n' + + '

Navigieren Sie zwischen den interaktiven Komponenten auf dieser Registerkarte des Dialogfelds, indem Sie TAB oder\n' + + ' UMSCHALT+TAB drücken.

\n' + + '\n' + + '

Wechseln Sie zu einer anderen Registerkarte des Dialogfelds, indem Sie den Fokus auf das Registerkartenmenü legen und dann die entsprechende Pfeiltaste\n' + + ' drücken, um durch die verfügbaren Registerkarten zu rotieren.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/el.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/el.js new file mode 100644 index 0000000..98afabe --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/el.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.el', +'

Έναρξη πλοήγησης μέσω πληκτρολογίου

\n' + + '\n' + + '
\n' + + '
Εστίαση στη γραμμή μενού
\n' + + '
Windows ή Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Εστίαση στη γραμμή εργαλείων
\n' + + '
Windows ή Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Εστίαση στο υποσέλιδο
\n' + + '
Windows ή Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Εστίαση στην ειδοποίηση
\n' + + '
Windows ή Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Εστίαση σε γραμμή εργαλείων βάσει περιεχομένου
\n' + + '
Windows, Linux ή macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Η πλοήγηση θα ξεκινήσει από το πρώτο στοιχείο περιβάλλοντος χρήστη, που θα επισημαίνεται ή θα είναι υπογραμμισμένο,\n' + + ' όπως στην περίπτωση της διαδρομής του στοιχείου Υποσέλιδου.

\n' + + '\n' + + '

Πλοήγηση μεταξύ ενοτήτων του περιβάλλοντος χρήστη

\n' + + '\n' + + '

Για να μετακινηθείτε από μια ενότητα περιβάλλοντος χρήστη στην επόμενη, πιέστε το πλήκτρο Tab.

\n' + + '\n' + + '

Για να μετακινηθείτε από μια ενότητα περιβάλλοντος χρήστη στην προηγούμενη, πιέστε τα πλήκτρα Shift+Tab.

\n' + + '\n' + + '

Η σειρά Tab αυτών των ενοτήτων περιβάλλοντος χρήστη είναι η εξής:

\n' + + '\n' + + '
    \n' + + '
  1. Γραμμή μενού
  2. \n' + + '
  3. Κάθε ομάδα γραμμής εργαλείων
  4. \n' + + '
  5. Πλαϊνή γραμμή
  6. \n' + + '
  7. Διαδρομή στοιχείου στο υποσέλιδο
  8. \n' + + '
  9. Κουμπί εναλλαγής μέτρησης λέξεων στο υποσέλιδο
  10. \n' + + '
  11. Σύνδεσμος επωνυμίας στο υποσέλιδο
  12. \n' + + '
  13. Λαβή αλλαγής μεγέθους προγράμματος επεξεργασίας στο υποσέλιδο
  14. \n' + + '
\n' + + '\n' + + '

Εάν δεν εμφανίζεται ενότητα περιβάλλοντος χρήστη, παραλείπεται.

\n' + + '\n' + + '

Εάν η εστίαση πλοήγησης βρίσκεται στο πληκτρολόγιο και δεν υπάρχει εμφανής πλαϊνή γραμμή, εάν πιέσετε Shift+Tab\n' + + ' η εστίαση μετακινείται στην πρώτη ομάδα γραμμής εργαλείων, όχι στην τελευταία.

\n' + + '\n' + + '

Πλοήγηση εντός των ενοτήτων του περιβάλλοντος χρήστη

\n' + + '\n' + + '

Για να μετακινηθείτε από ένα στοιχείο περιβάλλοντος χρήστη στο επόμενο, πιέστε το αντίστοιχο πλήκτρο βέλους.

\n' + + '\n' + + '

Με τα πλήκτρα αριστερού και δεξιού βέλους

\n' + + '\n' + + '
    \n' + + '
  • γίνεται μετακίνηση μεταξύ των μενού στη γραμμή μενού.
  • \n' + + '
  • ανοίγει ένα υπομενού σε ένα μενού.
  • \n' + + '
  • γίνεται μετακίνηση μεταξύ κουμπιών σε μια ομάδα γραμμής εργαλείων.
  • \n' + + '
  • γίνεται μετακίνηση μεταξύ στοιχείων στη διαδρομή στοιχείου στο υποσέλιδο.
  • \n' + + '
\n' + + '\n' + + '

Με τα πλήκτρα επάνω και κάτω βέλους

\n' + + '\n' + + '
    \n' + + '
  • γίνεται μετακίνηση μεταξύ των στοιχείων μενού σε ένα μενού.
  • \n' + + '
  • γίνεται μετακίνηση μεταξύ των στοιχείων μενού σε ένα αναδυόμενο μενού γραμμής εργαλείων.
  • \n' + + '
\n' + + '\n' + + '

Με τα πλήκτρα βέλους γίνεται κυκλική μετακίνηση εντός της εστιασμένης ενότητας περιβάλλοντος χρήστη.

\n' + + '\n' + + '

Για να κλείσετε ένα ανοιχτό μενού, ένα ανοιχτό υπομενού ή ένα ανοιχτό αναδυόμενο μενού, πιέστε το πλήκτρο Esc.

\n' + + '\n' + + '

Εάν η τρέχουσα εστίαση βρίσκεται στην κορυφή μιας ενότητας περιβάλλοντος χρήστη, πιέζοντας το πλήκτρο Esc,\n' + + ' γίνεται επίσης πλήρης έξοδος από την πλοήγηση μέσω πληκτρολογίου.

\n' + + '\n' + + '

Εκτέλεση ενός στοιχείου μενού ή κουμπιού γραμμής εργαλείων

\n' + + '\n' + + '

Όταν το επιθυμητό στοιχείο μενού ή κουμπί γραμμής εργαλείων είναι επισημασμένο, πιέστε τα πλήκτρα Return, Enter,\n' + + ' ή το πλήκτρο διαστήματος για να εκτελέσετε το στοιχείο.

\n' + + '\n' + + '

Πλοήγηση σε παράθυρα διαλόγου χωρίς καρτέλες

\n' + + '\n' + + '

Σε παράθυρα διαλόγου χωρίς καρτέλες, το πρώτο αλληλεπιδραστικό στοιχείο λαμβάνει την εστίαση όταν ανοίγει το παράθυρο διαλόγου.

\n' + + '\n' + + '

Μπορείτε να πλοηγηθείτε μεταξύ των αλληλεπιδραστικών στοιχείων παραθύρων διαλόγων πιέζοντας τα πλήκτρα Tab ή Shift+Tab.

\n' + + '\n' + + '

Πλοήγηση σε παράθυρα διαλόγου με καρτέλες

\n' + + '\n' + + '

Σε παράθυρα διαλόγου με καρτέλες, το πρώτο κουμπί στο μενού καρτέλας λαμβάνει την εστίαση όταν ανοίγει το παράθυρο διαλόγου.

\n' + + '\n' + + '

Μπορείτε να πλοηγηθείτε μεταξύ των αλληλεπιδραστικών στοιχείων αυτής της καρτέλα διαλόγου πιέζοντας τα πλήκτρα Tab ή\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Μπορείτε να κάνετε εναλλαγή σε άλλη καρτέλα του παραθύρου διαλόγου, μεταφέροντας την εστίαση στο μενού καρτέλας και πιέζοντας το κατάλληλο πλήκτρο βέλους\n' + + ' για να μετακινηθείτε κυκλικά στις διαθέσιμες καρτέλες.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/en.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/en.js new file mode 100644 index 0000000..5dd753e --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/en.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.en', +'

Begin keyboard navigation

\n' + + '\n' + + '
\n' + + '
Focus the Menu bar
\n' + + '
Windows or Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Focus the Toolbar
\n' + + '
Windows or Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Focus the footer
\n' + + '
Windows or Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Focus the notification
\n' + + '
Windows or Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Focus a contextual toolbar
\n' + + '
Windows, Linux or macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Navigation will start at the first UI item, which will be highlighted, or underlined in the case of the first item in\n' + + ' the Footer element path.

\n' + + '\n' + + '

Navigate between UI sections

\n' + + '\n' + + '

To move from one UI section to the next, press Tab.

\n' + + '\n' + + '

To move from one UI section to the previous, press Shift+Tab.

\n' + + '\n' + + '

The Tab order of these UI sections is:

\n' + + '\n' + + '
    \n' + + '
  1. Menu bar
  2. \n' + + '
  3. Each toolbar group
  4. \n' + + '
  5. Sidebar
  6. \n' + + '
  7. Element path in the footer
  8. \n' + + '
  9. Word count toggle button in the footer
  10. \n' + + '
  11. Branding link in the footer
  12. \n' + + '
  13. Editor resize handle in the footer
  14. \n' + + '
\n' + + '\n' + + '

If a UI section is not present, it is skipped.

\n' + + '\n' + + '

If the footer has keyboard navigation focus, and there is no visible sidebar, pressing Shift+Tab\n' + + ' moves focus to the first toolbar group, not the last.

\n' + + '\n' + + '

Navigate within UI sections

\n' + + '\n' + + '

To move from one UI element to the next, press the appropriate Arrow key.

\n' + + '\n' + + '

The Left and Right arrow keys

\n' + + '\n' + + '
    \n' + + '
  • move between menus in the menu bar.
  • \n' + + '
  • open a sub-menu in a menu.
  • \n' + + '
  • move between buttons in a toolbar group.
  • \n' + + '
  • move between items in the footer’s element path.
  • \n' + + '
\n' + + '\n' + + '

The Down and Up arrow keys

\n' + + '\n' + + '
    \n' + + '
  • move between menu items in a menu.
  • \n' + + '
  • move between items in a toolbar pop-up menu.
  • \n' + + '
\n' + + '\n' + + '

Arrow keys cycle within the focused UI section.

\n' + + '\n' + + '

To close an open menu, an open sub-menu, or an open pop-up menu, press the Esc key.

\n' + + '\n' + + '

If the current focus is at the ‘top’ of a particular UI section, pressing the Esc key also exits\n' + + ' keyboard navigation entirely.

\n' + + '\n' + + '

Execute a menu item or toolbar button

\n' + + '\n' + + '

When the desired menu item or toolbar button is highlighted, press Return, Enter,\n' + + ' or the Space bar to execute the item.

\n' + + '\n' + + '

Navigate non-tabbed dialogs

\n' + + '\n' + + '

In non-tabbed dialogs, the first interactive component takes focus when the dialog opens.

\n' + + '\n' + + '

Navigate between interactive dialog components by pressing Tab or Shift+Tab.

\n' + + '\n' + + '

Navigate tabbed dialogs

\n' + + '\n' + + '

In tabbed dialogs, the first button in the tab menu takes focus when the dialog opens.

\n' + + '\n' + + '

Navigate between interactive components of this dialog tab by pressing Tab or\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Switch to another dialog tab by giving the tab menu focus and then pressing the appropriate Arrow\n' + + ' key to cycle through the available tabs.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/es.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/es.js new file mode 100644 index 0000000..e426c2e --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/es.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.es', +'

Iniciar la navegación con el teclado

\n' + + '\n' + + '
\n' + + '
Enfocar la barra de menús
\n' + + '
Windows o Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Enfocar la barra de herramientas
\n' + + '
Windows o Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Enfocar el pie de página
\n' + + '
Windows o Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Enfocar la notificación
\n' + + '
Windows o Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Enfocar una barra de herramientas contextual
\n' + + '
Windows, Linux o macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

La navegación comenzará por el primer elemento de la interfaz de usuario (IU), de tal manera que se resaltará, o bien se subrayará si se trata del primer elemento de\n' + + ' la ruta de elemento del pie de página.

\n' + + '\n' + + '

Navegar entre las secciones de la IU

\n' + + '\n' + + '

Para pasar de una sección de la IU a la siguiente, pulse la tecla Tab.

\n' + + '\n' + + '

Para pasar de una sección de la IU a la anterior, pulse Mayús+Tab.

\n' + + '\n' + + '

El orden de tabulación de estas secciones de la IU es:

\n' + + '\n' + + '
    \n' + + '
  1. Barra de menús
  2. \n' + + '
  3. Cada grupo de barra de herramientas
  4. \n' + + '
  5. Barra lateral
  6. \n' + + '
  7. Ruta del elemento en el pie de página
  8. \n' + + '
  9. Botón de alternancia de recuento de palabras en el pie de página
  10. \n' + + '
  11. Enlace de personalización de marca en el pie de página
  12. \n' + + '
  13. Controlador de cambio de tamaño en el pie de página
  14. \n' + + '
\n' + + '\n' + + '

Si una sección de la IU no está presente, esta se omite.

\n' + + '\n' + + '

Si el pie de página tiene un enfoque de navegación con el teclado y no hay ninguna barra lateral visible, al pulsar Mayús+Tab,\n' + + ' el enfoque se moverá al primer grupo de barra de herramientas, en lugar de al último.

\n' + + '\n' + + '

Navegar dentro de las secciones de la IU

\n' + + '\n' + + '

Para pasar de un elemento de la IU al siguiente, pulse la tecla de flecha correspondiente.

\n' + + '\n' + + '

Las teclas de flecha izquierda y derecha permiten

\n' + + '\n' + + '
    \n' + + '
  • desplazarse entre los menús de la barra de menús.
  • \n' + + '
  • abrir el submenú de un menú.
  • \n' + + '
  • desplazarse entre los botones de un grupo de barra de herramientas.
  • \n' + + '
  • desplazarse entre los elementos de la ruta de elemento del pie de página.
  • \n' + + '
\n' + + '\n' + + '

Las teclas de flecha abajo y arriba permiten

\n' + + '\n' + + '
    \n' + + '
  • desplazarse entre los elementos de menú de un menú.
  • \n' + + '
  • desplazarse entre los elementos de un menú emergente de una barra de herramientas.
  • \n' + + '
\n' + + '\n' + + '

Las teclas de flecha van cambiando dentro de la sección de la IU enfocada.

\n' + + '\n' + + '

Para cerrar un menú, un submenú o un menú emergente que estén abiertos, pulse la tecla Esc.

\n' + + '\n' + + '

Si el enfoque actual se encuentra en la parte superior de una sección de la IU determinada, al pulsar la tecla Esc saldrá\n' + + ' de la navegación con el teclado por completo.

\n' + + '\n' + + '

Ejecutar un elemento de menú o un botón de barra de herramientas

\n' + + '\n' + + '

Si el elemento de menú o el botón de barra de herramientas deseado está resaltado, pulse la tecla Retorno o Entrar,\n' + + ' o la barra espaciadora para ejecutar el elemento.

\n' + + '\n' + + '

Navegar por cuadros de diálogo sin pestañas

\n' + + '\n' + + '

En los cuadros de diálogo sin pestañas, el primer componente interactivo se enfoca al abrirse el cuadro de diálogo.

\n' + + '\n' + + '

Para navegar entre los componentes interactivos del cuadro de diálogo, pulse las teclas Tab o Mayús+Tab.

\n' + + '\n' + + '

Navegar por cuadros de diálogo con pestañas

\n' + + '\n' + + '

En los cuadros de diálogo con pestañas, el primer botón del menú de pestaña se enfoca al abrirse el cuadro de diálogo.

\n' + + '\n' + + '

Para navegar entre componentes interactivos de esta pestaña del cuadro de diálogo, pulse las teclas Tab o\n' + + ' Mayús+Tab.

\n' + + '\n' + + '

Si desea cambiar a otra pestaña del cuadro de diálogo, enfoque el menú de pestañas y, a continuación, pulse la tecla de flecha\n' + + ' correspondiente para moverse por las pestañas disponibles.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/eu.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/eu.js new file mode 100644 index 0000000..c18b940 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/eu.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.eu', +'

Hasi teklatuaren nabigazioa

\n' + + '\n' + + '
\n' + + '
Fokuratu menu-barra
\n' + + '
Windows edo Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Fokuratu tresna-barra
\n' + + '
Windows edo Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Fokuratu orri-oina
\n' + + '
Windows edo Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Fokuratu jakinarazpena
\n' + + '
Windows edo Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Fokuratu testuinguruaren tresna-barra
\n' + + '
Windows, Linux edo macOS: Ktrl+F9
\n' + + '
\n' + + '\n' + + '

Nabigazioa EIko lehen elementuan hasiko da: elementu hori nabarmendu egingo da, edo azpimarratu lehen elementua bada\n' + + ' orri-oineko elementuaren bidea.

\n' + + '\n' + + '

Nabigatu EIko atalen artean

\n' + + '\n' + + '

EIko atal batetik hurrengora mugitzeko, sakatu Tabuladorea.

\n' + + '\n' + + '

EIko atal batetik aurrekora mugitzeko, sakatu Maius+Tabuladorea.

\n' + + '\n' + + '

EIko atal hauen Tabuladorea da:

\n' + + '\n' + + '
    \n' + + '
  1. Menu-barra
  2. \n' + + '
  3. Tresna-barraren talde bakoitza
  4. \n' + + '
  5. Alboko barra
  6. \n' + + '
  7. Orri-oineko elementuaren bidea
  8. \n' + + '
  9. Orri-oneko urrats-kontaketa txandakatzeko botoia
  10. \n' + + '
  11. Orri-oineko marken esteka
  12. \n' + + '
  13. Orri-oineko editorearen tamaina aldatzeko heldulekua
  14. \n' + + '
\n' + + '\n' + + '

EIko atal bat ez badago, saltatu egin da.

\n' + + '\n' + + '

Orri-oinak teklatuaren nabigazioa fokuratuta badago, eta alboko barra ikusgai ez badago, Maius+Tabuladorea sakatuz gero,\n' + + ' fokua tresna-barrako lehen taldera eramaten da, ez azkenera.

\n' + + '\n' + + '

Nabigatu EIko atalen barruan

\n' + + '\n' + + '

EIko elementu batetik hurrengora mugitzeko, sakatu dagokion Gezia tekla.

\n' + + '\n' + + '

Ezkerrera eta Eskuinera gezi-teklak

\n' + + '\n' + + '
    \n' + + '
  • menu-barrako menuen artean mugitzen da.
  • \n' + + '
  • ireki azpimenu bat menuan.
  • \n' + + '
  • mugitu botoi batetik bestera tresna-barren talde batean.
  • \n' + + '
  • mugitu orri-oineko elementuaren bideko elementu batetik bestera.
  • \n' + + '
\n' + + '\n' + + '

Gora eta Behera gezi-teklak

\n' + + '\n' + + '
    \n' + + '
  • mugitu menu bateko menu-elementuen artean.
  • \n' + + '
  • mugitu tresna-barrako menu gainerakor bateko menu-elementuen artean.
  • \n' + + '
\n' + + '\n' + + '

Gezia teklen zikloa nabarmendutako EI atalen barruan.

\n' + + '\n' + + '

Irekitako menu bat ixteko, ireki azpimenua, edo ireki menu gainerakorra, sakatu Ihes tekla.

\n' + + '\n' + + '

Une horretan fokuratzea EIko atal jakin baten "goialdean" badago, Ihes tekla sakatuz gero\n' + + ' teklatuaren nabigaziotik irtengo zara.

\n' + + '\n' + + '

Exekutatu menuko elementu bat edo tresna-barrako botoi bat

\n' + + '\n' + + '

Nahi den menuaren elementua edo tresna-barraren botoia nabarmenduta dagoenean, sakatu Itzuli, Sartu\n' + + ' edo Zuriune-barra elementua exekutatzeko.

\n' + + '\n' + + '

Nabigatu fitxarik gabeko elkarrizketak

\n' + + '\n' + + '

Fitxarik gabeko elkarrizketetan, lehen osagai interaktiboa fokuratzen da elkarrizketa irekitzen denean.

\n' + + '\n' + + '

Nabigatu elkarrizketa interaktiboko osagai batetik bestera Tabuladorea edo Maius+Tabuladorea sakatuta.

\n' + + '\n' + + '

Nabigatu fitxadun elkarrizketak

\n' + + '\n' + + '

Fitxadun elkarrizketetan, fitxa-menuko lehen botoia fokuratzen da elkarrizketa irekitzen denean.

\n' + + '\n' + + '

Nabigatu elkarrizketa-fitxa honen interaktiboko osagai batetik bestera Tabuladorea edo\n' + + ' Maius+Tabuladorea sakatuta.

\n' + + '\n' + + '

Aldatu beste elkarrizketa-fitxa batera fitxa-menua fokuratu eta dagokion Gezia\n' + + ' tekla sakatzeko, erabilgarri dauden fitxa batetik bestera txandakatzeko.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fa.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fa.js new file mode 100644 index 0000000..2a55012 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fa.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.fa', +'

شروع پیمایش صفحه‌کلید

\n' + + '\n' + + '
\n' + + '
تمرکز بر نوار منو
\n' + + '
Windows یا Linux:‎‏: Alt+F9
\n' + + '
‎‏macOS: ⌥F9‎‏
\n' + + '
تمرکز بر نوار ابزار
\n' + + '
Windows یا Linux‎‏: Alt+F10
\n' + + '
‎‏macOS: ⌥F10‎‏
\n' + + '
تمرکز بر پانویس
\n' + + '
Windows یا Linux‎‏: Alt+F11
\n' + + '
‎‏macOS: ⌥F11‎‏
\n' + + '
تمرکز اعلان
\n' + + '
ویندوز یا لینوکس: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
تمرکز بر نوار ابزار بافتاری
\n' + + '
Windows ،Linux یا macOS:‏ Ctrl+F9
\n' + + '
\n' + + '\n' + + '

پیمایش در اولین مورد رابط کاربری شروع می‌شود و درخصوص اولین مورد در\n' + + ' مسیر عنصر پانویس، برجسته یا زیرخط‌دار می‌شود.

\n' + + '\n' + + '

پیمایش بین بخش‌های رابط کاربری

\n' + + '\n' + + '

برای جابجایی از یک بخش رابط کاربری به بخش بعدی، Tab را فشار دهید.

\n' + + '\n' + + '

برای جابجایی از یک بخش رابط کاربری به بخش قبلی، Shift+Tab را فشار دهید.

\n' + + '\n' + + '

ترتیب Tab این بخش‌های رابط کاربری عبارتند از:

\n' + + '\n' + + '
    \n' + + '
  1. نوار منو
  2. \n' + + '
  3. هر گروه نوار ابزار
  4. \n' + + '
  5. نوار کناری
  6. \n' + + '
  7. مسیر عنصر در پانویس
  8. \n' + + '
  9. دکمه تغییر وضعیت تعداد کلمات در پانویس
  10. \n' + + '
  11. پیوند نمانام‌سازی در پانویس
  12. \n' + + '
  13. دسته تغییر اندازه ویرایشگر در پانویس
  14. \n' + + '
\n' + + '\n' + + '

اگر بخشی از رابط کاربری موجود نباشد، رد می‌شود.

\n' + + '\n' + + '

اگر پانویس دارای تمرکز بر پیمایش صفحه‌کلید باشد،‌ و نوار کناری قابل‌مشاهده وجود ندارد، فشردن Shift+Tab\n' + + ' تمرکز را به گروه نوار ابزار اول می‌برد، نه آخر.

\n' + + '\n' + + '

پیمایش در بخش‌های رابط کاربری

\n' + + '\n' + + '

برای جابجایی از یک عنصر رابط کاربری به بعدی، کلید جهت‌نمای مناسب را فشار دهید.

\n' + + '\n' + + '

کلیدهای جهت‌نمای چپ و راست

\n' + + '\n' + + '
    \n' + + '
  • جابجایی بین منوها در نوار منو.
  • \n' + + '
  • باز کردن منوی فرعی در یک منو.
  • \n' + + '
  • جابجایی بین دکمه‌ها در یک گروه نوار ابزار.
  • \n' + + '
  • جابجایی بین موارد در مسیر عنصر پانویس.
  • \n' + + '
\n' + + '\n' + + '

کلیدهای جهت‌نمای پایین و بالا

\n' + + '\n' + + '
    \n' + + '
  • جابجایی بین موارد منو در یک منو.
  • \n' + + '
  • جابجایی بین موارد در یک منوی بازشوی نوار ابزار.
  • \n' + + '
\n' + + '\n' + + '

کلیدهایجهت‌نما در بخش رابط کاربری متمرکز می‌چرخند.

\n' + + '\n' + + '

برای بستن یک منوی باز، یک منوی فرعی باز، یا یک منوی بازشوی باز، کلید Esc را فشار دهید.

\n' + + '\n' + + '

اگر تمرکز فعلی در «بالای» یک بخش رابط کاربری خاص است، فشردن کلید Esc نیز موجب\n' + + ' خروج کامل از پیمایش صفحه‌کلید می‌شود.

\n' + + '\n' + + '

اجرای یک مورد منو یا دکمه نوار ابزار

\n' + + '\n' + + '

وقتی مورد منو یا دکمه نوار ابزار مورد نظر هایلایت شد، دکمه بازگشت، Enter،\n' + + ' یا نوار Space را فشار دهید تا مورد را اجرا کنید.

\n' + + '\n' + + '

پیمایش در کادرهای گفتگوی بدون زبانه

\n' + + '\n' + + '

در کادرهای گفتگوی بدون زبانه، وقتی کادر گفتگو باز می‌شود، اولین جزء تعاملی متمرکز می‌شود.

\n' + + '\n' + + '

با فشردن Tab یا Shift+Tab، بین اجزای کادر گفتگوی تعاملی پیمایش کنید.

\n' + + '\n' + + '

پیمایش کادرهای گفتگوی زبانه‌دار

\n' + + '\n' + + '

در کادرهای گفتگوی زبانه‌دار، وقتی کادر گفتگو باز می‌شود، اولین دکمه در منوی زبانه متمرکز می‌شود.

\n' + + '\n' + + '

با فشردن Tab یا\n' + + ' Shift+Tab، بین اجزای تعاملی این زبانه کادر گفتگو پیمایش کنید.

\n' + + '\n' + + '

با دادن تمرکز به منوی زبانه و سپس فشار دادن کلید جهت‌نمای\n' + + ' مناسب برای چرخش میان زبانه‌های موجود، به زبانه کادر گفتگوی دیگری بروید.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fi.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fi.js new file mode 100644 index 0000000..f01dc91 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fi.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.fi', +'

Näppäimistönavigoinnin aloittaminen

\n' + + '\n' + + '
\n' + + '
Siirrä kohdistus valikkopalkkiin
\n' + + '
Windows tai Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Siirrä kohdistus työkalupalkkiin
\n' + + '
Windows tai Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Siirrä kohdistus alatunnisteeseen
\n' + + '
Windows tai Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Keskitä ilmoitukseen
\n' + + '
Windows ja Linux: Alt + F12
\n' + + '
macOS: ⌥F12
\n' + + '
Siirrä kohdistus kontekstuaaliseen työkalupalkkiin
\n' + + '
Windows, Linux tai macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Navigointi aloitetaan ensimmäisestä käyttöliittymän kohteesta, joka joko korostetaan tai alleviivataan, jos\n' + + ' kyseessä on Alatunniste-elementin polun ensimmäinen kohde.

\n' + + '\n' + + '

Käyttöliittymän eri osien välillä navigointi

\n' + + '\n' + + '

Paina sarkainnäppäintä siirtyäksesi käyttöliittymän osasta seuraavaan.

\n' + + '\n' + + '

Jos haluat siirtyä edelliseen käyttöliittymän osaan, paina Shift+sarkainnäppäin.

\n' + + '\n' + + '

Sarkainnäppäin siirtää sinua näissä käyttöliittymän osissa tässä järjestyksessä:

\n' + + '\n' + + '
    \n' + + '
  1. Valikkopalkki
  2. \n' + + '
  3. Työkalupalkin ryhmät
  4. \n' + + '
  5. Sivupalkki
  6. \n' + + '
  7. Elementin polku alatunnisteessa
  8. \n' + + '
  9. Sanalaskurin vaihtopainike alatunnisteessa
  10. \n' + + '
  11. Brändäyslinkki alatunnisteessa
  12. \n' + + '
  13. Editorin koon muuttamisen kahva alatunnisteessa
  14. \n' + + '
\n' + + '\n' + + '

Jos jotakin käyttöliittymän osaa ei ole, se ohitetaan.

\n' + + '\n' + + '

Jos kohdistus on siirretty alatunnisteeseen näppäimistönavigoinnilla eikä sivupalkkia ole näkyvissä, Shift+sarkainnäppäin\n' + + ' siirtää kohdistuksen työkalupalkin ensimmäiseen ryhmään, eikä viimeiseen.

\n' + + '\n' + + '

Käyttöliittymän eri osien sisällä navigointi

\n' + + '\n' + + '

Paina nuolinäppäimiä siirtyäksesi käyttöliittymäelementistä seuraavaan.

\n' + + '\n' + + '

Vasen- ja Oikea-nuolinäppäimet

\n' + + '\n' + + '
    \n' + + '
  • siirtävät sinua valikkopalkin valikoiden välillä.
  • \n' + + '
  • avaavat valikon alavalikon.
  • \n' + + '
  • siirtävät sinua työkalupalkin ryhmän painikkeiden välillä.
  • \n' + + '
  • siirtävät sinua kohteiden välillä alatunnisteen elementin polussa.
  • \n' + + '
\n' + + '\n' + + '

Alas- ja Ylös-nuolinäppäimet

\n' + + '\n' + + '
    \n' + + '
  • siirtävät sinua valikon valikkokohteiden välillä.
  • \n' + + '
  • siirtävät sinua työkalupalkin ponnahdusvalikon kohteiden välillä.
  • \n' + + '
\n' + + '\n' + + '

Nuolinäppäimet siirtävät sinua käyttöliittymän korostetun osan sisällä syklissä.

\n' + + '\n' + + '

Paina Esc-näppäintä sulkeaksesi avoimen valikon, avataksesi alavalikon tai avataksesi ponnahdusvalikon.

\n' + + '\n' + + '

Jos kohdistus on käyttöliittymän tietyn osion ylälaidassa, Esc-näppäimen painaminen\n' + + ' poistuu myös näppäimistönavigoinnista kokonaan.

\n' + + '\n' + + '

Suorita valikkokohde tai työkalupalkin painike

\n' + + '\n' + + '

Kun haluamasi valikkokohde tai työkalupalkin painike on korostettuna, paina Return-, Enter-\n' + + ' tai välilyöntinäppäintä suorittaaksesi kohteen.

\n' + + '\n' + + '

Välilehdittömissä valintaikkunoissa navigointi

\n' + + '\n' + + '

Kun välilehdetön valintaikkuna avautuu, kohdistus siirtyy sen ensimmäiseen interaktiiviseen komponenttiin.

\n' + + '\n' + + '

Voit siirtyä valintaikkunan interaktiivisten komponenttien välillä painamalla sarkainnäppäintä tai Shift+sarkainnäppäin.

\n' + + '\n' + + '

Välilehdellisissä valintaikkunoissa navigointi

\n' + + '\n' + + '

Kun välilehdellinen valintaikkuna avautuu, kohdistus siirtyy välilehtivalikon ensimmäiseen painikkeeseen.

\n' + + '\n' + + '

Voit siirtyä valintaikkunan välilehden interaktiivisen komponenttien välillä painamalla sarkainnäppäintä tai\n' + + ' Shift+sarkainnäppäin.

\n' + + '\n' + + '

Voit siirtyä valintaikkunan toiseen välilehteen siirtämällä kohdistuksen välilehtivalikkoon ja painamalla sopivaa nuolinäppäintä\n' + + ' siirtyäksesi käytettävissä olevien välilehtien välillä syklissä.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fr_FR.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fr_FR.js new file mode 100644 index 0000000..3f611e8 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fr_FR.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.fr_FR', +'

Débuter la navigation au clavier

\n' + + '\n' + + '
\n' + + '
Cibler la barre du menu
\n' + + '
Windows ou Linux : Alt+F9
\n' + + '
macOS : ⌥F9
\n' + + "
Cibler la barre d'outils
\n" + + '
Windows ou Linux : Alt+F10
\n' + + '
macOS : ⌥F10
\n' + + '
Cibler le pied de page
\n' + + '
Windows ou Linux : Alt+F11
\n' + + '
macOS : ⌥F11
\n' + + '
Cibler la notification
\n' + + '
Windows ou Linux : Alt+F12
\n' + + '
macOS : ⌥F12
\n' + + "
Cibler une barre d'outils contextuelle
\n" + + '
Windows, Linux ou macOS : Ctrl+F9
\n' + + '
\n' + + '\n' + + "

La navigation débutera sur le premier élément de l'interface utilisateur, qui sera mis en surbrillance ou bien souligné dans le cas du premier élément du\n" + + " chemin d'éléments du pied de page.

\n" + + '\n' + + "

Naviguer entre les sections de l'interface utilisateur

\n" + + '\n' + + "

Pour passer d'une section de l'interface utilisateur à la suivante, appuyez sur Tabulation.

\n" + + '\n' + + "

Pour passer d'une section de l'interface utilisateur à la précédente, appuyez sur Maj+Tabulation.

\n" + + '\n' + + "

L'ordre de Tabulation de ces sections de l'interface utilisateur est le suivant :

\n" + + '\n' + + '
    \n' + + '
  1. Barre du menu
  2. \n' + + "
  3. Chaque groupe de barres d'outils
  4. \n" + + '
  5. Barre latérale
  6. \n' + + "
  7. Chemin d'éléments du pied de page
  8. \n" + + "
  9. Bouton d'activation du compteur de mots dans le pied de page
  10. \n" + + '
  11. Lien de marque dans le pied de page
  12. \n' + + "
  13. Poignée de redimensionnement de l'éditeur dans le pied de page
  14. \n" + + '
\n' + + '\n' + + "

Si une section de l'interface utilisateur n'est pas présente, elle sera ignorée.

\n" + + '\n' + + "

Si le pied de page comporte un ciblage par navigation au clavier et qu'il n'y a aucune barre latérale visible, appuyer sur Maj+Tabulation\n" + + " déplace le ciblage vers le premier groupe de barres d'outils et non le dernier.

\n" + + '\n' + + "

Naviguer au sein des sections de l'interface utilisateur

\n" + + '\n' + + "

Pour passer d'un élément de l'interface utilisateur au suivant, appuyez sur la Flèche appropriée.

\n" + + '\n' + + '

Les touches fléchées Gauche et Droite

\n' + + '\n' + + '
    \n' + + '
  • se déplacent entre les menus de la barre des menus.
  • \n' + + "
  • ouvrent un sous-menu au sein d'un menu.
  • \n" + + "
  • se déplacent entre les boutons d'un groupe de barres d'outils.
  • \n" + + "
  • se déplacent entre les éléments du chemin d'éléments du pied de page.
  • \n" + + '
\n' + + '\n' + + '

Les touches fléchées Bas et Haut

\n' + + '\n' + + '
    \n' + + "
  • se déplacent entre les éléments de menu au sein d'un menu.
  • \n" + + "
  • se déplacent entre les éléments au sein d'un menu contextuel de barre d'outils.
  • \n" + + '
\n' + + '\n' + + "

Les Flèches parcourent la section de l'interface utilisateur ciblée.

\n" + + '\n' + + '

Pour fermer un menu ouvert, un sous-menu ouvert ou un menu contextuel ouvert, appuyez sur Echap.

\n' + + '\n' + + "

Si l'actuel ciblage se trouve en « haut » d'une section spécifique de l'interface utilisateur, appuyer sur Echap permet également de quitter\n" + + ' entièrement la navigation au clavier.

\n' + + '\n' + + "

Exécuter un élément de menu ou un bouton de barre d'outils

\n" + + '\n' + + "

Lorsque l'élément de menu ou le bouton de barre d'outils désiré est mis en surbrillance, appuyez sur la touche Retour arrière, Entrée\n" + + " ou la Barre d'espace pour exécuter l'élément.

\n" + + '\n' + + '

Naviguer au sein de dialogues sans onglets

\n' + + '\n' + + "

Dans les dialogues sans onglets, le premier composant interactif est ciblé lorsque le dialogue s'ouvre.

\n" + + '\n' + + '

Naviguez entre les composants du dialogue interactif en appuyant sur Tabulation ou Maj+Tabulation.

\n' + + '\n' + + '

Naviguer au sein de dialogues avec onglets

\n' + + '\n' + + "

Dans les dialogues avec onglets, le premier bouton du menu de l'onglet est ciblé lorsque le dialogue s'ouvre.

\n" + + '\n' + + '

Naviguez entre les composants interactifs de cet onglet de dialogue en appuyant sur Tabulation ou\n' + + ' Maj+Tabulation.

\n' + + '\n' + + "

Passez à un autre onglet de dialogue en ciblant le menu de l'onglet et en appuyant sur la Flèche\n" + + ' appropriée pour parcourir les onglets disponibles.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/he_IL.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/he_IL.js new file mode 100644 index 0000000..7d6513a --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/he_IL.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.he_IL', +'

התחל ניווט במקלדת

\n' + + '\n' + + '
\n' + + '
התמקד בשורת התפריטים
\n' + + '
Windows או Linux:‏ Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
העבר מיקוד לסרגל הכלים
\n' + + '
Windows או Linux:‏ Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
העבר מיקוד לכותרת התחתונה
\n' + + '
Windows או Linux:‏ Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
העבר מיקוד להודעה
\n' + + '
Windows או Linux:‏ Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
העבר מיקוד לסרגל כלים הקשרי
\n' + + '
Windows‏, Linux או macOS:‏ Ctrl+F9
\n' + + '
\n' + + '\n' + + '

הניווט יתחיל ברכיב הראשון במשך, שיודגש או שיהיה מתחתיו קו תחתון במקרה של הפריט הראשון\n' + + ' הנתיב של רכיב הכותרת התחתונה.

\n' + + '\n' + + '

עבור בין מקטעים במסך

\n' + + '\n' + + '

כדי לעבור בין המקטעים במסך, הקש Tab.

\n' + + '\n' + + '

כדי לעבור למקטע הקודם במסך, הקש Shift+Tab.

\n' + + '\n' + + '

הסדר מבחינת מקש Tab של הרכיבים במסך:

\n' + + '\n' + + '
    \n' + + '
  1. שורת התפריטים
  2. \n' + + '
  3. כל קבוצה בסרגל הכלים
  4. \n' + + '
  5. הסרגל הצידי
  6. \n' + + '
  7. נתיב של רכיב בכותרת התחתונה
  8. \n' + + '
  9. לחצן לספירת מילים בכותרת התחתונה
  10. \n' + + '
  11. קישור של המותג בכותרת התחתונה
  12. \n' + + '
  13. ידית לשינוי גודל עבור העורך בכותרת התחתונה
  14. \n' + + '
\n' + + '\n' + + '

אם רכיב כלשהו במסך לא מופיע, המערכת תדלג עליו.

\n' + + '\n' + + '

אם בכותרת התחתונה יש מיקוד של ניווט במקלדת, ולא מופיע סרגל בצד, יש להקיש Shift+Tab\n' + + ' מעביר את המיקוד לקבוצה הראשונה בסרגל הכלים, לא האחרונה.

\n' + + '\n' + + '

עבור בתוך מקטעים במסך

\n' + + '\n' + + '

כדי לעבור מרכיב אחד לרכיב אחר במסך, הקש על מקש החץ המתאים.

\n' + + '\n' + + '

מקשי החיצים שמאלה וימינה

\n' + + '\n' + + '
    \n' + + '
  • עבור בין תפריטים בשורת התפריטים.
  • \n' + + '
  • פתח תפריט משני בתפריט.
  • \n' + + '
  • עבור בין לחצנים בקבוצה בסרגל הכלים.
  • \n' + + '
  • עבור בין פריטים ברכיב בכותרת התחתונה.
  • \n' + + '
\n' + + '\n' + + '

מקשי החיצים למטה ולמעלה

\n' + + '\n' + + '
    \n' + + '
  • עבור בין פריטים בתפריט.
  • \n' + + '
  • עבור בין פריטים בחלון הקובץ של סרגל הכלים.
  • \n' + + '
\n' + + '\n' + + '

מקשי החצים משתנים בתוך המקטע במסך שעליו נמצא המיקוד.

\n' + + '\n' + + '

כדי לסגור תפריט פתוח, תפריט משני פתוח או חלון קופץ, הקש על Esc.

\n' + + '\n' + + "

אם המיקוד הוא על החלק 'העליון' של מקטע מסוים במסך, הקשה על Esc מביאה גם ליציאה\n" + + ' מהניווט במקלדת לחלוטין.

\n' + + '\n' + + '

הפעל פריט בתפריט או לחצן בסרגל הכלים

\n' + + '\n' + + '

כאשר הפריט הרצוי בתפריט או הלחצן בסרגל הכלים מודגשים, הקש על Return, Enter,\n' + + ' או על מקש הרווח כדי להפעיל את הפריט.

\n' + + '\n' + + '

ניווט בחלונות דו-שיח בלי כרטיסיות

\n' + + '\n' + + '

בחלונות דו-שיח בלי כרטיסיות, הרכיב האינטראקטיבי הראשון מקבל את המיקוד כאשר החלון נפתח.

\n' + + '\n' + + '

עבור בין רכיבים אינטראקטיביים בחלון על ידי הקשה על Tab או Shift+Tab.

\n' + + '\n' + + '

ניווט בחלונות דו-שיח עם כרטיסיות

\n' + + '\n' + + '

בחלונות דו-שיח עם כרטיסיות, הלחצן הראשון בתפריט מקבל את המיקוד כאשר החלון נפתח.

\n' + + '\n' + + '

עבור בין רכיבים אינטראקטיביים בחלון על ידי הקשה על Tab או\n' + + ' Shift+Tab.

\n' + + '\n' + + '

עבור לכרטיסיה אחרת בחלון על ידי העברת המיקוד לתפריט הכרטיסיות והקשה על החץהמתאים\n' + + ' כדי לעבור בין הכרטיסיות הזמינות.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hi.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hi.js new file mode 100644 index 0000000..ef59a5c --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hi.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.hi', +'

कीबोर्ड नेविगेशन शुरू करें

\n' + + '\n' + + '
\n' + + '
मेन्यू बार पर फ़ोकस करें
\n' + + '
Windows या Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
टूलबार पर फ़ोकस करें
\n' + + '
Windows या Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
फ़ुटर पर फ़ोकस करें
\n' + + '
Windows या Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
नोटिफ़िकेशन फ़ोकस
\n' + + '
Windows या Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
प्रासंगिक टूलबार पर फ़ोकस करें
\n' + + '
Windows, Linux या macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

नेविगेशन पहले UI आइटम पर शुरू होगा, जिसे हाइलाइट किया जाएगा या पहले आइटम के मामले में फ़ुटर तत्व पथ में\n' + + ' रेखांकित किया जाएगा।

\n' + + '\n' + + '

UI सेक्शन के बीच नेविगेट करें

\n' + + '\n' + + '

एक UI सेक्शन से दूसरे सेक्शन में जाने के लिए, Tab दबाएं।

\n' + + '\n' + + '

एक UI सेक्शन से पिछले सेक्शन में जाने के लिए, Shift+Tab दबाएं।

\n' + + '\n' + + '

इन UI सेक्शन का Tab क्रम नीचे दिया गया है:

\n' + + '\n' + + '
    \n' + + '
  1. मेन्यू बार
  2. \n' + + '
  3. प्रत्येक टूलबार समूह
  4. \n' + + '
  5. साइडबार
  6. \n' + + '
  7. फ़ुटर में तत्व पथ
  8. \n' + + '
  9. फ़ुटर में शब्द गणना टॉगल बटन
  10. \n' + + '
  11. फ़ुटर में ब्रांडिंग लिंक
  12. \n' + + '
  13. फ़ुटर में संपादक का आकार बदलने का हैंडल
  14. \n' + + '
\n' + + '\n' + + '

अगर कोई UI सेक्शन मौजूद नहीं है, तो उसे छोड़ दिया जाता है।

\n' + + '\n' + + '

अगर फ़ुटर में कीबोर्ड नेविगेशन फ़ोकस है, और कोई दिखा देने वाला साइडबार नहीं है, तो Shift+Tab दबाने से\n' + + ' फ़ोकस पहले टूलबार समूह पर चला जाता है, पिछले पर नहीं।

\n' + + '\n' + + '

UI सेक्शन के भीतर नेविगेट करें

\n' + + '\n' + + '

एक UI तत्व से दूसरे में जाने के लिए उपयुक्त ऐरो कुंजी दबाएं।

\n' + + '\n' + + '

बाएं और दाएं ऐरो कुंजियां

\n' + + '\n' + + '
    \n' + + '
  • मेन्यू बार में मेन्यू के बीच ले जाती हैं।
  • \n' + + '
  • मेन्यू में एक सब-मेन्यू खोलें।
  • \n' + + '
  • टूलबार समूह में बटनों के बीच ले जाएं।
  • \n' + + '
  • फ़ुटर के तत्व पथ में आइटम के बीच ले जाएं।
  • \n' + + '
\n' + + '\n' + + '

नीचे और ऊपर ऐरो कुंजियां

\n' + + '\n' + + '
    \n' + + '
  • मेन्यू में मेन्यू आइटम के बीच ले जाती हैं।
  • \n' + + '
  • टूलबार पॉप-अप मेन्यू में आइटम के बीच ले जाएं।
  • \n' + + '
\n' + + '\n' + + '

फ़ोकस वाले UI सेक्शन के भीतर ऐरो कुंजियां चलाती रहती हैं।

\n' + + '\n' + + '

कोई खुला मेन्यू, कोई खुला सब-मेन्यू या कोई खुला पॉप-अप मेन्यू बंद करने के लिए Esc कुंजी दबाएं।

\n' + + '\n' + + "

अगर मौजूदा फ़ोकस किसी विशेष UI सेक्शन के 'शीर्ष' पर है, तो Esc कुंजी दबाने से भी\n" + + ' कीबोर्ड नेविगेशन पूरी तरह से बाहर हो जाता है।

\n' + + '\n' + + '

मेन्यू आइटम या टूलबार बटन निष्पादित करें

\n' + + '\n' + + '

जब वांछित मेन्यू आइटम या टूलबार बटन हाइलाइट किया जाता है, तो आइटम को निष्पादित करने के लिए Return, Enter,\n' + + ' या Space bar दबाएं।

\n' + + '\n' + + '

गैर-टैब वाले डायलॉग पर नेविगेट करें

\n' + + '\n' + + '

गैर-टैब वाले डायलॉग में, डायलॉग खुलने पर पहला इंटरैक्टिव घटक फ़ोकस लेता है।

\n' + + '\n' + + '

Tab or Shift+Tab दबाकर इंटरैक्टिव डायलॉग घटकों के बीच नेविगेट करें।

\n' + + '\n' + + '

टैब किए गए डायलॉग पर नेविगेट करें

\n' + + '\n' + + '

टैब किए गए डायलॉग में, डायलॉग खुलने पर टैब मेन्यू में पहला बटन फ़ोकस लेता है।

\n' + + '\n' + + '

इस डायलॉग टैब के इंटरैक्टिव घटकों के बीच नेविगेट करने के लिए Tab या\n' + + ' Shift+Tab दबाएं।

\n' + + '\n' + + '

टैब मेन्यू को फ़ोकस देकर और फिर उपलब्ध टैब में के बीच जाने के लिए उपयुक्त ऐरो\n' + + ' कुंजी दबाकर दूसरे डायलॉग टैब पर स्विच करें।

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hr.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hr.js new file mode 100644 index 0000000..1bf35c5 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hr.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.hr', +'

Početak navigacije na tipkovnici

\n' + + '\n' + + '
\n' + + '
Fokusiranje trake izbornika
\n' + + '
Windows ili Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Fokusiranje alatne trake
\n' + + '
Windows ili Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Fokusiranje podnožja
\n' + + '
Windows ili Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Fokusiranje obavijesti
\n' + + '
Windows ili Linux: Alt + F12
\n' + + '
macOS: ⌥F12
\n' + + '
Fokusiranje kontekstne alatne trake
\n' + + '
Windows, Linux ili macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Navigacija će započeti kod prve stavke na korisničkom sučelju, koja će biti istaknuta ili podcrtana ako se radi o prvoj stavci u\n' + + ' putu elementa u podnožju.

\n' + + '\n' + + '

Navigacija između dijelova korisničkog sučelja

\n' + + '\n' + + '

Za pomicanje s jednog dijela korisničkog sučelja na drugi pritisnite tabulator.

\n' + + '\n' + + '

Za pomicanje s jednog dijela korisničkog sučelja na prethodni pritisnite Shift + tabulator.

\n' + + '\n' + + '

Ovo je redoslijed pomicanja tabulatora po dijelovima korisničkog sučelja:

\n' + + '\n' + + '
    \n' + + '
  1. Traka izbornika
  2. \n' + + '
  3. Pojedinačne grupe na alatnoj traci
  4. \n' + + '
  5. Bočna traka
  6. \n' + + '
  7. Put elemenata u podnožju
  8. \n' + + '
  9. Gumb za pomicanje po broju riječi u podnožju
  10. \n' + + '
  11. Veza na brand u podnožju
  12. \n' + + '
  13. Značajka za promjenu veličine alata za uređivanje u podnožju
  14. \n' + + '
\n' + + '\n' + + '

Ako neki dio korisničkog sučelja nije naveden, on se preskače.

\n' + + '\n' + + '

Ako u podnožju postoji fokus za navigaciju na tipkovnici, a nema vidljive bočne trake, pritiskom na Shift + tabulator\n' + + ' fokus se prebacuje na prvu skupinu na alatnoj traci, ne na zadnju.

\n' + + '\n' + + '

Navigacija unutar dijelova korisničkog sučelja

\n' + + '\n' + + '

Za pomicanje s jednog elementa korisničkog sučelja na drugi pritisnite tipku s odgovarajućom strelicom.

\n' + + '\n' + + '

Tipke s lijevom i desnom strelicom

\n' + + '\n' + + '
    \n' + + '
  • služe za pomicanje između izbornika na alatnoj traci.
  • \n' + + '
  • otvaraju podizbornik unutar izbornika.
  • \n' + + '
  • služe za pomicanje između gumba unutar skupina na alatnoj traci.
  • \n' + + '
  • služe za pomicanje između stavki na elementu puta u podnožju.
  • \n' + + '
\n' + + '\n' + + '

Tipke s donjom i gornjom strelicom

\n' + + '\n' + + '
    \n' + + '
  • služe za pomicanje između stavki unutar izbornika.
  • \n' + + '
  • služe za pomicanje između stavki na alatnoj traci skočnog izbornika.
  • \n' + + '
\n' + + '\n' + + '

Tipkama strelica kružno se pomičete unutar dijela korisničkog sučelja koji je u fokusu.

\n' + + '\n' + + '

Za zatvaranje otvorenog izbornika, otvorenog podizbornika ili otvorenog skočnog izbornika pritisnite tipku Esc.

\n' + + '\n' + + '

Ako je fokus trenutačno postavljen na vrh pojedinačnog dijela korisničkog sučelja, pritiskom na tipku Esc također\n' + + ' u potpunosti zatvarate navigaciju na tipkovnici.

\n' + + '\n' + + '

Izvršavanje radnji putem stavki izbornika ili gumba na alatnoj traci

\n' + + '\n' + + '

Nakon što se istakne stavka izbornika ili gumb na alatnoj traci s radnjom koju želite izvršiti, pritisnite tipku Return, Enter\n' + + ' ili razmak da biste pokrenuli željenu radnju.

\n' + + '\n' + + '

Navigacija dijaloškim okvirima izvan kartica

\n' + + '\n' + + '

Prilikom otvaranja dijaloških okvira izvan kartica fokus se nalazi na prvoj interaktivnoj komponenti.

\n' + + '\n' + + '

Navigaciju između interaktivnih dijaloških komponenata vršite pritiskom na tabulator ili Shift + tabulator.

\n' + + '\n' + + '

Navigacija dijaloškim okvirima u karticama

\n' + + '\n' + + '

Prilikom otvaranja dijaloških okvira u karticama fokus se nalazi na prvom gumbu u izborniku unutar kartice.

\n' + + '\n' + + '

Navigaciju između interaktivnih komponenata dijaloškog okvira u kartici vršite pritiskom na tabulator ili\n' + + ' Shift + tabulator.

\n' + + '\n' + + '

Na karticu s drugim dijaloškim okvirom možete se prebaciti tako da stavite fokus na izbornik kartice pa pritisnete tipku s odgovarajućom strelicom\n' + + ' za kružno pomicanje između dostupnih kartica.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hu_HU.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hu_HU.js new file mode 100644 index 0000000..5c984bb --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hu_HU.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.hu_HU', +'

Billentyűzetes navigáció indítása

\n' + + '\n' + + '
\n' + + '
Fókusz a menüsávra
\n' + + '
Windows és Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Fókusz az eszköztárra
\n' + + '
Windows és Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Fókusz a láblécre
\n' + + '
Windows és Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Ráközelítés az értesítésre
\n' + + '
Windows vagy Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Fókusz egy környezetfüggő eszköztárra
\n' + + '
Windows, Linux és macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

A navigáció az első felhasználói felületi elemnél kezdődik, amelyet a rendszer kiemel, illetve aláhúz, amennyiben az az első elem\n' + + ' a lábléc elemútvonalán.

\n' + + '\n' + + '

Navigálás a felhasználói felület szakaszai között

\n' + + '\n' + + '

A felhasználói felület következő szakaszára váltáshoz nyomja meg a Tab billentyűt.

\n' + + '\n' + + '

A felhasználói felület előző szakaszára váltáshoz nyomja meg a Shift+Tab billentyűt.

\n' + + '\n' + + '

A Tab billentyűvel a felhasználói felület szakaszai között a következő sorrendben vált:

\n' + + '\n' + + '
    \n' + + '
  1. Menüsáv
  2. \n' + + '
  3. Az egyes eszköztárcsoportok
  4. \n' + + '
  5. Oldalsáv
  6. \n' + + '
  7. Elemútvonal a láblécen
  8. \n' + + '
  9. Szószámátkapcsoló gomb a láblécen
  10. \n' + + '
  11. Márkalink a láblécen
  12. \n' + + '
  13. Szerkesztő átméretezési fogópontja a láblécen
  14. \n' + + '
\n' + + '\n' + + '

Ha a felhasználói felület valamelyik eleme nincs jelen, a rendszer kihagyja.

\n' + + '\n' + + '

Ha a billentyűzetes navigáció fókusza a láblécen van, és nincs látható oldalsáv, a Shift+Tab\n' + + ' billentyűkombináció lenyomásakor az első eszköztárcsoportra ugrik a fókusz, nem az utolsóra.

\n' + + '\n' + + '

Navigálás a felhasználói felület szakaszain belül

\n' + + '\n' + + '

A felhasználói felület következő elemére váltáshoz nyomja meg a megfelelő nyílbillentyűt.

\n' + + '\n' + + '

A bal és a jobb nyílgomb

\n' + + '\n' + + '
    \n' + + '
  • a menüsávban a menük között vált.
  • \n' + + '
  • a menükben megnyit egy almenüt.
  • \n' + + '
  • az eszköztárcsoportban a gombok között vált.
  • \n' + + '
  • a lábléc elemútvonalán az elemek között vált.
  • \n' + + '
\n' + + '\n' + + '

A le és a fel nyílgomb

\n' + + '\n' + + '
    \n' + + '
  • a menükben a menüpontok között vált.
  • \n' + + '
  • az eszköztár előugró menüjében az elemek között vált.
  • \n' + + '
\n' + + '\n' + + '

A nyílbillentyűk lenyomásával körkörösen lépkedhet a fókuszban lévő felhasználói felületi szakasz elemei között.

\n' + + '\n' + + '

A megnyitott menüket, almenüket és előugró menüket az Esc billentyűvel zárhatja be.

\n' + + '\n' + + '

Ha a fókusz az aktuális felületi elem „felső” részén van, az Esc billentyűvel az egész\n' + + ' billentyűzetes navigációból kilép.

\n' + + '\n' + + '

Menüpont vagy eszköztárgomb aktiválása

\n' + + '\n' + + '

Amikor a kívánt menüelem vagy eszköztárgomb van kijelölve, nyomja meg a Return, az Enter\n' + + ' vagy a Szóköz billentyűt az adott elem vagy gomb aktiválásához.

\n' + + '\n' + + '

Navigálás a lapokkal nem rendelkező párbeszédablakokban

\n' + + '\n' + + '

A lapokkal nem rendelkező párbeszédablakokban az első interaktív összetevő kapja a fókuszt, amikor a párbeszédpanel megnyílik.

\n' + + '\n' + + '

A párbeszédpanelek interaktív összetevői között a Tab vagy a Shift+Tab billentyűvel navigálhat.

\n' + + '\n' + + '

Navigálás a lapokkal rendelkező párbeszédablakokban

\n' + + '\n' + + '

A lapokkal rendelkező párbeszédablakokban a lapmenü első gombja kapja a fókuszt, amikor a párbeszédpanel megnyílik.

\n' + + '\n' + + '

A párbeszédpanel e lapjának interaktív összetevői között a Tab vagy\n' + + ' Shift+Tab billentyűvel navigálhat.

\n' + + '\n' + + '

A párbeszédablak másik lapjára úgy léphet, hogy a fókuszt a lapmenüre állítja, majd lenyomja a megfelelő nyílbillentyűt\n' + + ' a rendelkezésre álló lapok közötti lépkedéshez.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/id.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/id.js new file mode 100644 index 0000000..d607dd1 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/id.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.id', +'

Memulai navigasi keyboard

\n' + + '\n' + + '
\n' + + '
Fokus pada bilah Menu
\n' + + '
Windows atau Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Fokus pada Bilah Alat
\n' + + '
Windows atau Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Fokus pada footer
\n' + + '
Windows atau Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Fokuskan pemberitahuan
\n' + + '
Windows atau Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Fokus pada bilah alat kontekstual
\n' + + '
Windows, Linux, atau macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Navigasi akan dimulai dari item pertama UI, yang akan disorot atau digarisbawahi di\n' + + ' alur elemen Footer.

\n' + + '\n' + + '

Berpindah antar-bagian UI

\n' + + '\n' + + '

Untuk berpindah dari satu bagian UI ke bagian berikutnya, tekan Tab.

\n' + + '\n' + + '

Untuk berpindah dari satu bagian UI ke bagian sebelumnya, tekan Shift+Tab.

\n' + + '\n' + + '

Urutan Tab bagian-bagian UI ini adalah:

\n' + + '\n' + + '
    \n' + + '
  1. Bilah menu
  2. \n' + + '
  3. Tiap grup bilah alat
  4. \n' + + '
  5. Bilah sisi
  6. \n' + + '
  7. Alur elemen di footer
  8. \n' + + '
  9. Tombol aktifkan/nonaktifkan jumlah kata di footer
  10. \n' + + '
  11. Tautan merek di footer
  12. \n' + + '
  13. Pengatur pengubahan ukuran editor di footer
  14. \n' + + '
\n' + + '\n' + + '

Jika suatu bagian UI tidak ada, bagian tersebut dilewati.

\n' + + '\n' + + '

Jika fokus navigasi keyboard ada pada footer, tetapi tidak ada bilah sisi yang terlihat, menekan Shift+Tab\n' + + ' akan memindahkan fokus ke grup bilah alat pertama, bukan yang terakhir.

\n' + + '\n' + + '

Berpindah di dalam bagian-bagian UI

\n' + + '\n' + + '

Untuk berpindah dari satu elemen UI ke elemen berikutnya, tekan tombol Panah yang sesuai.

\n' + + '\n' + + '

Tombol panah Kiri dan Kanan untuk

\n' + + '\n' + + '
    \n' + + '
  • berpindah-pindah antar-menu di dalam bilah menu.
  • \n' + + '
  • membuka sub-menu di dalam menu.
  • \n' + + '
  • berpindah-pindah antar-tombol di dalam grup bilah alat.
  • \n' + + '
  • berpindah-pindah antar-item di dalam alur elemen footer.
  • \n' + + '
\n' + + '\n' + + '

Tombol panah Bawah dan Atas untuk

\n' + + '\n' + + '
    \n' + + '
  • berpindah-pindah antar-item menu di dalam menu.
  • \n' + + '
  • berpindah-pindah antar-item di dalam menu pop-up bilah alat.
  • \n' + + '
\n' + + '\n' + + '

Tombol Panah hanya bergerak di dalam bagian UI yang difokuskan.

\n' + + '\n' + + '

Untuk menutup menu, sub-menu, atau menu pop-up yang terbuka, tekan tombol Esc.

\n' + + '\n' + + '

Jika fokus sedang berada di ‘atas’ bagian UI tertentu, menekan tombol Esc juga dapat mengeluarkan fokus\n' + + ' dari seluruh navigasi keyboard.

\n' + + '\n' + + '

Menjalankan item menu atau tombol bilah alat

\n' + + '\n' + + '

Jika item menu atau tombol bilah alat yang diinginkan tersorot, tekan Return, Enter,\n' + + ' atau Spasi untuk menjalankan item.

\n' + + '\n' + + '

Berpindah dalam dialog tanpa tab

\n' + + '\n' + + '

Dalam dialog tanpa tab, fokus diarahkan pada komponen interaktif pertama saat dialog terbuka.

\n' + + '\n' + + '

Berpindah di antara komponen dalam dialog interaktif dengan menekan Tab atau Shift+Tab.

\n' + + '\n' + + '

Berpindah dalam dialog dengan tab

\n' + + '\n' + + '

Dalam dialog yang memiliki tab, fokus diarahkan pada tombol pertama di dalam menu saat dialog terbuka.

\n' + + '\n' + + '

Berpindah di antara komponen-komponen interaktif pada tab dialog ini dengan menekan Tab atau\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Beralih ke tab dialog lain dengan mengarahkan fokus pada menu tab lalu tekan tombol Panah\n' + + ' yang sesuai untuk berpindah ke berbagai tab yang tersedia.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/it.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/it.js new file mode 100644 index 0000000..3a791c9 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/it.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.it', +'

Iniziare la navigazione tramite tastiera

\n' + + '\n' + + '
\n' + + '
Impostare lo stato attivo per la barra dei menu
\n' + + '
Windows o Linux: ALT+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Impostare lo stato attivo per la barra degli strumenti
\n' + + '
Windows o Linux: ALT+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Impostare lo stato attivo per il piè di pagina
\n' + + '
Windows o Linux: ALT+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Metti a fuoco la notifica
\n' + + '
Windows o Linux: ALT+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Impostare lo stato attivo per la barra degli strumenti contestuale
\n' + + '
Windows, Linux o macOS: CTRL+F9
\n' + + '
\n' + + '\n' + + "

La navigazione inizierà dalla prima voce dell'interfaccia utente, che sarà evidenziata o sottolineata nel caso della prima voce\n" + + " nel percorso dell'elemento del piè di pagina.

\n" + + '\n' + + "

Navigare tra le sezioni dell'interfaccia utente

\n" + + '\n' + + "

Per passare da una sezione dell'interfaccia utente alla successiva, premere TAB.

\n" + + '\n' + + "

Per passare da una sezione dell'interfaccia utente alla precedente, premere MAIUSC+TAB.

\n" + + '\n' + + "

L'ordine di tabulazione di queste sezioni dell'interfaccia utente è:

\n" + + '\n' + + '
    \n' + + '
  1. Barra dei menu
  2. \n' + + '
  3. Ogni gruppo di barre degli strumenti
  4. \n' + + '
  5. Barra laterale
  6. \n' + + "
  7. Percorso dell'elemento nel piè di pagina
  8. \n" + + '
  9. Pulsante di attivazione/disattivazione del conteggio delle parole nel piè di pagina
  10. \n' + + '
  11. Collegamento al marchio nel piè di pagina
  12. \n' + + "
  13. Quadratino di ridimensionamento dell'editor nel piè di pagina
  14. \n" + + '
\n' + + '\n' + + "

Se una sezione dell'interfaccia utente non è presente, viene saltata.

\n" + + '\n' + + '

Se il piè di pagina ha lo stato attivo per la navigazione tramite tastiera e non è presente alcuna barra laterale visibile, premendo MAIUSC+TAB\n' + + " si sposta lo stato attivo sul primo gruppo di barre degli strumenti, non sull'ultimo.

\n" + + '\n' + + "

Navigare all'interno delle sezioni dell'interfaccia utente

\n" + + '\n' + + "

Per passare da un elemento dell'interfaccia utente al successivo, premere il tasto freccia appropriato.

\n" + + '\n' + + '

I tasti freccia Sinistra e Destra

\n' + + '\n' + + '
    \n' + + '
  • consentono di spostarsi tra i menu della barra dei menu.
  • \n' + + '
  • aprono un sottomenu in un menu.
  • \n' + + '
  • consentono di spostarsi tra i pulsanti di un gruppo di barre degli strumenti.
  • \n' + + "
  • consentono di spostarsi tra le voci nel percorso dell'elemento del piè di pagina.
  • \n" + + '
\n' + + '\n' + + '

I tasti freccia Giù e Su

\n' + + '\n' + + '
    \n' + + '
  • consentono di spostarsi tra le voci di un menu.
  • \n' + + '
  • consentono di spostarsi tra le voci di un menu a comparsa della barra degli strumenti.
  • \n' + + '
\n' + + '\n' + + "

I tasti freccia consentono di spostarsi all'interno della sezione dell'interfaccia utente con stato attivo.

\n" + + '\n' + + '

Per chiudere un menu aperto, un sottomenu aperto o un menu a comparsa aperto, premere il tasto ESC.

\n' + + '\n' + + "

Se lo stato attivo corrente si trova nella parte superiore di una particolare sezione dell'interfaccia utente, premendo il tasto ESC si esce\n" + + ' completamente dalla navigazione tramite tastiera.

\n' + + '\n' + + '

Eseguire una voce di menu o un pulsante della barra degli strumenti

\n' + + '\n' + + '

Quando la voce di menu o il pulsante della barra degli strumenti desiderati sono evidenziati, premere il tasto diritorno a capo, il tasto Invio\n' + + ' o la barra spaziatrice per eseguirli.

\n' + + '\n' + + '

Navigare nelle finestre di dialogo non a schede

\n' + + '\n' + + "

Nelle finestre di dialogo non a schede, all'apertura della finestra di dialogo diventa attivo il primo componente interattivo.

\n" + + '\n' + + '

Per spostarsi tra i componenti interattivi della finestra di dialogo, premere TAB o MAIUSC+TAB.

\n' + + '\n' + + '

Navigare nelle finestre di dialogo a schede

\n' + + '\n' + + "

Nelle finestre di dialogo a schede, all'apertura della finestra di dialogo diventa attivo il primo pulsante del menu della scheda.

\n" + + '\n' + + '

Per spostarsi tra i componenti interattivi di questa scheda della finestra di dialogo, premere TAB o\n' + + ' MAIUSC+TAB.

\n' + + '\n' + + "

Per passare a un'altra scheda della finestra di dialogo, attivare il menu della scheda e premere il tasto freccia\n" + + ' appropriato per scorrere le schede disponibili.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ja.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ja.js new file mode 100644 index 0000000..26872db --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ja.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ja', +'

キーボード ナビゲーションの開始

\n' + + '\n' + + '
\n' + + '
メニュー バーをフォーカス
\n' + + '
Windows または Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
ツール バーをフォーカス
\n' + + '
Windows または Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
フッターをフォーカス
\n' + + '
Windows または Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
通知にフォーカス
\n' + + '
Windows または Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
コンテキスト ツール バーをフォーカス
\n' + + '
Windows、Linux または macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

ナビゲーションは最初の UI 項目から開始され、強調表示されるか、フッターの要素パスにある最初の項目の場合は\n' + + ' 下線が引かれます。

\n' + + '\n' + + '

UI セクション間の移動

\n' + + '\n' + + '

次の UI セクションに移動するには、Tab を押します。

\n' + + '\n' + + '

前の UI セクションに移動するには、Shift+Tab を押します。

\n' + + '\n' + + '

これらの UI セクションの Tab の順序:

\n' + + '\n' + + '
    \n' + + '
  1. メニュー バー
  2. \n' + + '
  3. 各ツール バー グループ
  4. \n' + + '
  5. サイド バー
  6. \n' + + '
  7. フッターの要素パス
  8. \n' + + '
  9. フッターの単語数切り替えボタン
  10. \n' + + '
  11. フッターのブランド リンク
  12. \n' + + '
  13. フッターのエディター サイズ変更ハンドル
  14. \n' + + '
\n' + + '\n' + + '

UI セクションが存在しない場合は、スキップされます。

\n' + + '\n' + + '

フッターにキーボード ナビゲーション フォーカスがあり、表示可能なサイド バーがない場合、Shift+Tab を押すと、\n' + + ' フォーカスが最後ではなく最初のツール バー グループに移動します。

\n' + + '\n' + + '

UI セクション内の移動

\n' + + '\n' + + '

次の UI 要素に移動するには、適切な矢印キーを押します。

\n' + + '\n' + + '

左矢印右矢印のキー

\n' + + '\n' + + '
    \n' + + '
  • メニュー バーのメニュー間で移動します。
  • \n' + + '
  • メニュー内のサブメニューを開きます。
  • \n' + + '
  • ツール バー グループのボタン間で移動します。
  • \n' + + '
  • フッターの要素パスの項目間で移動します。
  • \n' + + '
\n' + + '\n' + + '

下矢印上矢印のキー

\n' + + '\n' + + '
    \n' + + '
  • メニュー内のメニュー項目間で移動します。
  • \n' + + '
  • ツール バー ポップアップ メニュー内のメニュー項目間で移動します。
  • \n' + + '
\n' + + '\n' + + '

矢印キーで、フォーカスされた UI セクション内で循環します。

\n' + + '\n' + + '

開いたメニュー、開いたサブメニュー、開いたポップアップ メニューを閉じるには、Esc キーを押します。

\n' + + '\n' + + '

現在のフォーカスが特定の UI セクションの「一番上」にある場合、Esc キーを押すと\n' + + ' キーボード ナビゲーションも完全に閉じられます。

\n' + + '\n' + + '

メニュー項目またはツール バー ボタンの実行

\n' + + '\n' + + '

目的のメニュー項目やツール バー ボタンが強調表示されている場合、リターンEnter、\n' + + ' またはスペース キーを押して項目を実行します。

\n' + + '\n' + + '

タブのないダイアログの移動

\n' + + '\n' + + '

タブのないダイアログでは、ダイアログが開くと最初の対話型コンポーネントがフォーカスされます。

\n' + + '\n' + + '

Tab または Shift+Tab を押して、対話型ダイアログ コンポーネント間で移動します。

\n' + + '\n' + + '

タブ付きダイアログの移動

\n' + + '\n' + + '

タブ付きダイアログでは、ダイアログが開くとタブ メニューの最初のボタンがフォーカスされます。

\n' + + '\n' + + '

Tab または\n' + + ' Shift+Tab を押して、このダイアログ タブの対話型コンポーネント間で移動します。

\n' + + '\n' + + '

タブ メニューをフォーカスしてから適切な矢印キーを押して表示可能なタブを循環して、\n' + + ' 別のダイアログに切り替えます。

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/kk.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/kk.js new file mode 100644 index 0000000..e31532f --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/kk.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.kk', +'

Пернетақта навигациясын бастау

\n' + + '\n' + + '
\n' + + '
Мәзір жолағын фокустау
\n' + + '
Windows немесе Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Құралдар тақтасын фокустау
\n' + + '
Windows немесе Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Төменгі деректемені фокустау
\n' + + '
Windows немесе Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Хабарландыруды белгілеу
\n' + + '
Windows немесе Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Мәтінмәндік құралдар тақтасын фокустау
\n' + + '
Windows, Linux немесе macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Навигация бөлектелетін немесе Төменгі деректеме элементінің жолындағы бірінші элемент жағдайында асты сызылатын\n' + + ' бірінші ПИ элементінен басталады.

\n' + + '\n' + + '

ПИ бөлімдері арасында навигациялау

\n' + + '\n' + + '

Бір ПИ бөлімінен келесісіне өту үшін Tab пернесін басыңыз.

\n' + + '\n' + + '

Бір ПИ бөлімінен алдыңғысына өту үшін Shift+Tab пернесін басыңыз.

\n' + + '\n' + + '

Осы ПИ бөлімдерінің Tab реті:

\n' + + '\n' + + '
    \n' + + '
  1. Мәзір жолағы
  2. \n' + + '
  3. Әрбір құралдар тақтасы тобы
  4. \n' + + '
  5. Бүйірлік жолақ
  6. \n' + + '
  7. Төменгі деректемедегі элемент жолы
  8. \n' + + '
  9. Төменгі деректемедегі сөздер санын ауыстыру түймесі
  10. \n' + + '
  11. Төменгі деректемедегі брендингтік сілтеме
  12. \n' + + '
  13. Төменгі деректемедегі редактор өлшемін өзгерту тұтқасы
  14. \n' + + '
\n' + + '\n' + + '

ПИ бөлімі көрсетілмесе, ол өткізіп жіберіледі.

\n' + + '\n' + + '

Төменгі деректемеде пернетақта навигациясының фокусы болса және бүйірлік жолақ көрінбесе, Shift+Tab тіркесімін басу әрекеті\n' + + ' фокусты соңғысы емес, бірінші құралдар тақтасы тобына жылжытады.

\n' + + '\n' + + '

ПИ бөлімдерінде навигациялау

\n' + + '\n' + + '

Бір ПИ элементінен келесісіне өту үшін Arrow (Көрсеткі) пернесін басыңыз.

\n' + + '\n' + + '

Left (Сол жақ) және Right (Оң жақ) көрсеткі пернелері

\n' + + '\n' + + '
    \n' + + '
  • мәзір жолағындағы мәзірлер арасында жылжыту.
  • \n' + + '
  • мәзірде ішкі мәзірді ашу.
  • \n' + + '
  • құралдар тақтасы тобындағы түймелер арасында жылжыту.
  • \n' + + '
  • төменгі деректеме элементінің жолындағы элементтер арасында жылжыту.
  • \n' + + '
\n' + + '\n' + + '

Down (Төмен) және Up (Жоғары) көрсеткі пернелері

\n' + + '\n' + + '
    \n' + + '
  • мәзірдегі мәзір элементтері арасында жылжыту.
  • \n' + + '
  • құралдар тақтасының ашылмалы мәзіріндегі мәзір элементтері арасында жылжыту.
  • \n' + + '
\n' + + '\n' + + '

Фокусталған ПИ бөліміндегі Arrow (Көрсеткі) пернелерінің циклі.

\n' + + '\n' + + '

Ашық мәзірді жабу үшін ішкі мәзірді ашып немесе ашылмалы мәзірді ашып, Esc пернесін басыңыз.

\n' + + '\n' + + '

Ағымдағы фокус белгілі бір ПИ бөлімінің «үстінде» болса, Esc пернесін басу әрекеті пернетақта\n' + + ' навигациясын толығымен жабады.

\n' + + '\n' + + '

Мәзір элементін немесе құралдар тақтасы түймесін орындау

\n' + + '\n' + + '

Қажетті мәзір элементі немесе құралдар тақтасы түймесі бөлектелген кезде, элементті орындау үшін Return (Қайтару), Enter (Енгізу)\n' + + ' немесе Space bar (Бос орын) пернесін басыңыз.

\n' + + '\n' + + '

Белгіленбеген диалог терезелерін навигациялау

\n' + + '\n' + + '

Белгіленбеген диалог терезелерінде диалог терезесі ашылған кезде бірінші интерактивті құрамдас фокусталады.

\n' + + '\n' + + '

Tab немесе Shift+Tab пернесін басу арқылы интерактивті диалог терезесінің құрамдастары арасында навигациялаңыз.

\n' + + '\n' + + '

Белгіленген диалог терезелерін навигациялау

\n' + + '\n' + + '

Белгіленген диалог терезелерінде диалог терезесі ашылған кезде қойынды мәзіріндегі бірінші түйме фокусталады.

\n' + + '\n' + + '

Tab немесе\n' + + ' Shift+Tab пернесін басу арқылы осы диалог терезесі қойындысының интерактивті құрамдастары арасында навигациялаңыз.

\n' + + '\n' + + '

Қойынды мәзірінің фокусын беру арқылы басқа диалог терезесінің қойындысына ауысып, тиісті Arrow (Көрсеткі)\n' + + ' пернесін басу арқылы қолжетімді қойындылар арасында айналдыруға болады.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ko_KR.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ko_KR.js new file mode 100644 index 0000000..e7c8e7f --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ko_KR.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ko_KR', +'

키보드 탐색 시작

\n' + + '\n' + + '
\n' + + '
메뉴 모음 포커스 표시
\n' + + '
Windows 또는 Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
도구 모음 포커스 표시
\n' + + '
Windows 또는 Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
푸터 포커스 표시
\n' + + '
Windows 또는 Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
알림 포커스
\n' + + '
Windows 또는 Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
컨텍스트 도구 모음에 포커스 표시
\n' + + '
Windows, Linux 또는 macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

첫 번째 UI 항목에서 탐색이 시작되며, 이때 첫 번째 항목이 강조 표시되거나 푸터 요소 경로에 있는\n' + + ' 경우 밑줄 표시됩니다.

\n' + + '\n' + + '

UI 섹션 간 탐색

\n' + + '\n' + + '

한 UI 섹션에서 다음 UI 섹션으로 이동하려면 Tab(탭)을 누릅니다.

\n' + + '\n' + + '

한 UI 섹션에서 이전 UI 섹션으로 돌아가려면 Shift+Tab(시프트+탭)을 누릅니다.

\n' + + '\n' + + '

이 UI 섹션의 Tab(탭) 순서는 다음과 같습니다.

\n' + + '\n' + + '
    \n' + + '
  1. 메뉴 바
  2. \n' + + '
  3. 각 도구 모음 그룹
  4. \n' + + '
  5. 사이드바
  6. \n' + + '
  7. 푸터의 요소 경로
  8. \n' + + '
  9. 푸터의 단어 수 토글 버튼
  10. \n' + + '
  11. 푸터의 브랜딩 링크
  12. \n' + + '
  13. 푸터의 에디터 크기 변경 핸들
  14. \n' + + '
\n' + + '\n' + + '

UI 섹션이 없는 경우 건너뛰기합니다.

\n' + + '\n' + + '

푸터에 키보드 탐색 포커스가 있고 사이드바는 보이지 않는 경우 Shift+Tab(시프트+탭)을 누르면\n' + + ' 포커스 표시가 마지막이 아닌 첫 번째 도구 모음 그룹으로 이동합니다.

\n' + + '\n' + + '

UI 섹션 내 탐색

\n' + + '\n' + + '

한 UI 요소에서 다음 UI 요소로 이동하려면 적절한 화살표 키를 누릅니다.

\n' + + '\n' + + '

왼쪽오른쪽 화살표 키의 용도:

\n' + + '\n' + + '
    \n' + + '
  • 메뉴 모음에서 메뉴 항목 사이를 이동합니다.
  • \n' + + '
  • 메뉴에서 하위 메뉴를 엽니다.
  • \n' + + '
  • 도구 모음 그룹에서 버튼 사이를 이동합니다.
  • \n' + + '
  • 푸터의 요소 경로에서 항목 간에 이동합니다.
  • \n' + + '
\n' + + '\n' + + '

아래 화살표 키의 용도:

\n' + + '\n' + + '
    \n' + + '
  • 메뉴에서 메뉴 항목 사이를 이동합니다.
  • \n' + + '
  • 도구 모음 팝업 메뉴에서 메뉴 항목 사이를 이동합니다.
  • \n' + + '
\n' + + '\n' + + '

화살표 키는 포커스 표시 UI 섹션 내에서 순환됩니다.

\n' + + '\n' + + '

열려 있는 메뉴, 열려 있는 하위 메뉴 또는 열려 있는 팝업 메뉴를 닫으려면 Esc 키를 누릅니다.

\n' + + '\n' + + "

현재 포커스 표시가 특정 UI 섹션 '상단'에 있는 경우 이때도 Esc 키를 누르면\n" + + ' 키보드 탐색이 완전히 종료됩니다.

\n' + + '\n' + + '

메뉴 항목 또는 도구 모음 버튼 실행

\n' + + '\n' + + '

원하는 메뉴 항목 또는 도구 모음 버튼이 강조 표시되어 있을 때 Return(리턴), Enter(엔터),\n' + + ' 또는 Space bar(스페이스바)를 눌러 해당 항목을 실행합니다.

\n' + + '\n' + + '

탭이 없는 대화 탐색

\n' + + '\n' + + '

탭이 없는 대화의 경우, 첫 번째 대화형 요소가 포커스 표시된 상태로 대화가 열립니다.

\n' + + '\n' + + '

대화형 요소들 사이를 이동할 때는 Tab(탭) 또는 Shift+Tab(시프트+탭)을 누릅니다.

\n' + + '\n' + + '

탭이 있는 대화 탐색

\n' + + '\n' + + '

탭이 있는 대화의 경우, 탭 메뉴에서 첫 번째 버튼이 포커스 표시된 상태로 대화가 열립니다.

\n' + + '\n' + + '

이 대화 탭의 대화형 요소들 사이를 이동할 때는 Tab(탭) 또는\n' + + ' Shift+Tab(시프트+탭)을 누릅니다.

\n' + + '\n' + + '

다른 대화 탭으로 이동하려면 탭 메뉴를 포커스 표시한 다음 적절한 화살표\n' + + ' 키를 눌러 사용 가능한 탭들을 지나 원하는 탭으로 이동합니다.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ms.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ms.js new file mode 100644 index 0000000..2c047bb --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ms.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ms', +'

Mulakan navigasi papan kekunci

\n' + + '\n' + + '
\n' + + '
Fokus bar Menu
\n' + + '
Windows atau Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Fokus Bar Alat
\n' + + '
Windows atau Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Fokus pengaki
\n' + + '
Windows atau Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Tumpu kepada pemberitahuan
\n' + + '
Windows atau Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Fokus bar alat kontekstual
\n' + + '
Windows, Linux atau macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Navigasi akan bermula pada item UI pertama, yang akan diserlahkan atau digaris bawah dalam saiz item pertama dalam\n' + + ' laluan elemen Pengaki.

\n' + + '\n' + + '

Navigasi antara bahagian UI

\n' + + '\n' + + '

Untuk bergerak dari satu bahagian UI ke yang seterusnya, tekan Tab.

\n' + + '\n' + + '

Untuk bergerak dari satu bahagian UI ke yang sebelumnya, tekan Shift+Tab.

\n' + + '\n' + + '

Tertib Tab bahagian UI ini ialah:

\n' + + '\n' + + '
    \n' + + '
  1. Bar menu
  2. \n' + + '
  3. Setiap kumpulan bar alat
  4. \n' + + '
  5. Bar sisi
  6. \n' + + '
  7. Laluan elemen dalam pengaki
  8. \n' + + '
  9. Butang togol kiraan perkataan dalam pengaki
  10. \n' + + '
  11. Pautan penjenamaan dalam pengaki
  12. \n' + + '
  13. Pemegang saiz semula editor dalam pengaki
  14. \n' + + '
\n' + + '\n' + + '

Jika bahagian UI tidak wujud, ia dilangkau.

\n' + + '\n' + + '

Jika pengaki mempunyai fokus navigasi papan kekunci dan tiada bar sisi kelihatan, menekan Shift+Tab\n' + + ' akan mengalihkan fokus ke kumpulan bar alat pertama, bukannya yang terakhir.

\n' + + '\n' + + '

Navigasi dalam bahagian UI

\n' + + '\n' + + '

Untuk bergerak dari satu elemen UI ke yang seterusnya, tekan kekunci Anak Panah yang bersesuaian.

\n' + + '\n' + + '

Kekunci anak panah Kiri dan Kanan

\n' + + '\n' + + '
    \n' + + '
  • bergerak antara menu dalam bar menu.
  • \n' + + '
  • membukan submenu dalam menu.
  • \n' + + '
  • bergerak antara butang dalam kumpulan bar alat.
  • \n' + + '
  • Laluan elemen dalam pengaki.
  • \n' + + '
\n' + + '\n' + + '

Kekunci anak panah Bawah dan Atas

\n' + + '\n' + + '
    \n' + + '
  • bergerak antara item menu dalam menu.
  • \n' + + '
  • bergerak antara item dalam menu timbul bar alat.
  • \n' + + '
\n' + + '\n' + + '

Kekunci Anak Panah berkitar dalam bahagian UI difokuskan.

\n' + + '\n' + + '

Untuk menutup menu buka, submenu terbuka atau menu timbul terbuka, tekan kekunci Esc.

\n' + + '\n' + + "

Jika fokus semasa berada di bahagian 'atas' bahagian UI tertentu, menekan kekunci Esc juga akan keluar daripada\n" + + ' navigasi papan kekunci sepenuhnya.

\n' + + '\n' + + '

Laksanakan item menu atau butang bar alat

\n' + + '\n' + + '

Apabila item menu atau butang bar alat yang diinginkan diserlahkan, tekan Return, Enter,\n' + + ' atau bar Space untuk melaksanakan item.

\n' + + '\n' + + '

Navigasi ke dialog tidak bertab

\n' + + '\n' + + '

Dalam dialog tidak bertab, komponen interaksi pertama difokuskan apabila dialog dibuka.

\n' + + '\n' + + '

Navigasi antara komponen dialog interaktif dengan menekan Tab atau Shift+Tab.

\n' + + '\n' + + '

Navigasi ke dialog bertab

\n' + + '\n' + + '

Dalam dialog bertab, butang pertama dalam menu tab difokuskan apabila dialog dibuka.

\n' + + '\n' + + '

Navigasi antara komponen interaktif tab dialog ini dengan menekan Tab atau\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Tukar kepada tab dialog lain dengan memfokuskan menu tab, kemudian menekan kekunci Anak Panah yang bersesuaian\n' + + ' untuk berkitar menerusi tab yang tersedia.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/nb_NO.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/nb_NO.js new file mode 100644 index 0000000..071e3f5 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/nb_NO.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.nb_NO', +'

Starte tastaturnavigering

\n' + + '\n' + + '
\n' + + '
Utheve menylinjen
\n' + + '
Windows eller Linux: Alt + F9
\n' + + '
macOS: ⌥F9
\n' + + '
Utheve verktøylinjen
\n' + + '
Windows eller Linux: Alt + F10
\n' + + '
macOS: ⌥F10
\n' + + '
Utheve bunnteksten
\n' + + '
Windows eller Linux: Alt + F11
\n' + + '
macOS: ⌥F11
\n' + + '
Fokuser på varselet
\n' + + '
Windows eller Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Utheve en kontekstuell verktøylinje
\n' + + '
Windows, Linux eller macOS: Ctrl + F9
\n' + + '
\n' + + '\n' + + '

Navigeringen starter ved det første grensesnittelementet, som utheves, eller understrekes når det gjelder det første elementet i\n' + + ' elementstien i bunnteksten.

\n' + + '\n' + + '

Navigere mellom grensesnittdeler

\n' + + '\n' + + '

Du kan bevege deg fra én grensesnittdel til den neste ved å trykke på tabulatortasten.

\n' + + '\n' + + '

Du kan bevege deg fra én grensesnittdel til den forrige ved å trykke på Shift + tabulatortasten.

\n' + + '\n' + + '

Rekkefølgen til tabulatortasten gjennom grensesnittdelene er:

\n' + + '\n' + + '
    \n' + + '
  1. Menylinjen
  2. \n' + + '
  3. Hver gruppe på verktøylinjen
  4. \n' + + '
  5. Sidestolpen
  6. \n' + + '
  7. Elementstien i bunnteksten
  8. \n' + + '
  9. Veksleknappen for ordantall i bunnteksten
  10. \n' + + '
  11. Merkelenken i bunnteksten
  12. \n' + + '
  13. Skaleringshåndtaket for redigeringsprogrammet i bunnteksten
  14. \n' + + '
\n' + + '\n' + + '

Hvis en grensesnittdel ikke er til stede, blir den hoppet over.

\n' + + '\n' + + '

Hvis tastaturnavigeringen har uthevet bunnteksten og det ikke finnes en synlig sidestolpe, kan du trykke på Shift + tabulatortasten\n' + + ' for å flytte fokuset til den første gruppen på verktøylinjen i stedet for den siste.

\n' + + '\n' + + '

Navigere innenfor grensesnittdeler

\n' + + '\n' + + '

Du kan bevege deg fra ett grensesnittelement til det neste ved å trykke på den aktuelle piltasten.

\n' + + '\n' + + '

De venstre og høyre piltastene

\n' + + '\n' + + '
    \n' + + '
  • beveger deg mellom menyer på menylinjen.
  • \n' + + '
  • åpner en undermeny i en meny.
  • \n' + + '
  • beveger deg mellom knapper i en gruppe på verktøylinjen.
  • \n' + + '
  • beveger deg mellom elementer i elementstien i bunnteksten.
  • \n' + + '
\n' + + '\n' + + '

Ned- og opp-piltastene

\n' + + '\n' + + '
    \n' + + '
  • beveger deg mellom menyelementer i en meny.
  • \n' + + '
  • beveger deg mellom elementer i en hurtigmeny på verktøylinjen.
  • \n' + + '
\n' + + '\n' + + '

Med piltastene kan du bevege deg innenfor den uthevede grensesnittdelen.

\n' + + '\n' + + '

Du kan lukke en åpen meny, en åpen undermeny eller en åpen hurtigmeny ved å klikke på Esc-tasten.

\n' + + '\n' + + '

Hvis det øverste nivået i en grensesnittdel er uthevet, kan du ved å trykke på Esc også avslutte\n' + + ' tastaturnavigeringen helt.

\n' + + '\n' + + '

Utføre et menyelement eller en knapp på en verktøylinje

\n' + + '\n' + + '

Når det ønskede menyelementet eller verktøylinjeknappen er uthevet, trykker du på Retur, Enter,\n' + + ' eller mellomromstasten for å utføre elementet.

\n' + + '\n' + + '

Navigere i dialogbokser uten faner

\n' + + '\n' + + '

I dialogbokser uten faner blir den første interaktive komponenten uthevet når dialogboksen åpnes.

\n' + + '\n' + + '

Naviger mellom interaktive komponenter i dialogboksen ved å trykke på tabulatortasten eller Shift + tabulatortasten.

\n' + + '\n' + + '

Navigere i fanebaserte dialogbokser

\n' + + '\n' + + '

I fanebaserte dialogbokser blir den første knappen i fanemenyen uthevet når dialogboksen åpnes.

\n' + + '\n' + + '

Naviger mellom interaktive komponenter i fanen ved å trykke på tabulatortasten eller\n' + + ' Shift + tabulatortasten.

\n' + + '\n' + + '

Veksle til en annen fane i dialogboksen ved å utheve fanemenyen, og trykk deretter på den aktuelle piltasten\n' + + ' for å bevege deg mellom de tilgjengelige fanene.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/nl.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/nl.js new file mode 100644 index 0000000..05c07ae --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/nl.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.nl', +'

Toetsenbordnavigatie starten

\n' + + '\n' + + '
\n' + + '
Focus op de menubalk instellen
\n' + + '
Windows of Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Focus op de werkbalk instellen
\n' + + '
Windows of Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Focus op de voettekst instellen
\n' + + '
Windows of Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Focus op de melding instellen
\n' + + '
Windows of Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Focus op een contextuele werkbalk instellen
\n' + + '
Windows, Linux of macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

De navigatie start bij het eerste UI-item, dat wordt gemarkeerd of onderstreept als het eerste item zich in\n' + + ' in het elementenpad van de voettekst bevindt.

\n' + + '\n' + + '

Navigeren tussen UI-secties

\n' + + '\n' + + '

Druk op Tab om naar de volgende UI-sectie te gaan.

\n' + + '\n' + + '

Druk op Shift+Tab om naar de vorige UI-sectie te gaan.

\n' + + '\n' + + '

De Tab-volgorde van deze UI-secties is:

\n' + + '\n' + + '
    \n' + + '
  1. Menubalk
  2. \n' + + '
  3. Elke werkbalkgroep
  4. \n' + + '
  5. Zijbalk
  6. \n' + + '
  7. Elementenpad in de voettekst
  8. \n' + + '
  9. Wisselknop voor aantal woorden in de voettekst
  10. \n' + + '
  11. Merkkoppeling in de voettekst
  12. \n' + + '
  13. Greep voor het wijzigen van het formaat van de editor in de voettekst
  14. \n' + + '
\n' + + '\n' + + '

Als een UI-sectie niet aanwezig is, wordt deze overgeslagen.

\n' + + '\n' + + '

Als de focus van de toetsenbordnavigatie is ingesteld op de voettekst en er geen zichtbare zijbalk is, kun je op Shift+Tab drukken\n' + + ' om de focus naar de eerste werkbalkgroep in plaats van de laatste te verplaatsen.

\n' + + '\n' + + '

Navigeren binnen UI-secties

\n' + + '\n' + + '

Druk op de pijltjestoets om naar het betreffende UI-element te gaan.

\n' + + '\n' + + '

Met de pijltjestoetsen Links en Rechts

\n' + + '\n' + + '
    \n' + + "
  • wissel je tussen menu's in de menubalk.
  • \n" + + '
  • open je een submenu in een menu.
  • \n' + + '
  • wissel je tussen knoppen in een werkbalkgroep.
  • \n' + + '
  • wissel je tussen items in het elementenpad in de voettekst.
  • \n' + + '
\n' + + '\n' + + '

Met de pijltjestoetsen Omlaag en Omhoog

\n' + + '\n' + + '
    \n' + + '
  • wissel je tussen menu-items in een menu.
  • \n' + + '
  • wissel je tussen items in een werkbalkpop-upmenu.
  • \n' + + '
\n' + + '\n' + + '

Met de pijltjestoetsen wissel je binnen de UI-sectie waarop de focus is ingesteld.

\n' + + '\n' + + '

Druk op de toets Esc om een geopend menu, submenu of pop-upmenu te sluiten.

\n' + + '\n' + + "

Als de huidige focus is ingesteld 'bovenaan' een bepaalde UI-sectie, kun je op de toets Esc drukken\n" + + ' om de toetsenbordnavigatie af te sluiten.

\n' + + '\n' + + '

Een menu-item of werkbalkknop uitvoeren

\n' + + '\n' + + '

Als het gewenste menu-item of de gewenste werkbalkknop is gemarkeerd, kun je op Return, Enter\n' + + ' of de spatiebalk drukken om het item uit te voeren.

\n' + + '\n' + + '

Navigeren in dialoogvensters zonder tabblad

\n' + + '\n' + + '

Als een dialoogvenster zonder tabblad wordt geopend, wordt de focus ingesteld op het eerste interactieve onderdeel.

\n' + + '\n' + + '

Je kunt navigeren tussen interactieve onderdelen van een dialoogvenster door op Tab of Shift+Tab te drukken.

\n' + + '\n' + + '

Navigeren in dialoogvensters met tabblad

\n' + + '\n' + + '

Als een dialoogvenster met tabblad wordt geopend, wordt de focus ingesteld op de eerste knop in het tabbladmenu.

\n' + + '\n' + + '

Je kunt navigeren tussen interactieve onderdelen van dit tabblad van het dialoogvenster door op Tab of\n' + + ' Shift+Tab te drukken.

\n' + + '\n' + + '

Je kunt overschakelen naar een ander tabblad van het dialoogvenster door de focus in te stellen op het tabbladmenu en vervolgens op de juiste pijltjestoets\n' + + ' te drukken om tussen de beschikbare tabbladen te wisselen.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pl.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pl.js new file mode 100644 index 0000000..e89f808 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pl.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.pl', +'

Początek nawigacji przy użyciu klawiatury

\n' + + '\n' + + '
\n' + + '
Ustaw fokus na pasek menu
\n' + + '
Windows lub Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Ustaw fokus na pasek narzędzi
\n' + + '
Windows lub Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Ustaw fokus na sekcję Footer
\n' + + '
Windows lub Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Skup się na powiadomieniu
\n' + + '
Windows lub Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Ustaw fokus na kontekstowy pasek narzędzi
\n' + + '
Windows, Linux lub macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Nawigacja zostanie rozpoczęta od pierwszego elementu interfejsu użytkownika, który jest podświetlony lub — w przypadku pierwszego elementu\n' + + ' w ścieżce elementów w sekcji Footer — podkreślony.

\n' + + '\n' + + '

Nawigacja pomiędzy sekcjami interfejsu użytkownika

\n' + + '\n' + + '

Aby przenieść się z danej sekcji interfejsu użytkownika do następnej, naciśnij Tab.

\n' + + '\n' + + '

Aby przenieść się z danej sekcji interfejsu użytkownika do poprzedniej, naciśnij Shift+Tab.

\n' + + '\n' + + '

Kolejność klawisza Tab w takich sekcjach interfejsu użytkownika jest następująca:

\n' + + '\n' + + '
    \n' + + '
  1. Pasek menu
  2. \n' + + '
  3. Każda grupa na pasku narzędzi
  4. \n' + + '
  5. Pasek boczny
  6. \n' + + '
  7. Ścieżka elementów w sekcji Footer
  8. \n' + + '
  9. Przycisk przełączania liczby słów w sekcji Footer
  10. \n' + + '
  11. Łącze brandujące w sekcji Footer
  12. \n' + + '
  13. Uchwyt zmiany rozmiaru edytora w sekcji Footer
  14. \n' + + '
\n' + + '\n' + + '

Jeżeli nie ma sekcji interfejsu użytkownika, jest to pomijane.

\n' + + '\n' + + '

Jeżeli na sekcji Footer jest ustawiony fokus nawigacji przy użyciu klawiatury i nie ma widocznego paska bocznego, naciśnięcie Shift+Tab\n' + + ' przenosi fokus na pierwszą grupę paska narzędzi, a nie na ostatnią.

\n' + + '\n' + + '

Nawigacja wewnątrz sekcji interfejsu użytkownika

\n' + + '\n' + + '

Aby przenieść się z danego elementu interfejsu użytkownika do następnego, naciśnij odpowiedni klawisz strzałki.

\n' + + '\n' + + '

Klawisze strzałek w prawo i w lewo służą do

\n' + + '\n' + + '
    \n' + + '
  • przenoszenia się pomiędzy menu na pasku menu,
  • \n' + + '
  • otwarcia podmenu w menu,
  • \n' + + '
  • przenoszenia się pomiędzy przyciskami w grupie paska narzędzi,
  • \n' + + '
  • przenoszenia się pomiędzy elementami w ścieżce elementów w sekcji Footer.
  • \n' + + '
\n' + + '\n' + + '

Klawisze strzałek w dół i w górę służą do

\n' + + '\n' + + '
    \n' + + '
  • przenoszenia się pomiędzy elementami menu w menu,
  • \n' + + '
  • przenoszenia się pomiędzy elementami w wyskakującym menu paska narzędzi.
  • \n' + + '
\n' + + '\n' + + '

Klawisze strzałek służą do przemieszczania się w sekcji interfejsu użytkownika z ustawionym fokusem.

\n' + + '\n' + + '

Aby zamknąć otwarte menu, otwarte podmenu lub otwarte menu wyskakujące, naciśnij klawisz Esc.

\n' + + '\n' + + '

Jeżeli fokus jest ustawiony na górze konkretnej sekcji interfejsu użytkownika, naciśnięcie klawisza Esc powoduje wyjście\n' + + ' z nawigacji przy użyciu klawiatury.

\n' + + '\n' + + '

Wykonanie elementu menu lub przycisku paska narzędzi

\n' + + '\n' + + '

Gdy podświetlony jest żądany element menu lub przycisk paska narzędzi, naciśnij klawisz Return, Enter\n' + + ' lub Spacja, aby go wykonać.

\n' + + '\n' + + '

Nawigacja po oknie dialogowym bez kart

\n' + + '\n' + + '

Gdy otwiera się okno dialogowe bez kart, fokus ustawiany jest na pierwszą interaktywną część okna.

\n' + + '\n' + + '

Pomiędzy interaktywnymi częściami okna dialogowego nawiguj, naciskając klawisze Tab lub Shift+Tab.

\n' + + '\n' + + '

Nawigacja po oknie dialogowym z kartami

\n' + + '\n' + + '

W przypadku okna dialogowego z kartami po otwarciu okna dialogowego fokus ustawiany jest na pierwszy przycisk w menu karty.

\n' + + '\n' + + '

Nawigację pomiędzy interaktywnymi częściami karty okna dialogowego prowadzi się poprzez naciskanie klawiszy Tab lub\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Przełączenie się na inną kartę okna dialogowego wykonuje się poprzez ustawienie fokusu na menu karty i naciśnięcie odpowiedniego klawisza strzałki\n' + + ' w celu przemieszczenia się pomiędzy dostępnymi kartami.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pt_BR.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pt_BR.js new file mode 100644 index 0000000..2938fcf --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pt_BR.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.pt_BR', +'

Iniciar navegação pelo teclado

\n' + + '\n' + + '
\n' + + '
Foco na barra de menus
\n' + + '
Windows ou Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Foco na barra de ferramentas
\n' + + '
Windows ou Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Foco no rodapé
\n' + + '
Windows ou Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Foco na notificação
\n' + + '
Windows ou Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Foco na barra de ferramentas contextual
\n' + + '
Windows, Linux ou macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

A navegação inicia no primeiro item da IU, que será destacado ou sublinhado no caso do primeiro item no\n' + + ' caminho do elemento Rodapé.

\n' + + '\n' + + '

Navegar entre seções da IU

\n' + + '\n' + + '

Para ir de uma seção da IU para a seguinte, pressione Tab.

\n' + + '\n' + + '

Para ir de uma seção da IU para a anterior, pressione Shift+Tab.

\n' + + '\n' + + '

A ordem de Tab destas seções da IU é:

\n' + + '\n' + + '
    \n' + + '
  1. Barra de menus
  2. \n' + + '
  3. Cada grupo da barra de ferramentas
  4. \n' + + '
  5. Barra lateral
  6. \n' + + '
  7. Caminho do elemento no rodapé
  8. \n' + + '
  9. Botão de alternar contagem de palavras no rodapé
  10. \n' + + '
  11. Link da marca no rodapé
  12. \n' + + '
  13. Alça de redimensionamento do editor no rodapé
  14. \n' + + '
\n' + + '\n' + + '

Se não houver uma seção da IU, ela será pulada.

\n' + + '\n' + + '

Se o rodapé tiver o foco da navegação pelo teclado e não houver uma barra lateral visível, pressionar Shift+Tab\n' + + ' move o foco para o primeiro grupo da barra de ferramentas, não para o último.

\n' + + '\n' + + '

Navegar dentro das seções da IU

\n' + + '\n' + + '

Para ir de um elemento da IU para o seguinte, pressione a Seta correspondente.

\n' + + '\n' + + '

As teclas de seta Esquerda e Direita

\n' + + '\n' + + '
    \n' + + '
  • movem entre menus na barra de menus.
  • \n' + + '
  • abrem um submenu em um menu.
  • \n' + + '
  • movem entre botões em um grupo da barra de ferramentas.
  • \n' + + '
  • movem entre itens no caminho do elemento do rodapé.
  • \n' + + '
\n' + + '\n' + + '

As teclas de seta Abaixo e Acima

\n' + + '\n' + + '
    \n' + + '
  • movem entre itens de menu em um menu.
  • \n' + + '
  • movem entre itens em um menu suspenso da barra de ferramentas.
  • \n' + + '
\n' + + '\n' + + '

As teclas de Seta alternam dentre a seção da IU em foco.

\n' + + '\n' + + '

Para fechar um menu aberto, um submenu aberto ou um menu suspenso aberto, pressione Esc.

\n' + + '\n' + + '

Se o foco atual estiver no ‘alto’ de determinada seção da IU, pressionar Esc também sai\n' + + ' totalmente da navegação pelo teclado.

\n' + + '\n' + + '

Executar um item de menu ou botão da barra de ferramentas

\n' + + '\n' + + '

Com o item de menu ou botão da barra de ferramentas desejado destacado, pressione Return, Enter,\n' + + ' ou a Barra de espaço para executar o item.

\n' + + '\n' + + '

Navegar por caixas de diálogo sem guias

\n' + + '\n' + + '

Em caixas de diálogo sem guias, o primeiro componente interativo recebe o foco quando a caixa de diálogo abre.

\n' + + '\n' + + '

Navegue entre componentes interativos de caixa de diálogo pressionando Tab ou Shift+Tab.

\n' + + '\n' + + '

Navegar por caixas de diálogo com guias

\n' + + '\n' + + '

Em caixas de diálogo com guias, o primeiro botão no menu da guia recebe o foco quando a caixa de diálogo abre.

\n' + + '\n' + + '

Navegue entre componentes interativos dessa guia da caixa de diálogo pressionando Tab ou\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Alterne para outra guia da caixa de diálogo colocando o foco no menu da guia e pressionando a Seta\n' + + ' adequada para percorrer as guias disponíveis.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pt_PT.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pt_PT.js new file mode 100644 index 0000000..03da3d6 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pt_PT.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.pt_PT', +'

Iniciar navegação com teclado

\n' + + '\n' + + '
\n' + + '
Foco na barra de menu
\n' + + '
Windows ou Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Foco na barra de ferramentas
\n' + + '
Windows ou Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Foco no rodapé
\n' + + '
Windows ou Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Focar a notificação
\n' + + '
Windows ou Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Foco numa barra de ferramentas contextual
\n' + + '
Windows, Linux ou macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

A navegação começará no primeiro item de IU, que estará realçado ou sublinhado, no caso do primeiro item no\n' + + ' caminho do elemento do rodapé.

\n' + + '\n' + + '

Navegar entre secções de IU

\n' + + '\n' + + '

Para se mover de uma secção de IU para a seguinte, prima Tab.

\n' + + '\n' + + '

Para se mover de uma secção de IU para a anterior, prima Shift+Tab.

\n' + + '\n' + + '

A ordem de tabulação destas secções de IU é:

\n' + + '\n' + + '
    \n' + + '
  1. Barra de menu
  2. \n' + + '
  3. Cada grupo da barra de ferramentas
  4. \n' + + '
  5. Barra lateral
  6. \n' + + '
  7. Caminho do elemento no rodapé
  8. \n' + + '
  9. Botão de alternar da contagem de palavras no rodapé
  10. \n' + + '
  11. Ligação da marca no rodapé
  12. \n' + + '
  13. Alça de redimensionamento do editor no rodapé
  14. \n' + + '
\n' + + '\n' + + '

Se uma secção de IU não estiver presente, é ignorada.

\n' + + '\n' + + '

Se o rodapé tiver foco de navegação com teclado e não existir uma barra lateral visível, premir Shift+Tab\n' + + ' move o foco para o primeiro grupo da barra de ferramentas e não para o último.

\n' + + '\n' + + '

Navegar nas secções de IU

\n' + + '\n' + + '

Para se mover de um elemento de IU para o seguinte, prima a tecla de seta adequada.

\n' + + '\n' + + '

As teclas de seta Para a esquerda e Para a direita

\n' + + '\n' + + '
    \n' + + '
  • movem-se entre menus na barra de menu.
  • \n' + + '
  • abrem um submenu num menu.
  • \n' + + '
  • movem-se entre botões num grupo da barra de ferramentas.
  • \n' + + '
  • movem-se entre itens no caminho do elemento do rodapé.
  • \n' + + '
\n' + + '\n' + + '

As teclas de seta Para cima e Para baixo

\n' + + '\n' + + '
    \n' + + '
  • movem-se entre itens de menu num menu.
  • \n' + + '
  • movem-se entre itens num menu de pop-up da barra de ferramentas.
  • \n' + + '
\n' + + '\n' + + '

As teclas de seta deslocam-se ciclicamente na secção de IU em foco.

\n' + + '\n' + + '

Para fechar um menu aberto, um submenu aberto ou um menu de pop-up aberto, prima a tecla Esc.

\n' + + '\n' + + '

Se o foco atual estiver no "topo" de determinada secção de IU, premir a tecla Esc também fecha\n' + + ' completamente a navegação com teclado.

\n' + + '\n' + + '

Executar um item de menu ou botão da barra de ferramentas

\n' + + '\n' + + '

Quando o item de menu ou o botão da barra de ferramentas pretendido estiver realçado, prima Retrocesso, Enter\n' + + ' ou a Barra de espaço para executar o item.

\n' + + '\n' + + '

Navegar em diálogos sem separadores

\n' + + '\n' + + '

Nos diálogos sem separadores, o primeiro componente interativo fica em foco quando o diálogo abre.

\n' + + '\n' + + '

Navegue entre componentes interativos do diálogo, premindo Tab ou Shift+Tab.

\n' + + '\n' + + '

Navegar em diálogos com separadores

\n' + + '\n' + + '

Nos diálogos com separadores, o primeiro botão no menu do separador fica em foco quando o diálogo abre.

\n' + + '\n' + + '

Navegue entre os componentes interativos deste separador do diálogo, premindo Tab ou\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Mude para outro separador do diálogo colocando o menu do separador em foco e, em seguida, premindo a tecla de seta\n' + + ' adequada para se deslocar ciclicamente pelos separadores disponíveis.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ro.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ro.js new file mode 100644 index 0000000..38d3441 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ro.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ro', +'

Începeți navigarea de la tastatură

\n' + + '\n' + + '
\n' + + '
Focalizare pe bara de meniu
\n' + + '
Windows sau Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Focalizare pe bara de instrumente
\n' + + '
Windows sau Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Focalizare pe subsol
\n' + + '
Windows sau Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Focalizare pe notificare
\n' + + '
Windows sau Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Focalizare pe o bară de instrumente contextuală
\n' + + '
Windows, Linux sau macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Navigarea va începe de la primul element al interfeței cu utilizatorul, care va fi evidențiat sau subliniat în cazul primului element din\n' + + ' calea elementului Subsol.

\n' + + '\n' + + '

Navigați între secțiunile interfeței cu utilizatorul

\n' + + '\n' + + '

Pentru a trece de la o secțiune a interfeței cu utilizatorul la alta, apăsați Tab.

\n' + + '\n' + + '

Pentru a trece de la o secțiune a interfeței cu utilizatorul la cea anterioară, apăsați Shift+Tab.

\n' + + '\n' + + '

Ordinea cu Tab a acestor secțiuni ale interfeței cu utilizatorul este următoarea:

\n' + + '\n' + + '
    \n' + + '
  1. Bara de meniu
  2. \n' + + '
  3. Fiecare grup de bare de instrumente
  4. \n' + + '
  5. Bara laterală
  6. \n' + + '
  7. Calea elementului în subsol
  8. \n' + + '
  9. Buton de comutare a numărului de cuvinte în subsol
  10. \n' + + '
  11. Link de branding în subsol
  12. \n' + + '
  13. Mâner de redimensionare a editorului în subsol
  14. \n' + + '
\n' + + '\n' + + '

În cazul în care o secțiune a interfeței cu utilizatorul nu este prezentă, aceasta este omisă.

\n' + + '\n' + + '

În cazul în care subsolul are focalizarea navigației asupra tastaturii și nu există o bară laterală vizibilă, apăsarea butonului Shift+Tab\n' + + ' mută focalizarea pe primul grup de bare de instrumente, nu pe ultimul.

\n' + + '\n' + + '

Navigați în secțiunile interfeței cu utilizatorul

\n' + + '\n' + + '

Pentru a trece de la un element de interfață cu utilizatorul la următorul, apăsați tasta cu săgeata corespunzătoare.

\n' + + '\n' + + '

Tastele cu săgeți către stânga și dreapta

\n' + + '\n' + + '
    \n' + + '
  • navighează între meniurile din bara de meniuri.
  • \n' + + '
  • deschid un sub-meniu dintr-un meniu.
  • \n' + + '
  • navighează între butoanele dintr-un grup de bare de instrumente.
  • \n' + + '
  • navighează între elementele din calea elementelor subsolului.
  • \n' + + '
\n' + + '\n' + + '

Tastele cu săgeți în sus și în jos

\n' + + '\n' + + '
    \n' + + '
  • navighează între elementele de meniu dintr-un meniu.
  • \n' + + '
  • navighează între elementele unui meniu pop-up din bara de instrumente.
  • \n' + + '
\n' + + '\n' + + '

Tastele cu săgeți navighează în cadrul secțiunii interfeței cu utilizatorul asupra căreia se focalizează.

\n' + + '\n' + + '

Pentru a închide un meniu deschis, un sub-meniu deschis sau un meniu pop-up deschis, apăsați tasta Esc.

\n' + + '\n' + + '

Dacă focalizarea curentă este asupra „părții superioare” a unei anumite secțiuni a interfeței cu utilizatorul, prin apăsarea tastei Esc se iese, de asemenea,\n' + + ' în întregime din navigarea de la tastatură.

\n' + + '\n' + + '

Executarea unui element de meniu sau a unui buton din bara de instrumente

\n' + + '\n' + + '

Atunci când elementul de meniu dorit sau butonul dorit din bara de instrumente este evidențiat, apăsați Return, Enter,\n' + + ' sau bara de spațiu pentru a executa elementul.

\n' + + '\n' + + '

Navigarea de dialoguri fără file

\n' + + '\n' + + '

În dialogurile fără file, prima componentă interactivă beneficiază de focalizare la deschiderea dialogului.

\n' + + '\n' + + '

Navigați între componentele dialogului interactiv apăsând Tab sau Shift+Tab.

\n' + + '\n' + + '

Navigarea de dialoguri cu file

\n' + + '\n' + + '

În dialogurile cu file, primul buton din meniul cu file beneficiază de focalizare la deschiderea dialogului.

\n' + + '\n' + + '

Navigați între componentele interactive ale acestei file de dialog apăsând Tab sau\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Treceți la o altă filă de dialog focalizând asupra meniului cu file și apoi apăsând săgeata corespunzătoare\n' + + ' pentru a parcurge filele disponibile.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ru.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ru.js new file mode 100644 index 0000000..d310f54 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ru.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.ru', +'

Начните управление с помощью клавиатуры

\n' + + '\n' + + '
\n' + + '
Фокус на панели меню
\n' + + '
Windows или Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Фокус на панели инструментов
\n' + + '
Windows или Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Фокус на нижнем колонтитуле
\n' + + '
Windows или Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Фокус на уведомлении
\n' + + '
Windows или Linux: Alt+12
\n' + + '
macOS: ⌥F12
\n' + + '
Фокус на контекстной панели инструментов
\n' + + '
Windows, Linux или macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Первый доступный для управления элемент интерфейса будет выделен цветом или подчеркнут (если он находится\n' + + ' в пути элементов нижнего колонтитула).

\n' + + '\n' + + '

Переход между разделами пользовательского интерфейса

\n' + + '\n' + + '

Чтобы перейти из текущего раздела интерфейса в следующий, нажмите Tab.

\n' + + '\n' + + '

Чтобы перейти из текущего раздела интерфейса в предыдущий, нажмите Shift+Tab.

\n' + + '\n' + + '

Вкладки разделов интерфейса расположены в следующем порядке:

\n' + + '\n' + + '
    \n' + + '
  1. Панель меню
  2. \n' + + '
  3. Группы панели инструментов
  4. \n' + + '
  5. Боковая панель
  6. \n' + + '
  7. Путь элементов нижнего колонтитула
  8. \n' + + '
  9. Подсчет слов/символов в нижнем колонтитуле
  10. \n' + + '
  11. Брендовая ссылка в нижнем колонтитуле
  12. \n' + + '
  13. Угол для изменения размера окна редактора
  14. \n' + + '
\n' + + '\n' + + '

Если раздел интерфейса отсутствует, он пропускается.

\n' + + '\n' + + '

Если при управлении с клавиатуры фокус находится на нижнем колонтитуле, а видимая боковая панель отсутствует, то при нажатии сочетания клавиш Shift+Tab\n' + + ' фокус переносится на первую группу панели инструментов, а не на последнюю.

\n' + + '\n' + + '

Переход между элементами внутри разделов пользовательского интерфейса

\n' + + '\n' + + '

Чтобы перейти от текущего элемента интерфейса к следующему, нажмите соответствующую клавишу со стрелкой.

\n' + + '\n' + + '

Клавиши со стрелками влево и вправо позволяют

\n' + + '\n' + + '
    \n' + + '
  • перемещаться между разными меню в панели меню.
  • \n' + + '
  • открывать разделы меню.
  • \n' + + '
  • перемещаться между кнопками в группе панели инструментов.
  • \n' + + '
  • перемещаться между элементами в пути элементов нижнего колонтитула.
  • \n' + + '
\n' + + '\n' + + '

Клавиши со стрелками вниз и вверх позволяют

\n' + + '\n' + + '
    \n' + + '
  • перемещаться между элементами одного меню.
  • \n' + + '
  • перемещаться между элементами всплывающего меню в панели инструментов.
  • \n' + + '
\n' + + '\n' + + '

При использовании клавиш со стрелками вы будете циклически перемещаться по элементам в пределах выбранного раздела интерфейса.

\n' + + '\n' + + '

Чтобы закрыть открытое меню, его раздел или всплывающее меню, нажмите клавишу Esc.

\n' + + '\n' + + '

Если фокус находится наверху какого-либо раздела интерфейса, нажатие клавиши Esc также приведет\n' + + ' к выходу из режима управления с помощью клавиатуры.

\n' + + '\n' + + '

Использование элемента меню или кнопки на панели инструментов

\n' + + '\n' + + '

Когда элемент меню или кнопка панели инструментов будут выделены, нажмите Return, Enter\n' + + ' или Space, чтобы их активировать.

\n' + + '\n' + + '

Управление в диалоговом окне без вкладок

\n' + + '\n' + + '

При открытии диалогового окна без вкладок фокус переносится на первый интерактивный компонент.

\n' + + '\n' + + '

Для перехода между интерактивными компонентами диалогового окна нажимайте Tab или Shift+Tab.

\n' + + '\n' + + '

Управление в диалоговом окне с вкладками

\n' + + '\n' + + '

При открытии диалогового окна с вкладками фокус переносится на первую кнопку в меню вкладок.

\n' + + '\n' + + '

Для перехода между интерактивными компонентами этой вкладки диалогового окна нажимайте Tab или\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Для перехода на другую вкладку диалогового окна переместите фокус на меню вкладок, а затем используйте клавиши со стрелками\n' + + ' для циклического переключения между доступными вкладками.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sk.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sk.js new file mode 100644 index 0000000..60cc628 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sk.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.sk', +'

Začíname s navigáciou pomocou klávesnice

\n' + + '\n' + + '
\n' + + '
Prejsť na panel s ponukami
\n' + + '
Windows alebo Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Prejsť na panel nástrojov
\n' + + '
Windows alebo Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Prejsť na pätičku
\n' + + '
Windows alebo Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Zaostriť na oznámenie
\n' + + '
Windows alebo Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Prejsť na kontextový panel nástrojov
\n' + + '
Windows, Linux alebo macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Navigácia začne pri prvej položke používateľského rozhrania, ktorá bude zvýraznená alebo v prípade prvej položky\n' + + ' cesty k pätičke podčiarknutá.

\n' + + '\n' + + '

Navigácia medzi časťami používateľského rozhrania

\n' + + '\n' + + '

Ak sa chcete posunúť z jednej časti používateľského rozhrania do druhej, stlačte tlačidlo Tab.

\n' + + '\n' + + '

Ak sa chcete posunúť z jednej časti používateľského rozhrania do predchádzajúcej, stlačte tlačidlá Shift + Tab.

\n' + + '\n' + + '

Poradie prepínania medzi týmito časťami používateľského rozhrania pri stláčaní tlačidla Tab:

\n' + + '\n' + + '
    \n' + + '
  1. Panel s ponukou
  2. \n' + + '
  3. Každá skupina panela nástrojov
  4. \n' + + '
  5. Bočný panel
  6. \n' + + '
  7. Cesta k prvku v pätičke
  8. \n' + + '
  9. Prepínač počtu slov v pätičke
  10. \n' + + '
  11. Odkaz na informácie o značke v pätičke
  12. \n' + + '
  13. Úchyt na zmenu veľkosti editora v pätičke
  14. \n' + + '
\n' + + '\n' + + '

Ak nejaká časť používateľského rozhrania nie je prítomná, preskočí sa.

\n' + + '\n' + + '

Ak je pätička vybratá na navigáciu pomocou klávesnice a nie je viditeľný bočný panel, stlačením klávesov Shift+Tab\n' + + ' prejdete na prvú skupinu panela nástrojov, nie na poslednú.

\n' + + '\n' + + '

Navigácia v rámci častí používateľského rozhrania

\n' + + '\n' + + '

Ak sa chcete posunúť z jedného prvku používateľského rozhrania na ďalší, stlačte príslušný kláves so šípkou.

\n' + + '\n' + + '

Klávesy so šípkami doľava a doprava

\n' + + '\n' + + '
    \n' + + '
  • umožňujú presun medzi ponukami na paneli ponúk,
  • \n' + + '
  • otvárajú podponuku v rámci ponuky,
  • \n' + + '
  • umožňujú presun medzi tlačidlami v skupine panelov nástrojov,
  • \n' + + '
  • umožňujú presun medzi položkami cesty prvku v pätičke.
  • \n' + + '
\n' + + '\n' + + '

Klávesy so šípkami dole a hore

\n' + + '\n' + + '
    \n' + + '
  • umožňujú presun medzi položkami ponuky,
  • \n' + + '
  • umožňujú presun medzi položkami v kontextovej ponuke panela nástrojov.
  • \n' + + '
\n' + + '\n' + + '

Klávesy so šípkami vykonávajú prepínanie v rámci vybranej časti používateľského rozhrania.

\n' + + '\n' + + '

Ak chcete zatvoriť otvorenú ponuku, otvorenú podponuku alebo otvorenú kontextovú ponuku, stlačte kláves Esc.

\n' + + '\n' + + '

Ak je aktuálne vybratá horná časť konkrétneho používateľského rozhrania, stlačením klávesu Esc úplne ukončíte tiež\n' + + ' navigáciu pomocou klávesnice.

\n' + + '\n' + + '

Vykonanie príkazu položky ponuky alebo tlačidla panela nástrojov

\n' + + '\n' + + '

Keď je zvýraznená požadovaná položka ponuky alebo tlačidlo panela nástrojov, stlačením klávesov Return, Enter\n' + + ' alebo medzerníka vykonáte príslušný príkaz položky.

\n' + + '\n' + + '

Navigácia v dialógových oknách bez záložiek

\n' + + '\n' + + '

Pri otvorení dialógových okien bez záložiek prejdete na prvý interaktívny komponent.

\n' + + '\n' + + '

Medzi interaktívnymi dialógovými komponentmi môžete prechádzať stlačením klávesov Tab alebo Shift+Tab.

\n' + + '\n' + + '

Navigácia v dialógových oknách so záložkami

\n' + + '\n' + + '

Pri otvorení dialógových okien so záložkami prejdete na prvé tlačidlo v ponuke záložiek.

\n' + + '\n' + + '

Medzi interaktívnymi komponentmi tejto dialógovej záložky môžete prechádzať stlačením klávesov Tab alebo\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Ak chcete prepnúť na ďalšiu záložku dialógového okna, prejdite do ponuky záložiek a potom môžete stlačením príslušného klávesu so šípkou\n' + + ' prepínať medzi dostupnými záložkami.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sl_SI.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sl_SI.js new file mode 100644 index 0000000..2b25f5a --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sl_SI.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.sl_SI', +'

Začetek krmarjenja s tipkovnico

\n' + + '\n' + + '
\n' + + '
Fokus na menijsko vrstico
\n' + + '
Windows ali Linux: Alt + F9
\n' + + '
macOS: ⌥F9
\n' + + '
Fokus na orodno vrstico
\n' + + '
Windows ali Linux: Alt + F10
\n' + + '
macOS: ⌥F10
\n' + + '
Fokus na nogo
\n' + + '
Windows ali Linux: Alt + F11
\n' + + '
macOS: ⌥F11
\n' + + '
Označitev obvestila
\n' + + '
Windows ali Linux: Alt + F12
\n' + + '
macOS: ⌥F12
\n' + + '
Fokus na kontekstualno orodno vrstico
\n' + + '
Windows, Linux ali macOS: Ctrl + F9
\n' + + '
\n' + + '\n' + + '

Krmarjenje se bo začelo s prvim elementom uporabniškega vmesnika, ki bo izpostavljena ali podčrtan, če gre za prvi element na\n' + + ' poti do elementa noge.

\n' + + '\n' + + '

Krmarjenje med razdelki uporabniškega vmesnika

\n' + + '\n' + + '

Če se želite pomakniti z enega dela uporabniškega vmesnika na naslednjega, pritisnite tabulatorko.

\n' + + '\n' + + '

Če se želite pomakniti z enega dela uporabniškega vmesnika na prejšnjega, pritisnite shift + tabulatorko.

\n' + + '\n' + + '

Zaporedje teh razdelkov uporabniškega vmesnika, ko pritiskate tabulatorko, je:

\n' + + '\n' + + '
    \n' + + '
  1. Menijska vrstica
  2. \n' + + '
  3. Posamezne skupine orodne vrstice
  4. \n' + + '
  5. Stranska vrstica
  6. \n' + + '
  7. Pod do elementa v nogi
  8. \n' + + '
  9. Gumb za preklop štetja besed v nogi
  10. \n' + + '
  11. Povezava do blagovne znamke v nogi
  12. \n' + + '
  13. Ročaj za spreminjanje velikosti urejevalnika v nogi
  14. \n' + + '
\n' + + '\n' + + '

Če razdelek uporabniškega vmesnika ni prisoten, je preskočen.

\n' + + '\n' + + '

Če ima noga fokus za krmarjenje s tipkovnico in ni vidne stranske vrstice, s pritiskom na shift + tabulatorko\n' + + ' fokus premaknete na prvo skupino orodne vrstice, ne zadnjo.

\n' + + '\n' + + '

Krmarjenje v razdelkih uporabniškega vmesnika

\n' + + '\n' + + '

Če se želite premakniti z enega elementa uporabniškega vmesnika na naslednjega, pritisnite ustrezno puščično tipko.

\n' + + '\n' + + '

Leva in desna puščična tipka

\n' + + '\n' + + '
    \n' + + '
  • omogočata premikanje med meniji v menijski vrstici.
  • \n' + + '
  • odpreta podmeni v meniju.
  • \n' + + '
  • omogočata premikanje med gumbi v skupini orodne vrstice.
  • \n' + + '
  • omogočata premikanje med elementi na poti do elementov noge.
  • \n' + + '
\n' + + '\n' + + '

Spodnja in zgornja puščična tipka

\n' + + '\n' + + '
    \n' + + '
  • omogočata premikanje med elementi menija.
  • \n' + + '
  • omogočata premikanje med elementi v pojavnem meniju orodne vrstice.
  • \n' + + '
\n' + + '\n' + + '

Puščične tipke omogočajo kroženje znotraj razdelka uporabniškega vmesnika, na katerem je fokus.

\n' + + '\n' + + '

Če želite zapreti odprt meni, podmeni ali pojavni meni, pritisnite tipko Esc.

\n' + + '\n' + + '

Če je trenutni fokus na »vrhu« določenega razdelka uporabniškega vmesnika, s pritiskom tipke Esc zaprete\n' + + ' tudi celotno krmarjenje s tipkovnico.

\n' + + '\n' + + '

Izvajanje menijskega elementa ali gumba orodne vrstice

\n' + + '\n' + + '

Ko je označen želeni menijski element ali orodja vrstica, pritisnite vračalko, Enter\n' + + ' ali preslednico, da izvedete element.

\n' + + '\n' + + '

Krmarjenje po pogovornih oknih brez zavihkov

\n' + + '\n' + + '

Ko odprete pogovorno okno brez zavihkov, ima fokus prva interaktivna komponenta.

\n' + + '\n' + + '

Med interaktivnimi komponentami pogovornega okna se premikate s pritiskom tabulatorke ali kombinacije tipke shift + tabulatorke.

\n' + + '\n' + + '

Krmarjenje po pogovornih oknih z zavihki

\n' + + '\n' + + '

Ko odprete pogovorno okno z zavihki, ima fokus prvi gumb v meniju zavihka.

\n' + + '\n' + + '

Med interaktivnimi komponentami tega zavihka pogovornega okna se premikate s pritiskom tabulatorke ali\n' + + ' kombinacije tipke shift + tabulatorke.

\n' + + '\n' + + '

Na drug zavihek pogovornega okna preklopite tako, da fokus prestavite na meni zavihka in nato pritisnete ustrezno puščično\n' + + ' tipko, da se pomaknete med razpoložljivimi zavihki.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sv_SE.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sv_SE.js new file mode 100644 index 0000000..c30f2f2 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sv_SE.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.sv_SE', +'

Påbörja tangentbordsnavigering

\n' + + '\n' + + '
\n' + + '
Fokusera på menyraden
\n' + + '
Windows eller Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Fokusera på verktygsraden
\n' + + '
Windows eller Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Fokusera på verktygsraden
\n' + + '
Windows eller Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Fokusera aviseringen
\n' + + '
Windows eller Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Fokusera på en snabbverktygsrad
\n' + + '
Windows, Linux eller macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Navigeringen börjar vid det första gränssnittsobjektet, vilket är markerat eller understruket om det gäller det första objektet i\n' + + ' sidfotens elementsökväg.

\n' + + '\n' + + '

Navigera mellan UI-avsnitt

\n' + + '\n' + + '

Flytta från ett UI-avsnitt till nästa genom att trycka på Tabb.

\n' + + '\n' + + '

Flytta från ett UI-avsnitt till det föregående genom att trycka på Skift+Tabb.

\n' + + '\n' + + '

Tabb-ordningen för dessa UI-avsnitt är:

\n' + + '\n' + + '
    \n' + + '
  1. Menyrad
  2. \n' + + '
  3. Varje verktygsradsgrupp
  4. \n' + + '
  5. Sidoruta
  6. \n' + + '
  7. Elementsökväg i sidfoten
  8. \n' + + '
  9. Växlingsknapp för ordantal i sidfoten
  10. \n' + + '
  11. Varumärkeslänk i sidfoten
  12. \n' + + '
  13. Storlekshandtag för redigeraren i sidfoten
  14. \n' + + '
\n' + + '\n' + + '

Om ett UI-avsnitt inte finns hoppas det över.

\n' + + '\n' + + '

Om sidfoten har fokus på tangentbordsnavigering, och det inte finns någon synlig sidoruta, flyttas fokus till den första verktygsradsgruppen\n' + + ' när du trycker på Skift+Tabb, inte till den sista.

\n' + + '\n' + + '

Navigera i UI-avsnitt

\n' + + '\n' + + '

Flytta från ett UI-element till nästa genom att trycka på motsvarande piltangent.

\n' + + '\n' + + '

Vänsterpil och högerpil

\n' + + '\n' + + '
    \n' + + '
  • flytta mellan menyer på menyraden.
  • \n' + + '
  • öppna en undermeny på en meny.
  • \n' + + '
  • flytta mellan knappar i en verktygsradgrupp.
  • \n' + + '
  • flytta mellan objekt i sidfotens elementsökväg.
  • \n' + + '
\n' + + '\n' + + '

Nedpil och uppil

\n' + + '\n' + + '
    \n' + + '
  • flytta mellan menyalternativ på en meny.
  • \n' + + '
  • flytta mellan alternativ på en popup-meny på verktygsraden.
  • \n' + + '
\n' + + '\n' + + '

Piltangenterna cirkulerar inom det fokuserade UI-avsnittet.

\n' + + '\n' + + '

Tryck på Esc-tangenten om du vill stänga en öppen meny, undermeny eller popup-meny.

\n' + + '\n' + + '

Om det aktuella fokuset är högst upp i ett UI-avsnitt avlutas även tangentbordsnavigeringen helt när\n' + + ' du trycker på Esc-tangenten.

\n' + + '\n' + + '

Köra ett menyalternativ eller en verktygfältsknapp

\n' + + '\n' + + '

När menyalternativet eller verktygsradsknappen är markerad trycker du på Retur, Enter\n' + + ' eller blanksteg för att köra alternativet.

\n' + + '\n' + + '

Navigera i dialogrutor utan flikar

\n' + + '\n' + + '

I dialogrutor utan flikar är den första interaktiva komponenten i fokus när dialogrutan öppnas.

\n' + + '\n' + + '

Navigera mellan interaktiva dialogkomponenter genom att trycka på Tabb eller Skift+Tabb.

\n' + + '\n' + + '

Navigera i dialogrutor med flikar

\n' + + '\n' + + '

I dialogrutor utan flikar är den första knappen på flikmenyn i fokus när dialogrutan öppnas.

\n' + + '\n' + + '

Navigera mellan interaktiva komponenter på dialogrutefliken genom att trycka på Tabb eller\n' + + ' Skift+Tabb.

\n' + + '\n' + + '

Växla till en annan dialogruta genom att fokusera på flikmenyn och sedan trycka på motsvarande piltangent\n' + + ' för att cirkulera mellan de tillgängliga flikarna.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/th_TH.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/th_TH.js new file mode 100644 index 0000000..562fe7a --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/th_TH.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.th_TH', +'

เริ่มต้นการนำทางด้วยแป้นพิมพ์

\n' + + '\n' + + '
\n' + + '
โฟกัสที่แถบเมนู
\n' + + '
Windows หรือ Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
โฟกัสที่แถบเครื่องมือ
\n' + + '
Windows หรือ Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
โฟกัสที่ส่วนท้าย
\n' + + '
Windows หรือ Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
โฟกัสไปที่การแจ้งเตือน
\n' + + '
Windows หรือ Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
โฟกัสที่แถบเครื่องมือตามบริบท
\n' + + '
Windows, Linux หรือ macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

การนำทางจะเริ่มที่รายการ UI แรก ซึ่งจะมีการไฮไลต์หรือขีดเส้นใต้ไว้ในกรณีที่รายการแรกอยู่ใน\n' + + ' พาธองค์ประกอบส่วนท้าย

\n' + + '\n' + + '

การนำทางระหว่างส่วนต่างๆ ของ UI

\n' + + '\n' + + '

ในการย้ายจากส่วน UI หนึ่งไปยังส่วนถัดไป ให้กด Tab

\n' + + '\n' + + '

ในการย้ายจากส่วน UI หนึ่งไปยังส่วนก่อนหน้า ให้กด Shift+Tab

\n' + + '\n' + + '

ลำดับแท็บของส่วนต่างๆ ของ UI คือ:

\n' + + '\n' + + '
    \n' + + '
  1. แถบเมนู
  2. \n' + + '
  3. แต่ละกลุ่มแถบเครื่องมือ
  4. \n' + + '
  5. แถบข้าง
  6. \n' + + '
  7. พาธองค์ประกอบในส่วนท้าย
  8. \n' + + '
  9. ปุ่มสลับเปิด/ปิดจำนวนคำในส่วนท้าย
  10. \n' + + '
  11. ลิงก์ชื่อแบรนด์ในส่วนท้าย
  12. \n' + + '
  13. จุดจับปรับขนาดของตัวแก้ไขในส่วนท้าย
  14. \n' + + '
\n' + + '\n' + + '

หากส่วน UI ไม่ปรากฏ แสดงว่าถูกข้ามไป

\n' + + '\n' + + '

หากส่วนท้ายมีการโฟกัสการนำทางแป้นพิมพ์และไม่มีแถบข้างปรากฏ การกด Shift+Tab\n' + + ' จะย้ายการโฟกัสไปที่กลุ่มแถบเครื่องมือแรก ไม่ใช่สุดท้าย

\n' + + '\n' + + '

การนำทางภายในส่วนต่างๆ ของ UI

\n' + + '\n' + + '

ในการย้ายจากองค์ประกอบ UI หนึ่งไปยังองค์ประกอบส่วนถัดไป ให้กดปุ่มลูกศรที่เหมาะสม

\n' + + '\n' + + '

ปุ่มลูกศรซ้ายและขวา

\n' + + '\n' + + '
    \n' + + '
  • ย้ายไปมาระหว่างเมนูต่างๆ ในแถบเมนู
  • \n' + + '
  • เปิดเมนูย่อยในเมนู
  • \n' + + '
  • ย้ายไปมาระหว่างปุ่มต่างๆ ในกลุ่มแถบเครื่องมือ
  • \n' + + '
  • ย้ายไปมาระหว่างรายการต่างๆ ในพาธองค์ประกอบของส่วนท้าย
  • \n' + + '
\n' + + '\n' + + '

ปุ่มลูกศรลงและขึ้น

\n' + + '\n' + + '
    \n' + + '
  • ย้ายไปมาระหว่างรายการเมนูต่างๆ ในเมนู
  • \n' + + '
  • ย้ายไปมาระหว่างรายการต่างๆ ในเมนูป๊อบอัพแถบเครื่องมือ
  • \n' + + '
\n' + + '\n' + + '

ปุ่มลูกศรจะเลื่อนไปมาภายในส่วน UI ที่โฟกัส

\n' + + '\n' + + '

ในการปิดเมนูที่เปิดอยู่ เมนูย่อยที่เปิดอยู่ หรือเมนูป๊อบอัพที่เปิดอยู่ ให้กดปุ่ม Esc

\n' + + '\n' + + '

หากโฟกัสปัจจุบันอยู่ที่ ‘ด้านบนสุด’ ของส่วน UI เฉพาะ การกดปุ่ม Esc จะทำให้ออกจาก\n' + + ' การนำทางด้วยแป้นพิมพ์ทั้งหมดเช่นกัน

\n' + + '\n' + + '

การดำเนินการรายการเมนูหรือปุ่มในแถบเครื่องมือ

\n' + + '\n' + + '

เมื่อไฮไลต์รายการเมนูหรือปุ่มในแถบเครื่องมือที่ต้องการ ให้กด Return, Enter\n' + + ' หรือ Space bar เพื่อดำเนินการรายการดังกล่าว

\n' + + '\n' + + '

การนำทางสำหรับกล่องโต้ตอบที่ไม่อยู่ในแท็บ

\n' + + '\n' + + '

ในกล่องโต้ตอบที่ไม่อยู่ในแท็บ จะโฟกัสที่ส่วนประกอบเชิงโต้ตอบแรกเมื่อกล่องโต้ตอบเปิด

\n' + + '\n' + + '

นำทางระหว่างส่วนประกอบเชิงโต้ตอบต่างๆ ของกล่องโต้ตอบ โดยการกด Tab หรือ Shift+Tab

\n' + + '\n' + + '

การนำทางสำหรับกล่องโต้ตอบที่อยู่ในแท็บ

\n' + + '\n' + + '

ในกล่องโต้ตอบที่อยู่ในแท็บ จะโฟกัสที่ปุ่มแรกในเมนูแท็บเมื่อกล่องโต้ตอบเปิด

\n' + + '\n' + + '

นำทางระหว่างส่วนประกอบเชิงโต้ตอบต่างๆ ของแท็บกล่องโต้ตอบนี้โดยการกด Tab หรือ\n' + + ' Shift+Tab

\n' + + '\n' + + '

สลับไปยังแท็บกล่องโต้ตอบอื่นโดยการเลือกโฟกัสที่เมนูแท็บ แล้วกดปุ่มลูกศรที่เหมาะสม\n' + + ' เพื่อเลือกแท็บที่ใช้ได้

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/tr.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/tr.js new file mode 100644 index 0000000..37f39b0 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/tr.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.tr', +'

Klavyeyle gezintiyi başlatma

\n' + + '\n' + + '
\n' + + '
Menü çubuğuna odaklan
\n' + + '
Windows veya Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Araç çubuğuna odaklan
\n' + + '
Windows veya Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Alt bilgiye odaklan
\n' + + '
Windows veya Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Bildirime odakla
\n' + + '
Windows veya Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Bağlamsal araç çubuğuna odaklan
\n' + + '
Windows, Linux veya macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Gezinti ilk kullanıcı arabirimi öğesinden başlar, bu öğe vurgulanır ya da ilk öğe, Alt bilgi elemanı\n' + + ' yolundaysa altı çizilir.

\n' + + '\n' + + '

Kullanıcı arabirimi bölümleri arasında gezinme

\n' + + '\n' + + '

Sonraki kullanıcı arabirimi bölümüne gitmek için Sekme tuşuna basın.

\n' + + '\n' + + '

Önceki kullanıcı arabirimi bölümüne gitmek için Shift+Sekme tuşlarına basın.

\n' + + '\n' + + '

Bu kullanıcı arabirimi bölümlerinin Sekme sırası:

\n' + + '\n' + + '
    \n' + + '
  1. Menü çubuğu
  2. \n' + + '
  3. Her araç çubuğu grubu
  4. \n' + + '
  5. Kenar çubuğu
  6. \n' + + '
  7. Alt bilgide öğe yolu
  8. \n' + + '
  9. Alt bilgide sözcük sayısı geçiş düğmesi
  10. \n' + + '
  11. Alt bilgide marka bağlantısı
  12. \n' + + '
  13. Alt bilgide düzenleyiciyi yeniden boyutlandırma tutamacı
  14. \n' + + '
\n' + + '\n' + + '

Kullanıcı arabirimi bölümü yoksa atlanır.

\n' + + '\n' + + '

Alt bilgide klavyeyle gezinti odağı yoksa ve görünür bir kenar çubuğu mevcut değilse Shift+Sekme tuşlarına basıldığında\n' + + ' odak son araç çubuğu yerine ilk araç çubuğu grubuna taşınır.

\n' + + '\n' + + '

Kullanıcı arabirimi bölümleri içinde gezinme

\n' + + '\n' + + '

Sonraki kullanıcı arabirimi elemanına gitmek için uygun Ok tuşuna basın.

\n' + + '\n' + + '

Sol ve Sağ ok tuşları

\n' + + '\n' + + '
    \n' + + '
  • menü çubuğundaki menüler arasında hareket eder.
  • \n' + + '
  • menüde bir alt menü açar.
  • \n' + + '
  • araç çubuğu grubundaki düğmeler arasında hareket eder.
  • \n' + + '
  • alt bilginin öğe yolundaki öğeler arasında hareket eder.
  • \n' + + '
\n' + + '\n' + + '

Aşağı ve Yukarı ok tuşları

\n' + + '\n' + + '
    \n' + + '
  • menüdeki menü öğeleri arasında hareket eder.
  • \n' + + '
  • araç çubuğu açılır menüsündeki öğeler arasında hareket eder.
  • \n' + + '
\n' + + '\n' + + '

Ok tuşları, odaklanılan kullanıcı arabirimi bölümü içinde döngüsel olarak hareket eder.

\n' + + '\n' + + '

Açık bir menüyü, açık bir alt menüyü veya açık bir açılır menüyü kapatmak için Esc tuşuna basın.

\n' + + '\n' + + '

Geçerli odak belirli bir kullanıcı arabirimi bölümünün "üst" kısmındaysa Esc tuşuna basıldığında\n' + + ' klavyeyle gezintiden de tamamen çıkılır.

\n' + + '\n' + + '

Menü öğesini veya araç çubuğu düğmesini yürütme

\n' + + '\n' + + '

İstediğiniz menü öğesi veya araç çubuğu düğmesi vurgulandığında Return, Enter\n' + + ' veya Ara çubuğu tuşuna basın.

\n' + + '\n' + + '

Sekme bulunmayan iletişim kutularında gezinme

\n' + + '\n' + + '

Sekme bulunmayan iletişim kutularında, iletişim kutusu açıldığında ilk etkileşimli bileşene odaklanılır.

\n' + + '\n' + + '

Etkileşimli iletişim kutusu bileşenleri arasında gezinmek için Sekme veya Shift+ Sekme tuşlarına basın.

\n' + + '\n' + + '

Sekmeli iletişim kutularında gezinme

\n' + + '\n' + + '

Sekmeli iletişim kutularında, iletişim kutusu açıldığında sekme menüsündeki ilk düğmeye odaklanılır.

\n' + + '\n' + + '

Bu iletişim kutusu sekmesinin etkileşimli bileşenleri arasında gezinmek için Sekme veya\n' + + ' Shift+Sekme tuşlarına basın.

\n' + + '\n' + + '

Mevcut sekmeler arasında geçiş yapmak için sekme menüsüne odaklanıp uygun Ok tuşuna basarak\n' + + ' başka bir iletişim kutusu sekmesine geçiş yapın.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/uk.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/uk.js new file mode 100644 index 0000000..028d4a4 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/uk.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.uk', +'

Початок роботи з навігацією за допомогою клавіатури

\n' + + '\n' + + '
\n' + + '
Фокус на рядок меню
\n' + + '
Windows або Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Фокус на панелі інструментів
\n' + + '
Windows або Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Фокус на розділі "Нижній колонтитул"
\n' + + '
Windows або Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Фокус на сповіщення
\n' + + '
Windows або Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Фокус на контекстній панелі інструментів
\n' + + '
Windows, Linux або macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Навігація почнеться з першого елемента інтерфейсу користувача, який буде виділено або підкреслено в разі, якщо перший елемент знаходиться в\n' + + ' шляху до елемента "Нижній колонтитул".

\n' + + '\n' + + '

Навігація між розділами інтерфейсу користувача

\n' + + '\n' + + '

Щоб перейти з одного розділу інтерфейсу користувача до наступного розділу, натисніть клавішу Tab.

\n' + + '\n' + + '

Щоб перейти з одного розділу інтерфейсу користувача до попереднього розділу, натисніть сполучення клавіш Shift+Tab.

\n' + + '\n' + + '

Порядок Вкладок цих розділів інтерфейсу користувача такий:

\n' + + '\n' + + '
    \n' + + '
  1. Рядок меню
  2. \n' + + '
  3. Кожна група панелей інструментів
  4. \n' + + '
  5. Бічна панель
  6. \n' + + '
  7. Шлях до елементів у розділі "Нижній колонтитул"
  8. \n' + + '
  9. Кнопка перемикача "Кількість слів" у розділі "Нижній колонтитул"
  10. \n' + + '
  11. Посилання на брендинг у розділі "Нижній колонтитул"
  12. \n' + + '
  13. Маркер змінення розміру в розділі "Нижній колонтитул"
  14. \n' + + '
\n' + + '\n' + + '

Якщо розділ інтерфейсу користувача відсутній, він пропускається.

\n' + + '\n' + + '

Якщо фокус навігації клавіатури знаходиться на розділі "Нижній колонтитул", але користувач не бачить видиму бічну панель, натисніть Shift+Tab,\n' + + ' щоб перемістити фокус на першу групу панелі інструментів, а не на останню.

\n' + + '\n' + + '

Навігація в межах розділів інтерфейсу користувача

\n' + + '\n' + + '

Щоб перейти з одного елементу інтерфейсу користувача до наступного, натисніть відповідну клавішу зі стрілкою.

\n' + + '\n' + + '

Клавіші зі стрілками Ліворуч і Праворуч

\n' + + '\n' + + '
    \n' + + '
  • переміщують між меню в рядку меню.
  • \n' + + '
  • відкривають вкладене меню в меню.
  • \n' + + '
  • переміщують користувача між кнопками в групі панелі інструментів.
  • \n' + + '
  • переміщують між елементами в шляху до елементів у розділі "Нижній колонтитул".
  • \n' + + '
\n' + + '\n' + + '

Клавіші зі стрілками Вниз і Вгору

\n' + + '\n' + + '
    \n' + + '
  • переміщують між елементами меню в меню.
  • \n' + + '
  • переміщують між елементами в спливаючому меню панелі інструментів.
  • \n' + + '
\n' + + '\n' + + '

Клавіші зі стрілками переміщують фокус циклічно в межах розділу інтерфейсу користувача, на якому знаходиться фокус.

\n' + + '\n' + + '

Щоб закрити відкрите меню, відкрите вкладене меню або відкрите спливаюче меню, натисніть клавішу Esc.

\n' + + '\n' + + '

Якщо поточний фокус знаходиться на верхньому рівні певного розділу інтерфейсу користувача, натискання клавіші Esc також виконує вихід\n' + + ' з навігації за допомогою клавіатури повністю.

\n' + + '\n' + + '

Виконання елементу меню або кнопки панелі інструментів

\n' + + '\n' + + '

Коли потрібний елемент меню або кнопку панелі інструментів виділено, натисніть клавіші Return, Enter,\n' + + ' або Пробіл, щоб виконати цей елемент.

\n' + + '\n' + + '

Навігація по діалоговим вікнам без вкладок

\n' + + '\n' + + '

У діалогових вікнах без вкладок перший інтерактивний компонент приймає фокус, коли відкривається діалогове вікно.

\n' + + '\n' + + '

Переходьте між інтерактивними компонентами діалогового вікна, натискаючи клавіші Tab або Shift+Tab.

\n' + + '\n' + + '

Навігація по діалоговим вікнам з вкладками

\n' + + '\n' + + '

У діалогових вікнах із вкладками перша кнопка в меню вкладки приймає фокус, коли відкривається діалогове вікно.

\n' + + '\n' + + '

Переходьте між інтерактивними компонентами цієї вкладки діалогового вікна, натискаючи клавіші Tab або\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Щоб перейти на іншу вкладку діалогового вікна, перемістіть фокус на меню вкладки, а потім натисніть відповідну клавішу зі стрілкою,\n' + + ' щоб циклічно переходити по доступним вкладкам.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/vi.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/vi.js new file mode 100644 index 0000000..d8eda11 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/vi.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.vi', +'

Bắt đầu điều hướng bàn phím

\n' + + '\n' + + '
\n' + + '
Tập trung vào thanh menu
\n' + + '
Windows hoặc Linux: Alt+F9
\n' + + '
macOS: ⌥F9
\n' + + '
Tập trung vào thanh công cụ
\n' + + '
Windows hoặc Linux: Alt+F10
\n' + + '
macOS: ⌥F10
\n' + + '
Tập trung vào chân trang
\n' + + '
Windows hoặc Linux: Alt+F11
\n' + + '
macOS: ⌥F11
\n' + + '
Tập trung vào thông báo
\n' + + '
Windows hoặc Linux: Alt+F12
\n' + + '
macOS: ⌥F12
\n' + + '
Tập trung vào thanh công cụ ngữ cảnh
\n' + + '
Windows, Linux hoặc macOS: Ctrl+F9
\n' + + '
\n' + + '\n' + + '

Điều hướng sẽ bắt đầu từ mục UI đầu tiên. Mục này sẽ được tô sáng hoặc có gạch dưới (nếu là mục đầu tiên trong\n' + + ' đường dẫn phần tử Chân trang).

\n' + + '\n' + + '

Di chuyển qua lại giữa các phần UI

\n' + + '\n' + + '

Để di chuyển từ một phần UI sang phần tiếp theo, ấn Tab.

\n' + + '\n' + + '

Để di chuyển từ một phần UI về phần trước đó, ấn Shift+Tab.

\n' + + '\n' + + '

Thứ tự Tab của các phần UI này như sau:

\n' + + '\n' + + '
    \n' + + '
  1. Thanh menu
  2. \n' + + '
  3. Từng nhóm thanh công cụ
  4. \n' + + '
  5. Thanh bên
  6. \n' + + '
  7. Đường dẫn phần tử trong chân trang
  8. \n' + + '
  9. Nút chuyển đổi đếm chữ ở chân trang
  10. \n' + + '
  11. Liên kết thương hiệu ở chân trang
  12. \n' + + '
  13. Núm điều tác chỉnh kích cỡ trình soạn thảo ở chân trang
  14. \n' + + '
\n' + + '\n' + + '

Nếu người dùng không thấy một phần UI, thì có nghĩa phần đó bị bỏ qua.

\n' + + '\n' + + '

Nếu ở chân trang có tính năng tập trung điều hướng bàn phím, mà không có thanh bên nào hiện hữu, thao tác ấn Shift+Tab\n' + + ' sẽ chuyển hướng tập trung vào nhóm thanh công cụ đầu tiên, không phải cuối cùng.

\n' + + '\n' + + '

Di chuyển qua lại trong các phần UI

\n' + + '\n' + + '

Để di chuyển từ một phần tử UI sang phần tiếp theo, ấn phím Mũi tên tương ứng cho phù hợp.

\n' + + '\n' + + '

Các phím mũi tên TráiPhải

\n' + + '\n' + + '
    \n' + + '
  • di chuyển giữa các menu trong thanh menu.
  • \n' + + '
  • mở menu phụ trong một menu.
  • \n' + + '
  • di chuyển giữa các nút trong nhóm thanh công cụ.
  • \n' + + '
  • di chuyển giữa các mục trong đường dẫn phần tử của chân trang.
  • \n' + + '
\n' + + '\n' + + '

Các phím mũi tên Hướng xuốngHướng lên

\n' + + '\n' + + '
    \n' + + '
  • di chuyển giữa các mục menu trong menu.
  • \n' + + '
  • di chuyển giữa các mục trong menu thanh công cụ dạng bật lên.
  • \n' + + '
\n' + + '\n' + + '

Các phím mũi tên xoay vòng trong một phần UI tập trung.

\n' + + '\n' + + '

Để đóng một menu mở, một menu phụ đang mở, hoặc một menu dạng bật lên đang mở, hãy ấn phím Esc.

\n' + + '\n' + + '

Nếu trọng tâm hiện tại là ở phần “đầu” của một phần UI cụ thể, thao tác ấn phím Esc cũng sẽ thoát\n' + + ' toàn bộ phần điều hướng bàn phím.

\n' + + '\n' + + '

Thực hiện chức năng của một mục menu hoặc nút thanh công cụ

\n' + + '\n' + + '

Khi mục menu hoặc nút thanh công cụ muốn dùng được tô sáng, hãy ấn Return, Enter,\n' + + ' hoặc Phím cách để thực hiện chức năng mục đó.

\n' + + '\n' + + '

Điều hướng giữa các hộp thoại không có nhiều tab

\n' + + '\n' + + '

Trong các hộp thoại không có nhiều tab, khi hộp thoại mở ra, trọng tâm sẽ hướng vào thành phần tương tác đầu tiên.

\n' + + '\n' + + '

Di chuyển giữa các thành phần hộp thoại tương tác bằng cách ấn Tab hoặc Shift+Tab.

\n' + + '\n' + + '

Điều hướng giữa các hộp thoại có nhiều tab

\n' + + '\n' + + '

Trong các hộp thoại có nhiều tab, khi hộp thoại mở ra, trọng tâm sẽ hướng vào nút đầu tiên trong menu tab.

\n' + + '\n' + + '

Di chuyển giữa các thành phần tương tác của tab hộp thoại này bằng cách ấn Tab hoặc\n' + + ' Shift+Tab.

\n' + + '\n' + + '

Chuyển sang một tab hộp thoại khác bằng cách chuyển trọng tâm vào menu tab, rồi ấn phím Mũi tên phù hợp\n' + + ' để xoay vòng các tab hiện có.

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/zh_CN.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/zh_CN.js new file mode 100644 index 0000000..f7e73d1 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/zh_CN.js @@ -0,0 +1,87 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.zh_CN', +'

开始键盘导航

\n' + + '\n' + + '
\n' + + '
使菜单栏处于焦点
\n' + + '
Windows 或 Linux:Alt+F9
\n' + + '
macOS:⌥F9
\n' + + '
使工具栏处于焦点
\n' + + '
Windows 或 Linux:Alt+F10
\n' + + '
macOS:⌥F10
\n' + + '
使页脚处于焦点
\n' + + '
Windows 或 Linux:Alt+F11
\n' + + '
macOS:⌥F11
\n' + + '
使通知处于焦点
\n' + + '
Windows 或 Linux:Alt+F12
\n' + + '
macOS:⌥F12
\n' + + '
使上下文工具栏处于焦点
\n' + + '
Windows、Linux 或 macOS:Ctrl+F9
\n' + + '
\n' + + '\n' + + '

导航将在第一个 UI 项上开始,其中突出显示该项,或者对于页脚元素路径中的第一项,将为其添加下划线。

\n' + + '\n' + + '

在 UI 部分之间导航

\n' + + '\n' + + '

要从一个 UI 部分移至下一个,请按 Tab

\n' + + '\n' + + '

要从一个 UI 部分移至上一个,请按 Shift+Tab

\n' + + '\n' + + '

这些 UI 部分的 Tab 顺序为:

\n' + + '\n' + + '
    \n' + + '
  1. 菜单栏
  2. \n' + + '
  3. 每个工具栏组
  4. \n' + + '
  5. 边栏
  6. \n' + + '
  7. 页脚中的元素路径
  8. \n' + + '
  9. 页脚中的字数切换按钮
  10. \n' + + '
  11. 页脚中的品牌链接
  12. \n' + + '
  13. 页脚中的编辑器调整大小图柄
  14. \n' + + '
\n' + + '\n' + + '

如果不存在某个 UI 部分,则跳过它。

\n' + + '\n' + + '

如果键盘导航焦点在页脚,并且没有可见的边栏,则按 Shift+Tab 将焦点移至第一个工具栏组而非最后一个。

\n' + + '\n' + + '

在 UI 部分内导航

\n' + + '\n' + + '

要从一个 UI 元素移至下一个,请按相应的箭头键。

\n' + + '\n' + + '

箭头键

\n' + + '\n' + + '
    \n' + + '
  • 在菜单栏中的菜单之间移动。
  • \n' + + '
  • 打开菜单中的子菜单。
  • \n' + + '
  • 在工具栏组中的按钮之间移动。
  • \n' + + '
  • 在页脚的元素路径中的各项之间移动。
  • \n' + + '
\n' + + '\n' + + '

箭头键

\n' + + '\n' + + '
    \n' + + '
  • 在菜单中的菜单项之间移动。
  • \n' + + '
  • 在工具栏弹出菜单中的各项之间移动。
  • \n' + + '
\n' + + '\n' + + '

箭头键在具有焦点的 UI 部分内循环。

\n' + + '\n' + + '

要关闭打开的菜单、打开的子菜单或打开的弹出菜单,请按 Esc 键。

\n' + + '\n' + + '

如果当前的焦点在特定 UI 部分的“顶部”,则按 Esc 键还将完全退出键盘导航。

\n' + + '\n' + + '

执行菜单项或工具栏按钮

\n' + + '\n' + + '

当突出显示所需的菜单项或工具栏按钮时,按 ReturnEnter空格以执行该项。

\n' + + '\n' + + '

在非标签页式对话框中导航

\n' + + '\n' + + '

在非标签页式对话框中,当对话框打开时,第一个交互组件获得焦点。

\n' + + '\n' + + '

通过按 TabShift+Tab,在交互对话框组件之间导航。

\n' + + '\n' + + '

在标签页式对话框中导航

\n' + + '\n' + + '

在标签页式对话框中,当对话框打开时,标签页菜单中的第一个按钮获得焦点。

\n' + + '\n' + + '

通过按 TabShift+Tab,在此对话框的交互组件之间导航。

\n' + + '\n' + + '

通过将焦点移至另一对话框标签页的菜单,然后按相应的箭头键以在可用的标签页间循环,从而切换到该对话框标签页。

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/zh_TW.js b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/zh_TW.js new file mode 100644 index 0000000..5912770 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/zh_TW.js @@ -0,0 +1,93 @@ +tinymce.Resource.add('tinymce.html-i18n.help-keynav.zh_TW', +'

開始鍵盤瀏覽

\n' + + '\n' + + '
\n' + + '
跳至功能表列
\n' + + '
Windows 或 Linux:Alt+F9
\n' + + '
macOS:⌥F9
\n' + + '
跳至工具列
\n' + + '
Windows 或 Linux:Alt+F10
\n' + + '
macOS:⌥F10
\n' + + '
跳至頁尾
\n' + + '
Windows 或 Linux:Alt+F11
\n' + + '
macOS:⌥F11
\n' + + '
跳至通知
\n' + + '
Windows 或 Linux:Alt+F12
\n' + + '
macOS:⌥F12
\n' + + '
跳至關聯式工具列
\n' + + '
Windows、Linux 或 macOS:Ctrl+F9
\n' + + '
\n' + + '\n' + + '

瀏覽會從第一個 UI 項目開始,該項目會反白顯示,但如果是「頁尾」元素路徑的第一項,\n' + + ' 則加底線。

\n' + + '\n' + + '

在 UI 區段之間瀏覽

\n' + + '\n' + + '

從 UI 區段移至下一個,請按 Tab

\n' + + '\n' + + '

從 UI 區段移回上一個,請按 Shift+Tab

\n' + + '\n' + + '

這些 UI 區段的 Tab 順序如下:

\n' + + '\n' + + '
    \n' + + '
  1. 功能表列
  2. \n' + + '
  3. 各個工具列群組
  4. \n' + + '
  5. 側邊欄
  6. \n' + + '
  7. 頁尾中的元素路徑
  8. \n' + + '
  9. 頁尾中字數切換按鈕
  10. \n' + + '
  11. 頁尾中的品牌連結
  12. \n' + + '
  13. 頁尾中編輯器調整大小控點
  14. \n' + + '
\n' + + '\n' + + '

如果 UI 區段未顯示,表示已略過該區段。

\n' + + '\n' + + '

如果鍵盤瀏覽跳至頁尾,但沒有顯示側邊欄,則按下 Shift+Tab\n' + + ' 會跳至第一個工具列群組,而不是最後一個。

\n' + + '\n' + + '

在 UI 區段之內瀏覽

\n' + + '\n' + + '

在兩個 UI 元素之間移動,請按適當的方向鍵。

\n' + + '\n' + + '

向左向右方向鍵

\n' + + '\n' + + '
    \n' + + '
  • 在功能表列中的功能表之間移動。
  • \n' + + '
  • 開啟功能表中的子功能表。
  • \n' + + '
  • 在工具列群組中的按鈕之間移動。
  • \n' + + '
  • 在頁尾的元素路徑中項目之間移動。
  • \n' + + '
\n' + + '\n' + + '

向下向上方向鍵

\n' + + '\n' + + '
    \n' + + '
  • 在功能表中的功能表項目之間移動。
  • \n' + + '
  • 在工具列快顯功能表中的項目之間移動。
  • \n' + + '
\n' + + '\n' + + '

方向鍵會在所跳至 UI 區段之內循環。

\n' + + '\n' + + '

若要關閉已開啟的功能表、已開啟的子功能表,或已開啟的快顯功能表,請按 Esc 鍵。

\n' + + '\n' + + '

如果目前已跳至特定 UI 區段的「頂端」,則按 Esc 鍵也會結束\n' + + ' 整個鍵盤瀏覽。

\n' + + '\n' + + '

執行功能表列項目或工具列按鈕

\n' + + '\n' + + '

當想要的功能表項目或工具列按鈕已反白顯示時,按 ReturnEnter、\n' + + ' 或空白鍵即可執行該項目。

\n' + + '\n' + + '

瀏覽非索引標籤式對話方塊

\n' + + '\n' + + '

在非索引標籤式對話方塊中,開啟對話方塊時會跳至第一個互動元件。

\n' + + '\n' + + '

TabShift+Tab 即可在互動式對話方塊元件之間瀏覽。

\n' + + '\n' + + '

瀏覽索引標籤式對話方塊

\n' + + '\n' + + '

在索引標籤式對話方塊中,開啟對話方塊時會跳至索引標籤式功能表中的第一個按鈕。

\n' + + '\n' + + '

若要在此對話方塊的互動式元件之間瀏覽,請按 Tab 或\n' + + ' Shift+Tab

\n' + + '\n' + + '

先跳至索引標籤式功能表,然後按適當的方向鍵,即可切換至另一個對話方塊索引標籤,\n' + + ' 以循環瀏覽可用的索引標籤。

\n'); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/help/plugin.min.js b/apps/web-antd/public/tinymce/plugins/help/plugin.min.js new file mode 100644 index 0000000..acddac2 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/help/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");let t=0;const n=e=>{const n=(new Date).getTime(),a=Math.floor(window.crypto.getRandomValues(new Uint32Array(1))[0]/4294967295*1e9);return t++,e+"_"+a+t+String(n)},a=e=>t=>t.options.get(e),r=a("help_tabs"),o=a("forced_plugins"),i=e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=a=e,(r=String).prototype.isPrototypeOf(n)||(null===(o=a.constructor)||void 0===o?void 0:o.name)===r.name)?"string":t;var n,a,r,o})(e);const s=e=>undefined===e;const c=e=>"function"==typeof e,l=()=>false;class m{constructor(e,t){this.tag=e,this.value=t}static some(e){return new m(!0,e)}static none(){return m.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?m.some(e(this.value)):m.none()}bind(e){return this.tag?e(this.value):m.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:m.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return null==e?m.none():m.some(e)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}m.singletonNone=new m(!1);const u=Array.prototype.slice,p=Array.prototype.indexOf,y=(e,t)=>{const n=e.length,a=new Array(n);for(let r=0;r{const n=[];for(let a=0,r=e.length;a{const n=u.call(e,0);return n.sort(t),n},g=Object.keys,k=Object.hasOwnProperty,v=(e,t)=>k.call(e,t);var b=tinymce.util.Tools.resolve("tinymce.Resource"),f=tinymce.util.Tools.resolve("tinymce.util.I18n");const A=(e,t)=>b.load(`tinymce.html-i18n.help-keynav.${t}`,`${e}/js/i18n/keynav/${t}.js`),w=e=>A(e,f.getCode()).catch((()=>A(e,"en")));var C=tinymce.util.Tools.resolve("tinymce.Env");const S=e=>{const t=C.os.isMacOS()||C.os.isiOS(),n=t?{alt:"⌥",ctrl:"⌃",shift:"⇧",meta:"⌘",access:"⌃⌥"}:{meta:"Ctrl ",access:"Shift + Alt "},a=e.split("+"),r=y(a,(e=>{const t=e.toLowerCase().trim();return v(n,t)?n[t]:e}));return t?r.join("").replace(/\s/,""):r.join("+")},M=[{shortcuts:["Meta + B"],action:"Bold"},{shortcuts:["Meta + I"],action:"Italic"},{shortcuts:["Meta + U"],action:"Underline"},{shortcuts:["Meta + A"],action:"Select all"},{shortcuts:["Meta + Y","Meta + Shift + Z"],action:"Redo"},{shortcuts:["Meta + Z"],action:"Undo"},{shortcuts:["Access + 1"],action:"Heading 1"},{shortcuts:["Access + 2"],action:"Heading 2"},{shortcuts:["Access + 3"],action:"Heading 3"},{shortcuts:["Access + 4"],action:"Heading 4"},{shortcuts:["Access + 5"],action:"Heading 5"},{shortcuts:["Access + 6"],action:"Heading 6"},{shortcuts:["Access + 7"],action:"Paragraph"},{shortcuts:["Access + 8"],action:"Div"},{shortcuts:["Access + 9"],action:"Address"},{shortcuts:["Alt + 0"],action:"Open help dialog"},{shortcuts:["Alt + F9"],action:"Focus to menubar"},{shortcuts:["Alt + F10"],action:"Focus to toolbar"},{shortcuts:["Alt + F11"],action:"Focus to element path"},{shortcuts:["Alt + F12"],action:"Focus to notification"},{shortcuts:["Ctrl + F9"],action:"Focus to contextual toolbar"},{shortcuts:["Shift + Enter"],action:"Open popup menu for split buttons"},{shortcuts:["Meta + K"],action:"Insert link (if link plugin activated)"},{shortcuts:["Meta + S"],action:"Save (if save plugin activated)"},{shortcuts:["Meta + F"],action:"Find (if searchreplace plugin activated)"},{shortcuts:["Meta + Shift + F"],action:"Switch to or from fullscreen mode"}],_=()=>({name:"shortcuts",title:"Handy Shortcuts",items:[{type:"table",header:["Action","Shortcut"],cells:y(M,(e=>{const t=y(e.shortcuts,S).join(" or ");return[e.action,t]}))}]}),x=y([{key:"accordion",name:"Accordion"},{key:"anchor",name:"Anchor"},{key:"autolink",name:"Autolink"},{key:"autoresize",name:"Autoresize"},{key:"autosave",name:"Autosave"},{key:"charmap",name:"Character Map"},{key:"code",name:"Code"},{key:"codesample",name:"Code Sample"},{key:"colorpicker",name:"Color Picker"},{key:"directionality",name:"Directionality"},{key:"emoticons",name:"Emoticons"},{key:"fullscreen",name:"Full Screen"},{key:"help",name:"Help"},{key:"image",name:"Image"},{key:"importcss",name:"Import CSS"},{key:"insertdatetime",name:"Insert Date/Time"},{key:"link",name:"Link"},{key:"lists",name:"Lists"},{key:"advlist",name:"List Styles"},{key:"media",name:"Media"},{key:"nonbreaking",name:"Nonbreaking"},{key:"pagebreak",name:"Page Break"},{key:"preview",name:"Preview"},{key:"quickbars",name:"Quick Toolbars"},{key:"save",name:"Save"},{key:"searchreplace",name:"Search and Replace"},{key:"table",name:"Table"},{key:"textcolor",name:"Text Color"},{key:"visualblocks",name:"Visual Blocks"},{key:"visualchars",name:"Visual Characters"},{key:"wordcount",name:"Word Count"},{key:"a11ychecker",name:"Accessibility Checker",type:"premium"},{key:"typography",name:"Advanced Typography",type:"premium",slug:"advanced-typography"},{key:"ai",name:"AI Assistant",type:"premium"},{key:"casechange",name:"Case Change",type:"premium"},{key:"checklist",name:"Checklist",type:"premium"},{key:"advcode",name:"Enhanced Code Editor",type:"premium"},{key:"mediaembed",name:"Enhanced Media Embed",type:"premium",slug:"introduction-to-mediaembed"},{key:"advtable",name:"Enhanced Tables",type:"premium"},{key:"exportpdf",name:"Export to PDF",type:"premium"},{key:"exportword",name:"Export to Word",type:"premium"},{key:"footnotes",name:"Footnotes",type:"premium"},{key:"formatpainter",name:"Format Painter",type:"premium"},{key:"editimage",name:"Image Editing",type:"premium"},{key:"uploadcare",name:"Image Optimizer Powered by Uploadcare",type:"premium"},{key:"importword",name:"Import from Word",type:"premium"},{key:"inlinecss",name:"Inline CSS",type:"premium",slug:"inline-css"},{key:"linkchecker",name:"Link Checker",type:"premium"},{key:"math",name:"Math",type:"premium"},{key:"markdown",name:"Markdown",type:"premium"},{key:"mentions",name:"Mentions",type:"premium"},{key:"mergetags",name:"Merge Tags",type:"premium"},{key:"pageembed",name:"Page Embed",type:"premium"},{key:"permanentpen",name:"Permanent Pen",type:"premium"},{key:"powerpaste",name:"PowerPaste",type:"premium",slug:"introduction-to-powerpaste"},{key:"revisionhistory",name:"Revision History",type:"premium"},{key:"tinymcespellchecker",name:"Spell Checker",type:"premium",slug:"introduction-to-tiny-spellchecker"},{key:"autocorrect",name:"Spelling Autocorrect",type:"premium"},{key:"tableofcontents",name:"Table of Contents",type:"premium"},{key:"advtemplate",name:"Templates",type:"premium",slug:"advanced-templates"},{key:"tinycomments",name:"Tiny Comments",type:"premium",slug:"introduction-to-tiny-comments"},{key:"tinydrive",name:"Tiny Drive",type:"premium",slug:"tinydrive-introduction"}],(e=>({...e,type:e.type||"opensource",slug:e.slug||e.key}))),T=e=>{const t=e=>`${e.name}`,n=(e,n)=>{return(a=x,r=e=>e.key===n,((e,t,n)=>{for(let a=0,r=e.length;a((e,n)=>{const a=e.plugins[n].getMetadata;if(c(a)){const e=a();return{name:e.name,html:t(e)}}return{name:n,html:n}})(e,n)),(e=>{const n="premium"===e.type?`${e.name}*`:e.name;return{name:n,html:t({name:n,url:`https://www.tiny.cloud/docs/tinymce/7/${e.slug}/`})}}));var a,r},a=e=>{const t=(e=>{const t=g(e.plugins),n=o(e),a=s(n)?["onboarding"]:n.concat(["onboarding"]);return h(t,(e=>!(((e,t)=>p.call(e,t))(a,e)>-1)))})(e),a=d(y(t,(t=>n(e,t))),((e,t)=>e.name.localeCompare(t.name))),r=y(a,(e=>"
  • "+e.html+"
  • ")),i=r.length,c=r.join("");return"

    "+f.translate(["Plugins installed ({0}):",i])+"

      "+c+"
    "},r={type:"htmlpanel",presets:"document",html:[(e=>null==e?"":"
    "+a(e)+"
    ")(e),(()=>{const e=h(x,(({type:e})=>"premium"===e)),t=d(y(e,(e=>e.name)),((e,t)=>e.localeCompare(t))),n=y(t,(e=>`
  • ${e}
  • `)).join("");return"

    "+f.translate("Premium plugins:")+"

    "})()].join("")};return{name:"plugins",title:"Plugins",items:[r]}};var O=tinymce.util.Tools.resolve("tinymce.EditorManager");const P=(e,t,a)=>()=>{(async(e,t,a)=>{const o=_(),s=await(async e=>({name:"keyboardnav",title:"Keyboard Navigation",items:[{type:"htmlpanel",presets:"document",html:await w(e)}]}))(a),c=T(e),l=(()=>{var e,t;const n='TinyMCE '+(e=O.majorVersion,t=O.minorVersion,(0===e.indexOf("@")?"X.X.X":e+"."+t)+"");return{name:"versions",title:"Version",items:[{type:"htmlpanel",html:"

    "+f.translate(["You are using {0}",n])+"

    ",presets:"document"}]}})(),u={[o.name]:o,[s.name]:s,[c.name]:c,[l.name]:l,...t.get()};return m.from(r(e)).fold((()=>(e=>{const t=g(e),n=t.indexOf("versions");return-1!==n&&(t.splice(n,1),t.push("versions")),{tabs:e,names:t}})(u)),(e=>((e,t)=>{const a={},r=y(e,(e=>{var r;if(i(e))return v(t,e)&&(a[e]=t[e]),e;{const t=null!==(r=e.name)&&void 0!==r?r:n("tab-name");return a[t]=e,t}}));return{tabs:a,names:r}})(e,u)))})(e,t,a).then((({tabs:t,names:n})=>{const a={type:"tabpanel",tabs:(e=>{const t=[],n=e=>{t.push(e)};for(let t=0;t{return v(n=t,a=e)?m.from(n[a]):m.none();var n,a})))};e.windowManager.open({title:"Help",size:"medium",body:a,buttons:[{type:"cancel",name:"close",text:"Close",primary:!0}],initialData:{}})}))};e.add("help",((e,t)=>{const a=(()=>{let e={};return{get:()=>e,set:t=>{e=t}}})(),r=(e=>({addTab:t=>{var a;const r=null!==(a=t.name)&&void 0!==a?a:n("tab-name"),o=e.get();o[r]=t,e.set(o)}}))(a);(e=>{(0,e.options.register)("help_tabs",{processor:"array"})})(e);const o=P(e,a,t);return((e,t)=>{e.ui.registry.addButton("help",{icon:"help",tooltip:"Help",onAction:t,context:"any"}),e.ui.registry.addMenuItem("help",{text:"Help",icon:"help",shortcut:"Alt+0",onAction:t,context:"any"})})(e,o),((e,t)=>{e.addCommand("mceHelp",t)})(e,o),e.shortcuts.add("Alt+0","Open help dialog","mceHelp"),((e,t)=>{e.on("init",(()=>{w(t)}))})(e,t),r}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/image/plugin.min.js b/apps/web-antd/public/tinymce/plugins/image/plugin.min.js new file mode 100644 index 0000000..9480158 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/image/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=Object.getPrototypeOf,a=(e,t,a)=>{var i;return!!a(e,t.prototype)||(null===(i=e.constructor)||void 0===i?void 0:i.name)===t.name},i=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&a(e,String,((e,t)=>t.isPrototypeOf(e)))?"string":t})(t)===e,s=e=>t=>typeof t===e,r=i("string"),o=i("object"),n=e=>((e,i)=>o(e)&&a(e,i,((e,a)=>t(e)===a)))(e,Object),l=i("array"),c=e=>null===e;const m=s("boolean"),d=e=>!(e=>null==e)(e),g=s("function"),u=s("number"),p=()=>{};class h{constructor(e,t){this.tag=e,this.value=t}static some(e){return new h(!0,e)}static none(){return h.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?h.some(e(this.value)):h.none()}bind(e){return this.tag?e(this.value):h.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:h.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return d(e)?h.some(e):h.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}h.singletonNone=new h(!1);const b=Object.keys,v=Object.hasOwnProperty,y=(e,t)=>v.call(e,t),f=Array.prototype.push,w=e=>{const t=[];for(let a=0,i=e.length;a{((e,t,a)=>{if(!(r(a)||m(a)||u(a)))throw console.error("Invalid call to Attribute.set. Key ",t,":: Value ",a,":: Element ",e),new Error("Attribute value was not simple");e.setAttribute(t,a+"")})(e.dom,t,a)},D=e=>{if(null==e)throw new Error("Node cannot be null or undefined");return{dom:e}},_=D;var C=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),I=tinymce.util.Tools.resolve("tinymce.util.URI");const U=e=>e.length>0,S=e=>t=>t.options.get(e),x=S("image_dimensions"),N=S("image_advtab"),T=S("image_uploadtab"),E=S("image_prepend_url"),L=S("image_class_list"),O=S("image_description"),j=S("image_title"),M=S("image_caption"),R=S("image_list"),k=S("a11y_advanced_options"),z=S("automatic_uploads"),B=(e,t)=>Math.max(parseInt(e,10),parseInt(t,10)),P=e=>(e&&(e=e.replace(/px$/,"")),e),F=e=>(e.length>0&&/^[0-9]+$/.test(e)&&(e+="px"),e),H=e=>"IMG"===e.nodeName&&(e.hasAttribute("data-mce-object")||e.hasAttribute("data-mce-placeholder")),G=(e,t)=>{const a=e.options.get;return I.isDomSafe(t,"img",{allow_html_data_urls:a("allow_html_data_urls"),allow_script_urls:a("allow_script_urls"),allow_svg_data_urls:a("allow_svg_data_urls")})},W=C.DOM,$=e=>e.style.marginLeft&&e.style.marginRight&&e.style.marginLeft===e.style.marginRight?P(e.style.marginLeft):"",V=e=>e.style.marginTop&&e.style.marginBottom&&e.style.marginTop===e.style.marginBottom?P(e.style.marginTop):"",K=e=>e.style.borderWidth?P(e.style.borderWidth):"",Z=(e,t)=>{var a;return e.hasAttribute(t)&&null!==(a=e.getAttribute(t))&&void 0!==a?a:""},q=e=>null!==e.parentNode&&"FIGURE"===e.parentNode.nodeName,J=(e,t,a)=>{""===a||null===a?e.removeAttribute(t):e.setAttribute(t,a)},Q=(e,t)=>{const a=e.getAttribute("style"),i=t(null!==a?a:"");i.length>0?(e.setAttribute("style",i),e.setAttribute("data-mce-style",i)):e.removeAttribute("style")},X=(e,t)=>(e,a,i)=>{const s=e.style;s[a]?(s[a]=F(i),Q(e,t)):J(e,a,i)},Y=(e,t)=>e.style[t]?P(e.style[t]):Z(e,t),ee=(e,t)=>{const a=F(t);e.style.marginLeft=a,e.style.marginRight=a},te=(e,t)=>{const a=F(t);e.style.marginTop=a,e.style.marginBottom=a},ae=(e,t)=>{const a=F(t);e.style.borderWidth=a},ie=(e,t)=>{e.style.borderStyle=t},se=e=>{var t;return null!==(t=e.style.borderStyle)&&void 0!==t?t:""},re=e=>d(e)&&"FIGURE"===e.nodeName,oe=e=>0===W.getAttrib(e,"alt").length&&"presentation"===W.getAttrib(e,"role"),ne=e=>oe(e)?"":Z(e,"alt"),le=(e,t)=>{var a;const i=document.createElement("img");return J(i,"style",t.style),($(i)||""!==t.hspace)&&ee(i,t.hspace),(V(i)||""!==t.vspace)&&te(i,t.vspace),(K(i)||""!==t.border)&&ae(i,t.border),(se(i)||""!==t.borderStyle)&&ie(i,t.borderStyle),e(null!==(a=i.getAttribute("style"))&&void 0!==a?a:"")},ce=(e,t)=>({src:Z(t,"src"),alt:ne(t),title:Z(t,"title"),width:Y(t,"width"),height:Y(t,"height"),class:Z(t,"class"),style:e(Z(t,"style")),caption:q(t),hspace:$(t),vspace:V(t),border:K(t),borderStyle:se(t),isDecorative:oe(t)}),me=(e,t,a,i,s)=>{a[i]!==t[i]&&s(e,i,String(a[i]))},de=(e,t,a)=>{if(a){W.setAttrib(e,"role","presentation");const t=_(e);A(t,"alt","")}else{if(c(t)){_(e).dom.removeAttribute("alt")}else{const a=_(e);A(a,"alt",t)}"presentation"===W.getAttrib(e,"role")&&W.setAttrib(e,"role","")}},ge=(e,t)=>(a,i,s)=>{e(a,s),Q(a,t)},ue=(e,t,a)=>{const i=ce(e,a);me(a,i,t,"caption",((e,t,a)=>(e=>{q(e)?(e=>{const t=e.parentNode;d(t)&&(W.insertAfter(e,t),W.remove(t))})(e):(e=>{const t=W.create("figure",{class:"image"});W.insertAfter(t,e),t.appendChild(e),t.appendChild(W.create("figcaption",{contentEditable:"true"},"Caption")),t.contentEditable="false"})(e)})(e))),me(a,i,t,"src",J),me(a,i,t,"title",J),me(a,i,t,"width",X(0,e)),me(a,i,t,"height",X(0,e)),me(a,i,t,"class",J),me(a,i,t,"style",ge(((e,t)=>J(e,"style",t)),e)),me(a,i,t,"hspace",ge(ee,e)),me(a,i,t,"vspace",ge(te,e)),me(a,i,t,"border",ge(ae,e)),me(a,i,t,"borderStyle",ge(ie,e)),((e,t,a)=>{a.alt===t.alt&&a.isDecorative===t.isDecorative||de(e,a.alt,a.isDecorative)})(a,i,t)},pe=(e,t)=>{const a=(e=>{if(e.margin){const t=String(e.margin).split(" ");switch(t.length){case 1:e["margin-top"]=e["margin-top"]||t[0],e["margin-right"]=e["margin-right"]||t[0],e["margin-bottom"]=e["margin-bottom"]||t[0],e["margin-left"]=e["margin-left"]||t[0];break;case 2:e["margin-top"]=e["margin-top"]||t[0],e["margin-right"]=e["margin-right"]||t[1],e["margin-bottom"]=e["margin-bottom"]||t[0],e["margin-left"]=e["margin-left"]||t[1];break;case 3:e["margin-top"]=e["margin-top"]||t[0],e["margin-right"]=e["margin-right"]||t[1],e["margin-bottom"]=e["margin-bottom"]||t[2],e["margin-left"]=e["margin-left"]||t[1];break;case 4:e["margin-top"]=e["margin-top"]||t[0],e["margin-right"]=e["margin-right"]||t[1],e["margin-bottom"]=e["margin-bottom"]||t[2],e["margin-left"]=e["margin-left"]||t[3]}delete e.margin}return e})(e.dom.styles.parse(t)),i=e.dom.styles.parse(e.dom.styles.serialize(a));return e.dom.styles.serialize(i)},he=e=>{const t=e.selection.getNode(),a=e.dom.getParent(t,"figure.image");return a?e.dom.select("img",a)[0]:t&&("IMG"!==t.nodeName||H(t))?null:t},be=(e,t)=>{var a;const i=e.dom,s=(t=>{const a={};var i;return((e,t,a,i)=>{((e,t)=>{const a=b(e);for(let i=0,s=a.length;i{(t(e,s)?a:i)(e,s)}))})(t,((t,a)=>!e.schema.isValidChild(a,"figure")),(i=a,(e,t)=>{i[t]=e}),p),a})(e.schema.getTextBlockElements()),r=i.getParent(t.parentNode,(e=>{return t=s,a=e.nodeName,y(t,a)&&void 0!==t[a]&&null!==t[a];var t,a}),e.getBody());return r&&null!==(a=i.split(r,t))&&void 0!==a?a:t},ve=(e,t)=>{const a=((t,a)=>{const i=document.createElement("img");if(ue((t=>pe(e,t)),{...a,caption:!1},i),de(i,a.alt,a.isDecorative),a.caption){const e=W.create("figure",{class:"image"});return e.appendChild(i),e.appendChild(W.create("figcaption",{contentEditable:"true"},"Caption")),e.contentEditable="false",e}return i})(0,t);e.dom.setAttrib(a,"data-mce-id","__mcenew"),e.focus(),e.selection.setContent(a.outerHTML);const i=e.dom.select('*[data-mce-id="__mcenew"]')[0];if(e.dom.setAttrib(i,"data-mce-id",null),re(i)){const t=be(e,i);e.selection.select(t)}else e.selection.select(i)},ye=(e,t)=>{const a=he(e);if(a){const i={...ce((t=>pe(e,t)),a),...t},s=((e,t)=>{const a=t.src;return{...t,src:G(e,a)?a:""}})(e,i);i.src?((e,t)=>{const a=he(e);if(a)if(ue((t=>pe(e,t)),t,a),((e,t)=>{e.dom.setAttrib(t,"src",t.getAttribute("src"))})(e,a),re(a.parentNode)){e.dom.setStyle(a,"float","");const t=a.parentNode;be(e,t),e.selection.select(a.parentNode)}else e.selection.select(a),((e,t,a)=>{const i=()=>{a.onload=a.onerror=null,e.selection&&(e.selection.select(a),e.nodeChanged())};a.onload=()=>{t.width||t.height||!x(e)||e.dom.setAttribs(a,{width:String(a.clientWidth),height:String(a.clientHeight)}),i()},a.onerror=i})(e,t,a)})(e,s):((e,t)=>{if(t){const a=e.dom.is(t.parentNode,"figure.image")?t.parentNode:t;e.dom.remove(a),e.focus(),e.nodeChanged(),e.dom.isEmpty(e.getBody())&&(e.setContent(""),e.selection.setCursorLocation())}})(e,a)}else t.src&&ve(e,{src:"",alt:"",title:"",width:"",height:"",class:"",style:"",caption:!1,hspace:"",vspace:"",border:"",borderStyle:"",isDecorative:!1,...t})},fe=(we=(e,t)=>n(e)&&n(t)?fe(e,t):t,(...e)=>{if(0===e.length)throw new Error("Can't merge zero objects");const t={};for(let a=0;ar(e.value)?e.value:"",Ce=(e,t)=>{const a=[];return De.each(e,(e=>{const i=(e=>r(e.text)?e.text:r(e.title)?e.title:"")(e);if(void 0!==e.menu){const s=Ce(e.menu,t);a.push({text:i,items:s})}else{const s=t(e);a.push({text:i,value:s})}})),a},Ie=(e=_e)=>t=>t?h.from(t).map((t=>Ce(t,e))):h.none(),Ue=(e,t)=>(e=>{for(let i=0;iy(e,"items"))(a=e[i])?Ue(a.items,t):a.value===t?h.some(a):h.none();if(s.isSome())return s}var a;return h.none()})(e),Se=Ie,xe=(e,t)=>e.bind((e=>Ue(e,t))),Ne=e=>{const t=Se((t=>e.convertURL(t.value||t.url||"","src"))),a=new Promise((a=>{((e,t)=>{const a=R(e);r(a)?fetch(a).then((e=>{e.ok&&e.json().then(t)})):g(a)?a(t):t(a)})(e,(e=>{a(t(e).map((e=>w([[{text:"None",value:""}],e]))))}))})),i=(A=L(e),Ie(_e)(A)),s=N(e),o=T(e),n=(e=>U(e.options.get("images_upload_url")))(e),l=(e=>d(e.options.get("images_upload_handler")))(e),c=(e=>{const t=he(e);return t?ce((t=>pe(e,t)),t):{src:"",alt:"",title:"",width:"",height:"",class:"",style:"",caption:!1,hspace:"",vspace:"",border:"",borderStyle:"",isDecorative:!1}})(e),m=O(e),u=j(e),p=x(e),b=M(e),v=k(e),y=z(e),f=h.some(E(e)).filter((e=>r(e)&&e.length>0));var A;return a.then((e=>({image:c,imageList:e,classList:i,hasAdvTab:s,hasUploadTab:o,hasUploadUrl:n,hasUploadHandler:l,hasDescription:m,hasImageTitle:u,hasDimensions:p,hasImageCaption:b,prependURL:f,hasAccessibilityOptions:v,automaticUploads:y})))},Te=e=>{const t=e.imageList.map((e=>({name:"images",type:"listbox",label:"Image list",items:e}))),a={name:"alt",type:"input",label:"Alternative description",enabled:!(e.hasAccessibilityOptions&&e.image.isDecorative)},i=e.classList.map((e=>({name:"classes",type:"listbox",label:"Class",items:e})));return w([[{name:"src",type:"urlinput",filetype:"image",label:"Source",picker_text:"Browse files"}],t.toArray(),e.hasAccessibilityOptions&&e.hasDescription?[{type:"label",label:"Accessibility",items:[{name:"isDecorative",type:"checkbox",label:"Image is decorative"}]}]:[],e.hasDescription?[a]:[],e.hasImageTitle?[{name:"title",type:"input",label:"Image title"}]:[],e.hasDimensions?[{name:"dimensions",type:"sizeinput"}]:[],[{...(s=e.classList.isSome()&&e.hasImageCaption,s?{type:"grid",columns:2}:{type:"panel"}),items:w([i.toArray(),e.hasImageCaption?[{type:"label",label:"Caption",items:[{type:"checkbox",name:"caption",label:"Show caption"}]}]:[]])}]]);var s},Ee=e=>({title:"General",name:"general",items:Te(e)}),Le=Te,Oe=e=>({src:{value:e.src,meta:{}},images:e.src,alt:e.alt,title:e.title,dimensions:{width:e.width,height:e.height},classes:e.class,caption:e.caption,style:e.style,vspace:e.vspace,border:e.border,hspace:e.hspace,borderstyle:e.borderStyle,fileinput:[],isDecorative:e.isDecorative}),je=(e,t)=>({src:e.src.value,alt:null!==e.alt&&0!==e.alt.length||!t?e.alt:null,title:e.title,width:e.dimensions.width,height:e.dimensions.height,class:e.classes,style:e.style,caption:e.caption,hspace:e.hspace,vspace:e.vspace,border:e.border,borderStyle:e.borderstyle,isDecorative:e.isDecorative}),Me=(e,t,a,i)=>{((e,t)=>{const a=t.getData();((e,t)=>/^(?:[a-zA-Z]+:)?\/\//.test(t)?h.none():e.prependURL.bind((e=>t.substring(0,e.length)!==e?h.some(e+t):h.none())))(e,a.src.value).each((e=>{t.setData({src:{value:e,meta:a.src.meta}})}))})(t,i),((e,t)=>{const a=t.getData(),i=a.src.meta;if(void 0!==i){const s=fe({},a);((e,t,a)=>{e.hasDescription&&r(a.alt)&&(t.alt=a.alt),e.hasAccessibilityOptions&&(t.isDecorative=a.isDecorative||t.isDecorative||!1),e.hasImageTitle&&r(a.title)&&(t.title=a.title),e.hasDimensions&&(r(a.width)&&(t.dimensions.width=a.width),r(a.height)&&(t.dimensions.height=a.height)),r(a.class)&&xe(e.classList,a.class).each((e=>{t.classes=e.value})),e.hasImageCaption&&m(a.caption)&&(t.caption=a.caption),e.hasAdvTab&&(r(a.style)&&(t.style=a.style),r(a.vspace)&&(t.vspace=a.vspace),r(a.border)&&(t.border=a.border),r(a.hspace)&&(t.hspace=a.hspace),r(a.borderstyle)&&(t.borderstyle=a.borderstyle))})(e,s,i),t.setData(s)}})(t,i),((e,t,a,i)=>{const s=i.getData(),r=s.src.value,o=s.src.meta||{};o.width||o.height||!t.hasDimensions||(U(r)?e.imageSize(r).then((e=>{a.open&&i.setData({dimensions:e})})).catch((e=>console.error(e))):i.setData({dimensions:{width:"",height:""}}))})(e,t,a,i),((e,t,a)=>{const i=a.getData(),s=xe(e.imageList,i.src.value);t.prevImage=s,a.setData({images:s.map((e=>e.value)).getOr("")})})(t,a,i)},Re=(e,t,a,i)=>{const s=i.getData();var r;i.block("Uploading image"),(r=s.fileinput,(e=>0{i.unblock()}),(s=>{const r=URL.createObjectURL(s),o=()=>{i.unblock(),URL.revokeObjectURL(r)},n=s=>{i.setData({src:{value:s,meta:{}}}),i.showTab("general"),Me(e,t,a,i),i.focus("src")};var l;(l=s,new Promise(((e,t)=>{const a=new FileReader;a.onload=()=>{e(a.result)},a.onerror=()=>{var e;t(null===(e=a.error)||void 0===e?void 0:e.message)},a.readAsDataURL(l)}))).then((a=>{const l=e.createBlobCache(s,r,a);t.automaticUploads?e.uploadImage(l).then((e=>{n(e.url),o()})).catch((t=>{o(),e.alertErr(t,(()=>{i.focus("fileinput")}))})):(e.addToBlobCache(l),n(l.blobUri()),i.unblock())}))}))},ke=(e,t,a)=>(i,s)=>{"src"===s.name?Me(e,t,a,i):"images"===s.name?((e,t,a,i)=>{const s=i.getData(),r=xe(t.imageList,s.images);r.each((e=>{const t=""===s.alt||a.prevImage.map((e=>e.text===s.alt)).getOr(!1);t?""===e.value?i.setData({src:e,alt:a.prevAlt}):i.setData({src:e,alt:e.text}):i.setData({src:e})})),a.prevImage=r,Me(e,t,a,i)})(e,t,a,i):"alt"===s.name?a.prevAlt=i.getData().alt:"fileinput"===s.name?Re(e,t,a,i):"isDecorative"===s.name&&i.setEnabled("alt",!i.getData().isDecorative)},ze=e=>()=>{e.open=!1},Be=e=>e.hasAdvTab||e.hasUploadUrl||e.hasUploadHandler?{type:"tabpanel",tabs:w([[Ee(e)],e.hasAdvTab?[{title:"Advanced",name:"advanced",items:[{type:"grid",columns:2,items:[{type:"input",label:"Vertical space",name:"vspace",inputMode:"numeric"},{type:"input",label:"Horizontal space",name:"hspace",inputMode:"numeric"},{type:"input",label:"Border width",name:"border",inputMode:"numeric"},{type:"listbox",name:"borderstyle",label:"Border style",items:[{text:"Select...",value:""},{text:"Solid",value:"solid"},{text:"Dotted",value:"dotted"},{text:"Dashed",value:"dashed"},{text:"Double",value:"double"},{text:"Groove",value:"groove"},{text:"Ridge",value:"ridge"},{text:"Inset",value:"inset"},{text:"Outset",value:"outset"},{text:"None",value:"none"},{text:"Hidden",value:"hidden"}]}]}]}]:[],e.hasUploadTab&&(e.hasUploadUrl||e.hasUploadHandler)?[{title:"Upload",name:"upload",items:[{type:"dropzone",name:"fileinput"}]}]:[]])}:{type:"panel",items:Le(e)},Pe=(e,t,a)=>i=>{const s=fe(Oe(t.image),i.getData()),r={...s,style:le(a.normalizeCss,je(s,!1))};e.execCommand("mceUpdateImage",!1,je(r,t.hasAccessibilityOptions)),e.editorUpload.uploadImagesAuto(),i.close()},Fe=e=>t=>G(e,t)?(e=>new Promise((t=>{const a=document.createElement("img"),i=e=>{a.parentNode&&a.parentNode.removeChild(a),t(e)};a.addEventListener("load",(()=>{const e={width:B(a.width,a.clientWidth),height:B(a.height,a.clientHeight)};i(Promise.resolve(e))})),a.addEventListener("error",(()=>{i(Promise.reject(`Failed to get image dimensions for: ${e}`))}));const s=a.style;s.visibility="hidden",s.position="fixed",s.bottom=s.left="0px",s.width=s.height="auto",document.body.appendChild(a),a.src=e})))(e.documentBaseURI.toAbsolute(t)).then((e=>({width:String(e.width),height:String(e.height)}))):Promise.resolve({width:"",height:""}),He=e=>(t,a,i)=>{var s;return e.editorUpload.blobCache.create({blob:t,blobUri:a,name:null===(s=t.name)||void 0===s?void 0:s.replace(/\.[^\.]+$/,""),filename:t.name,base64:i.split(",")[1]})},Ge=e=>t=>{e.editorUpload.blobCache.add(t)},We=e=>(t,a)=>{e.windowManager.alert(t,a)},$e=e=>t=>pe(e,t),Ve=e=>t=>e.dom.parseStyle(t),Ke=e=>(t,a)=>e.dom.serializeStyle(t,a),Ze=e=>t=>Ae(e).upload([t],!1).then((e=>{var t;return 0===e.length?Promise.reject("Failed to upload image"):!1===e[0].status?Promise.reject(null===(t=e[0].error)||void 0===t?void 0:t.message):e[0]})),qe=e=>{const t={imageSize:Fe(e),addToBlobCache:Ge(e),createBlobCache:He(e),alertErr:We(e),normalizeCss:$e(e),parseStyle:Ve(e),serializeStyle:Ke(e),uploadImage:Ze(e)};return{open:()=>{Ne(e).then((a=>{const i=(e=>({prevImage:xe(e.imageList,e.image.src),prevAlt:e.image.alt,open:!0}))(a);return{title:"Insert/Edit Image",size:"normal",body:Be(a),buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:Oe(a.image),onSubmit:Pe(e,a,t),onChange:ke(t,a,i),onClose:ze(i)}})).then(e.windowManager.open)}}},Je=e=>{const t=e.attr("class");return d(t)&&/\bimage\b/.test(t)},Qe=e=>t=>{let a=t.length;const i=t=>{t.attr("contenteditable",e?"true":null)};for(;a--;){const s=t[a];Je(s)&&(s.attr("contenteditable",e?"false":null),De.each(s.getAll("figcaption"),i))}},Xe=e=>t=>{const a=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",a),a(),()=>{e.off("NodeChange",a)}};e.add("image",(e=>{(e=>{const t=e.options.register;t("image_dimensions",{processor:"boolean",default:!0}),t("image_advtab",{processor:"boolean",default:!1}),t("image_uploadtab",{processor:"boolean",default:!0}),t("image_prepend_url",{processor:"string",default:""}),t("image_class_list",{processor:"object[]"}),t("image_description",{processor:"boolean",default:!0}),t("image_title",{processor:"boolean",default:!1}),t("image_caption",{processor:"boolean",default:!1}),t("image_list",{processor:e=>{const t=!1===e||r(e)||((e,t)=>{if(l(e)){for(let a=0,i=e.length;a{e.on("PreInit",(()=>{e.parser.addNodeFilter("figure",Qe(!0)),e.serializer.addNodeFilter("figure",Qe(!1))}))})(e),(e=>{e.ui.registry.addToggleButton("image",{icon:"image",tooltip:"Insert/edit image",onAction:qe(e).open,onSetup:t=>{t.setActive(d(he(e)));const a=e.selection.selectorChangedWithUnbind("img:not([data-mce-object]):not([data-mce-placeholder]),figure.image",t.setActive).unbind,i=Xe(e)(t);return()=>{a(),i()}}}),e.ui.registry.addMenuItem("image",{icon:"image",text:"Image...",onAction:qe(e).open,onSetup:Xe(e)}),e.ui.registry.addContextMenu("image",{update:t=>e.selection.isEditable()&&(re(t)||"IMG"===t.nodeName&&!H(t))?["image"]:[]})})(e),(e=>{e.addCommand("mceImage",qe(e).open),e.addCommand("mceUpdateImage",((t,a)=>{e.undoManager.transact((()=>ye(e,a)))}))})(e)}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/importcss/plugin.min.js b/apps/web-antd/public/tinymce/plugins/importcss/plugin.min.js new file mode 100644 index 0000000..b1b1a5e --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/importcss/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(s=r=e,(o=String).prototype.isPrototypeOf(s)||(null===(n=r.constructor)||void 0===n?void 0:n.name)===o.name)?"string":t;var s,r,o,n})(t)===e,s=t("string"),r=t("object"),o=t("array"),n=e=>"function"==typeof e;var c=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),i=tinymce.util.Tools.resolve("tinymce.EditorManager"),l=tinymce.util.Tools.resolve("tinymce.Env"),a=tinymce.util.Tools.resolve("tinymce.util.Tools");const p=e=>t=>t.options.get(e),u=p("importcss_merge_classes"),m=p("importcss_exclusive"),f=p("importcss_selector_converter"),y=p("importcss_selector_filter"),d=p("importcss_groups"),h=p("importcss_append"),g=p("importcss_file_filter"),_=p("skin"),v=p("skin_url"),b=Array.prototype.push,x=/^\.(?:ephox|tiny-pageembed|mce)(?:[.-]+\w+)+$/,T=e=>s(e)?t=>-1!==t.indexOf(e):e instanceof RegExp?t=>e.test(t):e,S=(e,t)=>{let s={};const r=/^(?:([a-z0-9\-_]+))?(\.[a-z0-9_\-\.]+)$/i.exec(t);if(!r)return;const o=r[1],n=r[2].substr(1).split(".").join(" "),c=a.makeMap("a,img");return r[1]?(s={title:t},e.schema.getTextBlockElements()[o]?s.block=o:e.schema.getBlockElements()[o]||c[o.toLowerCase()]?s.selector=o:s.inline=o):r[2]&&(s={inline:"span",title:t.substr(1),classes:n}),u(e)?s.classes=n:s.attributes={class:n},s},k=(e,t)=>null===t||m(e),M=e=>{e.on("init",(()=>{const t=(()=>{const e=[],t=[],s={};return{addItemToGroup:(e,r)=>{s[e]?s[e].push(r):(t.push(e),s[e]=[r])},addItem:t=>{e.push(t)},toFormats:()=>{return(r=t,n=e=>{const t=s[e];return 0===t.length?[]:[{title:e,items:t}]},(e=>{const t=[];for(let s=0,r=e.length;s{const s=e.length,r=new Array(s);for(let o=0;oa.map(e,(e=>a.extend({},e,{original:e,selectors:{},filter:T(e.filter)}))))(d(e)),u=(t,s)=>{if(((e,t,s,r)=>!(k(e,s)?t in r:t in s.selectors))(e,t,s,r)){((e,t,s,r)=>{k(e,s)?r[t]=!0:s.selectors[t]=!0})(e,t,s,r);const o=((e,t,s,r)=>{let o;const n=f(e);return o=r&&r.selector_converter?r.selector_converter:n||(()=>S(e,s)),o.call(t,s,r)})(e,e.plugins.importcss,t,s);if(o){const t=o.name||c.DOM.uniqueId();return e.formatter.register(t,o),{title:o.title,format:t}}}return null};a.each(((e,t,r)=>{const o=[],n={},c=(t,n)=>{let p,u=t.href;if(u=(e=>{const t=l.cacheSuffix;return s(e)&&(e=e.replace("?"+t,"").replace("&"+t,"")),e})(u),u&&(!r||r(u,n))&&!((e,t)=>{const s=_(e);if(s){const r=v(e),o=r?e.documentBaseURI.toAbsolute(r):i.baseURL+"/skins/ui/"+s,n=i.baseURL+"/skins/content/",c=e.editorManager.suffix;return t===o+"/content"+(e.inline?".inline":"")+`${c}.css`||-1!==t.indexOf(n)}return!1})(e,u)){a.each(t.imports,(e=>{c(e,!0)}));try{p=t.cssRules||t.rules}catch(e){}a.each(p,(e=>{e.styleSheet&&e.styleSheet?c(e.styleSheet,!0):e.selectorText&&a.each(e.selectorText.split(","),(e=>{o.push(a.trim(e))}))}))}};a.each(e.contentCSS,(e=>{n[e]=!0})),r||(r=(e,t)=>t||n[e]);try{a.each(t.styleSheets,(e=>{c(e)}))}catch(e){}return o})(e,e.getDoc(),T(g(e))),(e=>{if(!x.test(e)&&(!n||n(e))){const s=((e,t)=>a.grep(e,(e=>!e.filter||e.filter(t))))(p,e);if(s.length>0)a.each(s,(s=>{const r=u(e,s);r&&t.addItemToGroup(s.title,r)}));else{const s=u(e,null);s&&t.addItem(s)}}}));const m=t.toFormats();e.dispatch("addStyleModifications",{items:m,replace:!h(e)})}))};e.add("importcss",(e=>((e=>{const t=e.options.register,o=e=>s(e)||n(e)||r(e);t("importcss_merge_classes",{processor:"boolean",default:!0}),t("importcss_exclusive",{processor:"boolean",default:!0}),t("importcss_selector_converter",{processor:"function"}),t("importcss_selector_filter",{processor:o}),t("importcss_file_filter",{processor:o}),t("importcss_groups",{processor:"object[]"}),t("importcss_append",{processor:"boolean",default:!1})})(e),M(e),(e=>({convertSelectorToFormat:t=>S(e,t)}))(e))))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/insertdatetime/plugin.min.js b/apps/web-antd/public/tinymce/plugins/insertdatetime/plugin.min.js new file mode 100644 index 0000000..8a05382 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/insertdatetime/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),a=t("insertdatetime_dateformat"),n=t("insertdatetime_timeformat"),r=t("insertdatetime_formats"),s=t("insertdatetime_element"),i="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),o="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),l="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),m="January February March April May June July August September October November December".split(" "),c=(e,t)=>{if((e=""+e).length(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=t.replace("%D","%m/%d/%Y")).replace("%r","%I:%M:%S %p")).replace("%Y",""+a.getFullYear())).replace("%y",""+a.getYear())).replace("%m",c(a.getMonth()+1,2))).replace("%d",c(a.getDate(),2))).replace("%H",""+c(a.getHours(),2))).replace("%M",""+c(a.getMinutes(),2))).replace("%S",""+c(a.getSeconds(),2))).replace("%I",""+((a.getHours()+11)%12+1))).replace("%p",a.getHours()<12?"AM":"PM")).replace("%B",""+e.translate(m[a.getMonth()]))).replace("%b",""+e.translate(l[a.getMonth()]))).replace("%A",""+e.translate(o[a.getDay()]))).replace("%a",""+e.translate(i[a.getDay()]))).replace("%%","%"),u=(e,t)=>{if(s(e)&&e.selection.isEditable()){const a=d(e,t);let n;n=/%[HMSIp]/.test(t)?d(e,"%Y-%m-%dT%H:%M"):d(e,"%Y-%m-%d");const r=e.dom.getParent(e.selection.getStart(),"time");r?((e,t,a,n)=>{const r=e.dom.create("time",{datetime:a},n);e.dom.replace(r,t),e.selection.select(r,!0),e.selection.collapse(!1)})(e,r,n,a):e.insertContent('")}else e.insertContent(d(e,t))};var p=tinymce.util.Tools.resolve("tinymce.util.Tools");const g=e=>t=>{const a=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",a),a(),()=>{e.off("NodeChange",a)}};e.add("insertdatetime",(e=>{(e=>{const t=e.options.register;t("insertdatetime_dateformat",{processor:"string",default:e.translate("%Y-%m-%d")}),t("insertdatetime_timeformat",{processor:"string",default:e.translate("%H:%M:%S")}),t("insertdatetime_formats",{processor:"string[]",default:["%H:%M:%S","%Y-%m-%d","%I:%M:%S %p","%D"]}),t("insertdatetime_element",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mceInsertDate",((t,n)=>{u(e,null!=n?n:a(e))})),e.addCommand("mceInsertTime",((t,a)=>{u(e,null!=a?a:n(e))}))})(e),(e=>{const t=r(e),a=(e=>{let t=e;return{get:()=>t,set:e=>{t=e}}})((e=>{const t=r(e);return t.length>0?t[0]:n(e)})(e)),s=t=>e.execCommand("mceInsertDate",!1,t);e.ui.registry.addSplitButton("insertdatetime",{icon:"insert-time",tooltip:"Insert date/time",select:e=>e===a.get(),fetch:a=>{a(p.map(t,(t=>({type:"choiceitem",text:d(e,t),value:t}))))},onAction:e=>{s(a.get())},onItemAction:(e,t)=>{a.set(t),s(t)},onSetup:g(e)});const i=e=>()=>{a.set(e),s(e)};e.ui.registry.addNestedMenuItem("insertdatetime",{icon:"insert-time",text:"Date/time",getSubmenuItems:()=>p.map(t,(t=>({type:"menuitem",text:d(e,t),onAction:i(t)}))),onSetup:g(e)})})(e)}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/link/plugin.min.js b/apps/web-antd/public/tinymce/plugins/link/plugin.min.js new file mode 100644 index 0000000..d8c40c7 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/link/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=o=e,(r=String).prototype.isPrototypeOf(n)||(null===(l=o.constructor)||void 0===l?void 0:l.name)===r.name)?"string":t;var n,o,r,l})(t)===e,n=e=>t=>typeof t===e,o=t("string"),r=t("object"),l=t("array"),s=e=>null===e;const i=n("boolean"),a=e=>!(e=>null==e)(e),c=n("function"),u=(e,t)=>{if(l(e)){for(let n=0,o=e.length;n{},d=(e,t)=>e===t;class m{constructor(e,t){this.tag=e,this.value=t}static some(e){return new m(!0,e)}static none(){return m.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?m.some(e(this.value)):m.none()}bind(e){return this.tag?e(this.value):m.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:m.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return a(e)?m.some(e):m.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}m.singletonNone=new m(!1);const h=Array.prototype.indexOf,p=Array.prototype.push,f=e=>{const t=[];for(let n=0,o=e.length;n{for(let n=0;ne.exists((e=>n(e,t))),b=e=>{const t=[],n=e=>{t.push(e)};for(let t=0;te?m.some(t):m.none(),y=e=>t=>t.options.get(e),_=y("link_assume_external_targets"),w=y("link_context_toolbar"),C=y("link_list"),O=y("link_default_target"),S=y("link_default_protocol"),A=y("link_target_list"),N=y("link_rel_list"),E=y("link_class_list"),R=y("link_title"),T=y("allow_unsafe_link_target"),L=y("link_quicklink"),P=y("link_attributes_postprocess"),M=Object.keys,D=Object.hasOwnProperty,B=(e,t)=>D.call(e,t);var I=tinymce.util.Tools.resolve("tinymce.util.URI"),K=tinymce.util.Tools.resolve("tinymce.dom.TreeWalker"),j=tinymce.util.Tools.resolve("tinymce.util.Tools");const U=e=>a(e)&&"a"===e.nodeName.toLowerCase(),q=e=>U(e)&&!!$(e),F=(e,t)=>{if(e.collapsed)return[];{const n=e.cloneContents(),o=n.firstChild,r=new K(o,n),l=[];let s=o;do{t(s)&&l.push(s)}while(s=r.next());return l}},V=e=>/^\w+:/i.test(e),$=e=>{var t,n;return null!==(n=null!==(t=e.getAttribute("data-mce-href"))&&void 0!==t?t:e.getAttribute("href"))&&void 0!==n?n:""},z=(e,t)=>{const n=["noopener"],o=e?e.split(/\s+/):[],r=e=>e.filter((e=>-1===j.inArray(n,e))),l=t?(e=>(e=r(e)).length>0?e.concat(n):n)(o):r(o);return l.length>0?(e=>j.trim(e.sort().join(" ")))(l):""},G=(e,t)=>(t=t||W(e.selection.getRng())[0]||e.selection.getNode(),Z(t)?m.from(e.dom.select("a[href]",t)[0]):m.from(e.dom.getParent(t,"a[href]"))),H=(e,t)=>G(e,t).isSome(),J=(e,t)=>t.fold((()=>e.getContent({format:"text"})),(e=>e.innerText||e.textContent||"")).replace(/\uFEFF/g,""),W=e=>F(e,q),Q=e=>j.grep(e,q),X=e=>Q(e).length>0,Y=e=>{const t=e.schema.getTextInlineElements();if(G(e).exists((e=>e.hasAttribute("data-mce-block"))))return!1;const n=e.selection.getRng();return!!n.collapsed||0===F(n,(e=>1===e.nodeType&&!U(e)&&!B(t,e.nodeName.toLowerCase()))).length},Z=e=>a(e)&&"FIGURE"===e.nodeName&&/\bimage\b/i.test(e.className),ee=(e,t,n)=>{const o=e.selection.getNode(),r=G(e,o),l=((e,t)=>{const n={...t};if(0===N(e).length&&!T(e)){const e=z(n.rel,"_blank"===n.target);n.rel=e||null}return m.from(n.target).isNone()&&!1===A(e)&&(n.target=O(e)),n.href=((e,t)=>"http"!==t&&"https"!==t||V(e)?e:t+"://"+e)(n.href,_(e)),n})(e,(e=>{return t=["title","rel","class","target"],n=(t,n)=>(e[n].each((e=>{t[n]=e.length>0?e:null})),t),o={href:e.href},((e,t)=>{for(let n=0,o=e.length;n{o=n(o,e)})),o;var t,n,o})(n)),s=P(e);a(s)&&s(l),e.undoManager.transact((()=>{n.href===t.href&&t.attach(),r.fold((()=>{((e,t,n,o)=>{const r=e.dom;Z(t)?le(r,t,o):n.fold((()=>{e.execCommand("mceInsertLink",!1,o);const t=e.selection.getEnd(),n=r.createRng();n.setStartAfter(t),n.setEndAfter(t),e.selection.setRng(n)}),(t=>{e.insertContent(r.createHTML("a",o,r.encode(t)))}))})(e,o,n.text,l)}),(t=>{e.focus(),((e,t,n,o)=>{n.each((e=>{B(t,"innerText")?t.innerText=e:t.textContent=e})),e.dom.setAttribs(t,o);const r=e.dom.createRng();r.setStartAfter(t),r.setEndAfter(t),e.selection.setRng(r)})(e,t,n.text,l)}))}))},te=e=>{const{class:t,href:n,rel:o,target:r,text:l,title:i}=e;return(e=>{const t={};var n;return((e,t,n,o)=>{((e,t)=>{const n=M(e);for(let o=0,r=n.length;o{(t(e,r)?n:o)(e,r)}))})(e,((e,t)=>!1===s(e)),(n=t,(e,t)=>{n[t]=e}),g),t})({class:t.getOrNull(),href:n,rel:o.getOrNull(),target:r.getOrNull(),text:l.getOrNull(),title:i.getOrNull()})},ne=(e,t,n)=>{const o=((e,t)=>{const n=e.options.get,o={allow_html_data_urls:n("allow_html_data_urls"),allow_script_urls:n("allow_script_urls"),allow_svg_data_urls:n("allow_svg_data_urls")},r=t.href;return{...t,href:I.isDomSafe(r,"a",o)?r:""}})(e,n);e.hasPlugin("rtc",!0)?e.execCommand("createlink",!1,te(o)):ee(e,t,o)},oe=e=>{e.hasPlugin("rtc",!0)?e.execCommand("unlink"):(e=>{e.undoManager.transact((()=>{const t=e.selection.getNode();Z(t)?re(e,t):(e=>{const t=e.dom,n=e.selection,o=n.getBookmark(),r=n.getRng().cloneRange(),l=t.getParent(r.startContainer,"a[href]",e.getBody()),s=t.getParent(r.endContainer,"a[href]",e.getBody());l&&r.setStartBefore(l),s&&r.setEndAfter(s),n.setRng(r),e.execCommand("unlink"),n.moveToBookmark(o)})(e),e.focus()}))})(e)},re=(e,t)=>{var n;const o=e.dom.select("img",t)[0];if(o){const r=e.dom.getParents(o,"a[href]",t)[0];r&&(null===(n=r.parentNode)||void 0===n||n.insertBefore(o,r),e.dom.remove(r))}},le=(e,t,n)=>{var o;const r=e.select("img",t)[0];if(r){const t=e.create("a",n);null===(o=r.parentNode)||void 0===o||o.insertBefore(t,r),t.appendChild(r)}},se=e=>o(e.value)?e.value:"",ie=(e,t)=>{const n=[];return j.each(e,(e=>{const r=(e=>o(e.text)?e.text:o(e.title)?e.title:"")(e);if(void 0!==e.menu){const o=ie(e.menu,t);n.push({text:r,items:o})}else{const o=t(e);n.push({text:r,value:o})}})),n},ae=(e=se)=>t=>m.from(t).map((t=>ie(t,e))),ce=e=>ae(se)(e),ue=ae,ge=(e,t)=>n=>({name:e,type:"listbox",label:t,items:n}),de=se,me=(e,t)=>k(t,(t=>(e=>{return B(t=e,n="items")&&void 0!==t[n]&&null!==t[n];var t,n})(t)?me(e,t.items):x(t.value===e,t))),he=(e,t)=>{const n={text:e.text,title:e.title},o=(e,o)=>{const r=(l=t,s=o,"link"===s?l.link:"anchor"===s?l.anchor:m.none()).getOr([]);var l,s;return((e,t,n,o)=>{const r=o[t],l=e.length>0;return void 0!==r?me(r,n).map((t=>({url:{value:t.value,meta:{text:l?e:t.text,attach:g}},text:l?e:t.text}))):m.none()})(n.text,o,r,e)};return{onChange:(e,t)=>{const r=t.name;return"url"===r?(e=>{const t=(o=e.url,x(n.text.length<=0,m.from(null===(r=o.meta)||void 0===r?void 0:r.text).getOr(o.value)));var o,r;const l=(e=>{var t;return x(n.title.length<=0,m.from(null===(t=e.meta)||void 0===t?void 0:t.title).getOr(""))})(e.url);return t.isSome()||l.isSome()?m.some({...t.map((e=>({text:e}))).getOr({}),...l.map((e=>({title:e}))).getOr({})}):m.none()})(e()):((e,t)=>h.call(e,t))(["anchor","link"],r)>-1?o(e(),r):"text"===r||"title"===r?(n[r]=e()[r],m.none()):m.none()}}};var pe=tinymce.util.Tools.resolve("tinymce.util.Delay");const fe=e=>{const t=e.href;return t.indexOf("@")>0&&-1===t.indexOf("/")&&-1===t.indexOf("mailto:")?m.some({message:"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",preprocess:e=>({...e,href:"mailto:"+t})}):m.none()},ke=(e,t)=>n=>{const o=n.href;return 1===e&&!V(o)||0===e&&/^\s*www(\.|\d\.)/i.test(o)?m.some({message:`The URL you entered seems to be an external link. Do you want to add the required ${t}:// prefix?`,preprocess:e=>({...e,href:t+"://"+o})}):m.none()},ve=e=>{const t=e.dom.select("a:not([href])"),n=f(((e,t)=>{const n=e.length,o=new Array(n);for(let r=0;r{const t=e.name||e.id;return t?[{text:t,value:"#"+t}]:[]})));return n.length>0?m.some([{text:"None",value:""}].concat(n)):m.none()},be=e=>{const t=E(e);return t.length>0?ce(t):m.none()},xe=e=>{try{return m.some(JSON.parse(e))}catch(e){return m.none()}},ye=(e,t)=>{const n=N(e);if(n.length>0){const o=v(t,"_blank"),r=e=>z(de(e),o);return(!1===T(e)?ue(r):ce)(n)}return m.none()},_e=[{text:"Current window",value:""},{text:"New window",value:"_blank"}],we=e=>{const t=A(e);return l(t)?ce(t).orThunk((()=>m.some(_e))):!1===t?m.none():m.some(_e)},Ce=(e,t,n)=>{const o=e.getAttrib(t,n);return null!==o&&o.length>0?m.some(o):m.none()},Oe=(e,t)=>(e=>{const t=t=>e.convertURL(t.value||t.url||"","href"),n=C(e);return new Promise((e=>{o(n)?fetch(n).then((e=>e.ok?e.text().then(xe):Promise.reject())).then(e,(()=>e(m.none()))):c(n)?n((t=>e(m.some(t)))):e(m.from(n))})).then((e=>e.bind(ue(t)).map((e=>e.length>0?[{text:"None",value:""}].concat(e):e))))})(e).then((n=>{const o=((e,t)=>{const n=e.dom,o=Y(e)?m.some(J(e.selection,t)):m.none(),r=t.bind((e=>m.from(n.getAttrib(e,"href")))),l=t.bind((e=>m.from(n.getAttrib(e,"target")))),s=t.bind((e=>Ce(n,e,"rel"))),i=t.bind((e=>Ce(n,e,"class")));return{url:r,text:o,title:t.bind((e=>Ce(n,e,"title"))),target:l,rel:s,linkClass:i}})(e,t);return{anchor:o,catalogs:{targets:we(e),rels:ye(e,o.target),classes:be(e),anchor:ve(e),link:n},optNode:t,flags:{titleEnabled:R(e)}}})),Se=e=>{const t=(e=>{const t=G(e);return Oe(e,t)})(e);t.then((t=>{const n=((e,t)=>n=>{const o=n.getData();if(!o.url.value)return oe(e),void n.close();const r=e=>m.from(o[e]).filter((n=>!v(t.anchor[e],n))),l={href:o.url.value,text:r("text"),target:r("target"),rel:r("rel"),class:r("linkClass"),title:r("title")},s={href:o.url.value,attach:void 0!==o.url.meta&&o.url.meta.attach?o.url.meta.attach:g};((e,t)=>k([fe,ke(_(e),S(e))],(e=>e(t))).fold((()=>Promise.resolve(t)),(n=>new Promise((o=>{((e,t,n)=>{const o=e.selection.getRng();pe.setEditorTimeout(e,(()=>{e.windowManager.confirm(t,(t=>{e.selection.setRng(o),n(t)}))}))})(e,n.message,(e=>{o(e?n.preprocess(t):t)}))})))))(e,l).then((t=>{ne(e,s,t)})),n.close()})(e,t);return((e,t,n)=>{const o=e.anchor.text.map((()=>({name:"text",type:"input",label:"Text to display"}))).toArray(),r=e.flags.titleEnabled?[{name:"title",type:"input",label:"Title"}]:[],l=((e,t)=>{const n=e.anchor,o=n.url.getOr("");return{url:{value:o,meta:{original:{value:o}}},text:n.text.getOr(""),title:n.title.getOr(""),anchor:o,link:o,rel:n.rel.getOr(""),target:n.target.or(t).getOr(""),linkClass:n.linkClass.getOr("")}})(e,m.from(O(n))),s=e.catalogs,i=he(l,s);return{title:"Insert/Edit Link",size:"normal",body:{type:"panel",items:f([[{name:"url",type:"urlinput",filetype:"file",label:"URL",picker_text:"Browse links"}],o,r,b([s.anchor.map(ge("anchor","Anchors")),s.rels.map(ge("rel","Rel")),s.targets.map(ge("target","Open link in...")),s.link.map(ge("link","Link list")),s.classes.map(ge("linkClass","Class"))])])},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:l,onChange:(e,{name:t})=>{i.onChange(e.getData,{name:t}).each((t=>{e.setData(t)}))},onSubmit:t}})(t,n,e)})).then((t=>{e.windowManager.open(t)}))};var Ae=tinymce.util.Tools.resolve("tinymce.util.VK");const Ne=(e,t)=>{if(t){const o=$(t);if(/^#/.test(o)){const t=e.dom.select(`${o},[name="${n=o,((e,t)=>((e,t)=>""===t||e.length>=t.length&&e.substr(0,0+t.length)===t)(e,t))(n,"#")?(e=>e.substring(1))(n):n}"]`);t.length&&e.selection.scrollIntoView(t[0],!0)}else(e=>{const t=document.createElement("a");t.target="_blank",t.href=e,t.rel="noreferrer noopener";const n=new MouseEvent("click",{bubbles:!0,cancelable:!0,view:window});document.dispatchEvent(n),((e,t)=>{document.body.appendChild(e),e.dispatchEvent(t),document.body.removeChild(e)})(t,n)})(t.href)}var n},Ee=(e,t)=>{const n=Q(e.dom.getParents(t));return x(1===n.length,n[0])},Re=e=>e.selection.isCollapsed()||(e=>{const t=e.selection.getRng(),n=t.startContainer;return q(n)&&t.startContainer===t.endContainer&&1===e.dom.select("img",n).length})(e)?Ee(e,e.selection.getStart()):(e=>{const t=W(e.selection.getRng());return x(t.length>0,t[0]).or(Ee(e,e.selection.getNode()))})(e),Te=e=>()=>{e.execCommand("mceLink",!1,{dialog:!0})},Le=(e,t)=>(e.on("NodeChange",t),()=>e.off("NodeChange",t)),Pe=e=>t=>{const n=()=>{t.setActive(!e.mode.isReadOnly()&&H(e,e.selection.getNode())),t.setEnabled(e.selection.isEditable())};return n(),Le(e,n)},Me=e=>t=>{const n=()=>{t.setEnabled(e.selection.isEditable())};return n(),Le(e,n)},De=e=>t=>{const n=e.dom.getParents(e.selection.getStart()),o=n=>{t.setEnabled((t=>{return X(t)||(n=e.selection.getRng(),W(n).length>0);var n})(n)&&e.selection.isEditable())};return o(n),Le(e,(e=>o(e.parents)))},Be=e=>{const t=(e=>{const t=(()=>{const e=(e=>{const t=(e=>{let t=e;return{get:()=>t,set:e=>{t=e}}})(m.none()),n=()=>t.get().each(e);return{clear:()=>{n(),t.set(m.none())},isSet:()=>t.get().isSome(),get:()=>t.get(),set:e=>{n(),t.set(m.some(e))}}})(g);return{...e,on:t=>e.get().each(t)}})(),n=()=>t.get().or(Re(e));return e.on("contextmenu",(n=>{Ee(e,n.target).each(t.set)})),e.on("SelectionChange",(()=>{t.isSet()||Re(e).each(t.set)})),e.on("click",(n=>{t.clear();const o=Q(e.dom.getParents(n.target));1===o.length&&Ae.metaKeyPressed(n)&&(n.preventDefault(),Ne(e,o[0]))})),e.on("keydown",(o=>{t.clear(),!o.isDefaultPrevented()&&13===o.keyCode&&(e=>!0===e.altKey&&!1===e.shiftKey&&!1===e.ctrlKey&&!1===e.metaKey)(o)&&n().each((t=>{o.preventDefault(),Ne(e,t)}))})),{gotoSelectedLink:()=>n().each((t=>Ne(e,t)))}})(e);((e,t)=>{e.ui.registry.addToggleButton("link",{icon:"link",tooltip:"Insert/edit link",shortcut:"Meta+K",onAction:Te(e),onSetup:Pe(e)}),e.ui.registry.addButton("openlink",{icon:"new-tab",tooltip:"Open link",onAction:t.gotoSelectedLink,onSetup:De(e)}),e.ui.registry.addButton("unlink",{icon:"unlink",tooltip:"Remove link",onAction:()=>oe(e),onSetup:De(e)})})(e,t),((e,t)=>{e.ui.registry.addMenuItem("openlink",{text:"Open link",icon:"new-tab",onAction:t.gotoSelectedLink,onSetup:De(e)}),e.ui.registry.addMenuItem("link",{icon:"link",text:"Link...",shortcut:"Meta+K",onAction:Te(e),onSetup:Me(e)}),e.ui.registry.addMenuItem("unlink",{icon:"unlink",text:"Remove link",onAction:()=>oe(e),onSetup:De(e)})})(e,t),(e=>{e.ui.registry.addContextMenu("link",{update:t=>e.dom.isEditable(t)?X(e.dom.getParents(t,"a"))?"link unlink openlink":"link":""})})(e),((e,t)=>{const n=t=>{const n=e.selection.getNode();return t.setEnabled(H(e,n)&&e.selection.isEditable()),g};e.ui.registry.addContextForm("quicklink",{launch:{type:"contextformtogglebutton",icon:"link",tooltip:"Link",onSetup:Pe(e)},label:"Link",predicate:t=>w(e)&&H(e,t),initValue:()=>G(e).fold((()=>""),$),commands:[{type:"contextformtogglebutton",icon:"link",tooltip:"Link",primary:!0,onSetup:t=>{const n=e.selection.getNode();return t.setActive(H(e,n)),Pe(e)(t)},onAction:t=>{const n=t.getValue(),o=(t=>{const n=G(e),o=Y(e);if(n.isNone()&&o){const o=J(e.selection,n);return x(0===o.length,t)}return m.none()})(n);ne(e,{href:n,attach:g},{href:n,text:o,title:m.none(),rel:m.none(),target:m.from(O(e)),class:m.none()}),(e=>{e.selection.collapse(!1)})(e),t.hide()}},{type:"contextformbutton",icon:"unlink",tooltip:"Remove link",onSetup:n,onAction:t=>{oe(e),t.hide()}},{type:"contextformbutton",icon:"new-tab",tooltip:"Open link",onSetup:n,onAction:e=>{t.gotoSelectedLink(),e.hide()}}]})})(e,t)};e.add("link",(e=>{(e=>{const t=e.options.register;t("link_assume_external_targets",{processor:e=>{const t=o(e)||i(e);return t?!0===e?{value:1,valid:t}:"http"===e||"https"===e?{value:e,valid:t}:{value:0,valid:t}:{valid:!1,message:"Must be a string or a boolean."}},default:!1}),t("link_context_toolbar",{processor:"boolean",default:!1}),t("link_list",{processor:e=>o(e)||c(e)||u(e,r)}),t("link_default_target",{processor:"string"}),t("link_default_protocol",{processor:"string",default:"https"}),t("link_target_list",{processor:e=>i(e)||u(e,r),default:!0}),t("link_rel_list",{processor:"object[]",default:[]}),t("link_class_list",{processor:"object[]",default:[]}),t("link_title",{processor:"boolean",default:!0}),t("allow_unsafe_link_target",{processor:"boolean",default:!1}),t("link_quicklink",{processor:"boolean",default:!1}),t("link_attributes_postprocess",{processor:"function"})})(e),(e=>{e.addCommand("mceLink",((t,n)=>{!0!==(null==n?void 0:n.dialog)&&L(e)?e.dispatch("contexttoolbar-show",{toolbarKey:"quicklink"}):Se(e)}))})(e),Be(e),(e=>{e.addShortcut("Meta+K","",(()=>{e.execCommand("mceLink")}))})(e)}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/lists/plugin.min.js b/apps/web-antd/public/tinymce/plugins/lists/plugin.min.js new file mode 100644 index 0000000..ba49b0b --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/lists/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=o=e,(r=String).prototype.isPrototypeOf(n)||(null===(s=o.constructor)||void 0===s?void 0:s.name)===r.name)?"string":t;var n,o,r,s})(t)===e,n=e=>t=>typeof t===e,o=t("string"),r=t("object"),s=t("array"),i=n("boolean"),l=e=>!(e=>null==e)(e),a=n("function"),d=n("number"),c=()=>{},m=e=>()=>e,u=(e,t)=>e===t,p=e=>t=>!e(t),g=m(!1);class h{constructor(e,t){this.tag=e,this.value=t}static some(e){return new h(!0,e)}static none(){return h.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?h.some(e(this.value)):h.none()}bind(e){return this.tag?e(this.value):h.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:h.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return l(e)?h.some(e):h.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}h.singletonNone=new h(!1);const f=Array.prototype.slice,y=Array.prototype.indexOf,v=Array.prototype.push,C=(e,t)=>{return n=e,o=t,y.call(n,o)>-1;var n,o},b=(e,t)=>{for(let n=0,o=e.length;n{const n=e.length,o=new Array(n);for(let r=0;r{for(let n=0,o=e.length;n{const n=[];for(let o=0,r=e.length;o(S(e,((e,o)=>{n=t(n,e,o)})),n),A=(e,t,n)=>{for(let o=0,r=e.length;oA(e,t,g),x=(e,t)=>(e=>{const t=[];for(let n=0,o=e.length;n{const t=f.call(e,0);return t.reverse(),t},E=(e,t)=>t>=0&&tE(e,0),D=e=>E(e,e.length-1),B=(e,t)=>{const n=[],o=a(t)?e=>b(n,(n=>t(n,e))):e=>C(n,e);for(let t=0,r=e.length;te.exists((e=>n(e,t))),P=(e,t,n)=>e.isSome()&&t.isSome()?h.some(n(e.getOrDie(),t.getOrDie())):h.none(),I=e=>{if(null==e)throw new Error("Node cannot be null or undefined");return{dom:e}},R=(e,t)=>{const n=(t||document).createElement("div");if(n.innerHTML=e,!n.hasChildNodes()||n.childNodes.length>1){const t="HTML does not have a single root node";throw console.error(t,e),new Error(t)}return I(n.childNodes[0])},U=(e,t)=>{const n=(t||document).createElement(e);return I(n)},$=I,_=(e,t)=>{const n=e.dom;if(1!==n.nodeType)return!1;{const e=n;if(void 0!==e.matches)return e.matches(t);if(void 0!==e.msMatchesSelector)return e.msMatchesSelector(t);if(void 0!==e.webkitMatchesSelector)return e.webkitMatchesSelector(t);if(void 0!==e.mozMatchesSelector)return e.mozMatchesSelector(t);throw new Error("Browser lacks native selectors")}},H=(e,t)=>e.dom===t.dom,F=_,V="undefined"!=typeof window?window:Function("return this;")(),j=(e,t)=>((e,t)=>{let n=null!=t?t:V;for(let t=0;t{const t=j("ownerDocument.defaultView",e);return r(e)&&((e=>((e,t)=>{const n=((e,t)=>j(e,t))(e,t);if(null==n)throw new Error(e+" not available on this browser");return n})("HTMLElement",e))(t).prototype.isPrototypeOf(e)||/^HTML\w*Element$/.test(K(e).constructor.name))},Q=e=>e.dom.nodeName.toLowerCase(),W=e=>e.dom.nodeType,q=e=>t=>W(t)===e,Z=e=>G(e)&&z(e.dom),G=q(1),J=q(3),X=q(11),Y=e=>t=>G(t)&&Q(t)===e,ee=e=>h.from(e.dom.parentNode).map($),te=e=>N(e.dom.childNodes,$),ne=(e,t)=>{const n=e.dom.childNodes;return h.from(n[t]).map($)},oe=e=>ne(e,0),re=e=>ne(e,e.dom.childNodes.length-1),se=e=>$(e.dom.host),ie=e=>{const t=J(e)?e.dom.parentNode:e.dom;if(null==t||null===t.ownerDocument)return!1;const n=t.ownerDocument;return(e=>{const t=(e=>$(e.dom.getRootNode()))(e);return X(n=t)&&l(n.dom.host)?h.some(t):h.none();var n})($(t)).fold((()=>n.body.contains(t)),(o=ie,r=se,e=>o(r(e))));var o,r};var le=(e,t,n,o,r)=>e(n,o)?h.some(n):a(r)&&r(n)?h.none():t(n,o,r);const ae=(e,t,n)=>{let o=e.dom;const r=a(n)?n:g;for(;o.parentNode;){o=o.parentNode;const e=$(o);if(t(e))return h.some(e);if(r(e))break}return h.none()},de=(e,t,n)=>le(((e,t)=>t(e)),ae,e,t,n),ce=(e,t,n)=>ae(e,(e=>_(e,t)),n),me=(e,t)=>{ee(e).each((n=>{n.dom.insertBefore(t.dom,e.dom)}))},ue=(e,t)=>{e.dom.appendChild(t.dom)},pe=(e,t)=>{S(t,(t=>{ue(e,t)}))},ge=e=>{e.dom.textContent="",S(te(e),(e=>{he(e)}))},he=e=>{const t=e.dom;null!==t.parentNode&&t.parentNode.removeChild(t)};var fe=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),ye=tinymce.util.Tools.resolve("tinymce.dom.TreeWalker"),ve=tinymce.util.Tools.resolve("tinymce.util.VK");const Ce=e=>N(e,$),be=Object.keys,Ne=(e,t)=>{const n=be(e);for(let o=0,r=n.length;o{const n=e.dom;Ne(t,((e,t)=>{((e,t,n)=>{if(!(o(n)||i(n)||d(n)))throw console.error("Invalid call to Attribute.set. Key ",t,":: Value ",n,":: Element ",e),new Error("Attribute value was not simple");e.setAttribute(t,n+"")})(n,t,e)}))},Le=e=>O(e.dom.attributes,((e,t)=>(e[t.name]=t.value,e)),{}),Oe=e=>(e=>$(e.dom.cloneNode(!0)))(e),Ae=(e,t)=>{const n=((e,t)=>{const n=U(t),o=Le(e);return Se(n,o),n})(e,t);var o,r;r=n,(e=>h.from(e.dom.nextSibling).map($))(o=e).fold((()=>{ee(o).each((e=>{ue(e,r)}))}),(e=>{me(e,r)}));const s=te(e);return pe(n,s),he(e),n};var Te=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),xe=tinymce.util.Tools.resolve("tinymce.util.Tools");const ke=e=>t=>l(t)&&t.nodeName.toLowerCase()===e,Ee=e=>t=>l(t)&&e.test(t.nodeName),we=e=>l(e)&&3===e.nodeType,De=e=>l(e)&&1===e.nodeType,Be=Ee(/^(OL|UL|DL)$/),Me=Ee(/^(OL|UL)$/),Pe=ke("ol"),Ie=Ee(/^(LI|DT|DD)$/),Re=Ee(/^(DT|DD)$/),Ue=Ee(/^(TH|TD)$/),$e=ke("br"),_e=(e,t)=>l(t)&&t.nodeName in e.schema.getTextBlockElements(),He=(e,t)=>l(e)&&e.nodeName in t,Fe=(e,t)=>l(t)&&t.nodeName in e.schema.getVoidElements(),Ve=(e,t,n)=>{const o=e.isEmpty(t);return!(n&&e.select("span[data-mce-type=bookmark]",t).length>0)&&o},je=(e,t)=>e.isChildOf(t,e.getRoot()),Ke=e=>t=>t.options.get(e),ze=Ke("lists_indent_on_tab"),Qe=Ke("forced_root_block"),We=Ke("forced_root_block_attrs"),qe=(e,t,n={})=>{const o=e.dom,r=e.schema.getBlockElements(),s=o.createFragment(),i=Qe(e),l=We(e);let a,d,c=!1;for(d=o.create(i,{...l,...n.style?{style:n.style}:{}}),He(t.firstChild,r)||s.appendChild(d);a=t.firstChild;){const e=a.nodeName;c||"SPAN"===e&&"bookmark"===a.getAttribute("data-mce-type")||(c=!0),He(a,r)?(s.appendChild(a),d=null):(d||(d=o.create(i,l),s.appendChild(d)),d.appendChild(a))}return!c&&d&&d.appendChild(o.create("br",{"data-mce-bogus":"1"})),s},Ze=Te.DOM,Ge=Y("dd"),Je=Y("dt"),Xe=(e,t)=>{var n;Ge(t)?Ae(t,"dt"):Je(t)&&(n=t,h.from(n.dom.parentElement).map($)).each((n=>((e,t,n)=>{const o=Ze.select('span[data-mce-type="bookmark"]',t),r=qe(e,n),s=Ze.createRng();s.setStartAfter(n),s.setEndAfter(t);const i=s.extractContents();for(let t=i.firstChild;t;t=t.firstChild)if("LI"===t.nodeName&&e.dom.isEmpty(t)){Ze.remove(t);break}e.dom.isEmpty(i)||Ze.insertAfter(i,t),Ze.insertAfter(r,t);const l=n.parentElement;l&&Ve(e.dom,l)&&(e=>{const t=e.parentNode;t&&xe.each(o,(e=>{t.insertBefore(e,n.parentNode)})),Ze.remove(e)})(l),Ze.remove(n),Ve(e.dom,t)&&Ze.remove(t)})(e,n.dom,t.dom)))},Ye=e=>{Je(e)&&Ae(e,"dd")},et=(e,t)=>{if(we(e))return{container:e,offset:t};const n=fe.getNode(e,t);return we(n)?{container:n,offset:t>=e.childNodes.length?n.data.length:0}:n.previousSibling&&we(n.previousSibling)?{container:n.previousSibling,offset:n.previousSibling.data.length}:n.nextSibling&&we(n.nextSibling)?{container:n.nextSibling,offset:0}:{container:e,offset:t}},tt=e=>{const t=e.cloneRange(),n=et(e.startContainer,e.startOffset);t.setStart(n.container,n.offset);const o=et(e.endContainer,e.endOffset);return t.setEnd(o.container,o.offset),t},nt=["OL","UL","DL"],ot=nt.join(","),rt=(e,t)=>{const n=t||e.selection.getStart(!0);return e.dom.getParent(n,ot,lt(e,n))},st=e=>{const t=e.selection.getSelectedBlocks();return L(((e,t)=>{const n=xe.map(t,(t=>e.dom.getParent(t,"li,dd,dt",lt(e,t))||t));return B(n)})(e,t),Ie)},it=(e,t)=>{const n=e.dom.getParents(t,"TD,TH");return n.length>0?n[0]:e.getBody()},lt=(e,t)=>{const n=e.dom.getParents(t,e.dom.isBlock),o=T(n,(t=>{return(t=>t.nodeName.toLowerCase()!==Qe(e))(t)&&(n=e.schema,!Be(o=t)&&!Ie(o)&&b(nt,(e=>n.isValidChild(o.nodeName,e))));var n,o}));return o.getOr(e.getBody())},at=(e,t)=>{const n=e.dom.getParents(t,"ol,ul",lt(e,t));return D(n)},dt=(e,t)=>{const n=N(t,(t=>at(e,t).getOr(t)));return B(n)},ct=e=>/\btox\-/.test(e.className),mt=(e,t)=>A(e,Be,Ue).exists((e=>e.nodeName===t&&!ct(e))),ut=(e,t)=>null!==t&&!e.dom.isEditable(t),pt=(e,t)=>{const n=e.dom.getParent(t,"ol,ul,dl");return ut(e,n)||!e.selection.isEditable()},gt=(e,t)=>{const n=e.selection.getNode();return t({parents:e.dom.getParents(n),element:n}),e.on("NodeChange",t),()=>e.off("NodeChange",t)},ht=(e,t)=>{const n=(t||document).createDocumentFragment();return S(e,(e=>{n.appendChild(e.dom)})),$(n)},ft=(e,t,n)=>e.dispatch("ListMutation",{action:t,element:n}),yt=(vt=/^\s+|\s+$/g,e=>e.replace(vt,""));var vt;const Ct=(e,t,n)=>{((e,t,n)=>{if(!o(n))throw console.error("Invalid call to CSS.set. Property ",t,":: Value ",n,":: Element ",e),new Error("CSS value must be a string: "+n);(e=>void 0!==e.style&&a(e.style.getPropertyValue))(e)&&e.style.setProperty(t,n)})(e.dom,t,n)},bt=e=>F(e,"OL,UL"),Nt=e=>oe(e).exists(bt),St=e=>"listAttributes"in e,Lt=e=>"isComment"in e,Ot=e=>e.depth>0,At=e=>e.isSelected,Tt=e=>{const t=te(e),n=re(e).exists(bt)?t.slice(0,-1):t;return N(n,Oe)},xt=(e,t)=>{ue(e.item,t.list)},kt=(e,t)=>{const n={list:U(t,e),item:U("li",e)};return ue(n.list,n.item),n},Et=(e,t,n)=>{const o=t.slice(0,n.depth);return D(o).each((t=>{if(St(n)){const o=((e,t,n)=>{const o=U("li",e);return Se(o,t),pe(o,n),o})(e,n.itemAttributes,n.content);((e,t)=>{ue(e.list,t),e.item=t})(t,o),((e,t)=>{Q(e.list)!==t.listType&&(e.list=Ae(e.list,t.listType)),Se(e.list,t.listAttributes)})(t,n)}else if((e=>"isFragment"in e)(n))pe(t.item,n.content);else{const e=R(`\x3c!--${n.content}--\x3e`);ue(t.list,e)}})),o},wt=(e,t)=>{let n=h.none();const o=O(t,((t,o,r)=>Lt(o)?0===r?(n=h.some(o),t):Et(e,t,o):o.depth>t.length?((e,t,n)=>{const o=((e,t,n)=>{const o=[];for(let r=0;r{for(let t=1;t{for(let t=0;t{St(t)&&(Se(e.list,t.listAttributes),Se(e.item,t.itemAttributes)),pe(e.item,t.content)}))})(o,n),r=o,P(D(t),w(r),xt),t.concat(o)})(e,t,o):Et(e,t,o)),[]);return n.each((e=>{const t=R(`\x3c!--${e.content}--\x3e`);w(o).each((e=>{((e,t)=>{oe(e).fold((()=>{ue(e,t)}),(n=>{e.dom.insertBefore(t.dom,n.dom)}))})(e.list,t)}))})),w(o).map((e=>e.list))},Dt=e=>(S(e,((t,n)=>{((e,t)=>{const n=e[t].depth,o=e=>e.depth===n&&!e.dirty,r=e=>e.depthA(e.slice(t+1),o,r)))})(e,n).fold((()=>{t.dirty&&St(t)&&(e=>{e.listAttributes=((e,t)=>{const n={};var o;return((e,t,n,o)=>{Ne(e,((e,r)=>{(t(e,r)?n:o)(e,r)}))})(e,t,(o=n,(e,t)=>{o[t]=e}),c),n})(e.listAttributes,((e,t)=>"start"!==t))})(t)}),(e=>{return o=e,void(St(n=t)&&St(o)&&(n.listType=o.listType,n.listAttributes={...o.listAttributes}));var n,o}))})),e),Bt=(e,t,n,o)=>{var r,s;if(8===W(s=o)||"#comment"===Q(s))return[{depth:e+1,content:null!==(r=o.dom.nodeValue)&&void 0!==r?r:"",dirty:!1,isSelected:!1,isComment:!0}];t.each((e=>{H(e.start,o)&&n.set(!0)}));const i=((e,t,n)=>ee(e).filter(G).map((o=>({depth:t,dirty:!1,isSelected:n,content:Tt(e),itemAttributes:Le(e),listAttributes:Le(o),listType:Q(o),isInPreviousLi:!1}))))(o,e,n.get());t.each((e=>{H(e.end,o)&&n.set(!1)}));const l=re(o).filter(bt).map((o=>Pt(e,t,n,o))).getOr([]);return i.toArray().concat(l)},Mt=(e,t,n,o)=>oe(o).filter(bt).fold((()=>Bt(e,t,n,o)),(r=>{const s=O(te(o),((o,s,i)=>{if(0===i)return o;if(F(s,"LI"))return o.concat(Bt(e,t,n,s));{const t={isFragment:!0,depth:e,content:[s],isSelected:!1,dirty:!1,parentListType:Q(r)};return o.concat(t)}}),[]);return Pt(e,t,n,r).concat(s)})),Pt=(e,t,n,o)=>x(te(o),(o=>(bt(o)?Pt:Mt)(e+1,t,n,o))),It=(e,t,n)=>{const o=((e,t)=>{const n=(()=>{let e=!1;return{get:()=>e,set:t=>{e=t}}})();return N(e,(e=>({sourceList:e,entries:Pt(0,t,n,e)})))})(t,(e=>{const t=N(st(e),$);return P(T(t,p(Nt)),T(k(t),p(Nt)),((e,t)=>({start:e,end:t})))})(e));S(o,(t=>{((e,t)=>{S(L(e,At),(e=>((e,t)=>{switch(e){case"Indent":t.depth++;break;case"Outdent":t.depth--;break;case"Flatten":t.depth=0}t.dirty=!0})(t,e)))})(t.entries,n);const o=((e,t)=>x(((e,t)=>{if(0===e.length)return[];{let n=t(e[0]);const o=[];let r=[];for(let s=0,i=e.length;sw(t).exists(Ot)?((e,t)=>{const n=Dt(t);return wt(e.contentDocument,n).toArray()})(e,t):((e,t)=>{const n=Dt(t);return N(n,(t=>{const n=Lt(t)?ht([R(`\x3c!--${t.content}--\x3e`)]):ht(t.content),o=St(t)?t.itemAttributes:{};return $(qe(e,n.dom,o))}))})(e,t))))(e,t.entries);var r;S(o,(t=>{ft(e,"Indent"===n?"IndentList":"OutdentList",t.dom)})),r=t.sourceList,S(o,(e=>{me(r,e)})),he(t.sourceList)}))},Rt=(e,t)=>{const n=Ce((e=>{const t=(e=>{const t=at(e,e.selection.getStart()),n=L(e.selection.getSelectedBlocks(),Me);return t.toArray().concat(n)})(e),n=(e=>{const t=e.selection.getStart();return e.dom.getParents(t,"ol,ul",lt(e,t))})(e);return T(n,(e=>{return t=$(e),ee(t).exists((e=>Ie(e.dom)&&oe(e).exists((e=>!Be(e.dom)))&&re(e).exists((e=>!Be(e.dom)))));var t})).fold((()=>dt(e,t)),(e=>[e]))})(e)),o=Ce((e=>L(st(e),Re))(e));let r=!1;if(n.length||o.length){const s=e.selection.getBookmark();It(e,n,t),((e,t,n)=>{S(n,"Indent"===t?Ye:t=>Xe(e,t))})(e,t,o),e.selection.moveToBookmark(s),e.selection.setRng(tt(e.selection.getRng())),e.nodeChanged(),r=!0}return r},Ut=(e,t)=>!(e=>{const t=rt(e);return ut(e,t)||!e.selection.isEditable()})(e)&&Rt(e,t),$t=e=>Ut(e,"Indent"),_t=e=>Ut(e,"Outdent"),Ht=e=>Ut(e,"Flatten"),Ft=e=>"\ufeff"===e;var Vt=tinymce.util.Tools.resolve("tinymce.dom.BookmarkManager");const jt=Te.DOM,Kt=e=>{const t={},n=n=>{let o=e[n?"startContainer":"endContainer"],r=e[n?"startOffset":"endOffset"];if(De(o)){const e=jt.create("span",{"data-mce-type":"bookmark"});o.hasChildNodes()?(r=Math.min(r,o.childNodes.length-1),n?o.insertBefore(e,o.childNodes[r]):jt.insertAfter(e,o.childNodes[r])):o.appendChild(e),o=e,r=0}t[n?"startContainer":"endContainer"]=o,t[n?"startOffset":"endOffset"]=r};return n(!0),e.collapsed||n(),t},zt=e=>{const t=t=>{let n=e[t?"startContainer":"endContainer"],o=e[t?"startOffset":"endOffset"];if(n){if(De(n)&&n.parentNode){const e=n;o=(e=>{var t;let n=null===(t=e.parentNode)||void 0===t?void 0:t.firstChild,o=0;for(;n;){if(n===e)return o;De(n)&&"bookmark"===n.getAttribute("data-mce-type")||o++,n=n.nextSibling}return-1})(n),n=n.parentNode,jt.remove(e),!n.hasChildNodes()&&jt.isBlock(n)&&n.appendChild(jt.create("br"))}e[t?"startContainer":"endContainer"]=n,e[t?"startOffset":"endOffset"]=o}};t(!0),t();const n=jt.createRng();return n.setStart(e.startContainer,e.startOffset),e.endContainer&&n.setEnd(e.endContainer,e.endOffset),tt(n)},Qt=e=>{switch(e){case"UL":return"ToggleUlList";case"OL":return"ToggleOlList";case"DL":return"ToggleDLList"}},Wt=(e,t)=>{xe.each(t,((t,n)=>{e.setAttribute(n,t)}))},qt=(e,t,n)=>{((e,t,n)=>{const o=n["list-style-type"]?n["list-style-type"]:null;e.setStyle(t,"list-style-type",o)})(e,t,n),((e,t,n)=>{Wt(t,n["list-attributes"]),xe.each(e.select("li",t),(e=>{Wt(e,n["list-item-attributes"])}))})(e,t,n)},Zt=(e,t)=>l(t)&&!He(t,e.schema.getBlockElements()),Gt=(e,t,n,o)=>{let r=t[n?"startContainer":"endContainer"];const s=t[n?"startOffset":"endOffset"];De(r)&&(r=r.childNodes[Math.min(s,r.childNodes.length-1)]||r),!n&&$e(r.nextSibling)&&(r=r.nextSibling);const i=(t,n)=>{var r;const s=new ye(t,(t=>{for(;!e.dom.isBlock(t)&&t.parentNode&&o!==t;)t=t.parentNode;return t})(t)),i=n?"next":"prev";let l;for(;l=s[i]();)if(!Fe(e,l)&&!Ft(l.textContent)&&0!==(null===(r=l.textContent)||void 0===r?void 0:r.length))return h.some(l);return h.none()};if(n&&we(r))if(Ft(r.textContent))r=i(r,!1).getOr(r);else for(null!==r.parentNode&&Zt(e,r.parentNode)&&(r=r.parentNode);null!==r.previousSibling&&(Zt(e,r.previousSibling)||we(r.previousSibling));)r=r.previousSibling;if(!n&&we(r))if(Ft(r.textContent))r=i(r,!0).getOr(r);else for(null!==r.parentNode&&Zt(e,r.parentNode)&&(r=r.parentNode);null!==r.nextSibling&&(Zt(e,r.nextSibling)||we(r.nextSibling));)r=r.nextSibling;for(;r.parentNode!==o;){const t=r.parentNode;if(_e(e,r))return r;if(/^(TD|TH)$/.test(t.nodeName))return r;r=t}return r},Jt=(e,t,n)=>{const o=e.selection.getRng();let r="LI";const s=lt(e,((e,t)=>{const n=e.selection.getStart(!0),o=Gt(e,t,!0,e.getBody());return r=$(o),s=$(t.commonAncestorContainer),i=r,l=function(e,...t){return(...n)=>{const o=t.concat(n);return e.apply(null,o)}}(H,s),ae(i,l,void 0).isSome()?t.commonAncestorContainer:n;var r,s,i,l})(e,o)),i=e.dom;if("false"===i.getContentEditable(e.selection.getNode()))return;"DL"===(t=t.toUpperCase())&&(r="DT");const l=Kt(o),a=L(((e,t,n)=>{const o=[],r=e.dom,s=Gt(e,t,!0,n),i=Gt(e,t,!1,n);let l;const a=[];for(let e=s;e&&(a.push(e),e!==i);e=e.nextSibling);return xe.each(a,(t=>{var s;if(_e(e,t))return o.push(t),void(l=null);if(r.isBlock(t)||$e(t))return $e(t)&&r.remove(t),void(l=null);const i=t.nextSibling;Vt.isBookmarkNode(t)&&(Be(i)||_e(e,i)||!i&&t.parentNode===n)?l=null:(l||(l=r.create("p"),null===(s=t.parentNode)||void 0===s||s.insertBefore(l,t),o.push(l)),l.appendChild(t))})),o})(e,o,s),e.dom.isEditable);xe.each(a,(o=>{let s;const l=o.previousSibling,a=o.parentNode;Ie(a)||(l&&Be(l)&&l.nodeName===t&&((e,t,n)=>{const o=e.getStyle(t,"list-style-type");let r=n?n["list-style-type"]:"";return r=null===r?"":r,o===r})(i,l,n)?(s=l,o=i.rename(o,r),l.appendChild(o)):(s=i.create(t),a.insertBefore(s,o),s.appendChild(o),o=i.rename(o,r)),((e,t)=>{xe.each(["margin","margin-right","margin-bottom","margin-left","margin-top","padding","padding-right","padding-bottom","padding-left","padding-top"],(n=>e.setStyle(t,n,"")))})(i,o),qt(i,s,n),Yt(e.dom,s))})),e.selection.setRng(zt(l))},Xt=(e,t,n)=>{return((e,t)=>Be(e)&&e.nodeName===(null==t?void 0:t.nodeName))(t,n)&&((e,t,n)=>e.getStyle(t,"list-style-type",!0)===e.getStyle(n,"list-style-type",!0))(e,t,n)&&(o=n,t.className===o.className);var o},Yt=(e,t)=>{let n,o=t.nextSibling;if(Xt(e,t,o)){const r=o;for(;n=r.firstChild;)t.appendChild(n);e.remove(r)}if(o=t.previousSibling,Xt(e,t,o)){const r=o;for(;n=r.lastChild;)t.insertBefore(n,t.firstChild);e.remove(r)}},en=(e,t,n,o)=>{if(t.nodeName!==n){const r=e.dom.rename(t,n);qt(e.dom,r,o),ft(e,Qt(n),r)}else qt(e.dom,t,o),ft(e,Qt(n),t)},tn=(e,t,n,o)=>{if(t.classList.forEach(((e,n,o)=>{e.startsWith("tox-")&&(o.remove(e),0===o.length&&t.removeAttribute("class"))})),t.nodeName!==n){const r=e.dom.rename(t,n);qt(e.dom,r,o),ft(e,Qt(n),r)}else qt(e.dom,t,o),ft(e,Qt(n),t)},nn=e=>"list-style-type"in e,on=(e,t,n)=>{const o=rt(e);if(pt(e,o))return;const s=(e=>{const t=rt(e),n=e.selection.getSelectedBlocks();return((e,t)=>l(e)&&1===t.length&&t[0]===e)(t,n)?(e=>L(e.querySelectorAll(ot),Be))(t):L(n,(e=>Be(e)&&t!==e))})(e),i=r(n)?n:{};s.length>0?((e,t,n,o,r)=>{const s=Be(t);if(!s||t.nodeName!==o||nn(r)||ct(t)){Jt(e,o,r);const i=Kt(e.selection.getRng()),l=s?[t,...n]:n,a=s&&ct(t)?tn:en;xe.each(l,(t=>{a(e,t,o,r)})),e.selection.setRng(zt(i))}else Ht(e)})(e,o,s,t,i):((e,t,n,o)=>{if(t!==e.getBody())if(t)if(t.nodeName!==n||nn(o)||ct(t)){const r=Kt(e.selection.getRng());ct(t)&&t.classList.forEach(((e,n,o)=>{e.startsWith("tox-")&&(o.remove(e),0===o.length&&t.removeAttribute("class"))})),qt(e.dom,t,o);const s=e.dom.rename(t,n);Yt(e.dom,s),e.selection.setRng(zt(r)),Jt(e,n,o),ft(e,Qt(n),s)}else Ht(e);else Jt(e,n,o),ft(e,Qt(n),t)})(e,o,t,i)},rn=Te.DOM,sn=(e,t)=>{const n=xe.grep(e.select("ol,ul",t));xe.each(n,(t=>{((e,t)=>{const n=t.parentElement;if(n&&"LI"===n.nodeName&&n.firstChild===t){const o=n.previousSibling;o&&"LI"===o.nodeName?(o.appendChild(t),Ve(e,n)&&rn.remove(n)):rn.setStyle(n,"listStyleType","none")}if(Be(n)){const e=n.previousSibling;e&&"LI"===e.nodeName&&e.appendChild(t)}})(e,t)}))},ln=(e,t,n,o)=>{let r=t.startContainer;const s=t.startOffset;if(we(r)&&(n?s0))return r;const i=e.schema.getNonEmptyElements();De(r)&&(r=fe.getNode(r,s));const l=new ye(r,o);n&&((e,t)=>!!$e(t)&&e.isBlock(t.nextSibling)&&!$e(t.previousSibling))(e.dom,r)&&l.next();const a=n?l.next.bind(l):l.prev2.bind(l);for(;r=a();){if("LI"===r.nodeName&&!r.hasChildNodes())return r;if(i[r.nodeName])return r;if(we(r)&&r.data.length>0)return r}return null},an=(e,t)=>{const n=t.childNodes;return 1===n.length&&!Be(n[0])&&e.isBlock(n[0])},dn=e=>h.from(e).map($).filter(Z).exists((e=>((e,t=!1)=>{return ie(e)?e.dom.isContentEditable:(n=e,le(((e,t)=>_(e,t)),ce,n,"[contenteditable]",void 0)).fold(m(t),(e=>"true"===(e=>e.dom.contentEditable)(e)));var n})(e)&&!C(["details"],Q(e)))),cn=(e,t,n)=>{let o;const r=an(e,n)?n.firstChild:n;if(((e,t)=>{an(e,t)&&dn(t.firstChild)&&e.remove(t.firstChild,!0)})(e,t),!Ve(e,t,!0))for(;o=t.firstChild;)r.appendChild(o)},mn=(e,t,n)=>{let o;const r=t.parentNode;if(!je(e,t)||!je(e,n))return;Be(n.lastChild)&&(o=n.lastChild),r===n.lastChild&&$e(r.previousSibling)&&e.remove(r.previousSibling);const s=n.lastChild;s&&$e(s)&&t.hasChildNodes()&&e.remove(s),Ve(e,n,!0)&&ge($(n)),cn(e,t,n),o&&n.appendChild(o);const i=((e,t)=>{const n=e.dom,o=t.dom;return n!==o&&n.contains(o)})($(n),$(t))?e.getParents(t,Be,n):[];e.remove(t),S(i,(t=>{Ve(e,t)&&t!==e.getRoot()&&e.remove(t)}))},un=(e,t)=>{const n=e.dom,o=e.selection,r=o.getStart(),s=it(e,r),i=n.getParent(o.getStart(),"LI",s);if(i){const r=i.parentElement;if(r===e.getBody()&&Ve(n,r))return!0;const l=tt(o.getRng()),a=n.getParent(ln(e,l,t,s),"LI",s),d=a&&(t?n.isChildOf(i,a):n.isChildOf(a,i));if(a&&a!==i&&!d)return e.undoManager.transact((()=>{var n,o;t?((e,t,n,o)=>{const r=e.dom;if(r.isEmpty(o))((e,t,n)=>{ge($(n)),mn(e.dom,t,n),e.selection.setCursorLocation(n,0)})(e,n,o);else{const s=Kt(t);mn(r,n,o),e.selection.setRng(zt(s))}})(e,l,a,i):(null===(o=(n=i).parentNode)||void 0===o?void 0:o.firstChild)===n?_t(e):((e,t,n,o)=>{const r=Kt(t);mn(e.dom,n,o);const s=zt(r);e.selection.setRng(s)})(e,l,i,a)})),!0;if(d&&!t&&a!==i){const t=l.commonAncestorContainer.parentElement;return!(!t||n.isChildOf(a,t)||(e.undoManager.transact((()=>{const o=Kt(l);cn(n,t,a),t.remove();const r=zt(o);e.selection.setRng(r)})),0))}if(!a&&!t&&0===l.startOffset&&0===l.endOffset)return e.undoManager.transact((()=>{Ht(e)})),!0}return!1},pn=e=>{const t=e.selection.getStart(),n=it(e,t);return e.dom.getParent(t,"LI,DT,DD",n)||st(e).length>0},gn=(e,t)=>{const n=e.selection;return!pt(e,n.getNode())&&(n.isCollapsed()?((e,t)=>un(e,t)||((e,t)=>{const n=e.dom,o=e.selection.getStart(),r=it(e,o),s=n.getParent(o,n.isBlock,r);if(s&&n.isEmpty(s,void 0,{checkRootAsContent:!0})){const o=tt(e.selection.getRng()),i=ln(e,o,t,r),l=n.getParent(i,"LI",r);if(i&&l){const a=e=>C(["td","th","caption"],Q(e)),d=e=>e.dom===r;return!!((e,t,n=u)=>P(e,t,n).getOr(e.isNone()&&t.isNone()))(de($(l),a,d),de($(o.startContainer),a,d),H)&&(e.undoManager.transact((()=>{const o=l.parentNode;((e,t,n)=>{const o=e.getParent(t.parentNode,e.isBlock,n);e.remove(t),o&&e.isEmpty(o)&&e.remove(o)})(n,s,r),Yt(n,o),e.selection.select(i,!0),e.selection.collapse(t)})),!0)}}return!1})(e,t))(e,t):(e=>!!pn(e)&&(e.undoManager.transact((()=>{let t=!0;const n=()=>t=!1;e.on("input",n),e.execCommand("Delete"),e.off("input",n),t&&e.dispatch("input"),sn(e.dom,e.getBody())})),!0))(e))},hn=e=>{const t=k(yt(e).split("")),n=N(t,((e,t)=>{const n=e.toUpperCase().charCodeAt(0)-"A".charCodeAt(0)+1;return Math.pow(26,t)*n}));return O(n,((e,t)=>e+t),0)},fn=e=>{if(--e<0)return"";{const t=e%26,n=Math.floor(e/26);return fn(n)+String.fromCharCode("A".charCodeAt(0)+t)}},yn=e=>{const t=parseInt(e.start,10);return M(e.listStyleType,"upper-alpha")?fn(t):M(e.listStyleType,"lower-alpha")?fn(t).toLowerCase():e.start},vn=(e,t)=>()=>{const n=rt(e);return l(n)&&n.nodeName===t},Cn=e=>{e.addCommand("mceListProps",(()=>{(e=>{const t=rt(e);Pe(t)&&!pt(e,t)&&e.windowManager.open({title:"List Properties",body:{type:"panel",items:[{type:"input",name:"start",label:"Start list at number",inputMode:"numeric"}]},initialData:{start:yn({start:e.dom.getAttrib(t,"start","1"),listStyleType:h.from(e.dom.getStyle(t,"list-style-type"))})},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],onSubmit:t=>{(e=>{switch((e=>/^[0-9]+$/.test(e)?2:/^[A-Z]+$/.test(e)?0:/^[a-z]+$/.test(e)?1:e.length>0?4:3)(e)){case 2:return h.some({listStyleType:h.none(),start:e});case 0:return h.some({listStyleType:h.some("upper-alpha"),start:hn(e).toString()});case 1:return h.some({listStyleType:h.some("lower-alpha"),start:hn(e).toString()});case 3:return h.some({listStyleType:h.none(),start:""});case 4:return h.none()}})(t.getData().start).each((t=>{e.execCommand("mceListUpdate",!1,{attrs:{start:"1"===t.start?"":t.start},styles:{"list-style-type":t.listStyleType.getOr("")}})})),t.close()}})})(e)}))};var bn=tinymce.util.Tools.resolve("tinymce.html.Node");const Nn=e=>3===e.type,Sn=e=>0===e.length,Ln=e=>{const t=(t,n)=>{const o=bn.create("li");S(t,(e=>o.append(e))),n?e.insert(o,n,!0):e.append(o)},n=O(e.children(),((e,n)=>Nn(n)?[...e,n]:Sn(e)||Nn(n)?e:(t(e,n),[])),[]);Sn(n)||t(n)},On=(e,t)=>n=>(n.setEnabled(e.selection.isEditable()),gt(e,(o=>{n.setActive(mt(o.parents,t)),n.setEnabled(!pt(e,o.element)&&e.selection.isEditable())}))),An=(e,t)=>n=>gt(e,(o=>n.setEnabled(mt(o.parents,t)&&!pt(e,o.element))));e.add("lists",(e=>((e=>{(0,e.options.register)("lists_indent_on_tab",{processor:"boolean",default:!0})})(e),(e=>{e.on("PreInit",(()=>{const{parser:t}=e;t.addNodeFilter("ul,ol",(e=>S(e,Ln)))}))})(e),e.hasPlugin("rtc",!0)?Cn(e):((e=>{ze(e)&&(e=>{e.on("keydown",(t=>{t.keyCode!==ve.TAB||ve.metaKeyPressed(t)||e.undoManager.transact((()=>{(t.shiftKey?_t(e):$t(e))&&t.preventDefault()}))}))})(e),(e=>{e.on("ExecCommand",(t=>{const n=t.command.toLowerCase();"delete"!==n&&"forwarddelete"!==n||!pn(e)||sn(e.dom,e.getBody())})),e.on("keydown",(t=>{t.keyCode===ve.BACKSPACE?gn(e,!1)&&t.preventDefault():t.keyCode===ve.DELETE&&gn(e,!0)&&t.preventDefault()}))})(e)})(e),(e=>{e.on("BeforeExecCommand",(t=>{const n=t.command.toLowerCase();"indent"===n?$t(e):"outdent"===n&&_t(e)})),e.addCommand("InsertUnorderedList",((t,n)=>{on(e,"UL",n)})),e.addCommand("InsertOrderedList",((t,n)=>{on(e,"OL",n)})),e.addCommand("InsertDefinitionList",((t,n)=>{on(e,"DL",n)})),e.addCommand("RemoveList",(()=>{Ht(e)})),Cn(e),e.addCommand("mceListUpdate",((t,n)=>{r(n)&&((e,t)=>{const n=rt(e);null===n||pt(e,n)||e.undoManager.transact((()=>{r(t.styles)&&e.dom.setStyles(n,t.styles),r(t.attrs)&&Ne(t.attrs,((t,o)=>e.dom.setAttrib(n,o,t)))}))})(e,n)})),e.addQueryStateHandler("InsertUnorderedList",vn(e,"UL")),e.addQueryStateHandler("InsertOrderedList",vn(e,"OL")),e.addQueryStateHandler("InsertDefinitionList",vn(e,"DL"))})(e)),(e=>{const t=t=>()=>e.execCommand(t);e.hasPlugin("advlist")||(e.ui.registry.addToggleButton("numlist",{icon:"ordered-list",active:!1,tooltip:"Numbered list",onAction:t("InsertOrderedList"),onSetup:On(e,"OL")}),e.ui.registry.addToggleButton("bullist",{icon:"unordered-list",active:!1,tooltip:"Bullet list",onAction:t("InsertUnorderedList"),onSetup:On(e,"UL")}))})(e),(e=>{const t={text:"List properties...",icon:"ordered-list",onAction:()=>e.execCommand("mceListProps"),onSetup:An(e,"OL")};e.ui.registry.addMenuItem("listprops",t),e.ui.registry.addContextMenu("lists",{update:t=>{const n=rt(e,t);return Pe(n)?["listprops"]:[]}})})(e),(e=>({backspaceDelete:t=>{gn(e,t)}}))(e))))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/media/plugin.min.js b/apps/web-antd/public/tinymce/plugins/media/plugin.min.js new file mode 100644 index 0000000..5a31076 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/media/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(r=o=e,(a=String).prototype.isPrototypeOf(r)||(null===(s=o.constructor)||void 0===s?void 0:s.name)===a.name)?"string":t;var r,o,a,s})(t)===e,r=t("string"),o=t("object"),a=t("array"),s=e=>!(e=>null==e)(e);class i{constructor(e,t){this.tag=e,this.value=t}static some(e){return new i(!0,e)}static none(){return i.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?i.some(e(this.value)):i.none()}bind(e){return this.tag?e(this.value):i.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:i.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return s(e)?i.some(e):i.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}i.singletonNone=new i(!1);const n=Array.prototype.push,l=(e,t)=>{for(let r=0,o=e.length;r{const t=[];for(let r=0,o=e.length;rh(e,t)?i.from(e[t]):i.none(),h=(e,t)=>u.call(e,t),p=e=>t=>t.options.get(e),g=p("audio_template_callback"),b=p("video_template_callback"),w=p("iframe_template_callback"),v=p("media_live_embeds"),f=p("media_filter_html"),y=p("media_url_resolver"),x=p("media_alt_source"),_=p("media_poster"),k=p("media_dimensions");var j=tinymce.util.Tools.resolve("tinymce.util.Tools"),O=tinymce.util.Tools.resolve("tinymce.dom.DOMUtils"),A=tinymce.util.Tools.resolve("tinymce.html.DomParser");const S=O.DOM,$=e=>e.replace(/px$/,""),C=e=>{const t=e.attr("style"),r=t?S.parseStyle(t):{};return{type:"ephox-embed-iri",source:e.attr("data-ephox-embed-iri"),altsource:"",poster:"",width:d(r,"max-width").map($).getOr(""),height:d(r,"max-height").map($).getOr("")}},T=(e,t)=>{let r={};for(let o=A({validate:!1,forced_root_block:!1},t).parse(e);o;o=o.walk())if(1===o.type){const e=o.name;if(o.attr("data-ephox-embed-iri")){r=C(o);break}r.source||"param"!==e||(r.source=o.attr("movie")),"iframe"!==e&&"object"!==e&&"embed"!==e&&"video"!==e&&"audio"!==e||(r.type||(r.type=e),r=j.extend(o.attributes.map,r)),"source"===e&&(r.source?r.altsource||(r.altsource=o.attr("src")):r.source=o.attr("src")),"img"!==e||r.poster||(r.poster=o.attr("src"))}return r.source=r.source||r.src||"",r.altsource=r.altsource||"",r.poster=r.poster||"",r},z=e=>{var t;const r=null!==(t=e.toLowerCase().split(".").pop())&&void 0!==t?t:"";return d({mp3:"audio/mpeg",m4a:"audio/x-m4a",wav:"audio/wav",mp4:"video/mp4",webm:"video/webm",ogg:"video/ogg",swf:"application/x-shockwave-flash"},r).getOr("")};var D=tinymce.util.Tools.resolve("tinymce.html.Node"),F=tinymce.util.Tools.resolve("tinymce.html.Serializer");const M=(e,t={})=>A({forced_root_block:!1,validate:!1,allow_conditional_comments:!0,...t},e),N=O.DOM,P=e=>/^[0-9.]+$/.test(e)?e+"px":e,R=(e,t)=>{const r=t.attr("style"),o=r?N.parseStyle(r):{};s(e.width)&&(o["max-width"]=P(e.width)),s(e.height)&&(o["max-height"]=P(e.height)),t.attr("style",N.serializeStyle(o))},E=["source","altsource"],U=(e,t,r,o)=>{let a=0,s=0;const i=M(o);i.addNodeFilter("source",(e=>a=e.length));const n=i.parse(e);for(let e=n;e;e=e.walk())if(1===e.type){const o=e.name;if(e.attr("data-ephox-embed-iri")){R(t,e);break}switch(o){case"video":case"object":case"embed":case"img":case"iframe":void 0!==t.height&&void 0!==t.width&&(e.attr("width",t.width),e.attr("height",t.height))}if(r)switch(o){case"video":e.attr("poster",t.poster),e.attr("src",null);for(let r=a;r<2;r++)if(t[E[r]]){const o=new D("source",1);o.attr("src",t[E[r]]),o.attr("type",t[E[r]+"mime"]||null),e.append(o)}break;case"iframe":e.attr("src",t.source);break;case"object":const r=e.getAll("img").length>0;if(t.poster&&!r){e.attr("src",t.poster);const r=new D("img",1);r.attr("src",t.poster),r.attr("width",t.width),r.attr("height",t.height),e.append(r)}break;case"source":if(s<2&&(e.attr("src",t[E[s]]),e.attr("type",t[E[s]+"mime"]||null),!t[E[s]])){e.remove();continue}s++;break;case"img":t.poster||e.remove()}}return F({},o).serialize(n)},L=[{regex:/youtu\.be\/([\w\-_\?&=.]+)/i,type:"iframe",w:560,h:314,url:"www.youtube.com/embed/$1",allowFullscreen:!0},{regex:/youtube\.com(.+)v=([^&]+)(&([a-z0-9&=\-_]+))?/i,type:"iframe",w:560,h:314,url:"www.youtube.com/embed/$2?$4",allowFullscreen:!0},{regex:/youtube.com\/embed\/([a-z0-9\?&=\-_]+)/i,type:"iframe",w:560,h:314,url:"www.youtube.com/embed/$1",allowFullscreen:!0},{regex:/vimeo\.com\/([0-9]+)\?h=(\w+)/,type:"iframe",w:425,h:350,url:"player.vimeo.com/video/$1?h=$2&title=0&byline=0&portrait=0&color=8dc7dc",allowFullscreen:!0},{regex:/vimeo\.com\/(.*)\/([0-9]+)\?h=(\w+)/,type:"iframe",w:425,h:350,url:"player.vimeo.com/video/$2?h=$3&title=0&byline=0",allowFullscreen:!0},{regex:/vimeo\.com\/([0-9]+)/,type:"iframe",w:425,h:350,url:"player.vimeo.com/video/$1?title=0&byline=0&portrait=0&color=8dc7dc",allowFullscreen:!0},{regex:/vimeo\.com\/(.*)\/([0-9]+)/,type:"iframe",w:425,h:350,url:"player.vimeo.com/video/$2?title=0&byline=0",allowFullscreen:!0},{regex:/maps\.google\.([a-z]{2,3})\/maps\/(.+)msid=(.+)/,type:"iframe",w:425,h:350,url:'maps.google.com/maps/ms?msid=$2&output=embed"',allowFullscreen:!1},{regex:/dailymotion\.com\/video\/([^_]+)/,type:"iframe",w:480,h:270,url:"www.dailymotion.com/embed/video/$1",allowFullscreen:!0},{regex:/dai\.ly\/([^_]+)/,type:"iframe",w:480,h:270,url:"www.dailymotion.com/embed/video/$1",allowFullscreen:!0}],I=(e,t)=>{const r=(e=>{const t=e.match(/^(https?:\/\/|www\.)(.+)$/i);return t&&t.length>1?"www."===t[1]?"https://":t[1]:"https://"})(t),o=e.regex.exec(t);let a=r+e.url;if(s(o))for(let e=0;eo[e]?o[e]:""));return a.replace(/\?$/,"")},B=e=>{const t=L.filter((t=>t.regex.test(e)));return t.length>0?j.extend({},t[0],{url:I(t[0],e)}):null},G=(e,t)=>{var r;const o=j.extend({},t);if(!o.source&&(j.extend(o,T(null!==(r=o.embed)&&void 0!==r?r:"",e.schema)),!o.source))return"";o.altsource||(o.altsource=""),o.poster||(o.poster=""),o.source=e.convertURL(o.source,"source"),o.altsource=e.convertURL(o.altsource,"source"),o.sourcemime=z(o.source),o.altsourcemime=z(o.altsource),o.poster=e.convertURL(o.poster,"poster");const a=B(o.source);if(a&&(o.source=a.url,o.type=a.type,o.allowfullscreen=a.allowFullscreen,o.width=o.width||String(a.w),o.height=o.height||String(a.h)),o.embed)return U(o.embed,o,!0,e.schema);{const t=g(e),r=b(e),a=w(e);return o.width=o.width||"300",o.height=o.height||"150",j.each(o,((t,r)=>{o[r]=e.dom.encode(""+t)})),"iframe"===o.type?((e,t)=>{if(t)return t(e);{const t=e.allowfullscreen?' allowFullscreen="1"':"";return'"}})(o,a):"application/x-shockwave-flash"===o.sourcemime?(e=>{let t='';return e.poster&&(t+=''),t+="",t})(o):-1!==o.sourcemime.indexOf("audio")?((e,t)=>t?t(e):'")(o,t):((e,t)=>t?t(e):'")(o,r)}},W=e=>e.hasAttribute("data-mce-object")||e.hasAttribute("data-ephox-embed-iri"),q={},H=e=>t=>G(e,t),J=(e,t)=>{const r=y(e);return r?((e,t,r)=>new Promise(((o,a)=>{const s=r=>(r.html&&(q[e.source]=r),o({url:e.source,html:r.html?r.html:t(e)}));q[e.source]?s(q[e.source]):r({url:e.source}).then(s).catch(a)})))(t,H(e),r):((e,t)=>Promise.resolve({html:t(e),url:e.source}))(t,H(e))},K=(e,t)=>{const r={};return d(e,"dimensions").each((e=>{l(["width","height"],(o=>{d(t,o).orThunk((()=>d(e,o))).each((e=>r[o]=e))}))})),r},Q=(e,t)=>{const r=t&&"dimensions"!==t?((e,t)=>d(t,e).bind((e=>d(e,"meta"))))(t,e).getOr({}):{},a=((e,t,r)=>a=>{const s=()=>d(e,a),n=()=>d(t,a),l=e=>d(e,"value").bind((e=>e.length>0?i.some(e):i.none()));return{[a]:(a===r?s().bind((e=>o(e)?l(e).orThunk(n):n().orThunk((()=>i.from(e))))):n().orThunk((()=>s().bind((e=>o(e)?l(e):i.from(e)))))).getOr("")}})(e,r,t);return{...a("source"),...a("altsource"),...a("poster"),...a("embed"),...K(e,r)}},V=e=>{const t={...e,source:{value:d(e,"source").getOr("")},altsource:{value:d(e,"altsource").getOr("")},poster:{value:d(e,"poster").getOr("")}};return l(["width","height"],(r=>{d(e,r).each((e=>{const o=t.dimensions||{};o[r]=e,t.dimensions=o}))})),t},X=e=>t=>{const r=t&&t.msg?"Media embed handler error: "+t.msg:"Media embed handler threw unknown error.";e.notificationManager.open({type:"error",text:r})},Y=(e,t)=>o=>{if(r(o.url)&&o.url.trim().length>0){const r=o.html,a={...T(r,t.schema),source:o.url,embed:r};e.setData(V(a))}},Z=(e,t)=>{const r=e.dom.select("*[data-mce-object]");e.insertContent(t),((e,t)=>{const r=e.dom.select("*[data-mce-object]");for(let e=0;e=0;o--)t[e]===r[o]&&r.splice(o,1);e.selection.select(r[0])})(e,r),e.nodeChanged()},ee=(e,t)=>s(t)&&"ephox-embed-iri"===t&&s(B(e)),te=(e,t)=>((e,t)=>e.width!==t.width||e.height!==t.height)(e,t)&&ee(t.source,e.type),re=e=>{const t=(e=>{const t=e.selection.getNode(),r=W(t)?e.serializer.serialize(t,{selection:!0}):"",o=T(r,e.schema),a=(()=>{if(ee(o.source,o.type)){const r=e.dom.getRect(t);return{width:r.w.toString().replace(/px$/,""),height:r.h.toString().replace(/px$/,"")}}return{}})();return{embed:r,...o,...a}})(e),r=(e=>{let t=e;return{get:()=>t,set:e=>{t=e}}})(t),o=V(t),a=k(e)?[{type:"sizeinput",name:"dimensions",label:"Constrain proportions",constrain:!0}]:[],s={title:"General",name:"general",items:c([[{name:"source",type:"urlinput",filetype:"media",label:"Source",picker_text:"Browse files"}],a])},i=[];x(e)&&i.push({name:"altsource",type:"urlinput",filetype:"media",label:"Alternative source URL"}),_(e)&&i.push({name:"poster",type:"urlinput",filetype:"image",label:"Media poster (Image URL)"});const n={title:"Advanced",name:"advanced",items:i},l=[s,{title:"Embed",items:[{type:"textarea",name:"embed",label:"Paste your embed code below:"}]}];i.length>0&&l.push(n);const m={type:"tabpanel",tabs:l},u=e.windowManager.open({title:"Insert/Edit Media",size:"normal",body:m,buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],onSubmit:t=>{const o=Q(t.getData());((e,t,r)=>{var o,a;t.embed=te(e,t)&&k(r)?G(r,{...t,embed:""}):U(null!==(o=t.embed)&&void 0!==o?o:"",t,!1,r.schema),t.embed&&(e.source===t.source||(a=t.source,h(q,a)))?Z(r,t.embed):J(r,t).then((e=>{Z(r,e.html)})).catch(X(r))})(r.get(),o,e),t.close()},onChange:(t,o)=>{switch(o.name){case"source":((t,r)=>{const o=Q(r.getData(),"source");t.source!==o.source&&(Y(u,e)({url:o.source,html:""}),J(e,o).then(Y(u,e)).catch(X(e)))})(r.get(),t);break;case"embed":(t=>{var r;const o=Q(t.getData()),a=T(null!==(r=o.embed)&&void 0!==r?r:"",e.schema);t.setData(V(a))})(t);break;case"dimensions":case"altsource":case"poster":((t,r,o)=>{const a=Q(t.getData(),r),s=te(o,a)&&k(e)?{...a,embed:""}:a,i=G(e,s);t.setData(V({...s,embed:i}))})(t,o.name,r.get())}r.set(Q(t.getData()))},initialData:o})};var oe=tinymce.util.Tools.resolve("tinymce.Env");const ae=e=>{const t=e.name;return"iframe"===t||"video"===t||"audio"===t},se=(e,t,r,o=null)=>{const a=e.attr(r);return s(a)?a:h(t,r)?null:o},ie=(e,t,r)=>{const o="img"===t.name||"video"===e.name,a=o?"300":null,s="audio"===e.name?"30":"150",i=o?s:null;t.attr({width:se(e,r,"width",a),height:se(e,r,"height",i)})},ne=(e,t)=>{const r=t.name,o=new D("img",1);return ce(e,t,o),ie(t,o,{}),o.attr({style:t.attr("style"),src:oe.transparentSrc,"data-mce-object":r,class:"mce-object mce-object-"+r}),o},le=(e,t)=>{var r;const o=t.name,a=new D("span",1);a.attr({contentEditable:"false",style:t.attr("style"),"data-mce-object":o,class:"mce-preview-object mce-object-"+o}),ce(e,t,a);const i=e.dom.parseStyle(null!==(r=t.attr("style"))&&void 0!==r?r:""),n=new D(o,1);if(ie(t,n,i),n.attr({src:t.attr("src"),style:t.attr("style"),class:t.attr("class")}),"iframe"===o)n.attr({allowfullscreen:t.attr("allowfullscreen"),frameborder:"0",sandbox:t.attr("sandbox"),referrerpolicy:t.attr("referrerpolicy")});else{l(["controls","crossorigin","currentTime","loop","muted","poster","preload"],(e=>{n.attr(e,t.attr(e))}));const r=a.attr("data-mce-html");s(r)&&((e,t,r,o)=>{const a=M(e.schema).parse(o,{context:t});for(;a.firstChild;)r.append(a.firstChild)})(e,o,n,unescape(r))}const c=new D("span",1);return c.attr("class","mce-shim"),a.append(n),a.append(c),a},ce=(e,t,r)=>{var o;const a=null!==(o=t.attributes)&&void 0!==o?o:[];let s=a.length;for(;s--;){const t=a[s].name;let o=a[s].value;"width"===t||"height"===t||"style"===t||(n="data-mce-",(i=t).length>=9&&i.substr(0,9)===n)||("data"!==t&&"src"!==t||(o=e.convertURL(o,t)),r.attr("data-mce-p-"+t,o))}var i,n;const c=F({inner:!0},e.schema),m=new D("div",1);l(t.children(),(e=>m.append(e)));const u=c.serialize(m);u&&(r.attr("data-mce-html",escape(u)),r.empty())},me=e=>{const t=e.attr("class");return r(t)&&/\btiny-pageembed\b/.test(t)},ue=e=>{let t=e;for(;t=t.parent;)if(t.attr("data-ephox-embed-iri")||me(t))return!0;return!1},de=(e,t,r)=>{const o=(0,e.options.get)("xss_sanitization"),a=f(e);return M(e.schema,{sanitize:o,validate:a}).parse(r,{context:t})},he=e=>t=>{const r=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",r),r(),()=>{e.off("NodeChange",r)}};e.add("media",(e=>((e=>{const t=e.options.register;t("audio_template_callback",{processor:"function"}),t("video_template_callback",{processor:"function"}),t("iframe_template_callback",{processor:"function"}),t("media_live_embeds",{processor:"boolean",default:!0}),t("media_filter_html",{processor:"boolean",default:!0}),t("media_url_resolver",{processor:"function"}),t("media_alt_source",{processor:"boolean",default:!0}),t("media_poster",{processor:"boolean",default:!0}),t("media_dimensions",{processor:"boolean",default:!0})})(e),(e=>{e.addCommand("mceMedia",(()=>{re(e)}))})(e),(e=>{const t=()=>e.execCommand("mceMedia");e.ui.registry.addToggleButton("media",{tooltip:"Insert/edit media",icon:"embed",onAction:t,onSetup:t=>{const r=e.selection;t.setActive(W(r.getNode()));const o=r.selectorChangedWithUnbind("img[data-mce-object],span[data-mce-object],div[data-ephox-embed-iri]",t.setActive).unbind,a=he(e)(t);return()=>{o(),a()}}}),e.ui.registry.addMenuItem("media",{icon:"embed",text:"Media...",onAction:t,onSetup:he(e)})})(e),(e=>{e.on("ResolveName",(e=>{let t;1===e.target.nodeType&&(t=e.target.getAttribute("data-mce-object"))&&(e.name=t)}))})(e),(e=>{e.on("PreInit",(()=>{const{schema:t,serializer:r,parser:o}=e,a=t.getBoolAttrs();l("webkitallowfullscreen mozallowfullscreen".split(" "),(e=>{a[e]={}})),((e,t)=>{const r=m(e);for(let o=0,a=r.length;o{const o=t.getElementRule(r);o&&l(e,(e=>{o.attributes[e]={},o.attributesOrder.push(e)}))})),o.addNodeFilter("iframe,video,audio,object,embed",(e=>t=>{let r,o=t.length;for(;o--;)r=t[o],r.parent&&(r.parent.attr("data-mce-object")||(ae(r)&&v(e)?ue(r)||r.replace(le(e,r)):ue(r)||r.replace(ne(e,r))))})(e)),r.addAttributeFilter("data-mce-object",((t,r)=>{var o;let a=t.length;for(;a--;){const s=t[a];if(!s.parent)continue;const i=s.attr(r),n=new D(i,1);if("audio"!==i){const e=s.attr("class");e&&-1!==e.indexOf("mce-preview-object")&&s.firstChild?n.attr({width:s.firstChild.attr("width"),height:s.firstChild.attr("height")}):n.attr({width:s.attr("width"),height:s.attr("height")})}n.attr({style:s.attr("style")});const c=null!==(o=s.attributes)&&void 0!==o?o:[];let m=c.length;for(;m--;){const e=c[m].name;0===e.indexOf("data-mce-p-")&&n.attr(e.substr(11),c[m].value)}const u=s.attr("data-mce-html");if(u){const t=de(e,i,unescape(u));l(t.children(),(e=>n.append(e)))}s.replace(n)}}))})),e.on("SetContent",(()=>{const t=e.dom;l(t.select("span.mce-preview-object"),(e=>{0===t.select("span.mce-shim",e).length&&t.add(e,"span",{class:"mce-shim"})}))}))})(e),(e=>{e.on("mousedown",(t=>{const r=e.dom.getParent(t.target,".mce-preview-object");r&&"2"===e.dom.getAttrib(r,"data-mce-selected")&&t.stopImmediatePropagation()})),e.on("click keyup touchend",(()=>{const t=e.selection.getNode();t&&e.dom.hasClass(t,"mce-preview-object")&&e.dom.getAttrib(t,"data-mce-selected")&&t.setAttribute("data-mce-selected","2")})),e.on("ObjectResized",(t=>{const r=t.target;if(r.getAttribute("data-mce-object")){let o=r.getAttribute("data-mce-html");o&&(o=unescape(o),r.setAttribute("data-mce-html",escape(U(o,{width:String(t.width),height:String(t.height)},!1,e.schema))))}}))})(e),(e=>({showDialog:()=>{re(e)}}))(e))))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/nonbreaking/plugin.min.js b/apps/web-antd/public/tinymce/plugins/nonbreaking/plugin.min.js new file mode 100644 index 0000000..048e6d7 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/nonbreaking/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=n=>e=>typeof e===n,o=e("boolean"),a=e("number"),t=n=>e=>e.options.get(n),i=t("nonbreaking_force_tab"),s=t("nonbreaking_wrap"),r=(n,e)=>{let o="";for(let a=0;a{const o=s(n)||n.plugins.visualchars?`${r(" ",e)}`:r(" ",e);n.undoManager.transact((()=>n.insertContent(o)))};var l=tinymce.util.Tools.resolve("tinymce.util.VK");const u=n=>e=>{const o=()=>{e.setEnabled(n.selection.isEditable())};return n.on("NodeChange",o),o(),()=>{n.off("NodeChange",o)}};n.add("nonbreaking",(n=>{(n=>{const e=n.options.register;e("nonbreaking_force_tab",{processor:n=>o(n)?{value:n?3:0,valid:!0}:a(n)?{value:n,valid:!0}:{valid:!1,message:"Must be a boolean or number."},default:!1}),e("nonbreaking_wrap",{processor:"boolean",default:!0})})(n),(n=>{n.addCommand("mceNonBreaking",(()=>{c(n,1)}))})(n),(n=>{const e=()=>n.execCommand("mceNonBreaking");n.ui.registry.addButton("nonbreaking",{icon:"non-breaking",tooltip:"Nonbreaking space",onAction:e,onSetup:u(n)}),n.ui.registry.addMenuItem("nonbreaking",{icon:"non-breaking",text:"Nonbreaking space",onAction:e,onSetup:u(n)})})(n),(n=>{const e=i(n);e>0&&n.on("keydown",(o=>{if(o.keyCode===l.TAB&&!o.isDefaultPrevented()){if(o.shiftKey)return;o.preventDefault(),o.stopImmediatePropagation(),c(n,e)}}))})(n)}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/pagebreak/plugin.min.js b/apps/web-antd/public/tinymce/plugins/pagebreak/plugin.min.js new file mode 100644 index 0000000..b498342 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/pagebreak/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.Env");const t=e=>a=>a.options.get(e),n=t("pagebreak_separator"),o=t("pagebreak_split_block"),r="mce-pagebreak",s=e=>{const t=``;return e?`

    ${t}

    `:t},c=e=>a=>{const t=()=>{a.setEnabled(e.selection.isEditable())};return e.on("NodeChange",t),t(),()=>{e.off("NodeChange",t)}};e.add("pagebreak",(e=>{(e=>{const a=e.options.register;a("pagebreak_separator",{processor:"string",default:"\x3c!-- pagebreak --\x3e"}),a("pagebreak_split_block",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mcePageBreak",(()=>{e.insertContent(s(o(e)))}))})(e),(e=>{const a=()=>e.execCommand("mcePageBreak");e.ui.registry.addButton("pagebreak",{icon:"page-break",tooltip:"Page break",onAction:a,onSetup:c(e)}),e.ui.registry.addMenuItem("pagebreak",{text:"Page break",icon:"page-break",onAction:a,onSetup:c(e)})})(e),(e=>{const a=n(e),t=()=>o(e),c=new RegExp(a.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,(e=>"\\"+e)),"gi");e.on("BeforeSetContent",(e=>{e.content=e.content.replace(c,s(t()))})),e.on("PreInit",(()=>{e.serializer.addNodeFilter("img",(n=>{let o,s,c=n.length;for(;c--;)if(o=n[c],s=o.attr("class"),s&&-1!==s.indexOf(r)){const n=o.parent;if(n&&e.schema.getBlockElements()[n.name]&&t()){n.type=3,n.value=a,n.raw=!0,o.remove();continue}o.type=3,o.value=a,o.raw=!0}}))}))})(e),(e=>{e.on("ResolveName",(a=>{"IMG"===a.target.nodeName&&e.dom.hasClass(a.target,r)&&(a.name="pagebreak")}))})(e)}))}(); \ No newline at end of file diff --git a/apps/web-antd/public/tinymce/plugins/preview/plugin.min.js b/apps/web-antd/public/tinymce/plugins/preview/plugin.min.js new file mode 100644 index 0000000..41e9891 --- /dev/null +++ b/apps/web-antd/public/tinymce/plugins/preview/plugin.min.js @@ -0,0 +1,4 @@ +/** + * TinyMCE version 7.8.0 (TBD) + */ +!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>undefined===e;class r{constructor(e,t){this.tag=e,this.value=t}static some(e){return new r(!0,e)}static none(){return r.singletonNone}fold(e,t){return this.tag?t(this.value):e()}isSome(){return this.tag}isNone(){return!this.tag}map(e){return this.tag?r.some(e(this.value)):r.none()}bind(e){return this.tag?e(this.value):r.none()}exists(e){return this.tag&&e(this.value)}forall(e){return!this.tag||e(this.value)}filter(e){return!this.tag||e(this.value)?this:r.none()}getOr(e){return this.tag?this.value:e}or(e){return this.tag?this:e}getOrThunk(e){return this.tag?this.value:e()}orThunk(e){return this.tag?this:e()}getOrDie(e){if(this.tag)return this.value;throw new Error(null!=e?e:"Called getOrDie on None")}static from(e){return null==e?r.none():r.some(e)}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(e){this.tag&&e(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}r.singletonNone=new r(!1);const n=e=>()=>e,s=n(!1),i=(e,t)=>((e,t,n)=>{for(let s=0,i=e.length;sa(0,0),a=(e,t)=>({major:e,minor:t}),c={nu:a,detect:(e,t)=>{const r=String(t).toLowerCase();return 0===e.length?o():((e,t)=>{const r=((e,t)=>{for(let r=0;rNumber(t.replace(r,"$"+e));return a(n(1),n(2))})(e,r)},unknown:o},u=(e,t)=>{const r=String(t).toLowerCase();return i(e,(e=>e.search(r)))},d=(e,r,n=0,s)=>{const i=e.indexOf(r,n);return-1!==i&&(!!t(s)||i+r.length<=s)},l=/.*?version\/\ ?([0-9]+)\.([0-9]+).*/,h=e=>t=>d(t,e),m=[{name:"Edge",versionRegexes:[/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],search:e=>d(e,"edge/")&&d(e,"chrome")&&d(e,"safari")&&d(e,"applewebkit")},{name:"Chromium",brand:"Chromium",versionRegexes:[/.*?chrome\/([0-9]+)\.([0-9]+).*/,l],search:e=>d(e,"chrome")&&!d(e,"chromeframe")},{name:"IE",versionRegexes:[/.*?msie\ ?([0-9]+)\.([0-9]+).*/,/.*?rv:([0-9]+)\.([0-9]+).*/],search:e=>d(e,"msie")||d(e,"trident")},{name:"Opera",versionRegexes:[l,/.*?opera\/([0-9]+)\.([0-9]+).*/],search:h("opera")},{name:"Firefox",versionRegexes:[/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],search:h("firefox")},{name:"Safari",versionRegexes:[l,/.*?cpu os ([0-9]+)_([0-9]+).*/],search:e=>(d(e,"safari")||d(e,"mobile/"))&&d(e,"applewebkit")}],v=[{name:"Windows",search:h("win"),versionRegexes:[/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]},{name:"iOS",search:e=>d(e,"iphone")||d(e,"ipad"),versionRegexes:[/.*?version\/\ ?([0-9]+)\.([0-9]+).*/,/.*cpu os ([0-9]+)_([0-9]+).*/,/.*cpu iphone os ([0-9]+)_([0-9]+).*/]},{name:"Android",search:h("android"),versionRegexes:[/.*?android\ ?([0-9]+)\.([0-9]+).*/]},{name:"macOS",search:h("mac os x"),versionRegexes:[/.*?mac\ os\ x\ ?([0-9]+)_([0-9]+).*/]},{name:"Linux",search:h("linux"),versionRegexes:[]},{name:"Solaris",search:h("sunos"),versionRegexes:[]},{name:"FreeBSD",search:h("freebsd"),versionRegexes:[]},{name:"ChromeOS",search:h("cros"),versionRegexes:[/.*?chrome\/([0-9]+)\.([0-9]+).*/]}],g={browsers:n(m),oses:n(v)},p="Edge",w="Chromium",f="Opera",x="Firefox",S="Safari",y=e=>{const t=e.current,r=e.version,n=e=>()=>t===e;return{current:t,version:r,isEdge:n(p),isChromium:n(w),isIE:n("IE"),isOpera:n(f),isFirefox:n(x),isSafari:n(S)}},b=()=>y({current:void 0,version:c.unknown()}),O=y,R=(n(p),n(w),n("IE"),n(f),n(x),n(S),"Windows"),C="Android",A="Linux",k="macOS",D="Solaris",E="FreeBSD",I="ChromeOS",P=e=>{const t=e.current,r=e.version,n=e=>()=>t===e;return{current:t,version:r,isWindows:n(R),isiOS:n("iOS"),isAndroid:n(C),isMacOS:n(k),isLinux:n(A),isSolaris:n(D),isFreeBSD:n(E),isChromeOS:n(I)}},T=()=>P({current:void 0,version:c.unknown()}),_=P,B=(n(R),n("iOS"),n(C),n(A),n(k),n(D),n(E),n(I),(e,t,s)=>{const o=g.browsers(),a=g.oses(),d=t.bind((e=>((e,t)=>((e,t)=>{for(let r=0;r{const r=t.brand.toLowerCase();return i(e,(e=>{var t;return r===(null===(t=e.brand)||void 0===t?void 0:t.toLowerCase())})).map((e=>({current:e.name,version:c.nu(parseInt(t.version,10),0)})))})))(o,e))).orThunk((()=>((e,t)=>u(e,t).map((e=>{const r=c.detect(e.versionRegexes,t);return{current:e.name,version:r}})))(o,e))).fold(b,O),l=((e,t)=>u(e,t).map((e=>{const r=c.detect(e.versionRegexes,t);return{current:e.name,version:r}})))(a,e).fold(T,_),h=((e,t,r,s)=>{const i=e.isiOS()&&!0===/ipad/i.test(r),o=e.isiOS()&&!i,a=e.isiOS()||e.isAndroid(),c=a||s("(pointer:coarse)"),u=i||!o&&a&&s("(min-device-width:768px)"),d=o||a&&!u,l=t.isSafari()&&e.isiOS()&&!1===/safari/i.test(r),h=!d&&!u&&!l;return{isiPad:n(i),isiPhone:n(o),isTablet:n(u),isPhone:n(d),isTouch:n(c),isAndroid:e.isAndroid,isiOS:e.isiOS,isWebView:n(l),isDesktop:n(h)}})(l,d,e,s);return{browser:d,os:l,deviceType:h}}),L=e=>window.matchMedia(e).matches;let N=(e=>{let t,r=!1;return(...n)=>(r||(r=!0,t=e.apply(null,n)),t)})((()=>B(window.navigator.userAgent,r.from(window.navigator.userAgentData),L)));const F=()=>N();var M=tinymce.util.Tools.resolve("tinymce.util.Tools");const $=e=>t=>t.options.get(e),W=$("content_style"),U=$("content_css_cors"),K=$("body_class"),j=$("body_id"),V=e=>{const t=(e=>{var t;let r="";const n=e.dom.encode,s=null!==(t=W(e))&&void 0!==t?t:"";r+=``;const i=U(e)?' crossorigin="anonymous"':"";M.each(e.contentCSS,(t=>{r+='"})),s&&(r+='");const o=j(e),a=K(e),c=e.getBody().dir,u=c?' dir="'+n(c)+'"':"";return""+r+'"+e.getContent()+(()=>{const e=F().os.isMacOS()||F().os.isiOS();return` + + diff --git a/apps/web-antd/src/assets/imgs/diy/app-nav-bar-mp.png b/apps/web-antd/src/assets/imgs/diy/app-nav-bar-mp.png new file mode 100644 index 0000000..c982804 Binary files /dev/null and b/apps/web-antd/src/assets/imgs/diy/app-nav-bar-mp.png differ diff --git a/apps/web-antd/src/assets/imgs/diy/statusBar.png b/apps/web-antd/src/assets/imgs/diy/statusBar.png new file mode 100644 index 0000000..b85562e Binary files /dev/null and b/apps/web-antd/src/assets/imgs/diy/statusBar.png differ diff --git a/apps/web-antd/src/assets/imgs/wechat.png b/apps/web-antd/src/assets/imgs/wechat.png new file mode 100644 index 0000000..6afc5e4 Binary files /dev/null and b/apps/web-antd/src/assets/imgs/wechat.png differ diff --git a/apps/web-antd/src/bootstrap.ts b/apps/web-antd/src/bootstrap.ts new file mode 100644 index 0000000..be5a5ed --- /dev/null +++ b/apps/web-antd/src/bootstrap.ts @@ -0,0 +1,82 @@ +import { createApp, watchEffect } from 'vue'; +import VueDOMPurifyHTML from 'vue-dompurify-html'; + +import { registerAccessDirective } from '@vben/access'; +import { registerLoadingDirective } from '@vben/common-ui/es/loading'; +import { preferences } from '@vben/preferences'; +import { initStores } from '@vben/stores'; +import '@vben/styles'; +import '@vben/styles/antd'; + +import { useTitle } from '@vueuse/core'; + +import { $t, setupI18n } from '#/locales'; +import { setupFormCreate } from '#/plugins/form-create'; + +import { initComponentAdapter } from './adapter/component'; +import { initSetupVbenForm } from './adapter/form'; +import App from './app.vue'; +import { router } from './router'; + +async function bootstrap(namespace: string) { + // 初始化组件适配器 + await initComponentAdapter(); + + // 初始化表单组件 + await initSetupVbenForm(); + + // // 设置弹窗的默认配置 + // setDefaultModalProps({ + // fullscreenButton: false, + // }); + // // 设置抽屉的默认配置 + // setDefaultDrawerProps({ + // zIndex: 1020, + // }); + + const app = createApp(App); + app.use(VueDOMPurifyHTML); + + // 注册v-loading指令 + registerLoadingDirective(app, { + loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令 + spinning: 'spinning', + }); + + // 国际化 i18n 配置 + await setupI18n(app); + + // 配置 pinia-store + await initStores(app, { namespace }); + + // 安装权限指令 + registerAccessDirective(app); + + // 初始化 tippy + const { initTippy } = await import('@vben/common-ui/es/tippy'); + initTippy(app); + + // 配置路由及路由守卫 + app.use(router); + + // formCreate + setupFormCreate(app); + + // 配置Motion插件 + const { MotionPlugin } = await import('@vben/plugins/motion'); + app.use(MotionPlugin); + + // 动态更新标题 + watchEffect(() => { + if (preferences.app.dynamicTitle) { + const routeTitle = router.currentRoute.value.meta?.title; + const pageTitle = + (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; + useTitle(pageTitle); + } + }); + + app.mount('#app'); +} + +export { bootstrap }; diff --git a/apps/web-antd/src/components/cron-tab/cron-tab.vue b/apps/web-antd/src/components/cron-tab/cron-tab.vue new file mode 100644 index 0000000..242ecb5 --- /dev/null +++ b/apps/web-antd/src/components/cron-tab/cron-tab.vue @@ -0,0 +1,961 @@ + + + + + diff --git a/apps/web-antd/src/components/cron-tab/index.ts b/apps/web-antd/src/components/cron-tab/index.ts new file mode 100644 index 0000000..8f4baae --- /dev/null +++ b/apps/web-antd/src/components/cron-tab/index.ts @@ -0,0 +1 @@ +export { default as CronTab } from './cron-tab.vue'; diff --git a/apps/web-antd/src/components/cron-tab/types.ts b/apps/web-antd/src/components/cron-tab/types.ts new file mode 100644 index 0000000..2adf942 --- /dev/null +++ b/apps/web-antd/src/components/cron-tab/types.ts @@ -0,0 +1,266 @@ +export interface ShortcutsType { + text: string; + value: string; +} + +export interface CronRange { + start: number | string | undefined; + end: number | string | undefined; +} + +export interface CronLoop { + start: number | string | undefined; + end: number | string | undefined; +} + +export interface CronItem { + type: string; + range: CronRange; + loop: CronLoop; + appoint: string[]; + last?: string; +} + +export interface CronValue { + second: CronItem; + minute: CronItem; + hour: CronItem; + day: CronItem; + month: CronItem; + week: CronItem & { last: string }; + year: CronItem; +} + +export interface WeekOption { + value: string; + label: string; +} + +export interface CronData { + second: string[]; + minute: string[]; + hour: string[]; + day: string[]; + month: string[]; + week: WeekOption[]; + year: number[]; +} + +const getYear = (): number[] => { + const v: number[] = []; + const y = new Date().getFullYear(); + for (let i = 0; i < 11; i++) { + v.push(y + i); + } + return v; +}; + +export const CronValueDefault: CronValue = { + second: { + type: '0', + range: { + start: 1, + end: 2, + }, + loop: { + start: 0, + end: 1, + }, + appoint: [], + }, + minute: { + type: '0', + range: { + start: 1, + end: 2, + }, + loop: { + start: 0, + end: 1, + }, + appoint: [], + }, + hour: { + type: '0', + range: { + start: 1, + end: 2, + }, + loop: { + start: 0, + end: 1, + }, + appoint: [], + }, + day: { + type: '0', + range: { + start: 1, + end: 2, + }, + loop: { + start: 1, + end: 1, + }, + appoint: [], + }, + month: { + type: '0', + range: { + start: 1, + end: 2, + }, + loop: { + start: 1, + end: 1, + }, + appoint: [], + }, + week: { + type: '5', + range: { + start: '2', + end: '3', + }, + loop: { + start: 0, + end: '2', + }, + last: '2', + appoint: [], + }, + year: { + type: '-1', + range: { + start: getYear()[0], + end: getYear()[1], + }, + loop: { + start: getYear()[0], + end: 1, + }, + appoint: [], + }, +}; + +export const CronDataDefault: CronData = { + second: [ + '0', + '5', + '15', + '20', + '25', + '30', + '35', + '40', + '45', + '50', + '55', + '59', + ], + minute: [ + '0', + '5', + '15', + '20', + '25', + '30', + '35', + '40', + '45', + '50', + '55', + '59', + ], + hour: [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '10', + '11', + '12', + '13', + '14', + '15', + '16', + '17', + '18', + '19', + '20', + '21', + '22', + '23', + ], + day: [ + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '10', + '11', + '12', + '13', + '14', + '15', + '16', + '17', + '18', + '19', + '20', + '21', + '22', + '23', + '24', + '25', + '26', + '27', + '28', + '29', + '30', + '31', + ], + month: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], + week: [ + { + value: '1', + label: '周日', + }, + { + value: '2', + label: '周一', + }, + { + value: '3', + label: '周二', + }, + { + value: '4', + label: '周三', + }, + { + value: '5', + label: '周四', + }, + { + value: '6', + label: '周五', + }, + { + value: '7', + label: '周六', + }, + ], + year: getYear(), +}; diff --git a/apps/web-antd/src/components/cropper/cropper-avatar.vue b/apps/web-antd/src/components/cropper/cropper-avatar.vue new file mode 100644 index 0000000..72c71d0 --- /dev/null +++ b/apps/web-antd/src/components/cropper/cropper-avatar.vue @@ -0,0 +1,123 @@ + + + diff --git a/apps/web-antd/src/components/cropper/cropper-modal.vue b/apps/web-antd/src/components/cropper/cropper-modal.vue new file mode 100644 index 0000000..90c9dbc --- /dev/null +++ b/apps/web-antd/src/components/cropper/cropper-modal.vue @@ -0,0 +1,301 @@ + + + diff --git a/apps/web-antd/src/components/cropper/cropper.vue b/apps/web-antd/src/components/cropper/cropper.vue new file mode 100644 index 0000000..cc8836c --- /dev/null +++ b/apps/web-antd/src/components/cropper/cropper.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/apps/web-antd/src/components/cropper/index.ts b/apps/web-antd/src/components/cropper/index.ts new file mode 100644 index 0000000..43fd89f --- /dev/null +++ b/apps/web-antd/src/components/cropper/index.ts @@ -0,0 +1,3 @@ +export { default as CropperAvatar } from './cropper-avatar.vue'; +export { default as CropperImage } from './cropper.vue'; +export type { CropperType } from './typing'; diff --git a/apps/web-antd/src/components/cropper/typing.ts b/apps/web-antd/src/components/cropper/typing.ts new file mode 100644 index 0000000..f471274 --- /dev/null +++ b/apps/web-antd/src/components/cropper/typing.ts @@ -0,0 +1,68 @@ +import type { ButtonProps } from 'ant-design-vue'; +import type Cropper from 'cropperjs'; + +import type { CSSProperties } from 'vue'; + +export interface apiFunParams { + file: Blob; + filename: string; + name: string; +} + +export interface CropendResult { + imgBase64: string; + imgInfo: Cropper.Data; +} + +export interface CropperProps { + src?: string; + alt?: string; + circled?: boolean; + realTimePreview?: boolean; + height?: number | string; + crossorigin?: '' | 'anonymous' | 'use-credentials' | undefined; + imageStyle?: CSSProperties; + options?: Cropper.Options; +} + +export interface CropperAvatarProps { + width?: number | string; + value?: string; + showBtn?: boolean; + btnProps?: ButtonProps; + btnText?: string; + uploadApi?: (params: apiFunParams) => Promise; + size?: number; +} + +export interface CropperModalProps { + circled?: boolean; + uploadApi?: (params: apiFunParams) => Promise; + src?: string; + size?: number; +} + +export const defaultOptions: Cropper.Options = { + aspectRatio: 1, + zoomable: true, + zoomOnTouch: true, + zoomOnWheel: true, + cropBoxMovable: true, + cropBoxResizable: true, + toggleDragModeOnDblclick: true, + autoCrop: true, + background: true, + highlight: true, + center: true, + responsive: true, + restore: true, + checkCrossOrigin: true, + checkOrientation: true, + scalable: true, + modal: true, + guides: true, + movable: true, + rotatable: true, +}; + +export type { Cropper as CropperType }; diff --git a/apps/web-antd/src/components/description/description.vue b/apps/web-antd/src/components/description/description.vue new file mode 100644 index 0000000..d591e57 --- /dev/null +++ b/apps/web-antd/src/components/description/description.vue @@ -0,0 +1,195 @@ + diff --git a/apps/web-antd/src/components/description/index.ts b/apps/web-antd/src/components/description/index.ts new file mode 100644 index 0000000..a707c48 --- /dev/null +++ b/apps/web-antd/src/components/description/index.ts @@ -0,0 +1,3 @@ +export { default as Description } from './description.vue'; +export * from './typing'; +export { useDescription } from './use-description'; diff --git a/apps/web-antd/src/components/description/typing.ts b/apps/web-antd/src/components/description/typing.ts new file mode 100644 index 0000000..97cd8ca --- /dev/null +++ b/apps/web-antd/src/components/description/typing.ts @@ -0,0 +1,43 @@ +import type { DescriptionsProps } from 'ant-design-vue/es/descriptions'; +import type { JSX } from 'vue/jsx-runtime'; + +import type { CSSProperties, VNode } from 'vue'; + +import type { Recordable } from '@vben/types'; + +export interface DescriptionItemSchema { + labelMinWidth?: number; + contentMinWidth?: number; + labelStyle?: CSSProperties; // 自定义标签样式 + field: string; // 对应 data 中的字段名 + label: JSX.Element | string | VNode; // 内容的描述 + span?: number; // 包含列的数量 + show?: (...arg: any) => boolean; // 是否显示 + slot?: string; // 插槽名称 + render?: ( + val: any, + data?: Recordable, + ) => Element | JSX.Element | number | string | undefined | VNode; // 自定义需要展示的内容 +} + +export interface DescriptionProps extends DescriptionsProps { + useCard?: boolean; // 是否包含卡片组件 + schema: DescriptionItemSchema[]; // 描述项配置 + data: Recordable; // 数据 + title?: string; // 标题 + bordered?: boolean; // 是否包含边框 + column?: + | number + | { + lg: number; + md: number; + sm: number; + xl: number; + xs: number; + xxl: number; + }; // 列数 +} + +export interface DescInstance { + setDescProps(descProps: Partial): void; +} diff --git a/apps/web-antd/src/components/description/use-description.ts b/apps/web-antd/src/components/description/use-description.ts new file mode 100644 index 0000000..fd24920 --- /dev/null +++ b/apps/web-antd/src/components/description/use-description.ts @@ -0,0 +1,31 @@ +import type { Component } from 'vue'; + +import type { DescInstance, DescriptionProps } from './typing'; + +import { h, reactive } from 'vue'; + +import Description from './description.vue'; + +export function useDescription(options?: Partial) { + const propsState = reactive>(options || {}); + + const api: DescInstance = { + setDescProps: (descProps: Partial): void => { + Object.assign(propsState, descProps); + }, + }; + + // 创建一个包装组件,将 propsState 合并到 props 中 + const DescriptionWrapper: Component = { + name: 'UseDescription', + inheritAttrs: false, + setup(_props, { attrs, slots }) { + return () => { + // @ts-ignore - 避免类型实例化过深 + return h(Description, { ...propsState, ...attrs }, slots); + }; + }, + }; + + return [DescriptionWrapper, api] as const; +} diff --git a/apps/web-antd/src/components/dict-tag/dict-tag.vue b/apps/web-antd/src/components/dict-tag/dict-tag.vue new file mode 100644 index 0000000..14cc34d --- /dev/null +++ b/apps/web-antd/src/components/dict-tag/dict-tag.vue @@ -0,0 +1,71 @@ + + + diff --git a/apps/web-antd/src/components/dict-tag/index.ts b/apps/web-antd/src/components/dict-tag/index.ts new file mode 100644 index 0000000..881265a --- /dev/null +++ b/apps/web-antd/src/components/dict-tag/index.ts @@ -0,0 +1 @@ +export { default as DictTag } from './dict-tag.vue'; diff --git a/apps/web-antd/src/components/form-create/components/dict-select.vue b/apps/web-antd/src/components/form-create/components/dict-select.vue new file mode 100644 index 0000000..03a5274 --- /dev/null +++ b/apps/web-antd/src/components/form-create/components/dict-select.vue @@ -0,0 +1,74 @@ + + + + diff --git a/apps/web-antd/src/components/form-create/components/use-api-select.tsx b/apps/web-antd/src/components/form-create/components/use-api-select.tsx new file mode 100644 index 0000000..c63e2cd --- /dev/null +++ b/apps/web-antd/src/components/form-create/components/use-api-select.tsx @@ -0,0 +1,336 @@ +import type { ApiSelectProps } from '#/components/form-create/typing'; + +import { defineComponent, onMounted, ref, useAttrs } from 'vue'; + +import { isEmpty } from '@vben/utils'; + +import { + Checkbox, + CheckboxGroup, + Radio, + RadioGroup, + Select, + SelectOption, +} from 'ant-design-vue'; + +import { requestClient } from '#/api/request'; + +export function useApiSelect(option: ApiSelectProps) { + return defineComponent({ + name: option.name, + props: { + // 选项标签 + labelField: { + type: String, + default: () => option.labelField ?? 'label', + }, + // 选项的值 + valueField: { + type: String, + default: () => option.valueField ?? 'value', + }, + // api 接口 + url: { + type: String, + default: () => option.url ?? '', + }, + // 请求类型 + method: { + type: String, + default: 'GET', + }, + // 选项解析函数 + parseFunc: { + type: String, + default: '', + }, + // 请求参数 + data: { + type: String, + default: '', + }, + // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio + selectType: { + type: String, + default: 'select', + }, + // 是否多选 + multiple: { + type: Boolean, + default: false, + }, + // 是否远程搜索 + remote: { + type: Boolean, + default: false, + }, + // 远程搜索时携带的参数 + remoteField: { + type: String, + default: 'label', + }, + // 返回值类型(用于部门选择器等):id 返回 ID,name 返回名称 + returnType: { + type: String, + default: 'id', + }, + }, + setup(props) { + const attrs = useAttrs(); + const options = ref([]); // 下拉数据 + const loading = ref(false); // 是否正在从远程获取数据 + const queryParam = ref(); // 当前输入的值 + const getOptions = async () => { + options.value = []; + // 接口选择器 + if (isEmpty(props.url)) { + return; + } + + switch (props.method) { + case 'GET': { + let url: string = props.url; + if (props.remote && queryParam.value !== undefined) { + url = url.includes('?') + ? `${url}&${props.remoteField}=${queryParam.value}` + : `${url}?${props.remoteField}=${queryParam.value}`; + } + parseOptions(await requestClient.get(url)); + break; + } + case 'POST': { + const data: any = JSON.parse(props.data); + if (props.remote) { + data[props.remoteField] = queryParam.value; + } + parseOptions(await requestClient.post(props.url, data)); + break; + } + } + }; + + function parseOptions(data: any) { + // 情况一:如果有自定义解析函数优先使用自定义解析 + if (!isEmpty(props.parseFunc)) { + options.value = parseFunc()?.(data); + return; + } + // 情况二:返回的直接是一个列表 + if (Array.isArray(data)) { + parseOptions0(data); + return; + } + // 情况二:返回的是分页数据,尝试读取 list + data = data.list; + if (!!data && Array.isArray(data)) { + parseOptions0(data); + return; + } + // 情况三:不是 yudao-vue-pro 标准返回 + console.warn( + `接口[${props.url}] 返回结果不是 yudao-vue-pro 标准返回建议采用自定义解析函数处理`, + ); + } + + function parseOptions0(data: any[]) { + if (Array.isArray(data)) { + options.value = data.map((item: any) => { + const label = parseExpression(item, props.labelField); + let value = parseExpression(item, props.valueField); + + // 根据 returnType 决定返回值 + // 如果设置了 returnType 为 'name',则返回 label 作为 value + if (props.returnType === 'name') { + value = label; + } + + return { + label, + value, + }; + }); + return; + } + console.warn(`接口[${props.url}] 返回结果不是一个数组`); + } + + function parseFunc() { + let parse: any = null; + if (props.parseFunc) { + // 解析字符串函数 + // eslint-disable-next-line no-new-func + parse = new Function(`return ${props.parseFunc}`)(); + } + return parse; + } + + function parseExpression(data: any, template: string) { + // 检测是否使用了表达式 + if (!template.includes('${')) { + return data[template]; + } + // 正则表达式匹配模板字符串中的 ${...} + const pattern = /\$\{([^}]*)\}/g; + // 使用replace函数配合正则表达式和回调函数来进行替换 + return template.replaceAll(pattern, (_, expr) => { + // expr 是匹配到的 ${} 内的表达式(这里是属性名),从 data 中获取对应的值 + const result = data[expr.trim()]; // 去除前后空白,以防用户输入带空格的属性名 + if (!result) { + console.warn( + `接口选择器选项模版[${template}][${expr.trim()}] 解析值失败结果为[${result}], 请检查属性名称是否存在于接口返回值中,存在则忽略此条!!!`, + ); + } + return result; + }); + } + + const remoteMethod = async (query: any) => { + if (!query) { + return; + } + loading.value = true; + try { + queryParam.value = query; + await getOptions(); + } finally { + loading.value = false; + } + }; + + onMounted(async () => { + await getOptions(); + }); + + const buildSelect = () => { + const { + modelValue, + 'onUpdate:modelValue': onUpdateModelValue, + ...restAttrs + } = attrs; + + if (props.multiple) { + // fix:多写此步是为了解决 multiple 属性问题 + return ( + + ); + } + return ( + + ); + }; + const buildCheckbox = () => { + const { + modelValue, + 'onUpdate:modelValue': onUpdateModelValue, + ...restAttrs + } = attrs; + if (isEmpty(options.value)) { + options.value = [ + { label: '选项1', value: '选项1' }, + { label: '选项2', value: '选项2' }, + ]; + } + return ( + + {options.value.map( + (item: { label: any; value: any }, index: any) => ( + + {item.label} + + ), + )} + + ); + }; + const buildRadio = () => { + const { + modelValue, + 'onUpdate:modelValue': onUpdateModelValue, + ...restAttrs + } = attrs; + if (isEmpty(options.value)) { + options.value = [ + { label: '选项1', value: '选项1' }, + { label: '选项2', value: '选项2' }, + ]; + } + return ( + + {options.value.map( + (item: { label: any; value: any }, index: any) => ( + + {item.label} + + ), + )} + + ); + }; + return () => ( + <> + {(() => { + switch (props.selectType) { + case 'checkbox': { + return buildCheckbox(); + } + case 'radio': { + return buildRadio(); + } + case 'select': { + return buildSelect(); + } + default: { + return buildSelect(); + } + } + })()} + + ); + }, + }); +} diff --git a/apps/web-antd/src/components/form-create/components/use-images-upload.tsx b/apps/web-antd/src/components/form-create/components/use-images-upload.tsx new file mode 100644 index 0000000..a9d0572 --- /dev/null +++ b/apps/web-antd/src/components/form-create/components/use-images-upload.tsx @@ -0,0 +1,25 @@ +import { defineComponent } from 'vue'; + +import ImageUpload from '#/components/upload/image-upload.vue'; + +export function useImagesUpload() { + return defineComponent({ + name: 'ImagesUpload', + props: { + multiple: { + type: Boolean, + default: true, + }, + maxNumber: { + type: Number, + default: 5, + }, + }, + setup() { + // TODO: @puhui999:@dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递 + return (props: { maxNumber?: number; multiple?: boolean }) => ( + + ); + }, + }); +} diff --git a/apps/web-antd/src/components/form-create/helpers.ts b/apps/web-antd/src/components/form-create/helpers.ts new file mode 100644 index 0000000..f43b1ce --- /dev/null +++ b/apps/web-antd/src/components/form-create/helpers.ts @@ -0,0 +1,253 @@ +import type { Rule } from '@form-create/ant-design-vue'; + +import type { Ref } from 'vue'; + +import type { Menu } from '#/components/form-create/typing'; + +import { isRef, nextTick, onMounted } from 'vue'; + +import formCreate from '@form-create/ant-design-vue'; + +import { apiSelectRule } from '#/components/form-create/rules/data'; + +import { + useDictSelectRule, + useEditorRule, + useSelectRule, + useUploadFileRule, + useUploadImageRule, + useUploadImagesRule, +} from './rules'; + +/** 编码表单 Conf */ +export function encodeConf(designerRef: any) { + // 关联案例:https://gitee.com/yudaocode/yudao-ui-admin-vue3/pulls/834/ + return formCreate.toJson(designerRef.value.getOption()); +} + +/** 解码表单 Conf */ +export function decodeConf(conf: string) { + return formCreate.parseJson(conf); +} + +/** 编码表单 Fields */ +export function encodeFields(designerRef: any) { + const rule = designerRef.value.getRule(); + const fields: string[] = []; + rule.forEach((item: any) => { + fields.push(formCreate.toJson(item)); + }); + return fields; +} + +/** 解码表单 Fields */ +export function decodeFields(fields: string[]) { + const rule: Rule[] = []; + fields.forEach((item) => { + rule.push(formCreate.parseJson(item)); + }); + return rule; +} + +/** 设置表单的 Conf 和 Fields,适用 FcDesigner 场景 */ +export function setConfAndFields( + designerRef: any, + conf: string, + fields: string | string[], +) { + designerRef.value.setOption(decodeConf(conf)); + // 处理 fields 参数类型,确保传入 decodeFields 的是 string[] 类型 + const fieldsArray = Array.isArray(fields) ? fields : [fields]; + designerRef.value.setRule(decodeFields(fieldsArray)); +} + +/** 设置表单的 Conf 和 Fields,适用 form-create 场景 */ +export function setConfAndFields2( + detailPreview: any, + conf: string, + fields: string[], + value?: any, +) { + if (isRef(detailPreview)) { + detailPreview = detailPreview.value; + } + detailPreview.option = decodeConf(conf); + detailPreview.rule = decodeFields(fields); + if (value) { + detailPreview.value = value; + } +} + +export function makeRequiredRule() { + return { + type: 'Required', + field: 'formCreate$required', + title: '是否必填', + }; +} + +export function localeProps( + t: (msg: string) => any, + prefix: string, + rules: any[], +) { + return rules.map((rule: { field: string; title: any }) => { + if (rule.field === 'formCreate$required') { + rule.title = t('props.required') || rule.title; + } else if (rule.field && rule.field !== '_optionType') { + rule.title = t(`components.${prefix}.${rule.field}`) || rule.title; + } + return rule; + }); +} + +/** + * 解析表单组件的 field, title 等字段(递归,如果组件包含子组件) + * + * @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule + * @param fields 解析后表单组件字段 + * @param parentTitle 如果是子表单,子表单的标题,默认为空 + */ +export function parseFormFields( + rule: Record, + fields: Array> = [], + parentTitle: string = '', +) { + const { type, field, $required, title: tempTitle, children } = rule; + if (field && tempTitle) { + let title = tempTitle; + if (parentTitle) { + title = `${parentTitle}.${tempTitle}`; + } + let required = false; + if ($required) { + required = true; + } + fields.push({ + field, + title, + type, + required, + }); + // TODO 子表单 需要处理子表单字段 + // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) { + // // 解析子表单的字段 + // rule.props.rule.forEach((item) => { + // parseFields(item, fieldsPermission, title) + // }) + // } + } + if (children && Array.isArray(children)) { + children.forEach((rule) => { + parseFormFields(rule, fields); + }); + } +} + +/** + * 表单设计器增强 hook + * 新增 + * - 文件上传 + * - 单图上传 + * - 多图上传 + * - 字典选择器 + * - 用户选择器 + * - 部门选择器 + * - 富文本 + */ +export async function useFormCreateDesigner(designer: Ref) { + const editorRule = useEditorRule(); + const uploadFileRule = useUploadFileRule(); + const uploadImageRule = useUploadImageRule(); + const uploadImagesRule = useUploadImagesRule(); + + /** 构建表单组件 */ + function buildFormComponents() { + // 移除自带的上传组件规则,使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代 + designer.value?.removeMenuItem('upload'); + // 移除自带的富文本组件规则,使用 editorRule 替代 + designer.value?.removeMenuItem('fc-editor'); + const components = [ + editorRule, + uploadFileRule, + uploadImageRule, + uploadImagesRule, + ]; + components.forEach((component) => { + // 插入组件规则 + designer.value?.addComponent(component); + // 插入拖拽按钮到 `main` 分类下 + designer.value?.appendMenuItem('main', { + icon: component.icon, + name: component.name, + label: component.label, + }); + }); + } + + const userSelectRule = useSelectRule({ + name: 'UserSelect', + label: '用户选择器', + icon: 'icon-eye', + }); + const deptSelectRule = useSelectRule({ + name: 'DeptSelect', + label: '部门选择器', + icon: 'icon-tree', + props: [ + { + type: 'select', + field: 'returnType', + title: '返回值类型', + value: 'id', + options: [ + { label: '部门编号', value: 'id' }, + { label: '部门名称', value: 'name' }, + ], + }, + ], + }); + const dictSelectRule = useDictSelectRule(); + const apiSelectRule0 = useSelectRule({ + name: 'ApiSelect', + label: '接口选择器', + icon: 'icon-json', + props: [...apiSelectRule], + event: ['click', 'change', 'visibleChange', 'clear', 'blur', 'focus'], + }); + + /** 构建系统字段菜单 */ + function buildSystemMenu() { + // 移除自带的下拉选择器组件,使用 currencySelectRule 替代 + // designer.value?.removeMenuItem('select') + // designer.value?.removeMenuItem('radio') + // designer.value?.removeMenuItem('checkbox') + const components = [ + userSelectRule, + deptSelectRule, + dictSelectRule, + apiSelectRule0, + ]; + const menu: Menu = { + name: 'system', + title: '系统字段', + list: components.map((component) => { + // 插入组件规则 + designer.value?.addComponent(component); + // 插入拖拽按钮到 `system` 分类下 + return { + icon: component.icon, + name: component.name, + label: component.label, + }; + }), + }; + designer.value?.addMenu(menu); + } + + onMounted(async () => { + await nextTick(); + buildFormComponents(); + buildSystemMenu(); + }); +} diff --git a/apps/web-antd/src/components/form-create/index.ts b/apps/web-antd/src/components/form-create/index.ts new file mode 100644 index 0000000..2c51215 --- /dev/null +++ b/apps/web-antd/src/components/form-create/index.ts @@ -0,0 +1,3 @@ +export { useApiSelect } from './components/use-api-select'; + +export * from './helpers'; diff --git a/apps/web-antd/src/components/form-create/rules/data.ts b/apps/web-antd/src/components/form-create/rules/data.ts new file mode 100644 index 0000000..3d34bf7 --- /dev/null +++ b/apps/web-antd/src/components/form-create/rules/data.ts @@ -0,0 +1,182 @@ +/* eslint-disable no-template-curly-in-string */ +const selectRule = [ + { + type: 'select', + field: 'selectType', + title: '选择器类型', + value: 'select', + options: [ + { label: '下拉框', value: 'select' }, + { label: '单选框', value: 'radio' }, + { label: '多选框', value: 'checkbox' }, + ], + // 参考 https://www.form-create.com/v3/guide/control 组件联动,单选框和多选框不需要多选属性 + control: [ + { + value: 'select', + condition: '==', + method: 'hidden', + rule: [ + 'multiple', + 'clearable', + 'collapseTags', + 'multipleLimit', + 'allowCreate', + 'filterable', + 'noMatchText', + 'remote', + 'remoteMethod', + 'reserveKeyword', + 'defaultFirstOption', + 'automaticDropdown', + ], + }, + ], + }, + { + type: 'switch', + field: 'filterable', + title: '是否可搜索', + }, + { type: 'switch', field: 'multiple', title: '是否多选' }, + { + type: 'switch', + field: 'disabled', + title: '是否禁用', + }, + { type: 'switch', field: 'clearable', title: '是否可以清空选项' }, + { + type: 'switch', + field: 'collapseTags', + title: '多选时是否将选中值按文字的形式展示', + }, + { + type: 'inputNumber', + field: 'multipleLimit', + title: '多选时用户最多可以选择的项目数,为 0 则不限制', + props: { min: 0 }, + }, + { + type: 'input', + field: 'autocomplete', + title: 'autocomplete 属性', + }, + { type: 'input', field: 'placeholder', title: '占位符' }, + { type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目' }, + { + type: 'input', + field: 'noMatchText', + title: '搜索条件无匹配时显示的文字', + }, + { type: 'input', field: 'noDataText', title: '选项为空时显示的文字' }, + { + type: 'switch', + field: 'reserveKeyword', + title: '多选且可搜索时,是否在选中一个选项后保留当前的搜索关键词', + }, + { + type: 'switch', + field: 'defaultFirstOption', + title: '在输入框按下回车,选择第一个匹配项', + }, + { + type: 'switch', + field: 'popperAppendToBody', + title: '是否将弹出框插入至 body 元素', + value: true, + }, + { + type: 'switch', + field: 'automaticDropdown', + title: '对于不可搜索的 Select,是否在输入框获得焦点后自动弹出选项菜单', + }, +]; + +const apiSelectRule = [ + { + type: 'input', + field: 'url', + title: 'url 地址', + props: { + placeholder: '/system/user/simple-list', + }, + }, + { + type: 'select', + field: 'method', + title: '请求类型', + value: 'GET', + options: [ + { label: 'GET', value: 'GET' }, + { label: 'POST', value: 'POST' }, + ], + control: [ + { + value: 'GET', + condition: '!=', + method: 'hidden', + rule: [ + { + type: 'input', + field: 'data', + title: '请求参数 JSON 格式', + props: { + autoSize: true, + type: 'textarea', + placeholder: '{"type": 1}', + }, + }, + ], + }, + ], + }, + { + type: 'input', + field: 'labelField', + title: 'label 属性', + info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}', + props: { + placeholder: 'nickname', + }, + }, + { + type: 'input', + field: 'valueField', + title: 'value 属性', + info: '可以使用 el 表达式:${属性},来实现复杂数据组合。如:${nickname}-${id}', + props: { + placeholder: 'id', + }, + }, + { + type: 'input', + field: 'parseFunc', + title: '选项解析函数', + info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表 + (data: any)=>{ label: string; value: any }[]`, + props: { + autoSize: true, + rows: { minRows: 2, maxRows: 6 }, + type: 'textarea', + placeholder: ` + function (data) { + console.log(data) + return data.list.map(item=> ({label: item.nickname,value: item.id})) + }`, + }, + }, + { + type: 'switch', + field: 'remote', + info: '是否可搜索', + title: '其中的选项是否从服务器远程加载', + }, + { + type: 'input', + field: 'remoteField', + title: '请求参数', + info: '远程请求时请求携带的参数名称,如:name', + }, +]; + +export { apiSelectRule, selectRule }; diff --git a/apps/web-antd/src/components/form-create/rules/index.ts b/apps/web-antd/src/components/form-create/rules/index.ts new file mode 100644 index 0000000..db306da --- /dev/null +++ b/apps/web-antd/src/components/form-create/rules/index.ts @@ -0,0 +1,6 @@ +export { useDictSelectRule } from './use-dict-select'; +export { useEditorRule } from './use-editor-rule'; +export { useSelectRule } from './use-select-rule'; +export { useUploadFileRule } from './use-upload-file-rule'; +export { useUploadImageRule } from './use-upload-image-rule'; +export { useUploadImagesRule } from './use-upload-images-rule'; diff --git a/apps/web-antd/src/components/form-create/rules/use-dict-select.ts b/apps/web-antd/src/components/form-create/rules/use-dict-select.ts new file mode 100644 index 0000000..0f041c8 --- /dev/null +++ b/apps/web-antd/src/components/form-create/rules/use-dict-select.ts @@ -0,0 +1,70 @@ +import type { SystemDictTypeApi } from '#/api/system/dict/type'; + +import { onMounted, ref } from 'vue'; + +import { buildUUID, cloneDeep } from '@vben/utils'; + +import { getSimpleDictTypeList } from '#/api/system/dict/type'; +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; +import { selectRule } from '#/components/form-create/rules/data'; + +/** 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule */ +export function useDictSelectRule() { + const label = '字典选择器'; + const name = 'DictSelect'; + const rules = cloneDeep(selectRule); + const dictOptions = ref<{ label: string; value: string }[]>([]); // 字典类型下拉数据 + onMounted(async () => { + const data = await getSimpleDictTypeList(); + if (!data || data.length === 0) { + return; + } + dictOptions.value = + data?.map((item: SystemDictTypeApi.DictType) => ({ + label: item.name, + value: item.type, + })) ?? []; + }); + return { + icon: 'icon-descriptions', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + modelField: 'value', + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'select', + field: 'dictType', + title: '字典类型', + value: '', + options: dictOptions.value, + }, + { + type: 'select', + field: 'valueType', + title: '字典值类型', + value: 'str', + options: [ + { label: '数字', value: 'int' }, + { label: '字符串', value: 'str' }, + { label: '布尔值', value: 'bool' }, + ], + }, + ...rules, + ]); + }, + }; +} diff --git a/apps/web-antd/src/components/form-create/rules/use-editor-rule.ts b/apps/web-antd/src/components/form-create/rules/use-editor-rule.ts new file mode 100644 index 0000000..dcde682 --- /dev/null +++ b/apps/web-antd/src/components/form-create/rules/use-editor-rule.ts @@ -0,0 +1,36 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; + +export function useEditorRule() { + const label = '富文本'; + const name = 'Tinymce'; + return { + icon: 'icon-editor', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'input', + field: 'height', + title: '高度', + }, + { type: 'switch', field: 'readonly', title: '是否只读' }, + ]); + }, + }; +} diff --git a/apps/web-antd/src/components/form-create/rules/use-select-rule.ts b/apps/web-antd/src/components/form-create/rules/use-select-rule.ts new file mode 100644 index 0000000..3efd8c9 --- /dev/null +++ b/apps/web-antd/src/components/form-create/rules/use-select-rule.ts @@ -0,0 +1,45 @@ +import type { SelectRuleOption } from '#/components/form-create/typing'; + +import { buildUUID, cloneDeep } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; +import { selectRule } from '#/components/form-create/rules/data'; + +/** + * 通用选择器规则 hook + * + * @param option 规则配置 + */ +export function useSelectRule(option: SelectRuleOption) { + const label = option.label; + const name = option.name; + const rules = cloneDeep(selectRule); + return { + icon: option.icon, + label, + name, + event: option.event, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + if (!option.props) { + option.props = []; + } + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + ...option.props, + ...rules, + ]); + }, + }; +} diff --git a/apps/web-antd/src/components/form-create/rules/use-upload-file-rule.ts b/apps/web-antd/src/components/form-create/rules/use-upload-file-rule.ts new file mode 100644 index 0000000..91c21e5 --- /dev/null +++ b/apps/web-antd/src/components/form-create/rules/use-upload-file-rule.ts @@ -0,0 +1,84 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; + +export function useUploadFileRule() { + const label = '文件上传'; + const name = 'FileUpload'; + return { + icon: 'icon-upload', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'select', + field: 'fileType', + title: '文件类型', + value: ['doc', 'xls', 'ppt', 'txt', 'pdf'], + options: [ + { label: 'doc', value: 'doc' }, + { label: 'xls', value: 'xls' }, + { label: 'ppt', value: 'ppt' }, + { label: 'txt', value: 'txt' }, + { label: 'pdf', value: 'pdf' }, + ], + props: { + mode: 'multiple', + }, + }, + { + type: 'switch', + field: 'autoUpload', + title: '是否在选取文件后立即进行上传', + value: true, + }, + { + type: 'switch', + field: 'drag', + title: '拖拽上传', + value: false, + }, + { + type: 'switch', + field: 'isShowTip', + title: '是否显示提示', + value: true, + }, + { + type: 'inputNumber', + field: 'fileSize', + title: '大小限制(MB)', + value: 5, + props: { min: 0 }, + }, + { + type: 'inputNumber', + field: 'limit', + title: '数量限制', + value: 5, + props: { min: 0 }, + }, + { + type: 'switch', + field: 'disabled', + title: '是否禁用', + value: false, + }, + ]); + }, + }; +} diff --git a/apps/web-antd/src/components/form-create/rules/use-upload-image-rule.ts b/apps/web-antd/src/components/form-create/rules/use-upload-image-rule.ts new file mode 100644 index 0000000..58dd71d --- /dev/null +++ b/apps/web-antd/src/components/form-create/rules/use-upload-image-rule.ts @@ -0,0 +1,93 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; + +export function useUploadImageRule() { + const label = '单图上传'; + const name = 'ImageUpload'; + return { + icon: 'icon-image', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'switch', + field: 'drag', + title: '拖拽上传', + value: false, + }, + { + type: 'select', + field: 'fileType', + title: '图片类型限制', + value: ['image/jpeg', 'image/png', 'image/gif'], + options: [ + { label: 'image/apng', value: 'image/apng' }, + { label: 'image/bmp', value: 'image/bmp' }, + { label: 'image/gif', value: 'image/gif' }, + { label: 'image/jpeg', value: 'image/jpeg' }, + { label: 'image/pjpeg', value: 'image/pjpeg' }, + { label: 'image/svg+xml', value: 'image/svg+xml' }, + { label: 'image/tiff', value: 'image/tiff' }, + { label: 'image/webp', value: 'image/webp' }, + { label: 'image/x-icon', value: 'image/x-icon' }, + ], + props: { + mode: 'multiple', + }, + }, + { + type: 'inputNumber', + field: 'fileSize', + title: '大小限制(MB)', + value: 5, + props: { min: 0 }, + }, + { + type: 'input', + field: 'height', + title: '组件高度', + value: '150px', + }, + { + type: 'input', + field: 'width', + title: '组件宽度', + value: '150px', + }, + { + type: 'input', + field: 'borderradius', + title: '组件边框圆角', + value: '8px', + }, + { + type: 'switch', + field: 'disabled', + title: '是否显示删除按钮', + value: true, + }, + { + type: 'switch', + field: 'showBtnText', + title: '是否显示按钮文字', + value: true, + }, + ]); + }, + }; +} diff --git a/apps/web-antd/src/components/form-create/rules/use-upload-images-rule.ts b/apps/web-antd/src/components/form-create/rules/use-upload-images-rule.ts new file mode 100644 index 0000000..b1559ac --- /dev/null +++ b/apps/web-antd/src/components/form-create/rules/use-upload-images-rule.ts @@ -0,0 +1,89 @@ +import { buildUUID } from '@vben/utils'; + +import { + localeProps, + makeRequiredRule, +} from '#/components/form-create/helpers'; + +export function useUploadImagesRule() { + const label = '多图上传'; + const name = 'ImagesUpload'; + return { + icon: 'icon-image', + label, + name, + rule() { + return { + type: name, + field: buildUUID(), + title: label, + info: '', + $required: false, + }; + }, + props(_: any, { t }: any) { + return localeProps(t, `${name}.props`, [ + makeRequiredRule(), + { + type: 'switch', + field: 'drag', + title: '拖拽上传', + value: false, + }, + { + type: 'select', + field: 'fileType', + title: '图片类型限制', + value: ['image/jpeg', 'image/png', 'image/gif'], + options: [ + { label: 'image/apng', value: 'image/apng' }, + { label: 'image/bmp', value: 'image/bmp' }, + { label: 'image/gif', value: 'image/gif' }, + { label: 'image/jpeg', value: 'image/jpeg' }, + { label: 'image/pjpeg', value: 'image/pjpeg' }, + { label: 'image/svg+xml', value: 'image/svg+xml' }, + { label: 'image/tiff', value: 'image/tiff' }, + { label: 'image/webp', value: 'image/webp' }, + { label: 'image/x-icon', value: 'image/x-icon' }, + ], + props: { + mode: 'multiple', + maxNumber: 5, + }, + }, + { + type: 'inputNumber', + field: 'fileSize', + title: '大小限制(MB)', + value: 5, + props: { min: 0 }, + }, + { + type: 'inputNumber', + field: 'limit', + title: '数量限制', + value: 5, + props: { min: 0 }, + }, + { + type: 'input', + field: 'height', + title: '组件高度', + value: '150px', + }, + { + type: 'input', + field: 'width', + title: '组件宽度', + value: '150px', + }, + { + type: 'input', + field: 'borderradius', + title: '组件边框圆角', + value: '8px', + }, + ]); + }, + }; +} diff --git a/apps/web-antd/src/components/form-create/typing.ts b/apps/web-antd/src/components/form-create/typing.ts new file mode 100644 index 0000000..35c1a39 --- /dev/null +++ b/apps/web-antd/src/components/form-create/typing.ts @@ -0,0 +1,39 @@ +/** 数据字典 Select 选择器组件 Props 类型 */ +export interface DictSelectProps { + dictType: string; // 字典类型 + valueType?: 'bool' | 'int' | 'str'; // 字典值类型 + selectType?: 'checkbox' | 'radio' | 'select'; // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio + formCreateInject?: any; +} + +/** 左侧拖拽按钮 */ +export interface MenuItem { + label: string; + name: string; + icon: string; +} + +/** 左侧拖拽按钮分类 */ +export interface Menu { + title: string; + name: string; + list: MenuItem[]; +} + +/** 通用 API 下拉组件 Props 类型 */ +export interface ApiSelectProps { + name: string; // 组件名称 + labelField?: string; // 选项标签 + valueField?: string; // 选项的值 + url?: string; // url 接口 + isDict?: boolean; // 是否字典选择器 +} + +/** 选择组件规则配置类型 */ +export interface SelectRuleOption { + label: string; // label 名称 + name: string; // 组件名称 + icon: string; // 组件图标 + props?: any[]; // 组件规则 + event?: any[]; // 事件配置 +} diff --git a/apps/web-antd/src/components/markdown-view/index.ts b/apps/web-antd/src/components/markdown-view/index.ts new file mode 100644 index 0000000..742bce8 --- /dev/null +++ b/apps/web-antd/src/components/markdown-view/index.ts @@ -0,0 +1,3 @@ +export { default as MarkdownView } from './markdown-view.vue'; + +export * from './typing'; diff --git a/apps/web-antd/src/components/markdown-view/markdown-view.vue b/apps/web-antd/src/components/markdown-view/markdown-view.vue new file mode 100644 index 0000000..373957a --- /dev/null +++ b/apps/web-antd/src/components/markdown-view/markdown-view.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/apps/web-antd/src/components/markdown-view/typing.ts b/apps/web-antd/src/components/markdown-view/typing.ts new file mode 100644 index 0000000..350bbcf --- /dev/null +++ b/apps/web-antd/src/components/markdown-view/typing.ts @@ -0,0 +1,3 @@ +export type MarkdownViewProps = { + content: string; +}; diff --git a/apps/web-antd/src/components/operate-log/index.ts b/apps/web-antd/src/components/operate-log/index.ts new file mode 100644 index 0000000..cf38b5e --- /dev/null +++ b/apps/web-antd/src/components/operate-log/index.ts @@ -0,0 +1,3 @@ +export { default as OperateLog } from './operate-log.vue'; + +export type { OperateLogProps } from './typing'; diff --git a/apps/web-antd/src/components/operate-log/operate-log.vue b/apps/web-antd/src/components/operate-log/operate-log.vue new file mode 100644 index 0000000..d1ae601 --- /dev/null +++ b/apps/web-antd/src/components/operate-log/operate-log.vue @@ -0,0 +1,51 @@ + + diff --git a/apps/web-antd/src/components/operate-log/typing.ts b/apps/web-antd/src/components/operate-log/typing.ts new file mode 100644 index 0000000..773237b --- /dev/null +++ b/apps/web-antd/src/components/operate-log/typing.ts @@ -0,0 +1,5 @@ +import type { SystemOperateLogApi } from '#/api/system/operate-log'; + +export interface OperateLogProps { + logList: SystemOperateLogApi.OperateLog[]; // 操作日志列表 +} diff --git a/apps/web-antd/src/components/select-modal/dept-select-modal.vue b/apps/web-antd/src/components/select-modal/dept-select-modal.vue new file mode 100644 index 0000000..09cea78 --- /dev/null +++ b/apps/web-antd/src/components/select-modal/dept-select-modal.vue @@ -0,0 +1,143 @@ +// TODO @芋艿:是否有更好的组织形式?! + + diff --git a/apps/web-antd/src/components/select-modal/index.ts b/apps/web-antd/src/components/select-modal/index.ts new file mode 100644 index 0000000..2b6e918 --- /dev/null +++ b/apps/web-antd/src/components/select-modal/index.ts @@ -0,0 +1,2 @@ +export { default as DeptSelectModal } from './dept-select-modal.vue'; +export { default as UserSelectModal } from './user-select-modal.vue'; diff --git a/apps/web-antd/src/components/select-modal/user-select-modal.vue b/apps/web-antd/src/components/select-modal/user-select-modal.vue new file mode 100644 index 0000000..77ef981 --- /dev/null +++ b/apps/web-antd/src/components/select-modal/user-select-modal.vue @@ -0,0 +1,546 @@ + + + + + diff --git a/apps/web-antd/src/components/shortcut-date-range-picker/index.ts b/apps/web-antd/src/components/shortcut-date-range-picker/index.ts new file mode 100644 index 0000000..a3ee515 --- /dev/null +++ b/apps/web-antd/src/components/shortcut-date-range-picker/index.ts @@ -0,0 +1 @@ +export { default as ShortcutDateRangePicker } from './shortcut-date-range-picker.vue'; diff --git a/apps/web-antd/src/components/shortcut-date-range-picker/shortcut-date-range-picker.vue b/apps/web-antd/src/components/shortcut-date-range-picker/shortcut-date-range-picker.vue new file mode 100644 index 0000000..5a3ce05 --- /dev/null +++ b/apps/web-antd/src/components/shortcut-date-range-picker/shortcut-date-range-picker.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-antd/src/components/table-action/icons.ts b/apps/web-antd/src/components/table-action/icons.ts new file mode 100644 index 0000000..578265f --- /dev/null +++ b/apps/web-antd/src/components/table-action/icons.ts @@ -0,0 +1,16 @@ +export const ACTION_ICON = { + DOWNLOAD: 'lucide:download', + UPLOAD: 'lucide:upload', + ADD: 'lucide:plus', + EDIT: 'lucide:edit', + DELETE: 'lucide:trash-2', + REFRESH: 'lucide:refresh-cw', + SEARCH: 'lucide:search', + FILTER: 'lucide:filter', + MORE: 'lucide:ellipsis-vertical', + VIEW: 'lucide:eye', + COPY: 'lucide:copy', + CLOSE: 'lucide:x', + BOOK: 'lucide:book', + AUDIT: 'lucide:file-check', +}; diff --git a/apps/web-antd/src/components/table-action/index.ts b/apps/web-antd/src/components/table-action/index.ts new file mode 100644 index 0000000..672c0a5 --- /dev/null +++ b/apps/web-antd/src/components/table-action/index.ts @@ -0,0 +1,4 @@ +export * from './icons'; + +export { default as TableAction } from './table-action.vue'; +export * from './typing'; diff --git a/apps/web-antd/src/components/table-action/table-action.vue b/apps/web-antd/src/components/table-action/table-action.vue new file mode 100644 index 0000000..679f6fc --- /dev/null +++ b/apps/web-antd/src/components/table-action/table-action.vue @@ -0,0 +1,278 @@ + + + + + + diff --git a/apps/web-antd/src/components/table-action/typing.ts b/apps/web-antd/src/components/table-action/typing.ts new file mode 100644 index 0000000..1e44fb2 --- /dev/null +++ b/apps/web-antd/src/components/table-action/typing.ts @@ -0,0 +1,31 @@ +import type { + ButtonProps, + ButtonType, +} from 'ant-design-vue/es/button/buttonTypes'; +import type { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip'; + +export interface PopConfirm { + title: string; + okText?: string; + cancelText?: string; + confirm: () => void; + cancel?: () => void; + icon?: string; + disabled?: boolean; +} + +export interface ActionItem extends ButtonProps { + onClick?: () => void; + type?: ButtonType; + label?: string; + color?: 'error' | 'success' | 'warning'; + icon?: string; + popConfirm?: PopConfirm; + disabled?: boolean; + divider?: boolean; + // 权限编码控制是否显示 + auth?: string[]; + // 业务控制是否显示 + ifShow?: ((action: ActionItem) => boolean) | boolean; + tooltip?: string | TooltipProps; +} diff --git a/apps/web-antd/src/components/tinymce/editor.vue b/apps/web-antd/src/components/tinymce/editor.vue new file mode 100644 index 0000000..722073c --- /dev/null +++ b/apps/web-antd/src/components/tinymce/editor.vue @@ -0,0 +1,344 @@ + + + + + + diff --git a/apps/web-antd/src/components/tinymce/helper.ts b/apps/web-antd/src/components/tinymce/helper.ts new file mode 100644 index 0000000..1f98dda --- /dev/null +++ b/apps/web-antd/src/components/tinymce/helper.ts @@ -0,0 +1,85 @@ +const validEvents = new Set([ + 'onActivate', + 'onAddUndo', + 'onBeforeAddUndo', + 'onBeforeExecCommand', + 'onBeforeGetContent', + 'onBeforePaste', + 'onBeforeRenderUI', + 'onBeforeSetContent', + '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', + 'onObjectResized', + 'onObjectResizeStart', + 'onObjectSelected', + 'onPaste', + 'onPostProcess', + 'onPostRender', + 'onPreProcess', + 'onProgressState', + 'onRedo', + 'onRemove', + 'onReset', + 'onSaveContent', + 'onSelectionChange', + 'onSetAttrib', + 'onSetContent', + 'onShow', + 'onSubmit', + 'onUndo', + 'onVisualAid', +]); + +const isValidKey = (key: string) => validEvents.has(key); + +export const bindHandlers = ( + initEvent: Event, + listeners: any, + editor: any, +): void => { + Object.keys(listeners) + .filter((element) => isValidKey(element)) + .forEach((key: string) => { + const handler = listeners[key]; + if (typeof handler === 'function') { + if (key === 'onInit') { + handler(initEvent, editor); + } else { + editor.on(key.slice(2), (e: any) => handler(e, editor)); + } + } + }); +}; diff --git a/apps/web-antd/src/components/tinymce/img-upload.vue b/apps/web-antd/src/components/tinymce/img-upload.vue new file mode 100644 index 0000000..e5d47bb --- /dev/null +++ b/apps/web-antd/src/components/tinymce/img-upload.vue @@ -0,0 +1,84 @@ + + + + diff --git a/apps/web-antd/src/components/tinymce/index.ts b/apps/web-antd/src/components/tinymce/index.ts new file mode 100644 index 0000000..c277d78 --- /dev/null +++ b/apps/web-antd/src/components/tinymce/index.ts @@ -0,0 +1 @@ +export { default as Tinymce } from './editor.vue'; diff --git a/apps/web-antd/src/components/tinymce/tinymce.ts b/apps/web-antd/src/components/tinymce/tinymce.ts new file mode 100644 index 0000000..45a867b --- /dev/null +++ b/apps/web-antd/src/components/tinymce/tinymce.ts @@ -0,0 +1,17 @@ +// Any plugins you want to setting has to be imported +// Detail plugins list see https://www.tiny.cloud/docs/plugins/ +// Custom builds see https://www.tiny.cloud/download/custom-builds/ +// colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration + +export const plugins = + 'preview importcss searchreplace autolink autosave save directionality code visualblocks visualchars fullscreen image link media codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help emoticons accordion'; + +// 和 vben2.0 不同,从 https://www.tiny.cloud/ 拷贝 Vue 部分,然后去掉 importword exportword exportpdf | math 部分,并额外增加最后一行(来自 vben2.0 差异的部分) +export const toolbar = + 'undo redo | accordion accordionremove | \\\n' + + ' blocks fontfamily fontsize | bold italic underline strikethrough | \\\n' + + ' align numlist bullist | link image | table media | \\\n' + + ' lineheight outdent indent | forecolor backcolor removeformat | \\\n' + + ' charmap emoticons | code fullscreen preview | save print | \\\n' + + ' pagebreak anchor codesample | ltr rtl | \\\n' + + ' hr searchreplace alignleft aligncenter alignright blockquote subscript superscript'; diff --git a/apps/web-antd/src/components/upload/file-upload.vue b/apps/web-antd/src/components/upload/file-upload.vue new file mode 100644 index 0000000..b2a8007 --- /dev/null +++ b/apps/web-antd/src/components/upload/file-upload.vue @@ -0,0 +1,344 @@ + + + + + diff --git a/apps/web-antd/src/components/upload/image-upload.vue b/apps/web-antd/src/components/upload/image-upload.vue new file mode 100644 index 0000000..fe962f5 --- /dev/null +++ b/apps/web-antd/src/components/upload/image-upload.vue @@ -0,0 +1,335 @@ + + + + + diff --git a/apps/web-antd/src/components/upload/index.ts b/apps/web-antd/src/components/upload/index.ts new file mode 100644 index 0000000..14e57fe --- /dev/null +++ b/apps/web-antd/src/components/upload/index.ts @@ -0,0 +1,3 @@ +export { default as FileUpload } from './file-upload.vue'; +export { default as ImageUpload } from './image-upload.vue'; +export { default as InputUpload } from './input-upload.vue'; diff --git a/apps/web-antd/src/components/upload/input-upload.vue b/apps/web-antd/src/components/upload/input-upload.vue new file mode 100644 index 0000000..6649546 --- /dev/null +++ b/apps/web-antd/src/components/upload/input-upload.vue @@ -0,0 +1,74 @@ + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/conversation/list.vue b/apps/web-antd/src/views/ai/chat/index/modules/conversation/list.vue new file mode 100644 index 0000000..f150e62 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/conversation/list.vue @@ -0,0 +1,435 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/conversation/update-form.vue b/apps/web-antd/src/views/ai/chat/index/modules/conversation/update-form.vue new file mode 100644 index 0000000..fda49d7 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/conversation/update-form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/message/file-upload.vue b/apps/web-antd/src/views/ai/chat/index/modules/message/file-upload.vue new file mode 100644 index 0000000..3d5e27f --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/message/file-upload.vue @@ -0,0 +1,304 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/message/files.vue b/apps/web-antd/src/views/ai/chat/index/modules/message/files.vue new file mode 100644 index 0000000..ed163a0 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/message/files.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/message/knowledge.vue b/apps/web-antd/src/views/ai/chat/index/modules/message/knowledge.vue new file mode 100644 index 0000000..960188c --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/message/knowledge.vue @@ -0,0 +1,103 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/message/list-empty.vue b/apps/web-antd/src/views/ai/chat/index/modules/message/list-empty.vue new file mode 100644 index 0000000..f97a054 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/message/list-empty.vue @@ -0,0 +1,37 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/message/list.vue b/apps/web-antd/src/views/ai/chat/index/modules/message/list.vue new file mode 100644 index 0000000..a841ee2 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/message/list.vue @@ -0,0 +1,243 @@ + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/message/loading.vue b/apps/web-antd/src/views/ai/chat/index/modules/message/loading.vue new file mode 100644 index 0000000..5928a19 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/message/loading.vue @@ -0,0 +1,9 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/message/new-conversation.vue b/apps/web-antd/src/views/ai/chat/index/modules/message/new-conversation.vue new file mode 100644 index 0000000..41c08ce --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/message/new-conversation.vue @@ -0,0 +1,23 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/message/reasoning.vue b/apps/web-antd/src/views/ai/chat/index/modules/message/reasoning.vue new file mode 100644 index 0000000..de3175d --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/message/reasoning.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/message/web-search.vue b/apps/web-antd/src/views/ai/chat/index/modules/message/web-search.vue new file mode 100644 index 0000000..d48a94c --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/message/web-search.vue @@ -0,0 +1,173 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/role/category-list.vue b/apps/web-antd/src/views/ai/chat/index/modules/role/category-list.vue new file mode 100644 index 0000000..91f6d64 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/role/category-list.vue @@ -0,0 +1,43 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/role/list.vue b/apps/web-antd/src/views/ai/chat/index/modules/role/list.vue new file mode 100644 index 0000000..544dfb0 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/role/list.vue @@ -0,0 +1,126 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/index/modules/role/repository.vue b/apps/web-antd/src/views/ai/chat/index/modules/role/repository.vue new file mode 100644 index 0000000..e9236a6 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/index/modules/role/repository.vue @@ -0,0 +1,249 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/manager/data.ts b/apps/web-antd/src/views/ai/chat/manager/data.ts new file mode 100644 index 0000000..f8dcb1f --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/manager/data.ts @@ -0,0 +1,223 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemUserApi } from '#/api/system/user'; + +import { DICT_TYPE } from '@vben/constants'; + +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 关联数据 */ +let userList: SystemUserApi.User[] = []; +getSimpleUserList().then((data) => (userList = data)); + +/** 列表的搜索表单 */ +export function useGridFormSchemaConversation(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + placeholder: '请输入用户编号', + allowClear: true, + }, + }, + { + fieldName: 'title', + label: '聊天标题', + component: 'Input', + componentProps: { + placeholder: '请输入聊天标题', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumnsConversation(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '对话编号', + fixed: 'left', + minWidth: 180, + }, + { + field: 'title', + title: '对话标题', + minWidth: 180, + fixed: 'left', + }, + { + title: '用户', + minWidth: 180, + field: 'userId', + formatter: ({ cellValue }) => { + if (cellValue === 0) { + return '系统'; + } + return userList.find((user) => user.id === cellValue)?.nickname || '-'; + }, + }, + { + field: 'roleName', + title: '角色', + minWidth: 180, + }, + { + field: 'model', + title: '模型标识', + minWidth: 180, + }, + { + field: 'messageCount', + title: '消息数', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'temperature', + title: '温度参数', + minWidth: 80, + }, + { + title: '回复数 Token 数', + field: 'maxTokens', + minWidth: 120, + }, + { + title: '上下文数量', + field: 'maxContexts', + minWidth: 120, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchemaMessage(): VbenFormSchema[] { + return [ + { + fieldName: 'conversationId', + label: '对话编号', + component: 'Input', + componentProps: { + placeholder: '请输入对话编号', + allowClear: true, + }, + }, + { + fieldName: 'userId', + label: '用户编号', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择用户编号', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumnsMessage(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '消息编号', + fixed: 'left', + minWidth: 180, + }, + { + field: 'conversationId', + title: '对话编号', + minWidth: 180, + fixed: 'left', + }, + { + title: '用户', + minWidth: 180, + field: 'userId', + formatter: ({ cellValue }) => + userList.find((user) => user.id === cellValue)?.nickname || '-', + }, + { + field: 'roleName', + title: '角色', + minWidth: 180, + }, + { + field: 'type', + title: '消息类型', + minWidth: 100, + }, + { + field: 'model', + title: '模型标识', + minWidth: 180, + }, + { + field: 'content', + title: '消息内容', + minWidth: 300, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'replyId', + title: '回复消息编号', + minWidth: 180, + }, + { + title: '携带上下文', + field: 'useContext', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + minWidth: 100, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-antd/src/views/ai/chat/manager/index.vue b/apps/web-antd/src/views/ai/chat/manager/index.vue new file mode 100644 index 0000000..69c4e84 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/manager/index.vue @@ -0,0 +1,29 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/manager/modules/conversation-list.vue b/apps/web-antd/src/views/ai/chat/manager/modules/conversation-list.vue new file mode 100644 index 0000000..6bfe702 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/manager/modules/conversation-list.vue @@ -0,0 +1,94 @@ + + + diff --git a/apps/web-antd/src/views/ai/chat/manager/modules/message-list.vue b/apps/web-antd/src/views/ai/chat/manager/modules/message-list.vue new file mode 100644 index 0000000..bf6bd57 --- /dev/null +++ b/apps/web-antd/src/views/ai/chat/manager/modules/message-list.vue @@ -0,0 +1,91 @@ + + + diff --git a/apps/web-antd/src/views/ai/image/index/index.vue b/apps/web-antd/src/views/ai/image/index/index.vue new file mode 100644 index 0000000..16fdb5b --- /dev/null +++ b/apps/web-antd/src/views/ai/image/index/index.vue @@ -0,0 +1,131 @@ + + + diff --git a/apps/web-antd/src/views/ai/image/index/modules/card.vue b/apps/web-antd/src/views/ai/image/index/modules/card.vue new file mode 100644 index 0000000..57b12e6 --- /dev/null +++ b/apps/web-antd/src/views/ai/image/index/modules/card.vue @@ -0,0 +1,133 @@ + + diff --git a/apps/web-antd/src/views/ai/image/index/modules/common/index.vue b/apps/web-antd/src/views/ai/image/index/modules/common/index.vue new file mode 100644 index 0000000..bd10987 --- /dev/null +++ b/apps/web-antd/src/views/ai/image/index/modules/common/index.vue @@ -0,0 +1,218 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/conversation/list.vue b/apps/web-ele/src/views/ai/chat/index/modules/conversation/list.vue new file mode 100644 index 0000000..0280554 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/conversation/list.vue @@ -0,0 +1,442 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/conversation/update-form.vue b/apps/web-ele/src/views/ai/chat/index/modules/conversation/update-form.vue new file mode 100644 index 0000000..6a7ae84 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/conversation/update-form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/message/file-upload.vue b/apps/web-ele/src/views/ai/chat/index/modules/message/file-upload.vue new file mode 100644 index 0000000..eff1b57 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/message/file-upload.vue @@ -0,0 +1,304 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/message/files.vue b/apps/web-ele/src/views/ai/chat/index/modules/message/files.vue new file mode 100644 index 0000000..ed163a0 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/message/files.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/message/knowledge.vue b/apps/web-ele/src/views/ai/chat/index/modules/message/knowledge.vue new file mode 100644 index 0000000..6feff41 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/message/knowledge.vue @@ -0,0 +1,103 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/message/list-empty.vue b/apps/web-ele/src/views/ai/chat/index/modules/message/list-empty.vue new file mode 100644 index 0000000..f97a054 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/message/list-empty.vue @@ -0,0 +1,37 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/message/list.vue b/apps/web-ele/src/views/ai/chat/index/modules/message/list.vue new file mode 100644 index 0000000..36630d1 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/message/list.vue @@ -0,0 +1,243 @@ + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/message/loading.vue b/apps/web-ele/src/views/ai/chat/index/modules/message/loading.vue new file mode 100644 index 0000000..a5e0765 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/message/loading.vue @@ -0,0 +1,9 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/message/new-conversation.vue b/apps/web-ele/src/views/ai/chat/index/modules/message/new-conversation.vue new file mode 100644 index 0000000..339af17 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/message/new-conversation.vue @@ -0,0 +1,25 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/message/reasoning.vue b/apps/web-ele/src/views/ai/chat/index/modules/message/reasoning.vue new file mode 100644 index 0000000..de3175d --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/message/reasoning.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/message/web-search.vue b/apps/web-ele/src/views/ai/chat/index/modules/message/web-search.vue new file mode 100644 index 0000000..d48a94c --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/message/web-search.vue @@ -0,0 +1,173 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/role/category-list.vue b/apps/web-ele/src/views/ai/chat/index/modules/role/category-list.vue new file mode 100644 index 0000000..96f32a1 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/role/category-list.vue @@ -0,0 +1,43 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/role/list.vue b/apps/web-ele/src/views/ai/chat/index/modules/role/list.vue new file mode 100644 index 0000000..a3f3e40 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/role/list.vue @@ -0,0 +1,123 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/index/modules/role/repository.vue b/apps/web-ele/src/views/ai/chat/index/modules/role/repository.vue new file mode 100644 index 0000000..20bc9e4 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/index/modules/role/repository.vue @@ -0,0 +1,263 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/manager/data.ts b/apps/web-ele/src/views/ai/chat/manager/data.ts new file mode 100644 index 0000000..266d44c --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/manager/data.ts @@ -0,0 +1,223 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemUserApi } from '#/api/system/user'; + +import { DICT_TYPE } from '@vben/constants'; + +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 关联数据 */ +let userList: SystemUserApi.User[] = []; +getSimpleUserList().then((data) => (userList = data)); + +/** 列表的搜索表单 */ +export function useGridFormSchemaConversation(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + placeholder: '请输入用户编号', + clearable: true, + }, + }, + { + fieldName: 'title', + label: '聊天标题', + component: 'Input', + componentProps: { + placeholder: '请输入聊天标题', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumnsConversation(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '对话编号', + fixed: 'left', + minWidth: 180, + }, + { + field: 'title', + title: '对话标题', + minWidth: 180, + fixed: 'left', + }, + { + title: '用户', + minWidth: 180, + field: 'userId', + formatter: ({ cellValue }) => { + if (cellValue === 0) { + return '系统'; + } + return userList.find((user) => user.id === cellValue)?.nickname || '-'; + }, + }, + { + field: 'roleName', + title: '角色', + minWidth: 180, + }, + { + field: 'model', + title: '模型标识', + minWidth: 180, + }, + { + field: 'messageCount', + title: '消息数', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'temperature', + title: '温度参数', + minWidth: 80, + }, + { + title: '回复数 Token 数', + field: 'maxTokens', + minWidth: 120, + }, + { + title: '上下文数量', + field: 'maxContexts', + minWidth: 120, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchemaMessage(): VbenFormSchema[] { + return [ + { + fieldName: 'conversationId', + label: '对话编号', + component: 'Input', + componentProps: { + placeholder: '请输入对话编号', + clearable: true, + }, + }, + { + fieldName: 'userId', + label: '用户编号', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择用户编号', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumnsMessage(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '消息编号', + fixed: 'left', + minWidth: 180, + }, + { + field: 'conversationId', + title: '对话编号', + minWidth: 180, + fixed: 'left', + }, + { + title: '用户', + minWidth: 180, + field: 'userId', + formatter: ({ cellValue }) => + userList.find((user) => user.id === cellValue)?.nickname || '-', + }, + { + field: 'roleName', + title: '角色', + minWidth: 180, + }, + { + field: 'type', + title: '消息类型', + minWidth: 100, + }, + { + field: 'model', + title: '模型标识', + minWidth: 180, + }, + { + field: 'content', + title: '消息内容', + minWidth: 300, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'replyId', + title: '回复消息编号', + minWidth: 180, + }, + { + title: '携带上下文', + field: 'useContext', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + minWidth: 100, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/chat/manager/index.vue b/apps/web-ele/src/views/ai/chat/manager/index.vue new file mode 100644 index 0000000..e87c3d3 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/manager/index.vue @@ -0,0 +1,29 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/manager/modules/conversation-list.vue b/apps/web-ele/src/views/ai/chat/manager/modules/conversation-list.vue new file mode 100644 index 0000000..b6ea3dc --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/manager/modules/conversation-list.vue @@ -0,0 +1,93 @@ + + + diff --git a/apps/web-ele/src/views/ai/chat/manager/modules/message-list.vue b/apps/web-ele/src/views/ai/chat/manager/modules/message-list.vue new file mode 100644 index 0000000..f568214 --- /dev/null +++ b/apps/web-ele/src/views/ai/chat/manager/modules/message-list.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/web-ele/src/views/ai/image/index/index.vue b/apps/web-ele/src/views/ai/image/index/index.vue new file mode 100644 index 0000000..1a2f842 --- /dev/null +++ b/apps/web-ele/src/views/ai/image/index/index.vue @@ -0,0 +1,128 @@ + + + diff --git a/apps/web-ele/src/views/ai/image/index/modules/card.vue b/apps/web-ele/src/views/ai/image/index/modules/card.vue new file mode 100644 index 0000000..91018c7 --- /dev/null +++ b/apps/web-ele/src/views/ai/image/index/modules/card.vue @@ -0,0 +1,136 @@ + + diff --git a/apps/web-ele/src/views/ai/image/index/modules/common/index.vue b/apps/web-ele/src/views/ai/image/index/modules/common/index.vue new file mode 100644 index 0000000..c0197e4 --- /dev/null +++ b/apps/web-ele/src/views/ai/image/index/modules/common/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/ai/image/index/modules/dall3/index.vue b/apps/web-ele/src/views/ai/image/index/modules/dall3/index.vue new file mode 100644 index 0000000..299b93f --- /dev/null +++ b/apps/web-ele/src/views/ai/image/index/modules/dall3/index.vue @@ -0,0 +1,260 @@ + + + diff --git a/apps/web-ele/src/views/ai/image/index/modules/detail.vue b/apps/web-ele/src/views/ai/image/index/modules/detail.vue new file mode 100644 index 0000000..7959a44 --- /dev/null +++ b/apps/web-ele/src/views/ai/image/index/modules/detail.vue @@ -0,0 +1,206 @@ + + + diff --git a/apps/web-ele/src/views/ai/image/index/modules/list.vue b/apps/web-ele/src/views/ai/image/index/modules/list.vue new file mode 100644 index 0000000..9036549 --- /dev/null +++ b/apps/web-ele/src/views/ai/image/index/modules/list.vue @@ -0,0 +1,222 @@ + + + diff --git a/apps/web-ele/src/views/ai/image/index/modules/midjourney/index.vue b/apps/web-ele/src/views/ai/image/index/modules/midjourney/index.vue new file mode 100644 index 0000000..5c847ba --- /dev/null +++ b/apps/web-ele/src/views/ai/image/index/modules/midjourney/index.vue @@ -0,0 +1,257 @@ + + + diff --git a/apps/web-ele/src/views/ai/image/index/modules/stable-diffusion/index.vue b/apps/web-ele/src/views/ai/image/index/modules/stable-diffusion/index.vue new file mode 100644 index 0000000..5d7914f --- /dev/null +++ b/apps/web-ele/src/views/ai/image/index/modules/stable-diffusion/index.vue @@ -0,0 +1,309 @@ + + + diff --git a/apps/web-ele/src/views/ai/image/manager/data.ts b/apps/web-ele/src/views/ai/image/manager/data.ts new file mode 100644 index 0000000..68cc3c4 --- /dev/null +++ b/apps/web-ele/src/views/ai/image/manager/data.ts @@ -0,0 +1,180 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemUserApi } from '#/api/system/user'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +let userList: SystemUserApi.User[] = []; +async function getUserData() { + userList = await getSimpleUserList(); +} + +getUserData(); + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择用户编号', + clearable: true, + }, + }, + { + fieldName: 'platform', + label: '平台', + component: 'Select', + componentProps: { + placeholder: '请选择平台', + clearable: true, + options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'), + }, + }, + { + fieldName: 'status', + label: '绘画状态', + component: 'Select', + componentProps: { + placeholder: '请选择绘画状态', + clearable: true, + options: getDictOptions(DICT_TYPE.AI_IMAGE_STATUS, 'number'), + }, + }, + { + fieldName: 'publicStatus', + label: '是否发布', + component: 'Select', + componentProps: { + placeholder: '请选择是否发布', + clearable: true, + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onPublicStatusChange?: ( + newStatus: boolean, + row: any, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 180, + fixed: 'left', + }, + { + field: 'picUrl', + title: '图片', + minWidth: 110, + fixed: 'left', + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'userId', + title: '用户', + minWidth: 180, + formatter: ({ cellValue }) => + userList.find((user) => user.id === cellValue)?.nickname || '-', + }, + { + field: 'platform', + title: '平台', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_PLATFORM }, + }, + }, + { + field: 'model', + title: '模型', + minWidth: 180, + }, + { + field: 'status', + title: '绘画状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_IMAGE_STATUS }, + }, + }, + { + minWidth: 100, + title: '是否发布', + field: 'publicStatus', + align: 'center', + cellRender: { + attrs: { beforeChange: onPublicStatusChange }, + name: 'CellSwitch', + props: { + activeValue: true, + inactiveValue: false, + }, + }, + }, + { + field: 'prompt', + title: '提示词', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'width', + title: '宽度', + minWidth: 180, + }, + { + field: 'height', + title: '高度', + minWidth: 180, + }, + { + field: 'errorMessage', + title: '错误信息', + minWidth: 180, + }, + { + field: 'taskId', + title: '任务编号', + minWidth: 180, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/image/manager/index.vue b/apps/web-ele/src/views/ai/image/manager/index.vue new file mode 100644 index 0000000..72830e7 --- /dev/null +++ b/apps/web-ele/src/views/ai/image/manager/index.vue @@ -0,0 +1,116 @@ + + + diff --git a/apps/web-ele/src/views/ai/image/square/index.vue b/apps/web-ele/src/views/ai/image/square/index.vue new file mode 100644 index 0000000..636aade --- /dev/null +++ b/apps/web-ele/src/views/ai/image/square/index.vue @@ -0,0 +1,90 @@ + + diff --git a/apps/web-ele/src/views/ai/knowledge/document/data.ts b/apps/web-ele/src/views/ai/knowledge/document/data.ts new file mode 100644 index 0000000..668fb09 --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/document/data.ts @@ -0,0 +1,180 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { AiKnowledgeDocumentApi } from '#/api/ai/knowledge/document'; + +import { AiModelTypeEnum, CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getModelSimpleList } from '#/api/ai/model/model'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '知识库名称', + rules: 'required', + }, + { + fieldName: 'description', + label: '知识库描述', + component: 'Textarea', + componentProps: { + rows: 3, + placeholder: '请输入知识库描述', + }, + }, + { + component: 'ApiSelect', + fieldName: 'embeddingModelId', + label: '向量模型', + componentProps: { + api: () => getModelSimpleList(AiModelTypeEnum.EMBEDDING), + labelField: 'name', + valueField: 'id', + allowClear: true, + placeholder: '请选择向量模型', + }, + rules: 'required', + }, + { + fieldName: 'topK', + label: '检索 topK', + component: 'InputNumber', + componentProps: { + placeholder: '请输入检索 topK', + min: 0, + max: 10, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'similarityThreshold', + label: '检索相似度阈值', + component: 'InputNumber', + componentProps: { + placeholder: '请输入检索相似度阈值', + min: 0, + max: 1, + step: 0.01, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '是否启用', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '文件名称', + component: 'Input', + componentProps: { + placeholder: '请输入文件名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '是否启用', + component: 'Select', + componentProps: { + placeholder: '请选择是否启用', + allowClear: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onStatusChange?: ( + newStatus: number, + row: AiKnowledgeDocumentApi.KnowledgeDocument, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '文档编号', + minWidth: 100, + }, + { + field: 'name', + title: '文件名称', + minWidth: 200, + }, + { + field: 'contentLength', + title: '字符数', + minWidth: 100, + }, + { + field: 'tokens', + title: 'Token 数', + minWidth: 100, + }, + { + field: 'segmentMaxTokens', + title: '分片最大 Token 数', + minWidth: 150, + }, + { + field: 'retrievalCount', + title: '召回次数', + minWidth: 100, + }, + { + field: 'status', + title: '是否启用', + minWidth: 100, + align: 'center', + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: 'CellSwitch', + props: { + activeValue: CommonStatusEnum.ENABLE, + inactiveValue: CommonStatusEnum.DISABLE, + }, + }, + }, + { + field: 'createTime', + title: '上传时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + minWidth: 150, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/knowledge/document/form/index.vue b/apps/web-ele/src/views/ai/knowledge/document/form/index.vue new file mode 100644 index 0000000..1ef2429 --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/document/form/index.vue @@ -0,0 +1,200 @@ + + + diff --git a/apps/web-ele/src/views/ai/knowledge/document/form/modules/process-step.vue b/apps/web-ele/src/views/ai/knowledge/document/form/modules/process-step.vue new file mode 100644 index 0000000..e790c73 --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/document/form/modules/process-step.vue @@ -0,0 +1,158 @@ + + + diff --git a/apps/web-ele/src/views/ai/knowledge/document/form/modules/split-step.vue b/apps/web-ele/src/views/ai/knowledge/document/form/modules/split-step.vue new file mode 100644 index 0000000..e2c9781 --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/document/form/modules/split-step.vue @@ -0,0 +1,286 @@ + + + diff --git a/apps/web-ele/src/views/ai/knowledge/document/form/modules/upload-step.vue b/apps/web-ele/src/views/ai/knowledge/document/form/modules/upload-step.vue new file mode 100644 index 0000000..456aafd --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/document/form/modules/upload-step.vue @@ -0,0 +1,270 @@ + + + diff --git a/apps/web-ele/src/views/ai/knowledge/document/index.vue b/apps/web-ele/src/views/ai/knowledge/document/index.vue new file mode 100644 index 0000000..df0fc71 --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/document/index.vue @@ -0,0 +1,191 @@ + + + diff --git a/apps/web-ele/src/views/ai/knowledge/knowledge/data.ts b/apps/web-ele/src/views/ai/knowledge/knowledge/data.ts new file mode 100644 index 0000000..1e69f2c --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/knowledge/data.ts @@ -0,0 +1,172 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { AiModelTypeEnum, CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getModelSimpleList } from '#/api/ai/model/model'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '知识库名称', + componentProps: { + placeholder: '请输入知识库名称', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '知识库描述', + component: 'Textarea', + componentProps: { + rows: 3, + placeholder: '请输入知识库描述', + }, + }, + { + component: 'ApiSelect', + fieldName: 'embeddingModelId', + label: '向量模型', + componentProps: { + api: () => getModelSimpleList(AiModelTypeEnum.EMBEDDING), + labelField: 'name', + valueField: 'id', + allowClear: true, + placeholder: '请选择向量模型', + }, + rules: 'required', + }, + { + fieldName: 'topK', + label: '检索 topK', + component: 'InputNumber', + componentProps: { + placeholder: '请输入检索 topK', + min: 0, + max: 10, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'similarityThreshold', + label: '检索相似度阈值', + component: 'InputNumber', + componentProps: { + placeholder: '请输入检索相似度阈值', + min: 0, + max: 1, + step: 0.01, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '是否启用', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '知识库名称', + component: 'Input', + componentProps: { + placeholder: '请输入知识库名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '是否启用', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择是否启用', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '知识库名称', + minWidth: 150, + }, + { + field: 'description', + title: '知识库描述', + minWidth: 200, + }, + { + field: 'embeddingModel', + title: '向量化模型', + minWidth: 150, + }, + { + field: 'status', + title: '是否启用', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 280, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/knowledge/knowledge/index.vue b/apps/web-ele/src/views/ai/knowledge/knowledge/index.vue new file mode 100644 index 0000000..55cfbcc --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/knowledge/index.vue @@ -0,0 +1,166 @@ + + + diff --git a/apps/web-ele/src/views/ai/knowledge/knowledge/modules/form.vue b/apps/web-ele/src/views/ai/knowledge/knowledge/modules/form.vue new file mode 100644 index 0000000..5daf714 --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/knowledge/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/ai/knowledge/knowledge/retrieval/index.vue b/apps/web-ele/src/views/ai/knowledge/knowledge/retrieval/index.vue new file mode 100644 index 0000000..1af68df --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/knowledge/retrieval/index.vue @@ -0,0 +1,214 @@ + + diff --git a/apps/web-ele/src/views/ai/knowledge/segment/data.ts b/apps/web-ele/src/views/ai/knowledge/segment/data.ts new file mode 100644 index 0000000..33cb64b --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/segment/data.ts @@ -0,0 +1,130 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { AiKnowledgeSegmentApi } from '#/api/ai/knowledge/segment'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'documentId', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'content', + label: '切片内容', + component: 'Textarea', + componentProps: { + placeholder: '请输入切片内容', + rows: 6, + showCount: true, + }, + rules: 'required', + }, + ]; +} +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'documentId', + label: '文档编号', + component: 'Input', + componentProps: { + placeholder: '请输入文档编号', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '是否启用', + component: 'Select', + componentProps: { + placeholder: '请选择是否启用', + allowClear: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onStatusChange?: ( + newStatus: number, + row: AiKnowledgeSegmentApi.KnowledgeSegment, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '分段编号', + minWidth: 100, + }, + { + type: 'expand', + width: 40, + slots: { content: 'expand_content' }, + }, + { + field: 'content', + title: '切片内容', + minWidth: 250, + }, + { + field: 'contentLength', + title: '字符数', + minWidth: 100, + }, + { + field: 'tokens', + title: 'token 数量', + minWidth: 120, + }, + { + field: 'retrievalCount', + title: '召回次数', + minWidth: 100, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + align: 'center', + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: 'CellSwitch', + props: { + activeValue: CommonStatusEnum.ENABLE, + inactiveValue: CommonStatusEnum.DISABLE, + }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 150, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/knowledge/segment/index.vue b/apps/web-ele/src/views/ai/knowledge/segment/index.vue new file mode 100644 index 0000000..980a904 --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/segment/index.vue @@ -0,0 +1,171 @@ + + + diff --git a/apps/web-ele/src/views/ai/knowledge/segment/modules/form.vue b/apps/web-ele/src/views/ai/knowledge/segment/modules/form.vue new file mode 100644 index 0000000..1b4b135 --- /dev/null +++ b/apps/web-ele/src/views/ai/knowledge/segment/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/ai/mindmap/index/index.vue b/apps/web-ele/src/views/ai/mindmap/index/index.vue new file mode 100644 index 0000000..0bdb46e --- /dev/null +++ b/apps/web-ele/src/views/ai/mindmap/index/index.vue @@ -0,0 +1,99 @@ + + + diff --git a/apps/web-ele/src/views/ai/mindmap/index/modules/left.vue b/apps/web-ele/src/views/ai/mindmap/index/modules/left.vue new file mode 100644 index 0000000..f2bd48c --- /dev/null +++ b/apps/web-ele/src/views/ai/mindmap/index/modules/left.vue @@ -0,0 +1,75 @@ + + diff --git a/apps/web-ele/src/views/ai/mindmap/index/modules/right.vue b/apps/web-ele/src/views/ai/mindmap/index/modules/right.vue new file mode 100644 index 0000000..5adc66a --- /dev/null +++ b/apps/web-ele/src/views/ai/mindmap/index/modules/right.vue @@ -0,0 +1,215 @@ + + + + + diff --git a/apps/web-ele/src/views/ai/mindmap/manager/data.ts b/apps/web-ele/src/views/ai/mindmap/manager/data.ts new file mode 100644 index 0000000..967981d --- /dev/null +++ b/apps/web-ele/src/views/ai/mindmap/manager/data.ts @@ -0,0 +1,97 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemUserApi } from '#/api/system/user'; + +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 关联数据 */ +let userList: SystemUserApi.User[] = []; +getSimpleUserList().then((data) => (userList = data)); + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择用户', + allowClear: true, + }, + }, + { + fieldName: 'prompt', + label: '提示词', + component: 'Input', + componentProps: { + placeholder: '请输入提示词', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 180, + fixed: 'left', + }, + { + field: 'userId', + title: '用户', + minWidth: 180, + formatter: ({ cellValue }) => + userList.find((user) => user.id === cellValue)?.nickname || '-', + }, + { + field: 'prompt', + title: '提示词', + minWidth: 180, + }, + { + field: 'generatedContent', + title: '思维导图', + minWidth: 300, + }, + { + field: 'model', + title: '模型', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'errorMessage', + title: '错误信息', + minWidth: 180, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/mindmap/manager/index.vue b/apps/web-ele/src/views/ai/mindmap/manager/index.vue new file mode 100644 index 0000000..9684dfc --- /dev/null +++ b/apps/web-ele/src/views/ai/mindmap/manager/index.vue @@ -0,0 +1,120 @@ + + + diff --git a/apps/web-ele/src/views/ai/model/apiKey/data.ts b/apps/web-ele/src/views/ai/model/apiKey/data.ts new file mode 100644 index 0000000..1c307ed --- /dev/null +++ b/apps/web-ele/src/views/ai/model/apiKey/data.ts @@ -0,0 +1,147 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'platform', + label: '所属平台', + component: 'Select', + componentProps: { + placeholder: '请选择所属平台', + options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'), + allowClear: true, + }, + rules: 'required', + }, + { + component: 'Input', + fieldName: 'name', + label: '名称', + rules: 'required', + componentProps: { + placeholder: '请输入名称', + }, + }, + { + component: 'Input', + fieldName: 'apiKey', + label: '密钥', + rules: 'required', + componentProps: { + placeholder: '请输入密钥', + }, + }, + { + component: 'Input', + fieldName: 'url', + label: '自定义 API URL', + componentProps: { + placeholder: '请输入自定义 API URL', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名称', + component: 'Input', + componentProps: { + placeholder: '请输入名称', + allowClear: true, + }, + }, + { + fieldName: 'platform', + label: '平台', + component: 'Select', + componentProps: { + allowClear: true, + placeholder: '请选择平台', + options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'), + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + allowClear: true, + placeholder: '请选择状态', + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'platform', + title: '所属平台', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_PLATFORM }, + }, + minWidth: 100, + }, + { + field: 'name', + title: '名称', + minWidth: 120, + }, + { + field: 'apiKey', + title: '密钥', + minWidth: 140, + }, + { + field: 'url', + title: '自定义 API URL', + minWidth: 180, + }, + { + field: 'status', + title: '状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + minWidth: 80, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/model/apiKey/index.vue b/apps/web-ele/src/views/ai/model/apiKey/index.vue new file mode 100644 index 0000000..a6b5181 --- /dev/null +++ b/apps/web-ele/src/views/ai/model/apiKey/index.vue @@ -0,0 +1,128 @@ + + + diff --git a/apps/web-ele/src/views/ai/model/apiKey/modules/form.vue b/apps/web-ele/src/views/ai/model/apiKey/modules/form.vue new file mode 100644 index 0000000..c7e5388 --- /dev/null +++ b/apps/web-ele/src/views/ai/model/apiKey/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/ai/model/chatRole/data.ts b/apps/web-ele/src/views/ai/model/chatRole/data.ts new file mode 100644 index 0000000..ab70227 --- /dev/null +++ b/apps/web-ele/src/views/ai/model/chatRole/data.ts @@ -0,0 +1,318 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { AiModelTypeEnum, CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleKnowledgeList } from '#/api/ai/knowledge/knowledge'; +import { getModelSimpleList } from '#/api/ai/model/model'; +import { getToolSimpleList } from '#/api/ai/model/tool'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'formType', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '角色名称', + rules: 'required', + componentProps: { + placeholder: '请输入角色名称', + }, + }, + { + component: 'ImageUpload', + fieldName: 'avatar', + label: '角色头像', + rules: 'required', + }, + { + fieldName: 'modelId', + label: '绑定模型', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择绑定模型', + api: () => getModelSimpleList(AiModelTypeEnum.CHAT), + labelField: 'name', + valueField: 'id', + allowClear: true, + }, + dependencies: { + triggerFields: ['formType'], + show: (values) => { + return values.formType === 'create' || values.formType === 'update'; + }, + }, + }, + { + component: 'Input', + fieldName: 'category', + label: '角色类别', + rules: 'required', + componentProps: { + placeholder: '请输入角色类别', + }, + dependencies: { + triggerFields: ['formType'], + show: (values) => { + return values.formType === 'create' || values.formType === 'update'; + }, + }, + }, + { + component: 'Textarea', + fieldName: 'description', + label: '角色描述', + componentProps: { + placeholder: '请输入角色描述', + }, + rules: 'required', + }, + { + fieldName: 'systemMessage', + label: '角色设定', + component: 'Textarea', + componentProps: { + placeholder: '请输入角色设定', + }, + rules: 'required', + }, + { + fieldName: 'knowledgeIds', + label: '引用知识库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择引用知识库', + api: getSimpleKnowledgeList, + labelField: 'name', + mode: 'multiple', + valueField: 'id', + allowClear: true, + }, + }, + { + fieldName: 'toolIds', + label: '引用工具', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择引用工具', + api: getToolSimpleList, + mode: 'multiple', + labelField: 'name', + valueField: 'id', + allowClear: true, + }, + }, + { + fieldName: 'mcpClientNames', + label: '引用 MCP', + component: 'Select', + componentProps: { + placeholder: '请选择 MCP', + options: getDictOptions(DICT_TYPE.AI_MCP_CLIENT_NAME, 'string'), + mode: 'multiple', + allowClear: true, + }, + }, + { + fieldName: 'publicStatus', + label: '是否公开', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + }, + defaultValue: true, + dependencies: { + triggerFields: ['formType'], + show: (values) => { + return values.formType === 'create' || values.formType === 'update'; + }, + }, + rules: 'required', + }, + { + fieldName: 'sort', + label: '角色排序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入角色排序', + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['formType'], + show: (values) => { + return values.formType === 'create' || values.formType === 'update'; + }, + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + dependencies: { + triggerFields: ['formType'], + show: (values) => { + return values.formType === 'create' || values.formType === 'update'; + }, + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '角色名称', + component: 'Input', + componentProps: { + placeholder: '请输入角色名称', + }, + }, + { + fieldName: 'category', + label: '角色类别', + component: 'Input', + componentProps: { + placeholder: '请输入角色类别', + }, + }, + { + fieldName: 'publicStatus', + label: '是否公开', + component: 'Select', + componentProps: { + placeholder: '请选择是否公开', + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + allowClear: true, + }, + defaultValue: true, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '角色名称', + minWidth: 100, + }, + { + title: '绑定模型', + field: 'modelName', + minWidth: 100, + }, + { + title: '角色头像', + field: 'avatar', + minWidth: 140, + cellRender: { + name: 'CellImage', + props: { + width: 40, + height: 40, + }, + }, + }, + { + title: '角色类别', + field: 'category', + minWidth: 100, + }, + { + title: '角色描述', + field: 'description', + minWidth: 100, + }, + { + title: '角色设定', + field: 'systemMessage', + minWidth: 100, + }, + { + title: '知识库', + field: 'knowledgeIds', + minWidth: 100, + formatter: ({ cellValue }) => { + return !cellValue || cellValue.length === 0 + ? '-' + : `引用${cellValue.length}个`; + }, + }, + { + title: '工具', + field: 'toolIds', + minWidth: 100, + formatter: ({ cellValue }) => { + return !cellValue || cellValue.length === 0 + ? '-' + : `引用${cellValue.length}个`; + }, + }, + { + title: 'MCP', + field: 'mcpClientNames', + minWidth: 100, + formatter: ({ cellValue }) => { + return !cellValue || cellValue.length === 0 + ? '-' + : `引用${cellValue.length}个`; + }, + }, + { + field: 'publicStatus', + title: '是否公开', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + minWidth: 80, + }, + { + field: 'status', + title: '状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + minWidth: 80, + }, + { + title: '角色排序', + field: 'sort', + minWidth: 80, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/model/chatRole/index.vue b/apps/web-ele/src/views/ai/model/chatRole/index.vue new file mode 100644 index 0000000..16e7840 --- /dev/null +++ b/apps/web-ele/src/views/ai/model/chatRole/index.vue @@ -0,0 +1,128 @@ + + + diff --git a/apps/web-ele/src/views/ai/model/chatRole/modules/form.vue b/apps/web-ele/src/views/ai/model/chatRole/modules/form.vue new file mode 100644 index 0000000..35c44d2 --- /dev/null +++ b/apps/web-ele/src/views/ai/model/chatRole/modules/form.vue @@ -0,0 +1,87 @@ + + + diff --git a/apps/web-ele/src/views/ai/model/model/data.ts b/apps/web-ele/src/views/ai/model/model/data.ts new file mode 100644 index 0000000..a29e31a --- /dev/null +++ b/apps/web-ele/src/views/ai/model/model/data.ts @@ -0,0 +1,272 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { AiModelApiKeyApi } from '#/api/ai/model/apiKey'; + +import { AiModelTypeEnum, CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getApiKeySimpleList } from '#/api/ai/model/apiKey'; + +/** 关联数据 */ +let apiKeyList: AiModelApiKeyApi.ApiKey[] = []; +getApiKeySimpleList().then((data) => (apiKeyList = data)); + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'platform', + label: '所属平台', + component: 'Select', + componentProps: { + placeholder: '请选择所属平台', + options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'), + allowClear: true, + }, + rules: 'required', + }, + { + fieldName: 'type', + label: '模型类型', + component: 'Select', + componentProps: (values) => { + return { + placeholder: '请输入模型类型', + disabled: !!values.id, + options: getDictOptions(DICT_TYPE.AI_MODEL_TYPE, 'number'), + allowClear: true, + }; + }, + rules: 'required', + }, + { + fieldName: 'keyId', + label: 'API 秘钥', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择 API 秘钥', + api: getApiKeySimpleList, + labelField: 'name', + valueField: 'id', + allowClear: true, + }, + rules: 'required', + }, + { + component: 'Input', + fieldName: 'name', + label: '模型名字', + rules: 'required', + componentProps: { + placeholder: '请输入模型名字', + }, + }, + { + component: 'Input', + fieldName: 'model', + label: '模型标识', + rules: 'required', + componentProps: { + placeholder: '请输入模型标识', + }, + }, + { + fieldName: 'sort', + label: '模型排序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入模型排序', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'temperature', + label: '温度参数', + component: 'InputNumber', + componentProps: { + placeholder: '请输入温度参数', + min: 0, + max: 2, + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [AiModelTypeEnum.CHAT].includes(values.type); + }, + }, + rules: 'required', + }, + { + fieldName: 'maxTokens', + label: '回复数 Token 数', + component: 'InputNumber', + componentProps: { + min: 0, + max: 8192, + placeholder: '请输入回复数 Token 数', + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [AiModelTypeEnum.CHAT].includes(values.type); + }, + }, + rules: 'required', + }, + { + fieldName: 'maxContexts', + label: '上下文数量', + component: 'InputNumber', + componentProps: { + min: 0, + max: 20, + placeholder: '请输入上下文数量', + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [AiModelTypeEnum.CHAT].includes(values.type); + }, + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '模型名字', + component: 'Input', + componentProps: { + placeholder: '请输入模型名字', + allowClear: true, + }, + }, + { + fieldName: 'model', + label: '模型标识', + component: 'Input', + componentProps: { + placeholder: '请输入模型标识', + allowClear: true, + }, + }, + { + fieldName: 'platform', + label: '模型平台', + component: 'Input', + componentProps: { + placeholder: '请输入模型平台', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'platform', + title: '所属平台', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_PLATFORM }, + }, + minWidth: 100, + }, + { + field: 'type', + title: '模型类型', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_MODEL_TYPE }, + }, + minWidth: 100, + }, + { + field: 'name', + title: '模型名字', + minWidth: 180, + }, + { + title: '模型标识', + field: 'model', + minWidth: 180, + }, + { + title: 'API 秘钥', + field: 'keyId', + formatter: ({ cellValue }) => { + return ( + apiKeyList.find((apiKey) => apiKey.id === cellValue)?.name || '-' + ); + }, + minWidth: 140, + }, + { + title: '排序', + field: 'sort', + minWidth: 80, + }, + { + field: 'status', + title: '状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + minWidth: 80, + }, + { + field: 'temperature', + title: '温度参数', + minWidth: 100, + }, + { + title: '回复数 Token 数', + field: 'maxTokens', + minWidth: 140, + }, + { + title: '上下文数量', + field: 'maxContexts', + minWidth: 120, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/model/model/index.vue b/apps/web-ele/src/views/ai/model/model/index.vue new file mode 100644 index 0000000..0448809 --- /dev/null +++ b/apps/web-ele/src/views/ai/model/model/index.vue @@ -0,0 +1,128 @@ + + + diff --git a/apps/web-ele/src/views/ai/model/model/modules/form.vue b/apps/web-ele/src/views/ai/model/model/modules/form.vue new file mode 100644 index 0000000..10727a6 --- /dev/null +++ b/apps/web-ele/src/views/ai/model/model/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/ai/model/tool/data.ts b/apps/web-ele/src/views/ai/model/tool/data.ts new file mode 100644 index 0000000..d8897ed --- /dev/null +++ b/apps/web-ele/src/views/ai/model/tool/data.ts @@ -0,0 +1,123 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + + { + component: 'Input', + fieldName: 'name', + label: '工具名称', + rules: 'required', + componentProps: { + placeholder: '请输入工具名称', + }, + }, + { + component: 'Textarea', + fieldName: 'description', + label: '工具描述', + componentProps: { + placeholder: '请输入工具描述', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + defaultValue: CommonStatusEnum.ENABLE, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '工具名称', + component: 'Input', + componentProps: { + placeholder: '请输入工具名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '工具编号', + minWidth: 100, + }, + { + field: 'name', + title: '工具名称', + minWidth: 120, + }, + { + field: 'description', + title: '工具描述', + minWidth: 140, + }, + { + field: 'status', + title: '状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + minWidth: 80, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/model/tool/index.vue b/apps/web-ele/src/views/ai/model/tool/index.vue new file mode 100644 index 0000000..18c2dd3 --- /dev/null +++ b/apps/web-ele/src/views/ai/model/tool/index.vue @@ -0,0 +1,131 @@ + + + diff --git a/apps/web-ele/src/views/ai/model/tool/modules/form.vue b/apps/web-ele/src/views/ai/model/tool/modules/form.vue new file mode 100644 index 0000000..afe7c21 --- /dev/null +++ b/apps/web-ele/src/views/ai/model/tool/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/ai/music/index/index.vue b/apps/web-ele/src/views/ai/music/index/index.vue new file mode 100644 index 0000000..af00e18 --- /dev/null +++ b/apps/web-ele/src/views/ai/music/index/index.vue @@ -0,0 +1,29 @@ + + + diff --git a/apps/web-ele/src/views/ai/music/index/list/audioBar/index.vue b/apps/web-ele/src/views/ai/music/index/list/audioBar/index.vue new file mode 100644 index 0000000..961620a --- /dev/null +++ b/apps/web-ele/src/views/ai/music/index/list/audioBar/index.vue @@ -0,0 +1,99 @@ + + + diff --git a/apps/web-ele/src/views/ai/music/index/list/index.vue b/apps/web-ele/src/views/ai/music/index/list/index.vue new file mode 100644 index 0000000..1e0dde3 --- /dev/null +++ b/apps/web-ele/src/views/ai/music/index/list/index.vue @@ -0,0 +1,100 @@ + + + + diff --git a/apps/web-ele/src/views/ai/music/index/list/songCard/index.vue b/apps/web-ele/src/views/ai/music/index/list/songCard/index.vue new file mode 100644 index 0000000..2d7bdb6 --- /dev/null +++ b/apps/web-ele/src/views/ai/music/index/list/songCard/index.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-ele/src/views/ai/music/index/list/songInfo/index.vue b/apps/web-ele/src/views/ai/music/index/list/songInfo/index.vue new file mode 100644 index 0000000..eb4fbc8 --- /dev/null +++ b/apps/web-ele/src/views/ai/music/index/list/songInfo/index.vue @@ -0,0 +1,25 @@ + + + diff --git a/apps/web-ele/src/views/ai/music/index/mode/desc.vue b/apps/web-ele/src/views/ai/music/index/mode/desc.vue new file mode 100644 index 0000000..bcf9a01 --- /dev/null +++ b/apps/web-ele/src/views/ai/music/index/mode/desc.vue @@ -0,0 +1,66 @@ + + + diff --git a/apps/web-ele/src/views/ai/music/index/mode/index.vue b/apps/web-ele/src/views/ai/music/index/mode/index.vue new file mode 100644 index 0000000..813ea73 --- /dev/null +++ b/apps/web-ele/src/views/ai/music/index/mode/index.vue @@ -0,0 +1,38 @@ + + + diff --git a/apps/web-ele/src/views/ai/music/index/mode/lyric.vue b/apps/web-ele/src/views/ai/music/index/mode/lyric.vue new file mode 100644 index 0000000..c64f915 --- /dev/null +++ b/apps/web-ele/src/views/ai/music/index/mode/lyric.vue @@ -0,0 +1,107 @@ + + + diff --git a/apps/web-ele/src/views/ai/music/index/title/index.vue b/apps/web-ele/src/views/ai/music/index/title/index.vue new file mode 100644 index 0000000..04dc58b --- /dev/null +++ b/apps/web-ele/src/views/ai/music/index/title/index.vue @@ -0,0 +1,27 @@ + + + diff --git a/apps/web-ele/src/views/ai/music/manager/data.ts b/apps/web-ele/src/views/ai/music/manager/data.ts new file mode 100644 index 0000000..7b5c63a --- /dev/null +++ b/apps/web-ele/src/views/ai/music/manager/data.ts @@ -0,0 +1,202 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemUserApi } from '#/api/system/user'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 关联数据 */ +let userList: SystemUserApi.User[] = []; +getSimpleUserList().then((data) => (userList = data)); + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择用户编号', + clearable: true, + }, + }, + { + fieldName: 'title', + label: '音乐名称', + component: 'Input', + componentProps: { + placeholder: '请输入音乐名称', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '绘画状态', + component: 'Select', + componentProps: { + placeholder: '请选择绘画状态', + clearable: true, + options: getDictOptions(DICT_TYPE.AI_MUSIC_STATUS, 'number'), + }, + }, + { + fieldName: 'generateMode', + label: '生成模式', + component: 'Select', + componentProps: { + placeholder: '请选择生成模式', + clearable: true, + options: getDictOptions(DICT_TYPE.AI_GENERATE_MODE, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'publicStatus', + label: '是否发布', + component: 'Select', + componentProps: { + placeholder: '请选择是否发布', + clearable: true, + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onPublicStatusChange?: ( + newStatus: boolean, + row: any, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 180, + fixed: 'left', + }, + { + title: '音乐名称', + minWidth: 180, + fixed: 'left', + field: 'title', + }, + { + minWidth: 180, + title: '用户', + field: 'userId', + formatter: ({ cellValue }) => { + return userList.find((user) => user.id === cellValue)?.nickname || '-'; + }, + }, + { + field: 'status', + title: '音乐状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_MUSIC_STATUS }, + }, + }, + { + field: 'model', + title: '模型', + minWidth: 180, + }, + { + title: '内容', + minWidth: 180, + slots: { default: 'content' }, + }, + { + field: 'duration', + title: '时长(秒)', + minWidth: 100, + }, + { + field: 'prompt', + title: '提示词', + minWidth: 180, + }, + { + field: 'lyric', + title: '歌词', + minWidth: 180, + }, + { + field: 'gptDescriptionPrompt', + title: '描述', + minWidth: 180, + }, + { + field: 'generateMode', + title: '生成模式', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_GENERATE_MODE }, + }, + }, + { + field: 'tags', + title: '风格标签', + minWidth: 180, + cellRender: { + name: 'CellTags', + }, + }, + { + minWidth: 100, + title: '是否发布', + field: 'publicStatus', + align: 'center', + cellRender: { + attrs: { beforeChange: onPublicStatusChange }, + name: 'CellSwitch', + props: { + activeValue: true, + inactiveValue: false, + }, + }, + }, + { + field: 'taskId', + title: '任务编号', + minWidth: 180, + }, + { + field: 'errorMessage', + title: '错误信息', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/music/manager/index.vue b/apps/web-ele/src/views/ai/music/manager/index.vue new file mode 100644 index 0000000..d0e04cb --- /dev/null +++ b/apps/web-ele/src/views/ai/music/manager/index.vue @@ -0,0 +1,152 @@ + + + diff --git a/apps/web-ele/src/views/ai/workflow/data.ts b/apps/web-ele/src/views/ai/workflow/data.ts new file mode 100644 index 0000000..5decf9f --- /dev/null +++ b/apps/web-ele/src/views/ai/workflow/data.ts @@ -0,0 +1,97 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'code', + label: '流程标识', + component: 'Input', + componentProps: { + placeholder: '请输入流程标识', + allowClear: true, + }, + }, + { + fieldName: 'name', + label: '流程名称', + component: 'Input', + componentProps: { + placeholder: '请输入流程名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'code', + title: '流程标识', + minWidth: 150, + }, + { + field: 'name', + title: '流程名称', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/workflow/form/index.vue b/apps/web-ele/src/views/ai/workflow/form/index.vue new file mode 100644 index 0000000..ca206ff --- /dev/null +++ b/apps/web-ele/src/views/ai/workflow/form/index.vue @@ -0,0 +1,288 @@ + + + diff --git a/apps/web-ele/src/views/ai/workflow/form/modules/basic-info.vue b/apps/web-ele/src/views/ai/workflow/form/modules/basic-info.vue new file mode 100644 index 0000000..09f8725 --- /dev/null +++ b/apps/web-ele/src/views/ai/workflow/form/modules/basic-info.vue @@ -0,0 +1,70 @@ + + + diff --git a/apps/web-ele/src/views/ai/workflow/form/modules/workflow-design.vue b/apps/web-ele/src/views/ai/workflow/form/modules/workflow-design.vue new file mode 100644 index 0000000..3acb5ad --- /dev/null +++ b/apps/web-ele/src/views/ai/workflow/form/modules/workflow-design.vue @@ -0,0 +1,286 @@ + + + diff --git a/apps/web-ele/src/views/ai/workflow/index.vue b/apps/web-ele/src/views/ai/workflow/index.vue new file mode 100644 index 0000000..ab31d47 --- /dev/null +++ b/apps/web-ele/src/views/ai/workflow/index.vue @@ -0,0 +1,124 @@ + + + diff --git a/apps/web-ele/src/views/ai/write/index/index.vue b/apps/web-ele/src/views/ai/write/index/index.vue new file mode 100644 index 0000000..04b5514 --- /dev/null +++ b/apps/web-ele/src/views/ai/write/index/index.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/ai/write/index/modules/left.vue b/apps/web-ele/src/views/ai/write/index/modules/left.vue new file mode 100644 index 0000000..a364265 --- /dev/null +++ b/apps/web-ele/src/views/ai/write/index/modules/left.vue @@ -0,0 +1,250 @@ + + + diff --git a/apps/web-ele/src/views/ai/write/index/modules/right.vue b/apps/web-ele/src/views/ai/write/index/modules/right.vue new file mode 100644 index 0000000..bd0a468 --- /dev/null +++ b/apps/web-ele/src/views/ai/write/index/modules/right.vue @@ -0,0 +1,144 @@ + + + + diff --git a/apps/web-ele/src/views/ai/write/index/modules/tag.vue b/apps/web-ele/src/views/ai/write/index/modules/tag.vue new file mode 100644 index 0000000..ed758fc --- /dev/null +++ b/apps/web-ele/src/views/ai/write/index/modules/tag.vue @@ -0,0 +1,33 @@ + + + + diff --git a/apps/web-ele/src/views/ai/write/manager/data.ts b/apps/web-ele/src/views/ai/write/manager/data.ts new file mode 100644 index 0000000..02f39e8 --- /dev/null +++ b/apps/web-ele/src/views/ai/write/manager/data.ts @@ -0,0 +1,171 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemUserApi } from '#/api/system/user'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 关联数据 */ +let userList: SystemUserApi.User[] = []; +getSimpleUserList().then((data) => (userList = data)); + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择用户', + allowClear: true, + }, + }, + { + fieldName: 'type', + label: '写作类型', + component: 'Select', + componentProps: { + allowClear: true, + placeholder: '请选择写作类型', + options: getDictOptions(DICT_TYPE.AI_WRITE_TYPE, 'number'), + }, + }, + { + fieldName: 'platform', + label: '平台', + component: 'Select', + componentProps: { + allowClear: true, + placeholder: '请选择平台', + options: getDictOptions(DICT_TYPE.AI_PLATFORM, 'string'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 180, + fixed: 'left', + }, + { + minWidth: 180, + title: '用户', + field: 'userId', + formatter: ({ cellValue }) => { + return userList.find((user) => user.id === cellValue)?.nickname || '-'; + }, + }, + { + field: 'type', + title: '写作类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_WRITE_TYPE }, + }, + }, + { + field: 'platform', + title: '平台', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_WRITE_TYPE }, + }, + }, + { + field: 'model', + title: '模型', + minWidth: 180, + }, + { + field: 'prompt', + title: '生成内容提示', + minWidth: 180, + }, + { + field: 'generatedContent', + title: '生成的内容', + minWidth: 180, + }, + { + field: 'originalContent', + title: '原文', + minWidth: 180, + }, + { + field: 'length', + title: '长度', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_WRITE_LENGTH }, + }, + }, + { + field: 'format', + title: '格式', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_WRITE_FORMAT }, + }, + }, + { + field: 'tone', + title: '语气', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_WRITE_TONE }, + }, + }, + { + field: 'language', + title: '语言', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.AI_WRITE_LANGUAGE }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'errorMessage', + title: '错误信息', + minWidth: 180, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/ai/write/manager/index.vue b/apps/web-ele/src/views/ai/write/manager/index.vue new file mode 100644 index 0000000..fa73240 --- /dev/null +++ b/apps/web-ele/src/views/ai/write/manager/index.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/web-ele/src/views/bpm/category/data.ts b/apps/web-ele/src/views/bpm/category/data.ts new file mode 100644 index 0000000..6aaf6e6 --- /dev/null +++ b/apps/web-ele/src/views/bpm/category/data.ts @@ -0,0 +1,178 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '分类名', + component: 'Input', + componentProps: { + placeholder: '请输入分类名', + }, + rules: 'required', + }, + { + label: '分类标志', + fieldName: 'code', + component: 'Input', + componentProps: { + placeholder: '请输入分类标志', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '分类描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入分类描述', + }, + }, + { + fieldName: 'status', + label: '分类状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'sort', + label: '分类排序', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入分类排序', + controlsPosition: 'right', + class: '!w-full', + }, + }, + ]; +} + +/** 重命名的表单 */ +export function useRenameFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '分类名', + component: 'Input', + componentProps: { + placeholder: '请输入分类名', + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '分类名', + component: 'Input', + componentProps: { + placeholder: '请输入分类名', + allowClear: true, + }, + }, + { + fieldName: 'code', + label: '分类标志', + component: 'Input', + componentProps: { + placeholder: '请输入分类标志', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '分类状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择分类状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '分类编号', + minWidth: 100, + }, + { + field: 'name', + title: '分类名', + minWidth: 200, + }, + { + field: 'code', + title: '分类标志', + minWidth: 200, + }, + { + field: 'description', + title: '分类描述', + minWidth: 200, + }, + { + field: 'status', + title: '分类状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'sort', + title: '分类排序', + minWidth: 100, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/category/index.vue b/apps/web-ele/src/views/bpm/category/index.vue new file mode 100644 index 0000000..c747e8a --- /dev/null +++ b/apps/web-ele/src/views/bpm/category/index.vue @@ -0,0 +1,129 @@ + + + diff --git a/apps/web-ele/src/views/bpm/category/modules/form.vue b/apps/web-ele/src/views/bpm/category/modules/form.vue new file mode 100644 index 0000000..4494d9d --- /dev/null +++ b/apps/web-ele/src/views/bpm/category/modules/form.vue @@ -0,0 +1,86 @@ + + + diff --git a/apps/web-ele/src/views/bpm/category/modules/rename-form.vue b/apps/web-ele/src/views/bpm/category/modules/rename-form.vue new file mode 100644 index 0000000..5882fe9 --- /dev/null +++ b/apps/web-ele/src/views/bpm/category/modules/rename-form.vue @@ -0,0 +1,80 @@ + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/child-process-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/child-process-node-config.vue new file mode 100644 index 0000000..9ad51c7 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/child-process-node-config.vue @@ -0,0 +1,779 @@ + + + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/condition-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/condition-node-config.vue new file mode 100644 index 0000000..e090971 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/condition-node-config.vue @@ -0,0 +1,197 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/copy-task-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/copy-task-node-config.vue new file mode 100644 index 0000000..461268b --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/copy-task-node-config.vue @@ -0,0 +1,472 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/delay-timer-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/delay-timer-node-config.vue new file mode 100644 index 0000000..2366eb9 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/delay-timer-node-config.vue @@ -0,0 +1,235 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition-dialog.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition-dialog.vue new file mode 100644 index 0000000..5fc3042 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition-dialog.vue @@ -0,0 +1,79 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition.vue new file mode 100644 index 0000000..e018cdd --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/condition.vue @@ -0,0 +1,312 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-param-setting.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-param-setting.vue new file mode 100644 index 0000000..c850182 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-param-setting.vue @@ -0,0 +1,237 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-setting.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-setting.vue new file mode 100644 index 0000000..2446eff --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/http-request-setting.vue @@ -0,0 +1,166 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/user-task-listener.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/user-task-listener.vue new file mode 100644 index 0000000..244e472 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/modules/user-task-listener.vue @@ -0,0 +1,109 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/router-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/router-node-config.vue new file mode 100644 index 0000000..8f89070 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/router-node-config.vue @@ -0,0 +1,291 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/start-user-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/start-user-node-config.vue new file mode 100644 index 0000000..bae452f --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/start-user-node-config.vue @@ -0,0 +1,268 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/trigger-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/trigger-node-config.vue new file mode 100644 index 0000000..f01543c --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/trigger-node-config.vue @@ -0,0 +1,684 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/user-task-node-config.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/user-task-node-config.vue new file mode 100644 index 0000000..effa8b4 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/user-task-node-config.vue @@ -0,0 +1,1178 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/utils.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/utils.ts new file mode 100644 index 0000000..ee5e068 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes-config/utils.ts @@ -0,0 +1,48 @@ +import { APPROVE_TYPE, ApproveType, TimeUnitType } from '../../consts'; + +/** 获取条件节点默认的名称 */ +export function getDefaultConditionNodeName( + index: number, + defaultFlow: boolean | undefined, +): string { + if (defaultFlow) { + return '其它情况'; + } + return `条件${index + 1}`; +} + +/** 获取包容分支条件节点默认的名称 */ +export function getDefaultInclusiveConditionNodeName( + index: number, + defaultFlow: boolean | undefined, +): string { + if (defaultFlow) { + return '其它情况'; + } + return `包容条件${index + 1}`; +} + +/** 转换时间单位字符串为枚举值 */ +export function convertTimeUnit(strTimeUnit: string) { + if (strTimeUnit === 'M') { + return TimeUnitType.MINUTE; + } + if (strTimeUnit === 'H') { + return TimeUnitType.HOUR; + } + if (strTimeUnit === 'D') { + return TimeUnitType.DAY; + } + return TimeUnitType.HOUR; +} + +/** 根据审批类型获取对应的文本描述 */ +export function getApproveTypeText(approveType: ApproveType): string { + let approveTypeText = ''; + APPROVE_TYPE.forEach((item) => { + if (item.value === approveType) { + approveTypeText = item.label; + } + }); + return approveTypeText; +} diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/child-process-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/child-process-node.vue new file mode 100644 index 0000000..cb4b928 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/child-process-node.vue @@ -0,0 +1,127 @@ + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/copy-task-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/copy-task-node.vue new file mode 100644 index 0000000..33b3758 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/copy-task-node.vue @@ -0,0 +1,120 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/delay-timer-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/delay-timer-node.vue new file mode 100644 index 0000000..cdde90a --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/delay-timer-node.vue @@ -0,0 +1,117 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/end-event-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/end-event-node.vue new file mode 100644 index 0000000..bf6a2c1 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/end-event-node.vue @@ -0,0 +1,61 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/exclusive-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/exclusive-node.vue new file mode 100644 index 0000000..7ad414a --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/exclusive-node.vue @@ -0,0 +1,307 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/inclusive-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/inclusive-node.vue new file mode 100644 index 0000000..cc2d7f1 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/inclusive-node.vue @@ -0,0 +1,309 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-data.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-data.ts new file mode 100644 index 0000000..a9f2a06 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-data.ts @@ -0,0 +1,56 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 流程实例列表字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'startUser', + title: '发起人', + slots: { + default: ({ row }: { row: any }) => { + return row.startUser?.nickname; + }, + }, + minWidth: 100, + }, + { + field: 'deptName', + title: '部门', + slots: { + default: ({ row }: { row: any }) => { + return row.startUser?.deptName; + }, + }, + minWidth: 100, + }, + { + field: 'createTime', + title: '开始时间', + formatter: 'formatDateTime', + minWidth: 140, + }, + { + field: 'endTime', + title: '结束时间', + formatter: 'formatDateTime', + minWidth: 140, + }, + { + field: 'status', + title: '流程状态', + minWidth: 90, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS }, + }, + }, + { + field: 'durationInMillis', + title: '耗时', + minWidth: 100, + formatter: 'formatPast2', + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-modal.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-modal.vue new file mode 100644 index 0000000..dec77d8 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/process-instance-modal.vue @@ -0,0 +1,45 @@ + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-data.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-data.ts new file mode 100644 index 0000000..fbb2d0e --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-data.ts @@ -0,0 +1,61 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 审批记录列表字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'assigneeUser', + title: '审批人', + slots: { + default: ({ row }: { row: any }) => { + return row.assigneeUser?.nickname || row.ownerUser?.nickname; + }, + }, + minWidth: 100, + }, + { + field: 'deptName', + title: '部门', + slots: { + default: ({ row }: { row: any }) => { + return row.assigneeUser?.deptName || row.ownerUser?.deptName; + }, + }, + minWidth: 100, + }, + { + field: 'createTime', + title: '开始时间', + formatter: 'formatDateTime', + minWidth: 140, + }, + { + field: 'endTime', + title: '结束时间', + formatter: 'formatDateTime', + minWidth: 140, + }, + { + field: 'status', + title: '审批状态', + minWidth: 90, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_TASK_STATUS }, + }, + }, + { + field: 'reason', + title: '审批建议', + minWidth: 160, + }, + { + field: 'durationInMillis', + title: '耗时', + minWidth: 100, + formatter: 'formatPast2', + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-modal.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-modal.vue new file mode 100644 index 0000000..38b869a --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/modules/task-list-modal.vue @@ -0,0 +1,48 @@ + + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/node-handler.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/node-handler.vue new file mode 100644 index 0000000..af40f45 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/node-handler.vue @@ -0,0 +1,355 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/parallel-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/parallel-node.vue new file mode 100644 index 0000000..1c13a78 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/parallel-node.vue @@ -0,0 +1,230 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/router-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/router-node.vue new file mode 100644 index 0000000..4305283 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/router-node.vue @@ -0,0 +1,119 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/start-user-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/start-user-node.vue new file mode 100644 index 0000000..73775c5 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/start-user-node.vue @@ -0,0 +1,128 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/trigger-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/trigger-node.vue new file mode 100644 index 0000000..37d1541 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/trigger-node.vue @@ -0,0 +1,122 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/user-task-node.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/user-task-node.vue new file mode 100644 index 0000000..a95b333 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/nodes/user-task-node.vue @@ -0,0 +1,154 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/process-node-tree.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/process-node-tree.vue new file mode 100644 index 0000000..ea8596a --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/process-node-tree.vue @@ -0,0 +1,164 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-designer.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-designer.vue new file mode 100644 index 0000000..d66629c --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-designer.vue @@ -0,0 +1,255 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-model.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-model.vue new file mode 100644 index 0000000..d4fa3b1 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-model.vue @@ -0,0 +1,267 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-viewer.vue b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-viewer.vue new file mode 100644 index 0000000..95cf3f3 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/components/simple-process-viewer.vue @@ -0,0 +1,45 @@ + + diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/consts.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/consts.ts new file mode 100644 index 0000000..e811314 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/consts.ts @@ -0,0 +1,888 @@ +import { BpmNodeTypeEnum, BpmTaskStatusEnum } from '@vben/constants'; + +interface DictDataType { + label: string; + value: number | string; +} + +// 用户任务的审批类型。 【参考飞书】 +export enum ApproveType { + /** + * 自动通过 + */ + AUTO_APPROVE = 2, + /** + * 自动拒绝 + */ + AUTO_REJECT = 3, + /** + * 人工审批 + */ + USER = 1, +} + +// 多人审批方式类型枚举 ( 用于审批节点 ) +export enum ApproveMethodType { + /** + * 多人或签(通过只需一人,拒绝只需一人) + */ + ANY_APPROVE = 3, + + /** + * 多人会签(按通过比例) + */ + APPROVE_BY_RATIO = 2, + + /** + * 随机挑选一人审批 + */ + RANDOM_SELECT_ONE_APPROVE = 1, + /** + * 多人依次审批 + */ + SEQUENTIAL_APPROVE = 4, +} + +export enum NodeId { + /** + * 发起人节点 Id + */ + END_EVENT_NODE_ID = 'EndEvent', + + /** + * 发起人节点 Id + */ + START_USER_NODE_ID = 'StartUserNode', +} + +// 条件配置类型 ( 用于条件节点配置 ) +export enum ConditionType { + /** + * 条件表达式 + */ + EXPRESSION = 1, + + /** + * 条件规则 + */ + RULE = 2, +} + +// 操作按钮类型枚举 (用于审批节点) +export enum OperationButtonType { + /** + * 加签 + */ + ADD_SIGN = 5, + /** + * 通过 + */ + APPROVE = 1, + /** + * 抄送 + */ + COPY = 7, + /** + * 委派 + */ + DELEGATE = 4, + /** + * 拒绝 + */ + REJECT = 2, + /** + * 退回 + */ + RETURN = 6, + /** + * 转办 + */ + TRANSFER = 3, +} + +// 审批拒绝类型枚举 +export enum RejectHandlerType { + /** + * 结束流程 + */ + FINISH_PROCESS = 1, + /** + * 驳回到指定节点 + */ + RETURN_USER_TASK = 2, +} + +// 用户任务超时处理类型枚举 +export enum TimeoutHandlerType { + /** + * 自动同意 + */ + APPROVE = 2, + /** + * 自动拒绝 + */ + REJECT = 3, + /** + * 自动提醒 + */ + REMINDER = 1, +} + +// 用户任务的审批人为空时,处理类型枚举 +export enum AssignEmptyHandlerType { + /** + * 自动通过 + */ + APPROVE = 1, + /** + * 转交给流程管理员 + */ + ASSIGN_ADMIN = 4, + /** + * 指定人员审批 + */ + ASSIGN_USER = 3, + /** + * 自动拒绝 + */ + REJECT = 2, +} + +// 用户任务的审批人与发起人相同时,处理类型枚举 +export enum AssignStartUserHandlerType { + /** + * 转交给部门负责人审批 + */ + ASSIGN_DEPT_LEADER = 3, + /** + * 自动跳过【参考飞书】:1)如果当前节点还有其他审批人,则交由其他审批人进行审批;2)如果当前节点没有其他审批人,则该节点自动通过 + */ + SKIP = 2, + /** + * 由发起人对自己审批 + */ + START_USER_AUDIT = 1, +} + +// 时间单位枚举 +export enum TimeUnitType { + /** + * 天 + */ + DAY = 3, + /** + * 小时 + */ + HOUR = 2, + /** + * 分钟 + */ + MINUTE = 1, +} + +/** + * 表单权限的枚举 + */ +export enum FieldPermissionType { + /** + * 隐藏 + */ + NONE = '3', + /** + * 只读 + */ + READ = '1', + /** + * 编辑 + */ + WRITE = '2', +} + +/** + * 延迟类型 + */ +export enum DelayTypeEnum { + /** + * 固定日期时间 + */ + FIXED_DATE_TIME = 2, + /** + * 固定时长 + */ + FIXED_TIME_DURATION = 1, +} + +/** + * 触发器类型枚举 + */ +export enum TriggerTypeEnum { + /** + * 表单数据删除触发器 + */ + FORM_DELETE = 11, + /** + * 表单数据更新触发器 + */ + FORM_UPDATE = 10, + /** + * 接收 HTTP 回调请求触发器 + */ + HTTP_CALLBACK = 2, + /** + * 发送 HTTP 请求触发器 + */ + HTTP_REQUEST = 1, +} + +export enum ChildProcessStartUserTypeEnum { + /** + * 表单 + */ + FROM_FORM = 2, + /** + * 同主流程发起人 + */ + MAIN_PROCESS_START_USER = 1, +} + +export enum ChildProcessStartUserEmptyTypeEnum { + /** + * 子流程管理员 + */ + CHILD_PROCESS_ADMIN = 2, + /** + * 主流程管理员 + */ + MAIN_PROCESS_ADMIN = 3, + /** + * 同主流程发起人 + */ + MAIN_PROCESS_START_USER = 1, +} + +export enum ChildProcessMultiInstanceSourceTypeEnum { + /** + * 固定数量 + */ + FIXED_QUANTITY = 1, + /** + * 多选表单 + */ + MULTIPLE_FORM = 3, + /** + * 数字表单 + */ + NUMBER_FORM = 2, +} + +// 候选人策略枚举 ( 用于审批节点。抄送节点 ) +export enum CandidateStrategy { + /** + * 审批人自选 + */ + APPROVE_USER_SELECT = 34, + /** + * 部门的负责人 + */ + DEPT_LEADER = 21, + /** + * 部门成员 + */ + DEPT_MEMBER = 20, + /** + * 流程表达式 + */ + EXPRESSION = 60, + /** + * 表单内部门负责人 + */ + FORM_DEPT_LEADER = 51, + /** + * 表单内用户字段 + */ + FORM_USER = 50, + /** + * 连续多级部门的负责人 + */ + MULTI_LEVEL_DEPT_LEADER = 23, + /** + * 指定岗位 + */ + POST = 22, + /** + * 指定角色 + */ + ROLE = 10, + /** + * 发起人自己 + */ + START_USER = 36, + /** + * 发起人部门负责人 + */ + START_USER_DEPT_LEADER = 37, + /** + * 发起人连续多级部门的负责人 + */ + START_USER_MULTI_LEVEL_DEPT_LEADER = 38, + /** + * 发起人自选 + */ + START_USER_SELECT = 35, + /** + * 指定用户 + */ + USER = 30, + /** + * 指定用户组 + */ + USER_GROUP = 40, +} + +export enum BpmHttpRequestParamTypeEnum { + /** + * 固定值 + */ + FIXED_VALUE = 1, + /** + * 表单 + */ + FROM_FORM = 2, +} + +// 这里定义 HTTP 请求参数类型 +export type HttpRequestParam = { + key: string; + type: number; + value: string; +}; + +// 监听器结构定义 +export type ListenerHandler = { + body?: HttpRequestParam[]; + enable: boolean; + header?: HttpRequestParam[]; + path?: string; +}; + +/** + * 条件规则结构定义 + */ +export type ConditionRule = { + leftSide: string | undefined; + opCode: string; + rightSide: string | undefined; +}; + +/** + * 条件结构定义 + */ +export type Condition = { + // 条件规则的逻辑关系是否为且 + and: boolean; + rules: ConditionRule[]; +}; + +/** + * 条件组结构定义 + */ +export type ConditionGroup = { + // 条件组的逻辑关系是否为且 + and: boolean; + // 条件数组 + conditions: Condition[]; +}; + +/** + * 条件节点设置结构定义,用于条件节点 + */ +export type ConditionSetting = { + // 条件表达式 + conditionExpression?: string; + // 条件组 + conditionGroups?: ConditionGroup; + // 条件类型 + conditionType?: ConditionType; + // 是否默认的条件 + defaultFlow?: boolean; +}; + +/** + * 审批拒绝结构定义 + */ +export type RejectHandler = { + // 退回节点 Id + returnNodeId?: string; + // 审批拒绝类型 + type: RejectHandlerType; +}; + +/** + * 审批超时结构定义 + */ +export type TimeoutHandler = { + // 是否开启超时处理 + enable: boolean; + // 执行动作是自动提醒, 最大提醒次数 + maxRemindCount?: number; + // 超时时间设置 + timeDuration?: string; + // 超时执行的动作 + type?: number; +}; + +/** + * 审批人为空的结构定义 + */ +export type AssignEmptyHandler = { + // 审批人为空的处理类型 + type: AssignEmptyHandlerType; + // 指定用户的编号数组 + userIds?: number[]; +}; + +/** + * 延迟设置 + */ +export type DelaySetting = { + // 延迟时间表达式 + delayTime: string; + // 延迟类型 + delayType: number; +}; + +/** + * 路由分支结构定义 + */ +export type RouterSetting = { + conditionExpression: string; + conditionGroups: ConditionGroup; + conditionType: ConditionType; + nodeId: string | undefined; +}; + +/** + * 操作按钮权限结构定义 + */ +export type ButtonSetting = { + displayName: string; + enable: boolean; + id: OperationButtonType; +}; + +/** + * HTTP 请求触发器结构定义 + */ +export type HttpRequestTriggerSetting = { + // 请求体参数设置 + body?: HttpRequestParam[]; + // 请求头参数设置 + header?: HttpRequestParam[]; + // 请求响应设置 + response?: Record[]; + // 请求 URL + url: string; +}; + +/** + * 流程表单触发器配置结构定义 + */ +export type FormTriggerSetting = { + // 条件表达式 + conditionExpression?: string; + // 条件组 + conditionGroups?: ConditionGroup; + // 条件类型 + conditionType?: ConditionType; + // 删除表单字段配置 + deleteFields?: string[]; + // 更新表单字段配置 + updateFormFields?: Record; +}; + +/** + * 触发器节点结构定义 + */ +export type TriggerSetting = { + formSettings?: FormTriggerSetting[]; + httpRequestSetting?: HttpRequestTriggerSetting; + type: TriggerTypeEnum; +}; + +export type IOParameter = { + source: string; + target: string; +}; + +export type StartUserSetting = { + emptyType?: ChildProcessStartUserEmptyTypeEnum; + formField?: string; + type: ChildProcessStartUserTypeEnum; +}; + +export type TimeoutSetting = { + enable: boolean; + timeExpression?: string; + type?: DelayTypeEnum; +}; + +export type MultiInstanceSetting = { + approveRatio?: number; + enable: boolean; + sequential?: boolean; + source?: string; + sourceType?: ChildProcessMultiInstanceSourceTypeEnum; +}; + +/** + * 子流程节点结构定义 + */ +export type ChildProcessSetting = { + async: boolean; + calledProcessDefinitionKey: string; + calledProcessDefinitionName: string; + inVariables?: IOParameter[]; + multiInstanceSetting: MultiInstanceSetting; + outVariables?: IOParameter[]; + skipStartUserNode: boolean; + startUserSetting: StartUserSetting; + timeoutSetting: TimeoutSetting; +}; + +/** + * 节点结构定义 + */ +export interface SimpleFlowNode { + id: string; + type: BpmNodeTypeEnum; + name: string; + showText?: string; + // 孩子节点 + childNode?: SimpleFlowNode; + // 条件节点 + conditionNodes?: SimpleFlowNode[]; + // 审批类型 + approveType?: ApproveType; + // 候选人策略 + candidateStrategy?: number; + // 候选人参数 + candidateParam?: string; + // 多人审批方式 + approveMethod?: ApproveMethodType; + // 通过比例 + approveRatio?: number; + // 审批按钮设置 + buttonsSetting?: any[]; + // 表单权限 + fieldsPermission?: Array>; + // 审批任务超时处理 + timeoutHandler?: TimeoutHandler; + // 审批任务拒绝处理 + rejectHandler?: RejectHandler; + // 审批人为空的处理 + assignEmptyHandler?: AssignEmptyHandler; + // 审批节点的审批人与发起人相同时,对应的处理类型 + assignStartUserHandlerType?: number; + // 创建任务监听器 + taskCreateListener?: ListenerHandler; + // 创建任务监听器 + taskAssignListener?: ListenerHandler; + // 创建任务监听器 + taskCompleteListener?: ListenerHandler; + // 条件设置 + conditionSetting?: ConditionSetting; + // 活动的状态,用于前端节点状态展示 + activityStatus?: BpmTaskStatusEnum; + // 延迟设置 + delaySetting?: DelaySetting; + // 路由分支 + routerGroups?: RouterSetting[]; + defaultFlowId?: string; + // 签名 + signEnable?: boolean; + // 审批意见 + reasonRequire?: boolean; + // 跳过表达式 + skipExpression?: string; + // 触发器设置 + triggerSetting?: TriggerSetting; + // 子流程 + childProcessSetting?: ChildProcessSetting; +} + +/** + * 条件组默认值 + */ +export const DEFAULT_CONDITION_GROUP_VALUE = { + and: true, + conditions: [ + { + and: true, + rules: [ + { + opCode: '==', + leftSide: undefined, + rightSide: '', + }, + ], + }, + ], +}; + +export const NODE_DEFAULT_TEXT = new Map(); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.USER_TASK_NODE, '请配置审批人'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.COPY_TASK_NODE, '请配置抄送人'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.CONDITION_NODE, '请设置条件'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.START_USER_NODE, '请设置发起人'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.DELAY_TIMER_NODE, '请设置延迟器'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.ROUTER_BRANCH_NODE, '请设置路由节点'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.TRIGGER_NODE, '请设置触发器'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.TRANSACTOR_NODE, '请设置办理人'); +NODE_DEFAULT_TEXT.set(BpmNodeTypeEnum.CHILD_PROCESS_NODE, '请设置子流程'); + +export const NODE_DEFAULT_NAME = new Map(); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.USER_TASK_NODE, '审批人'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.COPY_TASK_NODE, '抄送人'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.CONDITION_NODE, '条件'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.START_USER_NODE, '发起人'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.DELAY_TIMER_NODE, '延迟器'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.ROUTER_BRANCH_NODE, '路由分支'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.TRIGGER_NODE, '触发器'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.TRANSACTOR_NODE, '办理人'); +NODE_DEFAULT_NAME.set(BpmNodeTypeEnum.CHILD_PROCESS_NODE, '子流程'); + +// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序 +export const CANDIDATE_STRATEGY: DictDataType[] = [ + { label: '指定成员', value: CandidateStrategy.USER as any }, + { label: '指定角色', value: CandidateStrategy.ROLE as any }, + { label: '指定岗位', value: CandidateStrategy.POST as any }, + { label: '部门成员', value: CandidateStrategy.DEPT_MEMBER as any }, + { label: '部门负责人', value: CandidateStrategy.DEPT_LEADER as any }, + { + label: '连续多级部门负责人', + value: CandidateStrategy.MULTI_LEVEL_DEPT_LEADER as any, + }, + { label: '发起人自选', value: CandidateStrategy.START_USER_SELECT as any }, + { label: '审批人自选', value: CandidateStrategy.APPROVE_USER_SELECT as any }, + { label: '发起人本人', value: CandidateStrategy.START_USER as any }, + { + label: '发起人部门负责人', + value: CandidateStrategy.START_USER_DEPT_LEADER as any, + }, + { + label: '发起人连续部门负责人', + value: CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER as any, + }, + { label: '用户组', value: CandidateStrategy.USER_GROUP as any }, + { label: '表单内用户字段', value: CandidateStrategy.FORM_USER as any }, + { + label: '表单内部门负责人', + value: CandidateStrategy.FORM_DEPT_LEADER as any, + }, + { label: '流程表达式', value: CandidateStrategy.EXPRESSION as any }, +]; +// 审批节点 的审批类型 +export const APPROVE_TYPE: DictDataType[] = [ + { label: '人工审批', value: ApproveType.USER as any }, + { label: '自动通过', value: ApproveType.AUTO_APPROVE as any }, + { label: '自动拒绝', value: ApproveType.AUTO_REJECT as any }, +]; + +export const APPROVE_METHODS: DictDataType[] = [ + { + label: '按顺序依次审批', + value: ApproveMethodType.SEQUENTIAL_APPROVE as any, + }, + { + label: '会签(可同时审批,至少 % 人必须审批通过)', + value: ApproveMethodType.APPROVE_BY_RATIO as any, + }, + { + label: '或签(可同时审批,有一人通过即可)', + value: ApproveMethodType.ANY_APPROVE as any, + }, + { + label: '随机挑选一人审批', + value: ApproveMethodType.RANDOM_SELECT_ONE_APPROVE as any, + }, +]; + +export const CONDITION_CONFIG_TYPES: DictDataType[] = [ + { label: '条件规则', value: ConditionType.RULE as any }, + { label: '条件表达式', value: ConditionType.EXPRESSION as any }, +]; + +// 时间单位类型 +export const TIME_UNIT_TYPES: DictDataType[] = [ + { label: '分钟', value: TimeUnitType.MINUTE as any }, + { label: '小时', value: TimeUnitType.HOUR as any }, + { label: '天', value: TimeUnitType.DAY as any }, +]; +// 超时处理执行动作类型 +export const TIMEOUT_HANDLER_TYPES: DictDataType[] = [ + { label: '自动提醒', value: 1 }, + { label: '自动同意', value: 2 }, + { label: '自动拒绝', value: 3 }, +]; +export const REJECT_HANDLER_TYPES: DictDataType[] = [ + { label: '终止流程', value: RejectHandlerType.FINISH_PROCESS as any }, + { label: '驳回到指定节点', value: RejectHandlerType.RETURN_USER_TASK as any }, + // { label: '结束任务', value: RejectHandlerType.FINISH_TASK } +]; +export const ASSIGN_EMPTY_HANDLER_TYPES: DictDataType[] = [ + { label: '自动通过', value: 1 }, + { label: '自动拒绝', value: 2 }, + { label: '指定成员审批', value: 3 }, + { label: '转交给流程管理员', value: 4 }, +]; +export const ASSIGN_START_USER_HANDLER_TYPES: DictDataType[] = [ + { label: '由发起人对自己审批', value: 1 }, + { label: '自动跳过', value: 2 }, + { label: '转交给部门负责人审批', value: 3 }, +]; + +// 比较运算符 +export const COMPARISON_OPERATORS: DictDataType[] = [ + { + value: '==', + label: '等于', + }, + { + value: '!=', + label: '不等于', + }, + { + value: '>', + label: '大于', + }, + { + value: '>=', + label: '大于等于', + }, + { + value: '<', + label: '小于', + }, + { + value: '<=', + label: '小于等于', + }, +]; +// 审批操作按钮名称 +export const OPERATION_BUTTON_NAME = new Map(); +OPERATION_BUTTON_NAME.set(OperationButtonType.APPROVE, '通过'); +OPERATION_BUTTON_NAME.set(OperationButtonType.REJECT, '拒绝'); +OPERATION_BUTTON_NAME.set(OperationButtonType.TRANSFER, '转办'); +OPERATION_BUTTON_NAME.set(OperationButtonType.DELEGATE, '委派'); +OPERATION_BUTTON_NAME.set(OperationButtonType.ADD_SIGN, '加签'); +OPERATION_BUTTON_NAME.set(OperationButtonType.RETURN, '退回'); +OPERATION_BUTTON_NAME.set(OperationButtonType.COPY, '抄送'); + +// 默认的按钮权限设置 +export const DEFAULT_BUTTON_SETTING: ButtonSetting[] = [ + { id: OperationButtonType.APPROVE, displayName: '通过', enable: true }, + { id: OperationButtonType.REJECT, displayName: '拒绝', enable: true }, + { id: OperationButtonType.TRANSFER, displayName: '转办', enable: true }, + { id: OperationButtonType.DELEGATE, displayName: '委派', enable: true }, + { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: true }, + { id: OperationButtonType.RETURN, displayName: '退回', enable: true }, +]; + +// 办理人默认的按钮权限设置 +export const TRANSACTOR_DEFAULT_BUTTON_SETTING: ButtonSetting[] = [ + { id: OperationButtonType.APPROVE, displayName: '办理', enable: true }, + { id: OperationButtonType.REJECT, displayName: '拒绝', enable: false }, + { id: OperationButtonType.TRANSFER, displayName: '转办', enable: false }, + { id: OperationButtonType.DELEGATE, displayName: '委派', enable: false }, + { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false }, + { id: OperationButtonType.RETURN, displayName: '退回', enable: false }, +]; + +// 发起人的按钮权限。暂时定死,不可以编辑 +export const START_USER_BUTTON_SETTING: ButtonSetting[] = [ + { id: OperationButtonType.APPROVE, displayName: '提交', enable: true }, + { id: OperationButtonType.REJECT, displayName: '拒绝', enable: false }, + { id: OperationButtonType.TRANSFER, displayName: '转办', enable: false }, + { id: OperationButtonType.DELEGATE, displayName: '委派', enable: false }, + { id: OperationButtonType.ADD_SIGN, displayName: '加签', enable: false }, + { id: OperationButtonType.RETURN, displayName: '退回', enable: false }, +]; + +export const MULTI_LEVEL_DEPT: DictDataType[] = [ + { label: '第 1 级部门', value: 1 }, + { label: '第 2 级部门', value: 2 }, + { label: '第 3 级部门', value: 3 }, + { label: '第 4 级部门', value: 4 }, + { label: '第 5 级部门', value: 5 }, + { label: '第 6 级部门', value: 6 }, + { label: '第 7 级部门', value: 7 }, + { label: '第 8 级部门', value: 8 }, + { label: '第 9 级部门', value: 9 }, + { label: '第 10 级部门', value: 10 }, + { label: '第 11 级部门', value: 11 }, + { label: '第 12 级部门', value: 12 }, + { label: '第 13 级部门', value: 13 }, + { label: '第 14 级部门', value: 14 }, + { label: '第 15 级部门', value: 15 }, +]; + +export const DELAY_TYPE = [ + { label: '固定时长', value: DelayTypeEnum.FIXED_TIME_DURATION }, + { label: '固定日期', value: DelayTypeEnum.FIXED_DATE_TIME }, +]; + +export const BPM_HTTP_REQUEST_PARAM_TYPES = [ + { + value: 1, + label: '固定值', + }, + { + value: 2, + label: '表单', + }, +]; + +export const TRIGGER_TYPES: DictDataType[] = [ + { label: '发送 HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST as any }, + { label: '接收 HTTP 回调', value: TriggerTypeEnum.HTTP_CALLBACK as any }, + { label: '修改表单数据', value: TriggerTypeEnum.FORM_UPDATE as any }, + { label: '删除表单数据', value: TriggerTypeEnum.FORM_DELETE as any }, +]; + +export const CHILD_PROCESS_START_USER_TYPE = [ + { + label: '同主流程发起人', + value: ChildProcessStartUserTypeEnum.MAIN_PROCESS_START_USER, + }, + { label: '从表单中获取', value: ChildProcessStartUserTypeEnum.FROM_FORM }, +]; + +export const CHILD_PROCESS_START_USER_EMPTY_TYPE = [ + { + label: '同主流程发起人', + value: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_START_USER, + }, + { + label: '子流程管理员', + value: ChildProcessStartUserEmptyTypeEnum.CHILD_PROCESS_ADMIN, + }, + { + label: '主流程管理员', + value: ChildProcessStartUserEmptyTypeEnum.MAIN_PROCESS_ADMIN, + }, +]; + +export const CHILD_PROCESS_MULTI_INSTANCE_SOURCE_TYPE = [ + { + label: '固定数量', + value: ChildProcessMultiInstanceSourceTypeEnum.FIXED_QUANTITY, + }, + { + label: '数字表单', + value: ChildProcessMultiInstanceSourceTypeEnum.NUMBER_FORM, + }, + { + label: '多选表单', + value: ChildProcessMultiInstanceSourceTypeEnum.MULTIPLE_FORM, + }, +]; diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/helpers.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/helpers.ts new file mode 100644 index 0000000..3ffc2a3 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/helpers.ts @@ -0,0 +1,792 @@ +import type { Ref } from 'vue'; + +import type { + ConditionGroup, + HttpRequestParam, + SimpleFlowNode, +} from './consts'; + +import type { BpmUserGroupApi } from '#/api/bpm/userGroup'; +import type { SystemDeptApi } from '#/api/system/dept'; +import type { SystemPostApi } from '#/api/system/post'; +import type { SystemRoleApi } from '#/api/system/role'; +import type { SystemUserApi } from '#/api/system/user'; + +import { inject, nextTick, ref, toRaw, unref, watch } from 'vue'; + +import { + BpmNodeTypeEnum, + BpmTaskStatusEnum, + ProcessVariableEnum, +} from '@vben/constants'; + +import { + ApproveMethodType, + AssignEmptyHandlerType, + AssignStartUserHandlerType, + CandidateStrategy, + COMPARISON_OPERATORS, + ConditionType, + FieldPermissionType, + NODE_DEFAULT_NAME, + RejectHandlerType, +} from './consts'; + +export function useWatchNode(props: { + flowNode: SimpleFlowNode; +}): Ref { + const node = ref(props.flowNode); + watch( + () => props.flowNode, + (newValue) => { + node.value = newValue; + }, + ); + return node; +} + +// 解析 formCreate 所有表单字段, 并返回 +function parseFormCreateFields(formFields?: string[]) { + const result: Array> = []; + if (formFields) { + formFields.forEach((fieldStr: string) => { + parseFormFields(JSON.parse(fieldStr), result); + }); + } + return result; +} + +/** + * 解析表单组件的 field, title 等字段(递归,如果组件包含子组件) + * + * @param rule 组件的生成规则 https://www.form-create.com/v3/guide/rule + * @param fields 解析后表单组件字段 + * @param parentTitle 如果是子表单,子表单的标题,默认为空 + */ +export const parseFormFields = ( + rule: Record, + fields: Array> = [], + parentTitle: string = '', +) => { + const { type, field, $required, title: tempTitle, children } = rule; + if (field && tempTitle) { + let title = tempTitle; + if (parentTitle) { + title = `${parentTitle}.${tempTitle}`; + } + let required = false; + if ($required) { + required = true; + } + fields.push({ + field, + title, + type, + required, + }); + // TODO 子表单 需要处理子表单字段 + // if (type === 'group' && rule.props?.rule && Array.isArray(rule.props.rule)) { + // // 解析子表单的字段 + // rule.props.rule.forEach((item) => { + // parseFields(item, fieldsPermission, title) + // }) + // } + } + if (children && Array.isArray(children)) { + children.forEach((rule) => { + parseFormFields(rule, fields); + }); + } +}; + +/** + * @description 表单数据权限配置,用于发起人节点 、审批节点、抄送节点 + */ +export function useFormFieldsPermission( + defaultPermission: FieldPermissionType, +) { + // 字段权限配置. 需要有 field, title, permissioin 属性 + const fieldsPermissionConfig = ref>>([]); + + const formType = inject>('formType', ref()); // 表单类型 + + const formFields = inject>('formFields', ref([])); // 流程表单字段 + + function getNodeConfigFormFields( + nodeFormFields?: Array>, + ) { + nodeFormFields = toRaw(nodeFormFields); + fieldsPermissionConfig.value = + !nodeFormFields || nodeFormFields.length === 0 + ? getDefaultFieldsPermission(unref(formFields)) + : mergeFieldsPermission(nodeFormFields, unref(formFields)); + } + // 合并已经设置的表单字段权限,当前流程表单字段 (可能新增,或删除了字段) + function mergeFieldsPermission( + formFieldsPermisson: Array>, + formFields?: string[], + ) { + let mergedFieldsPermission: Array> = []; + if (formFields) { + mergedFieldsPermission = parseFormCreateFields(formFields).map((item) => { + const found = formFieldsPermisson.find( + (fieldPermission) => fieldPermission.field === item.field, + ); + return { + field: item.field, + title: item.title, + permission: found ? found.permission : defaultPermission, + }; + }); + } + return mergedFieldsPermission; + } + + // 默认的表单权限: 获取表单的所有字段,设置字段默认权限为只读 + function getDefaultFieldsPermission(formFields?: string[]) { + let defaultFieldsPermission: Array> = []; + if (formFields) { + defaultFieldsPermission = parseFormCreateFields(formFields).map( + (item) => { + return { + field: item.field, + title: item.title, + permission: defaultPermission, + }; + }, + ); + } + return defaultFieldsPermission; + } + + // 获取表单的所有字段,作为下拉框选项 + const formFieldOptions = parseFormCreateFields(unref(formFields)); + + return { + formType, + fieldsPermissionConfig, + formFieldOptions, + getNodeConfigFormFields, + }; +} + +/** + * @description 获取流程表单的字段 + */ +export function useFormFields() { + const formFields = inject>('formFields', ref([])); // 流程表单字段 + return parseFormCreateFields(unref(formFields)); +} + +// TODO @芋艿:后续需要把各种类似 useFormFieldsPermission 的逻辑,抽成一个通用方法。 +/** + * @description 获取流程表单的字段和发起人字段 + */ +export function useFormFieldsAndStartUser() { + const injectFormFields = inject>('formFields', ref([])); // 流程表单字段 + const formFields = parseFormCreateFields(unref(injectFormFields)); + // 添加发起人 + formFields.unshift({ + field: ProcessVariableEnum.START_USER_ID, + title: '发起人', + required: true, + }); + return formFields; +} + +export type UserTaskFormType = { + approveMethod: ApproveMethodType; + approveRatio?: number; + assignEmptyHandlerType?: AssignEmptyHandlerType; + assignEmptyHandlerUserIds?: number[]; + assignStartUserHandlerType?: AssignStartUserHandlerType; + buttonsSetting: any[]; + candidateStrategy: CandidateStrategy; + deptIds?: number[]; // 部门 + deptLevel?: number; // 部门层级 + expression?: string; // 流程表达式 + formDept?: string; // 表单内部门字段 + formUser?: string; // 表单内用户字段 + maxRemindCount?: number; + postIds?: number[]; // 岗位 + reasonRequire: boolean; + rejectHandlerType?: RejectHandlerType; + returnNodeId?: string; + roleIds?: number[]; // 角色 + signEnable: boolean; + skipExpression?: string; // 跳过表达式 + taskAssignListener?: { + body: HttpRequestParam[]; + header: HttpRequestParam[]; + }; + taskAssignListenerEnable?: boolean; + taskAssignListenerPath?: string; + taskCompleteListener?: { + body: HttpRequestParam[]; + header: HttpRequestParam[]; + }; + taskCompleteListenerEnable?: boolean; + taskCompleteListenerPath?: string; + taskCreateListener?: { + body: HttpRequestParam[]; + header: HttpRequestParam[]; + }; + taskCreateListenerEnable?: boolean; + taskCreateListenerPath?: string; + timeDuration?: number; + timeoutHandlerEnable?: boolean; + timeoutHandlerType?: number; + userGroups?: number[]; // 用户组 + userIds?: number[]; // 用户 +}; + +export type CopyTaskFormType = { + candidateStrategy: CandidateStrategy; + deptIds?: number[]; // 部门 + deptLevel?: number; // 部门层级 + expression?: string; // 流程表达式 + formDept?: string; // 表单内部门字段 + formUser?: string; // 表单内用户字段 + postIds?: number[]; // 岗位 + roleIds?: number[]; // 角色 + userGroups?: number[]; // 用户组 + userIds?: number[]; // 用户 +}; + +/** + * @description 节点表单数据。 用于审批节点、抄送节点 + */ +export function useNodeForm(nodeType: BpmNodeTypeEnum) { + const roleOptions = inject>('roleList', ref([])); // 角色列表 + const postOptions = inject>('postList', ref([])); // 岗位列表 + const userOptions = inject>('userList', ref([])); // 用户列表 + const deptOptions = inject>('deptList', ref([])); // 部门列表 + const userGroupOptions = inject>( + 'userGroupList', + ref([]), + ); // 用户组列表 + const deptTreeOptions = inject>( + 'deptTree', + ref([]), + ); // 部门树 + const formFields = inject>('formFields', ref([])); // 流程表单字段 + const configForm = ref(); + + if ( + nodeType === BpmNodeTypeEnum.USER_TASK_NODE || + nodeType === BpmNodeTypeEnum.TRANSACTOR_NODE + ) { + configForm.value = { + candidateStrategy: CandidateStrategy.USER, + approveMethod: ApproveMethodType.SEQUENTIAL_APPROVE, + approveRatio: 100, + rejectHandlerType: RejectHandlerType.FINISH_PROCESS, + assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT, + returnNodeId: '', + timeoutHandlerEnable: false, + timeoutHandlerType: 1, + timeDuration: 6, // 默认 6小时 + maxRemindCount: 1, // 默认 提醒 1次 + buttonsSetting: [], + }; + } + configForm.value = { + candidateStrategy: CandidateStrategy.USER, + }; + + function getShowText(): string { + let showText = ''; + // 指定成员 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.USER && + configForm.value?.userIds?.length > 0 + ) { + const candidateNames: string[] = []; + userOptions?.value.forEach((item: any) => { + if (configForm.value?.userIds?.includes(item.id)) { + candidateNames.push(item.nickname); + } + }); + showText = `指定成员:${candidateNames.join(',')}`; + } + // 指定角色 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.ROLE && + configForm.value.roleIds?.length > 0 + ) { + const candidateNames: string[] = []; + roleOptions?.value.forEach((item: any) => { + if (configForm.value?.roleIds?.includes(item.id)) { + candidateNames.push(item.name); + } + }); + showText = `指定角色:${candidateNames.join(',')}`; + } + // 指定部门 + if ( + (configForm.value?.candidateStrategy === CandidateStrategy.DEPT_MEMBER || + configForm.value?.candidateStrategy === CandidateStrategy.DEPT_LEADER || + configForm.value?.candidateStrategy === + CandidateStrategy.MULTI_LEVEL_DEPT_LEADER) && + configForm.value?.deptIds?.length > 0 + ) { + const candidateNames: string[] = []; + deptOptions?.value.forEach((item) => { + if (configForm.value?.deptIds?.includes(item.id)) { + candidateNames.push(item.name); + } + }); + if ( + configForm.value.candidateStrategy === CandidateStrategy.DEPT_MEMBER + ) { + showText = `部门成员:${candidateNames.join(',')}`; + } else if ( + configForm.value.candidateStrategy === CandidateStrategy.DEPT_LEADER + ) { + showText = `部门的负责人:${candidateNames.join(',')}`; + } else { + showText = `多级部门的负责人:${candidateNames.join(',')}`; + } + } + + // 指定岗位 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.POST && + configForm.value.postIds?.length > 0 + ) { + const candidateNames: string[] = []; + postOptions?.value.forEach((item) => { + if (configForm.value?.postIds?.includes(item.id)) { + candidateNames.push(item.name); + } + }); + showText = `指定岗位: ${candidateNames.join(',')}`; + } + // 指定用户组 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.USER_GROUP && + configForm.value?.userGroups?.length > 0 + ) { + const candidateNames: string[] = []; + userGroupOptions?.value.forEach((item) => { + if (configForm.value?.userGroups?.includes(item.id)) { + candidateNames.push(item.name); + } + }); + showText = `指定用户组: ${candidateNames.join(',')}`; + } + + // 表单内用户字段 + if (configForm.value?.candidateStrategy === CandidateStrategy.FORM_USER) { + const formFieldOptions = parseFormCreateFields(unref(formFields)); + const item = formFieldOptions.find( + (item) => item.field === configForm.value?.formUser, + ); + showText = `表单用户:${item?.title}`; + } + + // 表单内部门负责人 + if ( + configForm.value?.candidateStrategy === CandidateStrategy.FORM_DEPT_LEADER + ) { + showText = `表单内部门负责人`; + } + + // 审批人自选 + if ( + configForm.value?.candidateStrategy === + CandidateStrategy.APPROVE_USER_SELECT + ) { + showText = `审批人自选`; + } + + // 发起人自选 + if ( + configForm.value?.candidateStrategy === + CandidateStrategy.START_USER_SELECT + ) { + showText = `发起人自选`; + } + // 发起人自己 + if (configForm.value?.candidateStrategy === CandidateStrategy.START_USER) { + showText = `发起人自己`; + } + // 发起人的部门负责人 + if ( + configForm.value?.candidateStrategy === + CandidateStrategy.START_USER_DEPT_LEADER + ) { + showText = `发起人的部门负责人`; + } + // 发起人的部门负责人 + if ( + configForm.value?.candidateStrategy === + CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER + ) { + showText = `发起人连续部门负责人`; + } + // 流程表达式 + if (configForm.value?.candidateStrategy === CandidateStrategy.EXPRESSION) { + showText = `流程表达式:${configForm.value.expression}`; + } + return showText; + } + + /** + * 处理候选人参数的赋值 + */ + function handleCandidateParam() { + let candidateParam: string | undefined; + if (!configForm.value) { + return candidateParam; + } + switch (configForm.value.candidateStrategy) { + case CandidateStrategy.DEPT_LEADER: + case CandidateStrategy.DEPT_MEMBER: { + candidateParam = configForm.value.deptIds?.join(','); + break; + } + case CandidateStrategy.EXPRESSION: { + candidateParam = configForm.value.expression; + break; + } + // 表单内部门的负责人 + case CandidateStrategy.FORM_DEPT_LEADER: { + // 候选人参数格式: | 分隔 。左边为表单内部门字段。 右边为部门层级 + const deptFieldOnForm = configForm.value.formDept; + candidateParam = deptFieldOnForm?.concat( + `|${configForm.value.deptLevel}`, + ); + break; + } + case CandidateStrategy.FORM_USER: { + candidateParam = configForm.value?.formUser; + break; + } + // 指定连续多级部门的负责人 + case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: { + // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级 + const deptIds = configForm.value.deptIds?.join(','); + candidateParam = deptIds?.concat(`|${configForm.value.deptLevel}`); + break; + } + case CandidateStrategy.POST: { + candidateParam = configForm.value.postIds?.join(','); + break; + } + case CandidateStrategy.ROLE: { + candidateParam = configForm.value.roleIds?.join(','); + break; + } + // 发起人部门负责人 + case CandidateStrategy.START_USER_DEPT_LEADER: + case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER: { + candidateParam = `${configForm.value.deptLevel}`; + break; + } + case CandidateStrategy.USER: { + candidateParam = configForm.value.userIds?.join(','); + break; + } + case CandidateStrategy.USER_GROUP: { + candidateParam = configForm.value.userGroups?.join(','); + break; + } + default: { + break; + } + } + return candidateParam; + } + /** + * 解析候选人参数 + */ + function parseCandidateParam( + candidateStrategy: CandidateStrategy, + candidateParam: string | undefined, + ) { + if (!configForm.value || !candidateParam) { + return; + } + switch (candidateStrategy) { + case CandidateStrategy.DEPT_LEADER: + case CandidateStrategy.DEPT_MEMBER: { + configForm.value.deptIds = candidateParam + .split(',') + .map((item) => +item); + break; + } + case CandidateStrategy.EXPRESSION: { + configForm.value.expression = candidateParam; + break; + } + // 表单内的部门负责人 + case CandidateStrategy.FORM_DEPT_LEADER: { + // 候选人参数格式: | 分隔 。左边为表单内的部门字段。 右边为部门层级 + const paramArray = candidateParam.split('|'); + if (paramArray.length > 1) { + configForm.value.formDept = paramArray[0]; + if (paramArray[1]) configForm.value.deptLevel = +paramArray[1]; + } + break; + } + case CandidateStrategy.FORM_USER: { + configForm.value.formUser = candidateParam; + break; + } + // 指定连续多级部门的负责人 + case CandidateStrategy.MULTI_LEVEL_DEPT_LEADER: { + // 候选人参数格式: | 分隔 。左边为部门(多个部门用 , 分隔)。 右边为部门层级 + const paramArray = candidateParam.split('|') as string[]; + if (paramArray.length > 1) { + configForm.value.deptIds = paramArray[0] + ?.split(',') + .map((item) => +item); + if (paramArray[1]) configForm.value.deptLevel = +paramArray[1]; + } + break; + } + case CandidateStrategy.POST: { + configForm.value.postIds = candidateParam + .split(',') + .map((item) => +item); + break; + } + case CandidateStrategy.ROLE: { + configForm.value.roleIds = candidateParam + .split(',') + .map((item) => +item); + break; + } + // 发起人部门负责人 + case CandidateStrategy.START_USER_DEPT_LEADER: + case CandidateStrategy.START_USER_MULTI_LEVEL_DEPT_LEADER: { + configForm.value.deptLevel = +candidateParam; + break; + } + case CandidateStrategy.USER: { + configForm.value.userIds = candidateParam + .split(',') + .map((item) => +item); + break; + } + case CandidateStrategy.USER_GROUP: { + configForm.value.userGroups = candidateParam + .split(',') + .map((item) => +item); + break; + } + default: { + break; + } + } + } + return { + configForm, + roleOptions, + postOptions, + userOptions, + userGroupOptions, + deptTreeOptions, + handleCandidateParam, + parseCandidateParam, + getShowText, + }; +} + +/** + * @description 抽屉配置 + */ +export function useDrawer() { + // 抽屉配置是否可见 + const settingVisible = ref(false); + // 关闭配置抽屉 + function closeDrawer() { + settingVisible.value = false; + } + // 打开配置抽屉 + function openDrawer() { + settingVisible.value = true; + } + return { + settingVisible, + closeDrawer, + openDrawer, + }; +} + +/** + * @description 节点名称配置 + */ +export function useNodeName(nodeType: BpmNodeTypeEnum) { + // 节点名称 + const nodeName = ref(); + // 节点名称输入框 + const showInput = ref(false); + // 输入框的引用 + const inputRef = ref(null); + // 点击节点名称编辑图标 + function clickIcon() { + showInput.value = true; + } + // 修改节点名称 + function changeNodeName() { + showInput.value = false; + nodeName.value = + nodeName.value || (NODE_DEFAULT_NAME.get(nodeType) as string); + } + // 监听 showInput 的变化,当变为 true 时自动聚焦 + watch(showInput, (value) => { + if (value) { + nextTick(() => { + inputRef.value?.focus(); + }); + } + }); + + return { + nodeName, + showInput, + inputRef, + clickIcon, + changeNodeName, + }; +} + +export function useNodeName2( + node: Ref, + nodeType: BpmNodeTypeEnum, +) { + // 显示节点名称输入框 + const showInput = ref(false); + // 输入框的引用 + const inputRef = ref(null); + + // 监听 showInput 的变化,当变为 true 时自动聚焦 + watch(showInput, (value) => { + if (value) { + nextTick(() => { + inputRef.value?.focus(); + }); + } + }); + + // 修改节点名称 + function changeNodeName() { + showInput.value = false; + node.value.name = + node.value.name || (NODE_DEFAULT_NAME.get(nodeType) as string); + console.warn('node.value.name===>', node.value.name); + } + // 点击节点标题进行输入 + function clickTitle() { + showInput.value = true; + } + return { + showInput, + inputRef, + clickTitle, + changeNodeName, + }; +} + +/** + * @description 根据节点任务状态,获取节点任务状态样式 + */ +export function useTaskStatusClass( + taskStatus: BpmTaskStatusEnum | undefined, +): string { + if (!taskStatus) { + return ''; + } + if (taskStatus === BpmTaskStatusEnum.APPROVE) { + return 'status-pass'; + } + if (taskStatus === BpmTaskStatusEnum.RUNNING) { + return 'status-running'; + } + if (taskStatus === BpmTaskStatusEnum.REJECT) { + return 'status-reject'; + } + if (taskStatus === BpmTaskStatusEnum.CANCEL) { + return 'status-cancel'; + } + return ''; +} + +/** 条件组件文字展示 */ +export function getConditionShowText( + conditionType: ConditionType | undefined, + conditionExpression: string | undefined, + conditionGroups: ConditionGroup | undefined, + fieldOptions: Array>, +) { + let showText: string | undefined; + if (conditionType === ConditionType.EXPRESSION && conditionExpression) { + showText = `表达式:${conditionExpression}`; + } + if (conditionType === ConditionType.RULE) { + // 条件组是否为与关系 + const groupAnd = conditionGroups?.and; + let warningMessage: string | undefined; + const conditionGroup = conditionGroups?.conditions.map((item) => { + return `(${item.rules + .map((rule) => { + if (rule.leftSide && rule.rightSide) { + return `${getFormFieldTitle( + fieldOptions, + rule.leftSide, + )} ${getOpName(rule.opCode)} ${rule.rightSide}`; + } else { + // 有一条规则不完善。提示错误 + warningMessage = '请完善条件规则'; + return ''; + } + }) + .join(item.and ? ' 且 ' : ' 或 ')} ) `; + }); + showText = warningMessage + ? '' + : conditionGroup?.join(groupAnd ? ' 且 ' : ' 或 '); + } + return showText; +} + +/** 获取表单字段名称*/ +function getFormFieldTitle( + fieldOptions: Array>, + field: string, +) { + const item = fieldOptions.find((item) => item.field === field); + return item?.title; +} + +/** 获取操作符名称 */ +function getOpName(opCode: string): string | undefined { + const opName = COMPARISON_OPERATORS.find( + (item: any) => item.value === opCode, + ); + return opName?.label; +} + +/** 获取条件节点默认的名称 */ +export function getDefaultConditionNodeName( + index: number, + defaultFlow: boolean | undefined, +): string { + if (defaultFlow) { + return '其它情况'; + } + return `条件${index + 1}`; +} + +/** 获取包容分支条件节点默认的名称 */ +export function getDefaultInclusiveConditionNodeName( + index: number, + defaultFlow: boolean | undefined, +): string { + if (defaultFlow) { + return '其它情况'; + } + return `包容条件${index + 1}`; +} diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/index.ts b/apps/web-ele/src/views/bpm/components/simple-process-design/index.ts new file mode 100644 index 0000000..41269ea --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/index.ts @@ -0,0 +1,11 @@ +import './styles/simple-process-designer.scss'; + +export { default as HttpRequestSetting } from './components/nodes-config/modules/http-request-setting.vue'; + +export { default as SimpleProcessDesigner } from './components/simple-process-designer.vue'; + +export { default as SimpleProcessViewer } from './components/simple-process-viewer.vue'; + +export type { SimpleFlowNode } from './consts'; + +export { parseFormFields } from './helpers'; diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.ttf b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.ttf new file mode 100644 index 0000000..06f4e31 Binary files /dev/null and b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.ttf differ diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff new file mode 100644 index 0000000..0724e75 Binary files /dev/null and b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff differ diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff2 b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff2 new file mode 100644 index 0000000..c904bb6 Binary files /dev/null and b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/iconfont.woff2 differ diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/styles/simple-process-designer.scss b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/simple-process-designer.scss new file mode 100644 index 0000000..a36f969 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/simple-process-designer.scss @@ -0,0 +1,759 @@ +// TODO 整个样式是不是要重新优化一下 +// iconfont 样式 +@font-face { + font-family: iconfont; /* Project id 4495938 */ + src: + url('iconfont.woff2?t=1737639517142') format('woff2'), + url('iconfont.woff?t=1737639517142') format('woff'), + url('iconfont.ttf?t=1737639517142') format('truetype'); +} +// 配置节点头部 +.config-header { + display: flex; + flex-direction: column; + + .node-name { + display: flex; + align-items: center; + height: 24px; + font-size: 16px; + line-height: 24px; + cursor: pointer; + } + + .divide-line { + width: 100%; + height: 1px; + margin-top: 16px; + background: #eee; + } + + .config-editable-input { + max-width: 510px; + height: 24px; + font-size: 16px; + line-height: 24px; + border: 1px solid #d9d9d9; + border-radius: 4px; + transition: all 0.3s; + + &:focus { + outline: 0; + border-color: #40a9ff; + box-shadow: 0 0 0 2px rgb(24 144 255 / 20%); + } + } +} +// 节点连线气泡卡片样式 +.handler-item-wrapper { + display: flex; + flex-wrap: wrap; + width: 320px; + cursor: pointer; + + .handler-item { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 12px; + } + + .handler-item-icon { + width: 50px; + height: 50px; + text-align: center; + user-select: none; + background: #fff; + border: 1px solid #e2e2e2; + border-radius: 50%; + + &:hover { + background: #e2e2e2; + box-shadow: 0 2px 4px 0 rgb(0 0 0 / 10%); + } + + .icon-size { + font-size: 25px; + line-height: 50px; + } + } + + .approve { + color: #ff943e; + } + + .copy { + color: #3296fa; + } + + .condition { + color: #67c23a; + } + + .parallel { + color: #626aef; + } + + .inclusive { + color: #345da2; + } + + .delay { + color: #e47470; + } + + .trigger { + color: #3373d2; + } + + .router { + color: #ca3a31; + } + + .transactor { + color: #309; + } + + .child-process { + color: #963; + } + + .async-child-process { + color: #066; + } + + .handler-item-text { + width: 80px; + margin-top: 4px; + font-size: 13px; + text-align: center; + } +} +// Simple 流程模型样式 +.simple-process-model-container { + width: 100%; + height: 100%; + padding-top: 32px; + overflow-x: auto; + background-color: #fafafa; + + .simple-process-model { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-width: fit-content; + background: url('./svg/simple-process-bg.svg') 0 0 repeat; + transform: scale(1); + transform-origin: 50% 0 0; + transition: transform 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); + // 节点容器 定义节点宽度 + .node-container { + width: 200px; + } + // 节点 + .node-box { + position: relative; + display: flex; + flex-direction: column; + min-height: 70px; + padding: 5px 10px 8px; + cursor: pointer; + background-color: #fff; + border: 2px solid transparent; + border-radius: 8px; + box-shadow: 0 1px 4px 0 rgb(10 30 65 / 16%); + transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1); + + &.status-pass { + background-color: #a9da90; + border-color: #67c23a; + } + + &.status-pass:hover { + border-color: #67c23a; + } + + &.status-running { + background-color: #e7f0fe; + border-color: #5a9cf8; + } + + &.status-running:hover { + border-color: #5a9cf8; + } + + &.status-reject { + background-color: #f6e5e5; + border-color: #e47470; + } + + &.status-reject:hover { + border-color: #e47470; + } + + &:hover { + border-color: #0089ff; + + .node-toolbar { + opacity: 1; + } + + .branch-node-move { + display: flex; + } + } + + // 普通节点标题 + .node-title-container { + display: flex; + align-items: center; + padding: 4px; + cursor: pointer; + border-radius: 4px 4px 0 0; + + .node-title-icon { + display: flex; + align-items: center; + + &.user-task { + color: #ff943e; + } + + &.copy-task { + color: #3296fa; + } + + &.start-user { + color: #676565; + } + + &.delay-node { + color: #e47470; + } + + &.trigger-node { + color: #3373d2; + } + + &.router-node { + color: #ca3a31; + } + + &.transactor-task { + color: #309; + } + + &.child-process { + color: #963; + } + + &.async-child-process { + color: #066; + } + } + + .node-title { + margin-left: 4px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 14px; + font-weight: 600; + line-height: 18px; + color: #1f1f1f; + white-space: nowrap; + + &:hover { + border-bottom: 1px dashed #f60; + } + } + } + + // 条件节点标题 + .branch-node-title-container { + display: flex; + align-items: center; + justify-content: space-between; + padding: 4px 0; + cursor: pointer; + border-radius: 4px 4px 0 0; + + .input-max-width { + max-width: 115px !important; + } + + .branch-title { + overflow: hidden; + text-overflow: ellipsis; + font-size: 13px; + font-weight: 600; + color: #f60; + white-space: nowrap; + + &:hover { + border-bottom: 1px dashed #000; + } + } + + .branch-priority { + min-width: 50px; + font-size: 12px; + } + } + + .node-content { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 32px; + padding: 4px 8px; + margin-top: 4px; + line-height: 32px; + color: #111f2c; + background: rgb(0 0 0 / 3%); + border-radius: 4px; + + .node-text { + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 2; /* 这将限制文本显示为两行 */ + font-size: 14px; + line-height: 24px; + word-break: break-all; + -webkit-box-orient: vertical; + } + } + + //条件节点内容 + .branch-node-content { + display: flex; + align-items: center; + min-height: 32px; + padding: 4px 0; + margin-top: 4px; + line-height: 32px; + color: #111f2c; + background: rgb(0 0 0 / 3%); + border-radius: 4px; + + .branch-node-text { + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 1; /* 这将限制文本显示为一行 */ + font-size: 12px; + line-height: 24px; + word-break: break-all; + -webkit-box-orient: vertical; + } + } + + // 节点操作 :删除 + .node-toolbar { + position: absolute; + top: -20px; + right: 0; + display: flex; + opacity: 0; + + .toolbar-icon { + vertical-align: middle; + text-align: center; + } + } + + // 条件节点左右移动 + .branch-node-move { + position: absolute; + display: none; + align-items: center; + justify-content: center; + width: 10px; + height: 100%; + cursor: pointer; + } + + .move-node-left { + top: 0; + left: -2px; + background: rgb(126 134 142 / 8%); + border-top-left-radius: 8px; + border-bottom-left-radius: 8px; + } + + .move-node-right { + top: 0; + right: -2px; + background: rgb(126 134 142 / 8%); + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + } + } + + .node-config-error { + border-color: #ff5219 !important; + } + // 普通节点包装 + .node-wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + // 节点连线处理 + .node-handler-wrapper { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 70px; + user-select: none; + + &::before { + position: absolute; + top: 0; + z-index: 0; + width: 2px; + height: 100%; + margin: auto; + content: ''; + background-color: #dedede; + } + + .node-handler { + .add-icon { + position: relative; + top: -5px; + display: flex; + align-items: center; + justify-content: center; + width: 25px; + height: 25px; + color: #fff; + cursor: pointer; + background-color: #0089ff; + border-radius: 50%; + + &:hover { + transform: scale(1.1); + } + } + } + + .node-handler-arrow { + position: absolute; + bottom: 0; + left: 50%; + display: flex; + transform: translateX(-50%); + } + } + + // 条件节点包装 + .branch-node-wrapper { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-top: 16px; + + .branch-node-container { + position: relative; + display: flex; + min-width: fit-content; + + &::before { + position: absolute; + left: 50%; + width: 4px; + height: 100%; + content: ''; + background-color: #fafafa; + transform: translate(-50%); + } + + .branch-node-add { + position: absolute; + top: -18px; + left: 50%; + z-index: 1; + display: flex; + align-items: center; + height: 36px; + padding: 0 10px; + font-size: 12px; + line-height: 36px; + border: 2px solid #dedede; + border-radius: 18px; + transform: translateX(-50%); + transform-origin: center center; + } + + .branch-node-readonly { + position: absolute; + top: -18px; + left: 50%; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + background-color: #fff; + border: 2px solid #dedede; + border-radius: 50%; + transform: translateX(-50%); + transform-origin: center center; + + &.status-pass { + background-color: #e9f4e2; + border-color: #6bb63c; + } + + &.status-pass:hover { + border-color: #6bb63c; + } + + .icon-size { + font-size: 22px; + + &.condition { + color: #67c23a; + } + + &.parallel { + color: #626aef; + } + + &.inclusive { + color: #345da2; + } + } + } + + .branch-node-item { + position: relative; + display: flex; + flex-shrink: 0; + flex-direction: column; + align-items: center; + min-width: 280px; + padding: 40px 40px 0; + background: transparent; + border-top: 2px solid #dedede; + border-bottom: 2px solid #dedede; + + &::before { + position: absolute; + inset: 0; + width: 2px; + height: 100%; + margin: auto; + content: ''; + background-color: #dedede; + } + } + // 覆盖条件节点第一个节点左上角的线 + .branch-line-first-top { + position: absolute; + top: -5px; + left: -1px; + width: 50%; + height: 7px; + content: ''; + background-color: #fafafa; + } + // 覆盖条件节点第一个节点左下角的线 + .branch-line-first-bottom { + position: absolute; + bottom: -5px; + left: -1px; + width: 50%; + height: 7px; + content: ''; + background-color: #fafafa; + } + // 覆盖条件节点最后一个节点右上角的线 + .branch-line-last-top { + position: absolute; + top: -5px; + right: -1px; + width: 50%; + height: 7px; + content: ''; + background-color: #fafafa; + } + // 覆盖条件节点最后一个节点右下角的线 + .branch-line-last-bottom { + position: absolute; + right: -1px; + bottom: -5px; + width: 50%; + height: 7px; + content: ''; + background-color: #fafafa; + } + } + } + + .node-fixed-name { + display: inline-block; + width: auto; + padding: 0 4px; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; + white-space: nowrap; + } + // 开始节点包装 + .start-node-wrapper { + position: relative; + margin-top: 16px; + + .start-node-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + .start-node-box { + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + width: 90px; + height: 36px; + padding: 3px 4px; + color: #212121; + cursor: pointer; + background: #fafafa; + border-radius: 30px; + box-shadow: 0 1px 5px 0 rgb(10 30 65 / 8%); + } + } + } + + // 结束节点包装 + .end-node-wrapper { + margin-bottom: 16px; + + .end-node-box { + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + width: 80px; + height: 36px; + color: #212121; + background-color: #fff; + border: 2px solid transparent; + border-radius: 30px; + box-shadow: 0 1px 5px 0 rgb(10 30 65 / 8%); + + &.status-pass { + background-color: #a9da90; + border-color: #6bb63c; + } + + &.status-pass:hover { + border-color: #6bb63c; + } + + &.status-reject { + background-color: #f6e5e5; + border-color: #e47470; + } + + &.status-reject:hover { + border-color: #e47470; + } + + &.status-cancel { + background-color: #eaeaeb; + border-color: #919398; + } + + &.status-cancel:hover { + border-color: #919398; + } + } + } + + // 可编辑的 title 输入框 + .editable-title-input { + max-width: 145px; + height: 20px; + margin-left: 4px; + font-size: 12px; + line-height: 20px; + border: 1px solid #d9d9d9; + border-radius: 4px; + transition: all 0.3s; + + &:focus { + outline: 0; + border-color: #40a9ff; + box-shadow: 0 0 0 2px rgb(24 144 255 / 20%); + } + } + } +} + +.iconfont { + font-family: iconfont !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-trigger::before { + content: '\e6d3'; +} + +.icon-router::before { + content: '\e6b2'; +} + +.icon-delay::before { + content: '\e600'; +} + +.icon-start-user::before { + content: '\e679'; +} + +.icon-inclusive::before { + content: '\e602'; +} + +.icon-copy::before { + content: '\e7eb'; +} + +.icon-transactor::before { + content: '\e61c'; +} + +.icon-exclusive::before { + content: '\e717'; +} + +.icon-approve::before { + content: '\e715'; +} + +.icon-parallel::before { + content: '\e688'; +} + +.icon-async-child-process::before { + content: '\e6f2'; +} + +.icon-child-process::before { + content: '\e6c1'; +} diff --git a/apps/web-ele/src/views/bpm/components/simple-process-design/styles/svg/simple-process-bg.svg b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/svg/simple-process-bg.svg new file mode 100644 index 0000000..eb23ab5 --- /dev/null +++ b/apps/web-ele/src/views/bpm/components/simple-process-design/styles/svg/simple-process-bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web-ele/src/views/bpm/form/data.ts b/apps/web-ele/src/views/bpm/form/data.ts new file mode 100644 index 0000000..315c41d --- /dev/null +++ b/apps/web-ele/src/views/bpm/form/data.ts @@ -0,0 +1,61 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '表单名称', + component: 'Input', + componentProps: { + placeholder: '请输入表单名称', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '表单名称', + minWidth: 200, + }, + { + field: 'status', + title: '状态', + minWidth: 200, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 240, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/form/designer/data.ts b/apps/web-ele/src/views/bpm/form/designer/data.ts new file mode 100644 index 0000000..20740cb --- /dev/null +++ b/apps/web-ele/src/views/bpm/form/designer/data.ts @@ -0,0 +1,46 @@ +import type { VbenFormSchema } from '#/adapter/form'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '表单名称', + component: 'Input', + componentProps: { + placeholder: '请输入表单名称', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/form/designer/index.vue b/apps/web-ele/src/views/bpm/form/designer/index.vue new file mode 100644 index 0000000..f3e03e0 --- /dev/null +++ b/apps/web-ele/src/views/bpm/form/designer/index.vue @@ -0,0 +1,154 @@ + + + diff --git a/apps/web-ele/src/views/bpm/form/designer/modules/form.vue b/apps/web-ele/src/views/bpm/form/designer/modules/form.vue new file mode 100644 index 0000000..9b95870 --- /dev/null +++ b/apps/web-ele/src/views/bpm/form/designer/modules/form.vue @@ -0,0 +1,111 @@ + + + diff --git a/apps/web-ele/src/views/bpm/form/index.vue b/apps/web-ele/src/views/bpm/form/index.vue new file mode 100644 index 0000000..c4e6125 --- /dev/null +++ b/apps/web-ele/src/views/bpm/form/index.vue @@ -0,0 +1,185 @@ + + + diff --git a/apps/web-ele/src/views/bpm/form/modules/detail.vue b/apps/web-ele/src/views/bpm/form/modules/detail.vue new file mode 100644 index 0000000..ad23d23 --- /dev/null +++ b/apps/web-ele/src/views/bpm/form/modules/detail.vue @@ -0,0 +1,49 @@ + + + diff --git a/apps/web-ele/src/views/bpm/group/data.ts b/apps/web-ele/src/views/bpm/group/data.ts new file mode 100644 index 0000000..4ecf77c --- /dev/null +++ b/apps/web-ele/src/views/bpm/group/data.ts @@ -0,0 +1,167 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemUserApi } from '#/api/system/user'; + +import { h } from 'vue'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { ElTag } from 'element-plus'; + +import { z } from '#/adapter/form'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 关联数据 */ +let userList: SystemUserApi.User[] = []; +getSimpleUserList().then((data) => (userList = data)); + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '组名', + component: 'Input', + componentProps: { + placeholder: '请输入组名', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入描述', + }, + }, + { + fieldName: 'userIds', + label: '成员', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择成员', + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + mode: 'tags', + }, + rules: z.array(z.number()).min(1, '请选择成员').default([]), + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '组名', + component: 'Input', + componentProps: { + placeholder: '请输入组名', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + placeholder: '请选择状态', + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '组名', + minWidth: 200, + }, + { + field: 'description', + title: '描述', + minWidth: 200, + }, + { + field: 'userIds', + title: '成员', + minWidth: 200, + slots: { + default: ({ row }) => { + const userIds = row.userIds || []; + return userIds.map((userId: number) => + h( + ElTag, + { + type: 'primary', + class: 'mr-1', + }, + () => userList.find((u) => u.id === userId)?.nickname, + ), + ); + }, + }, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/group/index.vue b/apps/web-ele/src/views/bpm/group/index.vue new file mode 100644 index 0000000..bc65d61 --- /dev/null +++ b/apps/web-ele/src/views/bpm/group/index.vue @@ -0,0 +1,129 @@ + + + diff --git a/apps/web-ele/src/views/bpm/group/modules/form.vue b/apps/web-ele/src/views/bpm/group/modules/form.vue new file mode 100644 index 0000000..c470eed --- /dev/null +++ b/apps/web-ele/src/views/bpm/group/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/bpm/model/data.ts b/apps/web-ele/src/views/bpm/model/data.ts new file mode 100644 index 0000000..59405e7 --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/data.ts @@ -0,0 +1,49 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { BpmModelApi } from '#/api/bpm/model'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '流程名称', + minWidth: 200, + slots: { default: 'name' }, + }, + { + field: 'startUserIds', + title: '可见范围', + minWidth: 150, + slots: { default: 'startUserIds' }, + }, + { + field: 'type', + title: '流程类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_MODEL_TYPE }, + }, + }, + { + field: 'formType', + title: '表单信息', + minWidth: 150, + slots: { default: 'formInfo' }, + }, + { + field: 'deploymentTime', + title: '最后发布', + minWidth: 280, + slots: { default: 'deploymentTime' }, + }, + { + title: '操作', + width: 150, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/model/definition/data.ts b/apps/web-ele/src/views/bpm/model/definition/data.ts new file mode 100644 index 0000000..c5dff80 --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/definition/data.ts @@ -0,0 +1,73 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '定义编号', + minWidth: 250, + }, + { + field: 'name', + title: '流程名称', + minWidth: 150, + }, + { + field: 'icon', + title: '流程图标', + minWidth: 100, + cellRender: { + name: 'CellImage', + props: { + width: 32, + height: 32, + class: 'rounded', + }, + }, + }, + { + field: 'startUsers', + title: '可见范围', + minWidth: 100, + slots: { default: 'startUsers' }, + }, + { + field: 'modelType', + title: '流程类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_MODEL_TYPE }, + }, + }, + { + field: 'formType', + title: '表单信息', + minWidth: 150, + slots: { default: 'formInfo' }, + }, + { + field: 'version', + title: '流程版本', + minWidth: 80, + cellRender: { + name: 'CellTag', + }, + }, + { + field: 'deploymentTime', + title: '部署时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/model/definition/index.vue b/apps/web-ele/src/views/bpm/model/definition/index.vue new file mode 100644 index 0000000..7934c76 --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/definition/index.vue @@ -0,0 +1,151 @@ + + + diff --git a/apps/web-ele/src/views/bpm/model/form/index.vue b/apps/web-ele/src/views/bpm/model/form/index.vue new file mode 100644 index 0000000..8c42659 --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/form/index.vue @@ -0,0 +1,498 @@ + + + diff --git a/apps/web-ele/src/views/bpm/model/form/modules/basic-info.vue b/apps/web-ele/src/views/bpm/model/form/modules/basic-info.vue new file mode 100644 index 0000000..8d1bdc7 --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/form/modules/basic-info.vue @@ -0,0 +1,450 @@ + + + diff --git a/apps/web-ele/src/views/bpm/model/form/modules/bpm-model-editor.vue b/apps/web-ele/src/views/bpm/model/form/modules/bpm-model-editor.vue new file mode 100644 index 0000000..4f756af --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/form/modules/bpm-model-editor.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/apps/web-ele/src/views/bpm/model/form/modules/custom-print-template.vue b/apps/web-ele/src/views/bpm/model/form/modules/custom-print-template.vue new file mode 100644 index 0000000..3a75ed6 --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/form/modules/custom-print-template.vue @@ -0,0 +1,117 @@ + + + diff --git a/apps/web-ele/src/views/bpm/model/form/modules/extra-setting.vue b/apps/web-ele/src/views/bpm/model/form/modules/extra-setting.vue new file mode 100644 index 0000000..7392293 --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/form/modules/extra-setting.vue @@ -0,0 +1,618 @@ + + diff --git a/apps/web-ele/src/views/bpm/model/form/modules/form-design.vue b/apps/web-ele/src/views/bpm/model/form/modules/form-design.vue new file mode 100644 index 0000000..534001d --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/form/modules/form-design.vue @@ -0,0 +1,184 @@ + + diff --git a/apps/web-ele/src/views/bpm/model/form/modules/process-design.vue b/apps/web-ele/src/views/bpm/model/form/modules/process-design.vue new file mode 100644 index 0000000..8f2495b --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/form/modules/process-design.vue @@ -0,0 +1,81 @@ + + diff --git a/apps/web-ele/src/views/bpm/model/form/modules/simple-model-design.vue b/apps/web-ele/src/views/bpm/model/form/modules/simple-model-design.vue new file mode 100644 index 0000000..ba8ae33 --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/form/modules/simple-model-design.vue @@ -0,0 +1,48 @@ + + diff --git a/apps/web-ele/src/views/bpm/model/form/modules/tinymce-plugin.ts b/apps/web-ele/src/views/bpm/model/form/modules/tinymce-plugin.ts new file mode 100644 index 0000000..145dc89 --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/form/modules/tinymce-plugin.ts @@ -0,0 +1,78 @@ +/** TinyMCE 自定义功能: + * - processrecord 按钮:插入流程记录占位元素 + * - @ 自动补全:插入 mention 占位元素 + */ + +// @ts-ignore TinyMCE 全局或通过打包器提供 +import type { Editor } from 'tinymce'; + +export interface MentionItem { + id: string; + name: string; +} + +/** 在编辑器 setup 回调中注册流程记录按钮和 @ 自动补全 */ +export function setupTinyPlugins( + editor: Editor, + getMentionList: () => MentionItem[], +) { + // 按钮:流程记录 + editor.ui.registry.addButton('processrecord', { + text: '流程记录', + tooltip: '插入流程记录占位', + onAction: () => { + // 流程记录占位显示, 仅用于显示。process-print.vue 组件中会替换掉 + editor.insertContent( + [ + '
    ', + '', + '', + '', + '', + '', + '', + '
    流程记录
    节点操作
    ', + '
    ', + ].join(''), + ); + }, + }); + + // @ 自动补全 + editor.ui.registry.addAutocompleter('bpmMention', { + trigger: '@', + minChars: 0, + columns: 1, + fetch: ( + pattern: string, + _maxResults: number, + _fetchOptions: Record, + ) => { + const list = getMentionList(); + const keyword = (pattern || '').toLowerCase().trim(); + const data = list + .filter((i) => i.name.toLowerCase().includes(keyword)) + .map((i) => ({ + value: i.id, + text: i.name, + })); + return Promise.resolve(data); + }, + onAction: ( + autocompleteApi: any, + rng: Range, + value: string, + _meta: Record, + ) => { + const list = getMentionList(); + const item = list.find((i) => i.id === value); + const name = item ? item.name : value; + const info = encodeURIComponent(JSON.stringify({ id: value })); + editor.selection.setRng(rng); + editor.insertContent( + `@${name}`, + ); + autocompleteApi.hide(); + }, + }); +} diff --git a/apps/web-ele/src/views/bpm/model/index.vue b/apps/web-ele/src/views/bpm/model/index.vue new file mode 100644 index 0000000..e66062b --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/index.vue @@ -0,0 +1,220 @@ + + + diff --git a/apps/web-ele/src/views/bpm/model/modules/category-draggable-model.vue b/apps/web-ele/src/views/bpm/model/modules/category-draggable-model.vue new file mode 100644 index 0000000..bb6fd00 --- /dev/null +++ b/apps/web-ele/src/views/bpm/model/modules/category-draggable-model.vue @@ -0,0 +1,750 @@ + + + + + diff --git a/apps/web-ele/src/views/bpm/processExpression/data.ts b/apps/web-ele/src/views/bpm/processExpression/data.ts new file mode 100644 index 0000000..76b16d2 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processExpression/data.ts @@ -0,0 +1,126 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'expression', + label: '表达式', + component: 'Textarea', + componentProps: { + placeholder: '请输入表达式', + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '名字', + minWidth: 200, + }, + + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'expression', + title: '表达式', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/processExpression/index.vue b/apps/web-ele/src/views/bpm/processExpression/index.vue new file mode 100644 index 0000000..658ea45 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processExpression/index.vue @@ -0,0 +1,135 @@ + + + diff --git a/apps/web-ele/src/views/bpm/processExpression/modules/form.vue b/apps/web-ele/src/views/bpm/processExpression/modules/form.vue new file mode 100644 index 0000000..9fb9814 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processExpression/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/bpm/processInstance/create/index.vue b/apps/web-ele/src/views/bpm/processInstance/create/index.vue new file mode 100644 index 0000000..df55077 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/create/index.vue @@ -0,0 +1,339 @@ + + + + + diff --git a/apps/web-ele/src/views/bpm/processInstance/create/modules/form.vue b/apps/web-ele/src/views/bpm/processInstance/create/modules/form.vue new file mode 100644 index 0000000..7ba63b5 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/create/modules/form.vue @@ -0,0 +1,372 @@ + + + + + diff --git a/apps/web-ele/src/views/bpm/processInstance/data.ts b/apps/web-ele/src/views/bpm/processInstance/data.ts new file mode 100644 index 0000000..883cc97 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/data.ts @@ -0,0 +1,122 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getCategorySimpleList } from '#/api/bpm/category'; +import { getSimpleProcessDefinitionList } from '#/api/bpm/definition'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '流程名称', + component: 'Input', + componentProps: { + placeholder: '请输入流程名称', + allowClear: true, + }, + }, + { + fieldName: 'processDefinitionId', + label: '所属流程', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择流程定义', + allowClear: true, + api: getSimpleProcessDefinitionList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'category', + label: '流程分类', + component: 'ApiSelect', + componentProps: { + placeholder: '请输入流程分类', + allowClear: true, + api: getCategorySimpleList, + labelField: 'name', + valueField: 'code', + }, + }, + { + fieldName: 'status', + label: '流程状态', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS, + 'number', + ), + placeholder: '请选择流程状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '发起时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '流程名称', + minWidth: 200, + fixed: 'left', + }, + { + field: 'summary', + title: '摘要', + minWidth: 200, + slots: { + default: 'slot-summary', + }, + }, + { + field: 'categoryName', + title: '流程分类', + minWidth: 120, + fixed: 'left', + }, + { + field: 'status', + title: '流程状态', + minWidth: 250, + slots: { + default: 'slot-status', + }, + }, + { + field: 'startTime', + title: '发起时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'endTime', + title: '结束时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/processInstance/detail/index.vue b/apps/web-ele/src/views/bpm/processInstance/detail/index.vue new file mode 100644 index 0000000..fad5ed6 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/detail/index.vue @@ -0,0 +1,423 @@ + + + + + diff --git a/apps/web-ele/src/views/bpm/processInstance/detail/modules/bpm-viewer.vue b/apps/web-ele/src/views/bpm/processInstance/detail/modules/bpm-viewer.vue new file mode 100644 index 0000000..2b9dfde --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/detail/modules/bpm-viewer.vue @@ -0,0 +1,59 @@ + + + diff --git a/apps/web-ele/src/views/bpm/processInstance/detail/modules/operation-button.vue b/apps/web-ele/src/views/bpm/processInstance/detail/modules/operation-button.vue new file mode 100644 index 0000000..19a5fd4 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/detail/modules/operation-button.vue @@ -0,0 +1,1424 @@ + + diff --git a/apps/web-ele/src/views/bpm/processInstance/detail/modules/process-print.vue b/apps/web-ele/src/views/bpm/processInstance/detail/modules/process-print.vue new file mode 100644 index 0000000..8569373 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/detail/modules/process-print.vue @@ -0,0 +1,299 @@ + + + diff --git a/apps/web-ele/src/views/bpm/processInstance/detail/modules/signature.vue b/apps/web-ele/src/views/bpm/processInstance/detail/modules/signature.vue new file mode 100644 index 0000000..a3f1b08 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/detail/modules/signature.vue @@ -0,0 +1,64 @@ + + + diff --git a/apps/web-ele/src/views/bpm/processInstance/detail/modules/simple-bpm-viewer.vue b/apps/web-ele/src/views/bpm/processInstance/detail/modules/simple-bpm-viewer.vue new file mode 100644 index 0000000..751f390 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/detail/modules/simple-bpm-viewer.vue @@ -0,0 +1,178 @@ + + diff --git a/apps/web-ele/src/views/bpm/processInstance/detail/modules/task-list.vue b/apps/web-ele/src/views/bpm/processInstance/detail/modules/task-list.vue new file mode 100644 index 0000000..5b64e38 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/detail/modules/task-list.vue @@ -0,0 +1,197 @@ + + + diff --git a/apps/web-ele/src/views/bpm/processInstance/detail/modules/time-line.vue b/apps/web-ele/src/views/bpm/processInstance/detail/modules/time-line.vue new file mode 100644 index 0000000..f085a63 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/detail/modules/time-line.vue @@ -0,0 +1,486 @@ + + + + diff --git a/apps/web-ele/src/views/bpm/processInstance/index.vue b/apps/web-ele/src/views/bpm/processInstance/index.vue new file mode 100644 index 0000000..52936bc --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/index.vue @@ -0,0 +1,205 @@ + + + diff --git a/apps/web-ele/src/views/bpm/processInstance/manager/data.ts b/apps/web-ele/src/views/bpm/processInstance/manager/data.ts new file mode 100644 index 0000000..a356cd0 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/manager/data.ts @@ -0,0 +1,156 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getCategorySimpleList } from '#/api/bpm/category'; +import { getSimpleProcessDefinitionList } from '#/api/bpm/definition'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'startUserId', + label: '发起人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择发起人', + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'name', + label: '流程名称', + component: 'Input', + componentProps: { + placeholder: '请输入流程名称', + allowClear: true, + }, + }, + { + fieldName: 'processDefinitionId', + label: '所属流程', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择流程定义', + allowClear: true, + api: getSimpleProcessDefinitionList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'category', + label: '流程分类', + component: 'ApiSelect', + componentProps: { + placeholder: '请输入流程分类', + allowClear: true, + api: getCategorySimpleList, + labelField: 'name', + valueField: 'code', + }, + }, + { + fieldName: 'status', + label: '流程状态', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS, + 'number', + ), + placeholder: '请选择流程状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '发起时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '流程编号', + minWidth: 320, + fixed: 'left', + }, + { + field: 'name', + title: '流程名称', + minWidth: 200, + fixed: 'left', + }, + { + field: 'categoryName', + title: '流程分类', + minWidth: 120, + fixed: 'left', + }, + { + field: 'startUser.nickname', + title: '流程发起人', + minWidth: 120, + }, + { + field: 'startUser.deptName', + title: '发起部门', + minWidth: 120, + }, + { + field: 'status', + title: '流程状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS }, + }, + }, + { + field: 'startTime', + title: '发起时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'endTime', + title: '结束时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'durationInMillis', + title: '流程耗时', + minWidth: 180, + formatter: 'formatPast2', + }, + { + field: 'tasks', + title: '当前审批任务', + minWidth: 320, + slots: { default: 'tasks' }, + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/processInstance/manager/index.vue b/apps/web-ele/src/views/bpm/processInstance/manager/index.vue new file mode 100644 index 0000000..0afcbc4 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/manager/index.vue @@ -0,0 +1,148 @@ + + + diff --git a/apps/web-ele/src/views/bpm/processInstance/report/data.ts b/apps/web-ele/src/views/bpm/processInstance/report/data.ts new file mode 100644 index 0000000..dc099b5 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/report/data.ts @@ -0,0 +1,157 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { + VxeGridPropTypes, + VxeTableGridOptions, +} from '#/adapter/vxe-table'; +import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +interface FormField { + field: string; + title: string; + type: string; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema( + formFields: FormField[] = [], +): VbenFormSchema[] { + // 基础搜索字段配置 + const baseFormSchema: VbenFormSchema[] = [ + { + fieldName: 'startUserId', + label: '发起人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择发起人', + allowClear: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'name', + label: '流程名称', + component: 'Input', + componentProps: { + placeholder: '请输入流程名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '流程状态', + component: 'Select', + componentProps: { + placeholder: '请选择流程状态', + allowClear: true, + options: getDictOptions( + DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS, + 'number', + ), + }, + }, + { + fieldName: 'createTime', + label: '发起时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'endTime', + label: '结束时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; + + // 动态表单字段配置:目前支持 input 和 textarea 类型 + const dynamicFormSchema: VbenFormSchema[] = formFields + .filter((item) => ['input', 'textarea'].includes(item.type)) + .map((item) => ({ + fieldName: `formFieldsParams.${item.field}`, + label: item.title, + component: 'Input', + componentProps: { + placeholder: `请输入${item.title}`, + allowClear: true, + }, + })); + + return [...baseFormSchema, ...dynamicFormSchema]; +} + +/** 列表的字段 */ +export function useGridColumns( + formFields: FormField[] = [], +): VxeTableGridOptions['columns'] { + // 基础列配置 + const baseColumns: VxeGridPropTypes.Columns = + [ + { + field: 'name', + title: '流程名称', + minWidth: 250, + fixed: 'left', + }, + { + field: 'startUser.nickname', + title: '流程发起人', + minWidth: 200, + }, + { + field: 'status', + title: '流程状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS }, + }, + }, + { + field: 'startTime', + title: '发起时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'endTime', + title: '结束时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + ]; + + // 动态表单字段列配置:根据表单字段生成对应的列,从 formVariables 中获取值 + const formFieldColumns = formFields.map((item) => ({ + field: `formVariables.${item.field}`, + title: item.title, + minWidth: 120, + formatter: ({ row }: any) => { + return row.formVariables?.[item.field] ?? ''; + }, + })); + + return [ + ...baseColumns, + ...formFieldColumns, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/processInstance/report/index.vue b/apps/web-ele/src/views/bpm/processInstance/report/index.vue new file mode 100644 index 0000000..5c8641a --- /dev/null +++ b/apps/web-ele/src/views/bpm/processInstance/report/index.vue @@ -0,0 +1,163 @@ + + + diff --git a/apps/web-ele/src/views/bpm/processListener/data.ts b/apps/web-ele/src/views/bpm/processListener/data.ts new file mode 100644 index 0000000..339f2b8 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processListener/data.ts @@ -0,0 +1,210 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +export const EVENT_EXECUTION_OPTIONS = [ + { + label: 'start', + value: 'start', + }, + { + label: 'end', + value: 'end', + }, +]; + +export const EVENT_OPTIONS = [ + { label: 'create', value: 'create' }, + { label: 'assignment', value: 'assignment' }, + { label: 'complete', value: 'complete' }, + { label: 'delete', value: 'delete' }, + { label: 'update', value: 'update' }, + { label: 'timeout', value: 'timeout' }, +]; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'type', + label: '类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.BPM_PROCESS_LISTENER_TYPE, 'string'), + placeholder: '请选择类型', + allowClear: true, + }, + rules: 'required', + }, + { + fieldName: 'event', + label: '事件', + component: 'Select', + componentProps: { + options: EVENT_OPTIONS, + placeholder: '请选择事件', + allowClear: true, + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + trigger: (values) => (values.event = undefined), + componentProps: (values) => ({ + options: + values.type === 'execution' + ? EVENT_EXECUTION_OPTIONS + : EVENT_OPTIONS, + }), + }, + }, + { + fieldName: 'valueType', + label: '值类型', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE, + 'string', + ), + placeholder: '请选择值类型', + allowClear: true, + }, + rules: 'required', + }, + { + fieldName: 'value', + label: '类路径|表达式', + component: 'Input', + rules: 'required', + dependencies: { + triggerFields: ['valueType'], + trigger: (values) => (values.value = undefined), + componentProps: (values) => ({ + placeholder: + values.valueType === 'class' ? '请输入类路径' : '请输入表达式', + }), + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + allowClear: true, + }, + }, + { + fieldName: 'type', + label: '类型', + component: 'Select', + componentProps: { + placeholder: '请选择类型', + options: getDictOptions(DICT_TYPE.BPM_PROCESS_LISTENER_TYPE, 'string'), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '名字', + minWidth: 200, + }, + { + field: 'type', + title: '类型', + minWidth: 200, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_PROCESS_LISTENER_TYPE }, + }, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'event', + title: '事件', + minWidth: 200, + }, + { + field: 'valueType', + title: '值类型', + minWidth: 200, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE }, + }, + }, + { + field: 'value', + title: '值', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/processListener/index.vue b/apps/web-ele/src/views/bpm/processListener/index.vue new file mode 100644 index 0000000..fee6371 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processListener/index.vue @@ -0,0 +1,135 @@ + + + diff --git a/apps/web-ele/src/views/bpm/processListener/modules/form.vue b/apps/web-ele/src/views/bpm/processListener/modules/form.vue new file mode 100644 index 0000000..0e9a926 --- /dev/null +++ b/apps/web-ele/src/views/bpm/processListener/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/bpm/task/copy/data.ts b/apps/web-ele/src/views/bpm/task/copy/data.ts new file mode 100644 index 0000000..202887d --- /dev/null +++ b/apps/web-ele/src/views/bpm/task/copy/data.ts @@ -0,0 +1,92 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '流程名称', + component: 'Input', + componentProps: { + placeholder: '请输入流程名称', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '抄送时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'processInstanceName', + title: '流程名称', + minWidth: 200, + }, + { + field: 'summary', + title: '摘要', + minWidth: 200, + formatter: ({ cellValue }) => { + return cellValue && cellValue.length > 0 + ? cellValue + .map((item: any) => `${item.key} : ${item.value}`) + .join('\n') + : '-'; + }, + }, + { + field: 'startUser.nickname', + title: '流程发起人', + minWidth: 120, + }, + { + field: 'processInstanceStartTime', + title: '流程发起时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'activityName', + title: '抄送节点', + minWidth: 120, + }, + { + field: 'createUser.nickname', + title: '抄送人', + minWidth: 120, + formatter: ({ cellValue }) => { + return cellValue || '-'; + }, + }, + { + field: 'reason', + title: '抄送意见', + minWidth: 180, + }, + { + field: 'createTime', + title: '抄送时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/task/copy/index.vue b/apps/web-ele/src/views/bpm/task/copy/index.vue new file mode 100644 index 0000000..21dcec3 --- /dev/null +++ b/apps/web-ele/src/views/bpm/task/copy/index.vue @@ -0,0 +1,85 @@ + + + diff --git a/apps/web-ele/src/views/bpm/task/done/data.ts b/apps/web-ele/src/views/bpm/task/done/data.ts new file mode 100644 index 0000000..3c5e9f6 --- /dev/null +++ b/apps/web-ele/src/views/bpm/task/done/data.ts @@ -0,0 +1,154 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getCategorySimpleList } from '#/api/bpm/category'; +import { getSimpleProcessDefinitionList } from '#/api/bpm/definition'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '任务名称', + component: 'Input', + componentProps: { + placeholder: '请输入任务名称', + allowClear: true, + }, + }, + { + fieldName: 'processDefinitionKey', + label: '所属流程', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择流程定义', + allowClear: true, + api: getSimpleProcessDefinitionList, + labelField: 'name', + valueField: 'key', + }, + }, + { + fieldName: 'category', + label: '流程分类', + component: 'ApiSelect', + componentProps: { + placeholder: '请输入流程分类', + allowClear: true, + api: getCategorySimpleList, + labelField: 'name', + valueField: 'code', + }, + }, + { + fieldName: 'status', + label: '审批状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.BPM_TASK_STATUS, 'number'), + placeholder: '请选择审批状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '发起时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'processInstance.name', + title: '流程', + minWidth: 200, + }, + { + field: 'processInstance.summary', + title: '摘要', + minWidth: 200, + formatter: ({ cellValue }) => { + return cellValue && cellValue.length > 0 + ? cellValue + .map((item: any) => `${item.key} : ${item.value}`) + .join('\n') + : '-'; + }, + }, + { + field: 'processInstance.startUser.nickname', + title: '发起人', + minWidth: 120, + }, + { + field: 'processInstance.createTime', + title: '发起时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'name', + title: '当前任务', + minWidth: 180, + }, + { + field: 'createTime', + title: '任务开始时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'endTime', + title: '任务结束时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'status', + title: '审批状态', + minWidth: 180, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_TASK_STATUS }, + }, + }, + { + field: 'reason', + title: '审批建议', + minWidth: 180, + }, + { + field: 'durationInMillis', + title: '耗时', + minWidth: 180, + formatter: 'formatPast2', + }, + { + field: 'processInstanceId', + title: '流程编号', + minWidth: 280, + }, + { + field: 'id', + title: '任务编号', + minWidth: 280, + }, + { + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/task/done/index.vue b/apps/web-ele/src/views/bpm/task/done/index.vue new file mode 100644 index 0000000..cc1aba7 --- /dev/null +++ b/apps/web-ele/src/views/bpm/task/done/index.vue @@ -0,0 +1,114 @@ + + + diff --git a/apps/web-ele/src/views/bpm/task/manager/data.ts b/apps/web-ele/src/views/bpm/task/manager/data.ts new file mode 100644 index 0000000..947982b --- /dev/null +++ b/apps/web-ele/src/views/bpm/task/manager/data.ts @@ -0,0 +1,104 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '任务名称', + component: 'Input', + componentProps: { + placeholder: '请输入任务名称', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'processInstance.name', + title: '流程', + minWidth: 200, + }, + { + field: 'processInstance.startUser.nickname', + title: '发起人', + minWidth: 120, + }, + { + field: 'name', + title: '当前任务', + minWidth: 180, + }, + { + field: 'createTime', + title: '任务开始时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'endTime', + title: '任务结束时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'assigneeUser.nickname', + title: '审批人', + minWidth: 180, + }, + { + field: 'status', + title: '审批状态', + minWidth: 180, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BPM_TASK_STATUS }, + }, + }, + { + field: 'reason', + title: '审批建议', + minWidth: 180, + }, + { + field: 'durationInMillis', + title: '耗时', + minWidth: 180, + formatter: 'formatPast2', + }, + { + field: 'processInstanceId', + title: '流程编号', + minWidth: 280, + }, + { + field: 'id', + title: '任务编号', + minWidth: 280, + }, + { + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/task/manager/index.vue b/apps/web-ele/src/views/bpm/task/manager/index.vue new file mode 100644 index 0000000..3757557 --- /dev/null +++ b/apps/web-ele/src/views/bpm/task/manager/index.vue @@ -0,0 +1,79 @@ + + + diff --git a/apps/web-ele/src/views/bpm/task/todo/data.ts b/apps/web-ele/src/views/bpm/task/todo/data.ts new file mode 100644 index 0000000..0b1efd7 --- /dev/null +++ b/apps/web-ele/src/views/bpm/task/todo/data.ts @@ -0,0 +1,131 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getCategorySimpleList } from '#/api/bpm/category'; +import { getSimpleProcessDefinitionList } from '#/api/bpm/definition'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '任务名称', + component: 'Input', + componentProps: { + placeholder: '请输入任务名称', + allowClear: true, + }, + }, + { + fieldName: 'processDefinitionKey', + label: '所属流程', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择流程定义', + allowClear: true, + api: getSimpleProcessDefinitionList, + labelField: 'name', + valueField: 'key', + }, + }, + { + fieldName: 'category', + label: '流程分类', + component: 'ApiSelect', + componentProps: { + placeholder: '请输入流程分类', + allowClear: true, + api: getCategorySimpleList, + labelField: 'name', + valueField: 'code', + }, + }, + { + fieldName: 'status', + label: '流程状态', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS, + 'number', + ), + placeholder: '请选择流程状态', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '发起时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'processInstance.name', + title: '流程', + minWidth: 200, + }, + { + field: 'processInstance.summary', + title: '摘要', + minWidth: 200, + formatter: ({ cellValue }) => { + return cellValue && cellValue.length > 0 + ? cellValue + .map((item: any) => `${item.key} : ${item.value}`) + .join('\n') + : '-'; + }, + }, + { + field: 'processInstance.startUser.nickname', + title: '发起人', + minWidth: 120, + }, + { + field: 'processInstance.createTime', + title: '发起时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'name', + title: '当前任务', + minWidth: 180, + }, + { + field: 'createTime', + title: '任务时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'processInstanceId', + title: '流程编号', + minWidth: 280, + }, + { + field: 'id', + title: '任务编号', + minWidth: 280, + }, + { + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/bpm/task/todo/index.vue b/apps/web-ele/src/views/bpm/task/todo/index.vue new file mode 100644 index 0000000..d808307 --- /dev/null +++ b/apps/web-ele/src/views/bpm/task/todo/index.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/crm/backlog/data.ts b/apps/web-ele/src/views/crm/backlog/data.ts new file mode 100644 index 0000000..d0b66d1 --- /dev/null +++ b/apps/web-ele/src/views/crm/backlog/data.ts @@ -0,0 +1,102 @@ +import type { Ref } from 'vue'; + +export interface LeftSideItem { + name: string; + menu: string; + count: Ref; +} + +/** 跟进状态 */ +export const FOLLOWUP_STATUS = [ + { label: '待跟进', value: false }, + { label: '已跟进', value: true }, +]; + +/** 归属范围 */ +export const SCENE_TYPES = [ + { label: '我负责的', value: 1 }, + { label: '我参与的', value: 2 }, + { label: '下属负责的', value: 3 }, +]; + +/** 联系状态 */ +export const CONTACT_STATUS = [ + { label: '今日需联系', value: 1 }, + { label: '已逾期', value: 2 }, + { label: '已联系', value: 3 }, +]; + +/** 审批状态 */ +export const AUDIT_STATUS = [ + { label: '待审批', value: 10 }, + { label: '审核通过', value: 20 }, + { label: '审核不通过', value: 30 }, +]; + +/** 回款提醒类型 */ +export const RECEIVABLE_REMIND_TYPE = [ + { label: '待回款', value: 1 }, + { label: '已逾期', value: 2 }, + { label: '已回款', value: 3 }, +]; + +/** 合同过期状态 */ +export const CONTRACT_EXPIRY_TYPE = [ + { label: '即将过期', value: 1 }, + { label: '已过期', value: 2 }, +]; + +/** 左侧菜单 */ +export const useLeftSides = ( + customerTodayContactCount: Ref, + clueFollowCount: Ref, + customerFollowCount: Ref, + customerPutPoolRemindCount: Ref, + contractAuditCount: Ref, + contractRemindCount: Ref, + receivableAuditCount: Ref, + receivablePlanRemindCount: Ref, +): LeftSideItem[] => { + return [ + { + name: '今日需联系客户', + menu: 'customerTodayContact', + count: customerTodayContactCount, + }, + { + name: '分配给我的线索', + menu: 'clueFollow', + count: clueFollowCount, + }, + { + name: '分配给我的客户', + menu: 'customerFollow', + count: customerFollowCount, + }, + { + name: '待进入公海的客户', + menu: 'customerPutPoolRemind', + count: customerPutPoolRemindCount, + }, + { + name: '待审核合同', + menu: 'contractAudit', + count: contractAuditCount, + }, + { + name: '待审核回款', + menu: 'receivableAudit', + count: receivableAuditCount, + }, + { + name: '待回款提醒', + menu: 'receivablePlanRemind', + count: receivablePlanRemindCount, + }, + { + name: '即将到期的合同', + menu: 'contractRemind', + count: contractRemindCount, + }, + ]; +}; diff --git a/apps/web-ele/src/views/crm/backlog/index.vue b/apps/web-ele/src/views/crm/backlog/index.vue new file mode 100644 index 0000000..fed2241 --- /dev/null +++ b/apps/web-ele/src/views/crm/backlog/index.vue @@ -0,0 +1,115 @@ + + diff --git a/apps/web-ele/src/views/crm/backlog/modules/clue-follow-list.vue b/apps/web-ele/src/views/crm/backlog/modules/clue-follow-list.vue new file mode 100644 index 0000000..bd072ba --- /dev/null +++ b/apps/web-ele/src/views/crm/backlog/modules/clue-follow-list.vue @@ -0,0 +1,79 @@ + + + + diff --git a/apps/web-ele/src/views/crm/backlog/modules/contract-audit-list.vue b/apps/web-ele/src/views/crm/backlog/modules/contract-audit-list.vue new file mode 100644 index 0000000..5e4631c --- /dev/null +++ b/apps/web-ele/src/views/crm/backlog/modules/contract-audit-list.vue @@ -0,0 +1,124 @@ + + + + diff --git a/apps/web-ele/src/views/crm/backlog/modules/contract-remind-list.vue b/apps/web-ele/src/views/crm/backlog/modules/contract-remind-list.vue new file mode 100644 index 0000000..e35662c --- /dev/null +++ b/apps/web-ele/src/views/crm/backlog/modules/contract-remind-list.vue @@ -0,0 +1,125 @@ + + + + diff --git a/apps/web-ele/src/views/crm/backlog/modules/customer-follow-list.vue b/apps/web-ele/src/views/crm/backlog/modules/customer-follow-list.vue new file mode 100644 index 0000000..09ac5de --- /dev/null +++ b/apps/web-ele/src/views/crm/backlog/modules/customer-follow-list.vue @@ -0,0 +1,79 @@ + + + + diff --git a/apps/web-ele/src/views/crm/backlog/modules/customer-put-pool-remind-list.vue b/apps/web-ele/src/views/crm/backlog/modules/customer-put-pool-remind-list.vue new file mode 100644 index 0000000..15ac870 --- /dev/null +++ b/apps/web-ele/src/views/crm/backlog/modules/customer-put-pool-remind-list.vue @@ -0,0 +1,79 @@ + + + + diff --git a/apps/web-ele/src/views/crm/backlog/modules/customer-today-contact-list.vue b/apps/web-ele/src/views/crm/backlog/modules/customer-today-contact-list.vue new file mode 100644 index 0000000..f1770d3 --- /dev/null +++ b/apps/web-ele/src/views/crm/backlog/modules/customer-today-contact-list.vue @@ -0,0 +1,89 @@ + + + + diff --git a/apps/web-ele/src/views/crm/backlog/modules/receivable-audit-list.vue b/apps/web-ele/src/views/crm/backlog/modules/receivable-audit-list.vue new file mode 100644 index 0000000..d8f8d16 --- /dev/null +++ b/apps/web-ele/src/views/crm/backlog/modules/receivable-audit-list.vue @@ -0,0 +1,106 @@ + + + + diff --git a/apps/web-ele/src/views/crm/backlog/modules/receivable-plan-remind-list.vue b/apps/web-ele/src/views/crm/backlog/modules/receivable-plan-remind-list.vue new file mode 100644 index 0000000..7561331 --- /dev/null +++ b/apps/web-ele/src/views/crm/backlog/modules/receivable-plan-remind-list.vue @@ -0,0 +1,104 @@ + + + + diff --git a/apps/web-ele/src/views/crm/business/components/data.ts b/apps/web-ele/src/views/crm/business/components/data.ts new file mode 100644 index 0000000..10ecb5a --- /dev/null +++ b/apps/web-ele/src/views/crm/business/components/data.ts @@ -0,0 +1,52 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +/** 商机关联列表列定义 */ +export function useBusinessDetailListColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'name', + title: '商机名称', + fixed: 'left', + slots: { default: 'name' }, + }, + { + field: 'customerName', + title: '客户名称', + fixed: 'left', + slots: { default: 'customerName' }, + }, + { + field: 'totalPrice', + title: '商机金额(元)', + formatter: 'formatAmount2', + }, + { + field: 'dealTime', + title: '预计成交日期', + formatter: 'formatDate', + }, + { + field: 'ownerUserName', + title: '负责人', + }, + { + field: 'ownerUserDeptName', + title: '所属部门', + }, + { + field: 'statusTypeName', + title: '商机状态组', + fixed: 'right', + }, + { + field: 'statusName', + title: '商机阶段', + fixed: 'right', + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/business/components/detail-list-modal.vue b/apps/web-ele/src/views/crm/business/components/detail-list-modal.vue new file mode 100644 index 0000000..9b3bfee --- /dev/null +++ b/apps/web-ele/src/views/crm/business/components/detail-list-modal.vue @@ -0,0 +1,149 @@ + + + + diff --git a/apps/web-ele/src/views/crm/business/components/detail-list.vue b/apps/web-ele/src/views/crm/business/components/detail-list.vue new file mode 100644 index 0000000..e57ac8f --- /dev/null +++ b/apps/web-ele/src/views/crm/business/components/detail-list.vue @@ -0,0 +1,215 @@ + + + + diff --git a/apps/web-ele/src/views/crm/business/components/index.ts b/apps/web-ele/src/views/crm/business/components/index.ts new file mode 100644 index 0000000..a63de47 --- /dev/null +++ b/apps/web-ele/src/views/crm/business/components/index.ts @@ -0,0 +1 @@ +export { default as BusinessDetailsList } from './detail-list.vue'; diff --git a/apps/web-ele/src/views/crm/business/data.ts b/apps/web-ele/src/views/crm/business/data.ts new file mode 100644 index 0000000..93bcf41 --- /dev/null +++ b/apps/web-ele/src/views/crm/business/data.ts @@ -0,0 +1,281 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { useUserStore } from '@vben/stores'; +import { erpPriceMultiply } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getBusinessStatusTypeSimpleList } from '#/api/crm/business/status'; +import { getCustomerSimpleList } from '#/api/crm/customer'; +import { getSimpleUserList } from '#/api/system/user'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + const userStore = useUserStore(); + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '商机名称', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入商机名称', + allowClear: true, + }, + }, + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择负责人', + allowClear: true, + }, + defaultValue: userStore.userInfo?.id, + rules: 'required', + }, + { + fieldName: 'customerId', + label: '客户名称', + component: 'ApiSelect', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + allowClear: true, + }, + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.customerDefault, + }, + rules: 'required', + }, + { + fieldName: 'contactId', + label: '合同名称', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'statusTypeId', + label: '商机状态组', + component: 'ApiSelect', + componentProps: { + api: getBusinessStatusTypeSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择商机状态组', + allowClear: true, + }, + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + rules: 'required', + }, + { + fieldName: 'dealTime', + label: '预计成交日期', + component: 'DatePicker', + componentProps: { + showTime: false, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + placeholder: '请选择预计成交日期', + class: '!w-full', + }, + }, + { + fieldName: 'product', + label: '产品清单', + component: 'Input', + formItemClass: 'col-span-3', + componentProps: { + placeholder: '请输入产品清单', + allowClear: true, + }, + }, + { + fieldName: 'totalProductPrice', + label: '产品总金额', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + disabled: true, + placeholder: '请输入产品总金额', + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional().default(0), + }, + { + fieldName: 'discountPercent', + label: '整单折扣(%)', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + placeholder: '请输入整单折扣', + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).max(100).optional().default(0), + }, + { + fieldName: 'totalPrice', + label: '折扣后金额', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + disabled: true, + placeholder: '请输入折扣后金额', + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['totalProductPrice', 'discountPercent'], + trigger(values, form) { + const discountPrice = + erpPriceMultiply( + values.totalProductPrice, + values.discountPercent / 100, + ) ?? 0; + form.setFieldValue( + 'totalPrice', + values.totalProductPrice - discountPrice, + ); + }, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '商机名称', + component: 'Input', + componentProps: { + placeholder: '请输入商机名称', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '商机名称', + fixed: 'left', + width: 160, + slots: { default: 'name' }, + }, + { + field: 'customerName', + title: '客户名称', + fixed: 'left', + width: 120, + slots: { default: 'customerName' }, + }, + { + field: 'totalPrice', + title: '商机金额(元)', + width: 140, + formatter: 'formatAmount2', + }, + { + field: 'dealTime', + title: '预计成交日期', + formatter: 'formatDate', + width: 180, + }, + { + field: 'remark', + title: '备注', + width: 200, + }, + { + field: 'contactNextTime', + title: '下次联系时间', + formatter: 'formatDateTime', + width: 180, + }, + { + field: 'ownerUserName', + title: '负责人', + width: 100, + }, + { + field: 'ownerUserDeptName', + title: '所属部门', + width: 100, + }, + { + field: 'contactLastTime', + title: '最后跟进时间', + formatter: 'formatDateTime', + width: 180, + }, + { + field: 'updateTime', + title: '更新时间', + formatter: 'formatDateTime', + width: 180, + }, + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + width: 180, + }, + { + field: 'creatorName', + title: '创建人', + width: 100, + }, + { + field: 'statusTypeName', + title: '商机状态组', + fixed: 'right', + width: 140, + }, + { + field: 'statusName', + title: '商机阶段', + fixed: 'right', + width: 120, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/business/detail/data.ts b/apps/web-ele/src/views/crm/business/detail/data.ts new file mode 100644 index 0000000..1cb1c49 --- /dev/null +++ b/apps/web-ele/src/views/crm/business/detail/data.ts @@ -0,0 +1,141 @@ +import type { Ref } from 'vue'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { CrmBusinessApi } from '#/api/crm/business'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; + +import { + DEFAULT_STATUSES, + getBusinessStatusSimpleList, +} from '#/api/crm/business/status'; + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '商机金额(元)', + render: (val) => erpPriceInputFormatter(val), + }, + { + field: 'statusTypeName', + label: '商机组', + }, + { + field: 'ownerUserName', + label: '负责人', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '商机名称', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '商机金额(元)', + render: (val) => erpPriceInputFormatter(val), + }, + { + field: 'dealTime', + label: '预计成交日期', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'contactNextTime', + label: '下次联系时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'statusTypeName', + label: '商机状态组', + }, + { + field: 'statusName', + label: '商机阶段', + }, + { + field: 'remark', + label: '备注', + }, + ]; +} + +/** 商机状态更新表单 */ +export function useStatusFormSchema( + formData: Ref, +): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'statusId', + label: '商机状态', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'endStatus', + label: '商机状态', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'status', + label: '商机阶段', + component: 'Select', + dependencies: { + triggerFields: [''], + async componentProps() { + const statusList = await getBusinessStatusSimpleList( + formData.value?.statusTypeId ?? 0, + ); + const statusOptions = statusList.map((item) => ({ + label: `${item.name}(赢单率:${item.percent}%)`, + value: item.id, + })); + const options = DEFAULT_STATUSES.map((item) => ({ + label: `${item.name}(赢单率:${item.percent}%)`, + value: item.endStatus, + })); + statusOptions.push(...options); + return { + options: statusOptions, + }; + }, + }, + rules: 'required', + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/business/detail/index.vue b/apps/web-ele/src/views/crm/business/detail/index.vue new file mode 100644 index 0000000..1a3fbb8 --- /dev/null +++ b/apps/web-ele/src/views/crm/business/detail/index.vue @@ -0,0 +1,191 @@ + + + diff --git a/apps/web-ele/src/views/crm/business/detail/modules/info.vue b/apps/web-ele/src/views/crm/business/detail/modules/info.vue new file mode 100644 index 0000000..7e8753b --- /dev/null +++ b/apps/web-ele/src/views/crm/business/detail/modules/info.vue @@ -0,0 +1,36 @@ + + + diff --git a/apps/web-ele/src/views/crm/business/detail/modules/status-form.vue b/apps/web-ele/src/views/crm/business/detail/modules/status-form.vue new file mode 100644 index 0000000..53df20a --- /dev/null +++ b/apps/web-ele/src/views/crm/business/detail/modules/status-form.vue @@ -0,0 +1,85 @@ + + + diff --git a/apps/web-ele/src/views/crm/business/index.vue b/apps/web-ele/src/views/crm/business/index.vue new file mode 100644 index 0000000..f5eae10 --- /dev/null +++ b/apps/web-ele/src/views/crm/business/index.vue @@ -0,0 +1,208 @@ + + + diff --git a/apps/web-ele/src/views/crm/business/modules/form.vue b/apps/web-ele/src/views/crm/business/modules/form.vue new file mode 100644 index 0000000..051cfe7 --- /dev/null +++ b/apps/web-ele/src/views/crm/business/modules/form.vue @@ -0,0 +1,121 @@ + + + diff --git a/apps/web-ele/src/views/crm/business/status/data.ts b/apps/web-ele/src/views/crm/business/status/data.ts new file mode 100644 index 0000000..86f20d8 --- /dev/null +++ b/apps/web-ele/src/views/crm/business/status/data.ts @@ -0,0 +1,112 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { handleTree } from '@vben/utils'; + +import { getDeptList } from '#/api/system/dept'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '状态组名', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入状态组名', + }, + }, + { + fieldName: 'deptIds', + label: '应用部门', + component: 'ApiTreeSelect', + componentProps: { + api: async () => { + const data = await getDeptList(); + return handleTree(data); + }, + multiple: true, + fieldNames: { label: 'name', value: 'id', children: 'children' }, + placeholder: '请选择应用部门', + treeDefaultExpandAll: true, + }, + help: '不选择部门时,默认全公司生效', + }, + { + fieldName: 'statuses', + label: '阶段设置', + component: 'Input', + rules: 'required', + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '状态组名', + }, + { + field: 'deptNames', + title: '应用部门', + formatter: ({ cellValue }) => + cellValue?.length > 0 ? cellValue.join(' ') : '全公司', + }, + { + field: 'creator', + title: '创建人', + }, + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 商机状态阶段列表列配置 */ +export function useFormColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'defaultStatus', + title: '阶段', + minWidth: 100, + slots: { default: 'defaultStatus' }, + }, + { + field: 'name', + title: '阶段名称', + minWidth: 100, + slots: { default: 'name' }, + }, + { + field: 'percent', + title: '赢单率(%)', + minWidth: 100, + slots: { default: 'percent' }, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/business/status/index.vue b/apps/web-ele/src/views/crm/business/status/index.vue new file mode 100644 index 0000000..f04101c --- /dev/null +++ b/apps/web-ele/src/views/crm/business/status/index.vue @@ -0,0 +1,136 @@ + + + diff --git a/apps/web-ele/src/views/crm/business/status/modules/form.vue b/apps/web-ele/src/views/crm/business/status/modules/form.vue new file mode 100644 index 0000000..3be655c --- /dev/null +++ b/apps/web-ele/src/views/crm/business/status/modules/form.vue @@ -0,0 +1,208 @@ + + + diff --git a/apps/web-ele/src/views/crm/clue/data.ts b/apps/web-ele/src/views/crm/clue/data.ts new file mode 100644 index 0000000..fccd2bf --- /dev/null +++ b/apps/web-ele/src/views/crm/clue/data.ts @@ -0,0 +1,327 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { useUserStore } from '@vben/stores'; + +import { getAreaTree } from '#/api/system/area'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + const userStore = useUserStore(); + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '线索名称', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入线索名称', + }, + }, + { + fieldName: 'source', + label: '客户来源', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE, 'number'), + placeholder: '请选择客户来源', + }, + rules: 'required', + }, + { + fieldName: 'mobile', + label: '手机', + component: 'Input', + componentProps: { + placeholder: '请输入手机号', + }, + }, + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + clearable: true, + placeholder: '请选择负责人', + }, + defaultValue: userStore.userInfo?.id, + rules: 'required', + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + }, + { + fieldName: 'telephone', + label: '电话', + component: 'Input', + componentProps: { + placeholder: '请输入电话', + }, + }, + { + fieldName: 'email', + label: '邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入邮箱', + }, + }, + { + fieldName: 'wechat', + label: '微信', + component: 'Input', + componentProps: { + placeholder: '请输入微信', + }, + }, + { + fieldName: 'qq', + label: 'QQ', + component: 'Input', + componentProps: { + placeholder: '请输入 QQ', + }, + }, + { + fieldName: 'industryId', + label: '客户行业', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, 'number'), + placeholder: '请选择客户行业', + }, + }, + { + fieldName: 'level', + label: '客户级别', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL, 'number'), + placeholder: '请选择客户级别', + }, + }, + { + fieldName: 'areaId', + label: '地址', + component: 'ApiTreeSelect', + componentProps: { + api: getAreaTree, + fieldNames: { label: 'name', value: 'id', children: 'children' }, + placeholder: '请选择地址', + }, + }, + { + fieldName: 'detailAddress', + label: '详细地址', + component: 'Input', + componentProps: { + placeholder: '请输入详细地址', + }, + }, + { + fieldName: 'contactNextTime', + label: '下次联系时间', + component: 'DatePicker', + componentProps: { + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + placeholder: '请选择下次联系时间', + class: '!w-full', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '线索名称', + component: 'Input', + componentProps: { + placeholder: '请输入线索名称', + clearable: true, + }, + }, + { + fieldName: 'transformStatus', + label: '转化状态', + component: 'RadioGroup', + componentProps: { + options: [ + { label: '未转化', value: false }, + { label: '已转化', value: true }, + ], + placeholder: '请选择转化状态', + clearable: true, + }, + defaultValue: false, + }, + { + fieldName: 'mobile', + label: '手机号', + component: 'Input', + componentProps: { + placeholder: '请输入手机号', + clearable: true, + }, + }, + { + fieldName: 'telephone', + label: '电话', + component: 'Input', + componentProps: { + placeholder: '请输入电话', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + placeholder: ['开始日期', '结束日期'], + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '线索名称', + fixed: 'left', + minWidth: 160, + slots: { + default: 'name', + }, + }, + { + field: 'source', + title: '线索来源', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_SOURCE }, + }, + }, + { + field: 'mobile', + title: '手机', + minWidth: 120, + }, + { + field: 'telephone', + title: '电话', + minWidth: 130, + }, + { + field: 'email', + title: '邮箱', + minWidth: 180, + }, + { + field: 'detailAddress', + title: '地址', + minWidth: 180, + }, + { + field: 'industryId', + title: '客户行业', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY }, + }, + }, + { + field: 'level', + title: '客户级别', + minWidth: 135, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_LEVEL }, + }, + }, + { + field: 'contactNextTime', + title: '下次联系时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'contactLastTime', + title: '最后跟进时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'contactLastContent', + title: '最后跟进记录', + minWidth: 200, + }, + { + field: 'ownerUserName', + title: '负责人', + minWidth: 100, + }, + { + field: 'ownerUserDeptName', + title: '所属部门', + minWidth: 100, + }, + { + field: 'updateTime', + title: '更新时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + title: '操作', + width: 140, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/clue/detail/data.ts b/apps/web-ele/src/views/crm/clue/detail/data.ts new file mode 100644 index 0000000..9536eb7 --- /dev/null +++ b/apps/web-ele/src/views/crm/clue/detail/data.ts @@ -0,0 +1,111 @@ +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; + +/** 详情头部的配置 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'source', + label: '线索来源', + render: (val) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_SOURCE, + value: val, + }), + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'ownerUserName', + label: '负责人', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + ]; +} + +/** 详情基本信息的配置 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '线索名称', + }, + { + field: 'source', + label: '客户来源', + render: (val) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_SOURCE, + value: val, + }), + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'telephone', + label: '电话', + }, + { + field: 'email', + label: '邮箱', + }, + { + field: 'areaName', + label: '地址', + render: (val, data) => { + const areaName = val ?? ''; + const detailAddress = data?.detailAddress ?? ''; + return [areaName, detailAddress].filter((item) => !!item).join(' '); + }, + }, + { + field: 'qq', + label: 'QQ', + }, + { + field: 'wechat', + label: '微信', + }, + { + field: 'industryId', + label: '客户行业', + render: (val) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY, + value: val, + }), + }, + { + field: 'level', + label: '客户级别', + render: (val) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_LEVEL, + value: val, + }), + }, + { + field: 'contactNextTime', + label: '下次联系时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'remark', + label: '备注', + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/clue/detail/index.vue b/apps/web-ele/src/views/crm/clue/detail/index.vue new file mode 100644 index 0000000..e7e6f8d --- /dev/null +++ b/apps/web-ele/src/views/crm/clue/detail/index.vue @@ -0,0 +1,174 @@ + + + diff --git a/apps/web-ele/src/views/crm/clue/detail/modules/info.vue b/apps/web-ele/src/views/crm/clue/detail/modules/info.vue new file mode 100644 index 0000000..676b005 --- /dev/null +++ b/apps/web-ele/src/views/crm/clue/detail/modules/info.vue @@ -0,0 +1,36 @@ + + + diff --git a/apps/web-ele/src/views/crm/clue/index.vue b/apps/web-ele/src/views/crm/clue/index.vue new file mode 100644 index 0000000..db011a9 --- /dev/null +++ b/apps/web-ele/src/views/crm/clue/index.vue @@ -0,0 +1,193 @@ + + + diff --git a/apps/web-ele/src/views/crm/clue/modules/form.vue b/apps/web-ele/src/views/crm/clue/modules/form.vue new file mode 100644 index 0000000..db3f753 --- /dev/null +++ b/apps/web-ele/src/views/crm/clue/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/crm/contact/components/data.ts b/apps/web-ele/src/views/crm/contact/components/data.ts new file mode 100644 index 0000000..b2e63f7 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/components/data.ts @@ -0,0 +1,62 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 联系人明细列表列配置 */ +export function useDetailListColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'name', + title: '姓名', + fixed: 'left', + slots: { default: 'name' }, + }, + { + field: 'customerName', + title: '客户名称', + fixed: 'left', + slots: { default: 'customerName' }, + }, + { + field: 'sex', + title: '性别', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_USER_SEX }, + }, + }, + { + field: 'mobile', + title: '手机', + }, + { + field: 'telephone', + title: '电话', + }, + { + field: 'email', + title: '邮箱', + }, + { + field: 'post', + title: '职位', + }, + { + field: 'detailAddress', + title: '地址', + }, + { + field: 'master', + title: '关键决策人', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contact/components/detail-list-modal.vue b/apps/web-ele/src/views/crm/contact/components/detail-list-modal.vue new file mode 100644 index 0000000..9d665ce --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/components/detail-list-modal.vue @@ -0,0 +1,148 @@ + + + + diff --git a/apps/web-ele/src/views/crm/contact/components/detail-list.vue b/apps/web-ele/src/views/crm/contact/components/detail-list.vue new file mode 100644 index 0000000..b49e768 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/components/detail-list.vue @@ -0,0 +1,210 @@ + + + + diff --git a/apps/web-ele/src/views/crm/contact/components/index.ts b/apps/web-ele/src/views/crm/contact/components/index.ts new file mode 100644 index 0000000..d16cd53 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/components/index.ts @@ -0,0 +1 @@ +export { default as ContactDetailsList } from './detail-list.vue'; diff --git a/apps/web-ele/src/views/crm/contact/data.ts b/apps/web-ele/src/views/crm/contact/data.ts new file mode 100644 index 0000000..c0d13b2 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/data.ts @@ -0,0 +1,366 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { useUserStore } from '@vben/stores'; + +import { getSimpleContactList } from '#/api/crm/contact'; +import { getCustomerSimpleList } from '#/api/crm/customer'; +import { getAreaTree } from '#/api/system/area'; +import { getSimpleUserList } from '#/api/system/user'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + const userStore = useUserStore(); + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '联系人姓名', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入联系人姓名', + }, + }, + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + rules: 'required', + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择负责人', + }, + defaultValue: userStore.userInfo?.id, + }, + { + fieldName: 'customerId', + label: '客户名称', + component: 'ApiSelect', + rules: 'required', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + }, + }, + { + fieldName: 'mobile', + label: '手机', + component: 'Input', + componentProps: { + placeholder: '请输入手机号', + }, + }, + { + fieldName: 'telephone', + label: '电话', + component: 'Input', + componentProps: { + placeholder: '请输入电话', + }, + }, + { + fieldName: 'email', + label: '邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入邮箱', + }, + }, + { + fieldName: 'wechat', + label: '微信', + component: 'Input', + componentProps: { + placeholder: '请输入微信', + }, + }, + { + fieldName: 'qq', + label: 'QQ', + component: 'Input', + componentProps: { + placeholder: '请输入QQ', + }, + }, + { + fieldName: 'post', + label: '职位', + component: 'Input', + componentProps: { + placeholder: '请输入职位', + }, + }, + { + fieldName: 'master', + label: '关键决策人', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + placeholder: '请选择是否关键决策人', + }, + defaultValue: false, + }, + { + fieldName: 'sex', + label: '性别', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + placeholder: '请选择性别', + }, + }, + { + fieldName: 'parentId', + label: '直属上级', + component: 'ApiSelect', + componentProps: { + api: getSimpleContactList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择直属上级', + }, + }, + { + fieldName: 'areaId', + label: '地址', + component: 'ApiTreeSelect', + componentProps: { + api: getAreaTree, + fieldNames: { label: 'name', value: 'id', children: 'children' }, + placeholder: '请选择地址', + }, + }, + { + fieldName: 'detailAddress', + label: '详细地址', + component: 'Input', + componentProps: { + placeholder: '请输入详细地址', + }, + }, + { + fieldName: 'contactNextTime', + label: '下次联系时间', + component: 'DatePicker', + componentProps: { + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + placeholder: '请选择下次联系时间', + class: '!w-full', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + }, + }, + { + fieldName: 'name', + label: '姓名', + component: 'Input', + componentProps: { + placeholder: '请输入联系人姓名', + allowClear: true, + }, + }, + { + fieldName: 'mobile', + label: '手机号', + component: 'Input', + componentProps: { + placeholder: '请输入手机号', + allowClear: true, + }, + }, + { + fieldName: 'telephone', + label: '电话', + component: 'Input', + componentProps: { + placeholder: '请输入电话', + allowClear: true, + }, + }, + { + fieldName: 'wechat', + label: '微信', + component: 'Input', + componentProps: { + placeholder: '请输入微信', + allowClear: true, + }, + }, + { + fieldName: 'email', + label: '电子邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入电子邮箱', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '联系人姓名', + fixed: 'left', + minWidth: 240, + slots: { default: 'name' }, + }, + { + field: 'customerName', + title: '客户名称', + fixed: 'left', + minWidth: 240, + slots: { default: 'customerName' }, + }, + { + field: 'mobile', + title: '手机', + minWidth: 120, + }, + { + field: 'telephone', + title: '电话', + minWidth: 130, + }, + { + field: 'email', + title: '邮箱', + minWidth: 180, + }, + { + field: 'post', + title: '职位', + minWidth: 120, + }, + { + field: 'areaName', + title: '地址', + minWidth: 120, + }, + { + field: 'detailAddress', + title: '详细地址', + minWidth: 180, + }, + { + field: 'master', + title: '关键决策人', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'parentId', + title: '直属上级', + minWidth: 120, + slots: { default: 'parentId' }, + }, + { + field: 'contactNextTime', + title: '下次联系时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'sex', + title: '性别', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_USER_SEX }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'contactLastTime', + title: '最后跟进时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'ownerUserName', + title: '负责人', + minWidth: 120, + }, + { + field: 'ownerUserDeptName', + title: '所属部门', + minWidth: 120, + }, + { + field: 'updateTime', + title: '更新时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contact/detail/data.ts b/apps/web-ele/src/views/crm/contact/detail/data.ts new file mode 100644 index 0000000..3f10193 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/detail/data.ts @@ -0,0 +1,106 @@ +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; + +/** 详情页的基础字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'post', + label: '职务', + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '姓名', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'telephone', + label: '电话', + }, + { + field: 'email', + label: '邮箱', + }, + { + field: 'qq', + label: 'QQ', + }, + { + field: 'wechat', + label: '微信', + }, + { + field: 'areaName', + label: '地址', + render: (val, data) => { + const areaName = val ?? ''; + const detailAddress = data?.detailAddress ?? ''; + return [areaName, detailAddress].filter((item) => !!item).join(' '); + }, + }, + { + field: 'post', + label: '职务', + }, + { + field: 'parentName', + label: '直属上级', + }, + { + field: 'master', + label: '关键决策人', + render: (val) => + h(DictTag, { + type: DICT_TYPE.INFRA_BOOLEAN_STRING, + value: val, + }), + }, + { + field: 'sex', + label: '性别', + render: (val) => + h(DictTag, { type: DICT_TYPE.SYSTEM_USER_SEX, value: val }), + }, + { + field: 'contactNextTime', + label: '下次联系时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'remark', + label: '备注', + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contact/detail/index.vue b/apps/web-ele/src/views/crm/contact/detail/index.vue new file mode 100644 index 0000000..4cf2286 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/detail/index.vue @@ -0,0 +1,158 @@ + + + diff --git a/apps/web-ele/src/views/crm/contact/detail/modules/info.vue b/apps/web-ele/src/views/crm/contact/detail/modules/info.vue new file mode 100644 index 0000000..0a56400 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/detail/modules/info.vue @@ -0,0 +1,36 @@ + + + diff --git a/apps/web-ele/src/views/crm/contact/index.vue b/apps/web-ele/src/views/crm/contact/index.vue new file mode 100644 index 0000000..7f1ea9e --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/index.vue @@ -0,0 +1,213 @@ + + + diff --git a/apps/web-ele/src/views/crm/contact/modules/form.vue b/apps/web-ele/src/views/crm/contact/modules/form.vue new file mode 100644 index 0000000..5b0b3c7 --- /dev/null +++ b/apps/web-ele/src/views/crm/contact/modules/form.vue @@ -0,0 +1,80 @@ + + + diff --git a/apps/web-ele/src/views/crm/contract/components/data.ts b/apps/web-ele/src/views/crm/contract/components/data.ts new file mode 100644 index 0000000..c314faa --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/components/data.ts @@ -0,0 +1,92 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { erpPriceInputFormatter } from '@vben/utils'; + +export function useDetailListColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '合同编号', + field: 'no', + minWidth: 150, + fixed: 'left', + }, + { + title: '合同名称', + field: 'name', + minWidth: 150, + fixed: 'left', + slots: { default: 'name' }, + }, + { + title: '合同金额(元)', + field: 'totalPrice', + minWidth: 150, + formatter: 'formatAmount2', + }, + { + title: '合同开始时间', + field: 'startTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '合同结束时间', + field: 'endTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '已回款金额(元)', + field: 'totalReceivablePrice', + minWidth: 150, + formatter: 'formatAmount2', + }, + { + title: '未回款金额(元)', + field: 'unpaidPrice', + minWidth: 150, + formatter: ({ row }) => { + return erpPriceInputFormatter( + row.totalPrice - row.totalReceivablePrice, + ); + }, + }, + { + title: '负责人', + field: 'ownerUserName', + minWidth: 150, + }, + { + title: '所属部门', + field: 'ownerUserDeptName', + minWidth: 150, + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '创建人', + field: 'creatorName', + minWidth: 150, + }, + { + title: '备注', + field: 'remark', + minWidth: 150, + }, + { + title: '合同状态', + field: 'auditStatus', + fixed: 'right', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contract/components/detail-list.vue b/apps/web-ele/src/views/crm/contract/components/detail-list.vue new file mode 100644 index 0000000..63dc35c --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/components/detail-list.vue @@ -0,0 +1,133 @@ + + + + diff --git a/apps/web-ele/src/views/crm/contract/components/index.ts b/apps/web-ele/src/views/crm/contract/components/index.ts new file mode 100644 index 0000000..d9f6179 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/components/index.ts @@ -0,0 +1 @@ +export { default as ContractDetailsList } from './detail-list.vue'; diff --git a/apps/web-ele/src/views/crm/contract/config/data.ts b/apps/web-ele/src/views/crm/contract/config/data.ts new file mode 100644 index 0000000..c45dc50 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/config/data.ts @@ -0,0 +1,40 @@ +import type { VbenFormSchema } from '#/adapter/form'; + +import { z } from '#/adapter/form'; + +export const schema: VbenFormSchema[] = [ + { + component: 'RadioGroup', + fieldName: 'notifyEnabled', + label: '提前提醒设置', + componentProps: { + options: [ + { label: '提醒', value: true }, + { label: '不提醒', value: false }, + ], + }, + defaultValue: true, + }, + { + component: 'Input', + fieldName: 'notifyDays', + componentProps: { + placeholder: '请输入天数', + class: '!w-full', + }, + renderComponentContent: () => ({ + prepend: () => '提前', + append: () => '天提醒', + }), + rules: z.coerce.number().int().min(0, '天数不能小于 0'), + dependencies: { + triggerFields: ['notifyEnabled'], + show: (values) => values.notifyEnabled, + trigger(values) { + if (!values.notifyEnabled) { + values.notifyDays = undefined; + } + }, + }, + }, +]; diff --git a/apps/web-ele/src/views/crm/contract/config/index.vue b/apps/web-ele/src/views/crm/contract/config/index.vue new file mode 100644 index 0000000..c3dbb08 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/config/index.vue @@ -0,0 +1,61 @@ + + + diff --git a/apps/web-ele/src/views/crm/contract/data.ts b/apps/web-ele/src/views/crm/contract/data.ts new file mode 100644 index 0000000..db2ee86 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/data.ts @@ -0,0 +1,421 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { useUserStore } from '@vben/stores'; +import { erpPriceInputFormatter, erpPriceMultiply } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getSimpleBusinessList } from '#/api/crm/business'; +import { getSimpleContactList } from '#/api/crm/contact'; +import { getCustomerSimpleList } from '#/api/crm/customer'; +import { getSimpleUserList } from '#/api/system/user'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + const userStore = useUserStore(); + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '合同编号', + component: 'Input', + componentProps: { + placeholder: '保存时自动生成', + disabled: true, + }, + }, + { + fieldName: 'name', + label: '合同名称', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入合同名称', + }, + }, + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + defaultValue: userStore.userInfo?.id, + rules: 'required', + }, + { + fieldName: 'customerId', + label: '客户名称', + component: 'ApiSelect', + rules: 'required', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + }, + }, + { + fieldName: 'businessId', + label: '商机名称', + component: 'Select', + componentProps: { + options: [], + placeholder: '请选择商机', + }, + dependencies: { + triggerFields: ['customerId'], + disabled: (values) => !values.customerId, + async componentProps(values) { + if (!values.customerId) { + return { + options: [], + placeholder: '请选择客户', + }; + } + const res = await getSimpleBusinessList(); + const list = res.filter( + (item) => item.customerId === values.customerId, + ); + return { + options: list.map((item) => ({ + label: item.name, + value: item.id, + })), + placeholder: '请选择商机', + }; + }, + }, + }, + { + fieldName: 'orderDate', + label: '下单日期', + component: 'DatePicker', + rules: 'required', + componentProps: { + format: 'YYYY-MM-DD', + valueFormat: 'x', + placeholder: '请选择下单日期', + class: '!w-full', + }, + }, + { + fieldName: 'startTime', + label: '合同开始时间', + component: 'DatePicker', + componentProps: { + format: 'YYYY-MM-DD', + valueFormat: 'x', + placeholder: '请选择合同开始时间', + class: '!w-full', + }, + }, + { + fieldName: 'endTime', + label: '合同结束时间', + component: 'DatePicker', + componentProps: { + format: 'YYYY-MM-DD', + valueFormat: 'x', + placeholder: '请选择合同结束时间', + class: '!w-full', + }, + }, + { + fieldName: 'signUserId', + label: '公司签约人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + defaultValue: userStore.userInfo?.id, + }, + { + fieldName: 'signContactId', + label: '客户签约人', + component: 'Select', + componentProps: { + options: [], + placeholder: '请选择客户签约人', + }, + dependencies: { + triggerFields: ['customerId'], + disabled: (values) => !values.customerId, + async componentProps(values) { + if (!values.customerId) { + return { + options: [], + placeholder: '请选择客户', + }; + } + const res = await getSimpleContactList(); + const list = res.filter( + (item) => item.customerId === values.customerId, + ); + return { + options: list.map((item) => ({ + label: item.name, + value: item.id, + })), + placeholder: '请选择客户签约人', + }; + }, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 4, + }, + }, + { + fieldName: 'product', + label: '产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'totalProductPrice', + label: '产品总金额', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + placeholder: '请输入产品总金额', + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional().default(0), + }, + { + fieldName: 'discountPercent', + label: '整单折扣(%)', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + placeholder: '请输入整单折扣', + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).max(100).optional().default(0), + }, + { + fieldName: 'totalPrice', + label: '折扣后金额', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['totalProductPrice', 'discountPercent'], + trigger(values, form) { + const discountPrice = + erpPriceMultiply( + values.totalProductPrice, + values.discountPercent / 100, + ) ?? 0; + form.setFieldValue( + 'totalPrice', + values.totalProductPrice - discountPrice, + ); + }, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '合同编号', + component: 'Input', + componentProps: { + placeholder: '请输入合同编号', + clearable: true, + }, + }, + { + fieldName: 'name', + label: '合同名称', + component: 'Input', + componentProps: { + placeholder: '请输入合同名称', + clearable: true, + }, + }, + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + clearable: true, + }, + }, + ]; +} + +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '合同编号', + field: 'no', + minWidth: 180, + fixed: 'left', + }, + { + title: '合同名称', + field: 'name', + minWidth: 160, + fixed: 'left', + slots: { default: 'name' }, + }, + { + title: '客户名称', + field: 'customerName', + minWidth: 120, + slots: { default: 'customerName' }, + }, + { + title: '商机名称', + field: 'businessName', + minWidth: 130, + slots: { default: 'businessName' }, + }, + { + title: '合同金额(元)', + field: 'totalPrice', + minWidth: 140, + formatter: 'formatAmount2', + }, + { + title: '下单时间', + field: 'orderDate', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '合同开始时间', + field: 'startTime', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '合同结束时间', + field: 'endTime', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '客户签约人', + field: 'signContactName', + minWidth: 130, + slots: { default: 'signContactName' }, + }, + { + title: '公司签约人', + field: 'signUserName', + minWidth: 130, + }, + { + title: '备注', + field: 'remark', + minWidth: 200, + }, + { + title: '已回款金额(元)', + field: 'totalReceivablePrice', + minWidth: 140, + formatter: 'formatAmount2', + }, + { + title: '未回款金额(元)', + field: 'unReceivablePrice', + minWidth: 140, + formatter: ({ row }) => { + return erpPriceInputFormatter( + row.totalPrice - row.totalReceivablePrice, + ); + }, + }, + { + title: '最后跟进时间', + field: 'contactLastTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '负责人', + field: 'ownerUserName', + minWidth: 120, + }, + { + title: '所属部门', + field: 'ownerUserDeptName', + minWidth: 100, + }, + { + title: '更新时间', + field: 'updateTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '创建人', + field: 'creatorName', + minWidth: 120, + }, + { + title: '合同状态', + field: 'auditStatus', + fixed: 'right', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, + }, + }, + { + title: '操作', + field: 'actions', + fixed: 'right', + minWidth: 130, + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contract/detail/data.ts b/apps/web-ele/src/views/crm/contract/detail/data.ts new file mode 100644 index 0000000..bfe7b56 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/detail/data.ts @@ -0,0 +1,100 @@ +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; + +/** 详情头部的配置 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '合同金额(元)', + render: (val) => erpPriceInputFormatter(val) as string, + }, + { + field: 'orderDate', + label: '下单时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'totalReceivablePrice', + label: '回款金额(元)', + render: (val) => erpPriceInputFormatter(val) as string, + }, + { + field: 'ownerUserName', + label: '负责人', + }, + ]; +} + +/** 详情基本信息的配置 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '合同编号', + }, + { + field: 'name', + label: '合同名称', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'businessName', + label: '商机名称', + }, + { + field: 'totalPrice', + label: '合同金额(元)', + render: (val) => erpPriceInputFormatter(val) as string, + }, + { + field: 'orderDate', + label: '下单时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'startTime', + label: '合同开始时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'endTime', + label: '合同结束时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'signContactName', + label: '客户签约人', + }, + { + field: 'signUserName', + label: '公司签约人', + }, + { + field: 'remark', + label: '备注', + }, + { + field: 'auditStatus', + label: '合同状态', + render: (val) => + h(DictTag, { + type: DICT_TYPE.CRM_AUDIT_STATUS, + value: val, + }), + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/contract/detail/index.vue b/apps/web-ele/src/views/crm/contract/detail/index.vue new file mode 100644 index 0000000..9581c81 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/detail/index.vue @@ -0,0 +1,170 @@ + + + diff --git a/apps/web-ele/src/views/crm/contract/detail/modules/info.vue b/apps/web-ele/src/views/crm/contract/detail/modules/info.vue new file mode 100644 index 0000000..31f1d8c --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/detail/modules/info.vue @@ -0,0 +1,36 @@ + + + diff --git a/apps/web-ele/src/views/crm/contract/index.vue b/apps/web-ele/src/views/crm/contract/index.vue new file mode 100644 index 0000000..50adfc1 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/index.vue @@ -0,0 +1,267 @@ + + + diff --git a/apps/web-ele/src/views/crm/contract/modules/form.vue b/apps/web-ele/src/views/crm/contract/modules/form.vue new file mode 100644 index 0000000..b390654 --- /dev/null +++ b/apps/web-ele/src/views/crm/contract/modules/form.vue @@ -0,0 +1,121 @@ + + + diff --git a/apps/web-ele/src/views/crm/customer/data.ts b/apps/web-ele/src/views/crm/customer/data.ts new file mode 100644 index 0000000..a3fdcf4 --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/data.ts @@ -0,0 +1,396 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { z } from '@vben/common-ui'; +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { useUserStore } from '@vben/stores'; + +import { getAreaTree } from '#/api/system/area'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + const userStore = useUserStore(); + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '客户名称', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入客户名称', + allowClear: true, + }, + }, + { + fieldName: 'source', + label: '客户来源', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE, 'number'), + placeholder: '请选择客户来源', + allowClear: true, + }, + }, + { + fieldName: 'mobile', + label: '手机', + component: 'Input', + componentProps: { + placeholder: '请输入手机', + allowClear: true, + }, + }, + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择负责人', + allowClear: true, + }, + defaultValue: userStore.userInfo?.id, + rules: 'required', + }, + { + fieldName: 'telephone', + label: '电话', + component: 'Input', + componentProps: { + placeholder: '请输入电话', + allowClear: true, + }, + }, + { + fieldName: 'email', + label: '邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入邮箱', + allowClear: true, + }, + }, + { + fieldName: 'wechat', + label: '微信', + component: 'Input', + componentProps: { + placeholder: '请输入微信', + allowClear: true, + }, + }, + { + fieldName: 'qq', + label: 'QQ', + component: 'Input', + componentProps: { + placeholder: '请输入QQ', + allowClear: true, + }, + }, + { + fieldName: 'industryId', + label: '客户行业', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, 'number'), + placeholder: '请选择客户行业', + allowClear: true, + }, + }, + { + fieldName: 'level', + label: '客户级别', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL, 'number'), + placeholder: '请选择客户级别', + allowClear: true, + }, + }, + { + fieldName: 'areaId', + label: '地址', + component: 'ApiTreeSelect', + componentProps: { + api: getAreaTree, + fieldNames: { label: 'name', value: 'id', children: 'children' }, + placeholder: '请选择地址', + allowClear: true, + }, + }, + { + fieldName: 'detailAddress', + label: '详细地址', + component: 'Input', + componentProps: { + placeholder: '请输入详细地址', + allowClear: true, + }, + }, + { + fieldName: 'contactNextTime', + label: '下次联系时间', + component: 'DatePicker', + componentProps: { + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + placeholder: '请选择下次联系时间', + allowClear: true, + class: '!w-full', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '客户名称', + component: 'Input', + componentProps: { + placeholder: '请输入客户名称', + allowClear: true, + }, + }, + { + fieldName: 'mobile', + label: '手机号', + component: 'Input', + componentProps: { + placeholder: '请输入手机号', + allowClear: true, + }, + }, + { + fieldName: 'telephone', + label: '电话', + component: 'Input', + componentProps: { + placeholder: '请输入电话', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + placeholder: ['开始日期', '结束日期'], + allowClear: true, + }, + }, + ]; +} + +/** 导入客户的表单 */ +export function useImportFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择负责人', + allowClear: true, + class: 'w-full', + }, + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + rules: 'required', + }, + { + fieldName: 'file', + label: '客户数据', + component: 'Upload', + rules: 'required', + help: '仅允许导入 xls、xlsx 格式文件', + }, + { + fieldName: 'updateSupport', + label: '是否覆盖', + component: 'Switch', + componentProps: { + activeValue: true, + inactiveValue: false, + }, + rules: z.boolean().default(false), + help: '是否更新已经存在的客户数据', + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '客户名称', + fixed: 'left', + minWidth: 160, + slots: { default: 'name' }, + }, + { + field: 'source', + title: '客户来源', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_SOURCE }, + }, + }, + { + field: 'mobile', + title: '手机', + minWidth: 120, + }, + { + field: 'telephone', + title: '电话', + minWidth: 130, + }, + { + field: 'email', + title: '邮箱', + minWidth: 180, + }, + { + field: 'level', + title: '客户级别', + minWidth: 135, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_LEVEL }, + }, + }, + { + field: 'industryId', + title: '客户行业', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY }, + }, + }, + { + field: 'contactNextTime', + title: '下次联系时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'lockStatus', + title: '锁定状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'dealStatus', + title: '成交状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'contactLastTime', + title: '最后跟进时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'contactLastContent', + title: '最后跟进记录', + minWidth: 200, + }, + { + field: 'detailAddress', + title: '地址', + minWidth: 180, + }, + { + field: 'poolDay', + title: '距离进入公海天数', + minWidth: 140, + formatter: ({ cellValue }) => + cellValue === null ? '-' : `${cellValue} 天`, + }, + { + field: 'ownerUserName', + title: '负责人', + minWidth: 100, + }, + { + field: 'ownerUserDeptName', + title: '所属部门', + minWidth: 100, + }, + { + field: 'updateTime', + title: '更新时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 100, + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/customer/detail/data.ts b/apps/web-ele/src/views/crm/customer/detail/data.ts new file mode 100644 index 0000000..9d55b40 --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/detail/data.ts @@ -0,0 +1,130 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { useUserStore } from '@vben/stores'; +import { formatDateTime } from '@vben/utils'; + +import { getSimpleUserList } from '#/api/system/user'; +import { DictTag } from '#/components/dict-tag'; + +/** 分配客户表单 */ +export function useDistributeFormSchema(): VbenFormSchema[] { + const userStore = useUserStore(); + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + defaultValue: userStore.userInfo?.id, + rules: 'required', + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'level', + label: '客户级别', + render: (val) => + h(DictTag, { type: DICT_TYPE.CRM_CUSTOMER_LEVEL, value: val }), + }, + { + field: 'dealStatus', + label: '成交状态', + render: (val) => (val ? '已成交' : '未成交'), + }, + { + field: 'ownerUserName', + label: '负责人', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '客户名称', + }, + { + field: 'source', + label: '客户来源', + render: (val) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_SOURCE, + value: val, + }), + }, + { + field: 'mobile', + label: '手机', + }, + { + field: 'telephone', + label: '电话', + }, + { + field: 'email', + label: '邮箱', + }, + { + field: 'areaName', + label: '地址', + render: (val, data) => { + const areaName = val ?? ''; + const detailAddress = data?.detailAddress ?? ''; + return [areaName, detailAddress].filter(Boolean).join(' '); + }, + }, + { + field: 'qq', + label: 'QQ', + }, + { + field: 'wechat', + label: '微信', + }, + { + field: 'industryId', + label: '客户行业', + render: (val) => + h(DictTag, { + type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY, + value: val, + }), + }, + { + field: 'contactNextTime', + label: '下次联系时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'remark', + label: '备注', + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/customer/detail/index.vue b/apps/web-ele/src/views/crm/customer/detail/index.vue new file mode 100644 index 0000000..77c0e1b --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/detail/index.vue @@ -0,0 +1,311 @@ + + + diff --git a/apps/web-ele/src/views/crm/customer/detail/modules/distribute-form.vue b/apps/web-ele/src/views/crm/customer/detail/modules/distribute-form.vue new file mode 100644 index 0000000..aa7ca2c --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/detail/modules/distribute-form.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web-ele/src/views/crm/customer/detail/modules/info.vue b/apps/web-ele/src/views/crm/customer/detail/modules/info.vue new file mode 100644 index 0000000..abeaa24 --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/detail/modules/info.vue @@ -0,0 +1,36 @@ + + + diff --git a/apps/web-ele/src/views/crm/customer/index.vue b/apps/web-ele/src/views/crm/customer/index.vue new file mode 100644 index 0000000..b3f67b0 --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/index.vue @@ -0,0 +1,217 @@ + + + diff --git a/apps/web-ele/src/views/crm/customer/limitConfig/data.ts b/apps/web-ele/src/views/crm/customer/limitConfig/data.ts new file mode 100644 index 0000000..3b05ebf --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/limitConfig/data.ts @@ -0,0 +1,156 @@ +import type { VbenFormSchema } from '@vben/common-ui'; + +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { handleTree } from '@vben/utils'; + +import { LimitConfType } from '#/api/crm/customer/limitConfig'; +import { getSimpleDeptList } from '#/api/system/dept'; +import { getSimpleUserList } from '#/api/system/user'; + +/** 新增/修改的表单 */ +export function useFormSchema(confType: LimitConfType): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'type', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'userIds', + label: '规则适用人群', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + mode: 'multiple', + allowClear: true, + placeholder: '请选择规则适用人群', + }, + }, + { + fieldName: 'deptIds', + label: '规则适用部门', + component: 'ApiTreeSelect', + componentProps: { + api: async () => { + const data = await getSimpleDeptList(); + return handleTree(data); + }, + multiple: true, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择规则适用部门', + treeDefaultExpandAll: true, + }, + }, + { + fieldName: 'maxCount', + label: + confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT + ? '拥有客户数上限' + : '锁定客户数上限', + component: 'InputNumber', + componentProps: { + placeholder: `请输入${ + LimitConfType.CUSTOMER_QUANTITY_LIMIT === confType + ? '拥有客户数上限' + : '锁定客户数上限' + }`, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'dealCountEnabled', + label: '成交客户是否占用拥有客户数', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + }, + dependencies: { + triggerFields: [''], + show: () => confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT, + }, + defaultValue: false, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + confType: LimitConfType, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + fixed: 'left', + }, + { + field: 'users', + title: '规则适用人群', + formatter: ({ cellValue }) => { + return cellValue + .map((user: any) => { + return user.nickname; + }) + .join(','); + }, + }, + { + field: 'depts', + title: '规则适用部门', + formatter: ({ cellValue }) => { + return cellValue + .map((dept: any) => { + return dept.name; + }) + .join(','); + }, + }, + { + field: 'maxCount', + title: + confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT + ? '拥有客户数上限' + : '锁定客户数上限', + }, + { + field: 'dealCountEnabled', + title: '成交客户是否占用拥有客户数', + visible: confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/customer/limitConfig/index.vue b/apps/web-ele/src/views/crm/customer/limitConfig/index.vue new file mode 100644 index 0000000..a4cedb1 --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/limitConfig/index.vue @@ -0,0 +1,172 @@ + + + diff --git a/apps/web-ele/src/views/crm/customer/limitConfig/modules/form.vue b/apps/web-ele/src/views/crm/customer/limitConfig/modules/form.vue new file mode 100644 index 0000000..ded5a12 --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/limitConfig/modules/form.vue @@ -0,0 +1,100 @@ + + + diff --git a/apps/web-ele/src/views/crm/customer/modules/form.vue b/apps/web-ele/src/views/crm/customer/modules/form.vue new file mode 100644 index 0000000..549adc9 --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/modules/form.vue @@ -0,0 +1,85 @@ + + + diff --git a/apps/web-ele/src/views/crm/customer/modules/import-form.vue b/apps/web-ele/src/views/crm/customer/modules/import-form.vue new file mode 100644 index 0000000..f85545d --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/modules/import-form.vue @@ -0,0 +1,86 @@ + + + diff --git a/apps/web-ele/src/views/crm/customer/pool/data.ts b/apps/web-ele/src/views/crm/customer/pool/data.ts new file mode 100644 index 0000000..d057b67 --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/pool/data.ts @@ -0,0 +1,161 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '客户名称', + component: 'Input', + componentProps: { + placeholder: '请输入客户名称', + allowClear: true, + }, + }, + { + fieldName: 'mobile', + label: '手机', + component: 'Input', + componentProps: { + placeholder: '请输入手机', + allowClear: true, + }, + }, + { + fieldName: 'industryId', + label: '所属行业', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY, 'number'), + placeholder: '请选择所属行业', + allowClear: true, + }, + }, + { + fieldName: 'level', + label: '客户级别', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL, 'number'), + placeholder: '请选择客户级别', + allowClear: true, + }, + }, + { + fieldName: 'source', + label: '客户来源', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE, 'number'), + placeholder: '请选择客户来源', + allowClear: true, + }, + }, + ]; +} + +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '客户名称', + field: 'name', + minWidth: 160, + fixed: 'left', + slots: { default: 'name' }, + }, + { + title: '客户来源', + field: 'source', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_SOURCE }, + }, + }, + { + title: '手机', + field: 'mobile', + minWidth: 120, + }, + { + title: '电话', + field: 'telephone', + minWidth: 120, + }, + { + title: '邮箱', + field: 'email', + minWidth: 140, + }, + { + title: '客户级别', + field: 'level', + minWidth: 135, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_LEVEL }, + }, + }, + { + title: '客户行业', + field: 'industryId', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY }, + }, + }, + { + title: '下次联系时间', + field: 'contactNextTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '备注', + field: 'remark', + minWidth: 200, + }, + { + title: '成交状态', + field: 'dealStatus', + minWidth: 80, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + title: '最后跟进时间', + field: 'contactLastTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '最后跟进记录', + field: 'contactLastContent', + minWidth: 200, + }, + { + title: '更新时间', + field: 'updateTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '创建人', + field: 'creatorName', + minWidth: 100, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/customer/pool/index.vue b/apps/web-ele/src/views/crm/customer/pool/index.vue new file mode 100644 index 0000000..abdc0d3 --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/pool/index.vue @@ -0,0 +1,97 @@ + + + diff --git a/apps/web-ele/src/views/crm/customer/poolConfig/data.ts b/apps/web-ele/src/views/crm/customer/poolConfig/data.ts new file mode 100644 index 0000000..5fa6e26 --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/poolConfig/data.ts @@ -0,0 +1,83 @@ +import type { VbenFormSchema } from '#/adapter/form'; + +import { z } from '#/adapter/form'; + +export const schema: VbenFormSchema[] = [ + { + component: 'RadioGroup', + fieldName: 'enabled', + label: '客户公海规则设置', + componentProps: { + options: [ + { label: '开启', value: true }, + { label: '关闭', value: false }, + ], + }, + }, + { + component: 'Input', + fieldName: 'contactExpireDays', + componentProps: { + placeholder: '请输入天数', + class: '!w-full', + }, + renderComponentContent: () => ({ + append: () => '天不跟进或', + }), + rules: z.coerce.number().int().min(0, '天数不能小于 0'), + dependencies: { + triggerFields: ['enabled'], + show: (value) => value.enabled, + }, + }, + { + component: 'Input', + fieldName: 'dealExpireDays', + componentProps: { + placeholder: '请输入天数', + class: '!w-full', + }, + renderComponentContent: () => ({ + prepend: () => '或', + append: () => '天未成交', + }), + rules: z.coerce.number().int().min(0, '天数不能小于 0'), + dependencies: { + triggerFields: ['enabled'], + show: (value) => value.enabled, + }, + }, + { + component: 'RadioGroup', + fieldName: 'notifyEnabled', + label: '提前提醒设置', + componentProps: { + options: [ + { label: '开启', value: true }, + { label: '关闭', value: false }, + ], + }, + dependencies: { + triggerFields: ['enabled'], + show: (value) => value.enabled, + }, + defaultValue: false, + }, + { + component: 'Input', + fieldName: 'notifyDays', + componentProps: { + placeholder: '请输入天数', + class: '!w-full', + }, + renderComponentContent: () => ({ + prepend: () => '提前', + append: () => '天提醒', + }), + rules: z.coerce.number().int().min(0, '天数不能小于 0'), + dependencies: { + triggerFields: ['notifyEnabled'], + show: (value) => value.enabled && value.notifyEnabled, + }, + }, +]; diff --git a/apps/web-ele/src/views/crm/customer/poolConfig/index.vue b/apps/web-ele/src/views/crm/customer/poolConfig/index.vue new file mode 100644 index 0000000..5a40003 --- /dev/null +++ b/apps/web-ele/src/views/crm/customer/poolConfig/index.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web-ele/src/views/crm/followup/data.ts b/apps/web-ele/src/views/crm/followup/data.ts new file mode 100644 index 0000000..514a2d9 --- /dev/null +++ b/apps/web-ele/src/views/crm/followup/data.ts @@ -0,0 +1,195 @@ +import type { Ref } from 'vue'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { getBusinessPageByCustomer } from '#/api/crm/business'; +import { getContactPageByCustomer } from '#/api/crm/contact'; +import { BizTypeEnum } from '#/api/crm/permission'; + +/** 新增/修改的表单 */ +export function useFormSchema( + bizId: Ref, +): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'bizId', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'bizType', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'type', + label: '跟进类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_FOLLOW_UP_TYPE, 'number'), + }, + rules: 'required', + }, + { + fieldName: 'nextTime', + label: '下次联系时间', + component: 'DatePicker', + componentProps: { + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + placeholder: '请选择下次联系时间', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'content', + label: '跟进内容', + component: 'Textarea', + rules: 'required', + }, + { + fieldName: 'picUrls', + label: '图片', + component: 'ImageUpload', + }, + { + fieldName: 'fileUrls', + label: '附件', + component: 'FileUpload', + }, + { + fieldName: 'contactIds', + label: '关联联系人', + component: 'ApiSelect', + componentProps: { + api: async () => { + if (!bizId.value) { + return []; + } + const res = await getContactPageByCustomer({ + pageNo: 1, + pageSize: 100, + customerId: bizId.value, + }); + return res.list; + }, + labelField: 'name', + valueField: 'id', + mode: 'multiple', + }, + }, + { + fieldName: 'businessIds', + label: '关联商机', + component: 'ApiSelect', + componentProps: { + api: async () => { + if (!bizId.value) { + return []; + } + const res = await getBusinessPageByCustomer({ + pageNo: 1, + pageSize: 100, + customerId: bizId.value, + }); + return res.list; + }, + labelField: 'name', + valueField: 'id', + mode: 'multiple', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + bizType: number, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + }, + { field: 'creatorName', title: '跟进人' }, + { + field: 'type', + title: '跟进类型', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_FOLLOW_UP_TYPE }, + }, + }, + { field: 'content', title: '跟进内容' }, + { + field: 'nextTime', + title: '下次联系时间', + formatter: 'formatDateTime', + }, + { + field: 'contacts', + title: '关联联系人', + visible: bizType === BizTypeEnum.CRM_CUSTOMER, + slots: { default: 'contacts' }, + }, + { + field: 'businesses', + title: '关联商机', + visible: bizType === BizTypeEnum.CRM_CUSTOMER, + slots: { default: 'businesses' }, + }, + { + field: 'actions', + title: '操作', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的系统字段 */ +export function useFollowUpDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'ownerUserName', + label: '负责人', + }, + { + field: 'contactLastContent', + label: '最后跟进记录', + }, + { + field: 'contactLastTime', + label: '最后跟进时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'creatorName', + label: '创建人', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'updateTime', + label: '更新时间', + render: (val) => formatDateTime(val) as string, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/followup/index.ts b/apps/web-ele/src/views/crm/followup/index.ts new file mode 100644 index 0000000..911cbed --- /dev/null +++ b/apps/web-ele/src/views/crm/followup/index.ts @@ -0,0 +1 @@ +export { default as FollowUp } from './index.vue'; diff --git a/apps/web-ele/src/views/crm/followup/index.vue b/apps/web-ele/src/views/crm/followup/index.vue new file mode 100644 index 0000000..03fea55 --- /dev/null +++ b/apps/web-ele/src/views/crm/followup/index.vue @@ -0,0 +1,165 @@ + + + diff --git a/apps/web-ele/src/views/crm/followup/modules/form.vue b/apps/web-ele/src/views/crm/followup/modules/form.vue new file mode 100644 index 0000000..51df040 --- /dev/null +++ b/apps/web-ele/src/views/crm/followup/modules/form.vue @@ -0,0 +1,81 @@ + + + diff --git a/apps/web-ele/src/views/crm/permission/index.ts b/apps/web-ele/src/views/crm/permission/index.ts new file mode 100644 index 0000000..2988df4 --- /dev/null +++ b/apps/web-ele/src/views/crm/permission/index.ts @@ -0,0 +1,2 @@ +export { default as PermissionList } from './modules/list.vue'; +export { default as TransferForm } from './modules/transfer-form.vue'; diff --git a/apps/web-ele/src/views/crm/permission/modules/data.ts b/apps/web-ele/src/views/crm/permission/modules/data.ts new file mode 100644 index 0000000..fdbe9bc --- /dev/null +++ b/apps/web-ele/src/views/crm/permission/modules/data.ts @@ -0,0 +1,233 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { BizTypeEnum, PermissionLevelEnum } from '#/api/crm/permission'; +import { getSimpleUserList } from '#/api/system/user'; + +/** 新增/修改的表单 */ +export function useTransferFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'newOwnerUserId', + label: '选择新负责人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + rules: 'required', + }, + { + fieldName: 'oldOwnerHandler', + label: '老负责人', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '加入团队', + value: true, + }, + { + label: '移除', + value: false, + }, + ], + }, + rules: 'required', + }, + { + fieldName: 'oldOwnerPermissionLevel', + label: '老负责人权限级别', + component: 'RadioGroup', + componentProps: { + options: getDictOptions( + DICT_TYPE.CRM_PERMISSION_LEVEL, + 'number', + ).filter((dict) => dict.value !== PermissionLevelEnum.OWNER), + }, + dependencies: { + triggerFields: ['oldOwnerHandler'], + show: (values) => values.oldOwnerHandler, + trigger(values) { + if (!values.oldOwnerHandler) { + values.oldOwnerPermissionLevel = undefined; + } + }, + }, + rules: 'required', + }, + { + fieldName: 'toBizTypes', + label: '同时转移', + component: 'CheckboxGroup', + componentProps: { + options: [ + { + label: '联系人', + value: BizTypeEnum.CRM_CONTACT, + }, + { + label: '商机', + value: BizTypeEnum.CRM_BUSINESS, + }, + { + label: '合同', + value: BizTypeEnum.CRM_CONTRACT, + }, + ], + }, + }, + ]; +} + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'bizId', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'ids', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'userId', + label: '选择人员', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + dependencies: { + triggerFields: ['ids'], + show: (values) => { + return values.ids === undefined; + }, + }, + }, + { + fieldName: 'level', + label: '权限级别', + component: 'RadioGroup', + componentProps: { + options: getDictOptions( + DICT_TYPE.CRM_PERMISSION_LEVEL, + 'number', + ).filter((dict) => dict.value !== PermissionLevelEnum.OWNER), + }, + rules: 'required', + }, + { + fieldName: 'bizType', + label: 'Crm 类型', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '联系人', + value: BizTypeEnum.CRM_CONTACT, + }, + { + label: '商机', + value: BizTypeEnum.CRM_BUSINESS, + }, + { + label: '合同', + value: BizTypeEnum.CRM_CONTRACT, + }, + ], + }, + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'toBizTypes', + label: '同时添加至', + component: 'CheckboxGroup', + componentProps: { + options: [ + { + label: '联系人', + value: BizTypeEnum.CRM_CONTACT, + }, + { + label: '商机', + value: BizTypeEnum.CRM_BUSINESS, + }, + { + label: '合同', + value: BizTypeEnum.CRM_CONTRACT, + }, + ], + }, + dependencies: { + triggerFields: ['ids', 'bizType'], + show: (values) => { + return ( + values.ids === undefined && + values.bizType === BizTypeEnum.CRM_CUSTOMER + ); + }, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + }, + { + field: 'nickname', + title: '姓名', + }, + { + field: 'deptName', + title: '部门', + }, + { + field: 'postNames', + title: '岗位', + }, + { + field: 'level', + title: '权限级别', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_PERMISSION_LEVEL }, + }, + }, + { + field: 'createTime', + title: '加入时间', + formatter: 'formatDateTime', + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/permission/modules/form.vue b/apps/web-ele/src/views/crm/permission/modules/form.vue new file mode 100644 index 0000000..949a77f --- /dev/null +++ b/apps/web-ele/src/views/crm/permission/modules/form.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/web-ele/src/views/crm/permission/modules/list.vue b/apps/web-ele/src/views/crm/permission/modules/list.vue new file mode 100644 index 0000000..957f0dd --- /dev/null +++ b/apps/web-ele/src/views/crm/permission/modules/list.vue @@ -0,0 +1,265 @@ + + + diff --git a/apps/web-ele/src/views/crm/permission/modules/transfer-form.vue b/apps/web-ele/src/views/crm/permission/modules/transfer-form.vue new file mode 100644 index 0000000..e823d48 --- /dev/null +++ b/apps/web-ele/src/views/crm/permission/modules/transfer-form.vue @@ -0,0 +1,120 @@ + + + diff --git a/apps/web-ele/src/views/crm/product/category/data.ts b/apps/web-ele/src/views/crm/product/category/data.ts new file mode 100644 index 0000000..beca873 --- /dev/null +++ b/apps/web-ele/src/views/crm/product/category/data.ts @@ -0,0 +1,97 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { CrmProductCategoryApi } from '#/api/crm/product/category'; + +import { handleTree } from '@vben/utils'; + +import { getProductCategoryList } from '#/api/crm/product/category'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'parentId', + label: '上级分类', + component: 'ApiTreeSelect', + componentProps: { + clearable: true, + api: async () => { + const data = await getProductCategoryList(); + data.unshift({ + id: 0, + name: '顶级分类', + } as CrmProductCategoryApi.ProductCategory); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择上级分类', + showSearch: true, + treeDefaultExpandAll: true, + }, + rules: 'selectRequired', + }, + { + fieldName: 'name', + label: '分类名称', + component: 'Input', + componentProps: { + placeholder: '请输入分类名称', + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '分类名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入分类名称', + }, + }, + ]; +} + +/** 表格列配置 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '分类名称', + treeNode: true, + }, + { + field: 'id', + title: '分类编号', + }, + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + }, + { + field: 'actions', + title: '操作', + width: 250, + fixed: 'right', + slots: { + default: 'actions', + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/product/category/index.vue b/apps/web-ele/src/views/crm/product/category/index.vue new file mode 100644 index 0000000..3aa4c03 --- /dev/null +++ b/apps/web-ele/src/views/crm/product/category/index.vue @@ -0,0 +1,168 @@ + + + diff --git a/apps/web-ele/src/views/crm/product/category/modules/form.vue b/apps/web-ele/src/views/crm/product/category/modules/form.vue new file mode 100644 index 0000000..329dcdf --- /dev/null +++ b/apps/web-ele/src/views/crm/product/category/modules/form.vue @@ -0,0 +1,94 @@ + + + diff --git a/apps/web-ele/src/views/crm/product/components/data.ts b/apps/web-ele/src/views/crm/product/components/data.ts new file mode 100644 index 0000000..266a912 --- /dev/null +++ b/apps/web-ele/src/views/crm/product/components/data.ts @@ -0,0 +1,111 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 产品详情列表的列定义 */ +export function useDetailListColumns( + showBusinessPrice: boolean, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'productName', + title: '产品名称', + }, + { + field: 'productNo', + title: '产品条码', + }, + { + field: 'productUnit', + title: '产品单位', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_PRODUCT_UNIT }, + }, + }, + { + field: 'productPrice', + title: '产品价格(元)', + formatter: 'formatAmount2', + }, + { + field: 'businessPrice', + title: '商机价格(元)', + formatter: 'formatAmount2', + visible: showBusinessPrice, + }, + { + field: 'contractPrice', + title: '合同价格(元)', + formatter: 'formatAmount2', + visible: !showBusinessPrice, + }, + { + field: 'count', + title: '数量', + formatter: 'formatAmount3', + }, + { + field: 'totalPrice', + title: '合计金额(元)', + formatter: 'formatAmount2', + }, + ]; +} + +/** 产品编辑表格的列定义 */ +export function useProductEditTableColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50 }, + { + field: 'productId', + title: '产品名称', + minWidth: 100, + slots: { default: 'productId' }, + }, + { + field: 'productNo', + title: '条码', + minWidth: 150, + }, + { + field: 'productUnit', + title: '单位', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_PRODUCT_UNIT }, + }, + }, + { + field: 'productPrice', + title: '价格(元)', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'sellingPrice', + title: '售价(元)', + minWidth: 100, + slots: { default: 'sellingPrice' }, + }, + { + field: 'count', + title: '数量', + minWidth: 100, + slots: { default: 'count' }, + }, + { + field: 'totalPrice', + title: '合计', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/product/components/detail-list.vue b/apps/web-ele/src/views/crm/product/components/detail-list.vue new file mode 100644 index 0000000..5ce23bc --- /dev/null +++ b/apps/web-ele/src/views/crm/product/components/detail-list.vue @@ -0,0 +1,79 @@ + + + + diff --git a/apps/web-ele/src/views/crm/product/components/edit-table.vue b/apps/web-ele/src/views/crm/product/components/edit-table.vue new file mode 100644 index 0000000..0be8808 --- /dev/null +++ b/apps/web-ele/src/views/crm/product/components/edit-table.vue @@ -0,0 +1,201 @@ + + + diff --git a/apps/web-ele/src/views/crm/product/components/index.ts b/apps/web-ele/src/views/crm/product/components/index.ts new file mode 100644 index 0000000..dc52792 --- /dev/null +++ b/apps/web-ele/src/views/crm/product/components/index.ts @@ -0,0 +1,2 @@ +export { default as ProductDetailsList } from './detail-list.vue'; +export { default as ProductEditTable } from './edit-table.vue'; diff --git a/apps/web-ele/src/views/crm/product/data.ts b/apps/web-ele/src/views/crm/product/data.ts new file mode 100644 index 0000000..ef6efe9 --- /dev/null +++ b/apps/web-ele/src/views/crm/product/data.ts @@ -0,0 +1,231 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { useUserStore } from '@vben/stores'; +import { handleTree } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getProductCategoryList } from '#/api/crm/product/category'; +import { getSimpleUserList } from '#/api/system/user'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + const userStore = useUserStore(); + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '产品名称', + rules: 'required', + componentProps: { + placeholder: '请输入产品名称', + clearable: true, + }, + }, + { + component: 'ApiSelect', + fieldName: 'ownerUserId', + label: '负责人', + rules: 'required', + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择负责人', + clearable: true, + }, + defaultValue: userStore.userInfo?.id, + }, + { + component: 'Input', + fieldName: 'no', + label: '产品编码', + rules: 'required', + componentProps: { + placeholder: '请输入产品编码', + clearable: true, + }, + }, + { + component: 'ApiTreeSelect', + fieldName: 'categoryId', + label: '产品类型', + rules: 'required', + componentProps: { + api: async () => { + const data = await getProductCategoryList(); + return handleTree(data); + }, + fieldNames: { label: 'name', value: 'id', children: 'children' }, + placeholder: '请选择产品类型', + clearable: true, + }, + }, + { + fieldName: 'unit', + label: '产品单位', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_PRODUCT_UNIT, 'number'), + placeholder: '请选择产品单位', + clearable: true, + }, + rules: 'required', + }, + { + component: 'InputNumber', + fieldName: 'price', + label: '价格(元)', + rules: 'required', + componentProps: { + min: 0, + precision: 2, + step: 0.1, + placeholder: '请输入产品价格', + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + component: 'Textarea', + fieldName: 'description', + label: '产品描述', + componentProps: { + placeholder: '请输入产品描述', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '上架状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_PRODUCT_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '产品名称', + component: 'Input', + componentProps: { + placeholder: '请输入产品名称', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '上架状态', + component: 'Select', + componentProps: { + clearable: true, + placeholder: '请选择上架状态', + options: getDictOptions(DICT_TYPE.CRM_PRODUCT_STATUS, 'number'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '产品编号', + visible: false, + }, + { + field: 'name', + title: '产品名称', + minWidth: 240, + slots: { default: 'name' }, + }, + { + field: 'categoryName', + title: '产品类型', + minWidth: 120, + }, + { + field: 'unit', + title: '产品单位', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_PRODUCT_UNIT }, + }, + }, + { + field: 'no', + title: '产品编码', + minWidth: 120, + }, + { + field: 'price', + title: '价格(元)', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'description', + title: '产品描述', + minWidth: 200, + }, + { + field: 'status', + title: '上架状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_PRODUCT_STATUS }, + }, + minWidth: 120, + }, + { + field: 'ownerUserName', + title: '负责人', + minWidth: 120, + }, + { + field: 'updateTime', + title: '更新时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/product/detail/data.ts b/apps/web-ele/src/views/crm/product/detail/data.ts new file mode 100644 index 0000000..0adf792 --- /dev/null +++ b/apps/web-ele/src/views/crm/product/detail/data.ts @@ -0,0 +1,72 @@ +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { erpPriceInputFormatter } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'categoryName', + label: '产品类别', + }, + { + field: 'unit', + label: '产品单位', + render: (val) => + h(DictTag, { type: DICT_TYPE.CRM_PRODUCT_UNIT, value: val }), + }, + { + field: 'price', + label: '产品价格(元)', + render: (val) => erpPriceInputFormatter(val), + }, + { + field: 'no', + label: '产品编码', + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'name', + label: '产品名称', + }, + { + field: 'no', + label: '产品编码', + }, + { + field: 'price', + label: '价格(元)', + render: (val) => erpPriceInputFormatter(val), + }, + { + field: 'description', + label: '产品描述', + }, + { + field: 'categoryName', + label: '产品类型', + }, + { + field: 'status', + label: '是否上下架', + render: (val) => + h(DictTag, { type: DICT_TYPE.CRM_PRODUCT_STATUS, value: val }), + }, + { + field: 'unit', + label: '产品单位', + render: (val) => + h(DictTag, { type: DICT_TYPE.CRM_PRODUCT_UNIT, value: val }), + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/product/detail/index.vue b/apps/web-ele/src/views/crm/product/detail/index.vue new file mode 100644 index 0000000..7886b6c --- /dev/null +++ b/apps/web-ele/src/views/crm/product/detail/index.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/crm/product/detail/modules/info.vue b/apps/web-ele/src/views/crm/product/detail/modules/info.vue new file mode 100644 index 0000000..813bad2 --- /dev/null +++ b/apps/web-ele/src/views/crm/product/detail/modules/info.vue @@ -0,0 +1,23 @@ + + + diff --git a/apps/web-ele/src/views/crm/product/index.vue b/apps/web-ele/src/views/crm/product/index.vue new file mode 100644 index 0000000..07ad1ca --- /dev/null +++ b/apps/web-ele/src/views/crm/product/index.vue @@ -0,0 +1,157 @@ + + + diff --git a/apps/web-ele/src/views/crm/product/modules/form.vue b/apps/web-ele/src/views/crm/product/modules/form.vue new file mode 100644 index 0000000..ef022e6 --- /dev/null +++ b/apps/web-ele/src/views/crm/product/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/crm/receivable/components/data.ts b/apps/web-ele/src/views/crm/receivable/components/data.ts new file mode 100644 index 0000000..4f3f0e1 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/components/data.ts @@ -0,0 +1,73 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 详情列表的字段 */ +export function useDetailListColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '回款编号', + field: 'no', + minWidth: 150, + fixed: 'left', + }, + { + title: '客户名称', + field: 'customerName', + minWidth: 150, + }, + { + title: '合同编号', + field: 'contract.no', + minWidth: 150, + }, + { + title: '回款日期', + field: 'returnTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '回款金额(元)', + field: 'price', + minWidth: 150, + formatter: 'formatAmount2', + }, + { + title: '回款方式', + field: 'returnType', + minWidth: 150, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE }, + }, + }, + { + title: '负责人', + field: 'ownerUserName', + minWidth: 150, + }, + { + title: '备注', + field: 'remark', + minWidth: 150, + }, + { + title: '回款状态', + field: 'auditStatus', + minWidth: 100, + fixed: 'right', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, + }, + }, + { + title: '操作', + field: 'actions', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/receivable/components/detail-list.vue b/apps/web-ele/src/views/crm/receivable/components/detail-list.vue new file mode 100644 index 0000000..fb2f935 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/components/detail-list.vue @@ -0,0 +1,144 @@ + + + + diff --git a/apps/web-ele/src/views/crm/receivable/components/index.ts b/apps/web-ele/src/views/crm/receivable/components/index.ts new file mode 100644 index 0000000..971ab65 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/components/index.ts @@ -0,0 +1 @@ +export { default as ReceivableDetailsList } from './detail-list.vue'; diff --git a/apps/web-ele/src/views/crm/receivable/data.ts b/apps/web-ele/src/views/crm/receivable/data.ts new file mode 100644 index 0000000..d81d147 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/data.ts @@ -0,0 +1,301 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { useUserStore } from '@vben/stores'; + +import { getContractSimpleList } from '#/api/crm/contract'; +import { getCustomerSimpleList } from '#/api/crm/customer'; +import { + getReceivablePlan, + getReceivablePlanSimpleList, +} from '#/api/crm/receivable/plan'; +import { getSimpleUserList } from '#/api/system/user'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + const userStore = useUserStore(); + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '回款编号', + component: 'Input', + componentProps: { + placeholder: '保存时自动生成', + disabled: true, + }, + }, + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + rules: 'required', + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择负责人', + allowClear: true, + }, + defaultValue: userStore.userInfo?.id, + }, + { + fieldName: 'customerId', + label: '客户名称', + component: 'ApiSelect', + rules: 'required', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + }, + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + }, + { + fieldName: 'contractId', + label: '合同名称', + component: 'Select', + rules: 'required', + dependencies: { + triggerFields: ['customerId'], + disabled: (values) => !values.customerId || values.id, + async componentProps(values) { + if (values.customerId) { + if (!values.id) { + // 特殊:只有在【新增】时,才清空合同编号 + values.contractId = undefined; + } + const contracts = await getContractSimpleList(values.customerId); + return { + options: contracts.map((item) => ({ + label: item.name, + value: item.id, + })), + placeholder: '请选择合同', + } as any; + } + }, + }, + }, + { + fieldName: 'planId', + label: '回款期数', + component: 'Select', + rules: 'required', + dependencies: { + triggerFields: ['contractId'], + disabled: (values) => !values.contractId, + async componentProps(values) { + if (values.contractId) { + values.planId = undefined; + const plans = await getReceivablePlanSimpleList( + values.customerId, + values.contractId, + ); + return { + options: plans.map((item) => ({ + label: item.period, + value: item.id, + })), + placeholder: '请选择回款期数', + onChange: async (value: any) => { + const plan = await getReceivablePlan(value); + values.returnTime = plan?.returnTime; + values.price = plan?.price; + values.returnType = plan?.returnType; + }, + } as any; + } + }, + }, + }, + { + fieldName: 'returnType', + label: '回款方式', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE, 'number'), + placeholder: '请选择回款方式', + }, + }, + { + fieldName: 'price', + label: '回款金额', + component: 'InputNumber', + rules: 'required', + componentProps: { + placeholder: '请输入回款金额', + min: 0, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'returnTime', + label: '回款日期', + component: 'DatePicker', + rules: 'required', + componentProps: { + placeholder: '请选择回款日期', + valueFormat: 'x', + format: 'YYYY-MM-DD', + class: '!w-full', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 4, + }, + formItemClass: 'md:col-span-2', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '回款编号', + component: 'Input', + componentProps: { + placeholder: '请输入回款编号', + allowClear: true, + }, + }, + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + allowClear: true, + }, + }, + ]; +} + +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '回款编号', + field: 'no', + minWidth: 160, + fixed: 'left', + slots: { default: 'no' }, + }, + { + title: '客户名称', + field: 'customerName', + minWidth: 150, + slots: { default: 'customerName' }, + }, + { + title: '合同编号', + field: 'contract', + minWidth: 160, + slots: { default: 'contractNo' }, + }, + { + title: '回款日期', + field: 'returnTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '回款金额(元)', + field: 'price', + minWidth: 150, + formatter: 'formatAmount2', + }, + { + title: '回款方式', + field: 'returnType', + minWidth: 150, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE }, + }, + }, + { + title: '备注', + field: 'remark', + minWidth: 150, + }, + { + title: '合同金额(元)', + field: 'contract.totalPrice', + minWidth: 150, + formatter: 'formatAmount2', + }, + { + title: '负责人', + field: 'ownerUserName', + minWidth: 150, + }, + { + title: '所属部门', + field: 'ownerUserDeptName', + minWidth: 150, + }, + { + title: '更新时间', + field: 'updateTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '创建人', + field: 'creatorName', + minWidth: 150, + }, + { + title: '回款状态', + field: 'auditStatus', + minWidth: 100, + fixed: 'right', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_AUDIT_STATUS }, + }, + }, + { + title: '操作', + field: 'actions', + minWidth: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/receivable/detail/data.ts b/apps/web-ele/src/views/crm/receivable/detail/data.ts new file mode 100644 index 0000000..dcceec5 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/detail/data.ts @@ -0,0 +1,105 @@ +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'totalPrice', + label: '合同金额(元)', + render: (val, data) => + erpPriceInputFormatter(val ?? data?.contract?.totalPrice ?? 0), + }, + { + field: 'returnTime', + label: '回款日期', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'price', + label: '回款金额(元)', + render: (val) => erpPriceInputFormatter(val), + }, + { + field: 'ownerUserName', + label: '负责人', + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '回款编号', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'contract', + label: '合同编号', + render: (val, data) => + val && data?.contract?.no ? data?.contract?.no : '', + }, + { + field: 'returnTime', + label: '回款日期', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'price', + label: '回款金额', + render: (val) => erpPriceInputFormatter(val), + }, + { + field: 'returnType', + label: '回款方式', + render: (val) => + h(DictTag, { + type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE, + value: val, + }), + }, + { + field: 'remark', + label: '备注', + }, + ]; +} + +/** 系统信息字段 */ +export function useDetailSystemSchema(): DescriptionItemSchema[] { + return [ + { + field: 'ownerUserName', + label: '负责人', + }, + { + field: 'creatorName', + label: '创建人', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'updateTime', + label: '更新时间', + render: (val) => formatDateTime(val) as string, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/receivable/detail/index.vue b/apps/web-ele/src/views/crm/receivable/detail/index.vue new file mode 100644 index 0000000..70579ef --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/detail/index.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/web-ele/src/views/crm/receivable/detail/modules/info.vue b/apps/web-ele/src/views/crm/receivable/detail/modules/info.vue new file mode 100644 index 0000000..f7d18e3 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/detail/modules/info.vue @@ -0,0 +1,35 @@ + + + diff --git a/apps/web-ele/src/views/crm/receivable/index.vue b/apps/web-ele/src/views/crm/receivable/index.vue new file mode 100644 index 0000000..a5a1604 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/index.vue @@ -0,0 +1,263 @@ + + + diff --git a/apps/web-ele/src/views/crm/receivable/modules/form.vue b/apps/web-ele/src/views/crm/receivable/modules/form.vue new file mode 100644 index 0000000..ada8f56 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/modules/form.vue @@ -0,0 +1,102 @@ + + + diff --git a/apps/web-ele/src/views/crm/receivable/plan/components/data.ts b/apps/web-ele/src/views/crm/receivable/plan/components/data.ts new file mode 100644 index 0000000..9a67c0b --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/plan/components/data.ts @@ -0,0 +1,62 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +/** 详情列表的字段 */ +export function useDetailListColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '客户名称', + field: 'customerName', + minWidth: 150, + }, + { + title: '合同编号', + field: 'contractNo', + minWidth: 150, + }, + { + title: '期数', + field: 'period', + minWidth: 150, + }, + { + title: '计划回款(元)', + field: 'price', + minWidth: 150, + formatter: 'formatAmount2', + }, + { + title: '计划回款日期', + field: 'returnTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '提前几天提醒', + field: 'remindDays', + minWidth: 150, + }, + { + title: '提醒日期', + field: 'remindTime', + minWidth: 150, + formatter: 'formatDateTime', + }, + { + title: '负责人', + field: 'ownerUserName', + minWidth: 150, + }, + { + title: '备注', + field: 'remark', + minWidth: 150, + }, + { + title: '操作', + field: 'actions', + width: 240, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/receivable/plan/components/detail-list.vue b/apps/web-ele/src/views/crm/receivable/plan/components/detail-list.vue new file mode 100644 index 0000000..1aa30b9 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/plan/components/detail-list.vue @@ -0,0 +1,147 @@ + + + + diff --git a/apps/web-ele/src/views/crm/receivable/plan/components/index.ts b/apps/web-ele/src/views/crm/receivable/plan/components/index.ts new file mode 100644 index 0000000..cbb4b12 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/plan/components/index.ts @@ -0,0 +1,2 @@ +export { default as ReceivablePlanDetailsInfo } from '../detail/modules/info.vue'; +export { default as ReceivablePlanDetailsList } from './detail-list.vue'; diff --git a/apps/web-ele/src/views/crm/receivable/plan/data.ts b/apps/web-ele/src/views/crm/receivable/plan/data.ts new file mode 100644 index 0000000..50b67f6 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/plan/data.ts @@ -0,0 +1,289 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { useUserStore } from '@vben/stores'; +import { erpPriceInputFormatter } from '@vben/utils'; + +import { getContractSimpleList } from '#/api/crm/contract'; +import { getCustomerSimpleList } from '#/api/crm/customer'; +import { getSimpleUserList } from '#/api/system/user'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + const userStore = useUserStore(); + return [ + { + fieldName: 'period', + label: '期数', + component: 'Input', + componentProps: { + placeholder: '保存时自动生成', + disabled: true, + }, + }, + { + fieldName: 'ownerUserId', + label: '负责人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + dependencies: { + triggerFields: ['id'], + disabled: (values) => values.id, + }, + defaultValue: userStore.userInfo?.id, + rules: 'required', + }, + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + rules: 'required', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + allowClear: true, + }, + }, + { + fieldName: 'contractId', + label: '合同', + component: 'Select', + rules: 'required', + componentProps: { + options: [], + placeholder: '请选择合同', + allowClear: true, + }, + dependencies: { + triggerFields: ['customerId'], + disabled: (values) => !values.customerId, + async componentProps(values) { + if (!values.customerId) { + return { + options: [], + placeholder: '请选择客户', + }; + } + const res = await getContractSimpleList(values.customerId); + return { + options: res.map((item) => ({ + label: item.name, + value: item.id, + })), + placeholder: '请选择合同', + onChange: (value: number) => { + const contract = res.find((item) => item.id === value); + if (contract) { + values.price = + contract.totalPrice - contract.totalReceivablePrice; + } + }, + }; + }, + }, + }, + { + fieldName: 'price', + label: '计划回款金额', + component: 'InputNumber', + rules: 'required', + componentProps: { + placeholder: '请输入计划回款金额', + min: 0, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'returnTime', + label: '计划回款日期', + component: 'DatePicker', + rules: 'required', + componentProps: { + placeholder: '请选择计划回款日期', + valueFormat: 'x', + format: 'YYYY-MM-DD', + class: '!w-full', + }, + defaultValue: new Date(), + }, + { + fieldName: 'remindDays', + label: '提前几天提醒', + component: 'InputNumber', + componentProps: { + placeholder: '请输入提前几天提醒', + min: 0, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'returnType', + label: '回款方式', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE, 'number'), + placeholder: '请选择回款方式', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 4, + }, + formItemClass: 'md:col-span-2', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择客户', + allowClear: true, + }, + }, + { + fieldName: 'contractNo', + label: '合同编号', + component: 'Input', + componentProps: { + placeholder: '请输入合同编号', + allowClear: true, + }, + }, + ]; +} + +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '客户名称', + field: 'customerName', + minWidth: 150, + fixed: 'left', + slots: { default: 'customerName' }, + }, + { + title: '合同编号', + field: 'contractNo', + minWidth: 200, + }, + { + title: '期数', + field: 'period', + minWidth: 150, + slots: { default: 'period' }, + }, + { + title: '计划回款金额(元)', + field: 'price', + minWidth: 160, + formatter: 'formatAmount2', + }, + { + title: '计划回款日期', + field: 'returnTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '提前几天提醒', + field: 'remindDays', + minWidth: 150, + }, + { + title: '提醒日期', + field: 'remindTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '回款方式', + field: 'returnType', + minWidth: 130, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE }, + }, + }, + { + title: '备注', + field: 'remark', + minWidth: 120, + }, + { + title: '负责人', + field: 'ownerUserName', + minWidth: 120, + }, + { + title: '实际回款金额(元)', + field: 'receivable.price', + minWidth: 160, + formatter: 'formatAmount2', + }, + { + title: '实际回款日期', + field: 'receivable.returnTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '未回款金额(元)', + field: 'unpaidPrice', + minWidth: 160, + formatter: ({ row }) => { + if (row.receivable) { + return erpPriceInputFormatter(row.price - row.receivable.price); + } + return erpPriceInputFormatter(row.price); + }, + }, + { + title: '更新时间', + field: 'updateTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '创建人', + field: 'creatorName', + minWidth: 100, + }, + { + title: '操作', + field: 'actions', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/receivable/plan/detail/data.ts b/apps/web-ele/src/views/crm/receivable/plan/detail/data.ts new file mode 100644 index 0000000..5059f02 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/plan/detail/data.ts @@ -0,0 +1,124 @@ +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'contractNo', + label: '合同编号', + }, + { + field: 'price', + label: '计划回款金额', + render: (val) => erpPriceInputFormatter(val), + }, + { + field: 'returnTime', + label: '计划回款日期', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'receivable', + label: '实际回款金额', + render: (val) => erpPriceInputFormatter(val?.price ?? 0), + }, + ]; +} + +/** 详情页的基础字段 */ +export function useDetailBaseSchema(): DescriptionItemSchema[] { + return [ + { + field: 'period', + label: '期数', + }, + { + field: 'customerName', + label: '客户名称', + }, + { + field: 'contractNo', + label: '合同编号', + }, + { + field: 'returnTime', + label: '计划回款日期', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'price', + label: '计划回款金额', + render: (val) => erpPriceInputFormatter(val), + }, + { + field: 'returnType', + label: '计划回款方式', + render: (val) => + h(DictTag, { + type: DICT_TYPE.CRM_RECEIVABLE_RETURN_TYPE, + value: val, + }), + }, + { + field: 'remindDays', + label: '提前几天提醒', + }, + { + field: 'receivable', + label: '实际回款金额', + render: (val) => erpPriceInputFormatter(val ?? 0), + }, + { + field: 'receivableRemain', + label: '未回款金额', + render: (val, data) => { + const paid = data?.receivable?.price ?? 0; + return erpPriceInputFormatter(Math.max(val - paid, 0)); + }, + }, + { + field: 'receivable.returnTime', + label: '实际回款日期', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'remark', + label: '备注', + }, + ]; +} + +/** 系统信息字段 */ +export function useDetailSystemSchema(): DescriptionItemSchema[] { + return [ + { + field: 'ownerUserName', + label: '负责人', + }, + { + field: 'creatorName', + label: '创建人', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'updateTime', + label: '更新时间', + render: (val) => formatDateTime(val) as string, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/receivable/plan/detail/index.vue b/apps/web-ele/src/views/crm/receivable/plan/detail/index.vue new file mode 100644 index 0000000..0a568de --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/plan/detail/index.vue @@ -0,0 +1,135 @@ + + + diff --git a/apps/web-ele/src/views/crm/receivable/plan/detail/modules/info.vue b/apps/web-ele/src/views/crm/receivable/plan/detail/modules/info.vue new file mode 100644 index 0000000..7aa6f8d --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/plan/detail/modules/info.vue @@ -0,0 +1,35 @@ + + + diff --git a/apps/web-ele/src/views/crm/receivable/plan/index.vue b/apps/web-ele/src/views/crm/receivable/plan/index.vue new file mode 100644 index 0000000..b662692 --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/plan/index.vue @@ -0,0 +1,228 @@ + + + diff --git a/apps/web-ele/src/views/crm/receivable/plan/modules/form.vue b/apps/web-ele/src/views/crm/receivable/plan/modules/form.vue new file mode 100644 index 0000000..7b6230f --- /dev/null +++ b/apps/web-ele/src/views/crm/receivable/plan/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/crm/statistics/customer/chartOptions.ts b/apps/web-ele/src/views/crm/statistics/customer/chartOptions.ts new file mode 100644 index 0000000..deecbd3 --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/customer/chartOptions.ts @@ -0,0 +1,530 @@ +import { DICT_TYPE } from '@vben/constants'; +import { getDictLabel } from '@vben/hooks'; + +export function getChartOptions(activeTabName: any, res: any): any { + switch (activeTabName) { + case 'conversionStat': { + return { + grid: { + left: 20, + right: 40, // 让 X 轴右侧显示完整 + bottom: 20, + containLabel: true, + }, + legend: {}, + series: [ + { + name: '客户转化率', + type: 'line', + data: res.map((item: any) => { + return { + name: item.time, + value: item.customerCreateCount + ? ( + (item.customerDealCount / item.customerCreateCount) * + 100 + ).toFixed(2) + : 0, + }; + }), + }, + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '客户转化率分析' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + yAxis: { + type: 'value', + name: '转化率(%)', + }, + xAxis: { + type: 'category', + name: '日期', + data: res.map((s: any) => s.time), + }, + }; + } + case 'customerSummary': { + return { + grid: { + bottom: '5%', + containLabel: true, + left: '5%', + right: '5%', + top: '5 %', + }, + legend: {}, + series: [ + { + name: '新增客户数', + type: 'bar', + yAxisIndex: 0, + data: res.map((item: any) => item.customerCreateCount), + }, + { + name: '成交客户数', + type: 'bar', + yAxisIndex: 1, + data: res.map((item: any) => item.customerDealCount), + }, + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '客户总量分析' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + yAxis: [ + { + type: 'value', + name: '新增客户数', + min: 0, + minInterval: 1, // 显示整数刻度 + }, + { + type: 'value', + name: '成交客户数', + min: 0, + minInterval: 1, // 显示整数刻度 + splitLine: { + lineStyle: { + type: 'dotted', // 右侧网格线虚化, 减少混乱 + opacity: 0.7, + }, + }, + }, + ], + xAxis: { + type: 'category', + name: '日期', + data: res.map((item: any) => item.time), + }, + }; + } + case 'dealCycleByArea': { + const data = res.map((s: any) => { + return { + areaName: s.areaName, + customerDealCycle: s.customerDealCycle, + customerDealCount: s.customerDealCount, + }; + }); + return { + grid: { + left: 20, + right: 40, // 让 X 轴右侧显示完整 + bottom: 20, + containLabel: true, + }, + legend: {}, + series: [ + { + name: '成交周期(天)', + type: 'bar', + data: data.map((s: any) => s.customerDealCycle), + yAxisIndex: 0, + }, + { + name: '成交客户数', + type: 'bar', + data: data.map((s: any) => s.customerDealCount), + yAxisIndex: 1, + }, + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '成交周期分析' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + yAxis: [ + { + type: 'value', + name: '成交周期(天)', + min: 0, + minInterval: 1, // 显示整数刻度 + }, + { + type: 'value', + name: '成交客户数', + min: 0, + minInterval: 1, // 显示整数刻度 + splitLine: { + lineStyle: { + type: 'dotted', // 右侧网格线虚化, 减少混乱 + opacity: 0.7, + }, + }, + }, + ], + xAxis: { + type: 'category', + name: '区域', + data: data.map((s: any) => s.areaName), + }, + }; + } + case 'dealCycleByProduct': { + const data = res.map((s: any) => { + return { + productName: s.productName ?? '未知', + customerDealCycle: s.customerDealCount, + customerDealCount: s.customerDealCount, + }; + }); + return { + grid: { + left: 20, + right: 40, // 让 X 轴右侧显示完整 + bottom: 20, + containLabel: true, + }, + legend: {}, + series: [ + { + name: '成交周期(天)', + type: 'bar', + data: data.map((s: any) => s.customerDealCycle), + yAxisIndex: 0, + }, + { + name: '成交客户数', + type: 'bar', + data: data.map((s: any) => s.customerDealCount), + yAxisIndex: 1, + }, + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '成交周期分析' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + yAxis: [ + { + type: 'value', + name: '成交周期(天)', + min: 0, + minInterval: 1, // 显示整数刻度 + }, + { + type: 'value', + name: '成交客户数', + min: 0, + minInterval: 1, // 显示整数刻度 + splitLine: { + lineStyle: { + type: 'dotted', // 右侧网格线虚化, 减少混乱 + opacity: 0.7, + }, + }, + }, + ], + xAxis: { + type: 'category', + name: '产品名称', + data: data.map((s: any) => s.productName), + }, + }; + } + case 'dealCycleByUser': { + const customerDealCycleByDate = res.customerDealCycleByDate; + const customerDealCycleByUser = res.customerDealCycleByUser; + return { + grid: { + left: 20, + right: 40, // 让 X 轴右侧显示完整 + bottom: 20, + containLabel: true, + }, + legend: {}, + series: [ + { + name: '成交周期(天)', + type: 'bar', + data: customerDealCycleByDate.map((s: any) => s.customerDealCycle), + yAxisIndex: 0, + }, + { + name: '成交客户数', + type: 'bar', + data: customerDealCycleByUser.map((s: any) => s.customerDealCount), + yAxisIndex: 1, + }, + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '成交周期分析' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + yAxis: [ + { + type: 'value', + name: '成交周期(天)', + min: 0, + minInterval: 1, // 显示整数刻度 + }, + { + type: 'value', + name: '成交客户数', + min: 0, + minInterval: 1, // 显示整数刻度 + splitLine: { + lineStyle: { + type: 'dotted', // 右侧网格线虚化, 减少混乱 + opacity: 0.7, + }, + }, + }, + ], + xAxis: { + type: 'category', + name: '日期', + data: customerDealCycleByDate.map((s: any) => s.time), + }, + }; + } + case 'followUpSummary': { + return { + grid: { + left: 20, + right: 30, // 让 X 轴右侧显示完整 + bottom: 20, + containLabel: true, + }, + legend: {}, + series: [ + { + name: '跟进客户数', + type: 'bar', + yAxisIndex: 0, + data: res.map((s: any) => s.followUpCustomerCount), + }, + { + name: '跟进次数', + type: 'bar', + yAxisIndex: 1, + data: res.map((s: any) => s.followUpRecordCount), + }, + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '客户跟进次数分析' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + yAxis: [ + { + type: 'value', + name: '跟进客户数', + min: 0, + minInterval: 1, // 显示整数刻度 + }, + { + type: 'value', + name: '跟进次数', + min: 0, + minInterval: 1, // 显示整数刻度 + splitLine: { + lineStyle: { + type: 'dotted', // 右侧网格线虚化, 减少混乱 + opacity: 0.7, + }, + }, + }, + ], + xAxis: { + type: 'category', + name: '日期', + axisTick: { + alignWithLabel: true, + }, + data: res.map((s: any) => s.time), + }, + }; + } + case 'followUpType': { + return { + title: { + text: '客户跟进方式分析', + left: 'center', + }, + legend: { + orient: 'vertical', + left: 'left', + }, + tooltip: { + trigger: 'item', + formatter: '{b} : {c}% ', + }, + toolbox: { + feature: { + saveAsImage: { show: true, name: '客户跟进方式分析' }, // 保存为图片 + }, + }, + series: [ + { + name: '跟进方式', + type: 'pie', + radius: '50%', + data: res.map((s: any) => { + return { + name: getDictLabel( + DICT_TYPE.CRM_FOLLOW_UP_TYPE, + s.followUpType, + ), + value: s.followUpRecordCount, + }; + }), + emphasis: { + itemStyle: { + shadowBlur: 10, + shadowOffsetX: 0, + shadowColor: 'rgba(0, 0, 0, 0.5)', + }, + }, + }, + ], + }; + } + case 'poolSummary': { + return { + grid: { + left: 20, + right: 40, // 让 X 轴右侧显示完整 + bottom: 20, + containLabel: true, + }, + legend: {}, + series: [ + { + name: '进入公海客户数', + type: 'bar', + yAxisIndex: 0, + data: res.map((s: any) => s.customerPutCount), + }, + { + name: '公海领取客户数', + type: 'bar', + yAxisIndex: 1, + data: res.map((s: any) => s.customerTakeCount), + }, + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '公海客户分析' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + yAxis: [ + { + type: 'value', + name: '进入公海客户数', + min: 0, + minInterval: 1, // 显示整数刻度 + }, + { + type: 'value', + name: '公海领取客户数', + min: 0, + minInterval: 1, // 显示整数刻度 + splitLine: { + lineStyle: { + type: 'dotted', // 右侧网格线虚化, 减少混乱 + opacity: 0.7, + }, + }, + }, + ], + xAxis: { + type: 'category', + name: '日期', + data: res.map((s: any) => s.time), + }, + }; + } + default: { + return {}; + } + } +} diff --git a/apps/web-ele/src/views/crm/statistics/customer/data.ts b/apps/web-ele/src/views/crm/statistics/customer/data.ts new file mode 100644 index 0000000..a2ec49e --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/customer/data.ts @@ -0,0 +1,401 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { useUserStore } from '@vben/stores'; +import { + beginOfDay, + endOfDay, + erpCalculatePercentage, + formatDateTime, + handleTree, +} from '@vben/utils'; + +import { getSimpleDeptList } from '#/api/system/dept'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +const userStore = useUserStore(); + +export const customerSummaryTabs = [ + { + tab: '客户总量分析', + key: 'customerSummary', + }, + { + tab: '客户跟进次数分析', + key: 'followUpSummary', + }, + { + tab: '客户跟进方式分析', + key: 'followUpType', + }, + { + tab: '客户转化率分析', + key: 'conversionStat', + }, + { + tab: '公海客户分析', + key: 'poolSummary', + }, + { + tab: '员工客户成交周期分析', + key: 'dealCycleByUser', + }, + { + tab: '地区客户成交周期分析', + key: 'dealCycleByArea', + }, + { + tab: '产品客户成交周期分析', + key: 'dealCycleByProduct', + }, +]; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'times', + label: '时间范围', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + }, + defaultValue: [ + formatDateTime(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))), + formatDateTime(endOfDay(new Date(Date.now() - 3600 * 1000 * 24))), + ], + }, + { + fieldName: 'interval', + label: '时间间隔', + component: 'Select', + componentProps: { + allowClear: true, + placeholder: '请选择时间间隔', + options: getDictOptions(DICT_TYPE.DATE_INTERVAL, 'number'), + }, + defaultValue: 2, + }, + { + fieldName: 'deptId', + label: '归属部门', + component: 'ApiTreeSelect', + componentProps: { + api: async () => { + const data = await getSimpleDeptList(); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + treeDefaultExpandAll: true, + placeholder: '请选择归属部门', + }, + defaultValue: userStore.userInfo?.deptId, + }, + { + fieldName: 'userId', + label: '员工', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择员工', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + activeTabName: any, +): VxeTableGridOptions['columns'] { + switch (activeTabName) { + case 'conversionStat': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'customerName', + title: '客户名称', + minWidth: 100, + }, + { + field: 'contractName', + title: '合同名称', + minWidth: 200, + }, + { + field: 'totalPrice', + title: '合同总金额', + minWidth: 200, + formatter: 'formatAmount2', + }, + { + field: 'receivablePrice', + title: '回款金额', + minWidth: 200, + formatter: 'formatAmount2', + }, + { + field: 'source', + title: '客户来源', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_SOURCE }, + }, + }, + { + field: 'industryId', + title: '客户行业', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY }, + }, + }, + { + field: 'ownerUserName', + title: '负责人', + minWidth: 200, + }, + { + field: 'creatorUserName', + title: '创建人', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 200, + formatter: 'formatDateTime', + }, + { + field: 'orderDate', + title: '下单日期', + minWidth: 200, + formatter: 'formatDateTime', + }, + ]; + } + case 'customerSummary': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'ownerUserName', + title: '员工姓名', + minWidth: 100, + }, + { + field: 'customerCreateCount', + title: '新增客户数', + minWidth: 200, + }, + { + field: 'customerDealCount', + title: '成交客户数', + minWidth: 200, + }, + { + field: 'customerDealRate', + title: '客户成交率(%)', + minWidth: 200, + formatter: ({ row }) => { + return erpCalculatePercentage( + row.customerDealCount, + row.customerCreateCount, + ); + }, + }, + { + field: 'contractPrice', + title: '合同总金额', + minWidth: 200, + formatter: 'formatAmount2', + }, + { + field: 'receivablePrice', + title: '回款金额', + minWidth: 200, + formatter: 'formatAmount2', + }, + { + field: 'creceivablePrice', + title: '未回款金额', + minWidth: 200, + formatter: ({ row }) => { + return erpCalculatePercentage( + row.receivablePrice, + row.contractPrice, + ); + }, + }, + { + field: 'ccreceivablePrice', + title: '回款完成率(%)', + formatter: ({ row }) => { + return erpCalculatePercentage( + row.receivablePrice, + row.contractPrice, + ); + }, + }, + ]; + } + case 'dealCycleByArea': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'areaName', + title: '区域', + minWidth: 200, + }, + { + field: 'customerDealCycle', + title: '成交周期(天)', + minWidth: 200, + }, + { + field: 'customerDealCount', + title: '成交客户数', + minWidth: 200, + }, + ]; + } + case 'dealCycleByProduct': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'productName', + title: '产品名称', + minWidth: 200, + }, + { + field: 'customerDealCycle', + title: '成交周期(天)', + minWidth: 200, + }, + { + field: 'customerDealCount', + title: '成交客户数', + minWidth: 200, + }, + ]; + } + case 'dealCycleByUser': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'ownerUserName', + title: '日期', + minWidth: 200, + }, + { + field: 'customerDealCycle', + title: '成交周期(天)', + minWidth: 200, + }, + { + field: 'customerDealCount', + title: '成交客户数', + minWidth: 200, + }, + ]; + } + case 'followUpSummary': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'ownerUserName', + title: '员工姓名', + minWidth: 200, + }, + { + field: 'followUpRecordCount', + title: '跟进次数', + minWidth: 200, + }, + { + field: 'followUpCustomerCount', + title: '跟进客户数', + minWidth: 200, + }, + ]; + } + case 'followUpType': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'followUpType', + title: '跟进方式', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_FOLLOW_UP_TYPE }, + }, + }, + { + field: 'followUpRecordCount', + title: '个数', + minWidth: 200, + }, + { + field: 'portion', + title: '占比(%)', + minWidth: 200, + }, + ]; + } + case 'poolSummary': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'ownerUserName', + title: '员工姓名', + minWidth: 200, + }, + { + field: 'customerPutCount', + title: '进入公海客户数', + minWidth: 200, + }, + { + field: 'customerTakeCount', + title: '公海领取客户数', + minWidth: 200, + }, + ]; + } + default: { + return []; + } + } +} diff --git a/apps/web-ele/src/views/crm/statistics/customer/index.vue b/apps/web-ele/src/views/crm/statistics/customer/index.vue new file mode 100644 index 0000000..58cf1ef --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/customer/index.vue @@ -0,0 +1,98 @@ + + + diff --git a/apps/web-ele/src/views/crm/statistics/funnel/chartOptions.ts b/apps/web-ele/src/views/crm/statistics/funnel/chartOptions.ts new file mode 100644 index 0000000..e86953f --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/funnel/chartOptions.ts @@ -0,0 +1,271 @@ +import { erpCalculatePercentage } from '@vben/utils'; + +export function getChartOptions( + activeTabName: any, + active: boolean, + res: any, +): any { + switch (activeTabName) { + case 'businessInversionRateSummary': { + return { + color: ['#6ca2ff', '#6ac9d7', '#ff7474'], + tooltip: { + trigger: 'axis', + axisPointer: { + // 坐标轴指示器,坐标轴触发有效 + type: 'shadow', // 默认为直线,可选为:'line' | 'shadow' + }, + }, + legend: { + data: ['赢单转化率', '商机总数', '赢单商机数'], + bottom: '0px', + itemWidth: 14, + }, + grid: { + top: '40px', + left: '40px', + right: '40px', + bottom: '40px', + containLabel: true, + borderColor: '#fff', + }, + xAxis: [ + { + type: 'category', + data: res.map((s: any) => s.time), + axisTick: { + alignWithLabel: true, + lineStyle: { width: 0 }, + }, + axisLabel: { + color: '#BDBDBD', + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { color: '#BDBDBD' }, + }, + splitLine: { + show: false, + }, + }, + ], + yAxis: [ + { + type: 'value', + name: '赢单转化率', + axisTick: { + alignWithLabel: true, + lineStyle: { width: 0 }, + }, + axisLabel: { + color: '#BDBDBD', + formatter: '{value}%', + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { color: '#BDBDBD' }, + }, + splitLine: { + show: false, + }, + }, + { + type: 'value', + name: '商机数', + axisTick: { + alignWithLabel: true, + lineStyle: { width: 0 }, + }, + axisLabel: { + color: '#BDBDBD', + formatter: '{value}个', + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { color: '#BDBDBD' }, + }, + splitLine: { + show: false, + }, + }, + ], + series: [ + { + name: '赢单转化率', + type: 'line', + yAxisIndex: 0, + data: res.map((s: any) => + erpCalculatePercentage(s.businessWinCount, s.businessCount), + ), + }, + { + name: '商机总数', + type: 'bar', + yAxisIndex: 1, + barWidth: 15, + data: res.map((s: any) => s.businessCount), + }, + { + name: '赢单商机数', + type: 'bar', + yAxisIndex: 1, + barWidth: 15, + data: res.map((s: any) => s.businessWinCount), + }, + ], + }; + } + case 'businessSummary': { + return { + grid: { + left: 30, + right: 30, // 让 X 轴右侧显示完整 + bottom: 20, + containLabel: true, + }, + legend: {}, + series: [ + { + name: '新增商机数量', + type: 'bar', + yAxisIndex: 0, + data: res.map((s: any) => s.businessCreateCount), + }, + { + name: '新增商机金额', + type: 'bar', + yAxisIndex: 1, + data: res.map((s: any) => s.totalPrice), + }, + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '新增商机分析' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + yAxis: [ + { + type: 'value', + name: '新增商机数量', + min: 0, + minInterval: 1, // 显示整数刻度 + }, + { + type: 'value', + name: '新增商机金额', + min: 0, + minInterval: 1, // 显示整数刻度 + splitLine: { + lineStyle: { + type: 'dotted', // 右侧网格线虚化, 减少混乱 + opacity: 0.7, + }, + }, + }, + ], + xAxis: { + type: 'category', + name: '日期', + data: res.map((s: any) => s.time), + }, + }; + } + case 'funnel': { + // tips:写死 value 值是为了保持漏斗顺序不变 + const list: { name: string; value: number }[] = []; + if (active) { + list.push( + { value: 60, name: `客户-${res.customerCount || 0}个` }, + { value: 40, name: `商机-${res.businessCount || 0}个` }, + { value: 20, name: `赢单-${res.businessWinCount || 0}个` }, + ); + } else { + list.push( + { + value: res.customerCount || 0, + name: `客户-${res.customerCount || 0}个`, + }, + { + value: res.businessCount || 0, + name: `商机-${res.businessCount || 0}个`, + }, + { + value: res.businessWinCount || 0, + name: `赢单-${res.businessWinCount || 0}个`, + }, + ); + } + return { + title: { + text: '销售漏斗', + }, + tooltip: { + trigger: 'item', + formatter: '{a}
    {b}', + }, + toolbox: { + feature: { + dataView: { readOnly: false }, + restore: {}, + saveAsImage: {}, + }, + }, + legend: { + data: ['客户', '商机', '赢单'], + }, + series: [ + { + name: '销售漏斗', + type: 'funnel', + left: '10%', + top: 60, + bottom: 60, + width: '80%', + min: 0, + max: 100, + minSize: '0%', + maxSize: '100%', + sort: 'descending', + gap: 2, + label: { + show: true, + position: 'inside', + }, + labelLine: { + length: 10, + lineStyle: { + width: 1, + type: 'solid', + }, + }, + itemStyle: { + borderColor: '#fff', + borderWidth: 1, + }, + emphasis: { + label: { + fontSize: 20, + }, + }, + data: list, + }, + ], + }; + } + default: { + return {}; + } + } +} diff --git a/apps/web-ele/src/views/crm/statistics/funnel/data.ts b/apps/web-ele/src/views/crm/statistics/funnel/data.ts new file mode 100644 index 0000000..5dd93db --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/funnel/data.ts @@ -0,0 +1,271 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { useUserStore } from '@vben/stores'; +import { beginOfDay, endOfDay, formatDateTime, handleTree } from '@vben/utils'; + +import { getSimpleDeptList } from '#/api/system/dept'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +const userStore = useUserStore(); + +export const customerSummaryTabs = [ + { + tab: '销售漏斗分析', + key: 'funnel', + }, + { + tab: '新增商机分析', + key: 'businessSummary', + }, + { + tab: '商机转化率分析', + key: 'businessInversionRateSummary', + }, +]; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'times', + label: '时间范围', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + }, + defaultValue: [ + formatDateTime(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))), + formatDateTime(endOfDay(new Date(Date.now() - 3600 * 1000 * 24))), + ], + }, + { + fieldName: 'interval', + label: '时间间隔', + component: 'Select', + componentProps: { + allowClear: true, + placeholder: '请选择时间间隔', + options: getDictOptions(DICT_TYPE.DATE_INTERVAL, 'number'), + }, + defaultValue: 2, + }, + { + fieldName: 'deptId', + label: '归属部门', + component: 'ApiTreeSelect', + componentProps: { + api: async () => { + const data = await getSimpleDeptList(); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + treeDefaultExpandAll: true, + placeholder: '请选择归属部门', + }, + defaultValue: userStore.userInfo?.deptId, + }, + { + fieldName: 'userId', + label: '员工', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + allowClear: true, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择员工', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + activeTabName: any, +): VxeTableGridOptions['columns'] { + switch (activeTabName) { + case 'businessInversionRateSummary': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'name', + title: '商机名称', + minWidth: 100, + }, + { + field: 'customerName', + title: '客户名称', + minWidth: 200, + }, + { + field: 'totalPrice', + title: '商机金额(元)', + minWidth: 200, + formatter: 'formatAmount2', + }, + { + field: 'dealTime', + title: '预计成交日期', + minWidth: 200, + formatter: 'formatDateTime', + }, + { + field: 'ownerUserName', + title: '负责人', + minWidth: 200, + }, + { + field: 'ownerUserDeptName', + title: '所属部门', + minWidth: 200, + }, + { + field: 'contactLastTime', + title: '最后跟进时间', + minWidth: 200, + formatter: 'formatDateTime', + }, + { + field: 'updateTime', + title: '更新时间', + minWidth: 200, + formatter: 'formatDateTime', + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 200, + formatter: 'formatDateTime', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 100, + }, + { + field: 'statusTypeName', + title: '商机状态组', + minWidth: 100, + }, + { + field: 'statusName', + title: '商机阶段', + minWidth: 100, + }, + ]; + } + case 'businessSummary': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'name', + title: '商机名称', + minWidth: 100, + }, + { + field: 'customerName', + title: '客户名称', + minWidth: 200, + }, + { + field: 'totalPrice', + title: '商机金额(元)', + minWidth: 200, + formatter: 'formatAmount2', + }, + { + field: 'dealTime', + title: '预计成交日期', + minWidth: 200, + formatter: 'formatDateTime', + }, + { + field: 'ownerUserName', + title: '负责人', + minWidth: 200, + }, + { + field: 'ownerUserDeptName', + title: '所属部门', + minWidth: 200, + }, + { + field: 'contactLastTime', + title: '最后跟进时间', + minWidth: 200, + formatter: 'formatDateTime', + }, + { + field: 'updateTime', + title: '更新时间', + minWidth: 200, + formatter: 'formatDateTime', + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 200, + formatter: 'formatDateTime', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 100, + }, + { + field: 'statusTypeName', + title: '商机状态组', + minWidth: 100, + }, + { + field: 'statusName', + title: '商机阶段', + minWidth: 100, + }, + ]; + } + case 'funnel': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'endStatus', + title: '阶段', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_BUSINESS_END_STATUS_TYPE }, + }, + }, + { + field: 'businessCount', + title: '商机数', + minWidth: 200, + }, + { + field: 'totalPrice', + title: '商机总金额(元)', + minWidth: 200, + formatter: 'formatAmount2', + }, + ]; + } + default: { + return []; + } + } +} diff --git a/apps/web-ele/src/views/crm/statistics/funnel/index.vue b/apps/web-ele/src/views/crm/statistics/funnel/index.vue new file mode 100644 index 0000000..7c11afd --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/funnel/index.vue @@ -0,0 +1,144 @@ + + + diff --git a/apps/web-ele/src/views/crm/statistics/performance/chartOptions.ts b/apps/web-ele/src/views/crm/statistics/performance/chartOptions.ts new file mode 100644 index 0000000..329b4d7 --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/performance/chartOptions.ts @@ -0,0 +1,394 @@ +export function getChartOptions(activeTabName: any, res: any): any { + switch (activeTabName) { + case 'ContractCountPerformance': { + return { + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true, + }, + legend: {}, + series: [ + { + name: '当月合同数量(个)', + type: 'line', + data: res.map((s: any) => s.currentMonthCount), + }, + { + name: '上月合同数量(个)', + type: 'line', + data: res.map((s: any) => s.lastMonthCount), + }, + { + name: '去年同月合同数量(个)', + type: 'line', + data: res.map((s: any) => s.lastYearCount), + }, + { + name: '环比增长率(%)', + type: 'line', + yAxisIndex: 1, + data: res.map((s: any) => + s.lastMonthCount === 0 + ? 'NULL' + : ( + ((s.currentMonthCount - s.lastMonthCount) / + s.lastMonthCount) * + 100 + ).toFixed(2), + ), + }, + { + name: '同比增长率(%)', + type: 'line', + yAxisIndex: 1, + data: res.map((s: any) => + s.lastYearCount === 0 + ? 'NULL' + : ( + ((s.currentMonthCount - s.lastYearCount) / + s.lastYearCount) * + 100 + ).toFixed(2), + ), + }, + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '客户总量分析' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + yAxis: [ + { + type: 'value', + name: '数量(个)', + axisTick: { + show: false, + }, + axisLabel: { + color: '#BDBDBD', + formatter: '{value}', + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { + color: '#BDBDBD', + }, + }, + splitLine: { + show: true, + lineStyle: { + color: '#e6e6e6', + }, + }, + }, + { + type: 'value', + name: '', + axisTick: { + alignWithLabel: true, + lineStyle: { + width: 0, + }, + }, + axisLabel: { + color: '#BDBDBD', + formatter: '{value}%', + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { + color: '#BDBDBD', + }, + }, + splitLine: { + show: true, + lineStyle: { + color: '#e6e6e6', + }, + }, + }, + ], + xAxis: { + type: 'category', + name: '日期', + data: res.map((s: any) => s.time), + }, + }; + } + case 'ContractPricePerformance': { + return { + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true, + }, + legend: {}, + series: [ + { + name: '当月合同金额(元)', + type: 'line', + data: res.map((s: any) => s.currentMonthCount), + }, + { + name: '上月合同金额(元)', + type: 'line', + data: res.map((s: any) => s.lastMonthCount), + }, + { + name: '去年同月合同金额(元)', + type: 'line', + data: res.map((s: any) => s.lastYearCount), + }, + { + name: '环比增长率(%)', + type: 'line', + yAxisIndex: 1, + data: res.map((s: any) => + s.lastMonthCount === 0 + ? 'NULL' + : ( + ((s.currentMonthCount - s.lastMonthCount) / + s.lastMonthCount) * + 100 + ).toFixed(2), + ), + }, + { + name: '同比增长率(%)', + type: 'line', + yAxisIndex: 1, + data: res.map((s: any) => + s.lastYearCount === 0 + ? 'NULL' + : ( + ((s.currentMonthCount - s.lastYearCount) / + s.lastYearCount) * + 100 + ).toFixed(2), + ), + }, + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '客户总量分析' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + yAxis: [ + { + type: 'value', + name: '金额(元)', + axisTick: { + show: false, + }, + axisLabel: { + color: '#BDBDBD', + formatter: '{value}', + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { + color: '#BDBDBD', + }, + }, + splitLine: { + show: true, + lineStyle: { + color: '#e6e6e6', + }, + }, + }, + { + type: 'value', + name: '', + axisTick: { + alignWithLabel: true, + lineStyle: { + width: 0, + }, + }, + axisLabel: { + color: '#BDBDBD', + formatter: '{value}%', + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { + color: '#BDBDBD', + }, + }, + splitLine: { + show: true, + lineStyle: { + color: '#e6e6e6', + }, + }, + }, + ], + xAxis: { + type: 'category', + name: '日期', + data: res.map((s: any) => s.time), + }, + }; + } + case 'ReceivablePricePerformance': { + return { + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true, + }, + legend: {}, + series: [ + { + name: '当月回款金额(元)', + type: 'line', + data: res.map((s: any) => s.currentMonthCount), + }, + { + name: '上月回款金额(元)', + type: 'line', + data: res.map((s: any) => s.lastMonthCount), + }, + { + name: '去年同月回款金额(元)', + type: 'line', + data: res.map((s: any) => s.lastYearCount), + }, + { + name: '环比增长率(%)', + type: 'line', + yAxisIndex: 1, + data: res.map((s: any) => + s.lastMonthCount === 0 + ? 'NULL' + : ( + ((s.currentMonthCount - s.lastMonthCount) / + s.lastMonthCount) * + 100 + ).toFixed(2), + ), + }, + { + name: '同比增长率(%)', + type: 'line', + yAxisIndex: 1, + data: res.map((s: any) => + s.lastYearCount === 0 + ? 'NULL' + : ( + ((s.currentMonthCount - s.lastYearCount) / + s.lastYearCount) * + 100 + ).toFixed(2), + ), + }, + ], + toolbox: { + feature: { + dataZoom: { + xAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '客户总量分析' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + yAxis: [ + { + type: 'value', + name: '金额(元)', + axisTick: { + show: false, + }, + axisLabel: { + color: '#BDBDBD', + formatter: '{value}', + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { + color: '#BDBDBD', + }, + }, + splitLine: { + show: true, + lineStyle: { + color: '#e6e6e6', + }, + }, + }, + { + type: 'value', + name: '', + axisTick: { + alignWithLabel: true, + lineStyle: { + width: 0, + }, + }, + axisLabel: { + color: '#BDBDBD', + formatter: '{value}%', + }, + /** 坐标轴轴线相关设置 */ + axisLine: { + lineStyle: { + color: '#BDBDBD', + }, + }, + splitLine: { + show: true, + lineStyle: { + color: '#e6e6e6', + }, + }, + }, + ], + xAxis: { + type: 'category', + name: '日期', + data: res.map((s: any) => s.time), + }, + }; + } + default: { + return {}; + } + } +} diff --git a/apps/web-ele/src/views/crm/statistics/performance/data.ts b/apps/web-ele/src/views/crm/statistics/performance/data.ts new file mode 100644 index 0000000..ab86673 --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/performance/data.ts @@ -0,0 +1,71 @@ +import type { VbenFormSchema } from '#/adapter/form'; + +import { useUserStore } from '@vben/stores'; +import { handleTree } from '@vben/utils'; + +import { getSimpleDeptList } from '#/api/system/dept'; +import { getSimpleUserList } from '#/api/system/user'; + +const userStore = useUserStore(); + +export const customerSummaryTabs = [ + { + tab: '员工合同数量统计', + key: 'ContractCountPerformance', + }, + { + tab: '员工合同金额统计', + key: 'ContractPricePerformance', + }, + { + tab: '员工回款金额统计', + key: 'ReceivablePricePerformance', + }, +]; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'time', + label: '选择年份', + component: 'DatePicker', + componentProps: { + type: 'year', + format: 'YYYY', + valueFormat: 'YYYY', + placeholder: '请选择年份', + }, + defaultValue: new Date().getFullYear().toString(), + }, + { + fieldName: 'deptId', + label: '归属部门', + component: 'ApiTreeSelect', + componentProps: { + api: async () => { + const data = await getSimpleDeptList(); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + treeDefaultExpandAll: true, + placeholder: '请选择归属部门', + }, + defaultValue: userStore.userInfo?.deptId, + }, + { + fieldName: 'userId', + label: '员工', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择员工', + allowClear: true, + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/crm/statistics/performance/index.vue b/apps/web-ele/src/views/crm/statistics/performance/index.vue new file mode 100644 index 0000000..8271ed7 --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/performance/index.vue @@ -0,0 +1,186 @@ + + + diff --git a/apps/web-ele/src/views/crm/statistics/portrait/chartOptions.ts b/apps/web-ele/src/views/crm/statistics/portrait/chartOptions.ts new file mode 100644 index 0000000..b63ea25 --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/portrait/chartOptions.ts @@ -0,0 +1,440 @@ +import { DICT_TYPE } from '@vben/constants'; +import { getDictLabel } from '@vben/hooks'; + +function areaReplace(areaName: string) { + if (!areaName) { + return areaName; + } + return areaName + .replace('维吾尔自治区', '') + .replace('壮族自治区', '') + .replace('回族自治区', '') + .replace('自治区', '') + .replace('省', ''); +} + +export function getChartOptions(activeTabName: any, res: any): any { + switch (activeTabName) { + case 'area': { + const data = res.map((item: any) => { + return { + ...item, + areaName: areaReplace(item.areaName), + }; + }); + let leftMin = 0; + let leftMax = 0; + let rightMin = 0; + let rightMax = 0; + data.forEach((item: any) => { + leftMin = Math.min(leftMin, item.customerCount || 0); + leftMax = Math.max(leftMax, item.customerCount || 0); + rightMin = Math.min(rightMin, item.dealCount || 0); + rightMax = Math.max(rightMax, item.dealCount || 0); + }); + return { + left: { + title: { + text: '全部客户', + left: 'center', + }, + tooltip: { + trigger: 'item', + showDelay: 0, + transitionDuration: 0.2, + }, + visualMap: { + text: ['高', '低'], + realtime: false, + calculable: true, + top: 'middle', + inRange: { + color: ['yellow', 'lightskyblue', 'orangered'], + }, + min: leftMin, + max: leftMax, + }, + series: [ + { + name: '客户地域分布', + type: 'map', + map: 'china', + roam: false, + selectedMode: false, + data: data.map((item: any) => { + return { + name: item.areaName, + value: item.customerCount || 0, + }; + }), + }, + ], + }, + right: { + title: { + text: '成交客户', + left: 'center', + }, + tooltip: { + trigger: 'item', + showDelay: 0, + transitionDuration: 0.2, + }, + visualMap: { + text: ['高', '低'], + realtime: false, + calculable: true, + top: 'middle', + inRange: { + color: ['yellow', 'lightskyblue', 'orangered'], + }, + min: rightMin, + max: rightMax, + }, + series: [ + { + name: '客户地域分布', + type: 'map', + map: 'china', + roam: false, + selectedMode: false, + data: data.map((item: any) => { + return { + name: item.areaName, + value: item.dealCount || 0, + }; + }), + }, + ], + }, + }; + } + case 'industry': { + return { + left: { + title: { + text: '全部客户', + left: 'center', + }, + tooltip: { + trigger: 'item', + }, + legend: { + orient: 'vertical', + left: 'left', + }, + toolbox: { + feature: { + saveAsImage: { show: true, name: '全部客户' }, // 保存为图片 + }, + }, + series: [ + { + name: '全部客户', + type: 'pie', + radius: ['40%', '70%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 10, + borderColor: '#fff', + borderWidth: 2, + }, + label: { + show: false, + position: 'center', + }, + emphasis: { + label: { + show: true, + fontSize: 40, + fontWeight: 'bold', + }, + }, + labelLine: { + show: false, + }, + data: res.map((r: any) => { + return { + name: getDictLabel( + DICT_TYPE.CRM_CUSTOMER_INDUSTRY, + r.industryId, + ), + value: r.customerCount, + }; + }), + }, + ], + }, + right: { + title: { + text: '成交客户', + left: 'center', + }, + tooltip: { + trigger: 'item', + }, + legend: { + orient: 'vertical', + left: 'left', + }, + toolbox: { + feature: { + saveAsImage: { show: true, name: '成交客户' }, // 保存为图片 + }, + }, + series: [ + { + name: '成交客户', + type: 'pie', + radius: ['40%', '70%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 10, + borderColor: '#fff', + borderWidth: 2, + }, + label: { + show: false, + position: 'center', + }, + emphasis: { + label: { + show: true, + fontSize: 40, + fontWeight: 'bold', + }, + }, + labelLine: { + show: false, + }, + data: res.map((r: any) => { + return { + name: getDictLabel( + DICT_TYPE.CRM_CUSTOMER_INDUSTRY, + r.industryId, + ), + value: r.dealCount, + }; + }), + }, + ], + }, + }; + } + case 'level': { + return { + left: { + title: { + text: '全部客户', + left: 'center', + }, + tooltip: { + trigger: 'item', + }, + legend: { + orient: 'vertical', + left: 'left', + }, + toolbox: { + feature: { + saveAsImage: { show: true, name: '全部客户' }, // 保存为图片 + }, + }, + series: [ + { + name: '全部客户', + type: 'pie', + radius: ['40%', '70%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 10, + borderColor: '#fff', + borderWidth: 2, + }, + label: { + show: false, + position: 'center', + }, + emphasis: { + label: { + show: true, + fontSize: 40, + fontWeight: 'bold', + }, + }, + labelLine: { + show: false, + }, + data: res.map((r: any) => { + return { + name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_LEVEL, r.level), + value: r.customerCount, + }; + }), + }, + ], + }, + right: { + title: { + text: '成交客户', + left: 'center', + }, + tooltip: { + trigger: 'item', + }, + legend: { + orient: 'vertical', + left: 'left', + }, + toolbox: { + feature: { + saveAsImage: { show: true, name: '成交客户' }, // 保存为图片 + }, + }, + series: [ + { + name: '成交客户', + type: 'pie', + radius: ['40%', '70%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 10, + borderColor: '#fff', + borderWidth: 2, + }, + label: { + show: false, + position: 'center', + }, + emphasis: { + label: { + show: true, + fontSize: 40, + fontWeight: 'bold', + }, + }, + labelLine: { + show: false, + }, + data: res.map((r: any) => { + return { + name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_LEVEL, r.level), + value: r.dealCount, + }; + }), + }, + ], + }, + }; + } + case 'source': { + return { + left: { + title: { + text: '全部客户', + left: 'center', + }, + tooltip: { + trigger: 'item', + }, + legend: { + orient: 'vertical', + left: 'left', + }, + toolbox: { + feature: { + saveAsImage: { show: true, name: '全部客户' }, // 保存为图片 + }, + }, + series: [ + { + name: '全部客户', + type: 'pie', + radius: ['40%', '70%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 10, + borderColor: '#fff', + borderWidth: 2, + }, + label: { + show: false, + position: 'center', + }, + emphasis: { + label: { + show: true, + fontSize: 40, + fontWeight: 'bold', + }, + }, + labelLine: { + show: false, + }, + data: res.map((r: any) => { + return { + name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_SOURCE, r.source), + value: r.customerCount, + }; + }), + }, + ], + }, + right: { + title: { + text: '成交客户', + left: 'center', + }, + tooltip: { + trigger: 'item', + }, + legend: { + orient: 'vertical', + left: 'left', + }, + toolbox: { + feature: { + saveAsImage: { show: true, name: '成交客户' }, // 保存为图片 + }, + }, + series: [ + { + name: '成交客户', + type: 'pie', + radius: ['40%', '70%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 10, + borderColor: '#fff', + borderWidth: 2, + }, + label: { + show: false, + position: 'center', + }, + emphasis: { + label: { + show: true, + fontSize: 40, + fontWeight: 'bold', + }, + }, + labelLine: { + show: false, + }, + data: res.map((r: any) => { + return { + name: getDictLabel(DICT_TYPE.CRM_CUSTOMER_SOURCE, r.source), + value: r.dealCount, + }; + }), + }, + ], + }, + }; + } + default: { + return {}; + } + } +} diff --git a/apps/web-ele/src/views/crm/statistics/portrait/data.ts b/apps/web-ele/src/views/crm/statistics/portrait/data.ts new file mode 100644 index 0000000..18e22e7 --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/portrait/data.ts @@ -0,0 +1,200 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { useUserStore } from '@vben/stores'; +import { beginOfDay, endOfDay, formatDateTime, handleTree } from '@vben/utils'; + +import { getSimpleDeptList } from '#/api/system/dept'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +const userStore = useUserStore(); + +export const customerSummaryTabs = [ + { + tab: '城市分布分析', + key: 'area', + }, + { + tab: '客户级别分析', + key: 'level', + }, + { + tab: '客户来源分析', + key: 'source', + }, + { + tab: '客户行业分析', + key: 'industry', + }, +]; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'times', + label: '时间范围', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + }, + defaultValue: [ + formatDateTime(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))), + formatDateTime(endOfDay(new Date(Date.now() - 3600 * 1000 * 24))), + ], + }, + { + fieldName: 'deptId', + label: '归属部门', + component: 'ApiTreeSelect', + componentProps: { + api: async () => { + const data = await getSimpleDeptList(); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + treeDefaultExpandAll: true, + placeholder: '请选择归属部门', + }, + defaultValue: userStore.userInfo?.deptId, + }, + { + fieldName: 'userId', + label: '员工', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择员工', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + activeTabName: any, +): VxeTableGridOptions['columns'] { + switch (activeTabName) { + case 'industry': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'industryId', + title: '客户行业', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_INDUSTRY }, + }, + }, + { + field: 'customerCount', + title: '客户个数', + minWidth: 200, + }, + { + field: 'dealCount', + title: '成交个数', + minWidth: 200, + }, + { + field: 'industryPortion', + title: '行业占比(%)', + minWidth: 200, + }, + { + field: 'dealPortion', + title: '成交占比(%)', + minWidth: 200, + }, + ]; + } + case 'level': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'level', + title: '客户级别', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_LEVEL }, + }, + }, + { + field: 'customerCount', + title: '客户个数', + minWidth: 200, + }, + { + field: 'dealCount', + title: '成交个数', + minWidth: 200, + }, + { + field: 'industryPortion', + title: '行业占比(%)', + minWidth: 200, + }, + { + field: 'dealPortion', + title: '成交占比(%)', + minWidth: 200, + }, + ]; + } + case 'source': { + return [ + { + type: 'seq', + title: '序号', + }, + { + field: 'source', + title: '客户来源', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.CRM_CUSTOMER_SOURCE }, + }, + }, + { + field: 'customerCount', + title: '客户个数', + minWidth: 200, + }, + { + field: 'dealCount', + title: '成交个数', + minWidth: 200, + }, + { + field: 'industryPortion', + title: '行业占比(%)', + minWidth: 200, + }, + { + field: 'dealPortion', + title: '成交占比(%)', + minWidth: 200, + }, + ]; + } + default: { + return []; + } + } +} diff --git a/apps/web-ele/src/views/crm/statistics/portrait/index.vue b/apps/web-ele/src/views/crm/statistics/portrait/index.vue new file mode 100644 index 0000000..5eed968 --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/portrait/index.vue @@ -0,0 +1,103 @@ + + + diff --git a/apps/web-ele/src/views/crm/statistics/rank/chartOptions.ts b/apps/web-ele/src/views/crm/statistics/rank/chartOptions.ts new file mode 100644 index 0000000..a6c36e9 --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/rank/chartOptions.ts @@ -0,0 +1,394 @@ +import { cloneDeep } from '@vben/utils'; + +export function getChartOptions(activeTabName: any, res: any): any { + switch (activeTabName) { + case 'contactCountRank': { + return { + dataset: { + dimensions: ['nickname', 'count'], + source: cloneDeep(res).toReversed(), + }, + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true, + }, + legend: { + top: 50, + }, + series: [ + { + name: '新增联系人数排行', + type: 'bar', + }, + ], + toolbox: { + feature: { + dataZoom: { + yAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '新增联系人数排行' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + xAxis: { + type: 'value', + name: '新增联系人数(个)', + }, + yAxis: { + type: 'category', + name: '创建人', + }, + }; + } + case 'contractCountRank': { + return { + dataset: { + dimensions: ['nickname', 'count'], + source: cloneDeep(res).toReversed(), + }, + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true, + }, + legend: { + top: 50, + }, + series: [ + { + name: '签约合同排行', + type: 'bar', + }, + ], + toolbox: { + feature: { + dataZoom: { + yAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '签约合同排行' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + xAxis: { + type: 'value', + name: '签约合同数(个)', + }, + yAxis: { + type: 'category', + name: '签订人', + }, + }; + } + case 'contractPriceRank': { + return { + dataset: { + dimensions: ['nickname', 'count'], + source: cloneDeep(res).toReversed(), + }, + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true, + }, + legend: { + top: 50, + }, + series: [ + { + name: '合同金额排行', + type: 'bar', + }, + ], + toolbox: { + feature: { + dataZoom: { + yAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '合同金额排行' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + xAxis: { + type: 'value', + name: '合同金额(元)', + }, + yAxis: { + type: 'category', + name: '签订人', + }, + }; + } + case 'customerCountRank': { + return { + dataset: { + dimensions: ['nickname', 'count'], + source: cloneDeep(res).toReversed(), + }, + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true, + }, + legend: { + top: 50, + }, + series: [ + { + name: '新增客户数排行', + type: 'bar', + }, + ], + toolbox: { + feature: { + dataZoom: { + yAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '新增客户数排行' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + xAxis: { + type: 'value', + name: '新增客户数(个)', + }, + yAxis: { + type: 'category', + name: '创建人', + }, + }; + } + case 'followCountRank': { + return { + dataset: { + dimensions: ['nickname', 'count'], + source: cloneDeep(res).toReversed(), + }, + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true, + }, + legend: { + top: 50, + }, + series: [ + { + name: '跟进次数排行', + type: 'bar', + }, + ], + toolbox: { + feature: { + dataZoom: { + yAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '跟进次数排行' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + xAxis: { + type: 'value', + name: '跟进次数(次)', + }, + yAxis: { + type: 'category', + name: '员工', + }, + }; + } + case 'followCustomerCountRank': { + return { + dataset: { + dimensions: ['nickname', 'count'], + source: cloneDeep(res).toReversed(), + }, + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true, + }, + legend: { + top: 50, + }, + series: [ + { + name: '跟进客户数排行', + type: 'bar', + }, + ], + toolbox: { + feature: { + dataZoom: { + yAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '跟进客户数排行' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + xAxis: { + type: 'value', + name: '跟进客户数(个)', + }, + yAxis: { + type: 'category', + name: '员工', + }, + }; + } + case 'productSalesRank': { + return { + dataset: { + dimensions: ['nickname', 'count'], + source: cloneDeep(res).toReversed(), + }, + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true, + }, + legend: { + top: 50, + }, + series: [ + { + name: '产品销量排行', + type: 'bar', + }, + ], + toolbox: { + feature: { + dataZoom: { + yAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '产品销量排行' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + xAxis: { + type: 'value', + name: '产品销量', + }, + yAxis: { + type: 'category', + name: '员工', + }, + }; + } + case 'receivablePriceRank': { + return { + dataset: { + dimensions: ['nickname', 'count'], + source: cloneDeep(res).toReversed(), + }, + grid: { + left: 20, + right: 20, + bottom: 20, + containLabel: true, + }, + legend: { + top: 50, + }, + series: [ + { + name: '回款金额排行', + type: 'bar', + }, + ], + toolbox: { + feature: { + dataZoom: { + yAxisIndex: false, // 数据区域缩放:Y 轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { show: true, name: '回款金额排行' }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow', + }, + }, + xAxis: { + type: 'value', + name: '回款金额(元)', + }, + yAxis: { + type: 'category', + name: '签订人', + nameGap: 30, + }, + }; + } + default: { + return {}; + } + } +} diff --git a/apps/web-ele/src/views/crm/statistics/rank/data.ts b/apps/web-ele/src/views/crm/statistics/rank/data.ts new file mode 100644 index 0000000..b60d90d --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/rank/data.ts @@ -0,0 +1,277 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { useUserStore } from '@vben/stores'; +import { beginOfDay, endOfDay, formatDateTime, handleTree } from '@vben/utils'; + +import { getSimpleDeptList } from '#/api/system/dept'; +import { getRangePickerDefaultProps } from '#/utils'; + +const userStore = useUserStore(); + +export const customerSummaryTabs = [ + { + tab: '合同金额排行', + key: 'contractPriceRank', + }, + { + tab: '回款金额排行', + key: 'receivablePriceRank', + }, + { + tab: '签约合同排行', + key: 'contractCountRank', + }, + { + tab: '产品销量排行', + key: 'productSalesRank', + }, + { + tab: '新增客户数排行', + key: 'customerCountRank', + }, + { + tab: '新增联系人数排行', + key: 'contactCountRank', + }, + { + tab: '跟进次数排行', + key: 'followCountRank', + }, + { + tab: '跟进客户数排行', + key: 'followCustomerCountRank', + }, +]; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'times', + label: '时间范围', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + }, + defaultValue: [ + formatDateTime(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))), + formatDateTime(endOfDay(new Date(Date.now() - 3600 * 1000 * 24))), + ], + }, + { + fieldName: 'deptId', + label: '归属部门', + component: 'ApiTreeSelect', + componentProps: { + api: async () => { + const data = await getSimpleDeptList(); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + treeDefaultExpandAll: true, + placeholder: '请选择归属部门', + }, + defaultValue: userStore.userInfo?.deptId, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + activeTabName: any, +): VxeTableGridOptions['columns'] { + switch (activeTabName) { + case 'contactCountRank': { + return [ + { + type: 'seq', + title: '公司排名', + }, + { + field: 'nickname', + title: '创建人', + minWidth: 200, + }, + { + field: 'deptName', + title: '部门', + minWidth: 200, + }, + { + field: 'count', + title: '新增联系人数(个)', + minWidth: 200, + }, + ]; + } + case 'contractCountRank': { + return [ + { + type: 'seq', + title: '公司排名', + }, + { + field: 'nickname', + title: '签订人', + minWidth: 200, + }, + { + field: 'deptName', + title: '部门', + minWidth: 200, + }, + { + field: 'count', + title: '签约合同数(个)', + minWidth: 200, + }, + ]; + } + case 'contractPriceRank': { + return [ + { + type: 'seq', + title: '公司排名', + }, + { + field: 'nickname', + title: '签订人', + minWidth: 200, + }, + { + field: 'deptName', + title: '部门', + minWidth: 200, + }, + { + field: 'count', + title: '合同金额(元)', + minWidth: 200, + formatter: 'formatAmount2', + }, + ]; + } + case 'customerCountRank': { + return [ + { + type: 'seq', + title: '公司排名', + }, + { + field: 'nickname', + title: '签订人', + minWidth: 200, + }, + { + field: 'deptName', + title: '部门', + minWidth: 200, + }, + { + field: 'count', + title: '新增客户数(个)', + minWidth: 200, + }, + ]; + } + case 'followCountRank': { + return [ + { + type: 'seq', + title: '公司排名', + }, + { + field: 'nickname', + title: '签订人', + minWidth: 200, + }, + { + field: 'deptName', + title: '部门', + minWidth: 200, + }, + { + field: 'count', + title: '跟进次数(次)', + minWidth: 200, + }, + ]; + } + case 'followCustomerCountRank': { + return [ + { + type: 'seq', + title: '公司排名', + }, + { + field: 'nickname', + title: '签订人', + minWidth: 200, + }, + { + field: 'deptName', + title: '部门', + minWidth: 200, + }, + { + field: 'count', + title: '跟进客户数(个)', + minWidth: 200, + }, + ]; + } + case 'productSalesRank': { + return [ + { + type: 'seq', + title: '公司排名', + }, + { + field: 'nickname', + title: '签订人', + minWidth: 200, + }, + { + field: 'deptName', + title: '部门', + minWidth: 200, + }, + { + field: 'count', + title: '产品销量', + minWidth: 200, + }, + ]; + } + case 'receivablePriceRank': { + return [ + { + type: 'seq', + title: '公司排名', + }, + { + field: 'nickname', + title: '签订人', + minWidth: 200, + }, + { + field: 'deptName', + title: '部门', + minWidth: 200, + }, + { + field: 'count', + title: '回款金额(元)', + minWidth: 200, + formatter: 'formatAmount2', + }, + ]; + } + default: { + return []; + } + } +} diff --git a/apps/web-ele/src/views/crm/statistics/rank/index.vue b/apps/web-ele/src/views/crm/statistics/rank/index.vue new file mode 100644 index 0000000..8253b2b --- /dev/null +++ b/apps/web-ele/src/views/crm/statistics/rank/index.vue @@ -0,0 +1,107 @@ + + + diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue new file mode 100644 index 0000000..f1f0b23 --- /dev/null +++ b/apps/web-ele/src/views/dashboard/analytics/analytics-trends.vue @@ -0,0 +1,98 @@ + + + diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue new file mode 100644 index 0000000..190fb41 --- /dev/null +++ b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-data.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue new file mode 100644 index 0000000..6ff5208 --- /dev/null +++ b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-sales.vue @@ -0,0 +1,46 @@ + + + diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue new file mode 100644 index 0000000..0915c7a --- /dev/null +++ b/apps/web-ele/src/views/dashboard/analytics/analytics-visits-source.vue @@ -0,0 +1,65 @@ + + + diff --git a/apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue b/apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue new file mode 100644 index 0000000..7e0f101 --- /dev/null +++ b/apps/web-ele/src/views/dashboard/analytics/analytics-visits.vue @@ -0,0 +1,55 @@ + + + diff --git a/apps/web-ele/src/views/dashboard/analytics/index.vue b/apps/web-ele/src/views/dashboard/analytics/index.vue new file mode 100644 index 0000000..5e3d6d2 --- /dev/null +++ b/apps/web-ele/src/views/dashboard/analytics/index.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/web-ele/src/views/dashboard/workspace/index.vue b/apps/web-ele/src/views/dashboard/workspace/index.vue new file mode 100644 index 0000000..4e15e7d --- /dev/null +++ b/apps/web-ele/src/views/dashboard/workspace/index.vue @@ -0,0 +1,260 @@ + + + diff --git a/apps/web-ele/src/views/database/entry/index.vue b/apps/web-ele/src/views/database/entry/index.vue new file mode 100644 index 0000000..12f1911 --- /dev/null +++ b/apps/web-ele/src/views/database/entry/index.vue @@ -0,0 +1,1053 @@ + + + + + diff --git a/apps/web-ele/src/views/database/feature/index.vue b/apps/web-ele/src/views/database/feature/index.vue new file mode 100644 index 0000000..c853a2e --- /dev/null +++ b/apps/web-ele/src/views/database/feature/index.vue @@ -0,0 +1,268 @@ + + + + + diff --git a/apps/web-ele/src/views/database/interface/project.vue b/apps/web-ele/src/views/database/interface/project.vue new file mode 100644 index 0000000..14a7d34 --- /dev/null +++ b/apps/web-ele/src/views/database/interface/project.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/apps/web-ele/src/views/database/interface/unit.vue b/apps/web-ele/src/views/database/interface/unit.vue new file mode 100644 index 0000000..2eeef06 --- /dev/null +++ b/apps/web-ele/src/views/database/interface/unit.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/apps/web-ele/src/views/database/interface/unit/FieldName.vue b/apps/web-ele/src/views/database/interface/unit/FieldName.vue new file mode 100644 index 0000000..c7e493f --- /dev/null +++ b/apps/web-ele/src/views/database/interface/unit/FieldName.vue @@ -0,0 +1,150 @@ + + + diff --git a/apps/web-ele/src/views/database/interface/unit/MaterialField.vue b/apps/web-ele/src/views/database/interface/unit/MaterialField.vue new file mode 100644 index 0000000..6487be0 --- /dev/null +++ b/apps/web-ele/src/views/database/interface/unit/MaterialField.vue @@ -0,0 +1,36 @@ + + + diff --git a/apps/web-ele/src/views/database/interface/unit/MeasureItem.vue b/apps/web-ele/src/views/database/interface/unit/MeasureItem.vue new file mode 100644 index 0000000..56cf3fd --- /dev/null +++ b/apps/web-ele/src/views/database/interface/unit/MeasureItem.vue @@ -0,0 +1,38 @@ + + + diff --git a/apps/web-ele/src/views/database/interface/unit/OtherItem.vue b/apps/web-ele/src/views/database/interface/unit/OtherItem.vue new file mode 100644 index 0000000..21b15e1 --- /dev/null +++ b/apps/web-ele/src/views/database/interface/unit/OtherItem.vue @@ -0,0 +1,38 @@ + + + diff --git a/apps/web-ele/src/views/database/interface/unit/SubItem.vue b/apps/web-ele/src/views/database/interface/unit/SubItem.vue new file mode 100644 index 0000000..017ef8e --- /dev/null +++ b/apps/web-ele/src/views/database/interface/unit/SubItem.vue @@ -0,0 +1,38 @@ + + + diff --git a/apps/web-ele/src/views/database/interface/unit/UnitSummary.vue b/apps/web-ele/src/views/database/interface/unit/UnitSummary.vue new file mode 100644 index 0000000..8a430d7 --- /dev/null +++ b/apps/web-ele/src/views/database/interface/unit/UnitSummary.vue @@ -0,0 +1,38 @@ + + + diff --git a/apps/web-ele/src/views/database/interface/unit/VariableSettings.vue b/apps/web-ele/src/views/database/interface/unit/VariableSettings.vue new file mode 100644 index 0000000..50b3917 --- /dev/null +++ b/apps/web-ele/src/views/database/interface/unit/VariableSettings.vue @@ -0,0 +1,267 @@ + + + diff --git a/apps/web-ele/src/views/database/list/catalog.ts b/apps/web-ele/src/views/database/list/catalog.ts new file mode 100644 index 0000000..0514400 --- /dev/null +++ b/apps/web-ele/src/views/database/list/catalog.ts @@ -0,0 +1,66 @@ +//清单目录 +// console.log(rowSchema) +import { ref } from 'vue' +type Tree = { id: string; label: string; children?: Tree[] } +export const treeData = ref([ + { + id: '1', + label: '清单目录', + children: [ + { + id: '2', + label: '广东', + children: [ + { id: '3', label: '广东工民建工料机工程' }, + { id: '4', label: '广东公路工料机工程' }, + { id: '5', label: '广东水利工料机工程' } + ] + } + ] + }, + { + id: '11', + label: '清单目录2', + children: [ + { + id: '12', + label: '广西', + children: [ + { id: '13', label: '工料机1费率工程' }, + { id: '14', label: '工料机2费率工程' }, + { id: '15', label: '工料机3费率工程' } + ] + } + ] + } +]) + +// 直接使用配置对象,DbTree 会自动创建 HierarchyContextMenuHandler +export const contextMenuHandler = { + rootKey: 'add-category', + rootText: '添加定额', + levels: [ + { + depth: 0, + addKey: 'add-province', + addText: '添加省市', + allowDelete: true + }, + { + depth: 1, + addKey: 'add-name', + addText: '添加名称', + allowDelete: true + }, + { + depth: 2, + addKey: 'add-model', + addText: '添加模式', + allowDelete: true + }, + { + depth: 3, + allowDelete: true + } + ] +} \ No newline at end of file diff --git a/apps/web-ele/src/views/database/list/config.vue b/apps/web-ele/src/views/database/list/config.vue new file mode 100644 index 0000000..be6d837 --- /dev/null +++ b/apps/web-ele/src/views/database/list/config.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/apps/web-ele/src/views/database/list/guide.ts b/apps/web-ele/src/views/database/list/guide.ts new file mode 100644 index 0000000..1db5a3f --- /dev/null +++ b/apps/web-ele/src/views/database/list/guide.ts @@ -0,0 +1,33 @@ +//清单指引 +import { ref } from 'vue' +const columns = ref([ +{type:'text',data:'code',title:'编码'}, +{type:'text',data:'name',title:'名称'}, +{type:'text',data:'unit',title:'单位',width:30}, + +]) + +const mockData = ()=>{ + const units = ['m³', 'm²', 'm', 'kg', 't', '个', '套', '台', '块', '根'] + const categories = ['混凝土', '钢材', '木材', '砖瓦', '管材', '电缆', '涂料', '五金', '设备', '其他'] + + const mockData = Array.from({ length: 30 }, (_, index) => ({ + code: `A${String(index + 1).padStart(6, '0')}`, + name: `${categories[index % categories.length]}材料${index + 1}`, + unit: units[index % units.length], + })) + return mockData; +} +let rowSchema: any = {} +// 根据 columns 的 data 字段生成对象结构 +columns.value.forEach((col: any) => { + if (col.data) { + rowSchema[col.data] = null + } +}) +export let dbSettings = { + data: mockData(), + dataSchema: rowSchema, + colWidths: 100, + columns: columns.value +} diff --git a/apps/web-ele/src/views/database/list/project.ts b/apps/web-ele/src/views/database/list/project.ts new file mode 100644 index 0000000..491e32c --- /dev/null +++ b/apps/web-ele/src/views/database/list/project.ts @@ -0,0 +1,108 @@ +//工程 +import { ref } from 'vue' +import { handleRowOperation, codeRenderer } from '#/components/db-hst/tree' +const columns = ref([ +{type:'text',data:'code',title:'编号',renderer: codeRenderer}, +{type:'text',data:'name',title:'名称'}, +{type:'text',data:'unit',title:'单位', width: 50}, +]) +const mockData = ()=>{ + const mockData = [] + let codeCounter = 1 + + // 生成5个父级工程项目 + for (let i = 0; i < 5; i++) { + const parentCode = `PRJ${String(codeCounter++).padStart(2, '0')}` + const parent = { + code: parentCode, + name: `工程分类${i + 1}`, + unit: ['项', 'm³', 'm²', 't', '套'][i % 5], + level: String(i), + __children: [] + } + + // 为每个父级生成2-4个子项 + const childCount = Math.floor(Math.random() * 3) + 2 + for (let j = 0; j < childCount; j++) { + const childCode = `${parentCode}-${String(j + 1).padStart(3, '0')}` + const child = { + code: childCode, + name: `${parent.name}-子项${j + 1}`, + unit: ['m³', 'm²', 't', '套', '台'][j % 5], + level: `${i}-${j + 1}`, + __children: [] + } + + // 为部分子项生成孙项 + if (j === 0 && i < 3) { + const grandChildCount = Math.floor(Math.random() * 2) + 1 + for (let k = 0; k < grandChildCount; k++) { + const grandChild = { + code: `${childCode}-${String(k + 1).padStart(2, '0')}`, + name: `${child.name}-明细${k + 1}`, + unit: ['m³', 'm²', 't', 'kg', '个'][k % 5], + level: `${i}-${j + 1}-${k + 1}`, + __children: [] + } + child.__children.push(grandChild) + } + } + + parent.__children.push(child) + } + + mockData.push(parent) + } + + return mockData +} +let rowSchema: any = {level: null, __children: []} +// 根据 columns 的 data 字段生成对象结构 +columns.value.forEach((col: any) => { + if (col.data && col.data !== 'level' && col.data !== '__children') { + rowSchema[col.data] = null + } +}) +export let dbSettings = { + data: mockData(), + dataSchema: rowSchema, + colWidths: 160, + columns: columns.value, + rowHeaders: false, + nestedRows: true, + bindRowsWithHeaders: true, + contextMenu: { + items: { + custom_row_above: { + name: '在上方插入行', + callback: function() { + handleRowOperation(this, 'above') + } + }, + custom_row_below: { + name: '在下方插入行', + callback: function() { + handleRowOperation(this, 'below') + } + }, + separator1: '---------', + custom_add_child: { + name: '添加子行', + callback: function() { + handleRowOperation(this, 'child') + } + }, + separator2: '---------', + remove_row: { + name: '删除行', + callback: function() { + handleRowOperation(this, 'delete') + } + }, + // separator3: '---------', + // undo: {}, + // redo: {} + } + }, +} + diff --git a/apps/web-ele/src/views/database/list/rightHst.ts b/apps/web-ele/src/views/database/list/rightHst.ts new file mode 100644 index 0000000..c7e73ff --- /dev/null +++ b/apps/web-ele/src/views/database/list/rightHst.ts @@ -0,0 +1,33 @@ +//右下角 +import { ref } from 'vue' +const columns = ref([ +{type:'text',data:'code',title:'编码'}, +{type:'text',data:'name',title:'名称'}, +{type:'text',data:'unit',title:'单位',width:30}, + +]) + +const mockData = ()=>{ + const units = ['m³', 'm²', 'm', 'kg', 't', '个', '套', '台', '块', '根'] + const categories = ['混凝土', '钢材', '木材', '砖瓦', '管材', '电缆', '涂料', '五金', '设备', '其他'] + + const mockData = Array.from({ length: 30 }, (_, index) => ({ + code: `B${String(index + 1).padStart(6, '0')}`, + name: `${categories[index % categories.length]}材料${index + 1}`, + unit: units[index % units.length], + })) + return mockData; +} +let rowSchema: any = {} +// 根据 columns 的 data 字段生成对象结构 +columns.value.forEach((col: any) => { + if (col.data) { + rowSchema[col.data] = null + } +}) +export let dbSettings = { + data: mockData(), + dataSchema: rowSchema, + colWidths: 100, + columns: columns.value +} diff --git a/apps/web-ele/src/views/database/list/rightTree.ts b/apps/web-ele/src/views/database/list/rightTree.ts new file mode 100644 index 0000000..d05d620 --- /dev/null +++ b/apps/web-ele/src/views/database/list/rightTree.ts @@ -0,0 +1,66 @@ +//右上 +// console.log(rowSchema) +import { ref } from 'vue' +type Tree = { id: string; label: string; children?: Tree[] } +export const treeData = ref([ + { + id: '1', + label: '清单目录', + children: [ + { + id: '2', + label: '广东', + children: [ + { id: '3', label: '广东工民建工料机工程' }, + { id: '4', label: '广东公路工料机工程' }, + { id: '5', label: '广东水利工料机工程' } + ] + } + ] + }, + { + id: '11', + label: '清单目录2', + children: [ + { + id: '12', + label: '广西', + children: [ + { id: '13', label: '工料机1费率工程' }, + { id: '14', label: '工料机2费率工程' }, + { id: '15', label: '工料机3费率工程' } + ] + } + ] + } +]) + +// 直接使用配置对象,DbTree 会自动创建 HierarchyContextMenuHandler +export const contextMenuHandler = { + rootKey: 'add-category', + rootText: '添加定额', + levels: [ + { + depth: 0, + addKey: 'add-province', + addText: '添加省市', + allowDelete: true + }, + { + depth: 1, + addKey: 'add-name', + addText: '添加名称', + allowDelete: true + }, + { + depth: 2, + addKey: 'add-model', + addText: '添加模式', + allowDelete: true + }, + { + depth: 3, + allowDelete: true + } + ] +} \ No newline at end of file diff --git a/apps/web-ele/src/views/database/list/sub.ts b/apps/web-ele/src/views/database/list/sub.ts new file mode 100644 index 0000000..a18415b --- /dev/null +++ b/apps/web-ele/src/views/database/list/sub.ts @@ -0,0 +1,35 @@ +//清单子目 +import { ref } from 'vue' +const columns = ref([ +{type:'text',data:'code',title:'编码'}, +{type:'text',data:'name',title:'名称'}, +{type:'text',data:'unit',title:'单位',width:30}, +{type:'text',data:'desc',title:'清单说明'}, + +]) + +const mockData = ()=>{ + const units = ['m³', 'm²', 'm', 'kg', 't', '个', '套', '台', '块', '根'] + const categories = ['混凝土', '钢材', '木材', '砖瓦', '管材', '电缆', '涂料', '五金', '设备', '其他'] + + const mockData = Array.from({ length: 30 }, (_, index) => ({ + code: `MAT${String(index + 1).padStart(6, '0')}`, + name: `${categories[index % categories.length]}材料${index + 1}`, + unit: units[index % units.length], + desc: `这是详细说明,` + })) + return mockData; +} +let rowSchema: any = {} +// 根据 columns 的 data 字段生成对象结构 +columns.value.forEach((col: any) => { + if (col.data) { + rowSchema[col.data] = null + } +}) +export let dbSettings = { + data: mockData(), + dataSchema: rowSchema, + colWidths: 100, + columns: columns.value +} diff --git a/apps/web-ele/src/views/database/materials/category.ts b/apps/web-ele/src/views/database/materials/category.ts new file mode 100644 index 0000000..8680f19 --- /dev/null +++ b/apps/web-ele/src/views/database/materials/category.ts @@ -0,0 +1,22 @@ +export const materialsColHeaders: string[] = [ + '名称', + '类别', + '除税基价代码', + '含税基价代码', + '除税编制代码', + '含税编制代码', +] + +export const materialsColumns:any[] = [ +{type:'text',data:'code',title:'编码'}, +{type:'text',data:'name',title:'名称'}, +{type:'text',data:'category',title:'类别', + renderer: 'db-dropdown', + source: ['人', '人机', '材', '机'], + readOnly: true, +}, +{type:'text',data:'priceExTax',title:'除税基价代码'}, +{type:'text',data:'priceInTax',title:'含税基价代码'}, +{type:'text',data:'priceExTaxComp',title:'除税编制代码'}, +{type:'text',data:'priceInTaxComp',title:'含税编制代码'}, +] diff --git a/apps/web-ele/src/views/database/materials/category.vue b/apps/web-ele/src/views/database/materials/category.vue new file mode 100644 index 0000000..8bd3cb0 --- /dev/null +++ b/apps/web-ele/src/views/database/materials/category.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/apps/web-ele/src/views/database/materials/machine.ts b/apps/web-ele/src/views/database/materials/machine.ts new file mode 100644 index 0000000..a480e30 --- /dev/null +++ b/apps/web-ele/src/views/database/materials/machine.ts @@ -0,0 +1,13 @@ +export const topColHeaders: string[] = [ + '编码', + '名称', + '型号规格', + '类别', + '单位', + '税率', + '除税基价', + '含税基价', + '除税编制价', + '含税编制价', + '计算基数' +] diff --git a/apps/web-ele/src/views/database/materials/machine.vue b/apps/web-ele/src/views/database/materials/machine.vue new file mode 100644 index 0000000..d7be0bd --- /dev/null +++ b/apps/web-ele/src/views/database/materials/machine.vue @@ -0,0 +1,423 @@ + + + + + diff --git a/apps/web-ele/src/views/database/quota/charging.vue b/apps/web-ele/src/views/database/quota/charging.vue new file mode 100644 index 0000000..7662392 --- /dev/null +++ b/apps/web-ele/src/views/database/quota/charging.vue @@ -0,0 +1,349 @@ + + + + + diff --git a/apps/web-ele/src/views/database/quota/price.vue b/apps/web-ele/src/views/database/quota/price.vue new file mode 100644 index 0000000..834de0d --- /dev/null +++ b/apps/web-ele/src/views/database/quota/price.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/apps/web-ele/src/views/database/quota/price/BaseSummary.vue b/apps/web-ele/src/views/database/quota/price/BaseSummary.vue new file mode 100644 index 0000000..7db8a4d --- /dev/null +++ b/apps/web-ele/src/views/database/quota/price/BaseSummary.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/apps/web-ele/src/views/database/quota/price/MarketMaterials.vue b/apps/web-ele/src/views/database/quota/price/MarketMaterials.vue new file mode 100644 index 0000000..0349b8f --- /dev/null +++ b/apps/web-ele/src/views/database/quota/price/MarketMaterials.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/apps/web-ele/src/views/database/quota/price/QuotaAdjustment.vue b/apps/web-ele/src/views/database/quota/price/QuotaAdjustment.vue new file mode 100644 index 0000000..2d9ff72 --- /dev/null +++ b/apps/web-ele/src/views/database/quota/price/QuotaAdjustment.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/apps/web-ele/src/views/database/quota/price/QuotaAdjustmentSetting.vue b/apps/web-ele/src/views/database/quota/price/QuotaAdjustmentSetting.vue new file mode 100644 index 0000000..5b895cf --- /dev/null +++ b/apps/web-ele/src/views/database/quota/price/QuotaAdjustmentSetting.vue @@ -0,0 +1,304 @@ + + + + + diff --git a/apps/web-ele/src/views/database/quota/price/QuotaDescription.vue b/apps/web-ele/src/views/database/quota/price/QuotaDescription.vue new file mode 100644 index 0000000..955d162 --- /dev/null +++ b/apps/web-ele/src/views/database/quota/price/QuotaDescription.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/apps/web-ele/src/views/database/quota/price/SubItemMaterials.vue b/apps/web-ele/src/views/database/quota/price/SubItemMaterials.vue new file mode 100644 index 0000000..6f9e236 --- /dev/null +++ b/apps/web-ele/src/views/database/quota/price/SubItemMaterials.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/apps/web-ele/src/views/database/quota/rate.vue b/apps/web-ele/src/views/database/quota/rate.vue new file mode 100644 index 0000000..7ec0d4b --- /dev/null +++ b/apps/web-ele/src/views/database/quota/rate.vue @@ -0,0 +1,294 @@ + + + + + diff --git a/apps/web-ele/src/views/database/quota/subitems.vue b/apps/web-ele/src/views/database/quota/subitems.vue new file mode 100644 index 0000000..e4102eb --- /dev/null +++ b/apps/web-ele/src/views/database/quota/subitems.vue @@ -0,0 +1,345 @@ + + + + + diff --git a/apps/web-ele/src/views/erp/finance/account/data.ts b/apps/web-ele/src/views/erp/finance/account/data.ts new file mode 100644 index 0000000..497d4e3 --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/account/data.ts @@ -0,0 +1,188 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { ErpAccountApi } from '#/api/erp/finance/account'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '名称', + rules: 'required', + componentProps: { + placeholder: '请输入名称', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + + { + fieldName: 'sort', + label: '排序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入排序', + precision: 0, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + defaultValue: 0, + }, + { + fieldName: 'defaultStatus', + label: '是否默认', + component: 'RadioGroup', + componentProps: { + options: [ + { + label: '是', + value: true, + }, + { + label: '否', + value: false, + }, + ], + }, + rules: z.boolean().default(false).optional(), + }, + { + fieldName: 'no', + label: '编码', + component: 'Input', + componentProps: { + placeholder: '请输入编码', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 3, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名称', + component: 'Input', + componentProps: { + placeholder: '请输入名称', + allowClear: true, + }, + }, + { + fieldName: 'no', + label: '编码', + component: 'Input', + componentProps: { + placeholder: '请输入编码', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onDefaultStatusChange?: ( + newStatus: boolean, + row: ErpAccountApi.Account, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '名称', + minWidth: 150, + }, + { + field: 'no', + title: '编码', + minWidth: 120, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + showOverflow: 'tooltip', + }, + { + field: 'sort', + title: '排序', + minWidth: 80, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'defaultStatus', + title: '是否默认', + minWidth: 100, + cellRender: { + attrs: { beforeChange: onDefaultStatusChange }, + name: 'CellSwitch', + props: { + activeValue: true, + inactiveValue: false, + }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/finance/account/index.vue b/apps/web-ele/src/views/erp/finance/account/index.vue new file mode 100644 index 0000000..1f54939 --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/account/index.vue @@ -0,0 +1,175 @@ + + + diff --git a/apps/web-ele/src/views/erp/finance/account/modules/form.vue b/apps/web-ele/src/views/erp/finance/account/modules/form.vue new file mode 100644 index 0000000..eced899 --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/account/modules/form.vue @@ -0,0 +1,86 @@ + + + diff --git a/apps/web-ele/src/views/erp/finance/payment/data.ts b/apps/web-ele/src/views/erp/finance/payment/data.ts new file mode 100644 index 0000000..1161721 --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/payment/data.ts @@ -0,0 +1,592 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { erpPriceInputFormatter } from '@vben/utils'; + +import { getAccountSimpleList } from '#/api/erp/finance/account'; +import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '付款单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'paymentTime', + label: '付款时间', + component: 'DatePicker', + componentProps: { + disabled: formType === 'detail', + placeholder: '选择付款时间', + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'supplierId', + label: '供应商', + component: 'ApiSelect', + componentProps: { + disabled: formType === 'detail', + placeholder: '请选择供应商', + clearable: true, + filterable: true, + api: getSupplierSimpleList, + labelField: 'name', + valueField: 'id', + }, + rules: 'required', + }, + { + fieldName: 'financeUserId', + label: '财务人员', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择财务人员', + clearable: true, + filterable: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autosize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '采购入库、退货单', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'accountId', + label: '付款账户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择付款账户', + clearable: true, + filterable: true, + api: getAccountSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'totalPrice', + label: '合计付款', + component: 'InputNumber', + componentProps: { + placeholder: '合计付款', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'discountPrice', + label: '优惠金额', + component: 'InputNumber', + componentProps: { + disabled: formType === 'detail', + placeholder: '请输入优惠金额', + precision: 2, + formatter: erpPriceInputFormatter, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'paymentPrice', + label: '实际付款', + component: 'InputNumber', + componentProps: { + placeholder: '实际付款', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['totalPrice', 'discountPrice'], + componentProps: (values) => { + const totalPrice = values.totalPrice || 0; + const discountPrice = values.discountPrice || 0; + values.paymentPrice = totalPrice - discountPrice; + return {}; + }, + }, + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + disabled: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'bizNo', + title: '采购单据编号', + minWidth: 200, + }, + { + field: 'totalPrice', + title: '应付金额', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'paidPrice', + title: '已付金额', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'paymentPrice', + title: '本次付款', + minWidth: 115, + fixed: 'right', + slots: { default: 'paymentPrice' }, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '付款单号', + component: 'Input', + componentProps: { + placeholder: '请输入付款单号', + clearable: true, + }, + }, + { + fieldName: 'paymentTime', + label: '付款时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'supplierId', + label: '供应商', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择供应商', + clearable: true, + filterable: true, + api: getSupplierSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + clearable: true, + filterable: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'financeUserId', + label: '财务人员', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择财务人员', + clearable: true, + filterable: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'accountId', + label: '付款账户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择付款账户', + clearable: true, + filterable: true, + api: getAccountSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择状态', + clearable: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + clearable: true, + }, + }, + { + fieldName: 'bizNo', + label: '采购单号', + component: 'Input', + componentProps: { + placeholder: '请输入采购单号', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '付款单号', + width: 180, + fixed: 'left', + }, + { + field: 'supplierName', + title: '供应商', + minWidth: 120, + }, + { + field: 'paymentTime', + title: '付款时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'financeUserName', + title: '财务人员', + minWidth: 120, + }, + { + field: 'accountName', + title: '付款账户', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '合计付款', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'discountPrice', + title: '优惠金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'paymentPrice', + title: '实际付款', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 90, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 采购入库单选择表单的配置项 */ +export function usePurchaseInGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '入库单号', + component: 'Input', + componentProps: { + placeholder: '请输入入库单号', + clearable: true, + }, + }, + { + fieldName: 'supplierId', + label: '供应商', + component: 'Input', + componentProps: { + disabled: true, + placeholder: '已自动选择供应商', + }, + }, + { + fieldName: 'paymentStatus', + label: '付款状态', + component: 'Select', + componentProps: { + options: [ + { label: '未付款', value: 0 }, + { label: '部分付款', value: 1 }, + { label: '全部付款', value: 2 }, + ], + placeholder: '请选择付款状态', + clearable: true, + }, + }, + ]; +} + +/** 采购入库单选择列表的字段 */ +export function usePurchaseInGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '入库单号', + width: 200, + fixed: 'left', + }, + { + field: 'supplierName', + title: '供应商', + minWidth: 120, + }, + { + field: 'inTime', + title: '入库时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'totalPrice', + title: '应付金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'paymentPrice', + title: '已付金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'unPaymentPrice', + title: '未付金额', + formatter: ({ row }) => { + return erpPriceInputFormatter(row.totalPrice - row.paymentPrice || 0); + }, + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + ]; +} + +/** 采购退货单选择表单的配置项 */ +export function useSaleReturnGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '退货单号', + component: 'Input', + componentProps: { + placeholder: '请输入退货单号', + clearable: true, + }, + }, + { + fieldName: 'supplierId', + label: '供应商', + component: 'Input', + componentProps: { + disabled: true, + placeholder: '已自动选择供应商', + }, + }, + { + fieldName: 'refundStatus', + label: '退款状态', + component: 'Select', + componentProps: { + options: [ + { label: '未退款', value: 0 }, + { label: '部分退款', value: 1 }, + { label: '全部退款', value: 2 }, + ], + placeholder: '请选择退款状态', + clearable: true, + }, + }, + ]; +} + +/** 采购退货单选择列表的字段 */ +export function useSaleReturnGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '退货单号', + width: 200, + fixed: 'left', + }, + { + field: 'supplierName', + title: '供应商', + minWidth: 120, + }, + { + field: 'returnTime', + title: '退货时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'totalPrice', + title: '应退金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'refundPrice', + title: '已退金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'unRefundPrice', + title: '未退金额', + formatter: ({ row }) => { + return erpPriceInputFormatter(row.totalPrice - row.refundPrice || 0); + }, + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/finance/payment/index.vue b/apps/web-ele/src/views/erp/finance/payment/index.vue new file mode 100644 index 0000000..5c6750b --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/payment/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/erp/finance/payment/modules/form.vue b/apps/web-ele/src/views/erp/finance/payment/modules/form.vue new file mode 100644 index 0000000..fe8796e --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/payment/modules/form.vue @@ -0,0 +1,195 @@ + + + diff --git a/apps/web-ele/src/views/erp/finance/payment/modules/item-form.vue b/apps/web-ele/src/views/erp/finance/payment/modules/item-form.vue new file mode 100644 index 0000000..54fd089 --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/payment/modules/item-form.vue @@ -0,0 +1,302 @@ + + + diff --git a/apps/web-ele/src/views/erp/finance/payment/modules/purchase-in-select.vue b/apps/web-ele/src/views/erp/finance/payment/modules/purchase-in-select.vue new file mode 100644 index 0000000..8169157 --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/payment/modules/purchase-in-select.vue @@ -0,0 +1,113 @@ + + + diff --git a/apps/web-ele/src/views/erp/finance/payment/modules/sale-return-select.vue b/apps/web-ele/src/views/erp/finance/payment/modules/sale-return-select.vue new file mode 100644 index 0000000..ec57fd5 --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/payment/modules/sale-return-select.vue @@ -0,0 +1,117 @@ + + + diff --git a/apps/web-ele/src/views/erp/finance/receipt/data.ts b/apps/web-ele/src/views/erp/finance/receipt/data.ts new file mode 100644 index 0000000..19e463c --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/receipt/data.ts @@ -0,0 +1,592 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { erpPriceInputFormatter } from '@vben/utils'; + +import { getAccountSimpleList } from '#/api/erp/finance/account'; +import { getCustomerSimpleList } from '#/api/erp/sale/customer'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '收款单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'receiptTime', + label: '收款时间', + component: 'DatePicker', + componentProps: { + disabled: formType === 'detail', + placeholder: '选择收款时间', + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + disabled: formType === 'detail', + placeholder: '请选择客户', + allowClear: true, + showSearch: true, + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + }, + rules: 'required', + }, + { + fieldName: 'financeUserId', + label: '财务人员', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择财务人员', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autoSize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '销售出库、退货单', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'accountId', + label: '收款账户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择收款账户', + allowClear: true, + showSearch: true, + api: getAccountSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'totalPrice', + label: '合计收款', + component: 'InputNumber', + componentProps: { + placeholder: '合计收款', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'discountPrice', + label: '优惠金额', + component: 'InputNumber', + componentProps: { + disabled: formType === 'detail', + placeholder: '请输入优惠金额', + precision: 2, + formatter: erpPriceInputFormatter, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'receiptPrice', + label: '实际收款', + component: 'InputNumber', + componentProps: { + placeholder: '实际收款', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['totalPrice', 'discountPrice'], + componentProps: (values) => { + const totalPrice = values.totalPrice || 0; + const discountPrice = values.discountPrice || 0; + values.receiptPrice = totalPrice - discountPrice; + return {}; + }, + }, + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + disabled: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'bizNo', + title: '销售单据编号', + minWidth: 200, + }, + { + field: 'totalPrice', + title: '应收金额', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'receiptedPrice', + title: '已收金额', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'receiptPrice', + title: '本次收款', + minWidth: 115, + fixed: 'right', + slots: { default: 'receiptPrice' }, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '收款单号', + component: 'Input', + componentProps: { + placeholder: '请输入收款单号', + allowClear: true, + }, + }, + { + fieldName: 'receiptTime', + label: '收款时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择客户', + allowClear: true, + showSearch: true, + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'financeUserId', + label: '财务人员', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择财务人员', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'accountId', + label: '收款账户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择收款账户', + allowClear: true, + showSearch: true, + api: getAccountSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + { + fieldName: 'bizNo', + label: '销售单号', + component: 'Input', + componentProps: { + placeholder: '请输入销售单号', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '收款单号', + width: 180, + fixed: 'left', + }, + { + field: 'customerName', + title: '客户', + minWidth: 120, + }, + { + field: 'receiptTime', + title: '收款时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'financeUserName', + title: '财务人员', + minWidth: 120, + }, + { + field: 'accountName', + title: '收款账户', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '合计收款', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'discountPrice', + title: '优惠金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'receiptPrice', + title: '实际收款', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 90, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 销售出库单选择表单的配置项 */ +export function useSaleOutGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '出库单号', + component: 'Input', + componentProps: { + placeholder: '请输入出库单号', + allowClear: true, + }, + }, + { + fieldName: 'customerId', + label: '客户', + component: 'Input', + componentProps: { + disabled: true, + placeholder: '已自动选择客户', + }, + }, + { + fieldName: 'receiptStatus', + label: '收款状态', + component: 'Select', + componentProps: { + options: [ + { label: '未收款', value: 0 }, + { label: '部分收款', value: 1 }, + { label: '全部收款', value: 2 }, + ], + placeholder: '请选择收款状态', + allowClear: true, + }, + }, + ]; +} + +/** 销售出库单选择列表的字段 */ +export function useSaleOutGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '出库单号', + width: 200, + fixed: 'left', + }, + { + field: 'customerName', + title: '客户', + minWidth: 120, + }, + { + field: 'outTime', + title: '出库时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'totalPrice', + title: '应收金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'receiptPrice', + title: '已收金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'unReceiptPrice', + title: '未收金额', + formatter: ({ row }) => { + return erpPriceInputFormatter(row.totalPrice - row.receiptPrice || 0); + }, + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + ]; +} + +/** 销售退货单选择表单的配置项 */ +export function useSaleReturnGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '退货单号', + component: 'Input', + componentProps: { + placeholder: '请输入退货单号', + allowClear: true, + }, + }, + { + fieldName: 'customerId', + label: '客户', + component: 'Input', + componentProps: { + disabled: true, + placeholder: '已自动选择客户', + }, + }, + { + fieldName: 'refundStatus', + label: '退款状态', + component: 'Select', + componentProps: { + options: [ + { label: '未退款', value: 0 }, + { label: '部分退款', value: 1 }, + { label: '全部退款', value: 2 }, + ], + placeholder: '请选择退款状态', + allowClear: true, + }, + }, + ]; +} + +/** 销售退货单选择列表的字段 */ +export function useSaleReturnGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '退货单号', + width: 200, + fixed: 'left', + }, + { + field: 'customerName', + title: '客户', + minWidth: 120, + }, + { + field: 'returnTime', + title: '退货时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'totalPrice', + title: '应退金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'refundPrice', + title: '已退金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'unRefundPrice', + title: '未退金额', + formatter: ({ row }) => { + return erpPriceInputFormatter(row.totalPrice - row.refundPrice || 0); + }, + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/finance/receipt/index.vue b/apps/web-ele/src/views/erp/finance/receipt/index.vue new file mode 100644 index 0000000..53a84ee --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/receipt/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/erp/finance/receipt/modules/form.vue b/apps/web-ele/src/views/erp/finance/receipt/modules/form.vue new file mode 100644 index 0000000..8464cce --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/receipt/modules/form.vue @@ -0,0 +1,209 @@ + + + diff --git a/apps/web-ele/src/views/erp/finance/receipt/modules/item-form.vue b/apps/web-ele/src/views/erp/finance/receipt/modules/item-form.vue new file mode 100644 index 0000000..be746db --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/receipt/modules/item-form.vue @@ -0,0 +1,299 @@ + + + diff --git a/apps/web-ele/src/views/erp/finance/receipt/modules/sale-out-select.vue b/apps/web-ele/src/views/erp/finance/receipt/modules/sale-out-select.vue new file mode 100644 index 0000000..b5d2def --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/receipt/modules/sale-out-select.vue @@ -0,0 +1,110 @@ + + + diff --git a/apps/web-ele/src/views/erp/finance/receipt/modules/sale-return-select.vue b/apps/web-ele/src/views/erp/finance/receipt/modules/sale-return-select.vue new file mode 100644 index 0000000..8bef717 --- /dev/null +++ b/apps/web-ele/src/views/erp/finance/receipt/modules/sale-return-select.vue @@ -0,0 +1,114 @@ + + + diff --git a/apps/web-ele/src/views/erp/home/index.vue b/apps/web-ele/src/views/erp/home/index.vue new file mode 100644 index 0000000..9f6f2ea --- /dev/null +++ b/apps/web-ele/src/views/erp/home/index.vue @@ -0,0 +1,51 @@ + + + diff --git a/apps/web-ele/src/views/erp/home/modules/summary-card.vue b/apps/web-ele/src/views/erp/home/modules/summary-card.vue new file mode 100644 index 0000000..ff98e55 --- /dev/null +++ b/apps/web-ele/src/views/erp/home/modules/summary-card.vue @@ -0,0 +1,69 @@ + + + diff --git a/apps/web-ele/src/views/erp/home/modules/time-summary-chart.vue b/apps/web-ele/src/views/erp/home/modules/time-summary-chart.vue new file mode 100644 index 0000000..fe164eb --- /dev/null +++ b/apps/web-ele/src/views/erp/home/modules/time-summary-chart.vue @@ -0,0 +1,161 @@ + + + diff --git a/apps/web-ele/src/views/erp/product/category/data.ts b/apps/web-ele/src/views/erp/product/category/data.ts new file mode 100644 index 0000000..e6b13c9 --- /dev/null +++ b/apps/web-ele/src/views/erp/product/category/data.ts @@ -0,0 +1,149 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { ErpProductCategoryApi } from '#/api/erp/product/category'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { handleTree } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getProductCategoryList } from '#/api/erp/product/category'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'parentId', + label: '上级分类', + component: 'ApiTreeSelect', + componentProps: { + allowClear: true, + api: async () => { + const data = await getProductCategoryList(); + data.unshift({ + id: 0, + name: '顶级分类', + }); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择上级分类', + treeDefaultExpandAll: true, + }, + rules: 'selectRequired', + }, + { + fieldName: 'name', + label: '分类名称', + component: 'Input', + componentProps: { + placeholder: '请输入分类名称', + }, + rules: 'required', + }, + { + fieldName: 'code', + label: '分类编码', + component: 'Input', + componentProps: { + placeholder: '请输入分类编码', + }, + rules: 'required', + }, + { + fieldName: 'sort', + label: '显示顺序', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入显示顺序', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 查询表单 */ +export function useQueryFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: '分类名称', + componentProps: { + placeholder: '请输入分类名称', + allowClear: true, + }, + }, + { + component: 'Select', + fieldName: 'status', + label: '开启状态', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择开启状态', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '分类名称', + align: 'left', + treeNode: true, + }, + { + field: 'code', + title: '分类编码', + }, + { + field: 'sort', + title: '显示顺序', + }, + { + field: 'status', + title: '分类状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/product/category/index.vue b/apps/web-ele/src/views/erp/product/category/index.vue new file mode 100644 index 0000000..19c20d3 --- /dev/null +++ b/apps/web-ele/src/views/erp/product/category/index.vue @@ -0,0 +1,183 @@ + + + diff --git a/apps/web-ele/src/views/erp/product/category/modules/form.vue b/apps/web-ele/src/views/erp/product/category/modules/form.vue new file mode 100644 index 0000000..981feb5 --- /dev/null +++ b/apps/web-ele/src/views/erp/product/category/modules/form.vue @@ -0,0 +1,91 @@ + + + diff --git a/apps/web-ele/src/views/erp/product/product/data.ts b/apps/web-ele/src/views/erp/product/product/data.ts new file mode 100644 index 0000000..8bfa9eb --- /dev/null +++ b/apps/web-ele/src/views/erp/product/product/data.ts @@ -0,0 +1,258 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { handleTree } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getProductCategorySimpleList } from '#/api/erp/product/category'; +import { getProductUnitSimpleList } from '#/api/erp/product/unit'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '名称', + rules: 'required', + componentProps: { + placeholder: '请输入名称', + }, + }, + { + fieldName: 'barCode', + label: '条码', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入条码', + }, + }, + { + fieldName: 'categoryId', + label: '分类', + component: 'ApiTreeSelect', + componentProps: { + api: async () => { + const data = await getProductCategorySimpleList(); + return handleTree(data); + }, + + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择分类', + treeDefaultExpandAll: true, + }, + rules: 'required', + }, + { + fieldName: 'unitId', + label: '单位', + component: 'ApiSelect', + componentProps: { + api: getProductUnitSimpleList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择单位', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'standard', + label: '规格', + component: 'Input', + componentProps: { + placeholder: '请输入规格', + }, + }, + { + fieldName: 'expiryDay', + label: '保质期天数', + component: 'InputNumber', + componentProps: { + placeholder: '请输入保质期天数', + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'weight', + label: '重量(kg)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入重量(kg)', + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'purchasePrice', + label: '采购价格', + component: 'InputNumber', + componentProps: { + placeholder: '请输入采购价格,单位:元', + precision: 2, + min: 0, + step: 0.01, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'salePrice', + label: '销售价格', + component: 'InputNumber', + componentProps: { + placeholder: '请输入销售价格,单位:元', + precision: 2, + min: 0, + step: 0.01, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'minPrice', + label: '最低价格', + component: 'InputNumber', + componentProps: { + placeholder: '请输入最低价格,单位:元', + precision: 2, + min: 0, + step: 0.01, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名称', + component: 'Input', + componentProps: { + placeholder: '请输入名称', + allowClear: true, + }, + }, + { + fieldName: 'categoryId', + label: '分类', + component: 'ApiTreeSelect', + componentProps: { + api: async () => { + const data = await getProductCategorySimpleList(); + return handleTree(data); + }, + + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择分类', + treeDefaultExpandAll: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'barCode', + title: '条码', + minWidth: 120, + }, + { + field: 'name', + title: '名称', + minWidth: 200, + }, + { + field: 'standard', + title: '规格', + minWidth: 100, + }, + { + field: 'categoryName', + title: '分类', + minWidth: 120, + }, + { + field: 'unitName', + title: '单位', + minWidth: 100, + }, + { + field: 'purchasePrice', + title: '采购价格', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'salePrice', + title: '销售价格', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'minPrice', + title: '最低价格', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/product/product/index.vue b/apps/web-ele/src/views/erp/product/product/index.vue new file mode 100644 index 0000000..f6bce71 --- /dev/null +++ b/apps/web-ele/src/views/erp/product/product/index.vue @@ -0,0 +1,150 @@ + + + diff --git a/apps/web-ele/src/views/erp/product/product/modules/form.vue b/apps/web-ele/src/views/erp/product/product/modules/form.vue new file mode 100644 index 0000000..baed5a7 --- /dev/null +++ b/apps/web-ele/src/views/erp/product/product/modules/form.vue @@ -0,0 +1,85 @@ + + + diff --git a/apps/web-ele/src/views/erp/product/unit/data.ts b/apps/web-ele/src/views/erp/product/unit/data.ts new file mode 100644 index 0000000..0914528 --- /dev/null +++ b/apps/web-ele/src/views/erp/product/unit/data.ts @@ -0,0 +1,101 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '单位名称', + rules: 'required', + componentProps: { + placeholder: '请输入单位名称', + }, + }, + { + fieldName: 'status', + label: '单位状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '单位名称', + component: 'Input', + componentProps: { + placeholder: '请输入单位名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '单位状态', + component: 'Select', + componentProps: { + placeholder: '请选择单位状态', + allowClear: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '单位编号', + minWidth: 100, + }, + { + field: 'name', + title: '单位名称', + minWidth: 200, + }, + { + field: 'status', + title: '单位状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/product/unit/index.vue b/apps/web-ele/src/views/erp/product/unit/index.vue new file mode 100644 index 0000000..1997240 --- /dev/null +++ b/apps/web-ele/src/views/erp/product/unit/index.vue @@ -0,0 +1,149 @@ + + + diff --git a/apps/web-ele/src/views/erp/product/unit/modules/form.vue b/apps/web-ele/src/views/erp/product/unit/modules/form.vue new file mode 100644 index 0000000..70b3a98 --- /dev/null +++ b/apps/web-ele/src/views/erp/product/unit/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/in/data.ts b/apps/web-ele/src/views/erp/purchase/in/data.ts new file mode 100644 index 0000000..4b4233c --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/in/data.ts @@ -0,0 +1,624 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { erpNumberFormatter, erpPriceInputFormatter } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getAccountSimpleList } from '#/api/erp/finance/account'; +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; +import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '入库单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'inTime', + label: '入库时间', + component: 'DatePicker', + componentProps: { + disabled: formType === 'detail', + placeholder: '选择入库时间', + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'orderNo', + label: '关联订单', + component: 'Input', + formItemClass: 'col-span-1', + rules: 'required', + componentProps: { + placeholder: '请选择关联订单', + disabled: formType === 'detail', + }, + }, + { + fieldName: 'supplierId', + label: '供应商', + component: 'ApiSelect', + componentProps: { + disabled: true, + placeholder: '请选择供应商', + allowClear: true, + showSearch: true, + api: getSupplierSimpleList, + labelField: 'name', + valueField: 'id', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autoSize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '入库产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'discountPercent', + label: '优惠率(%)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入优惠率', + min: 0, + max: 100, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional(), + }, + { + fieldName: 'discountPrice', + label: '付款优惠', + component: 'InputNumber', + componentProps: { + placeholder: '付款优惠', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'discountedPrice', + label: '优惠后金额', + component: 'InputNumber', + componentProps: { + placeholder: '优惠后金额', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['totalPrice', 'otherPrice'], + componentProps: (values) => { + const totalPrice = values.totalPrice || 0; + const otherPrice = values.otherPrice || 0; + values.discountedPrice = totalPrice - otherPrice; + return {}; + }, + }, + }, + { + fieldName: 'otherPrice', + label: '其他费用', + component: 'InputNumber', + componentProps: { + disabled: formType === 'detail', + placeholder: '请输入其他费用', + precision: 2, + formatter: erpPriceInputFormatter, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'accountId', + label: '结算账户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择结算账户', + allowClear: true, + showSearch: true, + api: getAccountSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'totalPrice', + label: '应付金额', + component: 'InputNumber', + componentProps: { + precision: 2, + min: 0, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional(), + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + formData?: any[], + disabled?: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'warehouseId', + title: '仓库名称', + minWidth: 200, + slots: { default: 'warehouseId' }, + }, + { + field: 'productId', + title: '产品名称', + minWidth: 200, + slots: { default: 'productId' }, + }, + { + field: 'stockCount', + title: '库存', + minWidth: 80, + }, + { + field: 'productBarCode', + title: '条码', + minWidth: 120, + }, + { + field: 'productUnitName', + title: '单位', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + field: 'totalCount', + title: '原数量', + formatter: 'formatAmount3', + minWidth: 120, + fixed: 'right', + visible: formData && formData[0]?.inCount !== undefined, + }, + { + field: 'inCount', + title: '已入库', + formatter: 'formatAmount3', + minWidth: 120, + fixed: 'right', + visible: formData && formData[0]?.returnCount !== undefined, + }, + { + field: 'count', + title: '数量', + minWidth: 120, + fixed: 'right', + slots: { default: 'count' }, + }, + { + field: 'productPrice', + title: '产品单价', + fixed: 'right', + minWidth: 120, + slots: { default: 'productPrice' }, + }, + { + field: 'totalProductPrice', + fixed: 'right', + title: '产品金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + fixed: 'right', + field: 'taxPercent', + title: '税率(%)', + minWidth: 105, + slots: { default: 'taxPercent' }, + }, + { + fixed: 'right', + field: 'taxPrice', + title: '税额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'totalPrice', + fixed: 'right', + title: '合计金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '入库单号', + component: 'Input', + componentProps: { + placeholder: '请输入入库单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'inTime', + label: '入库时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'supplierId', + label: '供应商', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择供应商', + allowClear: true, + showSearch: true, + api: getSupplierSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'warehouseId', + label: '仓库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择仓库', + allowClear: true, + showSearch: true, + api: getWarehouseSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'orderNo', + label: '关联订单', + component: 'Input', + componentProps: { + placeholder: '请输入关联订单号', + allowClear: true, + }, + }, + { + fieldName: 'accountId', + label: '结算账户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择结算账户', + allowClear: true, + showSearch: true, + api: getAccountSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'paymentStatus', + label: '付款状态', + component: 'Select', + componentProps: { + options: [ + { label: '未付款', value: 0 }, + { label: '部分付款', value: 1 }, + { label: '全部付款', value: 2 }, + ], + placeholder: '请选择付款状态', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '审批状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择审批状态', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '入库单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'supplierName', + title: '供应商', + minWidth: 120, + }, + { + field: 'inTime', + title: '入库时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '应付金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'paymentPrice', + title: '已付金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'unPaymentPrice', + title: '未付金额', + formatter: ({ row }) => { + return `${erpNumberFormatter(row.totalPrice - row.paymentPrice, 2)}元`; + }, + minWidth: 120, + }, + { + field: 'status', + title: '审批状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useOrderGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '订单单号', + component: 'Input', + componentProps: { + placeholder: '请输入订单单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'orderTime', + label: '订单时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useOrderGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'radio', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '订单单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'supplierName', + title: '供应商', + minWidth: 120, + }, + { + field: 'orderTime', + title: '订单时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'inCount', + title: '入库数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalProductPrice', + title: '金额合计', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '含税金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/purchase/in/index.vue b/apps/web-ele/src/views/erp/purchase/in/index.vue new file mode 100644 index 0000000..2b5c1cb --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/in/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/in/modules/form.vue b/apps/web-ele/src/views/erp/purchase/in/modules/form.vue new file mode 100644 index 0000000..21cfadf --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/in/modules/form.vue @@ -0,0 +1,231 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/in/modules/item-form.vue b/apps/web-ele/src/views/erp/purchase/in/modules/item-form.vue new file mode 100644 index 0000000..6ec8a50 --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/in/modules/item-form.vue @@ -0,0 +1,310 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/in/modules/purchase-order-select.vue b/apps/web-ele/src/views/erp/purchase/in/modules/purchase-order-select.vue new file mode 100644 index 0000000..0f1815f --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/in/modules/purchase-order-select.vue @@ -0,0 +1,124 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/order/data.ts b/apps/web-ele/src/views/erp/purchase/order/data.ts new file mode 100644 index 0000000..489af22 --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/order/data.ts @@ -0,0 +1,454 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { erpPriceInputFormatter } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getAccountSimpleList } from '#/api/erp/finance/account'; +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '订单单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'orderTime', + label: '订单时间', + component: 'DatePicker', + componentProps: { + placeholder: '选择订单时间', + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + label: '供应商', + fieldName: 'supplierId', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择供应商', + allowClear: true, + showSearch: true, + api: getSupplierSimpleList, + labelField: 'name', + valueField: 'id', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autoSize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '采购产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'discountPercent', + label: '优惠率(%)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入优惠率', + min: 0, + max: 100, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional(), + }, + { + fieldName: 'discountPrice', + label: '付款优惠', + component: 'InputNumber', + componentProps: { + placeholder: '付款优惠', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'totalPrice', + label: '优惠后金额', + component: 'InputNumber', + componentProps: { + placeholder: '优惠后金额', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'accountId', + label: '结算账户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择结算账户', + allowClear: true, + showSearch: true, + api: getAccountSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + component: 'InputNumber', + componentProps: { + placeholder: '请输入支付订金', + precision: 2, + min: 0, + controlsPosition: 'right', + class: '!w-full', + }, + fieldName: 'depositPrice', + label: '支付订金', + rules: z.number().min(0).optional(), + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + disabled: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'productId', + title: '产品名称', + minWidth: 200, + slots: { default: 'productId' }, + }, + { + field: 'stockCount', + title: '库存', + minWidth: 80, + }, + { + field: 'productBarCode', + title: '条码', + minWidth: 120, + }, + { + field: 'productUnitName', + title: '单位', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + field: 'count', + title: '数量', + minWidth: 120, + fixed: 'right', + slots: { default: 'count' }, + }, + { + field: 'productPrice', + title: '产品单价', + minWidth: 120, + fixed: 'right', + slots: { default: 'productPrice' }, + }, + { + field: 'totalProductPrice', + title: '金额', + minWidth: 120, + fixed: 'right', + formatter: 'formatAmount2', + }, + { + field: 'taxPercent', + title: '税率(%)', + minWidth: 105, + fixed: 'right', + slots: { default: 'taxPercent' }, + }, + { + field: 'taxPrice', + title: '税额', + minWidth: 120, + fixed: 'right', + formatter: 'formatAmount2', + }, + { + field: 'totalPrice', + title: '税额合计', + minWidth: 120, + fixed: 'right', + formatter: 'formatAmount2', + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '订单单号', + component: 'Input', + componentProps: { + placeholder: '请输入订单单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'orderTime', + label: '订单时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'supplierId', + label: '供应商', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择供应商', + allowClear: true, + showSearch: true, + api: getSupplierSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + { + fieldName: 'inStatus', + label: '入库状态', + component: 'Select', + componentProps: { + options: [ + { label: '未入库', value: 0 }, + { label: '部分入库', value: 1 }, + { label: '全部入库', value: 2 }, + ], + placeholder: '请选择入库状态', + allowClear: true, + }, + }, + { + fieldName: 'returnStatus', + label: '退货状态', + component: 'Select', + componentProps: { + options: [ + { label: '未退货', value: 0 }, + { label: '部分退货', value: 1 }, + { label: '全部退货', value: 2 }, + ], + placeholder: '请选择退货状态', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '订单单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'supplierName', + title: '供应商', + minWidth: 120, + }, + { + field: 'orderTime', + title: '订单时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'inCount', + title: '入库数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'returnCount', + title: '退货数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalProductPrice', + title: '金额合计', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '含税金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'depositPrice', + title: '支付订金', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/purchase/order/index.vue b/apps/web-ele/src/views/erp/purchase/order/index.vue new file mode 100644 index 0000000..024e3c2 --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/order/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/order/modules/form.vue b/apps/web-ele/src/views/erp/purchase/order/modules/form.vue new file mode 100644 index 0000000..03e9129 --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/order/modules/form.vue @@ -0,0 +1,172 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/order/modules/item-form.vue b/apps/web-ele/src/views/erp/purchase/order/modules/item-form.vue new file mode 100644 index 0000000..0d32f3a --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/order/modules/item-form.vue @@ -0,0 +1,329 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/return/data.ts b/apps/web-ele/src/views/erp/purchase/return/data.ts new file mode 100644 index 0000000..6a26869 --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/return/data.ts @@ -0,0 +1,617 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { erpNumberFormatter, erpPriceInputFormatter } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getAccountSimpleList } from '#/api/erp/finance/account'; +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; +import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '退货单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'returnTime', + label: '退货时间', + component: 'DatePicker', + componentProps: { + disabled: formType === 'detail', + placeholder: '选择退货时间', + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'orderNo', + label: '关联订单', + component: 'Input', + formItemClass: 'col-span-1', + rules: 'required', + componentProps: { + placeholder: '请选择关联订单', + disabled: formType === 'detail', + }, + }, + { + fieldName: 'supplierId', + label: '供应商', + component: 'ApiSelect', + componentProps: { + disabled: true, + placeholder: '请选择供应商', + allowClear: true, + showSearch: true, + api: getSupplierSimpleList, + labelField: 'name', + valueField: 'id', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autoSize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '退货产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'discountPercent', + label: '优惠率(%)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入优惠率', + min: 0, + max: 100, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional(), + }, + { + fieldName: 'discountPrice', + label: '退款优惠', + component: 'InputNumber', + componentProps: { + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'discountedPrice', + label: '优惠后金额', + component: 'InputNumber', + componentProps: { + placeholder: '优惠后金额', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['totalPrice', 'otherPrice'], + componentProps: (values) => { + const totalPrice = values.totalPrice || 0; + const otherPrice = values.otherPrice || 0; + values.discountedPrice = totalPrice - otherPrice; + return {}; + }, + }, + }, + { + fieldName: 'otherPrice', + label: '其他费用', + component: 'InputNumber', + componentProps: { + disabled: formType === 'detail', + placeholder: '请输入其他费用', + precision: 2, + formatter: erpPriceInputFormatter, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'accountId', + label: '结算账户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择结算账户', + allowClear: true, + showSearch: true, + api: getAccountSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'totalPrice', + label: '应退金额', + component: 'InputNumber', + componentProps: { + precision: 2, + min: 0, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional(), + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + formData?: any[], + disabled?: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'warehouseId', + title: '仓库名称', + minWidth: 200, + slots: { default: 'warehouseId' }, + }, + { + field: 'productId', + title: '产品名称', + minWidth: 200, + slots: { default: 'productId' }, + }, + { + field: 'stockCount', + title: '库存', + minWidth: 80, + formatter: 'formatAmount3', + }, + { + field: 'productBarCode', + title: '条码', + minWidth: 120, + }, + { + field: 'productUnitName', + title: '单位', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + field: 'inCount', + title: '已入库', + formatter: 'formatAmount3', + minWidth: 120, + fixed: 'right', + visible: formData && formData[0]?.inCount !== undefined, + }, + { + field: 'returnCount', + title: '已退货', + formatter: 'formatAmount3', + minWidth: 120, + fixed: 'right', + visible: formData && formData[0]?.returnCount !== undefined, + }, + { + field: 'count', + title: '数量', + minWidth: 120, + fixed: 'right', + slots: { default: 'count' }, + }, + { + field: 'productPrice', + title: '产品单价', + fixed: 'right', + minWidth: 120, + slots: { default: 'productPrice' }, + }, + { + field: 'totalProductPrice', + fixed: 'right', + title: '产品金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + fixed: 'right', + field: 'taxPercent', + title: '税率(%)', + minWidth: 105, + slots: { default: 'taxPercent' }, + }, + { + fixed: 'right', + field: 'taxPrice', + title: '税额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'totalPrice', + fixed: 'right', + title: '合计金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '退货单号', + component: 'Input', + componentProps: { + placeholder: '请输入退货单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'returnTime', + label: '退货时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'supplierId', + label: '供应商', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择供应商', + allowClear: true, + showSearch: true, + api: getSupplierSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'warehouseId', + label: '仓库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择仓库', + allowClear: true, + showSearch: true, + api: getWarehouseSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'orderNo', + label: '关联订单', + component: 'Input', + componentProps: { + placeholder: '请输入关联订单号', + allowClear: true, + }, + }, + { + fieldName: 'refundStatus', + label: '退款状态', + component: 'Select', + componentProps: { + options: [ + { label: '未退款', value: 0 }, + { label: '部分退款', value: 1 }, + { label: '全部退款', value: 2 }, + ], + placeholder: '请选择退款状态', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '审批状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择审批状态', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '退货单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '退货产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'supplierName', + title: '供应商', + minWidth: 120, + }, + { + field: 'returnTime', + title: '退货时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '应退金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'refundPrice', + title: '已退金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'unRefundPrice', + title: '未退金额', + formatter: ({ row }) => { + return `${erpNumberFormatter(row.totalPrice - row.refundPrice, 2)}元`; + }, + minWidth: 120, + }, + { + field: 'status', + title: '审批状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useOrderGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '订单单号', + component: 'Input', + componentProps: { + placeholder: '请输入订单单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'orderTime', + label: '订单时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useOrderGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'radio', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '订单单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'supplierName', + title: '供应商', + minWidth: 120, + }, + { + field: 'orderTime', + title: '订单时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'inCount', + title: '已入库数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'returnCount', + title: '已退货数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalProductPrice', + title: '金额合计', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '含税金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/purchase/return/index.vue b/apps/web-ele/src/views/erp/purchase/return/index.vue new file mode 100644 index 0000000..91c9cfb --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/return/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/return/modules/form.vue b/apps/web-ele/src/views/erp/purchase/return/modules/form.vue new file mode 100644 index 0000000..f496ae0 --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/return/modules/form.vue @@ -0,0 +1,231 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/return/modules/item-form.vue b/apps/web-ele/src/views/erp/purchase/return/modules/item-form.vue new file mode 100644 index 0000000..93e24ed --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/return/modules/item-form.vue @@ -0,0 +1,312 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/return/modules/purchase-order-select.vue b/apps/web-ele/src/views/erp/purchase/return/modules/purchase-order-select.vue new file mode 100644 index 0000000..8b6ae10 --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/return/modules/purchase-order-select.vue @@ -0,0 +1,124 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/supplier/data.ts b/apps/web-ele/src/views/erp/purchase/supplier/data.ts new file mode 100644 index 0000000..d433c09 --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/supplier/data.ts @@ -0,0 +1,234 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '供应商名称', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入供应商名称', + }, + }, + { + fieldName: 'contact', + label: '联系人', + component: 'Input', + componentProps: { + placeholder: '请输入联系人', + }, + }, + { + fieldName: 'mobile', + label: '手机号码', + component: 'Input', + componentProps: { + placeholder: '请输入手机号码', + }, + }, + { + fieldName: 'telephone', + label: '联系电话', + component: 'Input', + componentProps: { + placeholder: '请输入联系电话', + }, + }, + { + fieldName: 'email', + label: '电子邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入电子邮箱', + }, + }, + { + fieldName: 'fax', + label: '传真', + component: 'Input', + componentProps: { + placeholder: '请输入传真', + }, + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'sort', + label: '排序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入排序', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'taxNo', + label: '纳税人识别号', + component: 'Input', + componentProps: { + placeholder: '请输入纳税人识别号', + }, + }, + { + fieldName: 'taxPercent', + label: '税率(%)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入税率', + min: 0, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'bankName', + label: '开户行', + component: 'Input', + componentProps: { + placeholder: '请输入开户行', + }, + }, + { + fieldName: 'bankAccount', + label: '开户账号', + component: 'Input', + componentProps: { + placeholder: '请输入开户账号', + }, + }, + { + fieldName: 'bankAddress', + label: '开户地址', + component: 'Input', + componentProps: { + placeholder: '请输入开户地址', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 3, + }, + formItemClass: 'col-span-2', + }, + ]; +} + +/** 搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '供应商名称', + component: 'Input', + componentProps: { + placeholder: '请输入供应商名称', + allowClear: true, + }, + }, + { + fieldName: 'mobile', + label: '手机号码', + component: 'Input', + componentProps: { + placeholder: '请输入手机号码', + allowClear: true, + }, + }, + { + fieldName: 'telephone', + label: '联系电话', + component: 'Input', + componentProps: { + placeholder: '请输入联系电话', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '供应商名称', + minWidth: 150, + }, + { + field: 'contact', + title: '联系人', + minWidth: 120, + }, + { + field: 'mobile', + title: '手机号码', + minWidth: 130, + }, + { + field: 'telephone', + title: '联系电话', + minWidth: 130, + }, + { + field: 'email', + title: '电子邮箱', + minWidth: 180, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'sort', + title: '排序', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + showOverflow: 'tooltip', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/purchase/supplier/index.vue b/apps/web-ele/src/views/erp/purchase/supplier/index.vue new file mode 100644 index 0000000..915adb7 --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/supplier/index.vue @@ -0,0 +1,153 @@ + + + diff --git a/apps/web-ele/src/views/erp/purchase/supplier/modules/form.vue b/apps/web-ele/src/views/erp/purchase/supplier/modules/form.vue new file mode 100644 index 0000000..653dd5c --- /dev/null +++ b/apps/web-ele/src/views/erp/purchase/supplier/modules/form.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/customer/data.ts b/apps/web-ele/src/views/erp/sale/customer/data.ts new file mode 100644 index 0000000..adfdd56 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/customer/data.ts @@ -0,0 +1,241 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '客户名称', + rules: 'required', + componentProps: { + placeholder: '请输入客户名称', + }, + }, + { + fieldName: 'contact', + label: '联系人', + component: 'Input', + componentProps: { + placeholder: '请输入联系人', + }, + }, + { + fieldName: 'mobile', + label: '手机号码', + component: 'Input', + componentProps: { + placeholder: '请输入手机号码', + }, + }, + { + fieldName: 'telephone', + label: '联系电话', + component: 'Input', + componentProps: { + placeholder: '请输入联系电话', + }, + }, + { + fieldName: 'email', + label: '电子邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入电子邮箱', + }, + }, + { + fieldName: 'fax', + label: '传真', + component: 'Input', + componentProps: { + placeholder: '请输入传真', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'sort', + label: '排序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入排序', + precision: 0, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'taxNo', + label: '纳税人识别号', + component: 'Input', + componentProps: { + placeholder: '请输入纳税人识别号', + }, + }, + { + fieldName: 'taxPercent', + label: '税率(%)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入税率', + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).max(100).optional(), + }, + { + fieldName: 'bankName', + label: '开户行名称', + component: 'Input', + componentProps: { + placeholder: '请输入开户行名称', + }, + }, + { + fieldName: 'bankAccount', + label: '开户行账号', + component: 'Input', + componentProps: { + placeholder: '请输入开户行账号', + }, + }, + { + fieldName: 'bankAddress', + label: '开户行地址', + component: 'Input', + componentProps: { + placeholder: '请输入开户行地址', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 3, + }, + formItemClass: 'col-span-2', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '客户名称', + component: 'Input', + componentProps: { + placeholder: '请输入客户名称', + allowClear: true, + }, + }, + { + fieldName: 'mobile', + label: '手机号码', + component: 'Input', + componentProps: { + placeholder: '请输入手机号码', + allowClear: true, + }, + }, + { + fieldName: 'email', + label: '邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入邮箱', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '客户名称', + minWidth: 150, + }, + { + field: 'contact', + title: '联系人', + minWidth: 120, + }, + { + field: 'mobile', + title: '手机号码', + minWidth: 130, + }, + { + field: 'telephone', + title: '联系电话', + minWidth: 130, + }, + { + field: 'email', + title: '电子邮箱', + minWidth: 180, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'sort', + title: '排序', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + showOverflow: 'tooltip', + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/sale/customer/index.vue b/apps/web-ele/src/views/erp/sale/customer/index.vue new file mode 100644 index 0000000..c2e3bc9 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/customer/index.vue @@ -0,0 +1,149 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/customer/modules/form.vue b/apps/web-ele/src/views/erp/sale/customer/modules/form.vue new file mode 100644 index 0000000..d744556 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/customer/modules/form.vue @@ -0,0 +1,86 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/order/data.ts b/apps/web-ele/src/views/erp/sale/order/data.ts new file mode 100644 index 0000000..b153750 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/order/data.ts @@ -0,0 +1,467 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { erpPriceInputFormatter } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getAccountSimpleList } from '#/api/erp/finance/account'; +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getCustomerSimpleList } from '#/api/erp/sale/customer'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '订单单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'orderTime', + label: '订单时间', + component: 'DatePicker', + componentProps: { + placeholder: '选择订单时间', + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + label: '客户', + fieldName: 'customerId', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择客户', + allowClear: true, + showSearch: true, + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + }, + rules: 'required', + }, + { + fieldName: 'saleUserId', + label: '销售人员', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择销售人员', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autoSize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '销售产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'discountPercent', + label: '优惠率(%)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入优惠率', + min: 0, + max: 100, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional(), + }, + { + fieldName: 'discountPrice', + label: '付款优惠', + component: 'InputNumber', + componentProps: { + placeholder: '收款优惠', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'totalPrice', + label: '优惠后金额', + component: 'InputNumber', + componentProps: { + placeholder: '优惠后金额', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'accountId', + label: '结算账户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择结算账户', + allowClear: true, + showSearch: true, + api: getAccountSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + component: 'InputNumber', + componentProps: { + placeholder: '请输入收取订金', + precision: 2, + min: 0, + controlsPosition: 'right', + class: '!w-full', + }, + fieldName: 'depositPrice', + label: '收取订金', + rules: z.number().min(0).optional(), + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + disabled: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'productId', + title: '产品名称', + minWidth: 200, + slots: { default: 'productId' }, + }, + { + field: 'stockCount', + title: '库存', + minWidth: 80, + formatter: 'formatAmount3', + }, + { + field: 'productBarCode', + title: '条码', + minWidth: 120, + }, + { + field: 'productUnitName', + title: '单位', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + field: 'count', + title: '数量', + minWidth: 120, + fixed: 'right', + slots: { default: 'count' }, + }, + { + field: 'productPrice', + title: '产品单价', + minWidth: 120, + fixed: 'right', + slots: { default: 'productPrice' }, + }, + { + field: 'totalProductPrice', + title: '金额', + minWidth: 120, + fixed: 'right', + formatter: 'formatAmount2', + }, + { + field: 'taxPercent', + title: '税率(%)', + minWidth: 105, + fixed: 'right', + slots: { default: 'taxPercent' }, + }, + { + field: 'taxPrice', + title: '税额', + minWidth: 120, + fixed: 'right', + formatter: 'formatAmount2', + }, + { + field: 'totalPrice', + title: '税额合计', + minWidth: 120, + fixed: 'right', + formatter: 'formatAmount2', + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '订单单号', + component: 'Input', + componentProps: { + placeholder: '请输入订单单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'orderTime', + label: '订单时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'supplierId', + label: '客户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择客户', + allowClear: true, + showSearch: true, + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + { + fieldName: 'outStatus', + label: '出库状态', + component: 'Select', + componentProps: { + options: [ + { label: '未出库', value: 0 }, + { label: '部分出库', value: 1 }, + { label: '全部出库', value: 2 }, + ], + placeholder: '请选择出库状态', + allowClear: true, + }, + }, + { + fieldName: 'returnStatus', + label: '退货状态', + component: 'Select', + componentProps: { + options: [ + { label: '未退货', value: 0 }, + { label: '部分退货', value: 1 }, + { label: '全部退货', value: 2 }, + ], + placeholder: '请选择退货状态', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '订单单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'customerName', + title: '客户', + minWidth: 120, + }, + { + field: 'orderTime', + title: '订单时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'outCount', + title: '出库数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'returnCount', + title: '退货数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalProductPrice', + title: '金额合计', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '含税金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'depositPrice', + title: '收取订金', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/sale/order/index.vue b/apps/web-ele/src/views/erp/sale/order/index.vue new file mode 100644 index 0000000..e681b86 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/order/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/order/modules/form.vue b/apps/web-ele/src/views/erp/sale/order/modules/form.vue new file mode 100644 index 0000000..53dd591 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/order/modules/form.vue @@ -0,0 +1,162 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/order/modules/item-form.vue b/apps/web-ele/src/views/erp/sale/order/modules/item-form.vue new file mode 100644 index 0000000..0a0aef7 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/order/modules/item-form.vue @@ -0,0 +1,328 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/out/data.ts b/apps/web-ele/src/views/erp/sale/out/data.ts new file mode 100644 index 0000000..76f8e22 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/out/data.ts @@ -0,0 +1,637 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { erpNumberFormatter, erpPriceInputFormatter } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getAccountSimpleList } from '#/api/erp/finance/account'; +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getCustomerSimpleList } from '#/api/erp/sale/customer'; +import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '出库单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'outTime', + label: '出库时间', + component: 'DatePicker', + componentProps: { + disabled: formType === 'detail', + placeholder: '选择出库时间', + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'orderNo', + label: '关联订单', + component: 'Input', + formItemClass: 'col-span-1', + rules: 'required', + componentProps: { + placeholder: '请选择关联订单', + disabled: formType === 'detail', + }, + }, + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + disabled: true, + placeholder: '请选择客户', + allowClear: true, + showSearch: true, + api: getCustomerSimpleList, + fieldNames: { + label: 'name', + value: 'id', + }, + }, + rules: 'required', + }, + { + fieldName: 'saleUserId', + label: '销售人员', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择销售人员', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + fieldNames: { + label: 'nickname', + value: 'id', + }, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autoSize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '出库产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'discountPercent', + label: '优惠率(%)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入优惠率', + min: 0, + max: 100, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional(), + }, + { + fieldName: 'discountPrice', + label: '收款优惠', + component: 'InputNumber', + componentProps: { + placeholder: '付款优惠', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'discountedPrice', + label: '优惠后金额', + component: 'InputNumber', + componentProps: { + placeholder: '优惠后金额', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['totalPrice', 'otherPrice'], + componentProps: (values) => { + const totalPrice = values.totalPrice || 0; + const otherPrice = values.otherPrice || 0; + values.discountedPrice = totalPrice - otherPrice; + return {}; + }, + }, + }, + { + fieldName: 'otherPrice', + label: '其他费用', + component: 'InputNumber', + componentProps: { + disabled: formType === 'detail', + placeholder: '请输入其他费用', + precision: 2, + formatter: erpPriceInputFormatter, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'accountId', + label: '结算账户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择结算账户', + disabled: true, + allowClear: true, + showSearch: true, + api: getAccountSimpleList, + fieldNames: { + label: 'name', + value: 'id', + }, + }, + }, + { + fieldName: 'totalPrice', + label: '应收金额', + component: 'InputNumber', + componentProps: { + precision: 2, + min: 0, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional(), + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + formData?: any[], + disabled?: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'warehouseId', + title: '仓库名称', + minWidth: 200, + slots: { default: 'warehouseId' }, + }, + { + field: 'productId', + title: '产品名称', + minWidth: 200, + slots: { default: 'productId' }, + }, + { + field: 'stockCount', + title: '库存', + minWidth: 80, + formatter: 'formatAmount3', + }, + { + field: 'productBarCode', + title: '条码', + minWidth: 120, + }, + { + field: 'productUnitName', + title: '单位', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + field: 'totalCount', + title: '原数量', + formatter: 'formatAmount3', + minWidth: 120, + fixed: 'right', + visible: formData && formData[0]?.outCount !== undefined, + }, + { + field: 'outCount', + title: '已出库', + formatter: 'formatAmount3', + minWidth: 120, + fixed: 'right', + visible: formData && formData[0]?.returnCount !== undefined, + }, + { + field: 'count', + title: '数量', + minWidth: 120, + fixed: 'right', + slots: { default: 'count' }, + }, + { + field: 'productPrice', + title: '产品单价', + fixed: 'right', + minWidth: 120, + slots: { default: 'productPrice' }, + }, + { + field: 'totalProductPrice', + fixed: 'right', + title: '产品金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + fixed: 'right', + field: 'taxPercent', + title: '税率(%)', + minWidth: 105, + slots: { default: 'taxPercent' }, + }, + { + fixed: 'right', + field: 'taxPrice', + title: '税额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'totalPrice', + fixed: 'right', + title: '合计金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '出库单号', + component: 'Input', + componentProps: { + placeholder: '请输入出库单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + fieldNames: { + label: 'name', + value: 'id', + }, + }, + }, + { + fieldName: 'outTime', + label: '出库时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择客户', + allowClear: true, + showSearch: true, + api: getCustomerSimpleList, + fieldNames: { + label: 'name', + value: 'id', + }, + }, + }, + { + fieldName: 'warehouseId', + label: '仓库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择仓库', + allowClear: true, + showSearch: true, + api: getWarehouseSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + fieldNames: { + label: 'nickname', + value: 'id', + }, + }, + }, + { + fieldName: 'orderNo', + label: '关联订单', + component: 'Input', + componentProps: { + placeholder: '请输入关联订单号', + allowClear: true, + }, + }, + { + fieldName: 'receiptStatus', + label: '收款状态', + component: 'Select', + componentProps: { + options: [ + { label: '未收款', value: 0 }, + { label: '部分收款', value: 1 }, + { label: '全部收款', value: 2 }, + ], + placeholder: '请选择收款状态', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '审批状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择审批状态', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '出库单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'customerName', + title: '客户', + minWidth: 120, + }, + { + field: 'outTime', + title: '出库时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '应收金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'receiptPrice', + title: '已收金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'unReceiptPrice', + title: '未收金额', + formatter: ({ row }) => { + return `${erpNumberFormatter(row.totalPrice - row.receiptPrice, 2)}元`; + }, + minWidth: 120, + }, + { + field: 'status', + title: '审批状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useOrderGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '订单单号', + component: 'Input', + componentProps: { + placeholder: '请输入订单单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'orderTime', + label: '订单时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useOrderGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'radio', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '订单单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'customerName', + title: '客户', + minWidth: 120, + }, + { + field: 'orderTime', + title: '订单时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'outCount', + title: '出库数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalProductPrice', + title: '金额合计', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '含税金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/sale/out/index.vue b/apps/web-ele/src/views/erp/sale/out/index.vue new file mode 100644 index 0000000..528e7f5 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/out/index.vue @@ -0,0 +1,223 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/out/modules/form.vue b/apps/web-ele/src/views/erp/sale/out/modules/form.vue new file mode 100644 index 0000000..123f2f3 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/out/modules/form.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/out/modules/item-form.vue b/apps/web-ele/src/views/erp/sale/out/modules/item-form.vue new file mode 100644 index 0000000..3ec5996 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/out/modules/item-form.vue @@ -0,0 +1,310 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/out/modules/sale-order-select.vue b/apps/web-ele/src/views/erp/sale/out/modules/sale-order-select.vue new file mode 100644 index 0000000..de19126 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/out/modules/sale-order-select.vue @@ -0,0 +1,124 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/return/data.ts b/apps/web-ele/src/views/erp/sale/return/data.ts new file mode 100644 index 0000000..9138f35 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/return/data.ts @@ -0,0 +1,624 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { erpNumberFormatter, erpPriceInputFormatter } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getAccountSimpleList } from '#/api/erp/finance/account'; +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getCustomerSimpleList } from '#/api/erp/sale/customer'; +import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '退货单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'returnTime', + label: '退货时间', + component: 'DatePicker', + componentProps: { + disabled: formType === 'detail', + placeholder: '选择退货时间', + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'orderNo', + label: '关联订单', + component: 'Input', + formItemClass: 'col-span-1', + rules: 'required', + componentProps: { + placeholder: '请选择关联订单', + disabled: formType === 'detail', + }, + }, + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + disabled: true, + placeholder: '请选择客户', + allowClear: true, + showSearch: true, + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + }, + rules: 'required', + }, + { + fieldName: 'saleUserId', + label: '销售人员', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择销售人员', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autoSize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '退货产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'discountPercent', + label: '优惠率(%)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入优惠率', + min: 0, + max: 100, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional(), + }, + { + fieldName: 'discountPrice', + label: '退款优惠', + component: 'InputNumber', + componentProps: { + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'discountedPrice', + label: '优惠后金额', + component: 'InputNumber', + componentProps: { + placeholder: '优惠后金额', + precision: 2, + formatter: erpPriceInputFormatter, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['totalPrice', 'otherPrice'], + componentProps: (values) => { + const totalPrice = values.totalPrice || 0; + const otherPrice = values.otherPrice || 0; + values.discountedPrice = totalPrice - otherPrice; + return {}; + }, + }, + }, + { + fieldName: 'otherPrice', + label: '其他费用', + component: 'InputNumber', + componentProps: { + disabled: formType === 'detail', + placeholder: '请输入其他费用', + precision: 2, + formatter: erpPriceInputFormatter, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'accountId', + label: '结算账户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择结算账户', + disabled: true, + allowClear: true, + showSearch: true, + api: getAccountSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'totalPrice', + label: '应收金额', + component: 'InputNumber', + componentProps: { + precision: 2, + min: 0, + disabled: true, + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0).optional(), + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + formData?: any[], + disabled?: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'warehouseId', + title: '仓库名称', + minWidth: 200, + slots: { default: 'warehouseId' }, + }, + { + field: 'productId', + title: '产品名称', + minWidth: 200, + slots: { default: 'productId' }, + }, + { + field: 'stockCount', + title: '库存', + minWidth: 80, + formatter: 'formatAmount3', + }, + { + field: 'productBarCode', + title: '条码', + minWidth: 120, + }, + { + field: 'productUnitName', + title: '单位', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + field: 'totalCount', + title: '已出库', + formatter: 'formatAmount3', + minWidth: 120, + fixed: 'right', + visible: formData && formData[0]?.outCount !== undefined, + }, + { + field: 'returnCount', + title: '已退货', + formatter: 'formatAmount3', + minWidth: 120, + fixed: 'right', + visible: formData && formData[0]?.returnCount !== undefined, + }, + { + field: 'count', + title: '数量', + minWidth: 120, + fixed: 'right', + slots: { default: 'count' }, + }, + { + field: 'productPrice', + title: '产品单价', + fixed: 'right', + minWidth: 120, + slots: { default: 'productPrice' }, + }, + { + field: 'totalProductPrice', + fixed: 'right', + title: '产品金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + fixed: 'right', + field: 'taxPercent', + title: '税率(%)', + minWidth: 105, + slots: { default: 'taxPercent' }, + }, + { + fixed: 'right', + field: 'taxPrice', + title: '税额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'totalPrice', + fixed: 'right', + title: '合计金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '退货单号', + component: 'Input', + componentProps: { + placeholder: '请输入退货单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'returnTime', + label: '退货时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择客户', + allowClear: true, + showSearch: true, + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'warehouseId', + label: '仓库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择仓库', + allowClear: true, + showSearch: true, + api: getWarehouseSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'orderNo', + label: '关联订单', + component: 'Input', + componentProps: { + placeholder: '请输入关联订单号', + allowClear: true, + }, + }, + { + fieldName: 'refundStatus', + label: '退款状态', + component: 'Select', + componentProps: { + options: [ + { label: '未退款', value: 0 }, + { label: '部分退款', value: 1 }, + { label: '全部退款', value: 2 }, + ], + placeholder: '请选择退款状态', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '审批状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择审批状态', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '退货单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '退货产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'customerName', + title: '客户', + minWidth: 120, + }, + { + field: 'returnTime', + title: '退货时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '应收金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'refundPrice', + title: '已退金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'unRefundPrice', + title: '未退金额', + formatter: ({ row }) => { + return `${erpNumberFormatter(row.totalPrice - row.refundPrice, 2)}元`; + }, + minWidth: 120, + }, + { + field: 'status', + title: '审批状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useOrderGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '订单单号', + component: 'Input', + componentProps: { + placeholder: '请输入订单单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'orderTime', + label: '订单时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useOrderGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'radio', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '订单单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'customerName', + title: '客户', + minWidth: 120, + }, + { + field: 'orderTime', + title: '订单时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'returnCount', + title: '已退货数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalProductPrice', + title: '金额合计', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '含税金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/sale/return/index.vue b/apps/web-ele/src/views/erp/sale/return/index.vue new file mode 100644 index 0000000..5a76a96 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/return/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/return/modules/form.vue b/apps/web-ele/src/views/erp/sale/return/modules/form.vue new file mode 100644 index 0000000..35f59a9 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/return/modules/form.vue @@ -0,0 +1,231 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/return/modules/item-form.vue b/apps/web-ele/src/views/erp/sale/return/modules/item-form.vue new file mode 100644 index 0000000..3e169a9 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/return/modules/item-form.vue @@ -0,0 +1,310 @@ + + + diff --git a/apps/web-ele/src/views/erp/sale/return/modules/sale-order-select.vue b/apps/web-ele/src/views/erp/sale/return/modules/sale-order-select.vue new file mode 100644 index 0000000..05d4746 --- /dev/null +++ b/apps/web-ele/src/views/erp/sale/return/modules/sale-order-select.vue @@ -0,0 +1,124 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/check/data.ts b/apps/web-ele/src/views/erp/stock/check/data.ts new file mode 100644 index 0000000..c4beb2c --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/check/data.ts @@ -0,0 +1,307 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '盘点单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'checkTime', + label: '盘点时间', + component: 'DatePicker', + componentProps: { + placeholder: '选择盘点时间', + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autoSize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + disabled: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'warehouseId', + title: '仓库名称', + minWidth: 150, + slots: { default: 'warehouseId' }, + }, + { + field: 'productId', + title: '产品名称', + minWidth: 200, + slots: { default: 'productId' }, + }, + { + field: 'stockCount', + title: '账面库存', + minWidth: 80, + formatter: 'formatAmount3', + }, + { + field: 'productBarCode', + title: '条码', + minWidth: 120, + }, + { + field: 'productUnitName', + title: '单位', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + field: 'actualCount', + title: '实际库存', + minWidth: 120, + fixed: 'right', + slots: { default: 'actualCount' }, + formatter: 'formatAmount3', + }, + { + field: 'count', + title: '盈亏数量', + minWidth: 120, + fixed: 'right', + formatter: 'formatAmount3', + }, + { + field: 'productPrice', + title: '产品单价', + minWidth: 120, + fixed: 'right', + slots: { default: 'productPrice' }, + }, + { + field: 'totalPrice', + title: '金额', + minWidth: 120, + fixed: 'right', + formatter: 'formatAmount2', + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '盘点单号', + component: 'Input', + componentProps: { + placeholder: '请输入盘点单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'checkTime', + label: '盘点时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'warehouseId', + label: '仓库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择仓库', + allowClear: true, + showSearch: true, + api: getWarehouseSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '盘点单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'checkTime', + title: '盘点时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '总金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/stock/check/index.vue b/apps/web-ele/src/views/erp/stock/check/index.vue new file mode 100644 index 0000000..144ab76 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/check/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/check/modules/form.vue b/apps/web-ele/src/views/erp/stock/check/modules/form.vue new file mode 100644 index 0000000..72e504a --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/check/modules/form.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/check/modules/item-form.vue b/apps/web-ele/src/views/erp/stock/check/modules/item-form.vue new file mode 100644 index 0000000..545c1a6 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/check/modules/item-form.vue @@ -0,0 +1,312 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/in/data.ts b/apps/web-ele/src/views/erp/stock/in/data.ts new file mode 100644 index 0000000..9b59ce9 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/in/data.ts @@ -0,0 +1,332 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; +import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '入库单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'inTime', + label: '入库时间', + component: 'DatePicker', + componentProps: { + placeholder: '选择入库时间', + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + label: '供应商', + fieldName: 'supplierId', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择供应商', + allowClear: true, + showSearch: true, + api: getSupplierSimpleList, + labelField: 'name', + valueField: 'id', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autoSize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '入库产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + disabled: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'warehouseId', + title: '仓库名称', + minWidth: 150, + slots: { default: 'warehouseId' }, + }, + { + field: 'productId', + title: '产品名称', + minWidth: 200, + slots: { default: 'productId' }, + }, + { + field: 'stockCount', + title: '库存', + minWidth: 80, + formatter: 'formatAmount3', + }, + { + field: 'productBarCode', + title: '条码', + minWidth: 120, + }, + { + field: 'productUnitName', + title: '单位', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + field: 'count', + title: '数量', + minWidth: 120, + fixed: 'right', + slots: { default: 'count' }, + }, + { + field: 'productPrice', + title: '产品单价', + minWidth: 120, + fixed: 'right', + slots: { default: 'productPrice' }, + }, + { + field: 'totalPrice', + title: '金额', + minWidth: 120, + fixed: 'right', + formatter: 'formatAmount2', + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '入库单号', + component: 'Input', + componentProps: { + placeholder: '请输入入库单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'inTime', + label: '入库时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'supplierId', + label: '供应商', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择供应商', + allowClear: true, + showSearch: true, + api: getSupplierSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'warehouseId', + label: '仓库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择仓库', + allowClear: true, + showSearch: true, + api: getWarehouseSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '入库单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'supplierName', + title: '供应商', + minWidth: 120, + }, + { + field: 'inTime', + title: '入库时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '总金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/stock/in/index.vue b/apps/web-ele/src/views/erp/stock/in/index.vue new file mode 100644 index 0000000..4f6e1da --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/in/index.vue @@ -0,0 +1,223 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/in/modules/form.vue b/apps/web-ele/src/views/erp/stock/in/modules/form.vue new file mode 100644 index 0000000..8d1e420 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/in/modules/form.vue @@ -0,0 +1,128 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/in/modules/item-form.vue b/apps/web-ele/src/views/erp/stock/in/modules/item-form.vue new file mode 100644 index 0000000..2fa3431 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/in/modules/item-form.vue @@ -0,0 +1,301 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/move/data.ts b/apps/web-ele/src/views/erp/stock/move/data.ts new file mode 100644 index 0000000..0b4ffea --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/move/data.ts @@ -0,0 +1,318 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '调度单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'moveTime', + label: '调度时间', + component: 'DatePicker', + componentProps: { + placeholder: '选择调度时间', + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autoSize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + disabled: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'fromWarehouseId', + title: '调出仓库', + minWidth: 150, + slots: { default: 'fromWarehouseId' }, + }, + { + field: 'toWarehouseId', + title: '调入仓库', + minWidth: 150, + slots: { default: 'toWarehouseId' }, + }, + { + field: 'productId', + title: '产品名称', + minWidth: 200, + slots: { default: 'productId' }, + }, + { + field: 'stockCount', + title: '库存', + minWidth: 80, + formatter: 'formatAmount3', + }, + { + field: 'productBarCode', + title: '条码', + minWidth: 120, + }, + { + field: 'productUnitName', + title: '单位', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + field: 'count', + title: '数量', + minWidth: 120, + fixed: 'right', + slots: { default: 'count' }, + }, + { + field: 'productPrice', + title: '产品单价', + minWidth: 120, + fixed: 'right', + slots: { default: 'productPrice' }, + }, + { + field: 'totalPrice', + title: '金额', + minWidth: 120, + fixed: 'right', + formatter: 'formatAmount2', + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '调度单号', + component: 'Input', + componentProps: { + placeholder: '请输入调度单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'moveTime', + label: '调度时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'fromWarehouseId', + label: '调出仓库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择调出仓库', + allowClear: true, + showSearch: true, + api: getWarehouseSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'toWarehouseId', + label: '调入仓库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择调入仓库', + allowClear: true, + showSearch: true, + api: getWarehouseSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '调度单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'moveTime', + title: '调度时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '总金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/stock/move/index.vue b/apps/web-ele/src/views/erp/stock/move/index.vue new file mode 100644 index 0000000..c0a6481 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/move/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/move/modules/form.vue b/apps/web-ele/src/views/erp/stock/move/modules/form.vue new file mode 100644 index 0000000..94c879b --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/move/modules/form.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/move/modules/item-form.vue b/apps/web-ele/src/views/erp/stock/move/modules/item-form.vue new file mode 100644 index 0000000..8b3764c --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/move/modules/item-form.vue @@ -0,0 +1,321 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/out/data.ts b/apps/web-ele/src/views/erp/stock/out/data.ts new file mode 100644 index 0000000..eb11d68 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/out/data.ts @@ -0,0 +1,332 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getCustomerSimpleList } from '#/api/erp/sale/customer'; +import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单的配置项 */ +export function useFormSchema(formType: string): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'no', + label: '出库单号', + component: 'Input', + componentProps: { + placeholder: '系统自动生成', + disabled: true, + }, + }, + { + fieldName: 'outTime', + label: '出库时间', + component: 'DatePicker', + componentProps: { + placeholder: '选择出库时间', + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + rules: 'required', + }, + { + label: '客户', + fieldName: 'customerId', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择客户', + allowClear: true, + showSearch: true, + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + autoSize: { minRows: 1, maxRows: 1 }, + disabled: formType === 'detail', + }, + formItemClass: 'col-span-2', + }, + { + fieldName: 'fileUrl', + label: '附件', + component: 'FileUpload', + componentProps: { + maxNumber: 1, + maxSize: 10, + accept: [ + 'pdf', + 'doc', + 'docx', + 'xls', + 'xlsx', + 'txt', + 'jpg', + 'jpeg', + 'png', + ], + showDescription: formType !== 'detail', + disabled: formType === 'detail', + }, + formItemClass: 'col-span-3', + }, + { + fieldName: 'items', + label: '出库产品清单', + component: 'Input', + formItemClass: 'col-span-3', + }, + ]; +} + +/** 表单的明细表格列 */ +export function useFormItemColumns( + disabled: boolean, +): VxeTableGridOptions['columns'] { + return [ + { type: 'seq', title: '序号', minWidth: 50, fixed: 'left' }, + { + field: 'warehouseId', + title: '仓库名称', + minWidth: 150, + slots: { default: 'warehouseId' }, + }, + { + field: 'productId', + title: '产品名称', + minWidth: 200, + slots: { default: 'productId' }, + }, + { + field: 'stockCount', + title: '库存', + minWidth: 80, + formatter: 'formatAmount3', + }, + { + field: 'productBarCode', + title: '条码', + minWidth: 120, + }, + { + field: 'productUnitName', + title: '单位', + minWidth: 80, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + slots: { default: 'remark' }, + }, + { + field: 'count', + title: '数量', + minWidth: 120, + fixed: 'right', + slots: { default: 'count' }, + }, + { + field: 'productPrice', + title: '产品单价', + minWidth: 120, + fixed: 'right', + slots: { default: 'productPrice' }, + }, + { + field: 'totalPrice', + title: '金额', + minWidth: 120, + fixed: 'right', + formatter: 'formatAmount2', + }, + { + title: '操作', + width: 50, + fixed: 'right', + slots: { default: 'actions' }, + visible: !disabled, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '出库单号', + component: 'Input', + componentProps: { + placeholder: '请输入出库单号', + allowClear: true, + }, + }, + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'outTime', + label: '出库时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + { + fieldName: 'customerId', + label: '客户', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择客户', + allowClear: true, + showSearch: true, + api: getCustomerSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'warehouseId', + label: '仓库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择仓库', + allowClear: true, + showSearch: true, + api: getWarehouseSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'creator', + label: '创建人', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择创建人', + allowClear: true, + showSearch: true, + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.ERP_AUDIT_STATUS, 'number'), + placeholder: '请选择状态', + allowClear: true, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + fixed: 'left', + }, + { + field: 'no', + title: '出库单号', + width: 200, + fixed: 'left', + }, + { + field: 'productNames', + title: '产品信息', + showOverflow: 'tooltip', + minWidth: 120, + }, + { + field: 'customerName', + title: '客户', + minWidth: 120, + }, + { + field: 'outTime', + title: '出库时间', + width: 160, + formatter: 'formatDate', + }, + { + field: 'creatorName', + title: '创建人', + minWidth: 120, + }, + { + field: 'totalCount', + title: '总数量', + formatter: 'formatAmount3', + minWidth: 120, + }, + { + field: 'totalPrice', + title: '总金额', + formatter: 'formatAmount2', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_AUDIT_STATUS }, + }, + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/stock/out/index.vue b/apps/web-ele/src/views/erp/stock/out/index.vue new file mode 100644 index 0000000..96c244e --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/out/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/out/modules/form.vue b/apps/web-ele/src/views/erp/stock/out/modules/form.vue new file mode 100644 index 0000000..4d84dd3 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/out/modules/form.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/out/modules/item-form.vue b/apps/web-ele/src/views/erp/stock/out/modules/item-form.vue new file mode 100644 index 0000000..5aa4f9a --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/out/modules/item-form.vue @@ -0,0 +1,299 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/record/data.ts b/apps/web-ele/src/views/erp/stock/record/data.ts new file mode 100644 index 0000000..e4e3d0e --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/record/data.ts @@ -0,0 +1,133 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'warehouseId', + label: '仓库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择仓库', + allowClear: true, + showSearch: true, + api: getWarehouseSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'bizType', + label: '类型', + component: 'Select', + componentProps: { + placeholder: '请选择类型', + allowClear: true, + options: getDictOptions(DICT_TYPE.ERP_STOCK_RECORD_BIZ_TYPE, 'number'), + }, + }, + { + fieldName: 'bizNo', + label: '业务单号', + component: 'Input', + componentProps: { + placeholder: '请输入业务单号', + allowClear: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'productName', + title: '产品名称', + minWidth: 150, + }, + { + field: 'categoryName', + title: '产品分类', + width: 120, + }, + { + field: 'unitName', + title: '产品单位', + width: 100, + }, + { + field: 'warehouseName', + title: '仓库', + width: 120, + }, + { + field: 'bizType', + title: '类型', + width: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.ERP_STOCK_RECORD_BIZ_TYPE }, + }, + }, + { + field: 'bizNo', + title: '出入库单号', + width: 200, + showOverflow: 'tooltip', + }, + { + field: 'createTime', + title: '出入库日期', + width: 180, + formatter: 'formatDateTime', + }, + { + field: 'count', + title: '出入库数量', + width: 120, + formatter: 'formatAmount3', + }, + { + field: 'totalCount', + title: '库存量', + width: 100, + formatter: 'formatAmount3', + }, + { + field: 'creatorName', + title: '操作人', + width: 100, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/stock/record/index.vue b/apps/web-ele/src/views/erp/stock/record/index.vue new file mode 100644 index 0000000..8c97503 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/record/index.vue @@ -0,0 +1,79 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/stock/data.ts b/apps/web-ele/src/views/erp/stock/stock/data.ts new file mode 100644 index 0000000..037bb9c --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/stock/data.ts @@ -0,0 +1,69 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { getProductSimpleList } from '#/api/erp/product/product'; +import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse'; + +/** 搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'productId', + label: '产品', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择产品', + allowClear: true, + showSearch: true, + api: getProductSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + { + fieldName: 'warehouseId', + label: '仓库', + component: 'ApiSelect', + componentProps: { + placeholder: '请选择仓库', + allowClear: true, + showSearch: true, + api: getWarehouseSimpleList, + labelField: 'name', + valueField: 'id', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'productName', + title: '产品名称', + minWidth: 150, + }, + { + field: 'unitName', + title: '产品单位', + minWidth: 100, + }, + { + field: 'categoryName', + title: '产品分类', + minWidth: 120, + }, + { + field: 'count', + title: '库存量', + minWidth: 100, + formatter: 'formatAmount3', + }, + { + field: 'warehouseName', + title: '仓库', + minWidth: 120, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/stock/stock/index.vue b/apps/web-ele/src/views/erp/stock/stock/index.vue new file mode 100644 index 0000000..2b20d74 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/stock/index.vue @@ -0,0 +1,79 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/warehouse/data.ts b/apps/web-ele/src/views/erp/stock/warehouse/data.ts new file mode 100644 index 0000000..0f38019 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/warehouse/data.ts @@ -0,0 +1,209 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { ErpWarehouseApi } from '#/api/erp/stock/warehouse'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '仓库名称', + component: 'Input', + componentProps: { + placeholder: '请输入仓库名称', + }, + rules: 'required', + }, + { + fieldName: 'address', + label: '仓库地址', + component: 'Input', + componentProps: { + placeholder: '请输入仓库地址', + }, + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'warehousePrice', + label: '仓储费(元)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入仓储费,单位:元/天/KG', + min: 0, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'truckagePrice', + label: '搬运费(元)', + component: 'InputNumber', + componentProps: { + placeholder: '请输入搬运费,单位:元', + min: 0, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'principal', + label: '负责人', + component: 'Input', + componentProps: { + placeholder: '请输入负责人', + }, + }, + { + fieldName: 'sort', + label: '排序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入排序', + precision: 0, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '仓库名称', + component: 'Input', + componentProps: { + placeholder: '请输入仓库名称', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '仓库状态', + component: 'Select', + componentProps: { + placeholder: '请选择仓库状态', + allowClear: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onDefaultStatusChange?: ( + newStatus: boolean, + row: ErpWarehouseApi.Warehouse, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '仓库名称', + minWidth: 150, + }, + { + field: 'address', + title: '仓库地址', + minWidth: 200, + showOverflow: 'tooltip', + }, + { + field: 'warehousePrice', + title: '仓储费', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'truckagePrice', + title: '搬运费', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'principal', + title: '负责人', + minWidth: 100, + }, + { + field: 'sort', + title: '排序', + minWidth: 80, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'defaultStatus', + title: '是否默认', + minWidth: 100, + cellRender: { + attrs: { beforeChange: onDefaultStatusChange }, + name: 'CellSwitch', + props: { + activeValue: true, + inactiveValue: false, + }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + showOverflow: 'tooltip', + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/erp/stock/warehouse/index.vue b/apps/web-ele/src/views/erp/stock/warehouse/index.vue new file mode 100644 index 0000000..48669e1 --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/warehouse/index.vue @@ -0,0 +1,179 @@ + + + diff --git a/apps/web-ele/src/views/erp/stock/warehouse/modules/form.vue b/apps/web-ele/src/views/erp/stock/warehouse/modules/form.vue new file mode 100644 index 0000000..5bd04fd --- /dev/null +++ b/apps/web-ele/src/views/erp/stock/warehouse/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/infra/apiAccessLog/data.ts b/apps/web-ele/src/views/infra/apiAccessLog/data.ts new file mode 100644 index 0000000..fb59f59 --- /dev/null +++ b/apps/web-ele/src/views/infra/apiAccessLog/data.ts @@ -0,0 +1,273 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { JsonViewer } from '@vben/common-ui'; +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入用户编号', + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + clearable: true, + placeholder: '请选择用户类型', + }, + }, + { + fieldName: 'applicationName', + label: '应用名', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入应用名', + }, + }, + { + fieldName: 'beginTime', + label: '请求时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'duration', + label: '执行时长', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入执行时长', + }, + }, + { + fieldName: 'resultCode', + label: '结果码', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入结果码', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '日志编号', + minWidth: 100, + }, + { + field: 'userId', + title: '用户编号', + minWidth: 100, + }, + { + field: 'userType', + title: '用户类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.USER_TYPE }, + }, + }, + { + field: 'applicationName', + title: '应用名', + minWidth: 150, + }, + { + field: 'requestMethod', + title: '请求方法', + minWidth: 80, + }, + { + field: 'requestUrl', + title: '请求地址', + minWidth: 300, + }, + { + field: 'beginTime', + title: '请求时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'duration', + title: '执行时长', + minWidth: 120, + formatter: ({ row }) => `${row.duration} ms`, + }, + { + field: 'resultCode', + title: '操作结果', + minWidth: 150, + formatter: ({ row }) => { + return row.resultCode === 0 ? '成功' : `失败(${row.resultMsg})`; + }, + }, + { + field: 'operateModule', + title: '操作模块', + minWidth: 150, + }, + { + field: 'operateName', + title: '操作名', + minWidth: 220, + }, + { + field: 'operateType', + title: '操作类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_OPERATE_TYPE }, + }, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'id', + label: '日志编号', + }, + { + field: 'traceId', + label: '链路追踪', + }, + { + field: 'applicationName', + label: '应用名', + }, + { + field: 'userId', + label: '用户Id', + }, + { + field: 'userType', + label: '用户类型', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.USER_TYPE, + value: val, + }); + }, + }, + { + field: 'userIp', + label: '用户 IP', + }, + { + field: 'userAgent', + label: '用户 UA', + }, + { + field: 'requestMethod', + label: '请求信息', + render: (val, data) => { + if (val && data?.requestUrl) { + return `${val} ${data.requestUrl}`; + } + return ''; + }, + }, + { + field: 'requestParams', + label: '请求参数', + render: (val) => { + if (val) { + return h(JsonViewer, { + value: JSON.parse(val), + previewMode: true, + }); + } + return ''; + }, + }, + { + field: 'responseBody', + label: '请求结果', + }, + { + label: '请求时间', + field: 'beginTime', + render: (val, data) => { + if (val && data?.endTime) { + return `${formatDateTime(val)} ~ ${formatDateTime(data.endTime)}`; + } + return ''; + }, + }, + { + label: '请求耗时', + field: 'duration', + render: (val) => { + return val ? `${val} ms` : ''; + }, + }, + { + label: '操作结果', + field: 'resultCode', + render: (val, data) => { + if (val === 0) { + return '正常'; + } else if (val > 0 && data?.resultMsg) { + return `失败 | ${val} | ${data.resultMsg}`; + } + return ''; + }, + }, + { + field: 'operateModule', + label: '操作模块', + }, + { + field: 'operateName', + label: '操作名', + }, + { + field: 'operateType', + label: '操作类型', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.INFRA_OPERATE_TYPE, + value: val, + }); + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/apiAccessLog/index.vue b/apps/web-ele/src/views/infra/apiAccessLog/index.vue new file mode 100644 index 0000000..6ba4dad --- /dev/null +++ b/apps/web-ele/src/views/infra/apiAccessLog/index.vue @@ -0,0 +1,107 @@ + + + diff --git a/apps/web-ele/src/views/infra/apiAccessLog/modules/detail.vue b/apps/web-ele/src/views/infra/apiAccessLog/modules/detail.vue new file mode 100644 index 0000000..0b3c4c7 --- /dev/null +++ b/apps/web-ele/src/views/infra/apiAccessLog/modules/detail.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-ele/src/views/infra/apiErrorLog/data.ts b/apps/web-ele/src/views/infra/apiErrorLog/data.ts new file mode 100644 index 0000000..e669a77 --- /dev/null +++ b/apps/web-ele/src/views/infra/apiErrorLog/data.ts @@ -0,0 +1,249 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { JsonViewer } from '@vben/common-ui'; +import { DICT_TYPE, InfraApiErrorLogProcessStatusEnum } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入用户编号', + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + clearable: true, + placeholder: '请选择用户类型', + }, + }, + { + fieldName: 'applicationName', + label: '应用名', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入应用名', + }, + }, + { + fieldName: 'exceptionTime', + label: '异常时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'processStatus', + label: '处理状态', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS, + 'number', + ), + clearable: true, + placeholder: '请选择处理状态', + }, + defaultValue: InfraApiErrorLogProcessStatusEnum.INIT, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '日志编号', + minWidth: 100, + }, + { + field: 'userId', + title: '用户编号', + minWidth: 100, + }, + { + field: 'userType', + title: '用户类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.USER_TYPE }, + }, + }, + { + field: 'applicationName', + title: '应用名', + minWidth: 150, + }, + { + field: 'requestMethod', + title: '请求方法', + minWidth: 80, + }, + { + field: 'requestUrl', + title: '请求地址', + minWidth: 200, + }, + { + field: 'exceptionTime', + title: '异常发生时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'exceptionName', + title: '异常名', + minWidth: 180, + }, + { + field: 'processStatus', + title: '处理状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS }, + }, + }, + { + title: '操作', + minWidth: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'id', + label: '日志编号', + }, + { + field: 'traceId', + label: '链路追踪', + }, + { + field: 'applicationName', + label: '应用名', + }, + { + field: 'userId', + label: '用户Id', + }, + { + field: 'userType', + label: '用户类型', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.USER_TYPE, + value: val, + }); + }, + }, + { + field: 'userIp', + label: '用户 IP', + }, + { + field: 'userAgent', + label: '用户 UA', + }, + { + field: 'requestMethod', + label: '请求信息', + render: (val, data) => { + if (val && data?.requestUrl) { + return `${val} ${data.requestUrl}`; + } + return ''; + }, + }, + { + field: 'requestParams', + label: '请求参数', + render: (val) => { + if (val) { + return h(JsonViewer, { + value: JSON.parse(val), + previewMode: true, + }); + } + return ''; + }, + }, + { + field: 'exceptionTime', + label: '异常时间', + render: (val) => { + return formatDateTime(val) as string; + }, + }, + { + field: 'exceptionName', + label: '异常名', + }, + { + field: 'exceptionStackTrace', + label: '异常堆栈', + show: (val) => !val, + render: (val) => { + if (val) { + return h('textarea', { + value: val, + style: + 'width: 100%; min-height: 200px; max-height: 400px; resize: vertical;', + readonly: true, + }); + } + return ''; + }, + }, + { + field: 'processStatus', + label: '处理状态', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS, + value: val, + }); + }, + }, + { + field: 'processUserId', + label: '处理人', + show: (val) => !val, + }, + { + field: 'processTime', + label: '处理时间', + show: (val) => !val, + render: (val) => { + return formatDateTime(val) as string; + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/apiErrorLog/index.vue b/apps/web-ele/src/views/infra/apiErrorLog/index.vue new file mode 100644 index 0000000..964217c --- /dev/null +++ b/apps/web-ele/src/views/infra/apiErrorLog/index.vue @@ -0,0 +1,156 @@ + + + diff --git a/apps/web-ele/src/views/infra/apiErrorLog/modules/detail.vue b/apps/web-ele/src/views/infra/apiErrorLog/modules/detail.vue new file mode 100644 index 0000000..705cfdc --- /dev/null +++ b/apps/web-ele/src/views/infra/apiErrorLog/modules/detail.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-ele/src/views/infra/build/index.vue b/apps/web-ele/src/views/infra/build/index.vue new file mode 100644 index 0000000..53ec087 --- /dev/null +++ b/apps/web-ele/src/views/infra/build/index.vue @@ -0,0 +1,181 @@ + + + + diff --git a/apps/web-ele/src/views/infra/codegen/data.ts b/apps/web-ele/src/views/infra/codegen/data.ts new file mode 100644 index 0000000..a02a6e1 --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/data.ts @@ -0,0 +1,552 @@ +import type { Recordable } from '@vben/types'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { InfraCodegenApi } from '#/api/infra/codegen'; +import type { SystemMenuApi } from '#/api/system/menu'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { IconifyIcon } from '@vben/icons'; +import { handleTree } from '@vben/utils'; + +import { getDataSourceConfigList } from '#/api/infra/data-source-config'; +import { getMenuList } from '#/api/system/menu'; +import { $t } from '#/locales'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 导入数据库表的表单 */ +export function useImportTableFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'dataSourceConfigId', + label: '数据源', + component: 'ApiSelect', + componentProps: { + api: getDataSourceConfigList, + labelField: 'name', + valueField: 'id', + autoSelect: 'first', + placeholder: '请选择数据源', + }, + rules: 'selectRequired', + }, + { + fieldName: 'name', + label: '表名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入表名称', + }, + }, + { + fieldName: 'comment', + label: '表描述', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入表描述', + }, + }, + ]; +} + +/** 导入数据库表表格列定义 */ +export function useImportTableColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { field: 'name', title: '表名称', minWidth: 200 }, + { field: 'comment', title: '表描述', minWidth: 200 }, + ]; +} + +/** 基本信息表单的 schema */ +export function useBasicInfoFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'tableName', + label: '表名称', + component: 'Input', + componentProps: { + placeholder: '请输入仓库名称', + }, + rules: 'required', + }, + { + fieldName: 'tableComment', + label: '表描述', + component: 'Input', + componentProps: { + placeholder: '请输入表描述', + }, + rules: 'required', + }, + { + fieldName: 'className', + label: '实体类名称', + component: 'Input', + componentProps: { + placeholder: '请输入实体类名称', + }, + rules: 'required', + help: '默认去除表名的前缀。如果存在重复,则需要手动添加前缀,避免 MyBatis 报 Alias 重复的问题。', + }, + { + fieldName: 'author', + label: '作者', + component: 'Input', + componentProps: { + placeholder: '请输入作者', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + rows: 3, + placeholder: '请输入备注', + }, + formItemClass: 'md:col-span-2', + }, + ]; +} + +/** 生成信息表单基础 schema */ +export function useGenerationInfoBaseFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Select', + fieldName: 'templateType', + label: '生成模板', + componentProps: { + options: getDictOptions( + DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE, + 'number', + ), + class: 'w-full', + }, + rules: 'selectRequired', + }, + { + component: 'Select', + fieldName: 'frontType', + label: '前端类型', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE, 'number'), + class: 'w-full', + }, + rules: 'selectRequired', + }, + { + component: 'Select', + fieldName: 'scene', + label: '生成场景', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE, 'number'), + class: 'w-full', + }, + rules: 'selectRequired', + }, + { + fieldName: 'parentMenuId', + label: '上级菜单', + help: '分配到指定菜单下,例如 系统管理', + component: 'ApiTreeSelect', + componentProps: { + clearable: true, + api: async () => { + const data = await getMenuList(); + data.unshift({ + id: 0, + name: '顶级菜单', + } as SystemMenuApi.Menu); + return handleTree(data); + }, + class: 'w-full', + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择上级菜单', + filterTreeNode(input: string, node: Recordable) { + if (!input || input.length === 0) { + return true; + } + const name: string = node.label ?? ''; + if (!name) return false; + return name.includes(input) || $t(name).includes(input); + }, + showSearch: true, + treeDefaultExpandedKeys: [0], + }, + rules: 'selectRequired', + renderComponentContent() { + return { + title({ label, icon }: { icon: string; label: string }) { + const components = []; + if (!label) return ''; + if (icon) { + components.push(h(IconifyIcon, { class: 'size-4', icon })); + } + components.push(h('span', { class: '' }, $t(label || ''))); + return h('div', { class: 'flex items-center gap-1' }, components); + }, + }; + }, + }, + { + component: 'Input', + fieldName: 'moduleName', + label: '模块名', + help: '模块名,即一级目录,例如 system、infra、tool 等等', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'businessName', + label: '业务名', + help: '业务名,即二级目录,例如 user、permission、dict 等等', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'className', + label: '类名称', + help: '类名称(首字母大写),例如SysUser、SysMenu、SysDictData 等等', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'classComment', + label: '类描述', + help: '用作类描述,例如 用户', + rules: 'required', + }, + ]; +} + +/** 树表信息 schema */ +export function useGenerationInfoTreeFormSchema( + columns: InfraCodegenApi.CodegenColumn[] = [], +): VbenFormSchema[] { + return [ + { + component: 'Divider', + fieldName: 'treeDivider', + label: '', + renderComponentContent: () => { + return { + default: () => ['树表信息'], + }; + }, + formItemClass: 'md:col-span-2', + }, + { + component: 'Select', + fieldName: 'treeParentColumnId', + label: '父编号字段', + help: '树显示的父编码字段名,例如 parent_Id', + componentProps: { + class: 'w-full', + clearable: true, + placeholder: '请选择', + options: columns.map((column) => ({ + label: column.columnName, + value: column.id, + })), + }, + rules: 'selectRequired', + }, + { + component: 'Select', + fieldName: 'treeNameColumnId', + label: '名称字段', + help: '树节点显示的名称字段,一般是 name', + componentProps: { + class: 'w-full', + clearable: true, + placeholder: '请选择名称字段', + options: columns.map((column) => ({ + label: column.columnName, + value: column.id, + })), + }, + rules: 'selectRequired', + }, + ]; +} + +/** 主子表信息 schema */ +export function useGenerationInfoSubTableFormSchema( + columns: InfraCodegenApi.CodegenColumn[] = [], + tables: InfraCodegenApi.CodegenTable[] = [], +): VbenFormSchema[] { + return [ + { + component: 'Divider', + fieldName: 'subDivider', + label: '', + renderComponentContent: () => { + return { + default: () => ['主子表信息'], + }; + }, + formItemClass: 'md:col-span-2', + }, + { + component: 'Select', + fieldName: 'masterTableId', + label: '关联的主表', + help: '关联主表(父表)的表名, 如:system_user', + componentProps: { + class: 'w-full', + clearable: true, + placeholder: '请选择', + options: tables.map((table) => ({ + label: `${table.tableName}:${table.tableComment}`, + value: table.id, + })), + }, + rules: 'selectRequired', + }, + { + component: 'Select', + fieldName: 'subJoinColumnId', + label: '子表关联的字段', + help: '子表关联的字段, 如:user_id', + componentProps: { + class: 'w-full', + clearable: true, + placeholder: '请选择', + options: columns.map((column) => ({ + label: `${column.columnName}:${column.columnComment}`, + value: column.id, + })), + }, + rules: 'selectRequired', + }, + { + component: 'RadioGroup', + fieldName: 'subJoinMany', + label: '关联关系', + help: '主表与子表的关联关系', + componentProps: { + class: 'w-full', + clearable: true, + placeholder: '请选择', + options: [ + { + label: '一对多', + value: true, + }, + { + label: '一对一', + value: false, + }, + ], + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'tableName', + label: '表名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入表名称', + }, + }, + { + fieldName: 'tableComment', + label: '表描述', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入表描述', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + getDataSourceConfigName?: (dataSourceConfigId: number) => string | undefined, +): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'dataSourceConfigId', + title: '数据源', + minWidth: 120, + formatter: ({ cellValue }) => getDataSourceConfigName?.(cellValue) || '-', + }, + { + field: 'tableName', + title: '表名称', + minWidth: 200, + }, + { + field: 'tableComment', + title: '表描述', + minWidth: 200, + }, + { + field: 'className', + title: '实体', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'updateTime', + title: '更新时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 280, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 代码生成表格列定义 */ +export function useCodegenColumnTableColumns(): VxeTableGridOptions['columns'] { + return [ + { field: 'columnName', title: '字段列名', minWidth: 130 }, + { + field: 'columnComment', + title: '字段描述', + minWidth: 100, + slots: { default: 'columnComment' }, + }, + { field: 'dataType', title: '物理类型', minWidth: 100 }, + { + field: 'javaType', + title: 'Java 类型', + minWidth: 130, + slots: { default: 'javaType' }, + params: { + options: [ + { label: 'Long', value: 'Long' }, + { label: 'String', value: 'String' }, + { label: 'Integer', value: 'Integer' }, + { label: 'Double', value: 'Double' }, + { label: 'BigDecimal', value: 'BigDecimal' }, + { label: 'LocalDateTime', value: 'LocalDateTime' }, + { label: 'Boolean', value: 'Boolean' }, + ], + }, + }, + { + field: 'javaField', + title: 'Java 属性', + minWidth: 100, + slots: { default: 'javaField' }, + }, + { + field: 'createOperation', + title: '插入', + width: 40, + slots: { default: 'createOperation' }, + }, + { + field: 'updateOperation', + title: '编辑', + width: 40, + slots: { default: 'updateOperation' }, + }, + { + field: 'listOperationResult', + title: '列表', + width: 40, + slots: { default: 'listOperationResult' }, + }, + { + field: 'listOperation', + title: '查询', + width: 40, + slots: { default: 'listOperation' }, + }, + { + field: 'listOperationCondition', + title: '查询方式', + minWidth: 100, + slots: { default: 'listOperationCondition' }, + params: { + options: [ + { label: '=', value: '=' }, + { label: '!=', value: '!=' }, + { label: '>', value: '>' }, + { label: '>=', value: '>=' }, + { label: '<', value: '<' }, + { label: '<=', value: '<=' }, + { label: 'LIKE', value: 'LIKE' }, + { label: 'BETWEEN', value: 'BETWEEN' }, + ], + }, + }, + { + field: 'nullable', + title: '允许空', + width: 60, + slots: { default: 'nullable' }, + }, + { + field: 'htmlType', + title: '显示类型', + width: 130, + slots: { default: 'htmlType' }, + params: { + options: [ + { label: '文本框', value: 'input' }, + { label: '文本域', value: 'textarea' }, + { label: '下拉框', value: 'select' }, + { label: '单选框', value: 'radio' }, + { label: '复选框', value: 'checkbox' }, + { label: '日期控件', value: 'datetime' }, + { label: '图片上传', value: 'imageUpload' }, + { label: '文件上传', value: 'fileUpload' }, + { label: '富文本控件', value: 'editor' }, + ], + }, + }, + { + field: 'dictType', + title: '字典类型', + width: 120, + slots: { default: 'dictType' }, + }, + { + field: 'example', + title: '示例', + minWidth: 100, + slots: { default: 'example' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/codegen/edit/index.vue b/apps/web-ele/src/views/infra/codegen/edit/index.vue new file mode 100644 index 0000000..63161ed --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/edit/index.vue @@ -0,0 +1,169 @@ + + + diff --git a/apps/web-ele/src/views/infra/codegen/index.vue b/apps/web-ele/src/views/infra/codegen/index.vue new file mode 100644 index 0000000..cab2e64 --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/index.vue @@ -0,0 +1,283 @@ + + diff --git a/apps/web-ele/src/views/infra/codegen/modules/basic-info.vue b/apps/web-ele/src/views/infra/codegen/modules/basic-info.vue new file mode 100644 index 0000000..00c4991 --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/modules/basic-info.vue @@ -0,0 +1,45 @@ + + diff --git a/apps/web-ele/src/views/infra/codegen/modules/column-info.vue b/apps/web-ele/src/views/infra/codegen/modules/column-info.vue new file mode 100644 index 0000000..4933669 --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/modules/column-info.vue @@ -0,0 +1,152 @@ + + + diff --git a/apps/web-ele/src/views/infra/codegen/modules/generation-info.vue b/apps/web-ele/src/views/infra/codegen/modules/generation-info.vue new file mode 100644 index 0000000..56ffe61 --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/modules/generation-info.vue @@ -0,0 +1,176 @@ + + + diff --git a/apps/web-ele/src/views/infra/codegen/modules/import-table.vue b/apps/web-ele/src/views/infra/codegen/modules/import-table.vue new file mode 100644 index 0000000..2225c93 --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/modules/import-table.vue @@ -0,0 +1,119 @@ + + + diff --git a/apps/web-ele/src/views/infra/codegen/modules/preview-code.vue b/apps/web-ele/src/views/infra/codegen/modules/preview-code.vue new file mode 100644 index 0000000..4eb1104 --- /dev/null +++ b/apps/web-ele/src/views/infra/codegen/modules/preview-code.vue @@ -0,0 +1,279 @@ + + + diff --git a/apps/web-ele/src/views/infra/config/data.ts b/apps/web-ele/src/views/infra/config/data.ts new file mode 100644 index 0000000..c5fd28c --- /dev/null +++ b/apps/web-ele/src/views/infra/config/data.ts @@ -0,0 +1,185 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'category', + label: '参数分类', + component: 'Input', + componentProps: { + placeholder: '请输入参数分类', + }, + rules: 'required', + }, + { + fieldName: 'name', + label: '参数名称', + component: 'Input', + componentProps: { + placeholder: '请输入参数名称', + }, + rules: 'required', + }, + { + fieldName: 'key', + label: '参数键名', + component: 'Input', + componentProps: { + placeholder: '请输入参数键名', + }, + rules: 'required', + }, + { + fieldName: 'value', + label: '参数键值', + component: 'Input', + componentProps: { + placeholder: '请输入参数键值', + }, + rules: 'required', + }, + { + fieldName: 'visible', + label: '是否可见', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + }, + defaultValue: true, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '参数名称', + component: 'Input', + componentProps: { + placeholder: '请输入参数名称', + clearable: true, + }, + }, + { + fieldName: 'key', + label: '参数键名', + component: 'Input', + componentProps: { + placeholder: '请输入参数键名', + clearable: true, + }, + }, + { + fieldName: 'type', + label: '系统内置', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE, 'number'), + placeholder: '请选择系统内置', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '参数主键', + minWidth: 100, + }, + { + field: 'category', + title: '参数分类', + minWidth: 120, + }, + { + field: 'name', + title: '参数名称', + minWidth: 200, + }, + { + field: 'key', + title: '参数键名', + minWidth: 200, + }, + { + field: 'value', + title: '参数键值', + minWidth: 150, + }, + { + field: 'visible', + title: '是否可见', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'type', + title: '系统内置', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_CONFIG_TYPE }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 150, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/config/index.vue b/apps/web-ele/src/views/infra/config/index.vue new file mode 100644 index 0000000..1869904 --- /dev/null +++ b/apps/web-ele/src/views/infra/config/index.vue @@ -0,0 +1,183 @@ + + + diff --git a/apps/web-ele/src/views/infra/config/modules/form.vue b/apps/web-ele/src/views/infra/config/modules/form.vue new file mode 100644 index 0000000..777f29d --- /dev/null +++ b/apps/web-ele/src/views/infra/config/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/infra/dataSourceConfig/data.ts b/apps/web-ele/src/views/infra/dataSourceConfig/data.ts new file mode 100644 index 0000000..6f4c602 --- /dev/null +++ b/apps/web-ele/src/views/infra/dataSourceConfig/data.ts @@ -0,0 +1,92 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '数据源名称', + component: 'Input', + componentProps: { + placeholder: '请输入数据源名称', + }, + rules: 'required', + }, + { + fieldName: 'url', + label: '数据源连接', + component: 'Input', + componentProps: { + placeholder: '请输入数据源连接', + }, + rules: 'required', + }, + { + fieldName: 'username', + label: '用户名', + component: 'Input', + componentProps: { + placeholder: '请输入用户名', + }, + rules: 'required', + }, + { + fieldName: 'password', + label: '密码', + component: 'Input', + componentProps: { + placeholder: '请输入密码', + type: 'password', + }, + rules: 'required', + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '主键编号', + minWidth: 100, + }, + { + field: 'name', + title: '数据源名称', + minWidth: 150, + }, + { + field: 'url', + title: '数据源连接', + minWidth: 300, + }, + { + field: 'username', + title: '用户名', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/dataSourceConfig/index.vue b/apps/web-ele/src/views/infra/dataSourceConfig/index.vue new file mode 100644 index 0000000..c885bf4 --- /dev/null +++ b/apps/web-ele/src/views/infra/dataSourceConfig/index.vue @@ -0,0 +1,159 @@ + + + diff --git a/apps/web-ele/src/views/infra/dataSourceConfig/modules/form.vue b/apps/web-ele/src/views/infra/dataSourceConfig/modules/form.vue new file mode 100644 index 0000000..6291c74 --- /dev/null +++ b/apps/web-ele/src/views/infra/dataSourceConfig/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo01/data.ts b/apps/web-ele/src/views/infra/demo/demo01/data.ts new file mode 100644 index 0000000..d2242a8 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo01/data.ts @@ -0,0 +1,152 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { Demo01ContactApi } from '#/api/infra/demo/demo01'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名字', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + }, + { + fieldName: 'sex', + label: '性别', + rules: 'required', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + }, + }, + { + fieldName: 'birthday', + label: '出生年', + rules: 'required', + component: 'DatePicker', + componentProps: { + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + placeholder: '请选择出生年', + class: '!w-full', + }, + }, + { + fieldName: 'description', + label: '简介', + rules: 'required', + component: 'RichTextarea', + }, + { + fieldName: 'avatar', + label: '头像', + component: 'ImageUpload', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入名字', + }, + }, + { + fieldName: 'sex', + label: '性别', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + placeholder: '请选择性别', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 120, + }, + { + field: 'name', + title: '名字', + minWidth: 120, + }, + { + field: 'sex', + title: '性别', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_USER_SEX }, + }, + }, + { + field: 'birthday', + title: '出生年', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + field: 'description', + title: '简介', + minWidth: 120, + }, + { + field: 'avatar', + title: '头像', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/demo/demo01/index.vue b/apps/web-ele/src/views/infra/demo/demo01/index.vue new file mode 100644 index 0000000..4b5455a --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo01/index.vue @@ -0,0 +1,185 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo01/modules/form.vue b/apps/web-ele/src/views/infra/demo/demo01/modules/form.vue new file mode 100644 index 0000000..d97dd9c --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo01/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo02/data.ts b/apps/web-ele/src/views/infra/demo/demo02/data.ts new file mode 100644 index 0000000..6fb82a8 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo02/data.ts @@ -0,0 +1,120 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { Demo02CategoryApi } from '#/api/infra/demo/demo02'; + +import { handleTree } from '@vben/utils'; + +import { getDemo02CategoryList } from '#/api/infra/demo/demo02'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'parentId', + label: '上级示例分类', + component: 'ApiTreeSelect', + componentProps: { + clearable: true, + api: async () => { + const data = await getDemo02CategoryList({}); + data.unshift({ + id: 0, + name: '顶级示例分类', + }); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择上级示例分类', + treeDefaultExpandAll: true, + }, + rules: 'selectRequired', + }, + { + fieldName: 'name', + label: '名字', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入名字', + }, + }, + { + fieldName: 'parentId', + label: '父级编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入父级编号', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 120, + }, + { + field: 'name', + title: '名字', + minWidth: 120, + treeNode: true, + }, + { + field: 'parentId', + title: '父级编号', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/demo/demo02/index.vue b/apps/web-ele/src/views/infra/demo/demo02/index.vue new file mode 100644 index 0000000..b5eab45 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo02/index.vue @@ -0,0 +1,175 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo02/modules/form.vue b/apps/web-ele/src/views/infra/demo/demo02/modules/form.vue new file mode 100644 index 0000000..2f4a581 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo02/modules/form.vue @@ -0,0 +1,91 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/erp/data.ts b/apps/web-ele/src/views/infra/demo/demo03/erp/data.ts new file mode 100644 index 0000000..63580aa --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/erp/data.ts @@ -0,0 +1,381 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名字', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + }, + { + fieldName: 'sex', + label: '性别', + rules: 'required', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + }, + }, + { + fieldName: 'birthday', + label: '出生日期', + rules: 'required', + component: 'DatePicker', + componentProps: { + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + placeholder: '请选择出生日期', + class: '!w-full', + }, + }, + { + fieldName: 'description', + label: '简介', + rules: 'required', + component: 'RichTextarea', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入名字', + }, + }, + { + fieldName: 'sex', + label: '性别', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + placeholder: '请选择性别', + }, + }, + { + fieldName: 'description', + label: '简介', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入简介', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 120, + }, + { + field: 'name', + title: '名字', + minWidth: 120, + }, + { + field: 'sex', + title: '性别', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_USER_SEX }, + }, + }, + { + field: 'birthday', + title: '出生日期', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + field: 'description', + title: '简介', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 280, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +// ==================== 子表(学生课程) ==================== + +/** 新增/修改的表单 */ +export function useDemo03CourseFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名字', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + }, + { + fieldName: 'score', + label: '分数', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入分数', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useDemo03CourseGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'studentId', + label: '学生编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入学生编号', + }, + }, + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入名字', + }, + }, + { + fieldName: 'score', + label: '分数', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入分数', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useDemo03CourseGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 120, + }, + { + field: 'studentId', + title: '学生编号', + minWidth: 120, + }, + { + field: 'name', + title: '名字', + minWidth: 120, + }, + { + field: 'score', + title: '分数', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 280, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +// ==================== 子表(学生班级) ==================== + +/** 新增/修改的表单 */ +export function useDemo03GradeFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名字', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + }, + { + fieldName: 'teacher', + label: '班主任', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入班主任', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useDemo03GradeGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'studentId', + label: '学生编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入学生编号', + }, + }, + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入名字', + }, + }, + { + fieldName: 'teacher', + label: '班主任', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入班主任', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useDemo03GradeGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 120, + }, + { + field: 'studentId', + title: '学生编号', + minWidth: 120, + }, + { + field: 'name', + title: '名字', + minWidth: 120, + }, + { + field: 'teacher', + title: '班主任', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 280, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/demo/demo03/erp/index.vue b/apps/web-ele/src/views/infra/demo/demo03/erp/index.vue new file mode 100644 index 0000000..043dd98 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/erp/index.vue @@ -0,0 +1,207 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-course-form.vue b/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-course-form.vue new file mode 100644 index 0000000..dfb34f1 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-course-form.vue @@ -0,0 +1,92 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-course-list.vue b/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-course-list.vue new file mode 100644 index 0000000..076e33a --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-course-list.vue @@ -0,0 +1,197 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-grade-form.vue b/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-grade-form.vue new file mode 100644 index 0000000..0b41e20 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-grade-form.vue @@ -0,0 +1,92 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-grade-list.vue b/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-grade-list.vue new file mode 100644 index 0000000..c4b42fa --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/erp/modules/demo03-grade-list.vue @@ -0,0 +1,197 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/erp/modules/form.vue b/apps/web-ele/src/views/infra/demo/demo03/erp/modules/form.vue new file mode 100644 index 0000000..b0ad481 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/erp/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/inner/data.ts b/apps/web-ele/src/views/infra/demo/demo03/inner/data.ts new file mode 100644 index 0000000..e79cf78 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/inner/data.ts @@ -0,0 +1,275 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名字', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + }, + { + fieldName: 'sex', + label: '性别', + rules: 'required', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + }, + }, + { + fieldName: 'birthday', + label: '出生日期', + rules: 'required', + component: 'DatePicker', + componentProps: { + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + class: '!w-full', + }, + }, + { + fieldName: 'description', + label: '简介', + rules: 'required', + component: 'RichTextarea', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入名字', + }, + }, + { + fieldName: 'sex', + label: '性别', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + placeholder: '请选择性别', + }, + }, + { + fieldName: 'description', + label: '简介', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入简介', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { type: 'expand', width: 80, slots: { content: 'expand_content' } }, + { + field: 'id', + title: '编号', + minWidth: 120, + }, + { + field: 'name', + title: '名字', + minWidth: 120, + }, + { + field: 'sex', + title: '性别', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_USER_SEX }, + }, + }, + { + field: 'birthday', + title: '出生日期', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + field: 'description', + title: '简介', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 280, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +// ==================== 子表(学生课程) ==================== + +/** 新增/修改列表的字段 */ +export function useDemo03CourseGridEditColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '名字', + minWidth: 120, + slots: { default: 'name' }, + }, + { + field: 'score', + title: '分数', + minWidth: 120, + slots: { default: 'score' }, + }, + { + title: '操作', + width: 280, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 列表的字段 */ +export function useDemo03CourseGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 120, + }, + { + field: 'studentId', + title: '学生编号', + minWidth: 120, + }, + { + field: 'name', + title: '名字', + minWidth: 120, + }, + { + field: 'score', + title: '分数', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + ]; +} + +// ==================== 子表(学生班级) ==================== + +/** 新增/修改的表单 */ +export function useDemo03GradeFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名字', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + }, + { + fieldName: 'teacher', + label: '班主任', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入班主任', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useDemo03GradeGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 120, + }, + { + field: 'studentId', + title: '学生编号', + minWidth: 120, + }, + { + field: 'name', + title: '名字', + minWidth: 120, + }, + { + field: 'teacher', + title: '班主任', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/demo/demo03/inner/index.vue b/apps/web-ele/src/views/infra/demo/demo03/inner/index.vue new file mode 100644 index 0000000..eadfc82 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/inner/index.vue @@ -0,0 +1,201 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-course-form.vue b/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-course-form.vue new file mode 100644 index 0000000..0402bf4 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-course-form.vue @@ -0,0 +1,119 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-course-list.vue b/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-course-list.vue new file mode 100644 index 0000000..50297db --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-course-list.vue @@ -0,0 +1,56 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-grade-form.vue b/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-grade-form.vue new file mode 100644 index 0000000..3e28546 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-grade-form.vue @@ -0,0 +1,51 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-grade-list.vue b/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-grade-list.vue new file mode 100644 index 0000000..1ad2db7 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/inner/modules/demo03-grade-list.vue @@ -0,0 +1,56 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/inner/modules/form.vue b/apps/web-ele/src/views/infra/demo/demo03/inner/modules/form.vue new file mode 100644 index 0000000..753a689 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/inner/modules/form.vue @@ -0,0 +1,119 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/normal/data.ts b/apps/web-ele/src/views/infra/demo/demo03/normal/data.ts new file mode 100644 index 0000000..ef76bdd --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/normal/data.ts @@ -0,0 +1,211 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名字', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + }, + { + fieldName: 'sex', + label: '性别', + rules: 'required', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + }, + }, + { + fieldName: 'birthday', + label: '出生日期', + rules: 'required', + component: 'DatePicker', + componentProps: { + showTime: true, + format: 'YYYY-MM-DD HH:mm:ss', + valueFormat: 'x', + placeholder: '请选择出生日期', + class: '!w-full', + }, + }, + { + fieldName: 'description', + label: '简介', + rules: 'required', + component: 'RichTextarea', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名字', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入名字', + }, + }, + { + fieldName: 'sex', + label: '性别', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + placeholder: '请选择性别', + }, + }, + { + fieldName: 'description', + label: '简介', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入简介', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 120, + }, + { + field: 'name', + title: '名字', + minWidth: 120, + }, + { + field: 'sex', + title: '性别', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_USER_SEX }, + }, + }, + { + field: 'birthday', + title: '出生日期', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + field: 'description', + title: '简介', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +// ==================== 子表(学生课程) ==================== + +/** 新增/修改列表的字段 */ +export function useDemo03CourseGridEditColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '名字', + minWidth: 120, + slots: { default: 'name' }, + }, + { + field: 'score', + title: '分数', + minWidth: 120, + slots: { default: 'score' }, + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +// ==================== 子表(学生班级) ==================== + +/** 新增/修改的表单 */ +export function useDemo03GradeFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名字', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入名字', + }, + }, + { + fieldName: 'teacher', + label: '班主任', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入班主任', + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/infra/demo/demo03/normal/index.vue b/apps/web-ele/src/views/infra/demo/demo03/normal/index.vue new file mode 100644 index 0000000..7c8c07d --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/normal/index.vue @@ -0,0 +1,185 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/normal/modules/demo03-course-form.vue b/apps/web-ele/src/views/infra/demo/demo03/normal/modules/demo03-course-form.vue new file mode 100644 index 0000000..f460aee --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/normal/modules/demo03-course-form.vue @@ -0,0 +1,118 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/normal/modules/demo03-grade-form.vue b/apps/web-ele/src/views/infra/demo/demo03/normal/modules/demo03-grade-form.vue new file mode 100644 index 0000000..5d5f396 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/normal/modules/demo03-grade-form.vue @@ -0,0 +1,51 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/demo03/normal/modules/form.vue b/apps/web-ele/src/views/infra/demo/demo03/normal/modules/form.vue new file mode 100644 index 0000000..7b9d1b0 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/demo03/normal/modules/form.vue @@ -0,0 +1,119 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo01/index.vue b/apps/web-ele/src/views/infra/demo/general/demo01/index.vue new file mode 100644 index 0000000..0da2e0a --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo01/index.vue @@ -0,0 +1,314 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo01/modules/form.vue b/apps/web-ele/src/views/infra/demo/general/demo01/modules/form.vue new file mode 100644 index 0000000..05c2cf7 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo01/modules/form.vue @@ -0,0 +1,149 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo02/index.vue b/apps/web-ele/src/views/infra/demo/general/demo02/index.vue new file mode 100644 index 0000000..500cbe2 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo02/index.vue @@ -0,0 +1,264 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo02/modules/form.vue b/apps/web-ele/src/views/infra/demo/general/demo02/modules/form.vue new file mode 100644 index 0000000..e7743ec --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo02/modules/form.vue @@ -0,0 +1,138 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/erp/index.vue b/apps/web-ele/src/views/infra/demo/general/demo03/erp/index.vue new file mode 100644 index 0000000..9c62a60 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/erp/index.vue @@ -0,0 +1,342 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-course-form.vue b/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-course-form.vue new file mode 100644 index 0000000..dbabbae --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-course-form.vue @@ -0,0 +1,114 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-course-list.vue b/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-course-list.vue new file mode 100644 index 0000000..32abd38 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-course-list.vue @@ -0,0 +1,294 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-grade-form.vue b/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-grade-form.vue new file mode 100644 index 0000000..62c5a78 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-grade-form.vue @@ -0,0 +1,114 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-grade-list.vue b/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-grade-list.vue new file mode 100644 index 0000000..adc374d --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/demo03-grade-list.vue @@ -0,0 +1,294 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/form.vue b/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/form.vue new file mode 100644 index 0000000..d9cc415 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/erp/modules/form.vue @@ -0,0 +1,140 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/inner/index.vue b/apps/web-ele/src/views/infra/demo/general/demo03/inner/index.vue new file mode 100644 index 0000000..7fca75c --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/inner/index.vue @@ -0,0 +1,335 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-course-form.vue b/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-course-form.vue new file mode 100644 index 0000000..06cea78 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-course-form.vue @@ -0,0 +1,95 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-course-list.vue b/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-course-list.vue new file mode 100644 index 0000000..678042a --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-course-list.vue @@ -0,0 +1,59 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-grade-form.vue b/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-grade-form.vue new file mode 100644 index 0000000..c399fc3 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-grade-form.vue @@ -0,0 +1,67 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-grade-list.vue b/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-grade-list.vue new file mode 100644 index 0000000..4272b63 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/demo03-grade-list.vue @@ -0,0 +1,59 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/form.vue b/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/form.vue new file mode 100644 index 0000000..63ee76c --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/inner/modules/form.vue @@ -0,0 +1,176 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/normal/index.vue b/apps/web-ele/src/views/infra/demo/general/demo03/normal/index.vue new file mode 100644 index 0000000..3824493 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/normal/index.vue @@ -0,0 +1,314 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/normal/modules/demo03-course-form.vue b/apps/web-ele/src/views/infra/demo/general/demo03/normal/modules/demo03-course-form.vue new file mode 100644 index 0000000..7382839 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/normal/modules/demo03-course-form.vue @@ -0,0 +1,95 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/normal/modules/demo03-grade-form.vue b/apps/web-ele/src/views/infra/demo/general/demo03/normal/modules/demo03-grade-form.vue new file mode 100644 index 0000000..9357c68 --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/normal/modules/demo03-grade-form.vue @@ -0,0 +1,67 @@ + + + diff --git a/apps/web-ele/src/views/infra/demo/general/demo03/normal/modules/form.vue b/apps/web-ele/src/views/infra/demo/general/demo03/normal/modules/form.vue new file mode 100644 index 0000000..9dd6a5f --- /dev/null +++ b/apps/web-ele/src/views/infra/demo/general/demo03/normal/modules/form.vue @@ -0,0 +1,176 @@ + + + diff --git a/apps/web-ele/src/views/infra/druid/index.vue b/apps/web-ele/src/views/infra/druid/index.vue new file mode 100644 index 0000000..01ec58d --- /dev/null +++ b/apps/web-ele/src/views/infra/druid/index.vue @@ -0,0 +1,36 @@ + + + + diff --git a/apps/web-ele/src/views/mall/promotion/components/diy-editor/util.ts b/apps/web-ele/src/views/mall/promotion/components/diy-editor/util.ts new file mode 100644 index 0000000..8926ca5 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/components/diy-editor/util.ts @@ -0,0 +1,115 @@ +import type { NavigationBarProperty } from './components/mobile/navigation-bar/config'; +import type { PageConfigProperty } from './components/mobile/page-config/config'; +import type { TabBarProperty } from './components/mobile/tab-bar/config'; + +/** 页面装修组件 */ +export interface DiyComponent { + uid?: number; // 用于区分同一种组件的不同实例 + id: string; // 组件唯一标识 + name: string; // 组件名称 + icon: string; // 组件图标 + /* + 组件位置: + top: 固定于手机顶部,例如 顶部的导航栏 + bottom: 固定于手机底部,例如 底部的菜单导航栏 + center: 位于手机中心,每个组件占一行,顺序向下排列 + 空:同 center + fixed: 由组件自己决定位置,如弹窗位于手机中心、浮动按钮一般位于手机右下角 + */ + position?: '' | 'bottom' | 'center' | 'fixed' | 'top'; + property: T; // 组件属性 +} + +/** 页面装修组件库 */ +export interface DiyComponentLibrary { + name: string; // 组件库名称 + extended: boolean; // 是否展开 + components: string[]; // 组件列表 +} + +/** 组件样式 */ +export interface ComponentStyle { + bgType: 'color' | 'img'; // 背景类型 + bgColor: string; // 背景颜色 + bgImg: string; // 背景图片 + // 外边距 + margin: number; + marginTop: number; + marginRight: number; + marginBottom: number; + marginLeft: number; + // 内边距 + padding: number; + paddingTop: number; + paddingRight: number; + paddingBottom: number; + paddingLeft: number; + // 边框圆角 + borderRadius: number; + borderTopLeftRadius: number; + borderTopRightRadius: number; + borderBottomRightRadius: number; + borderBottomLeftRadius: number; +} + +/** 页面配置 */ +export interface PageConfig { + page: PageConfigProperty; // 页面属性 + navigationBar: NavigationBarProperty; // 顶部导航栏属性 + tabBar?: TabBarProperty; // 底部导航菜单属性 + + components: PageComponent[]; // 页面组件列表 +} + +export type PageComponent = Pick, 'id' | 'property'>; // 页面组件,只保留组件 ID,组件属性 + +/** 页面组件库 */ +export const PAGE_LIBS = [ + { + name: '基础组件', + extended: true, + components: [ + 'SearchBar', + 'NoticeBar', + 'MenuSwiper', + 'MenuGrid', + 'MenuList', + 'Popover', + 'FloatingActionButton', + ], + }, + { + name: '图文组件', + extended: true, + components: [ + 'ImageBar', + 'Carousel', + 'TitleBar', + 'VideoPlayer', + 'Divider', + 'MagicCube', + 'HotZone', + ], + }, + { + name: '商品组件', + extended: true, + components: ['ProductCard', 'ProductList'], + }, + { + name: '用户组件', + extended: true, + components: ['UserCard', 'UserOrder', 'UserWallet', 'UserCoupon'], + }, + { + name: '营销组件', + extended: true, + components: [ + 'PromotionCombination', + 'PromotionSeckill', + 'PromotionPoint', + 'CouponCard', + 'PromotionArticle', + ], + }, +] as DiyComponentLibrary[]; diff --git a/apps/web-ele/src/views/mall/promotion/components/draggable/index.vue b/apps/web-ele/src/views/mall/promotion/components/draggable/index.vue new file mode 100644 index 0000000..3c673a7 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/components/draggable/index.vue @@ -0,0 +1,95 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/components/index.ts b/apps/web-ele/src/views/mall/promotion/components/index.ts new file mode 100644 index 0000000..6dbe001 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/components/index.ts @@ -0,0 +1,10 @@ +export { default as AppLinkInput } from './app-link-input/index.vue'; +export { default as AppLinkSelectDialog } from './app-link-input/select-dialog.vue'; +export { default as ColorInput } from './color-input/index.vue'; +export { default as DiyEditor } from './diy-editor/index.vue'; +export { type DiyComponentLibrary, PAGE_LIBS } from './diy-editor/util'; +export { default as Draggable } from './draggable/index.vue'; +export { default as InputWithColor } from './input-with-color/index.vue'; + +export { default as MagicCubeEditor } from './magic-cube-editor/index.vue'; +export { default as VerticalButtonGroup } from './vertical-button-group/index.vue'; diff --git a/apps/web-ele/src/views/mall/promotion/components/input-with-color/index.vue b/apps/web-ele/src/views/mall/promotion/components/input-with-color/index.vue new file mode 100644 index 0000000..d825678 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/components/input-with-color/index.vue @@ -0,0 +1,44 @@ + + + + diff --git a/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/index.vue b/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/index.vue new file mode 100644 index 0000000..5f45238 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/index.vue @@ -0,0 +1,254 @@ + + diff --git a/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/util.ts b/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/util.ts new file mode 100644 index 0000000..d55cd4b --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/components/magic-cube-editor/util.ts @@ -0,0 +1,71 @@ +/** 坐标点 */ +export interface Point { + x: number; + y: number; +} + +/** 矩形 */ +export interface Rect { + left: number; // 左上角 X 轴坐标 + top: number; // 左上角 Y 轴坐标 + right: number; // 右下角 X 轴坐标 + bottom: number; // 右下角 Y 轴坐标 + width: number; // 矩形宽度 + height: number; // 矩形高度 +} + +/** + * 判断两个矩形是否重叠 + * + * @param a 矩形 A + * @param b 矩形 B + */ +export function isOverlap(a: Rect, b: Rect): boolean { + return ( + a.left < b.left + b.width && + a.left + a.width > b.left && + a.top < b.top + b.height && + a.height + a.top > b.top + ); +} + +/** + * 检查坐标点是否在矩形内 + * @param hotArea 矩形 + * @param point 坐标 + */ +export function isContains(hotArea: Rect, point: Point): boolean { + return ( + point.x >= hotArea.left && + point.x < hotArea.right && + point.y >= hotArea.top && + point.y < hotArea.bottom + ); +} + +/** + * 在两个坐标点中间,创建一个矩形 + * + * 存在以下情况: + * 1. 两个坐标点是同一个位置,只占一个位置的正方形,宽高都为 1 + * 2. X 轴坐标相同,只占一行的矩形,高度为 1 + * 3. Y 轴坐标相同,只占一列的矩形,宽度为 1 + * 4. 多行多列的矩形 + * + * @param a 坐标点一 + * @param b 坐标点二 + */ +export function createRect(a: Point, b: Point): Rect { + // 计算矩形的范围 + let [left, left2] = [a.x, b.x].toSorted(); + left = left ?? 0; + left2 = left2 ?? 0; + let [top, top2] = [a.y, b.y].toSorted(); + top = top ?? 0; + top2 = top2 ?? 0; + const right = left2 + 1; + const bottom = top2 + 1; + const height = bottom - top; + const width = right - left; + return { left, right, top, bottom, height, width }; +} diff --git a/apps/web-ele/src/views/mall/promotion/components/vertical-button-group/index.vue b/apps/web-ele/src/views/mall/promotion/components/vertical-button-group/index.vue new file mode 100644 index 0000000..16f4b33 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/components/vertical-button-group/index.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/apps/web-ele/src/views/mall/promotion/coupon/components/index.ts b/apps/web-ele/src/views/mall/promotion/coupon/components/index.ts new file mode 100644 index 0000000..24cb4e2 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/coupon/components/index.ts @@ -0,0 +1,2 @@ +export { default as CouponSelect } from './select.vue'; +export { default as CouponSendForm } from './send-form.vue'; diff --git a/apps/web-ele/src/views/mall/promotion/coupon/components/select-data.ts b/apps/web-ele/src/views/mall/promotion/coupon/components/select-data.ts new file mode 100644 index 0000000..4820881 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/coupon/components/select-data.ts @@ -0,0 +1,119 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { + discountFormat, + remainedCountFormat, + takeLimitCountFormat, + validityTypeFormat, +} from '../formatter'; + +/** 优惠券选择的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '优惠券名称', + component: 'Input', + componentProps: { + placeholder: '请输入优惠券名称', + clearable: true, + }, + }, + { + fieldName: 'discountType', + label: '优惠类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE, 'number'), + placeholder: '请选择优惠类型', + clearable: true, + }, + }, + ]; +} + +/** 优惠券选择的表格列 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 55 }, + { + field: 'name', + title: '优惠券名称', + minWidth: 140, + }, + { + field: 'productScope', + title: '类型', + minWidth: 80, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_PRODUCT_SCOPE }, + }, + }, + { + field: 'discountType', + title: '优惠类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_DISCOUNT_TYPE }, + }, + }, + { + field: 'discountPrice', + title: '优惠力度', + minWidth: 100, + formatter: ({ row }) => discountFormat(row), + }, + { + field: 'takeType', + title: '领取方式', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE }, + }, + }, + { + field: 'validityType', + title: '使用时间', + minWidth: 185, + align: 'center', + formatter: ({ row }) => validityTypeFormat(row), + }, + { + field: 'totalCount', + title: '发放数量', + minWidth: 100, + align: 'center', + }, + { + field: 'remainedCount', + title: '剩余数量', + minWidth: 100, + align: 'center', + formatter: ({ row }) => remainedCountFormat(row), + }, + { + field: 'takeLimitCount', + title: '领取上限', + minWidth: 100, + align: 'center', + formatter: ({ row }) => takeLimitCountFormat(row), + }, + { + field: 'status', + title: '状态', + minWidth: 80, + align: 'center', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/coupon/components/select.vue b/apps/web-ele/src/views/mall/promotion/coupon/components/select.vue new file mode 100644 index 0000000..073206a --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/coupon/components/select.vue @@ -0,0 +1,66 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/coupon/components/send-form-data.ts b/apps/web-ele/src/views/mall/promotion/coupon/components/send-form-data.ts new file mode 100644 index 0000000..6961bfa --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/coupon/components/send-form-data.ts @@ -0,0 +1,64 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridProps } from '#/adapter/vxe-table'; + +import { + discountFormat, + remainedCountFormat, + usePriceFormat, + validityTypeFormat, +} from '../formatter'; + +/** 搜索表单的 schema */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'name', + label: '优惠券名称', + componentProps: { + placeholder: '请输入优惠券名称', + clearable: true, + }, + }, + ]; +} + +/** 表格列配置 */ +export function useGridColumns(): VxeGridProps['columns'] { + return [ + { + title: '优惠券名称', + field: 'name', + minWidth: 120, + }, + { + title: '优惠金额 / 折扣', + field: 'discount', + minWidth: 120, + formatter: ({ row }) => discountFormat(row), + }, + { + title: '最低消费', + field: 'usePrice', + minWidth: 100, + formatter: ({ row }) => usePriceFormat(row), + }, + { + title: '有效期限', + field: 'validityType', + minWidth: 140, + formatter: ({ row }) => validityTypeFormat(row), + }, + { + title: '剩余数量', + minWidth: 100, + formatter: ({ row }) => remainedCountFormat(row), + }, + { + title: '操作', + width: 100, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/coupon/components/send-form.vue b/apps/web-ele/src/views/mall/promotion/coupon/components/send-form.vue new file mode 100644 index 0000000..ce60962 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/coupon/components/send-form.vue @@ -0,0 +1,86 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/coupon/data.ts b/apps/web-ele/src/views/mall/promotion/coupon/data.ts new file mode 100644 index 0000000..2c5dc8b --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/coupon/data.ts @@ -0,0 +1,111 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +import { getRangePickerDefaultProps } from '#/utils'; + +import { discountFormat } from './formatter'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'nickname', + label: '会员昵称', + component: 'Input', + componentProps: { + placeholder: '请输入会员昵称', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '领取时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'nickname', + title: '会员昵称', + minWidth: 100, + }, + { + field: 'name', + title: '优惠券名称', + minWidth: 140, + }, + { + field: 'productScope', + title: '类型', + minWidth: 110, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_PRODUCT_SCOPE }, + }, + }, + { + field: 'discountType', + title: '优惠', + minWidth: 110, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_DISCOUNT_TYPE }, + }, + }, + { + field: 'discountPrice', + title: '优惠力度', + minWidth: 110, + formatter: ({ row }) => { + return discountFormat(row); + }, + }, + { + field: 'takeType', + title: '领取方式', + minWidth: 110, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE }, + }, + }, + { + field: 'status', + title: '状态', + minWidth: 110, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_COUPON_STATUS }, + }, + }, + { + field: 'createTime', + title: '领取时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'useTime', + title: '使用时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'actions', + title: '操作', + width: 100, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/coupon/formatter.ts b/apps/web-ele/src/views/mall/promotion/coupon/formatter.ts new file mode 100644 index 0000000..2f34863 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/coupon/formatter.ts @@ -0,0 +1,64 @@ +import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate'; + +import { + CouponTemplateValidityTypeEnum, + PromotionDiscountTypeEnum, +} from '@vben/constants'; +import { floatToFixed2, formatDate } from '@vben/utils'; + +/** 格式化【优惠金额/折扣】 */ +export function discountFormat(row: MallCouponTemplateApi.CouponTemplate) { + if (row.discountType === PromotionDiscountTypeEnum.PRICE.type) { + return `¥${floatToFixed2(row.discountPrice)}`; + } + if (row.discountType === PromotionDiscountTypeEnum.PERCENT.type) { + return `${row.discountPercent}%`; + } + return `未知【${row.discountType}】`; +} + +/** 格式化【领取上限】 */ +export function takeLimitCountFormat( + row: MallCouponTemplateApi.CouponTemplate, +) { + if (row.takeLimitCount) { + if (row.takeLimitCount === -1) { + return '无领取限制'; + } + return `${row.takeLimitCount} 张/人`; + } else { + return ' '; + } +} + +/** 格式化【有效期限】 */ +export function validityTypeFormat(row: MallCouponTemplateApi.CouponTemplate) { + if (row.validityType === CouponTemplateValidityTypeEnum.DATE.type) { + return `${formatDate(row.validStartTime)} 至 ${formatDate(row.validEndTime)}`; + } + if (row.validityType === CouponTemplateValidityTypeEnum.TERM.type) { + return `领取后第 ${row.fixedStartTerm} - ${row.fixedEndTerm} 天内可用`; + } + return `未知【${row.validityType}】`; +} + +/** 格式化【totalCount】 */ +export function totalCountFormat(row: MallCouponTemplateApi.CouponTemplate) { + if (row.totalCount === -1) { + return '不限制'; + } + return row.totalCount; +} + +/** 格式化【剩余数量】 */ +export function remainedCountFormat(row: MallCouponTemplateApi.CouponTemplate) { + if (row.totalCount === -1) { + return '不限制'; + } + return row.totalCount - row.takeCount; +} + +/** 格式化【最低消费】 */ +export function usePriceFormat(row: MallCouponTemplateApi.CouponTemplate) { + return `¥${floatToFixed2(row.usePrice)}`; +} diff --git a/apps/web-ele/src/views/mall/promotion/coupon/index.vue b/apps/web-ele/src/views/mall/promotion/coupon/index.vue new file mode 100644 index 0000000..17580fc --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/coupon/index.vue @@ -0,0 +1,148 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/coupon/template/data.ts b/apps/web-ele/src/views/mall/promotion/coupon/template/data.ts new file mode 100644 index 0000000..035259b --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/coupon/template/data.ts @@ -0,0 +1,487 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { MallCouponTemplateApi } from '#/api/mall/promotion/coupon/couponTemplate'; + +import { + CommonStatusEnum, + CouponTemplateTakeTypeEnum, + CouponTemplateValidityTypeEnum, + DICT_TYPE, + PromotionDiscountTypeEnum, + PromotionProductScopeEnum, +} from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +import { + discountFormat, + remainedCountFormat, + takeLimitCountFormat, + totalCountFormat, + validityTypeFormat, +} from '../formatter'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '优惠券名称', + component: 'Input', + componentProps: { + placeholder: '请输入优惠券名称', + }, + rules: 'required', + }, + { + fieldName: 'description', + label: '优惠券描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入优惠券描述', + }, + }, + { + fieldName: 'productScope', + label: '优惠劵类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE, 'number'), + }, + rules: 'required', + defaultValue: PromotionProductScopeEnum.ALL.scope, + }, + // TODO @puhui999: 商品选择器优化 + { + fieldName: 'productSpuIds', + label: '商品', + component: 'Input', + componentProps: { + placeholder: '请选择商品', + }, + dependencies: { + triggerFields: ['productScope', 'productScopeValues'], + show: (model) => + model.productScope === PromotionProductScopeEnum.SPU.scope, + trigger(values, form) { + // 当加载已有数据时,根据 productScopeValues 设置 productSpuIds + if ( + values.productScope === PromotionProductScopeEnum.SPU.scope && + values.productScopeValues + ) { + form.setFieldValue('productSpuIds', values.productScopeValues); + } + }, + }, + rules: 'required', + }, + // TODO @puhui999: 商品分类选择器优化 + { + fieldName: 'productCategoryIds', + label: '商品分类', + component: 'Input', + componentProps: { + placeholder: '请选择商品分类', + }, + dependencies: { + triggerFields: ['productScope', 'productScopeValues'], + show: (model) => + model.productScope === PromotionProductScopeEnum.CATEGORY.scope, + trigger(values, form) { + // 当加载已有数据时,根据 productScopeValues 设置 productCategoryIds + if ( + values.productScope === PromotionProductScopeEnum.CATEGORY.scope && + values.productScopeValues + ) { + const categoryIds = values.productScopeValues; + // 单选时使用数组不能反显,取第一个元素 + form.setFieldValue( + 'productCategoryIds', + Array.isArray(categoryIds) && categoryIds.length > 0 + ? categoryIds[0] + : categoryIds, + ); + } + }, + }, + rules: 'required', + }, + { + fieldName: 'discountType', + label: '优惠类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE, 'number'), + }, + rules: 'required', + defaultValue: PromotionDiscountTypeEnum.PRICE.type, + }, + { + fieldName: 'discountPrice', + label: '优惠券面额', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + placeholder: '请输入优惠金额,单位:元', + addonAfter: '元', + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['discountType'], + show: (model) => + model.discountType === PromotionDiscountTypeEnum.PRICE.type, + }, + rules: 'required', + }, + { + fieldName: 'discountPercent', + label: '优惠券折扣', + component: 'InputNumber', + componentProps: { + min: 1, + max: 9.9, + precision: 1, + placeholder: '优惠券折扣不能小于 1 折,且不可大于 9.9 折', + addonAfter: '折', + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['discountType'], + show: (model) => + model.discountType === PromotionDiscountTypeEnum.PERCENT.type, + }, + rules: 'required', + }, + { + fieldName: 'discountLimitPrice', + label: '最多优惠', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + placeholder: '请输入最多优惠', + addonAfter: '元', + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['discountType'], + show: (model) => + model.discountType === PromotionDiscountTypeEnum.PERCENT.type, + }, + rules: 'required', + }, + { + fieldName: 'usePrice', + label: '满多少元可以使用', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + placeholder: '无门槛请设为 0', + addonAfter: '元', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'takeType', + label: '领取方式', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE, 'number'), + }, + rules: 'required', + defaultValue: CouponTemplateTakeTypeEnum.USER.type, + }, + { + fieldName: 'totalCount', + label: '发放数量', + component: 'InputNumber', + componentProps: { + min: -1, + placeholder: '发放数量,没有之后不能领取或发放,-1 为不限制', + addonAfter: '张', + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['takeType'], + show: (model) => + model.takeType === CouponTemplateTakeTypeEnum.USER.type, + }, + rules: 'required', + }, + { + fieldName: 'takeLimitCount', + label: '每人限领个数', + component: 'InputNumber', + componentProps: { + min: -1, + placeholder: '设置为 -1 时,可无限领取', + addonAfter: '张', + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['takeType'], + show: (model) => model.takeType === 1, + }, + rules: 'required', + }, + { + fieldName: 'validityType', + label: '有效期类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions( + DICT_TYPE.PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE, + 'number', + ), + }, + defaultValue: CouponTemplateValidityTypeEnum.DATE.type, + rules: 'required', + }, + { + fieldName: 'validTimes', + label: '固定日期', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + valueFormat: 'x', + }, + dependencies: { + triggerFields: ['validityType'], + show: (model) => + model.validityType === CouponTemplateValidityTypeEnum.DATE.type, + }, + rules: 'required', + }, + { + fieldName: 'fixedStartTerm', + label: '领取日期', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '第 0 为今天生效', + addonBefore: '第', + addonAfter: '天', + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['validityType'], + show: (model) => + model.validityType === CouponTemplateValidityTypeEnum.TERM.type, + }, + rules: 'required', + }, + { + fieldName: 'fixedEndTerm', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入结束天数', + addonBefore: '至', + addonAfter: '天有效', + controlsPosition: 'right', + class: '!w-full', + }, + dependencies: { + triggerFields: ['validityType'], + show: (model) => + model.validityType === CouponTemplateValidityTypeEnum.TERM.type, + }, + rules: 'required', + }, + { + fieldName: 'productScopeValues', + component: 'Input', + dependencies: { + triggerFields: ['productScope', 'productSpuIds', 'productCategoryIds'], + show: () => false, + trigger(values, form) { + switch (values.productScope) { + case PromotionProductScopeEnum.CATEGORY.scope: { + const categoryIds = Array.isArray(values.productCategoryIds) + ? values.productCategoryIds + : [values.productCategoryIds]; + form.setFieldValue('productScopeValues', categoryIds); + break; + } + case PromotionProductScopeEnum.SPU.scope: { + form.setFieldValue('productScopeValues', values.productSpuIds); + break; + } + } + }, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '优惠券名称', + component: 'Input', + componentProps: { + placeholder: '请输入优惠劵名', + clearable: true, + }, + }, + { + fieldName: 'discountType', + label: '优惠类型', + component: 'Select', + componentProps: { + placeholder: '请选择优惠类型', + clearable: true, + options: getDictOptions(DICT_TYPE.PROMOTION_DISCOUNT_TYPE, 'number'), + }, + }, + { + fieldName: 'status', + label: '优惠券状态', + component: 'Select', + componentProps: { + placeholder: '请选择优惠券状态', + clearable: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onStatusChange?: ( + newStatus: number, + row: MallCouponTemplateApi.CouponTemplate, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '优惠券名称', + minWidth: 140, + }, + { + field: 'productScope', + title: '类型', + minWidth: 130, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_PRODUCT_SCOPE }, + }, + }, + { + field: 'discountType', + title: '优惠', + minWidth: 110, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_DISCOUNT_TYPE }, + }, + }, + { + field: 'discountPrice', + title: '优惠力度', + minWidth: 110, + formatter: ({ row }) => { + return discountFormat(row); + }, + }, + { + field: 'takeType', + title: '领取方式', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE }, + }, + }, + { + field: 'validityType', + title: '使用时间', + minWidth: 180, + formatter: ({ row }) => { + return validityTypeFormat(row); + }, + }, + { + field: 'totalCount', + title: '发放数量', + minWidth: 100, + formatter: ({ row }) => { + return totalCountFormat(row); + }, + }, + { + field: 'remainedCount', + title: '剩余数量', + minWidth: 100, + formatter: ({ row }) => { + return remainedCountFormat(row); + }, + }, + { + field: 'takeLimitCount', + title: '领取上限', + minWidth: 100, + formatter: ({ row }) => { + return takeLimitCountFormat(row); + }, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + align: 'center', + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: 'CellSwitch', + props: { + activeValue: CommonStatusEnum.ENABLE, + inactiveValue: CommonStatusEnum.DISABLE, + }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/coupon/template/index.vue b/apps/web-ele/src/views/mall/promotion/coupon/template/index.vue new file mode 100644 index 0000000..cbe9993 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/coupon/template/index.vue @@ -0,0 +1,161 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/coupon/template/modules/form.vue b/apps/web-ele/src/views/mall/promotion/coupon/template/modules/form.vue new file mode 100644 index 0000000..c8b735c --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/coupon/template/modules/form.vue @@ -0,0 +1,149 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/discountActivity/data.ts b/apps/web-ele/src/views/mall/promotion/discountActivity/data.ts new file mode 100644 index 0000000..8de164a --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/discountActivity/data.ts @@ -0,0 +1,161 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDate } from '@vben/utils'; + +/** 表单配置 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '活动名称', + component: 'Input', + componentProps: { + placeholder: '请输入活动名称', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '活动状态', + component: 'Select', + componentProps: { + placeholder: '请选择活动状态', + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: 'required', + }, + { + fieldName: 'startTime', + label: '开始时间', + component: 'DatePicker', + componentProps: { + placeholder: '请选择开始时间', + showTime: false, + valueFormat: 'x', + format: 'YYYY-MM-DD', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'endTime', + label: '结束时间', + component: 'DatePicker', + componentProps: { + placeholder: '请选择结束时间', + showTime: false, + valueFormat: 'x', + format: 'YYYY-MM-DD', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 4, + }, + }, + // TODO + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '活动名称', + component: 'Input', + componentProps: { + placeholder: '请输入活动名称', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '活动状态', + component: 'Select', + componentProps: { + placeholder: '请选择活动状态', + clearable: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + { + fieldName: 'activeTime', + label: '活动时间', + component: 'RangePicker', + componentProps: { + placeholder: ['开始时间', '结束时间'], + clearable: true, + valueFormat: 'YYYY-MM-DD HH:mm:ss', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '活动编号', + minWidth: 80, + }, + { + field: 'name', + title: '活动名称', + minWidth: 140, + }, + { + field: 'activityTime', + title: '活动时间', + minWidth: 210, + formatter: ({ row }) => { + if (!row.startTime || !row.endTime) return ''; + return `${formatDate(row.startTime, 'YYYY-MM-DD')} ~ ${formatDate(row.endTime, 'YYYY-MM-DD')}`; + }, + }, + { + field: 'status', + title: '活动状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 150, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/discountActivity/index.vue b/apps/web-ele/src/views/mall/promotion/discountActivity/index.vue new file mode 100644 index 0000000..6d8d158 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/discountActivity/index.vue @@ -0,0 +1,157 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/discountActivity/modules/form.vue b/apps/web-ele/src/views/mall/promotion/discountActivity/modules/form.vue new file mode 100644 index 0000000..c1b67fe --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/discountActivity/modules/form.vue @@ -0,0 +1,96 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/diy/page/data.ts b/apps/web-ele/src/views/mall/promotion/diy/page/data.ts new file mode 100644 index 0000000..cec26ff --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/diy/page/data.ts @@ -0,0 +1,110 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单配置 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '页面名称', + component: 'Input', + componentProps: { + placeholder: '请输入页面名称', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 4, + }, + }, + { + fieldName: 'previewPicUrls', + component: 'ImageUpload', + label: '预览图', + componentProps: { + maxNumber: 10, + multiple: true, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '页面名称', + component: 'Input', + componentProps: { + placeholder: '请输入页面名称', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 80, + }, + { + field: 'previewPicUrls', + title: '预览图', + minWidth: 120, + cellRender: { + name: 'CellImages', + }, + }, + { + field: 'name', + title: '页面名称', + minWidth: 150, + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/diy/page/decorate/index.vue b/apps/web-ele/src/views/mall/promotion/diy/page/decorate/index.vue new file mode 100644 index 0000000..2dddd59 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/diy/page/decorate/index.vue @@ -0,0 +1,65 @@ + + diff --git a/apps/web-ele/src/views/mall/promotion/diy/page/index.vue b/apps/web-ele/src/views/mall/promotion/diy/page/index.vue new file mode 100644 index 0000000..af3b0cf --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/diy/page/index.vue @@ -0,0 +1,152 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/diy/page/modules/form.vue b/apps/web-ele/src/views/mall/promotion/diy/page/modules/form.vue new file mode 100644 index 0000000..b44b4a6 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/diy/page/modules/form.vue @@ -0,0 +1,84 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/diy/template/data.ts b/apps/web-ele/src/views/mall/promotion/diy/template/data.ts new file mode 100644 index 0000000..e3c8f14 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/diy/template/data.ts @@ -0,0 +1,121 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 表单配置 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + placeholder: '请输入模板名称', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 4, + }, + }, + { + fieldName: 'previewPicUrls', + component: 'ImageUpload', + label: '预览图', + componentProps: { + maxNumber: 10, + multiple: true, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + placeholder: '请输入模板名称', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 80, + }, + { + field: 'previewPicUrls', + title: '预览图', + minWidth: 120, + cellRender: { + name: 'CellImages', + }, + }, + { + field: 'name', + title: '模板名称', + minWidth: 150, + }, + { + field: 'used', + title: '是否使用', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 250, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/diy/template/decorate/index.vue b/apps/web-ele/src/views/mall/promotion/diy/template/decorate/index.vue new file mode 100644 index 0000000..3f655be --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/diy/template/decorate/index.vue @@ -0,0 +1,213 @@ + + diff --git a/apps/web-ele/src/views/mall/promotion/diy/template/index.vue b/apps/web-ele/src/views/mall/promotion/diy/template/index.vue new file mode 100644 index 0000000..df68ff8 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/diy/template/index.vue @@ -0,0 +1,180 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/diy/template/modules/form.vue b/apps/web-ele/src/views/mall/promotion/diy/template/modules/form.vue new file mode 100644 index 0000000..8e088d7 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/diy/template/modules/form.vue @@ -0,0 +1,86 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/a.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/a.png new file mode 100644 index 0000000..3293900 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/a.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/aini.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/aini.png new file mode 100644 index 0000000..02cf5c4 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/aini.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/aixin.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/aixin.png new file mode 100644 index 0000000..25e6422 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/aixin.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/baiyan.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/baiyan.png new file mode 100644 index 0000000..d16260a Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/baiyan.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/bizui.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/bizui.png new file mode 100644 index 0000000..a3b1800 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/bizui.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/buhaoyisi.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/buhaoyisi.png new file mode 100644 index 0000000..54c4b3f Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/buhaoyisi.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/bukesiyi.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/bukesiyi.png new file mode 100644 index 0000000..5f272e3 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/bukesiyi.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/dajing.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/dajing.png new file mode 100644 index 0000000..8649727 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/dajing.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/danao.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/danao.png new file mode 100644 index 0000000..aa85a29 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/danao.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/daxiao.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/daxiao.png new file mode 100644 index 0000000..26206bc Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/daxiao.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/dianzan.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/dianzan.png new file mode 100644 index 0000000..2e7f00e Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/dianzan.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/emo.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/emo.png new file mode 100644 index 0000000..9c84551 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/emo.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/esi.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/esi.png new file mode 100644 index 0000000..84e9726 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/esi.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/fadai.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/fadai.png new file mode 100644 index 0000000..0772de2 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/fadai.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/fankun.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/fankun.png new file mode 100644 index 0000000..6e18dac Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/fankun.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/feiwen.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/feiwen.png new file mode 100644 index 0000000..be97616 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/feiwen.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/fennu.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/fennu.png new file mode 100644 index 0000000..20c5733 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/fennu.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/ganga.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/ganga.png new file mode 100644 index 0000000..30ec329 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/ganga.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/ganmao.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/ganmao.png new file mode 100644 index 0000000..35bbb89 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/ganmao.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/hanyan.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/hanyan.png new file mode 100644 index 0000000..a0bc838 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/hanyan.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/haochi.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/haochi.png new file mode 100644 index 0000000..2e52b6b Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/haochi.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/hongxin.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/hongxin.png new file mode 100644 index 0000000..65b5de8 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/hongxin.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/huaixiao.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/huaixiao.png new file mode 100644 index 0000000..bc0e76c Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/huaixiao.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/jingkong.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/jingkong.png new file mode 100644 index 0000000..7aa6584 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/jingkong.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/jingshu.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/jingshu.png new file mode 100644 index 0000000..0e984d6 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/jingshu.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/jingya.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/jingya.png new file mode 100644 index 0000000..9ba6bab Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/jingya.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/kaixin.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/kaixin.png new file mode 100644 index 0000000..29c9f5d Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/kaixin.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/keai.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/keai.png new file mode 100644 index 0000000..d3b582c Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/keai.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/keshui.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/keshui.png new file mode 100644 index 0000000..cef489e Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/keshui.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/kun.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/kun.png new file mode 100644 index 0000000..1ddc388 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/kun.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/lengku.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/lengku.png new file mode 100644 index 0000000..c5c6fee Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/lengku.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/liuhan.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/liuhan.png new file mode 100644 index 0000000..e6ddc6f Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/liuhan.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/liukoushui.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/liukoushui.png new file mode 100644 index 0000000..3e2fba6 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/liukoushui.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/liulei.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/liulei.png new file mode 100644 index 0000000..dbf8204 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/liulei.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/mengbi.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/mengbi.png new file mode 100644 index 0000000..a4206ee Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/mengbi.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/mianwubiaoqing.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/mianwubiaoqing.png new file mode 100644 index 0000000..6f315b9 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/mianwubiaoqing.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/nanguo.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/nanguo.png new file mode 100644 index 0000000..19b9fb9 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/nanguo.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/outu.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/outu.png new file mode 100644 index 0000000..2f9a06d Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/outu.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/picture.svg b/apps/web-ele/src/views/mall/promotion/kefu/asserts/picture.svg new file mode 100644 index 0000000..8811d49 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/asserts/picture.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/shengqi.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/shengqi.png new file mode 100644 index 0000000..7dce41d Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/shengqi.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/shuizhuo.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/shuizhuo.png new file mode 100644 index 0000000..97d0f0a Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/shuizhuo.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/tianshi.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/tianshi.png new file mode 100644 index 0000000..eb922dd Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/tianshi.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/xiaodiaoya.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/xiaodiaoya.png new file mode 100644 index 0000000..29fbc0e Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/xiaodiaoya.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/xiaoku.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/xiaoku.png new file mode 100644 index 0000000..88a169d Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/xiaoku.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/xinsui.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/xinsui.png new file mode 100644 index 0000000..a0f572a Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/xinsui.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/xiong.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/xiong.png new file mode 100644 index 0000000..43dfd70 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/xiong.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/yiwen.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/yiwen.png new file mode 100644 index 0000000..4c0da70 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/yiwen.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/yun.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/yun.png new file mode 100644 index 0000000..56e5d02 Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/yun.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/asserts/ziya.png b/apps/web-ele/src/views/mall/promotion/kefu/asserts/ziya.png new file mode 100644 index 0000000..593ef5e Binary files /dev/null and b/apps/web-ele/src/views/mall/promotion/kefu/asserts/ziya.png differ diff --git a/apps/web-ele/src/views/mall/promotion/kefu/index.vue b/apps/web-ele/src/views/mall/promotion/kefu/index.vue new file mode 100644 index 0000000..096cb0c --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/index.vue @@ -0,0 +1,122 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/conversation-list.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/conversation-list.vue new file mode 100644 index 0000000..9f22f84 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/conversation-list.vue @@ -0,0 +1,250 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/member/member-info.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/member/member-info.vue new file mode 100644 index 0000000..4b87f37 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/member/member-info.vue @@ -0,0 +1,224 @@ + + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/member/order-browsing-history.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/member/order-browsing-history.vue new file mode 100644 index 0000000..a7a5fa6 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/member/order-browsing-history.vue @@ -0,0 +1,47 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/member/product-browsing-history.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/member/product-browsing-history.vue new file mode 100644 index 0000000..c35d87b --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/member/product-browsing-history.vue @@ -0,0 +1,58 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/message-list.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/message-list.vue new file mode 100644 index 0000000..6331870 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/message-list.vue @@ -0,0 +1,429 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/message/message-item.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/message/message-item.vue new file mode 100644 index 0000000..7c8e315 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/message/message-item.vue @@ -0,0 +1,25 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/message/order-item.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/message/order-item.vue new file mode 100644 index 0000000..ff0ecd9 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/message/order-item.vue @@ -0,0 +1,121 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/message/product-item.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/message/product-item.vue new file mode 100644 index 0000000..da58082 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/message/product-item.vue @@ -0,0 +1,73 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/constants.ts b/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/constants.ts new file mode 100644 index 0000000..266a6cf --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/constants.ts @@ -0,0 +1,17 @@ +/** 客服消息类型枚举类 */ +export const KeFuMessageContentTypeEnum = { + TEXT: 1, // 文本消息 + IMAGE: 2, // 图片消息 + VOICE: 3, // 语音消息 + VIDEO: 4, // 视频消息 + SYSTEM: 5, // 系统消息 + // ========== 商城特殊消息 ========== + PRODUCT: 10, // 商品消息 + ORDER: 11, // 订单消息" +}; + +/** Promotion 的 WebSocket 消息类型枚举类 */ +export const WebSocketMessageTypeConstants = { + KEFU_MESSAGE_TYPE: 'kefu_message_type', // 客服消息类型 + KEFU_MESSAGE_ADMIN_READ: 'kefu_message_read_status_change', // 客服消息管理员已读 +}; diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/emoji-select-popover.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/emoji-select-popover.vue new file mode 100644 index 0000000..2d14ec1 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/emoji-select-popover.vue @@ -0,0 +1,53 @@ + + + + diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/emoji.ts b/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/emoji.ts new file mode 100644 index 0000000..f1bd193 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/emoji.ts @@ -0,0 +1,126 @@ +import { onMounted, ref } from 'vue'; + +import { isEmpty } from '@vben/utils'; + +const emojiList = [ + { name: '[笑掉牙]', file: 'xiaodiaoya.png' }, + { name: '[可爱]', file: 'keai.png' }, + { name: '[冷酷]', file: 'lengku.png' }, + { name: '[闭嘴]', file: 'bizui.png' }, + { name: '[生气]', file: 'shengqi.png' }, + { name: '[惊恐]', file: 'jingkong.png' }, + { name: '[瞌睡]', file: 'keshui.png' }, + { name: '[大笑]', file: 'daxiao.png' }, + { name: '[爱心]', file: 'aixin.png' }, + { name: '[坏笑]', file: 'huaixiao.png' }, + { name: '[飞吻]', file: 'feiwen.png' }, + { name: '[疑问]', file: 'yiwen.png' }, + { name: '[开心]', file: 'kaixin.png' }, + { name: '[发呆]', file: 'fadai.png' }, + { name: '[流泪]', file: 'liulei.png' }, + { name: '[汗颜]', file: 'hanyan.png' }, + { name: '[惊悚]', file: 'jingshu.png' }, + { name: '[困~]', file: 'kun.png' }, + { name: '[心碎]', file: 'xinsui.png' }, + { name: '[天使]', file: 'tianshi.png' }, + { name: '[晕]', file: 'yun.png' }, + { name: '[啊]', file: 'a.png' }, + { name: '[愤怒]', file: 'fennu.png' }, + { name: '[睡着]', file: 'shuizhuo.png' }, + { name: '[面无表情]', file: 'mianwubiaoqing.png' }, + { name: '[难过]', file: 'nanguo.png' }, + { name: '[犯困]', file: 'fankun.png' }, + { name: '[好吃]', file: 'haochi.png' }, + { name: '[呕吐]', file: 'outu.png' }, + { name: '[龇牙]', file: 'ziya.png' }, + { name: '[懵比]', file: 'mengbi.png' }, + { name: '[白眼]', file: 'baiyan.png' }, + { name: '[饿死]', file: 'esi.png' }, + { name: '[凶]', file: 'xiong.png' }, + { name: '[感冒]', file: 'ganmao.png' }, + { name: '[流汗]', file: 'liuhan.png' }, + { name: '[笑哭]', file: 'xiaoku.png' }, + { name: '[流口水]', file: 'liukoushui.png' }, + { name: '[尴尬]', file: 'ganga.png' }, + { name: '[惊讶]', file: 'jingya.png' }, + { name: '[大惊]', file: 'dajing.png' }, + { name: '[不好意思]', file: 'buhaoyisi.png' }, + { name: '[大闹]', file: 'danao.png' }, + { name: '[不可思议]', file: 'bukesiyi.png' }, + { name: '[爱你]', file: 'aini.png' }, + { name: '[红心]', file: 'hongxin.png' }, + { name: '[点赞]', file: 'dianzan.png' }, + { name: '[恶魔]', file: 'emo.png' }, +]; + +export interface Emoji { + name: string; + url: string; +} + +export function useEmoji() { + const emojiPathList = ref([]); + + /** 加载本地图片 */ + async function initStaticEmoji() { + const pathList = import.meta.glob('../../asserts/*.{png,jpg,jpeg,svg}'); + for (const path in pathList) { + const imageModule: any = await pathList[path]?.(); + emojiPathList.value.push({ path, src: imageModule.default }); + } + } + + /** 初始化 */ + onMounted(async () => { + if (isEmpty(emojiPathList.value)) { + await initStaticEmoji(); + } + }); + + /** + * 将文本中的表情替换成图片 + * + * @return 替换后的文本 + * @param content 消息内容 + */ + function replaceEmoji(content: string) { + let newData = content; + if (typeof newData !== 'object') { + const reg = /\[(.+?)\]/g; // [] 中括号 + const zhEmojiName = newData.match(reg); + if (zhEmojiName) { + zhEmojiName.forEach((item) => { + const emojiFile = getEmojiFileByName(item); + newData = newData.replace( + item, + ``, + ); + }); + } + } + return newData; + } + + /** 获得所有表情 */ + function getEmojiList(): Emoji[] { + return emojiList.map((item) => ({ + url: getEmojiFileByName(item.name), + name: item.name, + })) as Emoji[]; + } + + function getEmojiFileByName(name: string) { + for (const emoji of emojiList) { + if (emoji.name === name) { + const emojiPath = emojiPathList.value.find( + (item: { path: string; src: string }) => + item.path.includes(emoji.file), + ); + return emojiPath ? emojiPath.src : undefined; + } + } + return false; + } + + return { replaceEmoji, getEmojiList }; +} diff --git a/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/picture-select-upload.vue b/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/picture-select-upload.vue new file mode 100644 index 0000000..b991f04 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/kefu/modules/tools/picture-select-upload.vue @@ -0,0 +1,90 @@ + + + + diff --git a/apps/web-ele/src/views/mall/promotion/point/activity/data.ts b/apps/web-ele/src/views/mall/promotion/point/activity/data.ts new file mode 100644 index 0000000..dcb750c --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/point/activity/data.ts @@ -0,0 +1,143 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +/** 表单配置 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'spuId', + label: '积分商城活动商品', + component: 'Input', + componentProps: { + placeholder: '请选择商品', + }, + rules: 'required', + }, + { + fieldName: 'sort', + label: '排序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入排序', + min: 0, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 4, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'status', + label: '活动状态', + component: 'Select', + componentProps: { + placeholder: '请选择活动状态', + clearable: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '活动编号', + minWidth: 80, + }, + { + field: 'picUrl', + title: '商品图片', + minWidth: 80, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'spuName', + title: '商品标题', + minWidth: 300, + }, + { + field: 'marketPrice', + title: '原价', + minWidth: 100, + formatter: 'formatFenToYuanAmount', + }, + { + field: 'point', + title: '兑换积分', + minWidth: 100, + }, + { + field: 'price', + title: '兑换金额', + minWidth: 100, + formatter: 'formatFenToYuanAmount', + }, + { + field: 'status', + title: '活动状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'stock', + title: '库存', + minWidth: 80, + }, + { + field: 'totalStock', + title: '总库存', + minWidth: 80, + }, + { + field: 'redeemedQuantity', + title: '已兑换数量', + minWidth: 100, + slots: { default: 'redeemedQuantity' }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 150, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/point/activity/index.vue b/apps/web-ele/src/views/mall/promotion/point/activity/index.vue new file mode 100644 index 0000000..4fcebfa --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/point/activity/index.vue @@ -0,0 +1,166 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/point/activity/modules/form.vue b/apps/web-ele/src/views/mall/promotion/point/activity/modules/form.vue new file mode 100644 index 0000000..5bded75 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/point/activity/modules/form.vue @@ -0,0 +1,105 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/point/components/index.ts b/apps/web-ele/src/views/mall/promotion/point/components/index.ts new file mode 100644 index 0000000..bb01940 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/point/components/index.ts @@ -0,0 +1 @@ +export { default as PointShowcase } from './showcase.vue'; diff --git a/apps/web-ele/src/views/mall/promotion/point/components/showcase.vue b/apps/web-ele/src/views/mall/promotion/point/components/showcase.vue new file mode 100644 index 0000000..f1bbe2a --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/point/components/showcase.vue @@ -0,0 +1,149 @@ + + + + diff --git a/apps/web-ele/src/views/mall/promotion/point/components/table-select.vue b/apps/web-ele/src/views/mall/promotion/point/components/table-select.vue new file mode 100644 index 0000000..eff545c --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/point/components/table-select.vue @@ -0,0 +1,239 @@ + + + + diff --git a/apps/web-ele/src/views/mall/promotion/rewardActivity/data.ts b/apps/web-ele/src/views/mall/promotion/rewardActivity/data.ts new file mode 100644 index 0000000..4109419 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/rewardActivity/data.ts @@ -0,0 +1,169 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +/** 表单配置 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '活动名称', + component: 'Input', + componentProps: { + placeholder: '请输入活动名称', + }, + rules: 'required', + }, + { + fieldName: 'startTime', + label: '开始时间', + component: 'DatePicker', + componentProps: { + placeholder: '请选择开始时间', + showTime: true, + valueFormat: 'x', + format: 'YYYY-MM-DD HH:mm:ss', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'endTime', + label: '结束时间', + component: 'DatePicker', + componentProps: { + placeholder: '请选择结束时间', + showTime: true, + valueFormat: 'x', + format: 'YYYY-MM-DD HH:mm:ss', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'conditionType', + label: '条件类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.PROMOTION_CONDITION_TYPE, 'number'), + }, + rules: 'required', + }, + { + fieldName: 'productScope', + label: '商品范围', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.PROMOTION_PRODUCT_SCOPE, 'number'), + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + rows: 4, + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '活动名称', + component: 'Input', + componentProps: { + placeholder: '请输入活动名称', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '活动状态', + component: 'Select', + componentProps: { + placeholder: '请选择活动状态', + clearable: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '活动时间', + component: 'RangePicker', + componentProps: { + placeholder: ['活动开始日期', '活动结束日期'], + clearable: true, + valueFormat: 'YYYY-MM-DD HH:mm:ss', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '活动名称', + minWidth: 140, + }, + { + field: 'productScope', + title: '活动范围', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PROMOTION_PRODUCT_SCOPE }, + }, + }, + { + field: 'startTime', + title: '活动开始时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'endTime', + title: '活动结束时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/rewardActivity/index.vue b/apps/web-ele/src/views/mall/promotion/rewardActivity/index.vue new file mode 100644 index 0000000..3a148d1 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/rewardActivity/index.vue @@ -0,0 +1,157 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/rewardActivity/modules/form.vue b/apps/web-ele/src/views/mall/promotion/rewardActivity/modules/form.vue new file mode 100644 index 0000000..e1c74b7 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/rewardActivity/modules/form.vue @@ -0,0 +1,103 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/seckill/activity/data.ts b/apps/web-ele/src/views/mall/promotion/seckill/activity/data.ts new file mode 100644 index 0000000..b274135 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/seckill/activity/data.ts @@ -0,0 +1,127 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '活动名称', + component: 'Input', + componentProps: { + placeholder: '请输入活动名称', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '活动状态', + component: 'Select', + componentProps: { + placeholder: '请选择活动状态', + clearable: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '活动编号', + minWidth: 80, + }, + { + field: 'name', + title: '活动名称', + minWidth: 140, + }, + { + field: 'configIds', + title: '秒杀时段', + minWidth: 220, + slots: { default: 'configIds' }, + }, + { + field: 'startTime', + title: '活动时间', + minWidth: 210, + slots: { default: 'timeRange' }, + }, + { + field: 'picUrl', + title: '商品图片', + minWidth: 80, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'spuName', + title: '商品标题', + minWidth: 300, + }, + { + field: 'marketPrice', + title: '原价', + minWidth: 100, + formatter: ({ row }) => `¥${(row.marketPrice / 100).toFixed(2)}`, + }, + { + field: 'seckillPrice', + title: '秒杀价', + minWidth: 100, + formatter: ({ row }) => { + if (!(row.products || row.products.length === 0)) { + return '¥0.00'; + } + const seckillPrice = Math.min( + ...row.products.map((item: any) => item.seckillPrice), + ); + return `¥${(seckillPrice / 100).toFixed(2)}`; + }, + }, + { + field: 'status', + title: '活动状态', + align: 'center', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'stock', + title: '库存', + align: 'center', + minWidth: 80, + }, + { + field: 'totalStock', + title: '总库存', + align: 'center', + minWidth: 80, + }, + { + field: 'createTime', + title: '创建时间', + align: 'center', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + align: 'center', + width: 150, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/seckill/activity/formatter.ts b/apps/web-ele/src/views/mall/promotion/seckill/activity/formatter.ts new file mode 100644 index 0000000..db1bce5 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/seckill/activity/formatter.ts @@ -0,0 +1,34 @@ +import { formatDate } from '@vben/utils'; + +// 全局变量,用于存储配置列表 +let configList: any[] = []; + +/** 设置配置列表 */ +export function setConfigList(list: any[]) { + configList = list; +} + +/** 格式化配置名称 */ +export function formatConfigNames(configId: number): string { + const config = configList.find((item) => item.id === configId); + return config === null || config === undefined + ? '' + : `${config.name}[${config.startTime} ~ ${config.endTime}]`; +} + +/** 格式化秒杀价格 */ +export function formatSeckillPrice(products: any[]): string { + if (!products || products.length === 0) { + return '¥0.00'; + } + const seckillPrice = Math.min(...products.map((item) => item.seckillPrice)); + return `¥${(seckillPrice / 100).toFixed(2)}`; +} + +/** 格式化活动时间范围 */ +export function formatTimeRange( + startTime: Date | string, + endTime: Date | string, +): string { + return `${formatDate(startTime, 'YYYY-MM-DD')} ~ ${formatDate(endTime, 'YYYY-MM-DD')}`; +} diff --git a/apps/web-ele/src/views/mall/promotion/seckill/activity/index.vue b/apps/web-ele/src/views/mall/promotion/seckill/activity/index.vue new file mode 100644 index 0000000..ea9d017 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/seckill/activity/index.vue @@ -0,0 +1,185 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/seckill/activity/modules/form.vue b/apps/web-ele/src/views/mall/promotion/seckill/activity/modules/form.vue new file mode 100644 index 0000000..3de767c --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/seckill/activity/modules/form.vue @@ -0,0 +1,121 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/seckill/components/index.ts b/apps/web-ele/src/views/mall/promotion/seckill/components/index.ts new file mode 100644 index 0000000..dd45216 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/seckill/components/index.ts @@ -0,0 +1 @@ +export { default as SeckillShowcase } from './showcase.vue'; diff --git a/apps/web-ele/src/views/mall/promotion/seckill/components/showcase.vue b/apps/web-ele/src/views/mall/promotion/seckill/components/showcase.vue new file mode 100644 index 0000000..5ee5fe6 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/seckill/components/showcase.vue @@ -0,0 +1,148 @@ + + + + diff --git a/apps/web-ele/src/views/mall/promotion/seckill/components/table-select.vue b/apps/web-ele/src/views/mall/promotion/seckill/components/table-select.vue new file mode 100644 index 0000000..ec3315b --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/seckill/components/table-select.vue @@ -0,0 +1,277 @@ + + + + diff --git a/apps/web-ele/src/views/mall/promotion/seckill/config/data.ts b/apps/web-ele/src/views/mall/promotion/seckill/config/data.ts new file mode 100644 index 0000000..85a7bf4 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/seckill/config/data.ts @@ -0,0 +1,153 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { MallSeckillConfigApi } from '#/api/mall/promotion/seckill/seckillConfig'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '秒杀时段名称', + component: 'Input', + componentProps: { + placeholder: '请输入秒杀时段名称', + }, + rules: 'required', + }, + { + fieldName: 'startTime', + label: '开始时间点', + component: 'TimePicker', + componentProps: { + format: 'HH:mm', + valueFormat: 'HH:mm', + placeholder: '请选择开始时间点', + }, + rules: 'required', + }, + { + fieldName: 'endTime', + label: '结束时间点', + component: 'TimePicker', + componentProps: { + format: 'HH:mm', + valueFormat: 'HH:mm', + placeholder: '请选择结束时间点', + }, + rules: 'required', + }, + { + fieldName: 'sliderPicUrls', + label: '秒杀轮播图', + component: 'ImageUpload', + componentProps: { + multiple: true, + maxNumber: 5, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '秒杀时段名称', + component: 'Input', + componentProps: { + placeholder: '请输入秒杀时段名称', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + placeholder: '请选择状态', + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + ]; +} + +/** 表格列配置 */ +export function useGridColumns( + onStatusChange?: ( + newStatus: number, + row: MallSeckillConfigApi.SeckillConfig, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + title: '秒杀时段名称', + field: 'name', + minWidth: 200, + }, + { + title: '开始时间点', + field: 'startTime', + minWidth: 120, + }, + { + title: '结束时间点', + field: 'endTime', + minWidth: 120, + }, + { + title: '秒杀轮播图', + field: 'sliderPicUrls', + minWidth: 100, + cellRender: { + name: 'CellImages', + }, + }, + { + title: '活动状态', + field: 'status', + minWidth: 100, + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: 'CellSwitch', + props: { + checkedValue: 1, + checkedChildren: '启用', + unCheckedValue: 0, + unCheckedChildren: '禁用', + }, + }, + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/promotion/seckill/config/index.vue b/apps/web-ele/src/views/mall/promotion/seckill/config/index.vue new file mode 100644 index 0000000..073c763 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/seckill/config/index.vue @@ -0,0 +1,152 @@ + + + diff --git a/apps/web-ele/src/views/mall/promotion/seckill/config/modules/form.vue b/apps/web-ele/src/views/mall/promotion/seckill/config/modules/form.vue new file mode 100644 index 0000000..36aaf29 --- /dev/null +++ b/apps/web-ele/src/views/mall/promotion/seckill/config/modules/form.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/web-ele/src/views/mall/statistics/member/index.vue b/apps/web-ele/src/views/mall/statistics/member/index.vue new file mode 100644 index 0000000..1e3f9dd --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/member/index.vue @@ -0,0 +1,113 @@ + + + diff --git a/apps/web-ele/src/views/mall/statistics/member/modules/area-card.vue b/apps/web-ele/src/views/mall/statistics/member/modules/area-card.vue new file mode 100644 index 0000000..7c71385 --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/member/modules/area-card.vue @@ -0,0 +1,101 @@ + + + diff --git a/apps/web-ele/src/views/mall/statistics/member/modules/area-chart-options.ts b/apps/web-ele/src/views/mall/statistics/member/modules/area-chart-options.ts new file mode 100644 index 0000000..b09f492 --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/member/modules/area-chart-options.ts @@ -0,0 +1,130 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { MallMemberStatisticsApi } from '#/api/mall/statistics/member'; + +import { fenToYuan } from '@vben/utils'; + +/** 会员地域分布图表配置 */ +export function getAreaChartOptions( + data: MallMemberStatisticsApi.AreaStatisticsRespVO[], +): any { + if (!data || data.length === 0) { + return { + title: { + text: '暂无数据', + left: 'center', + top: 'center', + textStyle: { + color: '#999', + fontSize: 14, + }, + }, + }; + } + + // 计算 min 和 max 值 + let min = Number.POSITIVE_INFINITY; + let max = Number.NEGATIVE_INFINITY; + const mapData = data.map((item) => { + const payUserCount = item.orderPayUserCount || 0; + min = Math.min(min, payUserCount); + max = Math.max(max, payUserCount); + return { + ...item, + name: item.areaName, + value: payUserCount, + }; + }); + // 如果所有值都为 0,设置合理的 min 和 max 值 + if (min === max && min === 0) { + min = 0; + max = 10; + } + + // 返回图表配置 + return { + tooltip: { + trigger: 'item', + formatter: (params: any) => { + const itemData = params?.data; + if (!itemData) { + return `${params?.name || ''}
    暂无数据`; + } + return `${itemData.areaName || params.name}
    +会员数量:${itemData.userCount || 0}
    +订单创建数量:${itemData.orderCreateUserCount || 0}
    +订单支付数量:${itemData.orderPayUserCount || 0}
    +订单支付金额:¥${Number(fenToYuan(itemData.orderPayPrice || 0)).toFixed(2)}`; + }, + }, + visualMap: { + text: ['高', '低'], + realtime: false, + calculable: true, + top: 'middle', + left: 10, + min, + max, + inRange: { + color: ['#e6f3ff', '#1890ff', '#0050b3'], + }, + }, + series: [ + { + name: '会员地域分布', + type: 'map', + map: 'china', + roam: false, + selectedMode: false, + itemStyle: { + borderColor: '#389e0d', + borderWidth: 0.5, + }, + emphasis: { + itemStyle: { + areaColor: '#ffec3d', + borderWidth: 1, + }, + }, + data: mapData, + }, + ], + }; +} + +/** VXE Grid 表格列配置 */ +export function getAreaTableColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'areaName', + title: '省份', + minWidth: 80, + sortable: true, + showOverflow: 'tooltip', + }, + { + field: 'userCount', + title: '会员数量', + minWidth: 100, + sortable: true, + }, + { + field: 'orderCreateUserCount', + title: '订单创建数量', + minWidth: 120, + sortable: true, + }, + { + field: 'orderPayUserCount', + title: '订单支付数量', + minWidth: 120, + sortable: true, + }, + { + field: 'orderPayPrice', + title: '订单支付金额', + minWidth: 120, + sortable: true, + formatter: 'formatFenToYuanAmount', + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/statistics/member/modules/funnel-card.vue b/apps/web-ele/src/views/mall/statistics/member/modules/funnel-card.vue new file mode 100644 index 0000000..845fd85 --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/member/modules/funnel-card.vue @@ -0,0 +1,147 @@ + + + diff --git a/apps/web-ele/src/views/mall/statistics/member/modules/sex-card.vue b/apps/web-ele/src/views/mall/statistics/member/modules/sex-card.vue new file mode 100644 index 0000000..b646619 --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/member/modules/sex-card.vue @@ -0,0 +1,65 @@ + + + diff --git a/apps/web-ele/src/views/mall/statistics/member/modules/sex-chart-options.ts b/apps/web-ele/src/views/mall/statistics/member/modules/sex-chart-options.ts new file mode 100644 index 0000000..81a7faf --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/member/modules/sex-chart-options.ts @@ -0,0 +1,28 @@ +/** 会员性别比例图表配置 */ +export function getSexChartOptions(data: any[]): any { + return { + tooltip: { + trigger: 'item', + confine: true, + formatter: '{a}
    {b} : {c} ({d}%)', + }, + legend: { + orient: 'vertical', + left: 'right', + }, + series: [ + { + name: '会员性别', + type: 'pie', + roseType: 'area', + label: { + show: false, + }, + labelLine: { + show: false, + }, + data, + }, + ], + }; +} diff --git a/apps/web-ele/src/views/mall/statistics/member/modules/terminal-card.vue b/apps/web-ele/src/views/mall/statistics/member/modules/terminal-card.vue new file mode 100644 index 0000000..50c088c --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/member/modules/terminal-card.vue @@ -0,0 +1,59 @@ + + + diff --git a/apps/web-ele/src/views/mall/statistics/member/modules/terminal-chart-options.ts b/apps/web-ele/src/views/mall/statistics/member/modules/terminal-chart-options.ts new file mode 100644 index 0000000..c3233bd --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/member/modules/terminal-chart-options.ts @@ -0,0 +1,27 @@ +/** 会员终端统计图配置 */ +export function getTerminalChartOptions(data: any[]): any { + return { + tooltip: { + trigger: 'item', + confine: true, + formatter: '{a}
    {b} : {c} ({d}%)', + }, + legend: { + orient: 'vertical', + left: 'right', + }, + series: [ + { + name: '会员终端', + type: 'pie', + label: { + show: false, + }, + labelLine: { + show: false, + }, + data, + }, + ], + }; +} diff --git a/apps/web-ele/src/views/mall/statistics/product/index.vue b/apps/web-ele/src/views/mall/statistics/product/index.vue new file mode 100644 index 0000000..2e38340 --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/product/index.vue @@ -0,0 +1,27 @@ + + + diff --git a/apps/web-ele/src/views/mall/statistics/product/modules/rank-card.vue b/apps/web-ele/src/views/mall/statistics/product/modules/rank-card.vue new file mode 100644 index 0000000..0a7e34e --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/product/modules/rank-card.vue @@ -0,0 +1,138 @@ + + + diff --git a/apps/web-ele/src/views/mall/statistics/product/modules/summary-card.vue b/apps/web-ele/src/views/mall/statistics/product/modules/summary-card.vue new file mode 100644 index 0000000..5d6b9bf --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/product/modules/summary-card.vue @@ -0,0 +1,261 @@ + + + diff --git a/apps/web-ele/src/views/mall/statistics/product/modules/summary-chart-options.ts b/apps/web-ele/src/views/mall/statistics/product/modules/summary-chart-options.ts new file mode 100644 index 0000000..ec6e474 --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/product/modules/summary-chart-options.ts @@ -0,0 +1,129 @@ +/** 商品统计折线图配置 */ +export function getProductSummaryChartOptions(data: any[]): any { + // 处理数据:将金额从分转换为元 + const processedData = data.map((item) => ({ + ...item, + orderPayPrice: Number((item.orderPayPrice / 100).toFixed(2)), + afterSaleRefundPrice: Number((item.afterSaleRefundPrice / 100).toFixed(2)), + })); + + return { + dataset: { + dimensions: [ + 'time', + 'browseCount', + 'browseUserCount', + 'orderPayPrice', + 'afterSaleRefundPrice', + ], + source: processedData, + }, + grid: { + left: 20, + right: 20, + bottom: 20, + top: 80, + containLabel: true, + }, + legend: { + top: 50, + }, + series: [ + { + name: '商品浏览量', + type: 'line', + smooth: true, + itemStyle: { color: '#B37FEB' }, + }, + { + name: '商品访客数', + type: 'line', + smooth: true, + itemStyle: { color: '#FFAB2B' }, + }, + { + name: '支付金额', + type: 'bar', + smooth: true, + yAxisIndex: 1, + itemStyle: { color: '#1890FF' }, + }, + { + name: '退款金额', + type: 'bar', + smooth: true, + yAxisIndex: 1, + itemStyle: { color: '#00C050' }, + }, + ], + toolbox: { + feature: { + // 数据区域缩放 + dataZoom: { + yAxisIndex: false, // Y轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { + show: true, + name: '商品状况', + }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + }, + padding: [5, 10], + }, + xAxis: { + type: 'category', + boundaryGap: true, + axisTick: { + show: false, + }, + }, + yAxis: [ + { + type: 'value', + name: '金额', + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + axisLabel: { + color: '#7F8B9C', + }, + splitLine: { + show: true, + lineStyle: { + color: '#F5F7F9', + }, + }, + }, + { + type: 'value', + name: '数量', + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + axisLabel: { + color: '#7F8B9C', + }, + splitLine: { + show: true, + lineStyle: { + color: '#F5F7F9', + }, + }, + }, + ], + }; +} diff --git a/apps/web-ele/src/views/mall/statistics/trade/index.vue b/apps/web-ele/src/views/mall/statistics/trade/index.vue new file mode 100644 index 0000000..0207bde --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/trade/index.vue @@ -0,0 +1,120 @@ + + + diff --git a/apps/web-ele/src/views/mall/statistics/trade/modules/trend-card.vue b/apps/web-ele/src/views/mall/statistics/trade/modules/trend-card.vue new file mode 100644 index 0000000..8dd3f27 --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/trade/modules/trend-card.vue @@ -0,0 +1,293 @@ + + + diff --git a/apps/web-ele/src/views/mall/statistics/trade/modules/trend-chart-options.ts b/apps/web-ele/src/views/mall/statistics/trade/modules/trend-chart-options.ts new file mode 100644 index 0000000..899a6a3 --- /dev/null +++ b/apps/web-ele/src/views/mall/statistics/trade/modules/trend-chart-options.ts @@ -0,0 +1,124 @@ +import type { MallTradeStatisticsApi } from '#/api/mall/statistics/trade'; + +import { fenToYuan } from '@vben/utils'; + +/** 交易趋势折线图配置 */ +export function getTradeTrendChartOptions( + data: MallTradeStatisticsApi.TradeTrendSummaryRespVO[], +): any { + // 处理数据:将分转换为元 + const processedData = data.map((item) => ({ + ...item, + turnoverPrice: Number(fenToYuan(item.turnoverPrice)), + orderPayPrice: Number(fenToYuan(item.orderPayPrice)), + rechargePrice: Number(fenToYuan(item.rechargePrice)), + expensePrice: Number(fenToYuan(item.expensePrice)), + })); + + return { + dataset: { + dimensions: [ + 'date', + 'turnoverPrice', + 'orderPayPrice', + 'rechargePrice', + 'expensePrice', + ], + source: processedData, + }, + grid: { + left: 20, + right: 20, + bottom: 20, + top: 80, + containLabel: true, + }, + legend: { + top: 50, + }, + series: [ + { + name: '营业额', + type: 'line', + smooth: true, + itemStyle: { color: '#1890FF' }, + }, + { + name: '商品支付金额', + type: 'line', + smooth: true, + itemStyle: { color: '#722ED1' }, + }, + { + name: '充值金额', + type: 'line', + smooth: true, + itemStyle: { color: '#FAAD14' }, + }, + { + name: '支出金额', + type: 'line', + smooth: true, + itemStyle: { color: '#52C41A' }, + }, + ], + toolbox: { + feature: { + // 数据区域缩放 + dataZoom: { + yAxisIndex: false, // Y轴不缩放 + }, + brush: { + type: ['lineX', 'clear'], // 区域缩放按钮、还原按钮 + }, + saveAsImage: { + show: true, + name: '交易状况', + }, // 保存为图片 + }, + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + }, + padding: [5, 10], + formatter(params: any) { + let result = `
    ${params[0].data.time}
    `; + params.forEach((item: any) => { + result += `
    + + ${item.seriesName}: ¥${item.data[item.dimensionNames[item.encode.y[0]]]} +
    `; + }); + return result; + }, + }, + xAxis: { + type: 'category', + boundaryGap: false, + axisTick: { + show: false, + }, + }, + yAxis: { + type: 'value', + axisLine: { + show: false, + }, + axisTick: { + show: false, + }, + axisLabel: { + formatter: '¥{value}', + color: '#7F8B9C', + }, + splitLine: { + show: true, + lineStyle: { + color: '#F5F7F9', + }, + }, + }, + }; +} diff --git a/apps/web-ele/src/views/mall/trade/afterSale/data.ts b/apps/web-ele/src/views/mall/trade/afterSale/data.ts new file mode 100644 index 0000000..2aa4ecc --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/afterSale/data.ts @@ -0,0 +1,172 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridPropTypes } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 拒绝售后表单的 schema 配置 */ +export function useDisagreeFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Textarea', + fieldName: 'reason', + label: '拒绝原因', + componentProps: { + placeholder: '请输入拒绝原因', + rows: 4, + }, + rules: z.string().min(2, { message: '拒绝原因不能少于 2 个字符' }), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'spuName', + label: '商品名称', + component: 'Input', + componentProps: { + placeholder: '请输入商品名称', + }, + }, + { + fieldName: 'no', + label: '退款编号', + component: 'Input', + componentProps: { + placeholder: '请输入退款编号', + }, + }, + { + fieldName: 'orderNo', + label: '订单编号', + component: 'Input', + componentProps: { + placeholder: '请输入订单编号', + }, + }, + { + fieldName: 'status', + label: '售后状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_STATUS, 'number'), + placeholder: '请选择售后状态', + clearable: true, + }, + }, + { + fieldName: 'way', + label: '售后方式', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_WAY, 'number'), + placeholder: '请选择售后方式', + clearable: true, + }, + }, + { + fieldName: 'type', + label: '售后类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.TRADE_AFTER_SALE_TYPE, 'number'), + placeholder: '请选择售后类型', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 表格列配置 */ +export function useGridColumns(): VxeGridPropTypes.Columns { + return [ + { + field: 'no', + title: '退款编号', + fixed: 'left', + minWidth: 200, + }, + { + field: 'orderNo', + title: '订单编号', + fixed: 'left', + minWidth: 200, + slots: { default: 'orderNo' }, + }, + { + field: 'productInfo', + title: '商品信息', + minWidth: 600, + slots: { default: 'productInfo' }, + }, + { + field: 'refundPrice', + title: '订单金额', + width: 120, + formatter: 'formatAmount2', + }, + { + field: 'user.nickname', + title: '买家', + minWidth: 120, + }, + { + field: 'createTime', + title: '申请时间', + width: 180, + formatter: 'formatDateTime', + }, + { + field: 'status', + title: '售后状态', + width: 100, + cellRender: { + name: 'CellDict', + props: { + type: DICT_TYPE.TRADE_AFTER_SALE_STATUS, + }, + }, + }, + { + field: 'way', + title: '售后方式', + width: 100, + cellRender: { + name: 'CellDict', + props: { + type: DICT_TYPE.TRADE_AFTER_SALE_WAY, + }, + }, + }, + { + title: '操作', + width: 160, + fixed: 'right', + align: 'center', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/afterSale/detail/data.ts b/apps/web-ele/src/views/mall/trade/afterSale/detail/data.ts new file mode 100644 index 0000000..1e532c9 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/afterSale/detail/data.ts @@ -0,0 +1,221 @@ +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { fenToYuan, formatDate } from '@vben/utils'; + +import { ElImage } from 'element-plus'; + +import { DictTag } from '#/components/dict-tag'; + +/** 订单信息 schema */ +export function useOrderInfoSchema(): DescriptionItemSchema[] { + return [ + { + field: 'orderNo', + label: '订单号', + }, + { + field: 'order.deliveryType', + label: '配送方式', + render: (val) => + h(DictTag, { + type: DICT_TYPE.TRADE_DELIVERY_TYPE, + value: val, + }), + }, + { + field: 'order.type', + label: '订单类型', + render: (val) => + h(DictTag, { + type: DICT_TYPE.TRADE_ORDER_TYPE, + value: val, + }), + }, + { + field: 'order.receiverName', + label: '收货人', + }, + { + field: 'order.userRemark', + label: '买家留言', + }, + { + field: 'order.terminal', + label: '订单来源', + render: (val) => + h(DictTag, { + type: DICT_TYPE.TERMINAL, + value: val, + }), + }, + { + field: 'order.receiverMobile', + label: '联系电话', + }, + { + field: 'order.remark', + label: '商家备注', + }, + { + field: 'order.payOrderId', + label: '支付单号', + }, + { + field: 'order.payChannelCode', + label: '付款方式', + render: (val) => + h(DictTag, { + type: DICT_TYPE.PAY_CHANNEL_CODE, + value: val, + }), + }, + { + field: 'user.nickname', + label: '买家', + }, + ]; +} + +/** 售后信息 schema */ +export function useAfterSaleInfoSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '退款编号', + }, + { + field: 'auditTime', + label: '申请时间', + render: (val) => formatDate(val) as string, + }, + { + field: 'type', + label: '售后类型', + render: (val) => + h(DictTag, { + type: DICT_TYPE.TRADE_AFTER_SALE_TYPE, + value: val, + }), + }, + { + field: 'way', + label: '售后方式', + render: (val) => + h(DictTag, { + type: DICT_TYPE.TRADE_AFTER_SALE_WAY, + value: val, + }), + }, + { + field: 'refundPrice', + label: '退款金额', + render: (val) => fenToYuan(val ?? 0), + }, + { + field: 'applyReason', + label: '退款原因', + }, + { + field: 'applyDescription', + label: '补充描述', + }, + { + field: 'applyPicUrls', + label: '凭证图片', + render: (val) => { + const images = val || []; + return h( + 'div', + { class: 'flex gap-10px' }, + images.map((url: string, index: number) => + h(ElImage, { + key: index, + src: url, + width: 60, + height: 60, + }), + ), + ); + }, + }, + ]; +} + +/** 退款状态 schema */ +export function useRefundStatusSchema(): DescriptionItemSchema[] { + return [ + { + field: 'status', + label: '退款状态', + render: (val) => + h(DictTag, { + type: DICT_TYPE.TRADE_AFTER_SALE_STATUS, + value: val, + }), + }, + { + field: 'reminder', + label: '提醒', + render: () => + h('div', { class: 'text-red-500 mb-10px' }, [ + h('div', '如果未发货,请点击同意退款给买家。'), + h('div', '如果实际已发货,请主动与买家联系。'), + h('div', '如果订单整体退款后,优惠券和余额会退还给买家.'), + ]), + }, + ]; +} + +/** 商品信息 columns */ +export function useProductColumns() { + return [ + { + field: 'spuName', + title: '商品信息', + minWidth: 300, + slots: { default: 'spuName' }, + }, + { + field: 'price', + title: '商品原价', + minWidth: 150, + formatter: 'formatFenToYuanAmount', + }, + { + field: 'count', + title: '数量', + minWidth: 100, + }, + { + field: 'payPrice', + title: '合计', + minWidth: 150, + formatter: 'formatFenToYuanAmount', + }, + ]; +} + +/** 操作日志 columns */ +export function useOperateLogSchema() { + return [ + { + field: 'createTime', + title: '操作时间', + width: 180, + formatter: 'formatDateTime', + }, + { + field: 'userType', + title: '操作人', + width: 100, + slots: { default: 'userType' }, + }, + { + field: 'content', + title: '操作内容', + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/afterSale/detail/index.vue b/apps/web-ele/src/views/mall/trade/afterSale/detail/index.vue new file mode 100644 index 0000000..66d4e6b --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/afterSale/detail/index.vue @@ -0,0 +1,307 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/afterSale/index.vue b/apps/web-ele/src/views/mall/trade/afterSale/index.vue new file mode 100644 index 0000000..8afaae7 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/afterSale/index.vue @@ -0,0 +1,157 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/afterSale/modules/disagree-form.vue b/apps/web-ele/src/views/mall/trade/afterSale/modules/disagree-form.vue new file mode 100644 index 0000000..c9d1adf --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/afterSale/modules/disagree-form.vue @@ -0,0 +1,78 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/brokerage/record/data.ts b/apps/web-ele/src/views/mall/trade/brokerage/record/data.ts new file mode 100644 index 0000000..a6ed265 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/brokerage/record/data.ts @@ -0,0 +1,137 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { fenToYuan } from '@vben/utils'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + placeholder: '请输入用户编号', + clearable: true, + }, + }, + { + fieldName: 'bizType', + label: '业务类型', + component: 'Select', + componentProps: { + placeholder: '请选择业务类型', + clearable: true, + options: getDictOptions(DICT_TYPE.BROKERAGE_RECORD_BIZ_TYPE, 'number'), + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + placeholder: '请选择状态', + clearable: true, + options: getDictOptions(DICT_TYPE.BROKERAGE_RECORD_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 60, + }, + { + field: 'userId', + title: '用户编号', + minWidth: 80, + }, + { + field: 'userAvatar', + title: '头像', + minWidth: 70, + cellRender: { + name: 'CellImage', + props: { + height: 40, + width: 40, + shape: 'circle', + }, + }, + }, + { + field: 'userNickname', + title: '昵称', + minWidth: 80, + }, + { + field: 'bizType', + title: '业务类型', + minWidth: 85, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BROKERAGE_RECORD_BIZ_TYPE }, + }, + }, + { + field: 'bizId', + title: '业务编号', + minWidth: 80, + }, + { + field: 'title', + title: '标题', + minWidth: 110, + }, + { + field: 'price', + title: '金额', + minWidth: 60, + formatter: ({ row }) => `¥${fenToYuan(row.price)}`, + }, + { + field: 'description', + title: '说明', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 85, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BROKERAGE_RECORD_STATUS }, + }, + }, + { + field: 'unfreezeTime', + title: '解冻时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'createTime', + title: '创建时间', + width: 180, + formatter: 'formatDateTime', + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/brokerage/record/index.vue b/apps/web-ele/src/views/mall/trade/brokerage/record/index.vue new file mode 100644 index 0000000..a959648 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/brokerage/record/index.vue @@ -0,0 +1,56 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/brokerage/user/data.ts b/apps/web-ele/src/views/mall/trade/brokerage/user/data.ts new file mode 100644 index 0000000..903935b --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/brokerage/user/data.ts @@ -0,0 +1,367 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { MallBrokerageUserApi } from '#/api/mall/trade/brokerage/user'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { fenToYuan } from '@vben/utils'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'bindUserId', + label: '推广员编号', + component: 'Input', + componentProps: { + placeholder: '请输入推广员编号', + clearable: true, + }, + }, + { + fieldName: 'brokerageEnabled', + label: '推广资格', + component: 'Select', + componentProps: { + placeholder: '请选择推广资格', + clearable: true, + options: [ + { label: '有', value: true }, + { label: '无', value: false }, + ], + }, + defaultValue: true, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onBrokerageEnabledChange?: ( + newEnabled: boolean, + row: MallBrokerageUserApi.BrokerageUser, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '用户编号', + minWidth: 80, + }, + { + field: 'avatar', + title: '头像', + minWidth: 70, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'nickname', + title: '昵称', + minWidth: 80, + }, + { + field: 'brokerageUserCount', + title: '推广人数', + minWidth: 80, + }, + { + field: 'brokerageOrderCount', + title: '推广订单数量', + minWidth: 110, + }, + { + field: 'brokerageOrderPrice', + title: '推广订单金额', + minWidth: 110, + formatter: ({ row }) => `¥${fenToYuan(row.brokerageOrderPrice)}`, + }, + { + field: 'withdrawPrice', + title: '已提现金额', + minWidth: 100, + formatter: ({ row }) => `¥${fenToYuan(row.withdrawPrice)}`, + }, + { + field: 'withdrawCount', + title: '已提现次数', + minWidth: 100, + }, + { + field: 'price', + title: '未提现金额', + minWidth: 100, + formatter: ({ row }) => `¥${fenToYuan(row.price)}`, + }, + { + field: 'frozenPrice', + title: '冻结中佣金', + minWidth: 100, + formatter: ({ row }) => `¥${fenToYuan(row.frozenPrice)}`, + }, + { + field: 'brokerageEnabled', + title: '推广资格', + minWidth: 80, + align: 'center', + cellRender: { + attrs: { beforeChange: onBrokerageEnabledChange }, + name: 'CellSwitch', + props: { + activeValue: true, + inactiveValue: false, + activeText: '有', + inactiveText: '无', + }, + }, + }, + { + field: 'brokerageTime', + title: '成为推广员时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'bindUserId', + title: '上级推广员编号', + minWidth: 150, + }, + { + field: 'bindUserTime', + title: '推广员绑定时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 150, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 创建分销员表单配置 */ +export function useCreateFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '分销员编号', + component: 'Input', + componentProps: { + placeholder: '请输入分销员编号', + }, + rules: 'required', + }, + { + fieldName: 'bindUserId', + label: '上级推广员编号', + component: 'Input', + componentProps: { + placeholder: '请输入上级推广员编号', + }, + rules: 'required', + }, + ]; +} + +/** 修改分销用户表单配置 */ +export function useUpdateFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'bindUserId', + label: '上级推广员编号', + component: 'Input', + componentProps: { + placeholder: '请输入上级推广员编号', + }, + rules: 'required', + }, + ]; +} + +/** 用户列表弹窗搜索表单配置 */ +export function useUserListFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'level', + label: '用户类型', + component: 'Select', + componentProps: { + options: [ + { label: '全部', value: undefined }, + { label: '一级推广人', value: '1' }, + { label: '二级推广人', value: '2' }, + ], + clearable: true, + }, + }, + { + fieldName: 'bindUserTime', + label: '绑定时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 用户列表弹窗表格列配置 */ +export function useUserListColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '用户编号', + minWidth: 80, + }, + { + field: 'avatar', + title: '头像', + minWidth: 70, + cellRender: { + name: 'CellImage', + props: { + width: 24, + height: 24, + shape: 'circle', + }, + }, + }, + { + field: 'nickname', + title: '昵称', + minWidth: 80, + }, + { + field: 'brokerageUserCount', + title: '推广人数', + minWidth: 80, + }, + { + field: 'brokerageOrderCount', + title: '推广订单数量', + minWidth: 110, + }, + { + field: 'brokerageEnabled', + title: '推广资格', + minWidth: 80, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'bindUserTime', + title: '绑定时间', + width: 180, + formatter: 'formatDateTime', + }, + ]; +} + +/** 推广订单列表弹窗搜索表单配置 */ +export function useOrderListFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'sourceUserLevel', + label: '用户类型', + component: 'Select', + componentProps: { + options: [ + { label: '全部', value: 0 }, + { label: '一级推广人', value: 1 }, + { label: '二级推广人', value: 2 }, + ], + }, + defaultValue: 0, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + placeholder: '请选择状态', + clearable: true, + options: getDictOptions(DICT_TYPE.BROKERAGE_RECORD_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 推广订单列表弹窗表格列配置 */ +export function useOrderListColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'bizId', + title: '订单编号', + minWidth: 80, + }, + { + field: 'sourceUserId', + title: '用户编号', + minWidth: 80, + }, + { + field: 'sourceUserAvatar', + title: '头像', + minWidth: 70, + cellRender: { + name: 'CellImage', + props: { + width: 24, + height: 24, + }, + }, + }, + { + field: 'sourceUserNickname', + title: '昵称', + minWidth: 80, + }, + { + field: 'price', + title: '佣金', + minWidth: 100, + formatter: ({ row }) => `¥${fenToYuan(row.price)}`, + }, + { + field: 'status', + title: '状态', + minWidth: 85, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BROKERAGE_RECORD_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + width: 180, + formatter: 'formatDateTime', + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/brokerage/user/index.vue b/apps/web-ele/src/views/mall/trade/brokerage/user/index.vue new file mode 100644 index 0000000..5b7d958 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/brokerage/user/index.vue @@ -0,0 +1,210 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/brokerage/user/modules/create-form.vue b/apps/web-ele/src/views/mall/trade/brokerage/user/modules/create-form.vue new file mode 100644 index 0000000..0ceb8bc --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/brokerage/user/modules/create-form.vue @@ -0,0 +1,191 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/brokerage/user/modules/order-list-modal.vue b/apps/web-ele/src/views/mall/trade/brokerage/user/modules/order-list-modal.vue new file mode 100644 index 0000000..7e9520f --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/brokerage/user/modules/order-list-modal.vue @@ -0,0 +1,61 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/brokerage/user/modules/update-form.vue b/apps/web-ele/src/views/mall/trade/brokerage/user/modules/update-form.vue new file mode 100644 index 0000000..f89a7e1 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/brokerage/user/modules/update-form.vue @@ -0,0 +1,161 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/brokerage/user/modules/user-list-modal.vue b/apps/web-ele/src/views/mall/trade/brokerage/user/modules/user-list-modal.vue new file mode 100644 index 0000000..4066748 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/brokerage/user/modules/user-list-modal.vue @@ -0,0 +1,52 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/brokerage/withdraw/data.ts b/apps/web-ele/src/views/mall/trade/brokerage/withdraw/data.ts new file mode 100644 index 0000000..86452dd --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/brokerage/withdraw/data.ts @@ -0,0 +1,148 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + placeholder: '请输入用户编号', + allowClear: true, + }, + }, + { + fieldName: 'type', + label: '提现类型', + component: 'Select', + componentProps: { + placeholder: '请选择提现类型', + allowClear: true, + options: getDictOptions(DICT_TYPE.BROKERAGE_WITHDRAW_TYPE, 'number'), + }, + }, + { + fieldName: 'userAccount', + label: '账号', + component: 'Input', + componentProps: { + placeholder: '请输入账号', + allowClear: true, + }, + }, + { + fieldName: 'userName', + label: '真实姓名', + component: 'Input', + componentProps: { + placeholder: '请输入真实姓名', + allowClear: true, + }, + }, + { + fieldName: 'bankName', + label: '提现银行', + component: 'Select', + componentProps: { + placeholder: '请选择提现银行', + allowClear: true, + options: getDictOptions(DICT_TYPE.BROKERAGE_BANK_NAME, 'string'), + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + placeholder: '请选择状态', + allowClear: true, + options: getDictOptions(DICT_TYPE.BROKERAGE_WITHDRAW_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '申请时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 80, + }, + { + field: 'userId', + title: '用户编号', + minWidth: 80, + }, + { + field: 'userNickname', + title: '用户昵称', + minWidth: 80, + }, + { + field: 'price', + title: '提现金额', + minWidth: 80, + formatter: 'formatAmount2', + }, + { + field: 'feePrice', + title: '提现手续费', + minWidth: 80, + formatter: 'formatAmount2', + }, + { + field: 'type', + title: '提现方式', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.BROKERAGE_WITHDRAW_TYPE }, + }, + }, + { + title: '提现信息', + minWidth: 200, + slots: { default: 'withdraw-info' }, + }, + { + field: 'createTime', + title: '申请时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'remark', + title: '备注', + minWidth: 120, + }, + { + title: '状态', + minWidth: 200, + slots: { default: 'status-info' }, + }, + { + title: '操作', + width: 150, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/brokerage/withdraw/index.vue b/apps/web-ele/src/views/mall/trade/brokerage/withdraw/index.vue new file mode 100644 index 0000000..633f9af --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/brokerage/withdraw/index.vue @@ -0,0 +1,233 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/config/data.ts b/apps/web-ele/src/views/mall/trade/config/data.ts new file mode 100644 index 0000000..6e91a59 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/config/data.ts @@ -0,0 +1,255 @@ +import type { VbenFormSchema } from '#/adapter/form'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +export const schema: VbenFormSchema[] = [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'type', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'afterSaleRefundReasons', + label: '退款理由', + component: 'Select', + componentProps: { + placeholder: '请直接输入退款理由', + multiple: true, + options: [], + class: 'w-full', + allowCreate: true, + filterable: true, + reserveKeyword: false, + }, + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'afterSale', + }, + }, + { + fieldName: 'afterSaleReturnReasons', + label: '退货理由', + component: 'Select', + componentProps: { + placeholder: '请直接输入退货理由', + multiple: true, + options: [], + class: 'w-full', + allowCreate: true, + filterable: true, + reserveKeyword: false, + }, + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'afterSale', + }, + }, + { + fieldName: 'deliveryExpressFreeEnabled', + label: '启用包邮', + component: 'Switch', + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'delivery', + }, + help: '商城是否启用全场包邮', + }, + { + fieldName: 'deliveryExpressFreePrice', + label: '满额包邮', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + placeholder: '请输入满额包邮金额', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'delivery', + }, + help: '商城商品满多少金额即可包邮,单位:元', + }, + { + fieldName: 'deliveryPickUpEnabled', + label: '启用门店自提', + component: 'Switch', + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'delivery', + }, + }, + { + fieldName: 'brokerageEnabled', + label: '启用分佣', + component: 'Switch', + help: '商城是否开启分销模式', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'brokerage', + }, + }, + { + fieldName: 'brokerageEnabledCondition', + label: '分佣模式', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.BROKERAGE_ENABLED_CONDITION, 'number'), + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'brokerage', + }, + help: '人人分销:每个用户都可以成为推广员 \n 指定分销:仅可在后台手动设置推广员', + }, + { + fieldName: 'brokerageBindMode', + label: '分销关系绑定', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.BROKERAGE_BIND_MODE, 'number'), + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'brokerage', + }, + help: '首次绑定:只要用户没有推广人,随时都可以绑定推广关系 \n 注册绑定:只有新用户注册时或首次进入系统时才可以绑定推广关系', + }, + { + fieldName: 'brokeragePosterUrls', + label: '分销海报图', + component: 'ImageUpload', + componentProps: { + maxNumber: 9, + }, + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'brokerage', + }, + help: '个人中心分销海报图片,建议尺寸 600x1000', + }, + { + fieldName: 'brokerageFirstPercent', + label: '一级返佣比例(%)', + component: 'InputNumber', + componentProps: { + min: 0, + max: 100, + placeholder: '请输入一级返佣比例', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'brokerage', + }, + help: '订单交易成功后给推广人返佣的百分比', + }, + { + fieldName: 'brokerageSecondPercent', + label: '二级返佣比例(%)', + component: 'InputNumber', + componentProps: { + min: 0, + max: 100, + placeholder: '请输入二级返佣比例', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'brokerage', + }, + help: '订单交易成功后给推广人的推荐人返佣的百分比', + }, + { + fieldName: 'brokerageFrozenDays', + label: '佣金冻结天数', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入佣金冻结天数', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'brokerage', + }, + help: '防止用户退款,佣金被提现了,所以需要设置佣金冻结时间,单位:天', + }, + { + fieldName: 'brokerageWithdrawMinPrice', + label: '提现最低金额(元)', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 2, + placeholder: '请输入提现最低金额', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'brokerage', + }, + help: '用户提现最低金额限制,单位:元', + }, + { + fieldName: 'brokerageWithdrawFeePercent', + label: '提现手续费(%)', + component: 'InputNumber', + componentProps: { + min: 0, + max: 100, + precision: 2, + placeholder: '请输入提现手续费百分比', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'brokerage', + }, + help: '提现手续费百分比,范围 0-100,0 为无提现手续费。例:设置 10,即收取 10% 手续费,提现10 元,到账 9 元,1 元手续费', + }, + { + fieldName: 'brokerageWithdrawTypes', + label: '提现方式', + component: 'CheckboxGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.BROKERAGE_WITHDRAW_TYPE, 'number'), + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => values.type === 'brokerage', + }, + help: '商城开通提现的付款方式', + }, +]; diff --git a/apps/web-ele/src/views/mall/trade/config/index.vue b/apps/web-ele/src/views/mall/trade/config/index.vue new file mode 100644 index 0000000..39745b8 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/config/index.vue @@ -0,0 +1,99 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/delivery/express/data.ts b/apps/web-ele/src/views/mall/trade/delivery/express/data.ts new file mode 100644 index 0000000..7e0b676 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/express/data.ts @@ -0,0 +1,156 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'code', + label: '公司编码', + componentProps: { + placeholder: '请输入快递编码', + }, + rules: 'required', + }, + { + component: 'Input', + fieldName: 'name', + label: '公司名称', + componentProps: { + placeholder: '请输入快递名称', + }, + rules: 'required', + }, + { + component: 'ImageUpload', + fieldName: 'logo', + label: '公司 logo', + rules: 'required', + help: '推荐 180x180 图片分辨率', + }, + { + fieldName: 'sort', + label: '显示顺序', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入显示顺序', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '快递公司名称', + component: 'Input', + componentProps: { + placeholder: '请输入快递公司名称', + allowClear: true, + }, + }, + { + fieldName: 'code', + label: '快递公司编号', + component: 'Input', + componentProps: { + placeholder: '请输入快递公司编号', + allowClear: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + placeholder: '请选择状态', + allowClear: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'code', + title: '公司编码', + minWidth: 120, + }, + { + field: 'name', + title: '公司名称', + minWidth: 150, + }, + { + field: 'logo', + title: '公司 logo', + minWidth: 120, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'sort', + title: '显示顺序', + minWidth: 100, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/delivery/express/index.vue b/apps/web-ele/src/views/mall/trade/delivery/express/index.vue new file mode 100644 index 0000000..06cb743 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/express/index.vue @@ -0,0 +1,143 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/delivery/express/modules/form.vue b/apps/web-ele/src/views/mall/trade/delivery/express/modules/form.vue new file mode 100644 index 0000000..5db07b9 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/express/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/data.ts b/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/data.ts new file mode 100644 index 0000000..bb1b0ce --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/data.ts @@ -0,0 +1,228 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 计费方式列标题映射 */ +export const CHARGE_MODE_TITLE_MAP: Record< + number, + { + extraCountTitle: string; + startCountTitle: string; + } +> = { + 1: { startCountTitle: '首件', extraCountTitle: '续件' }, + 2: { startCountTitle: '首件重量(kg)', extraCountTitle: '续件重量(kg)' }, + 3: { startCountTitle: '首件体积(m³)', extraCountTitle: '续件体积(m³)' }, +}; + +/** 包邮方式列标题映射 */ +export const FREE_MODE_TITLE_MAP: Record = { + 1: { freeCountTitle: '包邮件数' }, + 2: { freeCountTitle: '包邮重量(kg)' }, + 3: { freeCountTitle: '包邮体积(m³)' }, +}; + +/** 运费设置表格列 */ +export function useChargesColumns( + chargeMode = 1, +): VxeTableGridOptions['columns'] { + const chargeTitleMap = CHARGE_MODE_TITLE_MAP[chargeMode]; + return [ + { + field: 'areaIds', + title: '区域', + minWidth: 300, + slots: { default: 'areaIds' }, + }, + { + field: 'startCount', + title: chargeTitleMap?.startCountTitle, + width: 120, + slots: { default: 'startCount' }, + }, + { + field: 'startPrice', + title: '运费(元)', + width: 120, + slots: { default: 'startPrice' }, + }, + { + field: 'extraCount', + title: chargeTitleMap?.extraCountTitle, + width: 120, + slots: { default: 'extraCount' }, + }, + { + field: 'extraPrice', + title: '续费(元)', + width: 120, + slots: { default: 'extraPrice' }, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 包邮设置表格列 */ +export function useFreesColumns( + chargeMode = 1, +): VxeTableGridOptions['columns'] { + const freeTitleMap = FREE_MODE_TITLE_MAP[chargeMode]; + return [ + { + field: 'areaIds', + title: '区域', + minWidth: 300, + slots: { default: 'areaIds' }, + }, + { + field: 'freeCount', + title: freeTitleMap?.freeCountTitle, + width: 120, + slots: { default: 'freeCount' }, + }, + { + field: 'freePrice', + title: '包邮金额(元)', + width: 120, + slots: { default: 'freePrice' }, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '模板名称', + componentProps: { + placeholder: '请输入模板名称', + }, + rules: 'required', + }, + { + fieldName: 'chargeMode', + label: '计费方式', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.EXPRESS_CHARGE_MODE, 'number'), + }, + rules: z.number().default(1), + }, + { + fieldName: 'sort', + label: '显示顺序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入显示顺序', + min: 0, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'charges', + label: '运费设置', + component: 'Input', + formItemClass: 'col-span-3', + }, + { + fieldName: 'frees', + label: '包邮设置', + component: 'Input', + formItemClass: 'col-span-3', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + placeholder: '请输入模板名称', + clearable: true, + }, + }, + { + fieldName: 'chargeMode', + label: '计费方式', + component: 'Select', + componentProps: { + placeholder: '请选择计费方式', + clearable: true, + options: getDictOptions(DICT_TYPE.EXPRESS_CHARGE_MODE, 'number'), + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '模板名称', + minWidth: 200, + }, + { + field: 'chargeMode', + title: '计费方式', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.EXPRESS_CHARGE_MODE }, + }, + }, + { + field: 'sort', + title: '显示顺序', + minWidth: 100, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/index.vue b/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/index.vue new file mode 100644 index 0000000..fef3235 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/index.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/modules/charge-item-form.vue b/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/modules/charge-item-form.vue new file mode 100644 index 0000000..9025baa --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/modules/charge-item-form.vue @@ -0,0 +1,231 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/modules/form.vue b/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/modules/form.vue new file mode 100644 index 0000000..25b8c23 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/modules/form.vue @@ -0,0 +1,197 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/modules/free-item-form.vue b/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/modules/free-item-form.vue new file mode 100644 index 0000000..bbdfacf --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/expressTemplate/modules/free-item-form.vue @@ -0,0 +1,202 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/delivery/pickUpOrder/data.ts b/apps/web-ele/src/views/mall/trade/delivery/pickUpOrder/data.ts new file mode 100644 index 0000000..f3f9be6 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/pickUpOrder/data.ts @@ -0,0 +1,162 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridPropTypes } from '#/adapter/vxe-table'; +import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore'; + +import { ref } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { useUserStore } from '@vben/stores'; + +import { getSimpleDeliveryPickUpStoreList } from '#/api/mall/trade/delivery/pickUpStore'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 关联数据 */ +const userStore = useUserStore(); +const pickUpStoreList = ref([]); +getSimpleDeliveryPickUpStoreList().then((res) => { + pickUpStoreList.value = res; + // 移除自己无法核销的门店 + const userId = userStore?.userInfo?.id; + pickUpStoreList.value = pickUpStoreList.value.filter((item) => + item.verifyUserIds?.includes(userId), + ); +}); + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'pickUpStoreIds', + label: '自提门店', + component: 'Select', + componentProps: { + options: pickUpStoreList, + props: { + label: 'name', + value: 'id', + }, + placeholder: '请选择自提门店', + }, + defaultValue: pickUpStoreList.value[0]?.id, + }, + { + fieldName: 'no', + label: '订单号', + component: 'Input', + componentProps: { + placeholder: '请输入订单号', + clearable: true, + }, + }, + { + fieldName: 'userId', + label: '用户 UID', + component: 'Input', + componentProps: { + placeholder: '请输入用户 UID', + clearable: true, + }, + }, + { + fieldName: 'userNickname', + label: '用户昵称', + component: 'Input', + componentProps: { + placeholder: '请输入用户昵称', + clearable: true, + }, + }, + { + fieldName: 'userMobile', + label: '用户电话', + component: 'Input', + componentProps: { + placeholder: '请输入用户电话', + clearable: true, + }, + }, + ]; +} + +/** 表格列配置 */ +export function useGridColumns(): VxeGridPropTypes.Columns { + return [ + { + field: 'no', + title: '订单号', + fixed: 'left', + minWidth: 180, + }, + { + field: 'user.nickname', + title: '用户信息', + minWidth: 100, + }, + { + field: 'brokerageUser.nickname', + title: '推荐人信息', + minWidth: 100, + }, + { + field: 'spuName', + title: '商品信息', + minWidth: 300, + slots: { default: 'spuName' }, + }, + { + field: 'payPrice', + title: '实付金额(元)', + formatter: 'formatAmount2', + minWidth: 180, + }, + { + field: 'storeStaffName', + title: '核销员', + minWidth: 160, + }, + { + field: 'pickUpStoreId', + title: '核销门店', + minWidth: 160, + formatter: ({ row }) => { + return ( + pickUpStoreList.value.find((item) => item.id === row.pickUpStoreId) + ?.name || '' + ); + }, + }, + { + field: 'payStatus', + title: '支付状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + minWidth: 80, + }, + { + field: 'status', + title: '订单状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.TRADE_ORDER_STATUS }, + }, + minWidth: 80, + }, + { + field: 'createTime', + title: '下单时间', + formatter: 'formatDateTime', + minWidth: 160, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/delivery/pickUpOrder/index.vue b/apps/web-ele/src/views/mall/trade/delivery/pickUpOrder/index.vue new file mode 100644 index 0000000..878f389 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/pickUpOrder/index.vue @@ -0,0 +1,290 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/data.ts b/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/data.ts new file mode 100644 index 0000000..19922a0 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/data.ts @@ -0,0 +1,265 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getAreaTree } from '#/api/system/area'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '门店名称', + rules: 'required', + componentProps: { + placeholder: '请输入门店名称', + }, + }, + { + component: 'Input', + fieldName: 'phone', + label: '门店手机', + rules: 'mobileRequired', + componentProps: { + placeholder: '请输入门店手机', + }, + }, + { + component: 'ImageUpload', + fieldName: 'logo', + label: '门店 logo', + rules: 'required', + formItemClass: 'col-span-2', + componentProps: { + placeholder: '请上传门店 logo', + }, + help: '推荐 180x180 图片分辨率', + }, + { + component: 'Textarea', + fieldName: 'introduction', + label: '门店简介', + formItemClass: 'col-span-2', + componentProps: { + placeholder: '请输入门店简介', + rows: 4, + }, + }, + { + fieldName: 'areaId', + label: '门店所在地区', + component: 'ApiTreeSelect', + rules: 'required', + componentProps: { + api: getAreaTree, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择省市区', + }, + }, + { + component: 'Input', + fieldName: 'detailAddress', + label: '门店详细地址', + rules: 'required', + componentProps: { + placeholder: '请输入门店详细地址', + }, + }, + { + component: 'TimePicker', + fieldName: 'rangeTime', + label: '营业时间', + rules: 'required', + componentProps: { + isRange: true, + format: 'HH:mm', + }, + }, + { + fieldName: 'status', + label: '门店状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + component: 'Input', + fieldName: 'longitude', + label: '经度', + rules: 'required', + componentProps: { + placeholder: '请输入门店经度', + }, + }, + { + component: 'Input', + fieldName: 'latitude', + label: '纬度', + rules: 'required', + componentProps: { + placeholder: '请输入门店纬度', + }, + }, + ]; +} + +/** 绑定店员的表单 */ +export function useBindFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '门店名称', + dependencies: { + triggerFields: ['id'], + disabled: true, + }, + }, + { + component: 'ApiSelect', + fieldName: 'verifyUserIds', + label: '门店店员', + rules: 'required', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + multiple: true, + clearable: true, + placeholder: '请选择门店店员', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'phone', + label: '门店手机', + component: 'Input', + componentProps: { + placeholder: '请输入门店手机', + clearable: true, + }, + }, + { + fieldName: 'name', + label: '门店名称', + component: 'Input', + componentProps: { + placeholder: '请输入门店名称', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '门店状态', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择门店状态', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 80, + }, + { + field: 'logo', + title: '门店 logo', + minWidth: 100, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'name', + title: '门店名称', + minWidth: 150, + }, + { + field: 'phone', + title: '门店手机', + minWidth: 120, + }, + { + field: 'detailAddress', + title: '地址', + minWidth: 200, + }, + { + field: 'openingTime', + title: '营业时间', + minWidth: 160, + formatter: ({ row }) => { + return `${row.openingTime} ~ ${row.closingTime}`; + }, + }, + { + field: 'status', + title: '开启状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 160, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/index.vue b/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/index.vue new file mode 100644 index 0000000..8f470c0 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/index.vue @@ -0,0 +1,150 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/modules/bind-form.vue b/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/modules/bind-form.vue new file mode 100644 index 0000000..5d747f3 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/modules/bind-form.vue @@ -0,0 +1,85 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/modules/form.vue b/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/modules/form.vue new file mode 100644 index 0000000..0ca3656 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/delivery/pickUpStore/modules/form.vue @@ -0,0 +1,157 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/order/data.ts b/apps/web-ele/src/views/mall/trade/order/data.ts new file mode 100644 index 0000000..0d4e923 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/order/data.ts @@ -0,0 +1,450 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridPropTypes } from '#/adapter/vxe-table'; +import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore'; + +import { DeliveryTypeEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { convertToInteger, formatToFraction } from '@vben/utils'; + +import { getSimpleDeliveryExpressList } from '#/api/mall/trade/delivery/express'; +import { getSimpleDeliveryPickUpStoreList } from '#/api/mall/trade/delivery/pickUpStore'; +import { getAreaTree } from '#/api/system/area'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 关联数据 */ +let pickUpStoreList: MallDeliveryPickUpStoreApi.DeliveryPickUpStore[] = []; +getSimpleDeliveryPickUpStoreList().then((data) => { + pickUpStoreList = data; +}); + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'status', + label: '订单状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.TRADE_ORDER_STATUS, 'number'), + placeholder: '请选择订单状态', + clearable: true, + }, + }, + { + fieldName: 'payChannelCode', + label: '支付方式', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE, 'number'), + placeholder: '请选择支付方式', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'terminal', + label: '订单来源', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.TERMINAL, 'number'), + placeholder: '请选择订单来源', + clearable: true, + }, + }, + { + fieldName: 'deliveryType', + label: '配送方式', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.TRADE_DELIVERY_TYPE, 'number'), + placeholder: '请选择配送方式', + clearable: true, + }, + }, + { + fieldName: 'logisticsId', + label: '快递公司', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeliveryExpressList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择快递公司', + clearable: true, + }, + dependencies: { + triggerFields: ['deliveryType'], + show: (values) => values.deliveryType === DeliveryTypeEnum.EXPRESS.type, + }, + }, + { + fieldName: 'pickUpStoreId', + label: '自提门店', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeliveryPickUpStoreList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择自提门店', + clearable: true, + }, + dependencies: { + triggerFields: ['deliveryType'], + show: (values) => values.deliveryType === DeliveryTypeEnum.PICK_UP.type, + }, + }, + { + fieldName: 'pickUpVerifyCode', + label: '核销码', + component: 'Input', + componentProps: { + placeholder: '请输入核销码', + clearable: true, + }, + dependencies: { + triggerFields: ['deliveryType'], + show: (values) => values.deliveryType === DeliveryTypeEnum.PICK_UP.type, + }, + }, + { + fieldName: 'no', + label: '订单号', + component: 'Input', + componentProps: { + placeholder: '请输入订单号', + clearable: true, + }, + }, + { + fieldName: 'userId', + label: '用户 UID', + component: 'Input', + componentProps: { + placeholder: '请输入用户 UID', + clearable: true, + }, + }, + { + fieldName: 'userNickname', + label: '用户昵称', + component: 'Input', + componentProps: { + placeholder: '请输入用户昵称', + clearable: true, + }, + }, + { + fieldName: 'userMobile', + label: '用户电话', + component: 'Input', + componentProps: { + placeholder: '请输入用户电话', + clearable: true, + }, + }, + ]; +} + +/** 表格列配置 */ +export function useGridColumns(): VxeGridPropTypes.Columns { + return [ + { + type: 'expand', + width: 80, + slots: { content: 'expand_content' }, + fixed: 'left', + }, + { + field: 'no', + title: '订单号', + fixed: 'left', + minWidth: 180, + }, + { + field: 'createTime', + title: '下单时间', + formatter: 'formatDateTime', + minWidth: 160, + }, + { + field: 'terminal', + title: '订单来源', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.TERMINAL }, + }, + minWidth: 120, + }, + { + field: 'payChannelCode', + title: '支付方式', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PAY_CHANNEL_CODE }, + }, + minWidth: 120, + }, + { + field: 'payTime', + title: '支付时间', + formatter: 'formatDateTime', + minWidth: 160, + }, + { + field: 'type', + title: '订单类型', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.TRADE_ORDER_TYPE }, + }, + minWidth: 80, + }, + { + field: 'payPrice', + title: '实际支付', + formatter: 'formatAmount2', + minWidth: 180, + }, + { + field: 'user', + title: '买家/收货人', + formatter: ({ row }) => { + if (row.deliveryType === DeliveryTypeEnum.EXPRESS.type) { + return `买家:${row.user?.nickname} / 收货人: ${row.receiverName} ${row.receiverMobile}${row.receiverAreaName}${row.receiverDetailAddress}`; + } + if (row.deliveryType === DeliveryTypeEnum.PICK_UP.type) { + return `门店名称:${pickUpStoreList.find((item) => item.id === row.pickUpStoreId)?.name} / + 门店手机:${pickUpStoreList.find((item) => item.id === row.pickUpStoreId)?.phone} / + 自提门店:${pickUpStoreList.find((item) => item.id === row.pickUpStoreId)?.detailAddress} + `; + } + return ''; + }, + minWidth: 180, + }, + { + field: 'deliveryType', + title: '配送方式', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.TRADE_DELIVERY_TYPE }, + }, + minWidth: 80, + }, + { + field: 'status', + title: '订单状态', + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.TRADE_ORDER_STATUS }, + }, + minWidth: 80, + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 订单备注表单配置 */ +export function useRemarkFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + rows: 3, + }, + }, + ]; +} + +/** 订单调价表单配置 */ +export function usePriceFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'payPrice', + label: '应付金额(总)', + component: 'Input', + componentProps: { + placeholder: '请输入应付金额(总)', + disabled: true, + formatter: (value: string) => `${value}元`, + }, + }, + { + fieldName: 'adjustPrice', + label: '订单调价', + component: 'InputNumber', + componentProps: { + placeholder: '请输入订单调价', + step: 0.1, + precision: 2, + controlsPosition: 'right', + class: '!w-full', + }, + help: '订单调价。 正数,加价;负数,减价', + rules: 'required', + }, + { + fieldName: 'newPayPrice', + label: '调价后', + component: 'Input', + componentProps: { + placeholder: '', + formatter: (value: string) => `${value}元`, + }, + dependencies: { + triggerFields: ['payPrice', 'adjustPrice'], + disabled: true, + trigger(values, form) { + const originalPrice = convertToInteger(values.payPrice); + const adjustPrice = convertToInteger(values.adjustPrice); + const newPrice = originalPrice + adjustPrice; + form.setFieldValue('newPayPrice', formatToFraction(newPrice)); + }, + }, + }, + ]; +} + +/** 订单修改地址表单配置 */ +export function useAddressFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'receiverName', + label: '收件人', + component: 'Input', + componentProps: { + placeholder: '请输入收件人名称', + }, + rules: 'required', + }, + { + fieldName: 'receiverMobile', + label: '手机号', + component: 'Input', + componentProps: { + placeholder: '请输入收件人手机号', + }, + rules: 'required', + }, + { + fieldName: 'receiverAreaId', + label: '所在地', + component: 'ApiTreeSelect', + componentProps: { + api: getAreaTree, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择收件人所在地', + treeDefaultExpandAll: true, + }, + rules: 'required', + }, + { + fieldName: 'receiverDetailAddress', + label: '详细地址', + component: 'Input', + componentProps: { + placeholder: '请输入收件人详细地址', + type: 'textarea', + rows: 3, + }, + rules: 'required', + }, + ]; +} + +/** 订单发货表单配置 */ +export function useDeliveryFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'expressType', + label: '发货方式', + component: 'RadioGroup', + componentProps: { + options: [ + { label: '快递', value: 'express' }, + { label: '无需发货', value: 'none' }, + ], + }, + defaultValue: 'express', + }, + { + fieldName: 'logisticsId', + label: '物流公司', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeliveryExpressList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择物流公司', + }, + dependencies: { + triggerFields: ['expressType'], + show: (values) => values.expressType === 'express', + }, + rules: 'required', + }, + { + fieldName: 'logisticsNo', + label: '物流单号', + component: 'Input', + componentProps: { + placeholder: '请输入物流单号', + }, + dependencies: { + triggerFields: ['expressType'], + show: (values) => values.expressType === 'express', + }, + rules: 'required', + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/order/detail/data.ts b/apps/web-ele/src/views/mall/trade/order/detail/data.ts new file mode 100644 index 0000000..febf4ed --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/order/detail/data.ts @@ -0,0 +1,255 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { fenToYuan, formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; + +/** 订单基础信息 schema */ +export function useOrderInfoSchema(): DescriptionItemSchema[] { + return [ + { + field: 'no', + label: '订单号', + }, + { + field: 'user.nickname', + label: '买家', + }, + { + field: 'type', + label: '订单类型', + render: (val) => + h(DictTag, { + type: DICT_TYPE.TRADE_ORDER_TYPE, + value: val, + }), + }, + { + field: 'terminal', + label: '订单来源', + render: (val) => + h(DictTag, { + type: DICT_TYPE.TERMINAL, + value: val, + }), + }, + { + field: 'userRemark', + label: '买家留言', + }, + { + field: 'remark', + label: '商家备注', + }, + { + field: 'payOrderId', + label: '支付单号', + }, + { + field: 'payChannelCode', + label: '付款方式', + render: (val) => + h(DictTag, { + type: DICT_TYPE.PAY_CHANNEL_CODE, + value: val, + }), + }, + { + field: 'brokerageUser.nickname', + label: '推广用户', + }, + ]; +} + +/** 订单状态信息 schema */ +export function useOrderStatusSchema(): DescriptionItemSchema[] { + return [ + { + field: 'status', + label: '订单状态', + render: (val) => + h(DictTag, { + type: DICT_TYPE.TRADE_ORDER_STATUS, + value: val, + }), + }, + { + field: 'reminder', + label: '提醒', + render: () => + h('div', { class: 'space-y-1' }, [ + h('div', '买家付款成功后,货款将直接进入您的商户号(微信、支付宝)'), + h('div', '请及时关注你发出的包裹状态,确保可以配送至买家手中'), + h( + 'div', + '如果买家表示没收到货或货物有问题,请及时联系买家处理,友好协商', + ), + ]), + }, + ]; +} + +/** 订单金额信息 schema */ +export function useOrderPriceSchema(): DescriptionItemSchema[] { + return [ + { + field: 'totalPrice', + label: '商品总额', + render: (val) => `${fenToYuan(val ?? 0)} 元`, + }, + { + field: 'deliveryPrice', + label: '运费金额', + render: (val) => `${fenToYuan(val ?? 0)} 元`, + }, + { + field: 'adjustPrice', + label: '订单调价', + render: (val) => `${fenToYuan(val ?? 0)} 元`, + }, + { + field: 'couponPrice', + label: '优惠劵优惠', + render: (val) => + h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 元`), + }, + { + field: 'vipPrice', + label: 'VIP 优惠', + render: (val) => + h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 元`), + }, + { + field: 'discountPrice', + label: '活动优惠', + render: (val) => + h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 元`), + }, + { + field: 'pointPrice', + label: '积分抵扣', + render: (val) => + h('span', { class: 'text-red-500' }, `${fenToYuan(val ?? 0)} 元`), + }, + { + field: 'payPrice', + label: '应付金额', + render: (val) => `${fenToYuan(val ?? 0)} 元`, + }, + ]; +} + +/** 收货信息 schema */ +export function useDeliveryInfoSchema(): DescriptionItemSchema[] { + return [ + { + field: 'deliveryType', + label: '配送方式', + render: (val) => + h(DictTag, { + type: DICT_TYPE.TRADE_DELIVERY_TYPE, + value: val, + }), + }, + { + field: 'receiverName', + label: '收货人', + }, + { + field: 'receiverMobile', + label: '联系电话', + }, + { + field: 'receiverAddress', + label: '收货地址', + render: (val, data) => `${data?.receiverAreaName} ${val}`.trim(), + }, + { + field: 'deliveryTime', + label: '发货时间', + render: (val) => formatDateTime(val) as string, + }, + ]; +} + +/** 商品信息 columns */ +export function useProductColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'spuName', + title: '商品', + minWidth: 300, + slots: { default: 'spuName' }, + }, + { + field: 'price', + title: '商品原价', + width: 150, + formatter: 'formatFenToYuanAmount', + }, + { + field: 'count', + title: '数量', + width: 100, + }, + { + field: 'payPrice', + title: '合计', + width: 150, + formatter: 'formatFenToYuanAmount', + }, + { + field: 'afterSaleStatus', + title: '售后状态', + width: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.TRADE_ORDER_ITEM_AFTER_SALE_STATUS }, + }, + }, + ]; +} + +/** 物流详情 columns */ +export function useExpressTrackColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'time', + title: '时间', + width: 180, + formatter: 'formatDateTime', + }, + { + field: 'content', + title: '物流状态', + minWidth: 300, + }, + ]; +} + +/** 操作日志 columns */ +export function useOperateLogColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'createTime', + title: '操作时间', + width: 180, + formatter: 'formatDateTime', + }, + { + field: 'userType', + title: '操作人', + width: 100, + slots: { default: 'userType' }, + }, + { + field: 'content', + title: '操作内容', + minWidth: 200, + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/order/detail/index.vue b/apps/web-ele/src/views/mall/trade/order/detail/index.vue new file mode 100644 index 0000000..4a84bd8 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/order/detail/index.vue @@ -0,0 +1,341 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/order/index.vue b/apps/web-ele/src/views/mall/trade/order/index.vue new file mode 100644 index 0000000..c19b0fd --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/order/index.vue @@ -0,0 +1,182 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/order/modules/address-form.vue b/apps/web-ele/src/views/mall/trade/order/modules/address-form.vue new file mode 100644 index 0000000..18712c9 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/order/modules/address-form.vue @@ -0,0 +1,77 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/order/modules/delivery-form.vue b/apps/web-ele/src/views/mall/trade/order/modules/delivery-form.vue new file mode 100644 index 0000000..1e95cc8 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/order/modules/delivery-form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/order/modules/price-form.vue b/apps/web-ele/src/views/mall/trade/order/modules/price-form.vue new file mode 100644 index 0000000..bb6eafd --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/order/modules/price-form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/order/modules/remark-form.vue b/apps/web-ele/src/views/mall/trade/order/modules/remark-form.vue new file mode 100644 index 0000000..7fb5d29 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/order/modules/remark-form.vue @@ -0,0 +1,78 @@ + + + diff --git a/apps/web-ele/src/views/member/config/data.ts b/apps/web-ele/src/views/member/config/data.ts new file mode 100644 index 0000000..7004d70 --- /dev/null +++ b/apps/web-ele/src/views/member/config/data.ts @@ -0,0 +1,55 @@ +import type { VbenFormSchema } from '#/adapter/form'; + +export const schema: VbenFormSchema[] = [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Switch', + fieldName: 'pointTradeDeductEnable', + label: '积分抵扣', + help: '下单积分是否抵用订单金额', + }, + { + component: 'InputNumber', + fieldName: 'pointTradeDeductUnitPrice', + label: '积分抵扣', + help: '积分抵用比例(1 积分抵多少金额),单位:元', + componentProps: { + min: 0, + precision: 2, + placeholder: '请输入积分抵扣单价', + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + component: 'InputNumber', + fieldName: 'pointTradeDeductMaxPrice', + label: '积分抵扣最大值', + help: '单次下单积分使用上限,0 不限制', + componentProps: { + min: 0, + placeholder: '请输入积分抵扣最大值', + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + component: 'InputNumber', + fieldName: 'pointTradeGivePoint', + label: '1 元赠送多少分', + help: '下单支付金额按比例赠送积分(实际支付 1 元赠送多少积分)', + componentProps: { + min: 0, + placeholder: '请输入赠送积分比例', + controlsPosition: 'right', + class: '!w-full', + }, + }, +]; diff --git a/apps/web-ele/src/views/member/config/index.vue b/apps/web-ele/src/views/member/config/index.vue new file mode 100644 index 0000000..a3eefd2 --- /dev/null +++ b/apps/web-ele/src/views/member/config/index.vue @@ -0,0 +1,67 @@ + + + diff --git a/apps/web-ele/src/views/member/group/data.ts b/apps/web-ele/src/views/member/group/data.ts new file mode 100644 index 0000000..75b8b3d --- /dev/null +++ b/apps/web-ele/src/views/member/group/data.ts @@ -0,0 +1,124 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '分组名称', + component: 'Input', + componentProps: { + placeholder: '请输入分组名称', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '分组名称', + component: 'Input', + componentProps: { + placeholder: '请输入分组名称', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + placeholder: ['开始日期', '结束日期'], + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '分组名称', + minWidth: 150, + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + showOverflow: 'tooltip', + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/member/group/index.vue b/apps/web-ele/src/views/member/group/index.vue new file mode 100644 index 0000000..e7e06ed --- /dev/null +++ b/apps/web-ele/src/views/member/group/index.vue @@ -0,0 +1,125 @@ + + + diff --git a/apps/web-ele/src/views/member/group/modules/form.vue b/apps/web-ele/src/views/member/group/modules/form.vue new file mode 100644 index 0000000..6e36fe2 --- /dev/null +++ b/apps/web-ele/src/views/member/group/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/member/level/data.ts b/apps/web-ele/src/views/member/level/data.ts new file mode 100644 index 0000000..535dddf --- /dev/null +++ b/apps/web-ele/src/views/member/level/data.ts @@ -0,0 +1,192 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '等级名称', + component: 'Input', + componentProps: { + placeholder: '请输入等级名称', + }, + rules: 'required', + }, + { + fieldName: 'level', + label: '等级', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 0, + placeholder: '请输入等级', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'experience', + label: '升级经验', + component: 'InputNumber', + componentProps: { + min: 0, + precision: 0, + placeholder: '请输入升级经验', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'discountPercent', + label: '享受折扣(%)', + component: 'InputNumber', + componentProps: { + min: 0, + max: 100, + precision: 0, + placeholder: '请输入享受折扣', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'icon', + label: '等级图标', + component: 'ImageUpload', + }, + { + fieldName: 'backgroundUrl', + label: '等级背景图', + component: 'ImageUpload', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '等级名称', + component: 'Input', + componentProps: { + placeholder: '请输入等级名称', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + placeholder: '请选择状态', + clearable: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '等级编号', + minWidth: 80, + }, + { + field: 'icon', + title: '等级图标', + minWidth: 100, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'backgroundUrl', + title: '等级背景图', + minWidth: 120, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'name', + title: '等级名称', + minWidth: 120, + }, + { + field: 'level', + title: '等级', + minWidth: 80, + }, + { + field: 'experience', + title: '升级经验', + minWidth: 100, + }, + { + field: 'discountPercent', + title: '享受折扣(%)', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 80, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/member/level/index.vue b/apps/web-ele/src/views/member/level/index.vue new file mode 100644 index 0000000..3284397 --- /dev/null +++ b/apps/web-ele/src/views/member/level/index.vue @@ -0,0 +1,133 @@ + + + diff --git a/apps/web-ele/src/views/member/level/modules/form.vue b/apps/web-ele/src/views/member/level/modules/form.vue new file mode 100644 index 0000000..087d0d4 --- /dev/null +++ b/apps/web-ele/src/views/member/level/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/member/point/record/data.ts b/apps/web-ele/src/views/member/point/record/data.ts new file mode 100644 index 0000000..0ddb072 --- /dev/null +++ b/apps/web-ele/src/views/member/point/record/data.ts @@ -0,0 +1,121 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { ElTag } from 'element-plus'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'nickname', + label: '用户', + component: 'Input', + componentProps: { + placeholder: '请输入用户昵称', + clearable: true, + }, + }, + { + fieldName: 'bizType', + label: '业务类型', + component: 'Select', + componentProps: { + placeholder: '请选择业务类型', + clearable: true, + options: getDictOptions(DICT_TYPE.MEMBER_POINT_BIZ_TYPE, 'number'), + }, + }, + { + fieldName: 'title', + label: '积分标题', + component: 'Input', + componentProps: { + placeholder: '请输入积分标题', + clearable: true, + }, + }, + { + fieldName: 'createDate', + label: '获得时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'createTime', + title: '获得时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'nickname', + title: '用户', + minWidth: 150, + }, + { + field: 'point', + title: '获得积分', + minWidth: 120, + slots: { + default: ({ row }) => { + return h( + ElTag, + { + type: row.point > 0 ? 'primary' : 'danger', + }, + () => (row.point > 0 ? `+${row.point}` : row.point), + ); + }, + }, + }, + { + field: 'totalPoint', + title: '总积分', + minWidth: 100, + }, + { + field: 'title', + title: '标题', + minWidth: 200, + }, + { + field: 'description', + title: '描述', + minWidth: 200, + }, + { + field: 'bizId', + title: '业务编码', + minWidth: 120, + }, + { + field: 'bizType', + title: '业务类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.MEMBER_POINT_BIZ_TYPE }, + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/member/point/record/index.vue b/apps/web-ele/src/views/member/point/record/index.vue new file mode 100644 index 0000000..cbc15c6 --- /dev/null +++ b/apps/web-ele/src/views/member/point/record/index.vue @@ -0,0 +1,54 @@ + + + diff --git a/apps/web-ele/src/views/member/signin/config/data.ts b/apps/web-ele/src/views/member/signin/config/data.ts new file mode 100644 index 0000000..e3b17dc --- /dev/null +++ b/apps/web-ele/src/views/member/signin/config/data.ts @@ -0,0 +1,108 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'InputNumber', + fieldName: 'day', + label: '签到天数', + help: '只允许设置 1-7,默认签到 7 天为一个周期', + componentProps: { + min: 1, + max: 7, + precision: 0, + placeholder: '请输入签到天数', + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(1).max(7, '签到天数必须在 1-7 之间'), + }, + { + component: 'InputNumber', + fieldName: 'point', + label: '获得积分', + componentProps: { + min: 0, + precision: 0, + placeholder: '请输入获得积分', + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0, '获得积分不能小于 0'), + }, + { + component: 'InputNumber', + fieldName: 'experience', + label: '奖励经验', + componentProps: { + min: 0, + precision: 0, + placeholder: '请输入奖励经验', + controlsPosition: 'right', + class: '!w-full', + }, + rules: z.number().min(0, '奖励经验不能小于 0'), + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'day', + title: '签到天数', + minWidth: 120, + formatter: ({ cellValue }) => ['第', cellValue, '天'].join(' '), + }, + { + field: 'point', + title: '获得积分', + minWidth: 120, + }, + { + field: 'experience', + title: '奖励经验', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + title: '操作', + width: 150, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/member/signin/config/index.vue b/apps/web-ele/src/views/member/signin/config/index.vue new file mode 100644 index 0000000..3b253a4 --- /dev/null +++ b/apps/web-ele/src/views/member/signin/config/index.vue @@ -0,0 +1,131 @@ + + + diff --git a/apps/web-ele/src/views/member/signin/config/modules/form.vue b/apps/web-ele/src/views/member/signin/config/modules/form.vue new file mode 100644 index 0000000..f545399 --- /dev/null +++ b/apps/web-ele/src/views/member/signin/config/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/member/signin/record/data.ts b/apps/web-ele/src/views/member/signin/record/data.ts new file mode 100644 index 0000000..8217b3f --- /dev/null +++ b/apps/web-ele/src/views/member/signin/record/data.ts @@ -0,0 +1,85 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { h } from 'vue'; + +import { ElTag } from 'element-plus'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'nickname', + label: '签到用户', + component: 'Input', + componentProps: { + placeholder: '请输入签到用户', + clearable: true, + }, + }, + { + fieldName: 'day', + label: '签到天数', + component: 'Input', + componentProps: { + placeholder: '请输入签到天数', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '签到时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'nickname', + title: '签到用户', + minWidth: 150, + }, + { + field: 'day', + title: '签到天数', + minWidth: 120, + formatter: ({ cellValue }) => ['第', cellValue, '天'].join(' '), + }, + { + field: 'point', + title: '获得积分', + minWidth: 120, + slots: { + default: ({ row }) => { + return h( + ElTag, + { + type: row.point > 0 ? 'primary' : 'danger', + }, + () => (row.point > 0 ? `+${row.point}` : row.point), + ); + }, + }, + }, + { + field: 'createTime', + title: '签到时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + ]; +} diff --git a/apps/web-ele/src/views/member/signin/record/index.vue b/apps/web-ele/src/views/member/signin/record/index.vue new file mode 100644 index 0000000..d5a36b7 --- /dev/null +++ b/apps/web-ele/src/views/member/signin/record/index.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-ele/src/views/member/tag/data.ts b/apps/web-ele/src/views/member/tag/data.ts new file mode 100644 index 0000000..d5e24d1 --- /dev/null +++ b/apps/web-ele/src/views/member/tag/data.ts @@ -0,0 +1,79 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '标签名称', + componentProps: { + placeholder: '请输入标签名称', + }, + rules: 'required', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '标签名称', + component: 'Input', + componentProps: { + placeholder: '请输入标签名称', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + placeholder: ['开始日期', '结束日期'], + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '标签名称', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 150, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/member/tag/index.vue b/apps/web-ele/src/views/member/tag/index.vue new file mode 100644 index 0000000..6bc4124 --- /dev/null +++ b/apps/web-ele/src/views/member/tag/index.vue @@ -0,0 +1,131 @@ + + + diff --git a/apps/web-ele/src/views/member/tag/modules/form.vue b/apps/web-ele/src/views/member/tag/modules/form.vue new file mode 100644 index 0000000..c1bffdd --- /dev/null +++ b/apps/web-ele/src/views/member/tag/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/member/user/data.ts b/apps/web-ele/src/views/member/user/data.ts new file mode 100644 index 0000000..d64a178 --- /dev/null +++ b/apps/web-ele/src/views/member/user/data.ts @@ -0,0 +1,499 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { h } from 'vue'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { convertToInteger, formatToFraction } from '@vben/utils'; + +import { ElTag } from 'element-plus'; + +import { z } from '#/adapter/form'; +import { getSimpleGroupList } from '#/api/member/group'; +import { getSimpleLevelList } from '#/api/member/level'; +import { getSimpleTagList } from '#/api/member/tag'; +import { getAreaTree } from '#/api/system/area'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'mobile', + label: '手机号', + component: 'Input', + componentProps: { + placeholder: '请输入手机号', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE).optional(), + }, + { + fieldName: 'nickname', + label: '用户昵称', + component: 'Input', + componentProps: { + placeholder: '请输入用户昵称', + }, + }, + { + fieldName: 'avatar', + label: '头像', + component: 'ImageUpload', + }, + { + fieldName: 'name', + label: '真实名字', + component: 'Input', + componentProps: { + placeholder: '请输入真实名字', + }, + }, + { + fieldName: 'sex', + label: '用户性别', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + }, + }, + { + fieldName: 'birthday', + label: '出生日期', + component: 'DatePicker', + componentProps: { + format: 'YYYY-MM-DD', + valueFormat: 'x', + placeholder: '请选择出生日期', + class: '!w-full', + }, + }, + { + fieldName: 'areaId', + label: '所在地', + component: 'ApiTreeSelect', + componentProps: { + api: getAreaTree, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择所在地', + }, + }, + { + fieldName: 'tagIds', + label: '用户标签', + component: 'ApiSelect', + componentProps: { + api: getSimpleTagList, + labelField: 'name', + valueField: 'id', + multiple: true, + placeholder: '请选择用户标签', + }, + }, + { + fieldName: 'groupId', + label: '用户分组', + component: 'ApiSelect', + componentProps: { + api: getSimpleGroupList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择用户分组', + }, + }, + { + fieldName: 'mark', + label: '会员备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入会员备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'nickname', + label: '用户昵称', + component: 'Input', + componentProps: { + placeholder: '请输入用户昵称', + clearable: true, + }, + }, + { + fieldName: 'mobile', + label: '手机号', + component: 'Input', + componentProps: { + placeholder: '请输入手机号', + clearable: true, + }, + }, + { + fieldName: 'loginDate', + label: '登录时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '注册时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'tagIds', + label: '用户标签', + component: 'ApiSelect', + componentProps: { + api: getSimpleTagList, + labelField: 'name', + valueField: 'id', + multiple: true, + placeholder: '请选择用户标签', + clearable: true, + }, + }, + { + fieldName: 'levelId', + label: '用户等级', + component: 'ApiSelect', + componentProps: { + api: getSimpleLevelList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择用户等级', + clearable: true, + }, + }, + { + fieldName: 'groupId', + label: '用户分组', + component: 'ApiSelect', + componentProps: { + api: getSimpleGroupList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择用户分组', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + type: 'checkbox', + width: 50, + }, + { + field: 'id', + title: '用户编号', + minWidth: 100, + }, + { + field: 'avatar', + title: '头像', + minWidth: 80, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'mobile', + title: '手机号', + minWidth: 120, + }, + { + field: 'nickname', + title: '昵称', + minWidth: 120, + }, + { + field: 'levelName', + title: '等级', + minWidth: 100, + }, + { + field: 'groupName', + title: '分组', + minWidth: 100, + }, + { + field: 'tagNames', + title: '用户标签', + minWidth: 150, + slots: { + default: ({ row }) => { + return row.tagNames?.map((tagName: string, index: number) => { + return h( + ElTag, + { + key: index, + class: 'mr-1', + type: 'primary', + }, + () => tagName, + ); + }); + }, + }, + }, + { + field: 'point', + title: '积分', + minWidth: 80, + }, + { + field: 'status', + title: '状态', + minWidth: 80, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'loginDate', + title: '登录时间', + minWidth: 160, + formatter: 'formatDateTime', + }, + { + field: 'createTime', + title: '注册时间', + minWidth: 160, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 修改用户等级 */ +export function useLevelFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + label: '用户编号', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'nickname', + label: '用户昵称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'levelId', + label: '用户等级', + component: 'ApiSelect', + componentProps: { + api: getSimpleLevelList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择用户等级', + clearable: true, + }, + }, + { + fieldName: 'reason', + label: '修改原因', + component: 'Textarea', + componentProps: { + placeholder: '请输入修改原因', + }, + rules: 'required', + }, + ]; +} + +/** 修改用户余额 */ +export function useBalanceFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + label: '用户编号', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'nickname', + label: '用户昵称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'balance', + label: '变动前余额(元)', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'changeType', + label: '变动类型', + component: 'RadioGroup', + componentProps: { + options: [ + { label: '增加', value: 1 }, + { label: '减少', value: -1 }, + ], + }, + defaultValue: 1, + }, + { + fieldName: 'changeBalance', + label: '变动余额(元)', + component: 'InputNumber', + rules: 'required', + componentProps: { + min: 0, + precision: 2, + step: 0.1, + placeholder: '请输入变动余额', + controlsPosition: 'right', + class: '!w-full', + }, + defaultValue: 0, + }, + { + fieldName: 'balanceResult', + label: '变动后余额(元)', + component: 'Input', + dependencies: { + triggerFields: ['balance', 'changeBalance', 'changeType'], + disabled: true, + trigger(values, form) { + form.setFieldValue( + 'balanceResult', + formatToFraction( + convertToInteger(values.balance) + + convertToInteger(values.changeBalance) * values.changeType, + ), + ); + }, + }, + }, + ]; +} + +/** 修改用户积分 */ +export function usePointFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + label: '用户编号', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'nickname', + label: '用户昵称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'point', + label: '变动前积分', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'changeType', + label: '变动类型', + component: 'RadioGroup', + componentProps: { + options: [ + { label: '增加', value: 1 }, + { label: '减少', value: -1 }, + ], + }, + defaultValue: 1, + }, + { + fieldName: 'changePoint', + label: '变动积分', + component: 'InputNumber', + rules: 'required', + componentProps: { + min: 0, + precision: 0, + placeholder: '请输入变动积分', + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'pointResult', + label: '变动后积分', + component: 'Input', + componentProps: { + placeholder: '', + }, + dependencies: { + triggerFields: ['point', 'changePoint', 'changeType'], + disabled: true, + trigger(values, form) { + form.setFieldValue( + 'pointResult', + values.point + values.changePoint * values.changeType || + values.point, + ); + }, + }, + rules: z.number().min(0), + }, + ]; +} diff --git a/apps/web-ele/src/views/member/user/detail/index.vue b/apps/web-ele/src/views/member/user/detail/index.vue new file mode 100644 index 0000000..bf2ef4c --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/index.vue @@ -0,0 +1,127 @@ + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/account-info.vue b/apps/web-ele/src/views/member/user/detail/modules/account-info.vue new file mode 100644 index 0000000..d99cbf0 --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/account-info.vue @@ -0,0 +1,79 @@ + + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/address-list.vue b/apps/web-ele/src/views/member/user/detail/modules/address-list.vue new file mode 100644 index 0000000..488058e --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/address-list.vue @@ -0,0 +1,87 @@ + + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/after-sale-list.vue b/apps/web-ele/src/views/member/user/detail/modules/after-sale-list.vue new file mode 100644 index 0000000..5661f01 --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/after-sale-list.vue @@ -0,0 +1,156 @@ + + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/balance-list.vue b/apps/web-ele/src/views/member/user/detail/modules/balance-list.vue new file mode 100644 index 0000000..86b148c --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/balance-list.vue @@ -0,0 +1,43 @@ + + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/basic-info.vue b/apps/web-ele/src/views/member/user/detail/modules/basic-info.vue new file mode 100644 index 0000000..c6e3891 --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/basic-info.vue @@ -0,0 +1,98 @@ + + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/brokerage-list.vue b/apps/web-ele/src/views/member/user/detail/modules/brokerage-list.vue new file mode 100644 index 0000000..5adb98f --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/brokerage-list.vue @@ -0,0 +1,125 @@ + + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/coupon-list.vue b/apps/web-ele/src/views/member/user/detail/modules/coupon-list.vue new file mode 100644 index 0000000..51b908f --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/coupon-list.vue @@ -0,0 +1,156 @@ + + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/experience-record-list.vue b/apps/web-ele/src/views/member/user/detail/modules/experience-record-list.vue new file mode 100644 index 0000000..cd3d71e --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/experience-record-list.vue @@ -0,0 +1,150 @@ + + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/favorite-list.vue b/apps/web-ele/src/views/member/user/detail/modules/favorite-list.vue new file mode 100644 index 0000000..7fa7231 --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/favorite-list.vue @@ -0,0 +1,98 @@ + + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/order-list.vue b/apps/web-ele/src/views/member/user/detail/modules/order-list.vue new file mode 100644 index 0000000..37b562b --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/order-list.vue @@ -0,0 +1,133 @@ + + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/point-list.vue b/apps/web-ele/src/views/member/user/detail/modules/point-list.vue new file mode 100644 index 0000000..40ef482 --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/point-list.vue @@ -0,0 +1,68 @@ + + + diff --git a/apps/web-ele/src/views/member/user/detail/modules/sign-list.vue b/apps/web-ele/src/views/member/user/detail/modules/sign-list.vue new file mode 100644 index 0000000..afede65 --- /dev/null +++ b/apps/web-ele/src/views/member/user/detail/modules/sign-list.vue @@ -0,0 +1,65 @@ + + + diff --git a/apps/web-ele/src/views/member/user/index.vue b/apps/web-ele/src/views/member/user/index.vue new file mode 100644 index 0000000..6e166cd --- /dev/null +++ b/apps/web-ele/src/views/member/user/index.vue @@ -0,0 +1,212 @@ + + + diff --git a/apps/web-ele/src/views/member/user/modules/balance-form.vue b/apps/web-ele/src/views/member/user/modules/balance-form.vue new file mode 100644 index 0000000..0a5c34f --- /dev/null +++ b/apps/web-ele/src/views/member/user/modules/balance-form.vue @@ -0,0 +1,96 @@ + + + diff --git a/apps/web-ele/src/views/member/user/modules/form.vue b/apps/web-ele/src/views/member/user/modules/form.vue new file mode 100644 index 0000000..b70c081 --- /dev/null +++ b/apps/web-ele/src/views/member/user/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/member/user/modules/level-form.vue b/apps/web-ele/src/views/member/user/modules/level-form.vue new file mode 100644 index 0000000..50d5c6f --- /dev/null +++ b/apps/web-ele/src/views/member/user/modules/level-form.vue @@ -0,0 +1,81 @@ + + + diff --git a/apps/web-ele/src/views/member/user/modules/point-form.vue b/apps/web-ele/src/views/member/user/modules/point-form.vue new file mode 100644 index 0000000..16d810d --- /dev/null +++ b/apps/web-ele/src/views/member/user/modules/point-form.vue @@ -0,0 +1,83 @@ + + + diff --git a/apps/web-ele/src/views/mp/account/data.ts b/apps/web-ele/src/views/mp/account/data.ts new file mode 100644 index 0000000..d520153 --- /dev/null +++ b/apps/web-ele/src/views/mp/account/data.ts @@ -0,0 +1,143 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridPropTypes } from '#/adapter/vxe-table'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '名称', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入名称', + }, + }, + { + fieldName: 'account', + label: '微信号', + component: 'Input', + help: '在微信公众平台(mp.weixin.qq.com)的菜单 [设置与开发 - 公众号设置 - 账号详情] 中能找到「微信号」', + rules: 'required', + componentProps: { + placeholder: '请输入微信号', + }, + }, + { + fieldName: 'appId', + label: 'appId', + component: 'Input', + help: '在微信公众平台(mp.weixin.qq.com)的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者ID(AppID)」', + rules: 'required', + componentProps: { + placeholder: '请输入公众号 appId', + }, + }, + { + fieldName: 'appSecret', + label: 'appSecret', + component: 'Input', + help: '在微信公众平台(mp.weixin.qq.com)的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者密码(AppSecret)」', + rules: 'required', + componentProps: { + placeholder: '请输入公众号 appSecret', + }, + }, + { + fieldName: 'token', + label: 'token', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入公众号 token', + }, + }, + { + fieldName: 'aesKey', + label: '消息加解密密钥', + component: 'Input', + componentProps: { + placeholder: '请输入消息加解密密钥', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 搜索表单配置 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '名称', + component: 'Input', + componentProps: { + placeholder: '请输入名称', + clearable: true, + }, + }, + ]; +} + +/** 表格列配置 */ +export function useGridColumns(): VxeGridPropTypes.Columns { + return [ + { + title: '名称', + field: 'name', + minWidth: 150, + }, + { + title: '微信号', + field: 'account', + minWidth: 180, + }, + { + title: 'appId', + field: 'appId', + minWidth: 180, + }, + { + title: '服务器地址(URL)', + field: 'utl', + minWidth: 360, + slots: { + default: ({ row }) => { + return `http://服务端地址/admin-api/mp/open/${row.appId}`; + }, + }, + }, + { + title: '二维码', + field: 'qrCodeUrl', + minWidth: 120, + cellRender: { name: 'CellImage' }, + }, + { + title: '备注', + field: 'remark', + minWidth: 150, + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mp/account/index.vue b/apps/web-ele/src/views/mp/account/index.vue new file mode 100644 index 0000000..ff77958 --- /dev/null +++ b/apps/web-ele/src/views/mp/account/index.vue @@ -0,0 +1,178 @@ + + + diff --git a/apps/web-ele/src/views/mp/account/modules/form.vue b/apps/web-ele/src/views/mp/account/modules/form.vue new file mode 100644 index 0000000..792f9e0 --- /dev/null +++ b/apps/web-ele/src/views/mp/account/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/mp/autoReply/data.ts b/apps/web-ele/src/views/mp/autoReply/data.ts new file mode 100644 index 0000000..2f8c3e4 --- /dev/null +++ b/apps/web-ele/src/views/mp/autoReply/data.ts @@ -0,0 +1,147 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridPropTypes } from '#/adapter/vxe-table'; + +import { markRaw } from 'vue'; + +import { + AutoReplyMsgType, + DICT_TYPE, + RequestMessageTypes, +} from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { WxReply } from '#/views/mp/components'; + +/** 获取表格列配置 */ +export function useGridColumns( + msgType: AutoReplyMsgType, +): VxeGridPropTypes.Columns { + const columns: VxeGridPropTypes.Columns = []; + // 请求消息类型列(仅消息回复显示) + if (msgType === AutoReplyMsgType.Message) { + columns.push({ + field: 'requestMessageType', + title: '请求消息类型', + minWidth: 120, + }); + } + // 关键词列(仅关键词回复显示) + if (msgType === AutoReplyMsgType.Keyword) { + columns.push({ + field: 'requestKeyword', + title: '关键词', + minWidth: 150, + }); + } + // 匹配类型列(仅关键词回复显示) + if (msgType === AutoReplyMsgType.Keyword) { + columns.push({ + field: 'requestMatch', + title: '匹配类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH }, + }, + }); + } + // 回复消息类型列 + columns.push( + { + field: 'responseMessageType', + title: '回复消息类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.MP_MESSAGE_TYPE }, + }, + }, + { + field: 'responseContent', + title: '回复内容', + minWidth: 200, + slots: { default: 'replyContent' }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 140, + fixed: 'right', + slots: { default: 'actions' }, + }, + ); + return columns; +} + +/** 新增/修改的表单 */ +export function useFormSchema(msgType: AutoReplyMsgType): VbenFormSchema[] { + const schema: VbenFormSchema[] = []; + // 消息类型(仅消息回复显示) + if (msgType === AutoReplyMsgType.Message) { + schema.push({ + fieldName: 'requestMessageType', + label: '消息类型', + component: 'Select', + componentProps: { + placeholder: '请选择', + options: getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE).filter((d) => + RequestMessageTypes.has(d.value as string), + ), + }, + }); + } + // 匹配类型(仅关键词回复显示) + if (msgType === AutoReplyMsgType.Keyword) { + schema.push({ + fieldName: 'requestMatch', + label: '匹配类型', + component: 'Select', + componentProps: { + placeholder: '请选择匹配类型', + allowClear: true, + options: getDictOptions( + DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH, + 'number', + ), + }, + rules: 'required', + }); + } + // 关键词(仅关键词回复显示) + if (msgType === AutoReplyMsgType.Keyword) { + schema.push({ + fieldName: 'requestKeyword', + label: '关键词', + component: 'Input', + componentProps: { + placeholder: '请输入内容', + allowClear: true, + }, + rules: 'required', + }); + } + // 回复消息 + schema.push({ + fieldName: 'reply', + label: '回复消息', + component: markRaw(WxReply), + modelPropName: 'modelValue', + }); + return schema; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'accountId', + label: '公众号', + component: 'Input', + }, + ]; +} diff --git a/apps/web-ele/src/views/mp/autoReply/index.vue b/apps/web-ele/src/views/mp/autoReply/index.vue new file mode 100644 index 0000000..4ed1b84 --- /dev/null +++ b/apps/web-ele/src/views/mp/autoReply/index.vue @@ -0,0 +1,233 @@ + + + diff --git a/apps/web-ele/src/views/mp/autoReply/modules/content.vue b/apps/web-ele/src/views/mp/autoReply/modules/content.vue new file mode 100644 index 0000000..caa022c --- /dev/null +++ b/apps/web-ele/src/views/mp/autoReply/modules/content.vue @@ -0,0 +1,57 @@ + + + diff --git a/apps/web-ele/src/views/mp/autoReply/modules/form.vue b/apps/web-ele/src/views/mp/autoReply/modules/form.vue new file mode 100644 index 0000000..ef7eaaa --- /dev/null +++ b/apps/web-ele/src/views/mp/autoReply/modules/form.vue @@ -0,0 +1,156 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/index.ts b/apps/web-ele/src/views/mp/components/index.ts new file mode 100644 index 0000000..b14a2c4 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/index.ts @@ -0,0 +1,9 @@ +export { default as WxAccountSelect } from './wx-account-select/wx-account-select.vue'; +export { default as WxLocation } from './wx-location/wx-location.vue'; +export { default as WxMaterialSelect } from './wx-material-select/wx-material-select.vue'; +export { default as WxMsg } from './wx-msg/msg.vue'; // TODO @hw、@dylan:貌似和 antd 不同。antd 这里是 export { default as WxMsg } from './wx-msg/wx-msg.vue'; 看看哪个是对的 +export { default as WxMusic } from './wx-music/wx-music.vue'; +export { default as WxNews } from './wx-news/wx-news.vue'; +export { default as WxReply } from './wx-reply/wx-reply.vue'; +export { default as WxVideoPlayer } from './wx-video-play/wx-video-play.vue'; +export { default as WxVoicePlayer } from './wx-voice-play/wx-voice-play.vue'; diff --git a/apps/web-ele/src/views/mp/components/wx-account-select/wx-account-select.vue b/apps/web-ele/src/views/mp/components/wx-account-select/wx-account-select.vue new file mode 100644 index 0000000..5a37a5b --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-account-select/wx-account-select.vue @@ -0,0 +1,72 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-location/types.ts b/apps/web-ele/src/views/mp/components/wx-location/types.ts new file mode 100644 index 0000000..9566c2a --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-location/types.ts @@ -0,0 +1,6 @@ +export interface WxLocationProps { + label: string; + locationX: number; + locationY: number; + qqMapKey?: string; +} diff --git a/apps/web-ele/src/views/mp/components/wx-location/wx-location.vue b/apps/web-ele/src/views/mp/components/wx-location/wx-location.vue new file mode 100644 index 0000000..99130bd --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-location/wx-location.vue @@ -0,0 +1,72 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-material-select/wx-material-select.vue b/apps/web-ele/src/views/mp/components/wx-material-select/wx-material-select.vue new file mode 100644 index 0000000..f7e3c38 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-material-select/wx-material-select.vue @@ -0,0 +1,274 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-msg/card.scss b/apps/web-ele/src/views/mp/components/wx-msg/card.scss new file mode 100644 index 0000000..639f4ca --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/card.scss @@ -0,0 +1,116 @@ +.mp-card { + &__item { + box-sizing: border-box; + height: 200px; + margin-bottom: 16px; + font-size: 14px; + font-feature-settings: 'tnum'; + font-variant: tabular-nums; + line-height: 1.5; + color: rgb(0 0 0 / 65%); + cursor: pointer; + list-style: none; + background-color: #fff; + border: 1px solid #e8e8e8; + + &:hover { + border-color: rgb(0 0 0 / 9%); + box-shadow: 0 2px 8px rgb(0 0 0 / 9%); + } + + &--add { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + font-size: 16px; + color: rgb(0 0 0 / 45%); + background-color: #fff; + border: 1px dashed #000; + border-color: #d9d9d9; + border-radius: 2px; + + i { + margin-right: 10px; + } + + &:hover { + color: #40a9ff; + background-color: #fff; + border-color: #40a9ff; + } + } + } + + &__body { + display: flex; + padding: 24px; + } + + &__detail { + flex: 1; + } + + &__avatar { + width: 48px; + height: 48px; + margin-right: 12px; + overflow: hidden; + border-radius: 48px; + + img { + width: 100%; + height: 100%; + } + } + + &__title { + margin-bottom: 12px; + font-size: 16px; + color: rgb(0 0 0 / 85%); + + &:hover { + color: #1890ff; + } + } + + &__info { + display: -webkit-box; + height: 64px; + overflow: hidden; + -webkit-line-clamp: 3; + color: rgb(0 0 0 / 45%); + -webkit-box-orient: vertical; + } + + &__menu { + display: flex; + justify-content: space-around; + height: 50px; + line-height: 50px; + color: rgb(0 0 0 / 45%); + text-align: center; + background: #f7f9fa; + + &:hover { + color: #1890ff; + } + } +} + +/** joolun 额外加的 */ +.mp-comment__main { + flex: unset !important; + margin: 0 8px !important; + border-radius: 5px !important; +} + +.mp-comment__header { + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +.mp-comment__body { + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} diff --git a/apps/web-ele/src/views/mp/components/wx-msg/comment.scss b/apps/web-ele/src/views/mp/components/wx-msg/comment.scss new file mode 100644 index 0000000..98e137d --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/comment.scss @@ -0,0 +1,109 @@ +/* 来自 https://github.com/nmxiaowei/avue/blob/master/styles/src/element-ui/comment.scss */ +.mp-comment { + display: flex; + align-items: flex-start; + margin-bottom: 30px; + + &--reverse { + flex-direction: row-reverse; + + .mp-comment__main { + &::before, + &::after { + right: -8px; + left: auto; + border-width: 8px 0 8px 8px; + } + + &::before { + border-left-color: #dedede; + } + + &::after { + margin-right: 1px; + margin-left: auto; + border-left-color: #f8f8f8; + } + } + } + + &__avatar { + box-sizing: border-box; + width: 48px; + height: 48px; + vertical-align: middle; + border: 1px solid transparent; + border-radius: 50%; + } + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 5px 15px; + background: #f8f8f8; + border-bottom: 1px solid #eee; + } + + &__author { + font-size: 14px; + font-weight: 700; + color: #999; + } + + &__main { + position: relative; + flex: 1; + margin: 0 20px; + border: 1px solid #dedede; + border-radius: 2px; + + &::before, + &::after { + position: absolute; + top: 10px; + right: 100%; + left: -8px; + display: block; + width: 0; + height: 0; + pointer-events: none; + content: ' '; + border-color: transparent; + border-style: solid solid outset; + border-width: 8px 8px 8px 0; + } + + &::before { + z-index: 1; + border-right-color: #dedede; + } + + &::after { + z-index: 2; + margin-left: 1px; + border-right-color: #f8f8f8; + } + } + + &__body { + padding: 15px; + overflow: hidden; + font-family: + 'Segoe UI', 'Lucida Grande', Helvetica, Arial, 'Microsoft YaHei', + FreeSans, Arimo, 'Droid Sans', 'wenquanyi micro hei', 'Hiragino Sans GB', + 'Hiragino Sans GB W3', FontAwesome, sans-serif; + font-size: 14px; + color: #333; + background: #fff; + } + + blockquote { + padding: 1px 0 1px 15px; + margin: 0; + font-family: + Georgia, 'Times New Roman', Times, Kai, 'Kaiti SC', KaiTi, BiauKai, + FontAwesome, serif; + border-left: 4px solid #ddd; + } +} diff --git a/apps/web-ele/src/views/mp/components/wx-msg/msg-event.vue b/apps/web-ele/src/views/mp/components/wx-msg/msg-event.vue new file mode 100644 index 0000000..12b86cd --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/msg-event.vue @@ -0,0 +1,54 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-msg/msg-list.vue b/apps/web-ele/src/views/mp/components/wx-msg/msg-list.vue new file mode 100644 index 0000000..8bed75e --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/msg-list.vue @@ -0,0 +1,82 @@ + + diff --git a/apps/web-ele/src/views/mp/components/wx-msg/msg.vue b/apps/web-ele/src/views/mp/components/wx-msg/msg.vue new file mode 100644 index 0000000..1a9ab50 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/msg.vue @@ -0,0 +1,92 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-msg/wx-msg.vue b/apps/web-ele/src/views/mp/components/wx-msg/wx-msg.vue new file mode 100644 index 0000000..f6ca8c5 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-msg/wx-msg.vue @@ -0,0 +1,176 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-music/types.ts b/apps/web-ele/src/views/mp/components/wx-music/types.ts new file mode 100644 index 0000000..5164f53 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-music/types.ts @@ -0,0 +1,7 @@ +export interface WxMusicProps { + title?: string; + description?: string; + musicUrl?: string; + hqMusicUrl?: string; + thumbMediaUrl: string; +} diff --git a/apps/web-ele/src/views/mp/components/wx-music/wx-music.vue b/apps/web-ele/src/views/mp/components/wx-music/wx-music.vue new file mode 100644 index 0000000..959b940 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-music/wx-music.vue @@ -0,0 +1,54 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-news/wx-news.vue b/apps/web-ele/src/views/mp/components/wx-news/wx-news.vue new file mode 100644 index 0000000..6e52761 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-news/wx-news.vue @@ -0,0 +1,57 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/tab-image.vue b/apps/web-ele/src/views/mp/components/wx-reply/tab-image.vue new file mode 100644 index 0000000..c8700ab --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/tab-image.vue @@ -0,0 +1,158 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/tab-music.vue b/apps/web-ele/src/views/mp/components/wx-reply/tab-music.vue new file mode 100644 index 0000000..728609d --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/tab-music.vue @@ -0,0 +1,146 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/tab-news.vue b/apps/web-ele/src/views/mp/components/wx-reply/tab-news.vue new file mode 100644 index 0000000..80f58e0 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/tab-news.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/tab-text.vue b/apps/web-ele/src/views/mp/components/wx-reply/tab-text.vue new file mode 100644 index 0000000..790f7fa --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/tab-text.vue @@ -0,0 +1,32 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/tab-video.vue b/apps/web-ele/src/views/mp/components/wx-reply/tab-video.vue new file mode 100644 index 0000000..13be729 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/tab-video.vue @@ -0,0 +1,145 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/tab-voice.vue b/apps/web-ele/src/views/mp/components/wx-reply/tab-voice.vue new file mode 100644 index 0000000..3893f94 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/tab-voice.vue @@ -0,0 +1,158 @@ + + diff --git a/apps/web-ele/src/views/mp/components/wx-reply/types.ts b/apps/web-ele/src/views/mp/components/wx-reply/types.ts new file mode 100644 index 0000000..502b4c5 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/types.ts @@ -0,0 +1,42 @@ +import type { Ref } from 'vue'; + +import type { ReplyType } from '@vben/constants'; + +import { unref } from 'vue'; + +export interface Reply { + accountId: number; + articles?: any[]; + content?: null | string; + description?: null | string; + hqMusicUrl?: null | string; + introduction?: null | string; + mediaId?: null | string; + musicUrl?: null | string; + name?: null | string; + thumbMediaId?: null | string; + thumbMediaUrl?: null | string; + title?: null | string; + type: ReplyType; + url?: null | string; +} + +/** 利用旧的 reply[accountId, type] 初始化新的 Reply */ +export function createEmptyReply(old: Ref | Reply): Reply { + return { + accountId: unref(old).accountId, + articles: [], + content: null, + description: null, + hqMusicUrl: null, + introduction: null, + mediaId: null, + musicUrl: null, + name: null, + thumbMediaId: null, + thumbMediaUrl: null, + title: null, + type: unref(old).type, + url: null, + }; +} diff --git a/apps/web-ele/src/views/mp/components/wx-reply/wx-reply.vue b/apps/web-ele/src/views/mp/components/wx-reply/wx-reply.vue new file mode 100644 index 0000000..f80dece --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-reply/wx-reply.vue @@ -0,0 +1,171 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-video-play/wx-video-play.vue b/apps/web-ele/src/views/mp/components/wx-video-play/wx-video-play.vue new file mode 100644 index 0000000..520d2e5 --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-video-play/wx-video-play.vue @@ -0,0 +1,48 @@ + + + diff --git a/apps/web-ele/src/views/mp/components/wx-voice-play/wx-voice-play.vue b/apps/web-ele/src/views/mp/components/wx-voice-play/wx-voice-play.vue new file mode 100644 index 0000000..70b8efa --- /dev/null +++ b/apps/web-ele/src/views/mp/components/wx-voice-play/wx-voice-play.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/mp/draft/data.ts b/apps/web-ele/src/views/mp/draft/data.ts new file mode 100644 index 0000000..1ee1e10 --- /dev/null +++ b/apps/web-ele/src/views/mp/draft/data.ts @@ -0,0 +1,45 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { formatDateTime } from '@vben/utils'; + +/** 获取表格列配置 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'cover', + title: '图片', + width: 360, + slots: { default: 'cover' }, + }, + { + field: 'title', + title: '标题', + slots: { default: 'title' }, + }, + { + field: 'updateTime', + title: '修改时间', + formatter: ({ row }) => { + return formatDateTime(row.updateTime * 1000); + }, + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'accountId', + label: '公众号', + component: 'Input', + }, + ]; +} diff --git a/apps/web-ele/src/views/mp/draft/index.vue b/apps/web-ele/src/views/mp/draft/index.vue new file mode 100644 index 0000000..e6bfa4f --- /dev/null +++ b/apps/web-ele/src/views/mp/draft/index.vue @@ -0,0 +1,267 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/draft/modules/cover-select.vue b/apps/web-ele/src/views/mp/draft/modules/cover-select.vue new file mode 100644 index 0000000..0a4a302 --- /dev/null +++ b/apps/web-ele/src/views/mp/draft/modules/cover-select.vue @@ -0,0 +1,146 @@ + + + diff --git a/apps/web-ele/src/views/mp/draft/modules/form.vue b/apps/web-ele/src/views/mp/draft/modules/form.vue new file mode 100644 index 0000000..e77e8f6 --- /dev/null +++ b/apps/web-ele/src/views/mp/draft/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/mp/draft/modules/news-form.vue b/apps/web-ele/src/views/mp/draft/modules/news-form.vue new file mode 100644 index 0000000..632e385 --- /dev/null +++ b/apps/web-ele/src/views/mp/draft/modules/news-form.vue @@ -0,0 +1,260 @@ + + + diff --git a/apps/web-ele/src/views/mp/freePublish/data.ts b/apps/web-ele/src/views/mp/freePublish/data.ts new file mode 100644 index 0000000..82cfb5f --- /dev/null +++ b/apps/web-ele/src/views/mp/freePublish/data.ts @@ -0,0 +1,59 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridPropTypes } from '#/adapter/vxe-table'; +import type { MpAccountApi } from '#/api/mp/account'; + +import { formatDateTime } from '@vben/utils'; + +import { getSimpleAccountList } from '#/api/mp/account'; + +let accountList: MpAccountApi.Account[] = []; +getSimpleAccountList().then((data) => (accountList = data)); + +/** 搜索表单配置 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'accountId', + label: '公众号', + component: 'Select', + componentProps: { + options: accountList.map((item) => ({ + label: item.name, + value: item.id, + })), + placeholder: '请选择公众号', + clearable: true, + }, + }, + ]; +} + +/** 表格列配置 */ +export function useGridColumns(): VxeGridPropTypes.Columns { + return [ + { + field: 'cover', + title: '图片', + width: 360, + slots: { default: 'cover' }, + }, + { + field: 'title', + title: '标题', + slots: { default: 'title' }, + }, + { + field: 'updateTime', + title: '修改时间', + formatter: ({ row }) => { + return formatDateTime(row.updateTime * 1000); + }, + }, + { + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mp/freePublish/index.vue b/apps/web-ele/src/views/mp/freePublish/index.vue new file mode 100644 index 0000000..fdd6915 --- /dev/null +++ b/apps/web-ele/src/views/mp/freePublish/index.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/menu/assets/iphone_backImg.png b/apps/web-ele/src/views/mp/menu/assets/iphone_backImg.png new file mode 100644 index 0000000..bb09591 Binary files /dev/null and b/apps/web-ele/src/views/mp/menu/assets/iphone_backImg.png differ diff --git a/apps/web-ele/src/views/mp/menu/assets/menu_foot.png b/apps/web-ele/src/views/mp/menu/assets/menu_foot.png new file mode 100644 index 0000000..4a89d4b Binary files /dev/null and b/apps/web-ele/src/views/mp/menu/assets/menu_foot.png differ diff --git a/apps/web-ele/src/views/mp/menu/assets/menu_head.png b/apps/web-ele/src/views/mp/menu/assets/menu_head.png new file mode 100644 index 0000000..248cfb7 Binary files /dev/null and b/apps/web-ele/src/views/mp/menu/assets/menu_head.png differ diff --git a/apps/web-ele/src/views/mp/menu/data.ts b/apps/web-ele/src/views/mp/menu/data.ts new file mode 100644 index 0000000..ddb2bea --- /dev/null +++ b/apps/web-ele/src/views/mp/menu/data.ts @@ -0,0 +1,31 @@ +import type { VbenFormSchema } from '#/adapter/form'; + +import { getSimpleAccountList } from '#/api/mp/account'; + +/** 菜单未选中标识 */ +export const MENU_NOT_SELECTED = '__MENU_NOT_SELECTED__'; + +/** 菜单级别枚举 */ +export enum Level { + Child = '2', + Parent = '1', + Undefined = '0', +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'accountId', + label: '公众号', + component: 'ApiSelect', + componentProps: { + api: getSimpleAccountList, + labelField: 'name', + valueField: 'id', + autoSelect: 'first', + placeholder: '请选择公众号', + }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mp/menu/index.vue b/apps/web-ele/src/views/mp/menu/index.vue new file mode 100644 index 0000000..b4a0ec5 --- /dev/null +++ b/apps/web-ele/src/views/mp/menu/index.vue @@ -0,0 +1,374 @@ + + + diff --git a/apps/web-ele/src/views/mp/menu/modules/editor.vue b/apps/web-ele/src/views/mp/menu/modules/editor.vue new file mode 100644 index 0000000..103b2de --- /dev/null +++ b/apps/web-ele/src/views/mp/menu/modules/editor.vue @@ -0,0 +1,222 @@ + + + diff --git a/apps/web-ele/src/views/mp/menu/modules/previewer.vue b/apps/web-ele/src/views/mp/menu/modules/previewer.vue new file mode 100644 index 0000000..ba7c24c --- /dev/null +++ b/apps/web-ele/src/views/mp/menu/modules/previewer.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/apps/web-ele/src/views/mp/menu/modules/types.ts b/apps/web-ele/src/views/mp/menu/modules/types.ts new file mode 100644 index 0000000..ae0edbd --- /dev/null +++ b/apps/web-ele/src/views/mp/menu/modules/types.ts @@ -0,0 +1,116 @@ +export interface Replay { + title: string; + description: string; + picUrl: string; + url: string; +} + +export type MenuType = + | '' + | 'article_view_limited' + | 'click' + | 'location_select' + | 'pic_photo_or_album' + | 'pic_sysphoto' + | 'pic_weixin' + | 'scancode_push' + | 'scancode_waitmsg' + | 'view'; + +interface _RawMenu { + // db + id: number; + parentId: number; + accountId: number; + appId: string; + createTime: number; + + // mp-native + name: string; + menuKey: string; + type: MenuType; + url: string; + miniProgramAppId: string; + miniProgramPagePath: string; + articleId: string; + replyMessageType: string; + replyContent: string; + replyMediaId: string; + replyMediaUrl: string; + replyThumbMediaId: string; + replyThumbMediaUrl: string; + replyTitle: string; + replyDescription: string; + replyArticles: Replay; + replyMusicUrl: string; + replyHqMusicUrl: string; +} + +export type RawMenu = Partial<_RawMenu>; + +interface _Reply { + type: string; + accountId: number; + content: string; + mediaId: string; + url: string; + thumbMediaId: string; + thumbMediaUrl: string; + title: string; + description: string; + articles: null | Replay[]; + musicUrl: string; + hqMusicUrl: string; +} + +export type Reply = Partial<_Reply>; + +interface _Menu extends RawMenu { + children: _Menu[]; + reply: Reply; +} + +export type Menu = Partial<_Menu>; + +export const menuOptions = [ + { + value: 'view', + label: '跳转网页', + }, + { + value: 'miniprogram', + label: '跳转小程序', + }, + { + value: 'click', + label: '点击回复', + }, + { + value: 'article_view_limited', + label: '跳转图文消息', + }, + { + value: 'scancode_push', + label: '扫码直接返回结果', + }, + { + value: 'scancode_waitmsg', + label: '扫码回复', + }, + { + value: 'pic_sysphoto', + label: '系统拍照发图', + }, + { + value: 'pic_photo_or_album', + label: '拍照或者相册', + }, + { + value: 'pic_weixin', + label: '微信相册', + }, + { + value: 'location_select', + label: '选择地理位置', + }, +]; diff --git a/apps/web-ele/src/views/mp/messageTemplate/data.ts b/apps/web-ele/src/views/mp/messageTemplate/data.ts new file mode 100644 index 0000000..8dadf55 --- /dev/null +++ b/apps/web-ele/src/views/mp/messageTemplate/data.ts @@ -0,0 +1,143 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridPropTypes } from '#/adapter/vxe-table'; + +import { getUserPage } from '#/api/mp/user'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'accountId', + label: '公众号', + component: 'Input', + }, + ]; +} + +/** 发送消息模板表单 */ +export function useSendFormSchema(accountId?: number): VbenFormSchema[] { + return [ + { + fieldName: 'id', + label: '模板编号', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'title', + label: '模板标题', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'userId', + label: '用户', + component: 'ApiSelect', + componentProps: { + api: async () => { + if (!accountId) { + return []; + } + const data = await getUserPage({ + pageNo: 1, + pageSize: 100, + accountId, + }); + return (data.list || []).map((user) => ({ + label: user.nickname || user.openid, + value: user.id, + })); + }, + filterable: true, + placeholder: '请选择用户', + }, + rules: 'required', + }, + { + fieldName: 'data', + label: '模板数据', + component: 'Textarea', + componentProps: { + rows: 4, + placeholder: + '请输入模板数据(JSON 格式),例如:{"keyword1": {"value": "测试内容"}}', + }, + }, + { + fieldName: 'url', + label: '跳转链接', + component: 'Input', + componentProps: { + placeholder: '请输入跳转链接', + }, + }, + { + fieldName: 'miniProgramAppId', + label: '小程序 appId', + component: 'Input', + componentProps: { + placeholder: '请输入小程序 appId', + }, + }, + { + fieldName: 'miniProgramPagePath', + label: '小程序页面路径', + component: 'Input', + componentProps: { + placeholder: '请输入小程序页面路径', + }, + }, + ]; +} + +/** 表格列配置 */ +export function useGridColumns(): VxeGridPropTypes.Columns { + return [ + { + title: '公众号模板 ID', + field: 'templateId', + minWidth: 400, + }, + { + title: '标题', + field: 'title', + minWidth: 150, + }, + { + title: '模板内容', + field: 'content', + minWidth: 400, + }, + { + title: '模板示例', + field: 'example', + minWidth: 200, + }, + { + title: '一级行业', + field: 'primaryIndustry', + minWidth: 120, + }, + { + title: '二级行业', + field: 'deputyIndustry', + minWidth: 120, + }, + { + title: '创建时间', + field: 'createTime', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + title: '操作', + width: 140, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mp/messageTemplate/index.vue b/apps/web-ele/src/views/mp/messageTemplate/index.vue new file mode 100644 index 0000000..67405fb --- /dev/null +++ b/apps/web-ele/src/views/mp/messageTemplate/index.vue @@ -0,0 +1,165 @@ + + + diff --git a/apps/web-ele/src/views/mp/messageTemplate/modules/send-form.vue b/apps/web-ele/src/views/mp/messageTemplate/modules/send-form.vue new file mode 100644 index 0000000..ef809aa --- /dev/null +++ b/apps/web-ele/src/views/mp/messageTemplate/modules/send-form.vue @@ -0,0 +1,103 @@ + + + diff --git a/apps/web-ele/src/views/mp/modules/wx-msg/card.scss b/apps/web-ele/src/views/mp/modules/wx-msg/card.scss new file mode 100644 index 0000000..639f4ca --- /dev/null +++ b/apps/web-ele/src/views/mp/modules/wx-msg/card.scss @@ -0,0 +1,116 @@ +.mp-card { + &__item { + box-sizing: border-box; + height: 200px; + margin-bottom: 16px; + font-size: 14px; + font-feature-settings: 'tnum'; + font-variant: tabular-nums; + line-height: 1.5; + color: rgb(0 0 0 / 65%); + cursor: pointer; + list-style: none; + background-color: #fff; + border: 1px solid #e8e8e8; + + &:hover { + border-color: rgb(0 0 0 / 9%); + box-shadow: 0 2px 8px rgb(0 0 0 / 9%); + } + + &--add { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + font-size: 16px; + color: rgb(0 0 0 / 45%); + background-color: #fff; + border: 1px dashed #000; + border-color: #d9d9d9; + border-radius: 2px; + + i { + margin-right: 10px; + } + + &:hover { + color: #40a9ff; + background-color: #fff; + border-color: #40a9ff; + } + } + } + + &__body { + display: flex; + padding: 24px; + } + + &__detail { + flex: 1; + } + + &__avatar { + width: 48px; + height: 48px; + margin-right: 12px; + overflow: hidden; + border-radius: 48px; + + img { + width: 100%; + height: 100%; + } + } + + &__title { + margin-bottom: 12px; + font-size: 16px; + color: rgb(0 0 0 / 85%); + + &:hover { + color: #1890ff; + } + } + + &__info { + display: -webkit-box; + height: 64px; + overflow: hidden; + -webkit-line-clamp: 3; + color: rgb(0 0 0 / 45%); + -webkit-box-orient: vertical; + } + + &__menu { + display: flex; + justify-content: space-around; + height: 50px; + line-height: 50px; + color: rgb(0 0 0 / 45%); + text-align: center; + background: #f7f9fa; + + &:hover { + color: #1890ff; + } + } +} + +/** joolun 额外加的 */ +.mp-comment__main { + flex: unset !important; + margin: 0 8px !important; + border-radius: 5px !important; +} + +.mp-comment__header { + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +.mp-comment__body { + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} diff --git a/apps/web-ele/src/views/mp/modules/wx-msg/comment.scss b/apps/web-ele/src/views/mp/modules/wx-msg/comment.scss new file mode 100644 index 0000000..98e137d --- /dev/null +++ b/apps/web-ele/src/views/mp/modules/wx-msg/comment.scss @@ -0,0 +1,109 @@ +/* 来自 https://github.com/nmxiaowei/avue/blob/master/styles/src/element-ui/comment.scss */ +.mp-comment { + display: flex; + align-items: flex-start; + margin-bottom: 30px; + + &--reverse { + flex-direction: row-reverse; + + .mp-comment__main { + &::before, + &::after { + right: -8px; + left: auto; + border-width: 8px 0 8px 8px; + } + + &::before { + border-left-color: #dedede; + } + + &::after { + margin-right: 1px; + margin-left: auto; + border-left-color: #f8f8f8; + } + } + } + + &__avatar { + box-sizing: border-box; + width: 48px; + height: 48px; + vertical-align: middle; + border: 1px solid transparent; + border-radius: 50%; + } + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 5px 15px; + background: #f8f8f8; + border-bottom: 1px solid #eee; + } + + &__author { + font-size: 14px; + font-weight: 700; + color: #999; + } + + &__main { + position: relative; + flex: 1; + margin: 0 20px; + border: 1px solid #dedede; + border-radius: 2px; + + &::before, + &::after { + position: absolute; + top: 10px; + right: 100%; + left: -8px; + display: block; + width: 0; + height: 0; + pointer-events: none; + content: ' '; + border-color: transparent; + border-style: solid solid outset; + border-width: 8px 8px 8px 0; + } + + &::before { + z-index: 1; + border-right-color: #dedede; + } + + &::after { + z-index: 2; + margin-left: 1px; + border-right-color: #f8f8f8; + } + } + + &__body { + padding: 15px; + overflow: hidden; + font-family: + 'Segoe UI', 'Lucida Grande', Helvetica, Arial, 'Microsoft YaHei', + FreeSans, Arimo, 'Droid Sans', 'wenquanyi micro hei', 'Hiragino Sans GB', + 'Hiragino Sans GB W3', FontAwesome, sans-serif; + font-size: 14px; + color: #333; + background: #fff; + } + + blockquote { + padding: 1px 0 1px 15px; + margin: 0; + font-family: + Georgia, 'Times New Roman', Times, Kai, 'Kaiti SC', KaiTi, BiauKai, + FontAwesome, serif; + border-left: 4px solid #ddd; + } +} diff --git a/apps/web-ele/src/views/mp/statistics/chart-options.ts b/apps/web-ele/src/views/mp/statistics/chart-options.ts new file mode 100644 index 0000000..700cb34 --- /dev/null +++ b/apps/web-ele/src/views/mp/statistics/chart-options.ts @@ -0,0 +1,163 @@ +import type { MpStatisticsApi } from '#/api/mp/statistics'; + +/** 用户增减数据图表配置项 */ +export function userSummaryOption( + data: MpStatisticsApi.StatisticsUserSummaryRespVO[], + dates: string[], +): any { + return { + color: ['#67C23A', '#E5323E'], + legend: { + data: ['新增用户', '取消关注的用户'], + }, + tooltip: {}, + xAxis: { + data: dates, + }, + yAxis: { + minInterval: 1, + }, + series: [ + { + name: '新增用户', + type: 'bar', + label: { + show: true, + }, + barGap: 0, + data: data.map((item) => item.newUser), // 新增用户的数据 + }, + { + name: '取消关注的用户', + type: 'bar', + label: { + show: true, + }, + data: data.map((item) => item.cancelUser), // 取消关注的用户的数据 + }, + ], + }; +} + +/** 累计用户数据图表配置项 */ +export function userCumulateOption( + data: MpStatisticsApi.StatisticsUserCumulateRespVO[], + dates: string[], +): any { + return { + legend: { + data: ['累计用户量'], + }, + xAxis: { + type: 'category', + data: dates, + }, + yAxis: { + minInterval: 1, + }, + series: [ + { + name: '累计用户量', + data: data.map((item) => item.cumulateUser), // 累计用户量的数据 + type: 'line', + smooth: true, + label: { + show: true, + }, + }, + ], + }; +} + +/** 消息发送概况数据图表配置项 */ +export function upstreamMessageOption( + data: MpStatisticsApi.StatisticsUpstreamMessageRespVO[], + dates: string[], +): any { + return { + color: ['#67C23A', '#E5323E'], + legend: { + data: ['用户发送人数', '用户发送条数'], + }, + tooltip: {}, + xAxis: { + data: dates, // X 轴的日期范围 + }, + yAxis: { + minInterval: 1, + }, + series: [ + { + name: '用户发送人数', + type: 'line', + smooth: true, + label: { + show: true, + }, + data: data.map((item) => item.msgUser), // 用户发送人数的数据 + }, + { + name: '用户发送条数', + type: 'line', + smooth: true, + label: { + show: true, + }, + data: data.map((item) => item.msgCount), // 用户发送条数的数据 + }, + ], + }; +} + +/** 接口分析况数据图表配置项 */ +export function interfaceSummaryOption( + data: MpStatisticsApi.StatisticsInterfaceSummaryRespVO[], + dates: string[], +): any { + return { + color: ['#67C23A', '#E5323E', '#E6A23C', '#409EFF'], + legend: { + data: ['被动回复用户消息的次数', '失败次数', '最大耗时', '总耗时'], + }, + tooltip: {}, + xAxis: { + data: dates, // X 轴的日期范围 + }, + yAxis: {}, + series: [ + { + name: '被动回复用户消息的次数', + type: 'bar', + label: { + show: true, + }, + barGap: 0, + data: data.map((item) => item.callbackCount), // 被动回复用户消息的次数的数据 + }, + { + name: '失败次数', + type: 'bar', + label: { + show: true, + }, + data: data.map((item) => item.failCount), // 失败次数的数据 + }, + { + name: '最大耗时', + type: 'bar', + label: { + show: true, + }, + data: data.map((item) => item.maxTimeCost), // 最大耗时的数据 + }, + { + name: '总耗时', + type: 'bar', + label: { + show: true, + }, + data: data.map((item) => item.totalTimeCost), // 总耗时的数据 + }, + ], + }; +} diff --git a/apps/web-ele/src/views/mp/statistics/data.ts b/apps/web-ele/src/views/mp/statistics/data.ts new file mode 100644 index 0000000..b1e34ef --- /dev/null +++ b/apps/web-ele/src/views/mp/statistics/data.ts @@ -0,0 +1,27 @@ +import type { VbenFormSchema } from '#/adapter/form'; + +import { beginOfDay, endOfDay, formatDateTime } from '@vben/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'accountId', + label: '公众号', + component: 'Input', + }, + { + fieldName: 'dateRange', + label: '时间范围', + component: 'RangePicker', + componentProps: { + format: 'YYYY-MM-DD', + valueFormat: 'YYYY-MM-DD HH:mm:ss', + }, + defaultValue: [ + formatDateTime(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))), + formatDateTime(endOfDay(new Date(Date.now() - 3600 * 1000 * 24))), + ], + }, + ]; +} diff --git a/apps/web-ele/src/views/mp/statistics/index.vue b/apps/web-ele/src/views/mp/statistics/index.vue new file mode 100644 index 0000000..180ff6d --- /dev/null +++ b/apps/web-ele/src/views/mp/statistics/index.vue @@ -0,0 +1,157 @@ + + + diff --git a/apps/web-ele/src/views/mp/tag/data.ts b/apps/web-ele/src/views/mp/tag/data.ts new file mode 100644 index 0000000..ffce47f --- /dev/null +++ b/apps/web-ele/src/views/mp/tag/data.ts @@ -0,0 +1,78 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeGridPropTypes } from '#/adapter/vxe-table'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'accountId', + label: '公众号', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '标签名称', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入名称', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'accountId', + label: '公众号', + component: 'Input', + }, + ]; +} + +/** 表格列配置 */ +export function useGridColumns(): VxeGridPropTypes.Columns { + return [ + { + title: '编号', + field: 'id', + minWidth: 80, + }, + { + title: '标签名称', + field: 'name', + minWidth: 150, + }, + { + title: '粉丝数', + field: 'count', + minWidth: 100, + }, + { + title: '创建时间', + field: 'createTime', + formatter: 'formatDateTime', + minWidth: 180, + }, + { + title: '操作', + width: 140, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mp/tag/index.vue b/apps/web-ele/src/views/mp/tag/index.vue new file mode 100644 index 0000000..8ae00b5 --- /dev/null +++ b/apps/web-ele/src/views/mp/tag/index.vue @@ -0,0 +1,177 @@ + + + diff --git a/apps/web-ele/src/views/mp/tag/modules/form.vue b/apps/web-ele/src/views/mp/tag/modules/form.vue new file mode 100644 index 0000000..ca1bf4a --- /dev/null +++ b/apps/web-ele/src/views/mp/tag/modules/form.vue @@ -0,0 +1,83 @@ + + + diff --git a/apps/web-ele/src/views/mp/user/data.ts b/apps/web-ele/src/views/mp/user/data.ts new file mode 100644 index 0000000..2935544 --- /dev/null +++ b/apps/web-ele/src/views/mp/user/data.ts @@ -0,0 +1,124 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +/** 修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'nickname', + label: '昵称', + component: 'Input', + componentProps: { + placeholder: '请输入昵称', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'accountId', + label: '公众号', + component: 'Input', + }, + { + fieldName: 'openid', + label: '用户标识', + component: 'Input', + componentProps: { + placeholder: '请输入用户标识', + clearable: true, + }, + }, + { + fieldName: 'nickname', + label: '昵称', + component: 'Input', + componentProps: { + placeholder: '请输入昵称', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'openid', + title: '用户标识', + minWidth: 260, + }, + { + field: 'headImageUrl', + title: '用户头像', + minWidth: 80, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'nickname', + title: '昵称', + minWidth: 120, + }, + { + field: 'remark', + title: '备注', + minWidth: 120, + }, + { + field: 'tagIds', + title: '标签', + minWidth: 200, + cellRender: { + name: 'CellTags', + }, + }, + { + field: 'subscribeStatus', + title: '订阅状态', + minWidth: 100, + align: 'center', + formatter: ({ cellValue }) => { + return cellValue === 0 ? '已订阅' : '未订阅'; + }, + }, + { + field: 'subscribeTime', + title: '订阅时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/mp/user/index.vue b/apps/web-ele/src/views/mp/user/index.vue new file mode 100644 index 0000000..42212f8 --- /dev/null +++ b/apps/web-ele/src/views/mp/user/index.vue @@ -0,0 +1,137 @@ + + + diff --git a/apps/web-ele/src/views/mp/user/modules/form.vue b/apps/web-ele/src/views/mp/user/modules/form.vue new file mode 100644 index 0000000..dc1c687 --- /dev/null +++ b/apps/web-ele/src/views/mp/user/modules/form.vue @@ -0,0 +1,80 @@ + + + diff --git a/apps/web-ele/src/views/pay/app/data.ts b/apps/web-ele/src/views/pay/app/data.ts new file mode 100644 index 0000000..8ee1919 --- /dev/null +++ b/apps/web-ele/src/views/pay/app/data.ts @@ -0,0 +1,664 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { PayAppApi } from '#/api/pay/app'; + +import { h } from 'vue'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { InputUpload } from '#/components/upload'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '应用名', + component: 'Input', + componentProps: { + placeholder: '请输入应用名', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '开启状态', + component: 'Select', + componentProps: { + placeholder: '请选择开启状态', + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onStatusChange?: ( + newStatus: number, + row: PayAppApi.App, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { + field: 'appKey', + title: '应用标识', + minWidth: 40, + }, + { + field: 'name', + title: '应用名', + minWidth: 40, + }, + { + field: 'status', + title: '状态', + align: 'center', + minWidth: 40, + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: 'CellSwitch', + props: { + activeValue: CommonStatusEnum.ENABLE, + inactiveValue: CommonStatusEnum.DISABLE, + }, + }, + }, + { + title: '支付宝配置', + children: [ + { + title: 'APP', + slots: { + default: 'alipayAppConfig', + }, + }, + { + title: 'PC 网站', + slots: { + default: 'alipayPCConfig', + }, + }, + { + title: 'WAP 网站', + slots: { + default: 'alipayWAPConfig', + }, + minWidth: 10, + }, + { + title: '扫码', + slots: { + default: 'alipayQrConfig', + }, + }, + { + title: '条码', + slots: { + default: 'alipayBarConfig', + }, + }, + ], + }, + { + title: '微信配置', + children: [ + { + title: '小程序', + slots: { + default: 'wxLiteConfig', + }, + }, + { + title: 'JSAPI', + slots: { + default: 'wxPubConfig', + }, + }, + { + title: 'APP', + slots: { + default: 'wxAppConfig', + }, + }, + { + title: 'Native', + slots: { + default: 'wxNativeConfig', + }, + }, + { + title: 'WAP 网站', + slots: { + default: 'wxWapConfig', + }, + minWidth: 10, + }, + { + title: '条码', + slots: { + default: 'wxBarConfig', + }, + }, + ], + }, + { + title: '钱包支付配置', + field: 'walletConfig', + slots: { + default: 'walletConfig', + }, + }, + { + title: '模拟支付配置', + field: 'mockConfig', + slots: { + default: 'mockConfig', + }, + }, + { + title: '操作', + width: 140, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 应用新增/修改的表单 */ +export function useAppFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '应用名', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入应用名', + }, + }, + { + fieldName: 'appKey', + label: '应用标识', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入应用标识', + }, + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + rules: z.number().default(CommonStatusEnum.ENABLE), + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + { + fieldName: 'orderNotifyUrl', + label: '支付结果的回调地址', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入支付结果的回调地址', + }, + }, + { + fieldName: 'refundNotifyUrl', + label: '退款结果的回调地址', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入退款结果的回调地址', + }, + }, + { + fieldName: 'transferNotifyUrl', + label: '转账结果的回调地址', + component: 'Input', + componentProps: { + placeholder: '请输入转账结果的回调地址', + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + rows: 3, + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 渠道新增/修改的表单 */ +export function useChannelFormSchema(formType: string = ''): VbenFormSchema[] { + const schema: VbenFormSchema[] = [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + label: '应用编号', + fieldName: 'appId', + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + }, + { + label: '渠道编码', + fieldName: 'code', + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + }, + { + label: '渠道费率', + fieldName: 'feeRate', + component: 'InputNumber', + rules: 'required', + componentProps: { + placeholder: '请输入渠道费率', + addonAfter: '%', + controlsPosition: 'right', + class: '!w-full', + }, + defaultValue: 0, + }, + { + label: '渠道状态', + fieldName: 'status', + component: 'RadioGroup', + rules: z.number().default(CommonStatusEnum.ENABLE), + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + ]; + // 添加通用字段 + // 根据类型添加特定字段 + if (formType.includes('alipay_')) { + schema.push( + { + label: '开放平台 APPID', + fieldName: 'config.appId', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入开放平台 APPID', + }, + }, + { + label: '网关地址', + fieldName: 'config.serverUrl', + component: 'RadioGroup', + rules: 'required', + componentProps: { + options: [ + { + value: 'https://openapi.alipay.com/gateway.do', + label: '线上环境', + }, + { + value: 'https://openapi-sandbox.dl.alipaydev.com/gateway.do', + label: '沙箱环境', + }, + ], + }, + }, + { + label: '算法类型', + fieldName: 'config.signType', + component: 'RadioGroup', + rules: 'required', + componentProps: { + options: [ + { + value: 'RSA2', + label: 'RSA2', + }, + ], + }, + defaultValue: 'RSA2', + }, + { + label: '公钥类型', + fieldName: 'config.mode', + component: 'RadioGroup', + rules: 'required', + componentProps: { + options: [ + { + value: 1, + label: '公钥模式', + }, + { + value: 2, + label: '证书模式', + }, + ], + }, + }, + { + label: '应用私钥', + fieldName: 'config.privateKey', + component: 'Textarea', + rules: 'required', + componentProps: { + placeholder: '请输入应用私钥', + rows: 3, + }, + }, + { + label: '支付宝公钥', + fieldName: 'config.alipayPublicKey', + component: 'Textarea', + rules: 'required', + componentProps: { + placeholder: '请输入支付宝公钥', + rows: 3, + }, + dependencies: { + show(values: any) { + return values?.config?.mode === 1; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '商户公钥应用证书', + fieldName: 'config.appCertContent', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { + rows: 3, + placeholder: '请上传商户公钥应用证书', + }, + fileUploadProps: { + accept: ['crt'], + }, + }), + rules: 'required', + dependencies: { + show(values: any) { + return values?.config?.mode === 2; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '支付宝公钥证书', + fieldName: 'config.alipayPublicCertContent', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { rows: 3, placeholder: '请上传支付宝公钥证书' }, + fileUploadProps: { + accept: ['crt'], + }, + }), + rules: 'required', + dependencies: { + show(values: any) { + return values?.config?.mode === 2; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '根证书', + fieldName: 'config.rootCertContent', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { rows: 3, placeholder: '请上传根证书' }, + fileUploadProps: { + accept: ['crt'], + }, + }), + rules: 'required', + dependencies: { + show(values: any) { + return values?.config?.mode === 2; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '接口内容加密方式', + fieldName: 'config.encryptType', + component: 'RadioGroup', + rules: 'required', + componentProps: { + options: [ + { + value: 'NONE', + label: '无加密', + }, + { + value: 'AES', + label: 'AES', + }, + ], + }, + defaultValue: 'NONE', + }, + { + label: '接口内容加密密钥', + fieldName: 'config.encryptKey', + component: 'Input', + rules: 'required', + dependencies: { + show(values: any) { + return values?.config?.encryptType === 'AES'; + }, + triggerFields: ['config.encryptType', 'encryptType', 'config'], + }, + }, + ); + } else if (formType.includes('wx_')) { + schema.push( + { + label: '微信 APPID', + fieldName: 'config.appId', + help: '前往微信商户平台[https://pay.weixin.qq.com/index.php/extend/merchant_appid/mapay_platform/account_manage]查看 APPID', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入微信 APPID', + }, + }, + { + label: '商户号', + fieldName: 'config.mchId', + help: '前往微信商户平台[https://pay.weixin.qq.com/index.php/extend/pay_setting]查看商户号', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入商户号', + }, + }, + { + label: 'API 版本', + fieldName: 'config.apiVersion', + component: 'RadioGroup', + rules: 'required', + componentProps: { + options: [ + { + label: 'v2', + value: 'v2', + }, + { + label: 'v3', + value: 'v3', + }, + ], + }, + }, + { + label: '商户密钥', + fieldName: 'config.mchKey', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入商户密钥', + }, + dependencies: { + show(values: any) { + return values?.config?.apiVersion === 'v2'; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: 'apiclient_cert.p12 证书', + fieldName: 'config.keyContent', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { + rows: 3, + placeholder: '请上传 apiclient_cert.p12 证书', + }, + fileUploadProps: { + accept: ['p12'], + }, + }), + rules: 'required', + dependencies: { + show(values: any) { + return values?.config?.apiVersion === 'v2'; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: 'API V3 密钥', + fieldName: 'config.apiV3Key', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入 API V3 密钥', + }, + dependencies: { + show(values: any) { + return values?.config?.apiVersion === 'v3'; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: 'apiclient_key.pem 证书', + fieldName: 'config.privateKeyContent', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { + rows: 3, + placeholder: '请上传 apiclient_key.pem 证书', + }, + fileUploadProps: { + accept: ['pem'], + }, + }), + rules: 'required', + dependencies: { + show(values: any) { + return values?.config?.apiVersion === 'v3'; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '证书序列号', + fieldName: 'config.certSerialNo', + component: 'Input', + help: '前往微信商户平台[https://pay.weixin.qq.com/index.php/core/cert/api_cert#/api-cert-manage]查看证书序列号', + rules: 'required', + componentProps: { + placeholder: '请输入证书序列号', + }, + dependencies: { + show(values: any) { + return values?.config?.apiVersion === 'v3'; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: 'public_key.pem 证书', + fieldName: 'config.publicKeyContent', + component: h(InputUpload, { + inputType: 'textarea', + textareaProps: { + rows: 3, + placeholder: '请上传 public_key.pem 证书', + }, + fileUploadProps: { + accept: ['pem'], + }, + }), + dependencies: { + show(values: any) { + return values?.config?.apiVersion === 'v3'; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + { + label: '公钥 ID', + fieldName: 'config.publicKeyId', + component: 'Input', + help: '微信支付公钥产品简介及使用说明[https://pay.weixin.qq.com/doc/v3/merchant/4012153196]', + rules: 'required', + componentProps: { + placeholder: '请输入公钥 ID', + }, + dependencies: { + show(values: any) { + return values?.config?.apiVersion === 'v3'; + }, + triggerFields: ['config.mode', 'mode', 'config'], + }, + }, + ); + } + // 添加备注字段(所有类型都有) + schema.push({ + label: '备注', + fieldName: 'remark', + component: 'Input', + componentProps: { + placeholder: '请输入备注', + }, + }); + return schema; +} diff --git a/apps/web-ele/src/views/pay/app/index.vue b/apps/web-ele/src/views/pay/app/index.vue new file mode 100644 index 0000000..57920fe --- /dev/null +++ b/apps/web-ele/src/views/pay/app/index.vue @@ -0,0 +1,262 @@ + + + diff --git a/apps/web-ele/src/views/pay/app/modules/app-form.vue b/apps/web-ele/src/views/pay/app/modules/app-form.vue new file mode 100644 index 0000000..0f41f2c --- /dev/null +++ b/apps/web-ele/src/views/pay/app/modules/app-form.vue @@ -0,0 +1,83 @@ + + diff --git a/apps/web-ele/src/views/pay/app/modules/channel-form.vue b/apps/web-ele/src/views/pay/app/modules/channel-form.vue new file mode 100644 index 0000000..7070692 --- /dev/null +++ b/apps/web-ele/src/views/pay/app/modules/channel-form.vue @@ -0,0 +1,139 @@ + + + diff --git a/apps/web-ele/src/views/pay/cashier/data.ts b/apps/web-ele/src/views/pay/cashier/data.ts new file mode 100644 index 0000000..eb0cec9 --- /dev/null +++ b/apps/web-ele/src/views/pay/cashier/data.ts @@ -0,0 +1,83 @@ +import { + SvgAlipayAppIcon, + SvgAlipayBarIcon, + SvgAlipayPcIcon, + SvgAlipayQrIcon, + SvgAlipayWapIcon, + SvgMockIcon, + SvgWalletIcon, + SvgWxAppIcon, + SvgWxBarIcon, + SvgWxLiteIcon, + SvgWxNativeIcon, + SvgWxPubIcon, +} from '@vben/icons'; + +export const channelsAlipay = [ + { + name: '支付宝 PC 网站支付', + icon: SvgAlipayPcIcon, + code: 'alipay_pc', + }, + { + name: '支付宝 Wap 网站支付', + icon: SvgAlipayWapIcon, + code: 'alipay_wap', + }, + { + name: '支付宝 App 网站支付', + icon: SvgAlipayAppIcon, + code: 'alipay_app', + }, + { + name: '支付宝扫码支付', + icon: SvgAlipayQrIcon, + code: 'alipay_qr', + }, + { + name: '支付宝条码支付', + icon: SvgAlipayBarIcon, + code: 'alipay_bar', + }, +]; + +export const channelsWechat = [ + { + name: '微信公众号支付', + icon: SvgWxPubIcon, + code: 'wx_pub', + }, + { + name: '微信小程序支付', + icon: SvgWxLiteIcon, + code: 'wx_lite', + }, + { + name: '微信 App 支付', + icon: SvgWxAppIcon, + code: 'wx_app', + }, + { + name: '微信扫码支付', + icon: SvgWxNativeIcon, + code: 'wx_native', + }, + { + name: '微信条码支付', + icon: SvgWxBarIcon, + code: 'wx_bar', + }, +]; + +export const channelsMock = [ + { + name: '钱包支付', + icon: SvgWalletIcon, + code: 'wallet', + }, + { + name: '模拟支付', + icon: SvgMockIcon, + code: 'mock', + }, +]; diff --git a/apps/web-ele/src/views/pay/cashier/index.vue b/apps/web-ele/src/views/pay/cashier/index.vue new file mode 100644 index 0000000..ed5d450 --- /dev/null +++ b/apps/web-ele/src/views/pay/cashier/index.vue @@ -0,0 +1,397 @@ + + diff --git a/apps/web-ele/src/views/pay/demo/order/data.ts b/apps/web-ele/src/views/pay/demo/order/data.ts new file mode 100644 index 0000000..b88fba3 --- /dev/null +++ b/apps/web-ele/src/views/pay/demo/order/data.ts @@ -0,0 +1,115 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { formatDateTime } from '@vben/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'spuId', + label: '商品', + component: 'Select', + componentProps: { + options: [ + { label: '华为手机 --- 1.00元', value: 1 }, + { label: '小米电视 --- 10.00元', value: 2 }, + { label: '苹果手表 --- 100.00元', value: 3 }, + { label: '华硕笔记本 --- 1000.00元', value: 4 }, + { label: '蔚来汽车 --- 200000.00元', value: 5 }, + ], + placeholder: '请选择下单商品', + clearable: true, + }, + rules: 'required', + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '订单编号', + minWidth: 100, + }, + { + field: 'userId', + title: '用户编号', + minWidth: 100, + }, + { + field: 'spuName', + title: '商品名字', + minWidth: 150, + }, + { + field: 'price', + title: '支付价格', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'refundPrice', + title: '退款金额', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'payOrderId', + title: '支付单号', + minWidth: 120, + }, + { + field: 'payStatus', + title: '是否支付', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'payTime', + title: '支付时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'refundTime', + title: '退款时间', + minWidth: 180, + formatter: ({ cellValue, row }) => { + if (cellValue) { + return formatDateTime(cellValue) as string; + } + if (row.payRefundId) { + return '退款中,等待退款结果'; + } + return ''; + }, + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/pay/demo/order/index.vue b/apps/web-ele/src/views/pay/demo/order/index.vue new file mode 100644 index 0000000..cbfee2b --- /dev/null +++ b/apps/web-ele/src/views/pay/demo/order/index.vue @@ -0,0 +1,148 @@ + + + diff --git a/apps/web-ele/src/views/pay/demo/order/modules/form.vue b/apps/web-ele/src/views/pay/demo/order/modules/form.vue new file mode 100644 index 0000000..36b1b9d --- /dev/null +++ b/apps/web-ele/src/views/pay/demo/order/modules/form.vue @@ -0,0 +1,55 @@ + + + diff --git a/apps/web-ele/src/views/pay/demo/withdraw/data.ts b/apps/web-ele/src/views/pay/demo/withdraw/data.ts new file mode 100644 index 0000000..d2622c0 --- /dev/null +++ b/apps/web-ele/src/views/pay/demo/withdraw/data.ts @@ -0,0 +1,168 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'subject', + label: '提现标题', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入提现标题', + }, + }, + { + fieldName: 'price', + label: '提现金额', + component: 'InputNumber', + rules: 'required', + componentProps: { + min: 1, + precision: 2, + step: 0.01, + placeholder: '请输入提现金额', + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'type', + label: '提现类型', + component: 'Select', + rules: 'required', + componentProps: { + options: [ + { label: '支付宝', value: 1 }, + { label: '微信余额', value: 2 }, + { label: '钱包余额', value: 3 }, + ], + placeholder: '请选择提现类型', + }, + }, + { + fieldName: 'userAccount', + label: '收款人账号', + component: 'Input', + rules: 'required', + dependencies: { + triggerFields: ['type'], + componentProps: (values) => { + const type = values.type; + let placeholder = '请输入收款人账号'; + switch (type) { + case 1: { + placeholder = '请输入支付宝账号'; + break; + } + case 2: { + placeholder = '请输入微信 openid'; + break; + } + case 3: { + placeholder = '请输入钱包编号'; + break; + } + } + return { + placeholder, + }; + }, + }, + }, + { + fieldName: 'userName', + label: '收款人姓名', + component: 'Input', + componentProps: { + placeholder: '请输入收款人姓名', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '提现单编号', + minWidth: 100, + }, + { + field: 'subject', + title: '提现标题', + minWidth: 150, + }, + { + field: 'type', + title: '提现类型', + minWidth: 100, + slots: { default: 'type' }, + }, + { + field: 'price', + title: '提现金额', + minWidth: 100, + formatter: 'formatAmount2', + }, + { + field: 'userName', + title: '收款人姓名', + minWidth: 120, + }, + { + field: 'userAccount', + title: '收款人账号', + minWidth: 150, + }, + { + field: 'status', + title: '提现状态', + minWidth: 100, + slots: { default: 'status' }, + }, + { + field: 'payTransferId', + title: '转账单号', + minWidth: 120, + }, + { + field: 'transferChannelCode', + title: '转账渠道', + minWidth: 130, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PAY_CHANNEL_CODE }, + }, + }, + { + field: 'transferTime', + title: '转账时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'transferErrorMsg', + title: '转账失败原因', + minWidth: 150, + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/pay/demo/withdraw/index.vue b/apps/web-ele/src/views/pay/demo/withdraw/index.vue new file mode 100644 index 0000000..6357705 --- /dev/null +++ b/apps/web-ele/src/views/pay/demo/withdraw/index.vue @@ -0,0 +1,145 @@ + + + diff --git a/apps/web-ele/src/views/pay/demo/withdraw/modules/form.vue b/apps/web-ele/src/views/pay/demo/withdraw/modules/form.vue new file mode 100644 index 0000000..2370988 --- /dev/null +++ b/apps/web-ele/src/views/pay/demo/withdraw/modules/form.vue @@ -0,0 +1,55 @@ + + + diff --git a/apps/web-ele/src/views/pay/notify/data.ts b/apps/web-ele/src/views/pay/notify/data.ts new file mode 100644 index 0000000..5917b41 --- /dev/null +++ b/apps/web-ele/src/views/pay/notify/data.ts @@ -0,0 +1,271 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { getAppList } from '#/api/pay/app'; +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'appId', + label: '应用编号', + component: 'ApiSelect', + componentProps: { + api: getAppList, + labelField: 'name', + valueField: 'id', + autoSelect: 'first', + placeholder: '请选择应用编号', + }, + }, + { + fieldName: 'type', + label: '通知类型', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.PAY_NOTIFY_TYPE, 'number'), + placeholder: '请选择通知类型', + }, + }, + { + fieldName: 'dataId', + label: '关联编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入关联编号', + }, + }, + { + fieldName: 'status', + label: '通知状态', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.PAY_NOTIFY_STATUS, 'number'), + placeholder: '请选择通知状态', + }, + }, + { + fieldName: 'merchantOrderId', + label: '商户订单编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入商户订单编号', + }, + }, + { + fieldName: 'merchantRefundId', + label: '商户退款编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入商户退款编号', + }, + }, + { + fieldName: 'merchantTransferId', + label: '商户转账编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入商户转账编号', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '任务编号', + minWidth: 100, + }, + { + field: 'appName', + title: '应用名称', + minWidth: 150, + }, + { + field: 'merchantInfo', + title: '商户单信息', + minWidth: 240, + slots: { + default: 'merchantInfo', + }, + }, + { + field: 'type', + title: '通知类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PAY_NOTIFY_TYPE }, + }, + }, + { + field: 'dataId', + title: '关联编号', + minWidth: 120, + }, + { + field: 'status', + title: '通知状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PAY_NOTIFY_STATUS }, + }, + }, + { + field: 'lastExecuteTime', + title: '最后通知时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'nextNotifyTime', + title: '下次通知时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'notifyTimes', + title: '通知次数', + minWidth: 120, + formatter: ({ row }) => `${row.notifyTimes} / ${row.maxNotifyTimes}`, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'appId', + label: '应用编号', + }, + { + field: 'appName', + label: '应用名称', + }, + { + field: 'type', + label: '通知类型', + render: (val) => + h(DictTag, { + type: DICT_TYPE.PAY_NOTIFY_TYPE, + value: val, + }), + }, + { + field: 'dataId', + label: '关联编号', + }, + { + field: 'status', + label: '通知状态', + render: (val) => + h(DictTag, { + type: DICT_TYPE.PAY_NOTIFY_STATUS, + value: val, + }), + }, + { + field: 'merchantOrderId', + label: '商户订单编号', + }, + { + field: 'lastExecuteTime', + label: '最后通知时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'nextNotifyTime', + label: '下次通知时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'notifyTimes', + label: '通知次数', + }, + { + field: 'maxNotifyTimes', + label: '最大通知次数', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'updateTime', + label: '更新时间', + render: (val) => formatDateTime(val) as string, + }, + ]; +} + +/** 详情的日志字段 */ +export function useDetailLogColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '日志编号', + minWidth: 120, + }, + { + field: 'status', + title: '通知状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PAY_NOTIFY_STATUS }, + }, + }, + { + field: 'notifyTimes', + title: '通知次数', + minWidth: 120, + }, + { + field: 'createTime', + title: '通知时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'response', + title: '响应结果', + minWidth: 200, + }, + ]; +} diff --git a/apps/web-ele/src/views/pay/notify/index.vue b/apps/web-ele/src/views/pay/notify/index.vue new file mode 100644 index 0000000..9821fb9 --- /dev/null +++ b/apps/web-ele/src/views/pay/notify/index.vue @@ -0,0 +1,104 @@ + + diff --git a/apps/web-ele/src/views/pay/notify/modules/detail.vue b/apps/web-ele/src/views/pay/notify/modules/detail.vue new file mode 100644 index 0000000..d5b7bb7 --- /dev/null +++ b/apps/web-ele/src/views/pay/notify/modules/detail.vue @@ -0,0 +1,79 @@ + + + diff --git a/apps/web-ele/src/views/pay/order/data.ts b/apps/web-ele/src/views/pay/order/data.ts new file mode 100644 index 0000000..ff55e09 --- /dev/null +++ b/apps/web-ele/src/views/pay/order/data.ts @@ -0,0 +1,271 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; + +import { ElTag } from 'element-plus'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'appId', + label: '应用编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入应用编号', + }, + }, + { + fieldName: 'channelCode', + label: '支付渠道', + component: 'Select', + componentProps: { + clearable: true, + placeholder: '请选择支付渠道', + options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE, 'string'), + }, + }, + { + fieldName: 'merchantOrderId', + label: '商户单号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入商户单号', + }, + }, + { + fieldName: 'no', + label: '支付单号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入支付单号', + }, + }, + { + fieldName: 'channelOrderNo', + label: '渠道单号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入渠道单号', + }, + }, + { + fieldName: 'status', + label: '支付状态', + component: 'Select', + componentProps: { + clearable: true, + placeholder: '请选择支付状态', + options: getDictOptions(DICT_TYPE.PAY_ORDER_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'price', + title: '支付金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'refundPrice', + title: '退款金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'channelFeePrice', + title: '手续金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'no', + title: '订单号', + minWidth: 240, + slots: { + default: 'no', + }, + }, + { + field: 'status', + title: '支付状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PAY_ORDER_STATUS }, + }, + }, + { + field: 'channelCode', + title: '支付渠道', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PAY_CHANNEL_CODE }, + }, + }, + { + field: 'successTime', + title: '支付时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'appName', + title: '支付应用', + minWidth: 150, + }, + { + field: 'subject', + title: '商品标题', + minWidth: 200, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'merchantOrderId', + label: '商户单号', + }, + { + field: 'no', + label: '支付单号', + }, + { + field: 'appId', + label: '应用编号', + }, + { + field: 'appName', + label: '应用名称', + }, + { + field: 'status', + label: '支付状态', + render: (val) => + h(DictTag, { + type: DICT_TYPE.PAY_ORDER_STATUS, + value: val, + }), + }, + { + field: 'price', + label: '支付金额', + render: (val) => `¥${erpPriceInputFormatter(val)}`, + }, + { + field: 'channelFeePrice', + label: '手续费', + render: (val) => `¥${erpPriceInputFormatter(val)}`, + }, + { + field: 'channelFeeRate', + label: '手续费比例', + render: (val) => `${erpPriceInputFormatter(val)}%`, + }, + { + field: 'successTime', + label: '支付时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'expireTime', + label: '失效时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'updateTime', + label: '更新时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'subject', + label: '商品标题', + }, + { + field: 'body', + label: '商品描述', + }, + { + field: 'channelCode', + label: '支付渠道', + render: (val) => + h(DictTag, { + type: DICT_TYPE.PAY_CHANNEL_CODE, + value: val, + }), + }, + { + field: 'userIp', + label: '支付 IP', + }, + { + field: 'channelOrderNo', + label: '渠道单号', + render: (val) => (val ? h(ElTag, { color: 'green' }, () => val) : ''), + }, + { + field: 'channelUserId', + label: '渠道用户', + }, + { + field: 'refundPrice', + label: '退款金额', + render: (val) => `¥${erpPriceInputFormatter(val)}`, + }, + { + field: 'notifyUrl', + label: '通知 URL', + }, + { + field: 'channelNotifyData', + label: '支付通道异步回调内容', + }, + ]; +} diff --git a/apps/web-ele/src/views/pay/order/index.vue b/apps/web-ele/src/views/pay/order/index.vue new file mode 100644 index 0000000..adf9818 --- /dev/null +++ b/apps/web-ele/src/views/pay/order/index.vue @@ -0,0 +1,134 @@ + + + diff --git a/apps/web-ele/src/views/pay/order/modules/detail.vue b/apps/web-ele/src/views/pay/order/modules/detail.vue new file mode 100644 index 0000000..99c261d --- /dev/null +++ b/apps/web-ele/src/views/pay/order/modules/detail.vue @@ -0,0 +1,50 @@ + + diff --git a/apps/web-ele/src/views/pay/refund/data.ts b/apps/web-ele/src/views/pay/refund/data.ts new file mode 100644 index 0000000..8e1afd6 --- /dev/null +++ b/apps/web-ele/src/views/pay/refund/data.ts @@ -0,0 +1,280 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; + +import { ElTag } from 'element-plus'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'appId', + label: '应用编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入应用编号', + }, + }, + { + fieldName: 'channelCode', + label: '退款渠道', + component: 'Select', + componentProps: { + clearable: true, + placeholder: '请选择退款渠道', + options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE, 'string'), + }, + }, + { + fieldName: 'merchantOrderId', + label: '商户单号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入商户单号', + }, + }, + { + fieldName: 'merchantRefundId', + label: '退款单号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入退款单号', + }, + }, + { + fieldName: 'channelOrderNo', + label: '渠道单号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入渠道单号', + }, + }, + { + fieldName: 'channelRefundNo', + label: '渠道退款单号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入渠道退款单号', + }, + }, + { + fieldName: 'status', + label: '退款状态', + component: 'Select', + componentProps: { + clearable: true, + placeholder: '请选择退款状态', + options: getDictOptions(DICT_TYPE.PAY_REFUND_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'payPrice', + title: '支付金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'refundPrice', + title: '退款金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'merchantRefundId', + title: '退款单号', + minWidth: 240, + slots: { + default: 'no', + }, + }, + { + field: 'status', + title: '退款状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PAY_REFUND_STATUS }, + }, + }, + { + field: 'channelCode', + title: '退款渠道', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PAY_CHANNEL_CODE }, + }, + }, + { + field: 'successTime', + title: '退款时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'reason', + title: '退款原因', + minWidth: 200, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + // 基本信息部分 + { + field: 'merchantRefundId', + label: '商户退款单号', + render: (val) => h(ElTag, {}, () => val || '-'), + }, + { + field: 'channelRefundNo', + label: '渠道退款单号', + render: (val) => h(ElTag, { type: 'success' }, () => val || '-'), + }, + { + field: 'merchantOrderId', + label: '商户支付单号', + render: (val) => h(ElTag, {}, () => val || '-'), + }, + { + field: 'channelOrderNo', + label: '渠道支付单号', + render: (val) => h(ElTag, { type: 'success' }, () => val || '-'), + }, + { + field: 'appId', + label: '应用编号', + }, + { + field: 'appName', + label: '应用名称', + }, + { + field: 'payPrice', + label: '支付金额', + render: (val) => + h( + ElTag, + { type: 'success' }, + () => `¥${erpPriceInputFormatter(val || 0)}`, + ), + }, + { + field: 'refundPrice', + label: '退款金额', + render: (val) => + h( + ElTag, + { type: 'danger' }, + () => `¥${erpPriceInputFormatter(val || 0)}`, + ), + }, + { + field: 'status', + label: '退款状态', + render: (val) => + h(DictTag, { + type: DICT_TYPE.PAY_REFUND_STATUS, + value: val, + }), + }, + { + field: 'successTime', + label: '退款时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'updateTime', + label: '更新时间', + render: (val) => formatDateTime(val) as string, + }, + // 渠道信息部分 + { + field: 'channelCode', + label: '退款渠道', + render: (val) => + h(DictTag, { + type: DICT_TYPE.PAY_CHANNEL_CODE, + value: val, + }), + }, + { + field: 'reason', + label: '退款原因', + }, + { + field: 'userIp', + label: '退款 IP', + }, + { + field: 'notifyUrl', + label: '通知 URL', + }, + // 错误信息部分 + { + field: 'channelErrorCode', + label: '渠道错误码', + }, + { + field: 'channelErrorMsg', + label: '渠道错误码描述', + }, + { + field: 'channelNotifyData', + label: '支付通道异步回调内容', + }, + ]; +} diff --git a/apps/web-ele/src/views/pay/refund/index.vue b/apps/web-ele/src/views/pay/refund/index.vue new file mode 100644 index 0000000..c363ec2 --- /dev/null +++ b/apps/web-ele/src/views/pay/refund/index.vue @@ -0,0 +1,126 @@ + + diff --git a/apps/web-ele/src/views/pay/refund/modules/detail.vue b/apps/web-ele/src/views/pay/refund/modules/detail.vue new file mode 100644 index 0000000..1b93a77 --- /dev/null +++ b/apps/web-ele/src/views/pay/refund/modules/detail.vue @@ -0,0 +1,51 @@ + + + diff --git a/apps/web-ele/src/views/pay/transfer/data.ts b/apps/web-ele/src/views/pay/transfer/data.ts new file mode 100644 index 0000000..0e3b1a0 --- /dev/null +++ b/apps/web-ele/src/views/pay/transfer/data.ts @@ -0,0 +1,268 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { erpPriceInputFormatter, formatDateTime } from '@vben/utils'; + +import { ElTag } from 'element-plus'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'no', + label: '转账单号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入转账单号', + }, + }, + { + fieldName: 'channelCode', + label: '转账渠道', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE), + clearable: true, + placeholder: '请选择支付渠道', + }, + }, + { + fieldName: 'merchantTransferId', + label: '商户单号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入商户单号', + }, + }, + { + fieldName: 'type', + label: '类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.PAY_TRANSFER_TYPE), + clearable: true, + placeholder: '请选择类型', + }, + }, + { + fieldName: 'status', + label: '转账状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.PAY_TRANSFER_STATUS), + clearable: true, + placeholder: '请选择转账状态', + }, + }, + { + fieldName: 'userName', + label: '收款人姓名', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入收款人姓名', + }, + }, + { + fieldName: 'userAccount', + label: '收款人账号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入收款人账号', + }, + }, + { + fieldName: 'channelTransferNo', + label: '渠道单号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入渠道单号', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'price', + title: '转账金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'merchantTransferId', + title: '转账单号', + minWidth: 350, + slots: { + default: 'no', + }, + }, + { + field: 'status', + title: '转账状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PAY_TRANSFER_STATUS }, + }, + }, + { + field: 'channelCode', + title: '转账渠道', + minWidth: 140, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.PAY_CHANNEL_CODE }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'successTime', + title: '转账时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'subject', + title: '转账标题', + minWidth: 150, + }, + { + field: 'appName', + title: '支付应用', + minWidth: 150, + }, + { + field: 'userName', + title: '收款人姓名', + minWidth: 150, + }, + { + field: 'userAccount', + title: '收款账号', + minWidth: 200, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'merchantTransferId', + label: '商户单号', + render: (val) => h(ElTag, {}, () => val), + }, + { + field: 'no', + label: '转账单号', + render: (val) => h(ElTag, { color: 'orange' }, () => val), + }, + { + field: 'appId', + label: '应用编号', + }, + { + field: 'status', + label: '转账状态', + render: (val) => + h(DictTag, { + type: DICT_TYPE.PAY_TRANSFER_STATUS, + value: val, + }), + }, + { + field: 'price', + label: '转账金额', + render: (val) => + h( + ElTag, + { color: 'success' }, + () => `¥${erpPriceInputFormatter(val || 0)}`, + ), + }, + { + field: 'successTime', + label: '转账时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'userName', + label: '收款人姓名', + }, + { + field: 'userAccount', + label: '收款人账号', + }, + { + field: 'channelCode', + label: '支付渠道', + render: (val) => + h(DictTag, { + type: DICT_TYPE.PAY_CHANNEL_CODE, + value: val, + }), + }, + { + field: 'userIp', + label: '支付 IP', + }, + { + field: 'channelTransferNo', + label: '渠道单号', + render: (val) => (val ? h(ElTag, { color: 'success' }, () => val) : ''), + }, + { + field: 'notifyUrl', + label: '通知 URL', + }, + { + field: 'channelNotifyData', + label: '转账渠道通知内容', + }, + ]; +} diff --git a/apps/web-ele/src/views/pay/transfer/index.vue b/apps/web-ele/src/views/pay/transfer/index.vue new file mode 100644 index 0000000..e93f0c2 --- /dev/null +++ b/apps/web-ele/src/views/pay/transfer/index.vue @@ -0,0 +1,124 @@ + + + diff --git a/apps/web-ele/src/views/pay/transfer/modules/detail.vue b/apps/web-ele/src/views/pay/transfer/modules/detail.vue new file mode 100644 index 0000000..86dd915 --- /dev/null +++ b/apps/web-ele/src/views/pay/transfer/modules/detail.vue @@ -0,0 +1,51 @@ + + + diff --git a/apps/web-ele/src/views/pay/wallet/balance/data.ts b/apps/web-ele/src/views/pay/wallet/balance/data.ts new file mode 100644 index 0000000..2b30e86 --- /dev/null +++ b/apps/web-ele/src/views/pay/wallet/balance/data.ts @@ -0,0 +1,137 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + placeholder: '请输入用户编号', + clearable: true, + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + placeholder: '请选择用户类型', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '编号', + field: 'id', + minWidth: 100, + }, + { + title: '用户编号', + field: 'userId', + minWidth: 120, + }, + { + title: '用户类型', + field: 'userType', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.USER_TYPE }, + }, + }, + { + title: '余额', + field: 'balance', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + title: '累计支出', + field: 'totalExpense', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + title: '累计充值', + field: 'totalRecharge', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + title: '冻结金额', + field: 'freezePrice', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + field: 'actions', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 钱包交易记录列表字段 */ +export function useTransactionGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'title', + title: '关联业务标题', + minWidth: 200, + }, + { + field: 'price', + title: '交易金额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'balance', + title: '钱包余额', + minWidth: 120, + formatter: 'formatAmount2', + }, + { + field: 'createTime', + title: '交易时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + ]; +} diff --git a/apps/web-ele/src/views/pay/wallet/balance/index.vue b/apps/web-ele/src/views/pay/wallet/balance/index.vue new file mode 100644 index 0000000..7235632 --- /dev/null +++ b/apps/web-ele/src/views/pay/wallet/balance/index.vue @@ -0,0 +1,83 @@ + + + diff --git a/apps/web-ele/src/views/pay/wallet/balance/modules/detail.vue b/apps/web-ele/src/views/pay/wallet/balance/modules/detail.vue new file mode 100644 index 0000000..96749fd --- /dev/null +++ b/apps/web-ele/src/views/pay/wallet/balance/modules/detail.vue @@ -0,0 +1,65 @@ + + diff --git a/apps/web-ele/src/views/pay/wallet/rechargePackage/data.ts b/apps/web-ele/src/views/pay/wallet/rechargePackage/data.ts new file mode 100644 index 0000000..41caff6 --- /dev/null +++ b/apps/web-ele/src/views/pay/wallet/rechargePackage/data.ts @@ -0,0 +1,151 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '套餐名称', + component: 'Input', + rules: 'required', + componentProps: { + placeholder: '请输入套餐名称', + }, + }, + { + fieldName: 'payPrice', + label: '支付金额(元)', + component: 'InputNumber', + rules: z.number().min(0, '支付金额不能小于0'), + componentProps: { + min: 0, + precision: 2, + step: 0.01, + placeholder: '请输入支付金额', + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'bonusPrice', + label: '赠送金额(元)', + component: 'InputNumber', + rules: z.number().min(0, '赠送金额不能小于0'), + componentProps: { + min: 0, + precision: 2, + step: 0.01, + placeholder: '请输入赠送金额', + controlsPosition: 'right', + class: '!w-full', + }, + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '套餐名称', + component: 'Input', + componentProps: { + placeholder: '请输入套餐名称', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '套餐编号', + minWidth: 100, + }, + { + field: 'name', + title: '套餐名称', + minWidth: 150, + }, + { + field: 'payPrice', + title: '支付金额', + minWidth: 120, + formatter: 'formatFenToYuanAmount', + }, + { + field: 'bonusPrice', + title: '赠送金额', + minWidth: 120, + formatter: 'formatFenToYuanAmount', + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 160, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/pay/wallet/rechargePackage/index.vue b/apps/web-ele/src/views/pay/wallet/rechargePackage/index.vue new file mode 100644 index 0000000..3d89c7b --- /dev/null +++ b/apps/web-ele/src/views/pay/wallet/rechargePackage/index.vue @@ -0,0 +1,130 @@ + + + diff --git a/apps/web-ele/src/views/pay/wallet/rechargePackage/modules/form.vue b/apps/web-ele/src/views/pay/wallet/rechargePackage/modules/form.vue new file mode 100644 index 0000000..2e1243c --- /dev/null +++ b/apps/web-ele/src/views/pay/wallet/rechargePackage/modules/form.vue @@ -0,0 +1,101 @@ + + + diff --git a/apps/web-ele/src/views/system/area/data.ts b/apps/web-ele/src/views/system/area/data.ts new file mode 100644 index 0000000..e6cc06c --- /dev/null +++ b/apps/web-ele/src/views/system/area/data.ts @@ -0,0 +1,48 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemAreaApi } from '#/api/system/area'; + +import { z } from '#/adapter/form'; + +/** 查询 IP 的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'ip', + label: 'IP 地址', + component: 'Input', + componentProps: { + placeholder: '请输入 IP 地址', + }, + rules: z.string().ip({ message: '请输入正确的 IP 地址' }), + }, + { + fieldName: 'result', + label: '地址', + component: 'Input', + componentProps: { + placeholder: '展示查询 IP 结果', + readonly: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '地区编码', + minWidth: 120, + align: 'left', + fixed: 'left', + treeNode: true, + }, + { + field: 'name', + title: '地区名称', + minWidth: 200, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/area/index.vue b/apps/web-ele/src/views/system/area/index.vue new file mode 100644 index 0000000..a071419 --- /dev/null +++ b/apps/web-ele/src/views/system/area/index.vue @@ -0,0 +1,79 @@ + + + diff --git a/apps/web-ele/src/views/system/area/modules/form.vue b/apps/web-ele/src/views/system/area/modules/form.vue new file mode 100644 index 0000000..5bce43c --- /dev/null +++ b/apps/web-ele/src/views/system/area/modules/form.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-ele/src/views/system/dept/components/dept-select-modal.vue b/apps/web-ele/src/views/system/dept/components/dept-select-modal.vue new file mode 100644 index 0000000..be14cf6 --- /dev/null +++ b/apps/web-ele/src/views/system/dept/components/dept-select-modal.vue @@ -0,0 +1,141 @@ +// TODO @芋艿:是否有更好的组织形式?! + + diff --git a/apps/web-ele/src/views/system/dept/components/index.ts b/apps/web-ele/src/views/system/dept/components/index.ts new file mode 100644 index 0000000..d41597e --- /dev/null +++ b/apps/web-ele/src/views/system/dept/components/index.ts @@ -0,0 +1,2 @@ +// TODO @xingyu:【待讨论】是不是把 user select 放到 user 目录的 components 下,dept select 放到 dept 目录的 components 下 +export { default as DeptSelectModal } from './dept-select-modal.vue'; diff --git a/apps/web-ele/src/views/system/dept/data.ts b/apps/web-ele/src/views/system/dept/data.ts new file mode 100644 index 0000000..0bc18c1 --- /dev/null +++ b/apps/web-ele/src/views/system/dept/data.ts @@ -0,0 +1,162 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemDeptApi } from '#/api/system/dept'; +import type { SystemUserApi } from '#/api/system/user'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { handleTree } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getDeptList } from '#/api/system/dept'; +import { getSimpleUserList } from '#/api/system/user'; + +/** 关联数据 */ +let userList: SystemUserApi.User[] = []; +getSimpleUserList().then((data) => (userList = data)); + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'parentId', + label: '上级部门', + component: 'ApiTreeSelect', + componentProps: { + clearable: true, + api: async () => { + const data = await getDeptList(); + data.unshift({ + id: 0, + name: '顶级部门', + }); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择上级部门', + treeDefaultExpandAll: true, + }, + rules: 'selectRequired', + }, + { + fieldName: 'name', + label: '部门名称', + component: 'Input', + componentProps: { + placeholder: '请输入部门名称', + }, + rules: 'required', + }, + { + fieldName: 'sort', + label: '显示顺序', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入显示顺序', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'leaderUserId', + label: '负责人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择负责人', + clearable: true, + }, + rules: z.number().optional(), + }, + { + fieldName: 'phone', + label: '联系电话', + component: 'Input', + componentProps: { + maxLength: 11, + placeholder: '请输入联系电话', + }, + rules: 'mobileRequired', + }, + { + fieldName: 'email', + label: '邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入邮箱', + }, + rules: z.string().email('邮箱格式不正确').or(z.literal('')).optional(), + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'name', + title: '部门名称', + minWidth: 150, + align: 'left', + fixed: 'left', + treeNode: true, + }, + { + field: 'leaderUserId', + title: '负责人', + minWidth: 150, + formatter: ({ cellValue }) => + userList.find((user) => user.id === cellValue)?.nickname || '-', + }, + { + field: 'sort', + title: '显示顺序', + minWidth: 100, + }, + { + field: 'status', + title: '部门状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/dept/index.vue b/apps/web-ele/src/views/system/dept/index.vue new file mode 100644 index 0000000..0d3e39c --- /dev/null +++ b/apps/web-ele/src/views/system/dept/index.vue @@ -0,0 +1,193 @@ + + + diff --git a/apps/web-ele/src/views/system/dept/modules/form.vue b/apps/web-ele/src/views/system/dept/modules/form.vue new file mode 100644 index 0000000..b013618 --- /dev/null +++ b/apps/web-ele/src/views/system/dept/modules/form.vue @@ -0,0 +1,84 @@ + + + diff --git a/apps/web-ele/src/views/system/dict/data.ts b/apps/web-ele/src/views/system/dict/data.ts new file mode 100644 index 0000000..a8fe7c6 --- /dev/null +++ b/apps/web-ele/src/views/system/dict/data.ts @@ -0,0 +1,347 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleDictTypeList } from '#/api/system/dict/type'; + +// ============================== 字典类型 ============================== + +/** 类型新增/修改的表单 */ +export function useTypeFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '字典名称', + component: 'Input', + componentProps: { + placeholder: '请输入字典名称', + }, + rules: 'required', + }, + { + fieldName: 'type', + label: '字典类型', + component: 'Input', + componentProps: (values) => { + return { + placeholder: '请输入字典类型', + disabled: !!values.id, + }; + }, + rules: 'required', + dependencies: { + triggerFields: [''], + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 类型列表的搜索表单 */ +export function useTypeGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '字典名称', + component: 'Input', + componentProps: { + placeholder: '请输入字典名称', + clearable: true, + }, + }, + { + fieldName: 'type', + label: '字典类型', + component: 'Input', + componentProps: { + placeholder: '请输入字典类型', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + clearable: true, + }, + }, + ]; +} + +/** 类型列表的字段 */ +export function useTypeGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '字典编号', + minWidth: 100, + }, + { + field: 'name', + title: '字典名称', + minWidth: 200, + }, + { + field: 'type', + title: '字典类型', + minWidth: 220, + }, + { + field: 'status', + title: '状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + minWidth: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +// ============================== 字典数据 ============================== + +// TODO @芋艿:后续针对 antd,增加 +/** 颜色选项 */ +const colorOptions = [ + { value: '', label: '无' }, + { value: 'processing', label: '主要' }, + { value: 'success', label: '成功' }, + { value: 'default', label: '默认' }, + { value: 'warning', label: '警告' }, + { value: 'error', label: '危险' }, + { value: 'pink', label: 'pink' }, + { value: 'red', label: 'red' }, + { value: 'orange', label: 'orange' }, + { value: 'green', label: 'green' }, + { value: 'cyan', label: 'cyan' }, + { value: 'blue', label: 'blue' }, + { value: 'purple', label: 'purple' }, +]; + +/** 数据新增/修改的表单 */ +export function useDataFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'dictType', + label: '字典类型', + component: 'ApiSelect', + componentProps: (values) => { + return { + api: getSimpleDictTypeList, + placeholder: '请输入字典类型', + labelField: 'name', + valueField: 'type', + disabled: !!values.id, + }; + }, + rules: 'required', + dependencies: { + triggerFields: [''], + }, + }, + { + fieldName: 'label', + label: '数据标签', + component: 'Input', + componentProps: { + placeholder: '请输入数据标签', + }, + rules: 'required', + }, + { + fieldName: 'value', + label: '数据键值', + component: 'Input', + componentProps: { + placeholder: '请输入数据键值', + }, + rules: 'required', + }, + { + fieldName: 'sort', + label: '显示排序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入显示排序', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'colorType', + label: '颜色类型', + component: 'Select', + componentProps: { + options: colorOptions, + placeholder: '请选择颜色类型', + }, + }, + { + fieldName: 'cssClass', + label: 'CSS Class', + component: 'Input', + componentProps: { + placeholder: '请输入 CSS Class', + }, + help: '输入 hex 模式的颜色, 例如 #108ee9', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 字典数据列表搜索表单 */ +export function useDataGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'label', + label: '字典标签', + component: 'Input', + componentProps: { + placeholder: '请输入字典标签', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + clearable: true, + }, + }, + ]; +} + +/** 字典数据表格列 */ +export function useDataGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '字典编码', + minWidth: 100, + }, + { + field: 'label', + title: '字典标签', + minWidth: 180, + }, + { + field: 'value', + title: '字典键值', + minWidth: 100, + }, + { + field: 'sort', + title: '字典排序', + minWidth: 100, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'colorType', + title: '颜色类型', + minWidth: 120, + }, + { + field: 'cssClass', + title: 'CSS Class', + minWidth: 120, + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + minWidth: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/dict/index.vue b/apps/web-ele/src/views/system/dict/index.vue new file mode 100644 index 0000000..8d82425 --- /dev/null +++ b/apps/web-ele/src/views/system/dict/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/apps/web-ele/src/views/system/dict/modules/data-form.vue b/apps/web-ele/src/views/system/dict/modules/data-form.vue new file mode 100644 index 0000000..6f32030 --- /dev/null +++ b/apps/web-ele/src/views/system/dict/modules/data-form.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/web-ele/src/views/system/dict/modules/data-grid.vue b/apps/web-ele/src/views/system/dict/modules/data-grid.vue new file mode 100644 index 0000000..54053c3 --- /dev/null +++ b/apps/web-ele/src/views/system/dict/modules/data-grid.vue @@ -0,0 +1,202 @@ + + + diff --git a/apps/web-ele/src/views/system/dict/modules/type-form.vue b/apps/web-ele/src/views/system/dict/modules/type-form.vue new file mode 100644 index 0000000..5e7cce3 --- /dev/null +++ b/apps/web-ele/src/views/system/dict/modules/type-form.vue @@ -0,0 +1,86 @@ + + + diff --git a/apps/web-ele/src/views/system/dict/modules/type-grid.vue b/apps/web-ele/src/views/system/dict/modules/type-grid.vue new file mode 100644 index 0000000..d2e7ec0 --- /dev/null +++ b/apps/web-ele/src/views/system/dict/modules/type-grid.vue @@ -0,0 +1,189 @@ + + + diff --git a/apps/web-ele/src/views/system/loginlog/data.ts b/apps/web-ele/src/views/system/loginlog/data.ts new file mode 100644 index 0000000..2be5cdf --- /dev/null +++ b/apps/web-ele/src/views/system/loginlog/data.ts @@ -0,0 +1,147 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'username', + label: '用户名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入用户名称', + }, + }, + { + fieldName: 'userIp', + label: '登录地址', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入登录地址', + }, + }, + { + fieldName: 'createTime', + label: '登录时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '日志编号', + minWidth: 100, + }, + { + field: 'logType', + title: '操作类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_LOGIN_TYPE }, + }, + }, + { + field: 'username', + title: '用户名称', + minWidth: 180, + }, + { + field: 'userIp', + title: '登录地址', + minWidth: 180, + }, + { + field: 'userAgent', + title: '浏览器', + minWidth: 200, + }, + { + field: 'result', + title: '登录结果', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_LOGIN_RESULT }, + }, + }, + { + field: 'createTime', + title: '登录日期', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'id', + label: '日志编号', + }, + { + field: 'logType', + label: '操作类型', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_LOGIN_TYPE, + value: val, + }); + }, + }, + { + field: 'username', + label: '用户名称', + }, + { + field: 'userIp', + label: '登录地址', + }, + { + field: 'userAgent', + label: '浏览器', + }, + { + field: 'result', + label: '登录结果', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_LOGIN_RESULT, + value: val, + }); + }, + }, + { + field: 'createTime', + label: '登录日期', + render: (val) => formatDateTime(val) as string, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/loginlog/index.vue b/apps/web-ele/src/views/system/loginlog/index.vue new file mode 100644 index 0000000..f5c5dd9 --- /dev/null +++ b/apps/web-ele/src/views/system/loginlog/index.vue @@ -0,0 +1,104 @@ + + + diff --git a/apps/web-ele/src/views/system/loginlog/modules/detail.vue b/apps/web-ele/src/views/system/loginlog/modules/detail.vue new file mode 100644 index 0000000..d1c9bea --- /dev/null +++ b/apps/web-ele/src/views/system/loginlog/modules/detail.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-ele/src/views/system/mail/account/data.ts b/apps/web-ele/src/views/system/mail/account/data.ts new file mode 100644 index 0000000..ce36cb0 --- /dev/null +++ b/apps/web-ele/src/views/system/mail/account/data.ts @@ -0,0 +1,183 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'mail', + label: '邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入邮箱', + }, + rules: 'required', + }, + { + fieldName: 'username', + label: '用户名', + component: 'Input', + componentProps: { + placeholder: '请输入用户名', + }, + rules: 'required', + }, + { + fieldName: 'password', + label: '密码', + component: 'Input', + componentProps: { + showPassword: true, + placeholder: '请输入密码', + }, + rules: 'required', + }, + { + fieldName: 'host', + label: 'SMTP 服务器域名', + component: 'Input', + componentProps: { + placeholder: '请输入 SMTP 服务器域名', + }, + rules: 'required', + }, + { + fieldName: 'port', + label: 'SMTP 服务器端口', + component: 'InputNumber', + componentProps: { + placeholder: '请输入 SMTP 服务器端口', + min: 0, + max: 65_535, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'sslEnable', + label: '是否开启 SSL', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + }, + rules: z.boolean().default(true), + }, + { + fieldName: 'starttlsEnable', + label: '是否开启 STARTTLS', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + }, + rules: z.boolean().default(false), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'mail', + label: '邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入邮箱', + clearable: true, + }, + }, + { + fieldName: 'username', + label: '用户名', + component: 'Input', + componentProps: { + placeholder: '请输入用户名', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'mail', + title: '邮箱', + minWidth: 160, + }, + { + field: 'username', + title: '用户名', + minWidth: 160, + }, + { + field: 'host', + title: 'SMTP 服务器域名', + minWidth: 150, + }, + { + field: 'port', + title: 'SMTP 服务器端口', + minWidth: 130, + }, + { + field: 'sslEnable', + title: '是否开启 SSL', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'starttlsEnable', + title: '是否开启 STARTTLS', + minWidth: 145, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/mail/account/index.vue b/apps/web-ele/src/views/system/mail/account/index.vue new file mode 100644 index 0000000..6a15a08 --- /dev/null +++ b/apps/web-ele/src/views/system/mail/account/index.vue @@ -0,0 +1,172 @@ + + diff --git a/apps/web-ele/src/views/system/mail/account/modules/form.vue b/apps/web-ele/src/views/system/mail/account/modules/form.vue new file mode 100644 index 0000000..a7fc397 --- /dev/null +++ b/apps/web-ele/src/views/system/mail/account/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/system/mail/log/data.ts b/apps/web-ele/src/views/system/mail/log/data.ts new file mode 100644 index 0000000..cef8cd7 --- /dev/null +++ b/apps/web-ele/src/views/system/mail/log/data.ts @@ -0,0 +1,259 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { getSimpleMailAccountList } from '#/api/system/mail/account'; +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'sendTime', + label: '发送时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入用户编号', + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + clearable: true, + placeholder: '请选择用户类型', + }, + }, + { + fieldName: 'sendStatus', + label: '发送状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_MAIL_SEND_STATUS, 'number'), + clearable: true, + placeholder: '请选择发送状态', + }, + }, + { + fieldName: 'accountId', + label: '邮箱账号', + component: 'ApiSelect', + componentProps: { + api: getSimpleMailAccountList, + labelField: 'mail', + valueField: 'id', + clearable: true, + placeholder: '请选择邮箱账号', + }, + }, + { + fieldName: 'templateId', + label: '模板编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编号', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'sendTime', + title: '发送时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'userType', + title: '接收用户', + minWidth: 150, + slots: { default: 'userInfo' }, + }, + { + field: 'toMails', + title: '接收信息', + minWidth: 300, + formatter: ({ row }) => { + const lines: string[] = []; + if (row.toMails && row.toMails.length > 0) { + lines.push(`收件:${row.toMails.join('、')}`); + } + if (row.ccMails && row.ccMails.length > 0) { + lines.push(`抄送:${row.ccMails.join('、')}`); + } + if (row.bccMails && row.bccMails.length > 0) { + lines.push(`密送:${row.bccMails.join('、')}`); + } + return lines.join('\n'); + }, + }, + { + field: 'templateTitle', + title: '邮件标题', + minWidth: 120, + }, + { + field: 'templateContent', + title: '邮件内容', + minWidth: 300, + }, + { + field: 'fromMail', + title: '发送邮箱', + minWidth: 120, + }, + { + field: 'sendStatus', + title: '发送状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS }, + }, + }, + { + field: 'templateCode', + title: '模板编码', + minWidth: 120, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'id', + label: '编号', + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => { + return formatDateTime(val) as string; + }, + }, + { + field: 'fromMail', + label: '发送邮箱', + }, + { + field: 'userId', + label: '接收用户', + render: (val, data) => { + if (data?.userType && val) { + return h('div', [ + h(DictTag, { + type: DICT_TYPE.USER_TYPE, + value: data.userType, + }), + ` (${val})`, + ]); + } + return '无'; + }, + }, + { + field: 'toMails', + label: '接收信息', + render: (val, data) => { + const lines: string[] = []; + if (val && val.length > 0) { + lines.push(`收件:${val.join('、')}`); + } + if (data?.ccMails && data.ccMails.length > 0) { + lines.push(`抄送:${data.ccMails.join('、')}`); + } + if (data?.bccMails && data.bccMails.length > 0) { + lines.push(`密送:${data.bccMails.join('、')}`); + } + return h( + 'div', + { + style: { whiteSpace: 'pre-line' }, + }, + lines.join('\n'), + ); + }, + }, + { + field: 'templateId', + label: '模板编号', + }, + { + field: 'templateCode', + label: '模板编码', + }, + { + field: 'templateTitle', + label: '邮件标题', + }, + { + field: 'templateContent', + label: '邮件内容', + span: 2, + render: (val) => { + return h('div', { + innerHTML: val || '', + }); + }, + }, + { + field: 'sendStatus', + label: '发送状态', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS, + value: val, + }); + }, + }, + { + field: 'sendTime', + label: '发送时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'sendMessageId', + label: '发送消息编号', + }, + { + field: 'sendException', + label: '发送异常', + }, + ]; +} diff --git a/apps/web-ele/src/views/system/mail/log/index.vue b/apps/web-ele/src/views/system/mail/log/index.vue new file mode 100644 index 0000000..548f485 --- /dev/null +++ b/apps/web-ele/src/views/system/mail/log/index.vue @@ -0,0 +1,92 @@ + + diff --git a/apps/web-ele/src/views/system/mail/log/modules/detail.vue b/apps/web-ele/src/views/system/mail/log/modules/detail.vue new file mode 100644 index 0000000..ab0daa7 --- /dev/null +++ b/apps/web-ele/src/views/system/mail/log/modules/detail.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-ele/src/views/system/mail/template/data.ts b/apps/web-ele/src/views/system/mail/template/data.ts new file mode 100644 index 0000000..e57e92f --- /dev/null +++ b/apps/web-ele/src/views/system/mail/template/data.ts @@ -0,0 +1,258 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleMailAccountList } from '#/api/system/mail/account'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + placeholder: '请输入模板名称', + }, + rules: 'required', + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + placeholder: '请输入模板编码', + }, + rules: 'required', + }, + { + fieldName: 'accountId', + label: '邮箱账号', + component: 'ApiSelect', + componentProps: { + api: getSimpleMailAccountList, + labelField: 'mail', + valueField: 'id', + placeholder: '请选择邮箱账号', + }, + rules: 'required', + }, + { + fieldName: 'nickname', + label: '发送人名称', + component: 'Input', + componentProps: { + placeholder: '请输入发送人名称', + }, + }, + { + fieldName: 'title', + label: '模板标题', + component: 'Input', + componentProps: { + placeholder: '请输入模板标题', + }, + rules: 'required', + }, + { + fieldName: 'content', + label: '模板内容', + component: 'RichTextarea', + rules: 'required', + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 发送邮件表单 */ +export function useSendMailFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'templateParams', + label: '模板参数', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'content', + label: '模板内容', + component: 'RichTextarea', + componentProps: { + options: { + readonly: true, + }, + }, + }, + { + fieldName: 'toMails', + label: '收件邮箱', + component: 'InputTag', + componentProps: { + placeholder: '请输入收件邮箱,按 Enter 添加', + }, + }, + { + fieldName: 'ccMails', + label: '抄送邮箱', + component: 'InputTag', + componentProps: { + placeholder: '请输入抄送邮箱,按 Enter 添加', + }, + }, + { + fieldName: 'bccMails', + label: '密送邮箱', + component: 'InputTag', + componentProps: { + placeholder: '请输入密送邮箱,按 Enter 添加', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'status', + label: '开启状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + clearable: true, + placeholder: '请选择开启状态', + }, + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编码', + }, + }, + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板名称', + }, + }, + { + fieldName: 'accountId', + label: '邮箱账号', + component: 'ApiSelect', + componentProps: { + api: getSimpleMailAccountList, + labelField: 'mail', + valueField: 'id', + clearable: true, + placeholder: '请选择邮箱账号', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + getAccountMail?: (accountId: number) => string | undefined, +): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'code', + title: '模板编码', + minWidth: 120, + }, + { + field: 'name', + title: '模板名称', + minWidth: 120, + }, + { + field: 'title', + title: '模板标题', + minWidth: 120, + }, + { + field: 'accountId', + title: '邮箱账号', + minWidth: 120, + formatter: ({ cellValue }) => getAccountMail?.(cellValue) || '-', + }, + { + field: 'nickname', + title: '发送人名称', + minWidth: 120, + }, + { + field: 'status', + title: '开启状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/mail/template/index.vue b/apps/web-ele/src/views/system/mail/template/index.vue new file mode 100644 index 0000000..6fc015f --- /dev/null +++ b/apps/web-ele/src/views/system/mail/template/index.vue @@ -0,0 +1,205 @@ + + diff --git a/apps/web-ele/src/views/system/mail/template/modules/form.vue b/apps/web-ele/src/views/system/mail/template/modules/form.vue new file mode 100644 index 0000000..4014e46 --- /dev/null +++ b/apps/web-ele/src/views/system/mail/template/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/system/mail/template/modules/send-form.vue b/apps/web-ele/src/views/system/mail/template/modules/send-form.vue new file mode 100644 index 0000000..8f724ed --- /dev/null +++ b/apps/web-ele/src/views/system/mail/template/modules/send-form.vue @@ -0,0 +1,109 @@ + + + diff --git a/apps/web-ele/src/views/system/menu/data.ts b/apps/web-ele/src/views/system/menu/data.ts new file mode 100644 index 0000000..98a4060 --- /dev/null +++ b/apps/web-ele/src/views/system/menu/data.ts @@ -0,0 +1,319 @@ +import type { Recordable } from '@vben/types'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemMenuApi } from '#/api/system/menu'; + +import { h } from 'vue'; + +import { + CommonStatusEnum, + DICT_TYPE, + SystemMenuTypeEnum, +} from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { IconifyIcon } from '@vben/icons'; +import { handleTree, isHttpUrl } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getMenuList } from '#/api/system/menu'; +import { $t } from '#/locales'; +import { componentKeys } from '#/router/routes'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'parentId', + label: '上级菜单', + component: 'ApiTreeSelect', + componentProps: { + checkStrictly: true,//修复父类节点不能选中 + clearable: true, + api: async () => { + const data = await getMenuList(); + data.unshift({ + id: 0, + name: '顶级部门', + } as SystemMenuApi.Menu); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择上级菜单', + filterTreeNode(input: string, node: Recordable) { + if (!input || input.length === 0) { + return true; + } + const name: string = node.label ?? ''; + if (!name) return false; + return name.includes(input) || $t(name).includes(input); + }, + showSearch: true, + treeDefaultExpandedKeys: [0], + }, + rules: 'selectRequired', + renderComponentContent() { + return { + title({ label, icon }: { icon: string; label: string }) { + const components = []; + if (!label) return ''; + if (icon) { + components.push(h(IconifyIcon, { class: 'size-4', icon })); + } + components.push(h('span', { class: '' }, $t(label || ''))); + return h('div', { class: 'flex items-center gap-1' }, components); + }, + }; + }, + }, + { + fieldName: 'name', + label: '菜单名称', + component: 'Input', + componentProps: { + placeholder: '请输入菜单名称', + }, + rules: 'required', + }, + { + fieldName: 'type', + label: '菜单类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE, 'number'), + }, + rules: z.number().default(SystemMenuTypeEnum.DIR), + }, + { + fieldName: 'icon', + label: '菜单图标', + component: 'IconPicker', + componentProps: { + placeholder: '请选择菜单图标', + prefix: 'carbon', + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [SystemMenuTypeEnum.DIR, SystemMenuTypeEnum.MENU].includes( + values.type, + ); + }, + }, + }, + { + fieldName: 'path', + label: '路由地址', + component: 'Input', + componentProps: { + placeholder: '请输入路由地址', + }, + rules: z.string(), + help: '访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头', + dependencies: { + triggerFields: ['type', 'parentId'], + show: (values) => { + return [SystemMenuTypeEnum.DIR, SystemMenuTypeEnum.MENU].includes( + values.type, + ); + }, + rules: (values) => { + const schema = z.string().min(1, '路由地址不能为空'); + if (isHttpUrl(values.path)) { + return schema; + } + if (values.parentId === 0) { + return schema.refine( + (path) => path.charAt(0) === '/', + '路径必须以 / 开头', + ); + } + return schema.refine( + (path) => path.charAt(0) !== '/', + '路径不能以 / 开头', + ); + }, + }, + }, + { + fieldName: 'component', + label: '组件地址', + component: 'Input', + componentProps: { + placeholder: '请输入组件地址', + }, + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [SystemMenuTypeEnum.MENU].includes(values.type); + }, + }, + }, + { + fieldName: 'componentName', + label: '组件名称', + component: 'AutoComplete', + componentProps: { + clearable: true, + filterOption(input: string, option: { value: string }) { + return option.value.toLowerCase().includes(input.toLowerCase()); + }, + placeholder: '请选择组件名称', + options: componentKeys.map((v) => ({ value: v })), + }, + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [SystemMenuTypeEnum.MENU].includes(values.type); + }, + }, + }, + { + fieldName: 'permission', + label: '权限标识', + component: 'Input', + componentProps: { + placeholder: '请输入菜单描述', + }, + dependencies: { + show: (values) => { + return [SystemMenuTypeEnum.BUTTON, SystemMenuTypeEnum.MENU].includes( + values.type, + ); + }, + triggerFields: ['type'], + }, + }, + { + fieldName: 'sort', + label: '显示顺序', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入显示顺序', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '菜单状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'alwaysShow', + label: '总是显示', + component: 'RadioGroup', + componentProps: { + options: [ + { label: '总是', value: true }, + { label: '不是', value: false }, + ], + }, + rules: 'required', + defaultValue: true, + help: '选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单', + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [SystemMenuTypeEnum.MENU].includes(values.type); + }, + }, + }, + { + fieldName: 'keepAlive', + label: '缓存状态', + component: 'RadioGroup', + componentProps: { + options: [ + { label: '缓存', value: true }, + { label: '不缓存', value: false }, + ], + }, + rules: 'required', + defaultValue: true, + help: '选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段', + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [SystemMenuTypeEnum.MENU].includes(values.type); + }, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '菜单名称', + minWidth: 250, + align: 'left', + fixed: 'left', + slots: { default: 'name' }, + treeNode: true, + }, + { + field: 'type', + title: '菜单类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_MENU_TYPE }, + }, + }, + { + field: 'sort', + title: '显示排序', + minWidth: 100, + }, + { + field: 'permission', + title: '权限标识', + minWidth: 200, + }, + { + field: 'path', + title: '组件路径', + minWidth: 200, + }, + { + field: 'componentName', + title: '组件名称', + minWidth: 200, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/menu/index.vue b/apps/web-ele/src/views/system/menu/index.vue new file mode 100644 index 0000000..783bc74 --- /dev/null +++ b/apps/web-ele/src/views/system/menu/index.vue @@ -0,0 +1,181 @@ + + + diff --git a/apps/web-ele/src/views/system/menu/modules/form.vue b/apps/web-ele/src/views/system/menu/modules/form.vue new file mode 100644 index 0000000..9a91c16 --- /dev/null +++ b/apps/web-ele/src/views/system/menu/modules/form.vue @@ -0,0 +1,84 @@ + + + diff --git a/apps/web-ele/src/views/system/notice/data.ts b/apps/web-ele/src/views/system/notice/data.ts new file mode 100644 index 0000000..1746f69 --- /dev/null +++ b/apps/web-ele/src/views/system/notice/data.ts @@ -0,0 +1,134 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'title', + label: '公告标题', + component: 'Input', + componentProps: { + placeholder: '请输入公告标题', + }, + rules: 'required', + }, + { + fieldName: 'type', + label: '公告类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_NOTICE_TYPE, 'number'), + }, + rules: 'required', + }, + { + fieldName: 'content', + label: '公告内容', + component: 'RichTextarea', + rules: 'required', + }, + { + fieldName: 'status', + label: '公告状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'title', + label: '公告标题', + component: 'Input', + componentProps: { + placeholder: '请输入公告标题', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '公告状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择公告状态', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '公告编号', + minWidth: 100, + }, + { + field: 'title', + title: '公告标题', + minWidth: 200, + }, + { + field: 'type', + title: '公告类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_NOTICE_TYPE }, + }, + }, + { + field: 'status', + title: '公告状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/notice/index.vue b/apps/web-ele/src/views/system/notice/index.vue new file mode 100644 index 0000000..1809a1a --- /dev/null +++ b/apps/web-ele/src/views/system/notice/index.vue @@ -0,0 +1,191 @@ + + + diff --git a/apps/web-ele/src/views/system/notice/modules/form.vue b/apps/web-ele/src/views/system/notice/modules/form.vue new file mode 100644 index 0000000..f00a497 --- /dev/null +++ b/apps/web-ele/src/views/system/notice/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/system/notify/message/data.ts b/apps/web-ele/src/views/system/notify/message/data.ts new file mode 100644 index 0000000..c786ee1 --- /dev/null +++ b/apps/web-ele/src/views/system/notify/message/data.ts @@ -0,0 +1,237 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入用户编号', + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + placeholder: '请选择用户类型', + }, + }, + { + fieldName: 'templateCode', + label: '模板编码', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编码', + }, + }, + { + fieldName: 'templateType', + label: '模版类型', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, + 'number', + ), + clearable: true, + placeholder: '请选择模版类型', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'userType', + title: '用户类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.USER_TYPE }, + }, + }, + { + field: 'userId', + title: '用户编号', + minWidth: 100, + }, + { + field: 'templateCode', + title: '模板编码', + minWidth: 120, + }, + { + field: 'templateNickname', + title: '发送人名称', + minWidth: 180, + }, + { + field: 'templateContent', + title: '模版内容', + minWidth: 200, + }, + { + field: 'templateParams', + title: '模版参数', + minWidth: 180, + formatter: ({ cellValue }) => { + try { + return JSON.stringify(cellValue); + } catch { + return ''; + } + }, + }, + { + field: 'templateType', + title: '模版类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE }, + }, + }, + { + field: 'readStatus', + title: '是否已读', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'readTime', + title: '阅读时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'id', + label: '编号', + }, + { + field: 'userType', + label: '用户类型', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.USER_TYPE, + value: val, + }); + }, + }, + { + field: 'userId', + label: '用户编号', + }, + { + field: 'templateId', + label: '模版编号', + }, + { + field: 'templateCode', + label: '模板编码', + }, + { + field: 'templateNickname', + label: '发送人名称', + }, + { + field: 'templateContent', + label: '模版内容', + }, + { + field: 'templateParams', + label: '模版参数', + render: (val) => { + try { + return JSON.stringify(val); + } catch { + return ''; + } + }, + }, + { + field: 'templateType', + label: '模版类型', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, + value: val, + }); + }, + }, + { + field: 'readStatus', + label: '是否已读', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.INFRA_BOOLEAN_STRING, + value: val, + }); + }, + }, + { + field: 'readTime', + label: '阅读时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/notify/message/index.vue b/apps/web-ele/src/views/system/notify/message/index.vue new file mode 100644 index 0000000..fcac932 --- /dev/null +++ b/apps/web-ele/src/views/system/notify/message/index.vue @@ -0,0 +1,84 @@ + + + diff --git a/apps/web-ele/src/views/system/notify/message/modules/detail.vue b/apps/web-ele/src/views/system/notify/message/modules/detail.vue new file mode 100644 index 0000000..f7dbc8f --- /dev/null +++ b/apps/web-ele/src/views/system/notify/message/modules/detail.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-ele/src/views/system/notify/my/data.ts b/apps/web-ele/src/views/system/notify/my/data.ts new file mode 100644 index 0000000..21f051d --- /dev/null +++ b/apps/web-ele/src/views/system/notify/my/data.ts @@ -0,0 +1,137 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'readStatus', + label: '是否已读', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + clearable: true, + placeholder: '请选择是否已读', + }, + }, + { + fieldName: 'createTime', + label: '发送时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '', + width: 40, + type: 'checkbox', + }, + { + field: 'templateNickname', + title: '发送人', + minWidth: 180, + }, + { + field: 'createTime', + title: '发送时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'templateType', + title: '类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE }, + }, + }, + { + field: 'templateContent', + title: '消息内容', + minWidth: 300, + }, + { + field: 'readStatus', + title: '是否已读', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'readTime', + title: '阅读时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'templateNickname', + label: '发送人', + }, + { + field: 'createTime', + label: '发送时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'templateType', + label: '消息类型', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, + value: val, + }); + }, + }, + { + field: 'readStatus', + label: '是否已读', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.INFRA_BOOLEAN_STRING, + value: val, + }); + }, + }, + { + field: 'readTime', + label: '阅读时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'templateContent', + label: '消息内容', + }, + ]; +} diff --git a/apps/web-ele/src/views/system/notify/my/index.vue b/apps/web-ele/src/views/system/notify/my/index.vue new file mode 100644 index 0000000..9c37b52 --- /dev/null +++ b/apps/web-ele/src/views/system/notify/my/index.vue @@ -0,0 +1,191 @@ + + diff --git a/apps/web-ele/src/views/system/notify/my/modules/detail.vue b/apps/web-ele/src/views/system/notify/my/modules/detail.vue new file mode 100644 index 0000000..f7dbc8f --- /dev/null +++ b/apps/web-ele/src/views/system/notify/my/modules/detail.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-ele/src/views/system/notify/template/data.ts b/apps/web-ele/src/views/system/notify/template/data.ts new file mode 100644 index 0000000..5aedc89 --- /dev/null +++ b/apps/web-ele/src/views/system/notify/template/data.ts @@ -0,0 +1,286 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE, UserTypeEnum } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + placeholder: '请输入模板名称', + }, + rules: 'required', + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + placeholder: '请输入模板编码', + }, + rules: 'required', + }, + { + fieldName: 'nickname', + label: '发送人名称', + component: 'Input', + componentProps: { + placeholder: '请输入发送人名称', + }, + rules: 'required', + }, + { + fieldName: 'content', + label: '模板内容', + component: 'Textarea', + componentProps: { + placeholder: '请输入模板内容', + }, + rules: 'required', + }, + { + fieldName: 'type', + label: '模板类型', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, + 'number', + ), + placeholder: '请选择模板类型', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板名称', + }, + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编码', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + clearable: true, + placeholder: '请选择状态', + }, + }, + { + fieldName: 'type', + label: '模板类型', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, + 'number', + ), + clearable: true, + placeholder: '请选择模板类型', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 发送站内信表单 */ +export function useSendNotifyFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'content', + label: '模板内容', + component: 'Textarea', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'templateCode', + label: '模板编码', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + }, + rules: z.number().default(UserTypeEnum.MEMBER), + }, + { + fieldName: 'userId', + label: '接收人 ID', + component: 'Input', + componentProps: { + placeholder: '请输入用户编号', + }, + dependencies: { + show(values) { + return values.userType === UserTypeEnum.MEMBER; + }, + triggerFields: ['userType'], + }, + rules: 'required', + }, + { + fieldName: 'userId', + label: '接收人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择接收人', + }, + dependencies: { + show(values) { + return values.userType === UserTypeEnum.ADMIN; + }, + triggerFields: ['userType'], + }, + rules: 'required', + }, + { + fieldName: 'templateParams', + label: '模板参数', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '模板名称', + minWidth: 120, + }, + { + field: 'code', + title: '模板编码', + minWidth: 120, + }, + { + field: 'nickname', + title: '发送人名称', + minWidth: 120, + }, + { + field: 'content', + title: '模板内容', + minWidth: 200, + }, + { + field: 'type', + title: '模板类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE }, + }, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/notify/template/index.vue b/apps/web-ele/src/views/system/notify/template/index.vue new file mode 100644 index 0000000..33c2bf8 --- /dev/null +++ b/apps/web-ele/src/views/system/notify/template/index.vue @@ -0,0 +1,207 @@ + + + diff --git a/apps/web-ele/src/views/system/notify/template/modules/form.vue b/apps/web-ele/src/views/system/notify/template/modules/form.vue new file mode 100644 index 0000000..3ce13ff --- /dev/null +++ b/apps/web-ele/src/views/system/notify/template/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/system/notify/template/modules/send-form.vue b/apps/web-ele/src/views/system/notify/template/modules/send-form.vue new file mode 100644 index 0000000..0ed7404 --- /dev/null +++ b/apps/web-ele/src/views/system/notify/template/modules/send-form.vue @@ -0,0 +1,112 @@ + + + diff --git a/apps/web-ele/src/views/system/oauth2/client/data.ts b/apps/web-ele/src/views/system/oauth2/client/data.ts new file mode 100644 index 0000000..5705cb0 --- /dev/null +++ b/apps/web-ele/src/views/system/oauth2/client/data.ts @@ -0,0 +1,261 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'clientId', + label: '客户端编号', + component: 'Input', + componentProps: { + placeholder: '请输入客户端编号', + }, + rules: 'required', + }, + { + fieldName: 'secret', + label: '客户端密钥', + component: 'Input', + componentProps: { + placeholder: '请输入客户端密钥', + }, + rules: 'required', + }, + { + fieldName: 'name', + label: '应用名', + component: 'Input', + componentProps: { + placeholder: '请输入应用名', + }, + rules: 'required', + }, + { + fieldName: 'logo', + label: '应用图标', + component: 'ImageUpload', + rules: 'required', + }, + { + fieldName: 'description', + label: '应用描述', + component: 'Textarea', + componentProps: { + placeholder: '请输入应用描述', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'accessTokenValiditySeconds', + label: '访问令牌的有效期', + component: 'InputNumber', + componentProps: { + placeholder: '请输入访问令牌的有效期,单位:秒', + min: 0, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'refreshTokenValiditySeconds', + label: '刷新令牌的有效期', + component: 'InputNumber', + componentProps: { + placeholder: '请输入刷新令牌的有效期,单位:秒', + min: 0, + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'authorizedGrantTypes', + label: '授权类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE), + multiple: true, + placeholder: '请输入授权类型', + }, + rules: 'required', + }, + { + fieldName: 'scopes', + label: '授权范围', + component: 'InputTag', + componentProps: { + placeholder: '请输入授权范围', + }, + }, + { + fieldName: 'autoApproveScopes', + label: '自动授权范围', + component: 'Select', + componentProps: { + placeholder: '请输入自动授权范围', + multiple: true, + options: [], + }, + dependencies: { + triggerFields: ['scopes'], + componentProps: (values) => ({ + options: values.scopes + ? values.scopes.map((scope: string) => ({ + label: scope, + value: scope, + })) + : [], + }), + }, + }, + { + fieldName: 'redirectUris', + label: '可重定向的 URI 地址', + component: 'InputTag', + componentProps: { + placeholder: '请输入可重定向的 URI 地址', + }, + rules: 'required', + }, + { + fieldName: 'authorities', + label: '权限', + component: 'InputTag', + componentProps: { + placeholder: '请输入权限', + }, + }, + { + fieldName: 'resourceIds', + label: '资源', + component: 'InputTag', + componentProps: { + placeholder: '请输入资源', + }, + }, + { + fieldName: 'additionalInformation', + label: '附加信息', + component: 'Textarea', + componentProps: { + placeholder: '请输入附加信息,JSON 格式数据', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '应用名', + component: 'Input', + componentProps: { + placeholder: '请输入应用名', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + clearable: true, + placeholder: '请输入状态', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'clientId', + title: '客户端编号', + minWidth: 120, + }, + { + field: 'secret', + title: '客户端密钥', + minWidth: 120, + }, + { + field: 'name', + title: '应用名', + minWidth: 120, + }, + { + field: 'logo', + title: '应用图标', + minWidth: 100, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'status', + title: '状态', + minWidth: 80, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'accessTokenValiditySeconds', + title: '访问令牌的有效期', + minWidth: 150, + formatter: ({ cellValue }) => `${cellValue} 秒`, + }, + { + field: 'refreshTokenValiditySeconds', + title: '刷新令牌的有效期', + minWidth: 150, + formatter: ({ cellValue }) => `${cellValue} 秒`, + }, + { + field: 'authorizedGrantTypes', + title: '授权类型', + minWidth: 100, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/oauth2/client/index.vue b/apps/web-ele/src/views/system/oauth2/client/index.vue new file mode 100644 index 0000000..2ee4047 --- /dev/null +++ b/apps/web-ele/src/views/system/oauth2/client/index.vue @@ -0,0 +1,176 @@ + + + diff --git a/apps/web-ele/src/views/system/oauth2/client/modules/form.vue b/apps/web-ele/src/views/system/oauth2/client/modules/form.vue new file mode 100644 index 0000000..927e423 --- /dev/null +++ b/apps/web-ele/src/views/system/oauth2/client/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/system/oauth2/token/data.ts b/apps/web-ele/src/views/system/oauth2/token/data.ts new file mode 100644 index 0000000..51a9d59 --- /dev/null +++ b/apps/web-ele/src/views/system/oauth2/token/data.ts @@ -0,0 +1,93 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + placeholder: '请输入用户编号', + clearable: true, + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + placeholder: '请选择用户类型', + clearable: true, + }, + }, + { + fieldName: 'clientId', + label: '客户端编号', + component: 'Input', + componentProps: { + placeholder: '请输入客户端编号', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'accessToken', + title: '访问令牌', + minWidth: 300, + }, + { + field: 'refreshToken', + title: '刷新令牌', + minWidth: 300, + }, + { + field: 'userId', + title: '用户编号', + minWidth: 100, + }, + { + field: 'userType', + title: '用户类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.USER_TYPE }, + }, + }, + { + field: 'clientId', + title: '客户端编号', + minWidth: 120, + }, + { + field: 'expiresTime', + title: '过期时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/oauth2/token/index.vue b/apps/web-ele/src/views/system/oauth2/token/index.vue new file mode 100644 index 0000000..e2af944 --- /dev/null +++ b/apps/web-ele/src/views/system/oauth2/token/index.vue @@ -0,0 +1,143 @@ + + + diff --git a/apps/web-ele/src/views/system/operatelog/data.ts b/apps/web-ele/src/views/system/operatelog/data.ts new file mode 100644 index 0000000..67b321f --- /dev/null +++ b/apps/web-ele/src/views/system/operatelog/data.ts @@ -0,0 +1,191 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { formatDateTime } from '@vben/utils'; + +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '操作人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + clearable: true, + placeholder: '请选择操作人员', + }, + }, + { + fieldName: 'type', + label: '操作模块', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入操作模块', + }, + }, + { + fieldName: 'subType', + label: '操作名', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入操作名', + }, + }, + { + fieldName: 'action', + label: '操作内容', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入操作内容', + }, + }, + { + fieldName: 'createTime', + label: '操作时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'bizId', + label: '业务编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入业务编号', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '日志编号', + minWidth: 100, + }, + { + field: 'userName', + title: '操作人', + minWidth: 120, + }, + { + field: 'type', + title: '操作模块', + minWidth: 120, + }, + { + field: 'subType', + title: '操作名', + minWidth: 160, + }, + { + field: 'action', + title: '操作内容', + minWidth: 200, + }, + { + field: 'createTime', + title: '操作时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'bizId', + title: '业务编号', + minWidth: 120, + }, + { + field: 'userIp', + title: '操作 IP', + minWidth: 120, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'id', + label: '日志编号', + }, + { + field: 'traceId', + label: '链路追踪', + show: (val) => !val, + }, + { + field: 'userId', + label: '操作人编号', + }, + { + field: 'userName', + label: '操作人名字', + }, + { + field: 'userIp', + label: '操作人 IP', + }, + { + field: 'userAgent', + label: '操作人 UA', + }, + { + field: 'type', + label: '操作模块', + }, + { + field: 'subType', + label: '操作名', + }, + { + field: 'action', + label: '操作内容', + }, + { + field: 'extra', + label: '操作拓展参数', + show: (val) => !val, + }, + { + field: 'requestUrl', + label: '请求 URL', + render: (val, data) => { + if (data?.requestMethod && val) { + return `${data.requestMethod} ${val}`; + } + return ''; + }, + }, + { + field: 'createTime', + label: '操作时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'bizId', + label: '业务编号', + }, + ]; +} diff --git a/apps/web-ele/src/views/system/operatelog/index.vue b/apps/web-ele/src/views/system/operatelog/index.vue new file mode 100644 index 0000000..a67735d --- /dev/null +++ b/apps/web-ele/src/views/system/operatelog/index.vue @@ -0,0 +1,104 @@ + + + diff --git a/apps/web-ele/src/views/system/operatelog/modules/detail.vue b/apps/web-ele/src/views/system/operatelog/modules/detail.vue new file mode 100644 index 0000000..f4d8ae9 --- /dev/null +++ b/apps/web-ele/src/views/system/operatelog/modules/detail.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-ele/src/views/system/post/data.ts b/apps/web-ele/src/views/system/post/data.ts new file mode 100644 index 0000000..d9c94a3 --- /dev/null +++ b/apps/web-ele/src/views/system/post/data.ts @@ -0,0 +1,155 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '岗位名称', + componentProps: { + placeholder: '请输入岗位名称', + }, + rules: 'required', + }, + { + component: 'Input', + fieldName: 'code', + label: '岗位编码', + componentProps: { + placeholder: '请输入岗位编码', + }, + rules: 'required', + }, + { + fieldName: 'sort', + label: '显示顺序', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入显示顺序', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '岗位状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '岗位备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入岗位备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '岗位名称', + component: 'Input', + componentProps: { + placeholder: '请输入岗位名称', + clearable: true, + }, + }, + { + fieldName: 'code', + label: '岗位编码', + component: 'Input', + componentProps: { + placeholder: '请输入岗位编码', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '岗位状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择岗位状态', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '岗位编号', + minWidth: 200, + }, + { + field: 'name', + title: '岗位名称', + minWidth: 200, + }, + { + field: 'code', + title: '岗位编码', + minWidth: 200, + }, + { + field: 'sort', + title: '显示顺序', + minWidth: 100, + }, + { + field: 'remark', + title: '岗位备注', + minWidth: 200, + }, + { + field: 'status', + title: '岗位状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/post/index.vue b/apps/web-ele/src/views/system/post/index.vue new file mode 100644 index 0000000..1d0c826 --- /dev/null +++ b/apps/web-ele/src/views/system/post/index.vue @@ -0,0 +1,183 @@ + + + diff --git a/apps/web-ele/src/views/system/post/modules/form.vue b/apps/web-ele/src/views/system/post/modules/form.vue new file mode 100644 index 0000000..87453a2 --- /dev/null +++ b/apps/web-ele/src/views/system/post/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/system/role/data.ts b/apps/web-ele/src/views/system/role/data.ts new file mode 100644 index 0000000..2d1ecb0 --- /dev/null +++ b/apps/web-ele/src/views/system/role/data.ts @@ -0,0 +1,264 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { + CommonStatusEnum, + DICT_TYPE, + SystemDataScopeEnum, +} from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '角色名称', + component: 'Input', + componentProps: { + placeholder: '请输入角色名称', + }, + rules: 'required', + }, + { + fieldName: 'code', + label: '角色标识', + component: 'Input', + componentProps: { + placeholder: '请输入角色标识', + }, + rules: 'required', + }, + { + fieldName: 'sort', + label: '显示顺序', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入显示顺序', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '角色状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '角色备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入角色备注', + }, + }, + ]; +} + +/** 分配数据权限的表单 */ +export function useAssignDataPermissionFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '角色名称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + component: 'Input', + fieldName: 'code', + label: '角色标识', + componentProps: { + disabled: true, + }, + }, + { + component: 'Select', + fieldName: 'dataScope', + label: '权限范围', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE, 'number'), + }, + }, + { + fieldName: 'dataScopeDeptIds', + label: '部门范围', + component: 'Input', + formItemClass: 'items-start', + dependencies: { + triggerFields: ['dataScope'], + show: (values) => { + return values.dataScope === SystemDataScopeEnum.DEPT_CUSTOM; + }, + }, + }, + ]; +} + +/** 分配菜单的表单 */ +export function useAssignMenuFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '角色名称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'code', + label: '角色标识', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'menuIds', + label: '菜单权限', + component: 'Input', + formItemClass: 'items-start', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '角色名称', + component: 'Input', + componentProps: { + placeholder: '请输入角色名称', + clearable: true, + }, + }, + { + fieldName: 'code', + label: '角色标识', + component: 'Input', + componentProps: { + placeholder: '请输入角色标识', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '角色状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择角色状态', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '角色编号', + minWidth: 100, + }, + { + field: 'name', + title: '角色名称', + minWidth: 200, + }, + { + field: 'type', + title: '角色类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_ROLE_TYPE }, + }, + }, + { + field: 'code', + title: '角色标识', + minWidth: 200, + }, + { + field: 'sort', + title: '显示顺序', + minWidth: 100, + }, + { + field: 'remark', + title: '角色备注', + minWidth: 100, + }, + { + field: 'status', + title: '角色状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 240, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/role/index.vue b/apps/web-ele/src/views/system/role/index.vue new file mode 100644 index 0000000..de79b99 --- /dev/null +++ b/apps/web-ele/src/views/system/role/index.vue @@ -0,0 +1,232 @@ + + + diff --git a/apps/web-ele/src/views/system/role/modules/assign-data-permission-form.vue b/apps/web-ele/src/views/system/role/modules/assign-data-permission-form.vue new file mode 100644 index 0000000..c78f904 --- /dev/null +++ b/apps/web-ele/src/views/system/role/modules/assign-data-permission-form.vue @@ -0,0 +1,167 @@ + + + diff --git a/apps/web-ele/src/views/system/role/modules/assign-menu-form.vue b/apps/web-ele/src/views/system/role/modules/assign-menu-form.vue new file mode 100644 index 0000000..463a16a --- /dev/null +++ b/apps/web-ele/src/views/system/role/modules/assign-menu-form.vue @@ -0,0 +1,169 @@ + + + diff --git a/apps/web-ele/src/views/system/role/modules/form.vue b/apps/web-ele/src/views/system/role/modules/form.vue new file mode 100644 index 0000000..90522e3 --- /dev/null +++ b/apps/web-ele/src/views/system/role/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/system/sms/channel/data.ts b/apps/web-ele/src/views/system/sms/channel/data.ts new file mode 100644 index 0000000..f244c49 --- /dev/null +++ b/apps/web-ele/src/views/system/sms/channel/data.ts @@ -0,0 +1,193 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'signature', + label: '短信签名', + component: 'Input', + componentProps: { + placeholder: '请输入短信签名', + }, + rules: 'required', + }, + { + fieldName: 'code', + label: '渠道编码', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, 'string'), + placeholder: '请选择短信渠道', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '启用状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + { + fieldName: 'apiKey', + label: '短信 API 的账号', + component: 'Input', + componentProps: { + placeholder: '请输入短信 API 的账号', + }, + rules: 'required', + }, + { + fieldName: 'apiSecret', + label: '短信 API 的密钥', + component: 'Input', + componentProps: { + placeholder: '请输入短信 API 的密钥', + }, + }, + { + fieldName: 'callbackUrl', + label: '短信发送回调 URL', + component: 'Input', + componentProps: { + placeholder: '请输入短信发送回调 URL', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'signature', + label: '短信签名', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入短信签名', + }, + }, + { + fieldName: 'code', + label: '渠道编码', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, 'string'), + placeholder: '请选择短信渠道', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'signature', + title: '短信签名', + minWidth: 120, + }, + { + field: 'code', + title: '渠道编码', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE }, + }, + }, + { + field: 'status', + title: '启用状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'apiKey', + title: '短信 API 的账号', + minWidth: 180, + }, + { + field: 'apiSecret', + title: '短信 API 的密钥', + minWidth: 180, + }, + { + field: 'callbackUrl', + title: '短信发送回调 URL', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'remark', + title: '备注', + minWidth: 120, + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/sms/channel/index.vue b/apps/web-ele/src/views/system/sms/channel/index.vue new file mode 100644 index 0000000..b4400a2 --- /dev/null +++ b/apps/web-ele/src/views/system/sms/channel/index.vue @@ -0,0 +1,173 @@ + + + diff --git a/apps/web-ele/src/views/system/sms/channel/modules/form.vue b/apps/web-ele/src/views/system/sms/channel/modules/form.vue new file mode 100644 index 0000000..2e11fc8 --- /dev/null +++ b/apps/web-ele/src/views/system/sms/channel/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-ele/src/views/system/sms/log/data.ts b/apps/web-ele/src/views/system/sms/log/data.ts new file mode 100644 index 0000000..3657516 --- /dev/null +++ b/apps/web-ele/src/views/system/sms/log/data.ts @@ -0,0 +1,265 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { getSimpleSmsChannelList } from '#/api/system/sms/channel'; +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'mobile', + label: '手机号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入手机号', + }, + }, + { + fieldName: 'channelId', + label: '短信渠道', + component: 'ApiSelect', + componentProps: { + api: getSimpleSmsChannelList, + labelField: 'signature', + valueField: 'id', + clearable: true, + placeholder: '请选择短信渠道', + }, + }, + { + fieldName: 'templateId', + label: '模板编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编号', + }, + }, + { + fieldName: 'sendStatus', + label: '发送状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_SEND_STATUS, 'number'), + clearable: true, + placeholder: '请选择发送状态', + }, + }, + { + fieldName: 'sendTime', + label: '发送时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'receiveStatus', + label: '接收状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS, 'number'), + clearable: true, + placeholder: '请选择接收状态', + }, + }, + { + fieldName: 'receiveTime', + label: '接收时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'mobile', + title: '手机号', + minWidth: 120, + }, + { + field: 'templateContent', + title: '短信内容', + minWidth: 300, + }, + { + field: 'sendStatus', + title: '发送状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_SEND_STATUS }, + }, + }, + { + field: 'sendTime', + title: '发送时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'receiveStatus', + title: '接收状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS }, + }, + }, + { + field: 'receiveTime', + title: '接收时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'channelCode', + title: '短信渠道', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE }, + }, + }, + { + field: 'templateId', + title: '模板编号', + minWidth: 100, + }, + { + field: 'templateType', + title: '短信类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'createTime', + label: '创建时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'mobile', + label: '手机号', + }, + { + field: 'channelCode', + label: '短信渠道', + }, + { + field: 'templateId', + label: '模板编号', + }, + { + field: 'templateType', + label: '模板类型', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, + value: val, + }); + }, + }, + { + field: 'templateContent', + label: '短信内容', + }, + { + field: 'sendStatus', + label: '发送状态', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_SMS_SEND_STATUS, + value: val, + }); + }, + }, + { + field: 'sendTime', + label: '发送时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'apiSendCode', + label: 'API 发送编码', + }, + { + field: 'apiSendMsg', + label: 'API 发送消息', + }, + { + field: 'receiveStatus', + label: '接收状态', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS, + value: val, + }); + }, + }, + { + field: 'receiveTime', + label: '接收时间', + render: (val) => formatDateTime(val) as string, + }, + { + field: 'apiReceiveCode', + label: 'API 接收编码', + }, + { + field: 'apiReceiveMsg', + label: 'API 接收消息', + span: 2, + }, + { + field: 'apiRequestId', + label: 'API 请求 ID', + }, + { + field: 'apiSerialNo', + label: 'API 序列号', + }, + ]; +} diff --git a/apps/web-ele/src/views/system/sms/log/index.vue b/apps/web-ele/src/views/system/sms/log/index.vue new file mode 100644 index 0000000..0bd3a92 --- /dev/null +++ b/apps/web-ele/src/views/system/sms/log/index.vue @@ -0,0 +1,104 @@ + + + diff --git a/apps/web-ele/src/views/system/sms/log/modules/detail.vue b/apps/web-ele/src/views/system/sms/log/modules/detail.vue new file mode 100644 index 0000000..37af10f --- /dev/null +++ b/apps/web-ele/src/views/system/sms/log/modules/detail.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/web-ele/src/views/system/sms/template/data.ts b/apps/web-ele/src/views/system/sms/template/data.ts new file mode 100644 index 0000000..0a3d3e8 --- /dev/null +++ b/apps/web-ele/src/views/system/sms/template/data.ts @@ -0,0 +1,272 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleSmsChannelList } from '#/api/system/sms/channel'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'type', + label: '短信类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, 'number'), + placeholder: '请选择短信类型', + }, + rules: 'required', + }, + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + placeholder: '请输入模板名称', + }, + rules: 'required', + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + placeholder: '请输入模板编码', + }, + rules: 'required', + }, + { + fieldName: 'channelId', + label: '短信渠道', + component: 'ApiSelect', + componentProps: { + api: getSimpleSmsChannelList, + labelField: 'signature', + valueField: 'id', + placeholder: '请选择短信渠道', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'content', + label: '模板内容', + component: 'Textarea', + componentProps: { + placeholder: '请输入模板内容', + rows: 4, + }, + rules: 'required', + }, + { + fieldName: 'apiTemplateId', + label: '短信 API 的模板编号', + component: 'Input', + componentProps: { + placeholder: '请输入短信 API 的模板编号', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'type', + label: '短信类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, 'number'), + clearable: true, + placeholder: '请选择短信类型', + }, + }, + { + fieldName: 'status', + label: '开启状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + clearable: true, + placeholder: '请选择开启状态', + }, + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编码', + }, + }, + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板名称', + }, + }, + { + fieldName: 'channelId', + label: '短信渠道', + component: 'ApiSelect', + componentProps: { + api: getSimpleSmsChannelList, + labelField: 'signature', + valueField: 'id', + clearable: true, + placeholder: '请选择短信渠道', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 发送短信表单 */ +export function useSendSmsFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'content', + label: '模板内容', + component: 'Textarea', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'mobile', + label: '手机号码', + component: 'Input', + componentProps: { + placeholder: '请输入手机号码', + }, + rules: 'required', + }, + { + fieldName: 'templateParams', + label: '模板参数', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'type', + title: '短信类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE }, + }, + }, + { + field: 'name', + title: '模板名称', + minWidth: 120, + }, + { + field: 'code', + title: '模板编码', + minWidth: 120, + }, + { + field: 'content', + title: '模板内容', + minWidth: 200, + }, + { + field: 'status', + title: '开启状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'apiTemplateId', + title: '短信 API 的模板编号', + minWidth: 180, + }, + { + field: 'channelCode', + title: '短信渠道', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'remark', + title: '备注', + minWidth: 120, + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/sms/template/index.vue b/apps/web-ele/src/views/system/sms/template/index.vue new file mode 100644 index 0000000..0d340c2 --- /dev/null +++ b/apps/web-ele/src/views/system/sms/template/index.vue @@ -0,0 +1,207 @@ + + + diff --git a/apps/web-ele/src/views/system/sms/template/modules/form.vue b/apps/web-ele/src/views/system/sms/template/modules/form.vue new file mode 100644 index 0000000..25aea86 --- /dev/null +++ b/apps/web-ele/src/views/system/sms/template/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/system/sms/template/modules/send-form.vue b/apps/web-ele/src/views/system/sms/template/modules/send-form.vue new file mode 100644 index 0000000..a02b8e0 --- /dev/null +++ b/apps/web-ele/src/views/system/sms/template/modules/send-form.vue @@ -0,0 +1,109 @@ + + + diff --git a/apps/web-ele/src/views/system/social/client/data.ts b/apps/web-ele/src/views/system/social/client/data.ts new file mode 100644 index 0000000..f7f15d6 --- /dev/null +++ b/apps/web-ele/src/views/system/social/client/data.ts @@ -0,0 +1,208 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { + CommonStatusEnum, + DICT_TYPE, + SystemUserSocialTypeEnum, +} from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '应用名', + component: 'Input', + componentProps: { + placeholder: '请输入应用名', + }, + rules: 'required', + }, + { + fieldName: 'socialType', + label: '社交平台', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE, 'number'), + placeholder: '请选择社交平台', + }, + rules: 'required', + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + }, + rules: 'required', + }, + { + fieldName: 'clientId', + label: '客户端编号', + component: 'Input', + componentProps: { + placeholder: '请输入客户端编号,对应各平台的 appKey', + }, + rules: 'required', + }, + { + fieldName: 'clientSecret', + label: '客户端密钥', + component: 'Input', + componentProps: { + placeholder: '请输入客户端密钥,对应各平台的 appSecret', + }, + rules: 'required', + }, + { + fieldName: 'agentId', + label: 'agentId', + component: 'Input', + componentProps: { + placeholder: '授权方的网页应用 ID,有则填', + }, + dependencies: { + triggerFields: ['socialType'], + show: (values) => + values.socialType === SystemUserSocialTypeEnum.WECHAT_ENTERPRISE.type, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '应用名', + component: 'Input', + componentProps: { + placeholder: '请输入应用名', + clearable: true, + }, + }, + { + fieldName: 'socialType', + label: '社交平台', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE, 'number'), + placeholder: '请选择社交平台', + clearable: true, + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + placeholder: '请选择用户类型', + clearable: true, + }, + }, + { + fieldName: 'clientId', + label: '客户端编号', + component: 'Input', + componentProps: { + placeholder: '请输入客户端编号', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '应用名', + minWidth: 120, + }, + { + field: 'socialType', + title: '社交平台', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SOCIAL_TYPE }, + }, + }, + { + field: 'userType', + title: '用户类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.USER_TYPE }, + }, + }, + { + field: 'clientId', + title: '客户端编号', + minWidth: 180, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/social/client/index.vue b/apps/web-ele/src/views/system/social/client/index.vue new file mode 100644 index 0000000..9c7b733 --- /dev/null +++ b/apps/web-ele/src/views/system/social/client/index.vue @@ -0,0 +1,173 @@ + + + diff --git a/apps/web-ele/src/views/system/social/client/modules/form.vue b/apps/web-ele/src/views/system/social/client/modules/form.vue new file mode 100644 index 0000000..47bd5fd --- /dev/null +++ b/apps/web-ele/src/views/system/social/client/modules/form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-ele/src/views/system/social/user/data.ts b/apps/web-ele/src/views/system/social/user/data.ts new file mode 100644 index 0000000..77847d0 --- /dev/null +++ b/apps/web-ele/src/views/system/social/user/data.ts @@ -0,0 +1,162 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { ElImage } from 'element-plus'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'type', + label: '社交平台', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE, 'number'), + placeholder: '请选择社交平台', + clearable: true, + }, + }, + { + fieldName: 'nickname', + label: '用户昵称', + component: 'Input', + componentProps: { + placeholder: '请输入用户昵称', + clearable: true, + }, + }, + { + fieldName: 'openid', + label: '社交 openid', + component: 'Input', + componentProps: { + placeholder: '请输入社交 openid', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'type', + title: '社交平台', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SOCIAL_TYPE }, + }, + }, + { + field: 'openid', + title: '社交 openid', + minWidth: 180, + }, + { + field: 'nickname', + title: '用户昵称', + minWidth: 120, + }, + { + field: 'avatar', + title: '用户头像', + minWidth: 100, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'updateTime', + title: '更新时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'type', + label: '社交平台', + render: (val) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_SOCIAL_TYPE, + value: val, + }); + }, + }, + { + field: 'nickname', + label: '用户昵称', + }, + { + field: 'avatar', + label: '用户头像', + render: (val) => { + if (val) { + return h(ElImage, { + src: val, + previewSrcList: [val], + class: 'w-10 h-10 cursor-pointer', + previewTeleported: true, + }); + } + return '无'; + }, + }, + { + field: 'token', + label: '社交 token', + }, + { + field: 'rawTokenInfo', + label: '原始 Token 数据', + }, + { + field: 'rawUserInfo', + label: '原始 User 数据', + }, + { + field: 'code', + label: '最后一次的认证 code', + }, + { + field: 'state', + label: '最后一次的认证 state', + }, + ]; +} diff --git a/apps/web-ele/src/views/system/social/user/index.vue b/apps/web-ele/src/views/system/social/user/index.vue new file mode 100644 index 0000000..11dc331 --- /dev/null +++ b/apps/web-ele/src/views/system/social/user/index.vue @@ -0,0 +1,79 @@ + + + diff --git a/apps/web-ele/src/views/system/social/user/modules/detail.vue b/apps/web-ele/src/views/system/social/user/modules/detail.vue new file mode 100644 index 0000000..a4edf00 --- /dev/null +++ b/apps/web-ele/src/views/system/social/user/modules/detail.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-ele/src/views/system/tenant/data.ts b/apps/web-ele/src/views/system/tenant/data.ts new file mode 100644 index 0000000..54ac877 --- /dev/null +++ b/apps/web-ele/src/views/system/tenant/data.ts @@ -0,0 +1,259 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemTenantPackageApi } from '#/api/system/tenant-package'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getTenantPackageList } from '#/api/system/tenant-package'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 关联数据 */ +let tenantPackageList: SystemTenantPackageApi.TenantPackage[] = []; +getTenantPackageList().then((data) => (tenantPackageList = data)); + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '租户名称', + component: 'Input', + componentProps: { + placeholder: '请输入租户名称', + }, + rules: 'required', + }, + { + fieldName: 'packageId', + label: '租户套餐', + component: 'ApiSelect', + componentProps: { + api: getTenantPackageList, + labelField: 'name', + valueField: 'id', + placeholder: '请选择租户套餐', + }, + rules: 'required', + }, + { + fieldName: 'contactName', + label: '联系人', + component: 'Input', + componentProps: { + placeholder: '请输入联系人', + }, + rules: 'required', + }, + { + fieldName: 'contactMobile', + label: '联系手机', + component: 'Input', + componentProps: { + placeholder: '请输入联系手机', + }, + rules: 'mobile', + }, + { + label: '用户名称', + fieldName: 'username', + component: 'Input', + componentProps: { + placeholder: '请输入用户名称', + }, + rules: 'required', + dependencies: { + triggerFields: ['id'], + show: (values) => !values.id, + }, + }, + { + label: '用户密码', + fieldName: 'password', + component: 'Input', + componentProps: { + showPassword: true, + }, + rules: 'required', + dependencies: { + triggerFields: ['id'], + show: (values) => !values.id, + }, + }, + { + label: '账号额度', + fieldName: 'accountCount', + component: 'InputNumber', + componentProps: { + placeholder: '请输入账号额度', + controlsPosition: 'right', + class: '!w-full', + }, + rules: 'required', + }, + { + label: '过期时间', + fieldName: 'expireTime', + component: 'DatePicker', + componentProps: { + format: 'YYYY-MM-DD', + valueFormat: 'x', + placeholder: '请选择过期时间', + class: '!w-full', + }, + rules: 'required', + }, + { + label: '绑定域名', + fieldName: 'websites', + component: 'InputTag', + componentProps: { + placeholder: '请输入绑定域名', + }, + }, + { + fieldName: 'status', + label: '租户状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '租户名', + component: 'Input', + componentProps: { + placeholder: '请输入租户名', + clearable: true, + }, + }, + { + fieldName: 'contactName', + label: '联系人', + component: 'Input', + componentProps: { + placeholder: '请输入联系人', + clearable: true, + }, + }, + { + fieldName: 'contactMobile', + label: '联系手机', + component: 'Input', + componentProps: { + placeholder: '请输入联系手机', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + placeholder: '请选择状态', + clearable: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '租户编号', + minWidth: 100, + }, + { + field: 'name', + title: '租户名', + minWidth: 180, + }, + { + field: 'packageId', + title: '租户套餐', + minWidth: 180, + formatter: ({ cellValue }) => { + return cellValue === 0 + ? '系统租户' + : tenantPackageList.find((pkg) => pkg.id === cellValue)?.name || '-'; + }, + }, + { + field: 'contactName', + title: '联系人', + minWidth: 100, + }, + { + field: 'contactMobile', + title: '联系手机', + minWidth: 180, + }, + { + field: 'accountCount', + title: '账号额度', + minWidth: 100, + }, + { + field: 'expireTime', + title: '过期时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'websites', + title: '绑定域名', + minWidth: 180, + }, + { + field: 'status', + title: '租户状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/tenant/index.vue b/apps/web-ele/src/views/system/tenant/index.vue new file mode 100644 index 0000000..25a1be3 --- /dev/null +++ b/apps/web-ele/src/views/system/tenant/index.vue @@ -0,0 +1,186 @@ + + diff --git a/apps/web-ele/src/views/system/tenant/modules/form.vue b/apps/web-ele/src/views/system/tenant/modules/form.vue new file mode 100644 index 0000000..ccdd99f --- /dev/null +++ b/apps/web-ele/src/views/system/tenant/modules/form.vue @@ -0,0 +1,80 @@ + + diff --git a/apps/web-ele/src/views/system/tenantPackage/data.ts b/apps/web-ele/src/views/system/tenantPackage/data.ts new file mode 100644 index 0000000..01f165e --- /dev/null +++ b/apps/web-ele/src/views/system/tenantPackage/data.ts @@ -0,0 +1,131 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '套餐名称', + component: 'Input', + componentProps: { + placeholder: '请输入套餐名称', + }, + rules: 'required', + }, + { + fieldName: 'menuIds', + label: '菜单权限', + component: 'Input', + formItemClass: 'items-start', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '套餐名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入套餐名称', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + clearable: true, + placeholder: '请选择状态', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '套餐编号', + minWidth: 100, + }, + { + field: 'name', + title: '套餐名称', + minWidth: 180, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/tenantPackage/index.vue b/apps/web-ele/src/views/system/tenantPackage/index.vue new file mode 100644 index 0000000..4a76c12 --- /dev/null +++ b/apps/web-ele/src/views/system/tenantPackage/index.vue @@ -0,0 +1,173 @@ + + + diff --git a/apps/web-ele/src/views/system/tenantPackage/modules/form.vue b/apps/web-ele/src/views/system/tenantPackage/modules/form.vue new file mode 100644 index 0000000..c789d64 --- /dev/null +++ b/apps/web-ele/src/views/system/tenantPackage/modules/form.vue @@ -0,0 +1,161 @@ + + + diff --git a/apps/web-ele/src/views/system/user/components/index.ts b/apps/web-ele/src/views/system/user/components/index.ts new file mode 100644 index 0000000..9cb1c93 --- /dev/null +++ b/apps/web-ele/src/views/system/user/components/index.ts @@ -0,0 +1 @@ +export { default as UserSelectModal } from './user-select-modal.vue'; diff --git a/apps/web-ele/src/views/system/user/components/user-select-modal.vue b/apps/web-ele/src/views/system/user/components/user-select-modal.vue new file mode 100644 index 0000000..a9c2b1b --- /dev/null +++ b/apps/web-ele/src/views/system/user/components/user-select-modal.vue @@ -0,0 +1,518 @@ + + + + + diff --git a/apps/web-ele/src/views/system/user/data.ts b/apps/web-ele/src/views/system/user/data.ts new file mode 100644 index 0000000..98cf580 --- /dev/null +++ b/apps/web-ele/src/views/system/user/data.ts @@ -0,0 +1,352 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemUserApi } from '#/api/system/user'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { $t } from '@vben/locales'; +import { handleTree } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getDeptList } from '#/api/system/dept'; +import { getSimplePostList } from '#/api/system/post'; +import { getSimpleRoleList } from '#/api/system/role'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'username', + label: '用户名称', + component: 'Input', + componentProps: { + placeholder: '请输入用户名称', + }, + rules: 'required', + }, + { + label: '用户密码', + fieldName: 'password', + component: 'Input', + componentProps: { + showPassword: true, + }, + rules: 'required', + dependencies: { + triggerFields: ['id'], + show: (values) => !values.id, + }, + }, + { + fieldName: 'nickname', + label: '用户昵称', + component: 'Input', + componentProps: { + placeholder: '请输入用户昵称', + }, + rules: 'required', + }, + { + fieldName: 'deptId', + label: '归属部门', + component: 'ApiTreeSelect', + componentProps: { + api: async () => { + const data = await getDeptList(); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择归属部门', + treeDefaultExpandAll: true, + }, + }, + { + fieldName: 'postIds', + label: '岗位', + component: 'ApiSelect', + componentProps: { + api: getSimplePostList, + labelField: 'name', + valueField: 'id', + multiple: true, + placeholder: '请选择岗位', + }, + }, + { + fieldName: 'email', + label: '邮箱', + component: 'Input', + rules: z.string().email('邮箱格式不正确').or(z.literal('')).optional(), + componentProps: { + placeholder: '请输入邮箱', + }, + }, + { + fieldName: 'mobile', + label: '手机号码', + component: 'Input', + componentProps: { + placeholder: '请输入手机号码', + }, + }, + { + fieldName: 'sex', + label: '用户性别', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + }, + rules: z.number().default(1), + }, + { + fieldName: 'status', + label: '用户状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Textarea', + componentProps: { + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 重置密码的表单 */ +export function useResetPasswordFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'VbenInputPassword', + componentProps: { + passwordStrength: true, + placeholder: '请输入新密码', + }, + dependencies: { + rules(values) { + return z + .string({ message: '请输入新密码' }) + .min(5, '密码长度不能少于 5 个字符') + .max(20, '密码长度不能超过 20 个字符') + .refine( + (value) => value !== values.oldPassword, + '新旧密码不能相同', + ); + }, + triggerFields: ['newPassword', 'oldPassword'], + }, + fieldName: 'newPassword', + label: '新密码', + rules: 'required', + }, + { + component: 'VbenInputPassword', + componentProps: { + passwordStrength: true, + placeholder: $t('authentication.confirmPassword'), + }, + dependencies: { + rules(values) { + return z + .string({ message: '请输入确认密码' }) + .min(5, '密码长度不能少于 5 个字符') + .max(20, '密码长度不能超过 20 个字符') + .refine( + (value) => value === values.newPassword, + '新密码和确认密码不一致', + ); + }, + triggerFields: ['newPassword', 'confirmPassword'], + }, + fieldName: 'confirmPassword', + label: '确认密码', + rules: 'required', + }, + ]; +} + +/** 分配角色的表单 */ +export function useAssignRoleFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'username', + label: '用户名称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'nickname', + label: '用户昵称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'roleIds', + label: '角色', + component: 'ApiSelect', + componentProps: { + api: getSimpleRoleList, + labelField: 'name', + valueField: 'id', + multiple: true, + placeholder: '请选择角色', + }, + }, + ]; +} + +/** 用户导入的表单 */ +export function useImportFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'file', + label: '用户数据', + component: 'Upload', + rules: 'required', + help: '仅允许导入 xls、xlsx 格式文件', + }, + { + fieldName: 'updateSupport', + label: '是否覆盖', + component: 'Switch', + componentProps: { + checkedChildren: '是', + unCheckedChildren: '否', + }, + rules: z.boolean().default(false), + help: '是否更新已经存在的用户数据', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'username', + label: '用户名称', + component: 'Input', + componentProps: { + placeholder: '请输入用户名称', + clearable: true, + }, + }, + { + fieldName: 'mobile', + label: '手机号码', + component: 'Input', + componentProps: { + placeholder: '请输入手机号码', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onStatusChange?: ( + newStatus: number, + row: SystemUserApi.User, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '用户编号', + minWidth: 100, + }, + { + field: 'username', + title: '用户名称', + minWidth: 120, + }, + { + field: 'nickname', + title: '用户昵称', + minWidth: 120, + }, + { + field: 'deptName', + title: '部门', + minWidth: 120, + }, + { + field: 'mobile', + title: '手机号码', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + align: 'center', + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: 'CellSwitch', + props: { + activeValue: CommonStatusEnum.ENABLE, + inactiveValue: CommonStatusEnum.DISABLE, + }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-ele/src/views/system/user/index.vue b/apps/web-ele/src/views/system/user/index.vue new file mode 100644 index 0000000..e56a898 --- /dev/null +++ b/apps/web-ele/src/views/system/user/index.vue @@ -0,0 +1,296 @@ + + + diff --git a/apps/web-ele/src/views/system/user/modules/assign-role-form.vue b/apps/web-ele/src/views/system/user/modules/assign-role-form.vue new file mode 100644 index 0000000..9d3e0d8 --- /dev/null +++ b/apps/web-ele/src/views/system/user/modules/assign-role-form.vue @@ -0,0 +1,78 @@ + + + diff --git a/apps/web-ele/src/views/system/user/modules/dept-tree.vue b/apps/web-ele/src/views/system/user/modules/dept-tree.vue new file mode 100644 index 0000000..9c4d5a8 --- /dev/null +++ b/apps/web-ele/src/views/system/user/modules/dept-tree.vue @@ -0,0 +1,81 @@ + + + diff --git a/apps/web-ele/src/views/system/user/modules/form.vue b/apps/web-ele/src/views/system/user/modules/form.vue new file mode 100644 index 0000000..e20eb82 --- /dev/null +++ b/apps/web-ele/src/views/system/user/modules/form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/system/user/modules/import-form.vue b/apps/web-ele/src/views/system/user/modules/import-form.vue new file mode 100644 index 0000000..6222bf4 --- /dev/null +++ b/apps/web-ele/src/views/system/user/modules/import-form.vue @@ -0,0 +1,82 @@ + + + diff --git a/apps/web-ele/src/views/system/user/modules/reset-password-form.vue b/apps/web-ele/src/views/system/user/modules/reset-password-form.vue new file mode 100644 index 0000000..bd6ef27 --- /dev/null +++ b/apps/web-ele/src/views/system/user/modules/reset-password-form.vue @@ -0,0 +1,66 @@ + + + diff --git a/apps/web-ele/tailwind.config.mjs b/apps/web-ele/tailwind.config.mjs new file mode 100644 index 0000000..f17f556 --- /dev/null +++ b/apps/web-ele/tailwind.config.mjs @@ -0,0 +1 @@ +export { default } from '@vben/tailwind-config'; diff --git a/apps/web-ele/tsconfig.json b/apps/web-ele/tsconfig.json new file mode 100644 index 0000000..02c287f --- /dev/null +++ b/apps/web-ele/tsconfig.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/tsconfig/web-app.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "#/*": ["./src/*"] + } + }, + "references": [{ "path": "./tsconfig.node.json" }], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/apps/web-ele/tsconfig.node.json b/apps/web-ele/tsconfig.node.json new file mode 100644 index 0000000..c2f0d86 --- /dev/null +++ b/apps/web-ele/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/tsconfig/node.json", + "compilerOptions": { + "composite": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "noEmit": false + }, + "include": ["vite.config.mts"] +} diff --git a/apps/web-ele/vite.config.mts b/apps/web-ele/vite.config.mts new file mode 100644 index 0000000..8a36bcb --- /dev/null +++ b/apps/web-ele/vite.config.mts @@ -0,0 +1,27 @@ +import { defineConfig } from '@vben/vite-config'; + +import ElementPlus from 'unplugin-element-plus/vite'; + +export default defineConfig(async () => { + return { + application: {}, + vite: { + plugins: [ + ElementPlus({ + format: 'esm', + }), + ], + server: { + proxy: { + '/admin-api': { + changeOrigin: true, + rewrite: (path) => path.replace(/^\/admin-api/, ''), + // mock代理目标地址 + target: 'http://localhost:48080/admin-api', + ws: true, + }, + }, + }, + }, + }; +}); diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000..9bacfa3 --- /dev/null +++ b/cspell.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", + "version": "0.2", + "language": "en,en-US", + "allowCompoundWords": true, + "words": [ + "acmr", + "antd", + "antdv", + "archiver", + "astro", + "axios", + "brotli", + "clsx", + "cropperjs", + "defu", + "demi", + "dotenv", + "echarts", + "ependencies", + "esno", + "etag", + "execa", + "gitee", + "iconify", + "iconoir", + "intlify", + "isequal", + "jspm", + "kefu", + "lockb", + "lucide", + "minh", + "minw", + "mkdist", + "mockjs", + "naiveui", + "napi", + "nocheck", + "nolebase", + "noopener", + "noreferrer", + "nprogress", + "nuxt", + "pinia", + "prefixs", + "publint", + "qrcode", + "reka", + "rollup", + "shadcn", + "sonner", + "sortablejs", + "styl", + "taze", + "Tinymce", + "tdesign", + "ui-kit", + "uicons", + "unplugin", + "unref", + "vben", + "vbenjs", + "vite", + "vitejs", + "vitepress", + "vitest", + "vnode", + "vueuse", + "xingyu", + "yudao", + "yxxx" + ], + "ignorePaths": [ + "**/node_modules/**", + "**/dist/**", + "**/*-dist/**", + "**/icons/**", + "pnpm-lock.yaml", + "**/*.log", + "**/*.test.ts", + "**/*.spec.ts", + "**/__tests__/**" + ] +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..b29b567 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,5 @@ +// @ts-check + +import { defineConfig } from '@vben/eslint-config'; + +export default defineConfig(); diff --git a/internal/lint-configs/commitlint-config/index.mjs b/internal/lint-configs/commitlint-config/index.mjs new file mode 100644 index 0000000..36c3b09 --- /dev/null +++ b/internal/lint-configs/commitlint-config/index.mjs @@ -0,0 +1,153 @@ +import { execSync } from 'node:child_process'; + +import { getPackagesSync } from '@vben/node-utils'; + +const { packages } = getPackagesSync(); + +const allowedScopes = [ + ...packages.map((pkg) => pkg.packageJson.name), + 'project', + 'style', + 'lint', + 'ci', + 'dev', + 'deploy', + 'other', +]; + +// precomputed scope +const scopeComplete = execSync('git status --porcelain || true') + .toString() + .trim() + .split('\n') + .find((r) => ~r.indexOf('M src')) + ?.replaceAll(/(\/)/g, '%%') + ?.match(/src%%((\w|-)*)/)?.[1] + ?.replace(/s$/, ''); + +/** + * @type {import('cz-git').UserConfig} + */ +const userConfig = { + extends: ['@commitlint/config-conventional'], + plugins: ['commitlint-plugin-function-rules'], + prompt: { + /** @use `pnpm commit :f` */ + alias: { + b: 'build: bump dependencies', + c: 'chore: update config', + f: 'docs: fix typos', + r: 'docs: update README', + s: 'style: update code format', + }, + allowCustomIssuePrefixs: false, + // scopes: [...scopes, 'mock'], + allowEmptyIssuePrefixs: false, + customScopesAlign: scopeComplete ? 'bottom' : 'top', + defaultScope: scopeComplete, + // English + typesAppend: [ + { name: 'workflow: workflow improvements', value: 'workflow' }, + { name: 'types: type definition file changes', value: 'types' }, + ], + + // 中英文对照版 + // messages: { + // type: '选择你要提交的类型 :', + // scope: '选择一个提交范围 (可选):', + // customScope: '请输入自定义的提交范围 :', + // subject: '填写简短精炼的变更描述 :\n', + // body: '填写更加详细的变更描述 (可选)。使用 "|" 换行 :\n', + // breaking: '列举非兼容性重大的变更 (可选)。使用 "|" 换行 :\n', + // footerPrefixsSelect: '选择关联issue前缀 (可选):', + // customFooterPrefixs: '输入自定义issue前缀 :', + // footer: '列举关联issue (可选) 例如: #31, #I3244 :\n', + // confirmCommit: '是否提交或修改commit ?', + // }, + // types: [ + // { value: 'feat', name: 'feat: 新增功能' }, + // { value: 'fix', name: 'fix: 修复缺陷' }, + // { value: 'docs', name: 'docs: 文档变更' }, + // { value: 'style', name: 'style: 代码格式' }, + // { value: 'refactor', name: 'refactor: 代码重构' }, + // { value: 'perf', name: 'perf: 性能优化' }, + // { value: 'test', name: 'test: 添加疏漏测试或已有测试改动' }, + // { value: 'build', name: 'build: 构建流程、外部依赖变更 (如升级 npm 包、修改打包配置等)' }, + // { value: 'ci', name: 'ci: 修改 CI 配置、脚本' }, + // { value: 'revert', name: 'revert: 回滚 commit' }, + // { value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改 (不影响源文件、测试用例)' }, + // { value: 'wip', name: 'wip: 正在开发中' }, + // { value: 'workflow', name: 'workflow: 工作流程改进' }, + // { value: 'types', name: 'types: 类型定义文件修改' }, + // ], + // emptyScopesAlias: 'empty: 不填写', + // customScopesAlias: 'custom: 自定义', + }, + rules: { + /** + * type[scope]: [function] description + * + * ^^^^^^^^^^^^^^ empty line. + * - Something here + */ + 'body-leading-blank': [2, 'always'], + /** + * type[scope]: [function] description + * + * - something here + * + * ^^^^^^^^^^^^^^ + */ + 'footer-leading-blank': [1, 'always'], + /** + * type[scope]: [function] description + * ^^^^^ + */ + 'function-rules/scope-enum': [ + 2, // level: error + 'always', + (parsed) => { + if (!parsed.scope || allowedScopes.includes(parsed.scope)) { + return [true]; + } + + return [false, `scope must be one of ${allowedScopes.join(', ')}`]; + }, + ], + /** + * type[scope]: [function] description [No more than 108 characters] + * ^^^^^ + */ + 'header-max-length': [2, 'always', 108], + + 'scope-enum': [0], + 'subject-case': [0], + 'subject-empty': [2, 'never'], + 'type-empty': [2, 'never'], + /** + * type[scope]: [function] description + * ^^^^ + */ + 'type-enum': [ + 2, + 'always', + [ + 'feat', + 'fix', + 'perf', + 'style', + 'docs', + 'test', + 'refactor', + 'build', + 'ci', + 'chore', + 'revert', + 'types', + 'release', + ], + ], + }, +}; + +export default userConfig; diff --git a/internal/lint-configs/commitlint-config/package.json b/internal/lint-configs/commitlint-config/package.json new file mode 100644 index 0000000..0c3a3aa --- /dev/null +++ b/internal/lint-configs/commitlint-config/package.json @@ -0,0 +1,33 @@ +{ + "name": "@vben/commitlint-config", + "version": "5.5.9", + "private": true, + "homepage": "https://github.com/vbenjs/vue-vben-admin", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "internal/lint-configs/commitlint-config" + }, + "license": "MIT", + "type": "module", + "files": [ + "dist" + ], + "main": "./index.mjs", + "module": "./index.mjs", + "exports": { + ".": { + "import": "./index.mjs", + "default": "./index.mjs" + } + }, + "dependencies": { + "@commitlint/cli": "catalog:", + "@commitlint/config-conventional": "catalog:", + "@vben/node-utils": "workspace:*", + "commitlint-plugin-function-rules": "catalog:", + "cz-git": "catalog:", + "czg": "catalog:" + } +} diff --git a/internal/lint-configs/eslint-config/build.config.ts b/internal/lint-configs/eslint-config/build.config.ts new file mode 100644 index 0000000..97e572c --- /dev/null +++ b/internal/lint-configs/eslint-config/build.config.ts @@ -0,0 +1,7 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index'], +}); diff --git a/internal/lint-configs/eslint-config/package.json b/internal/lint-configs/eslint-config/package.json new file mode 100644 index 0000000..12556ec --- /dev/null +++ b/internal/lint-configs/eslint-config/package.json @@ -0,0 +1,56 @@ +{ + "name": "@vben/eslint-config", + "version": "5.0.0", + "private": true, + "homepage": "https://github.com/vbenjs/vue-vben-admin", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "internal/lint-configs/eslint-config" + }, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs" + } + }, + "dependencies": { + "eslint-config-turbo": "catalog:", + "eslint-plugin-command": "catalog:", + "eslint-plugin-import-x": "catalog:" + }, + "devDependencies": { + "@eslint/js": "catalog:", + "@types/eslint": "catalog:", + "@typescript-eslint/eslint-plugin": "catalog:", + "@typescript-eslint/parser": "catalog:", + "eslint": "catalog:", + "eslint-plugin-eslint-comments": "catalog:", + "eslint-plugin-jsdoc": "catalog:", + "eslint-plugin-jsonc": "catalog:", + "eslint-plugin-n": "catalog:", + "eslint-plugin-no-only-tests": "catalog:", + "eslint-plugin-perfectionist": "catalog:", + "eslint-plugin-prettier": "catalog:", + "eslint-plugin-regexp": "catalog:", + "eslint-plugin-unicorn": "catalog:", + "eslint-plugin-unused-imports": "catalog:", + "eslint-plugin-vitest": "catalog:", + "eslint-plugin-vue": "catalog:", + "globals": "catalog:", + "jsonc-eslint-parser": "catalog:", + "vue-eslint-parser": "catalog:" + } +} diff --git a/internal/lint-configs/eslint-config/src/configs/command.ts b/internal/lint-configs/eslint-config/src/configs/command.ts new file mode 100644 index 0000000..d0c902d --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/command.ts @@ -0,0 +1,9 @@ +import createCommand from 'eslint-plugin-command/config'; + +export async function command() { + return [ + { + ...createCommand(), + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/comments.ts b/internal/lint-configs/eslint-config/src/configs/comments.ts new file mode 100644 index 0000000..77ccd5d --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/comments.ts @@ -0,0 +1,24 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function comments(): Promise { + const [pluginComments] = await Promise.all([ + // @ts-expect-error - no types + interopDefault(import('eslint-plugin-eslint-comments')), + ] as const); + + return [ + { + plugins: { + 'eslint-comments': pluginComments, + }, + rules: { + 'eslint-comments/no-aggregating-enable': 'error', + 'eslint-comments/no-duplicate-disable': 'error', + 'eslint-comments/no-unlimited-disable': 'error', + 'eslint-comments/no-unused-enable': 'error', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/disableds.ts b/internal/lint-configs/eslint-config/src/configs/disableds.ts new file mode 100644 index 0000000..152b84c --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/disableds.ts @@ -0,0 +1,28 @@ +import type { Linter } from 'eslint'; + +export async function disableds(): Promise { + return [ + { + files: ['**/__tests__/**/*.?([cm])[jt]s?(x)'], + name: 'disables/test', + rules: { + '@typescript-eslint/ban-ts-comment': 'off', + 'no-console': 'off', + }, + }, + { + files: ['**/*.d.ts'], + name: 'disables/dts', + rules: { + '@typescript-eslint/triple-slash-reference': 'off', + }, + }, + { + files: ['**/*.js', '**/*.mjs', '**/*.cjs'], + name: 'disables/js', + rules: { + '@typescript-eslint/explicit-module-boundary-types': 'off', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/ignores.ts b/internal/lint-configs/eslint-config/src/configs/ignores.ts new file mode 100644 index 0000000..d552338 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/ignores.ts @@ -0,0 +1,54 @@ +import type { Linter } from 'eslint'; + +export async function ignores(): Promise { + return [ + { + ignores: [ + '**/node_modules', + '**/dist', + '**/dist-*', + '**/*-dist', + '**/.husky', + '**/.nitro', + '**/.output', + '**/Dockerfile', + '**/package-lock.json', + '**/yarn.lock', + '**/pnpm-lock.yaml', + '**/bun.lockb', + '**/output', + '**/coverage', + '**/temp', + '**/.temp', + '**/tmp', + '**/.tmp', + '**/.history', + '**/.turbo', + '**/.nuxt', + '**/.next', + '**/.vercel', + '**/.changeset', + '**/.idea', + '**/.cache', + '**/.output', + '**/.vite-inspect', + + '**/CHANGELOG*.md', + '**/*.min.*', + '**/LICENSE*', + '**/__snapshots__', + '**/*.snap', + '**/fixtures/**', + '**/.vitepress/cache/**', + '**/auto-import?(s).d.ts', + '**/components.d.ts', + '**/vite.config.mts.*', + '**/*.sh', + '**/*.ttf', + '**/*.woff', + '**/public/**', + '**/china.json', + ], + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/import.ts b/internal/lint-configs/eslint-config/src/configs/import.ts new file mode 100644 index 0000000..ce6cf65 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/import.ts @@ -0,0 +1,25 @@ +import type { Linter } from 'eslint'; + +import * as pluginImport from 'eslint-plugin-import-x'; + +export async function importPluginConfig(): Promise { + return [ + { + plugins: { + // @ts-expect-error - This is a dynamic import + import: pluginImport, + }, + rules: { + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], + 'import/first': 'error', + 'import/newline-after-import': 'error', + 'import/no-duplicates': 'error', + 'import/no-mutable-exports': 'error', + 'import/no-named-default': 'error', + 'import/no-self-import': 'error', + 'import/no-unresolved': 'off', + 'import/no-webpack-loader-syntax': 'error', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/index.ts b/internal/lint-configs/eslint-config/src/configs/index.ts new file mode 100644 index 0000000..c0284ef --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/index.ts @@ -0,0 +1,17 @@ +export * from './command'; +export * from './comments'; +export * from './disableds'; +export * from './ignores'; +export * from './import'; +export * from './javascript'; +export * from './jsdoc'; +export * from './jsonc'; +export * from './node'; +export * from './perfectionist'; +export * from './prettier'; +export * from './regexp'; +export * from './test'; +export * from './turbo'; +export * from './typescript'; +export * from './unicorn'; +export * from './vue'; diff --git a/internal/lint-configs/eslint-config/src/configs/javascript.ts b/internal/lint-configs/eslint-config/src/configs/javascript.ts new file mode 100644 index 0000000..44cf5b6 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/javascript.ts @@ -0,0 +1,241 @@ +import type { Linter } from 'eslint'; + +import js from '@eslint/js'; +import pluginUnusedImports from 'eslint-plugin-unused-imports'; +import globals from 'globals'; + +export async function javascript(): Promise { + return [ + { + languageOptions: { + ecmaVersion: 'latest', + globals: { + ...globals.browser, + ...globals.es2021, + ...globals.node, + document: 'readonly', + navigator: 'readonly', + window: 'readonly', + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 'latest', + sourceType: 'module', + }, + sourceType: 'module', + }, + linterOptions: { + reportUnusedDisableDirectives: true, + }, + plugins: { + 'unused-imports': pluginUnusedImports, + }, + rules: { + ...js.configs.recommended.rules, + 'accessor-pairs': [ + 'error', + { enforceForClassMembers: true, setWithoutGet: true }, + ], + 'array-callback-return': 'error', + 'block-scoped-var': 'error', + 'constructor-super': 'error', + 'default-case-last': 'error', + 'dot-notation': ['error', { allowKeywords: true }], + eqeqeq: ['error', 'always'], + 'keyword-spacing': 'off', + + 'new-cap': [ + 'error', + { capIsNew: false, newIsCap: true, properties: true }, + ], + 'no-alert': 'error', + 'no-array-constructor': 'error', + 'no-async-promise-executor': 'error', + 'no-caller': 'error', + 'no-case-declarations': 'error', + 'no-class-assign': 'error', + 'no-compare-neg-zero': 'error', + 'no-cond-assign': ['error', 'always'], + 'no-console': ['error', { allow: ['warn', 'error'] }], + 'no-const-assign': 'error', + 'no-control-regex': 'error', + 'no-debugger': 'error', + 'no-delete-var': 'error', + 'no-dupe-args': 'error', + 'no-dupe-class-members': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-empty': ['error', { allowEmptyCatch: true }], + 'no-empty-character-class': 'error', + 'no-empty-function': 'off', + 'no-empty-pattern': 'error', + 'no-eval': 'error', + 'no-ex-assign': 'error', + 'no-extend-native': 'error', + 'no-extra-bind': 'error', + 'no-extra-boolean-cast': 'error', + 'no-fallthrough': 'error', + 'no-func-assign': 'error', + 'no-global-assign': 'error', + 'no-implied-eval': 'error', + 'no-import-assign': 'error', + 'no-invalid-regexp': 'error', + 'no-irregular-whitespace': 'error', + 'no-iterator': 'error', + 'no-labels': ['error', { allowLoop: false, allowSwitch: false }], + 'no-lone-blocks': 'error', + 'no-loss-of-precision': 'error', + 'no-misleading-character-class': 'error', + 'no-multi-str': 'error', + 'no-new': 'error', + 'no-new-func': 'error', + 'no-new-object': 'error', + 'no-new-symbol': 'error', + 'no-new-wrappers': 'error', + 'no-obj-calls': 'error', + 'no-octal': 'error', + 'no-octal-escape': 'error', + 'no-proto': 'error', + 'no-prototype-builtins': 'error', + 'no-redeclare': ['error', { builtinGlobals: false }], + 'no-regex-spaces': 'error', + 'no-restricted-globals': [ + 'error', + { message: 'Use `globalThis` instead.', name: 'global' }, + { message: 'Use `globalThis` instead.', name: 'self' }, + ], + 'no-restricted-properties': [ + 'error', + { + message: + 'Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.', + property: '__proto__', + }, + { + message: 'Use `Object.defineProperty` instead.', + property: '__defineGetter__', + }, + { + message: 'Use `Object.defineProperty` instead.', + property: '__defineSetter__', + }, + { + message: 'Use `Object.getOwnPropertyDescriptor` instead.', + property: '__lookupGetter__', + }, + { + message: 'Use `Object.getOwnPropertyDescriptor` instead.', + property: '__lookupSetter__', + }, + ], + 'no-restricted-syntax': [ + 'error', + 'DebuggerStatement', + 'LabeledStatement', + 'WithStatement', + 'TSEnumDeclaration[const=true]', + 'TSExportAssignment', + ], + 'no-self-assign': ['error', { props: true }], + 'no-self-compare': 'error', + 'no-sequences': 'error', + 'no-shadow-restricted-names': 'error', + 'no-sparse-arrays': 'error', + 'no-template-curly-in-string': 'error', + 'no-this-before-super': 'error', + 'no-throw-literal': 'error', + 'no-undef': 'off', + 'no-undef-init': 'error', + 'no-unexpected-multiline': 'error', + 'no-unmodified-loop-condition': 'error', + 'no-unneeded-ternary': ['error', { defaultAssignment: false }], + 'no-unreachable': 'error', + 'no-unreachable-loop': 'error', + 'no-unsafe-finally': 'error', + 'no-unsafe-negation': 'error', + 'no-unused-expressions': [ + 'error', + { + allowShortCircuit: true, + allowTaggedTemplates: true, + allowTernary: true, + }, + ], + 'no-unused-vars': [ + 'error', + { + args: 'none', + caughtErrors: 'none', + ignoreRestSiblings: true, + vars: 'all', + }, + ], + 'no-use-before-define': [ + 'error', + { classes: false, functions: false, variables: false }, + ], + 'no-useless-backreference': 'error', + 'no-useless-call': 'error', + 'no-useless-catch': 'error', + 'no-useless-computed-key': 'error', + 'no-useless-constructor': 'error', + 'no-useless-rename': 'error', + 'no-useless-return': 'error', + 'no-var': 'error', + 'no-with': 'error', + 'object-shorthand': [ + 'error', + 'always', + { avoidQuotes: true, ignoreConstructors: false }, + ], + 'one-var': ['error', { initialized: 'never' }], + 'prefer-arrow-callback': [ + 'error', + { + allowNamedFunctions: false, + allowUnboundThis: true, + }, + ], + 'prefer-const': [ + 'error', + { + destructuring: 'all', + ignoreReadBeforeAssign: true, + }, + ], + 'prefer-exponentiation-operator': 'error', + + 'prefer-promise-reject-errors': 'error', + 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }], + 'prefer-rest-params': 'error', + 'prefer-spread': 'error', + 'prefer-template': 'error', + 'space-before-function-paren': 'off', + 'spaced-comment': 'error', + 'symbol-description': 'error', + 'unicode-bom': ['error', 'never'], + + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'error', + { + args: 'after-used', + argsIgnorePattern: '^_', + vars: 'all', + varsIgnorePattern: '^_', + }, + ], + 'use-isnan': [ + 'error', + { enforceForIndexOf: true, enforceForSwitchCase: true }, + ], + 'valid-typeof': ['error', { requireStringLiterals: true }], + + 'vars-on-top': 'error', + yoda: ['error', 'never'], + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/jsdoc.ts b/internal/lint-configs/eslint-config/src/configs/jsdoc.ts new file mode 100644 index 0000000..1368197 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/jsdoc.ts @@ -0,0 +1,34 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function jsdoc(): Promise { + const [pluginJsdoc] = await Promise.all([ + interopDefault(import('eslint-plugin-jsdoc')), + ] as const); + + return [ + { + plugins: { + jsdoc: pluginJsdoc, + }, + rules: { + 'jsdoc/check-access': 'warn', + 'jsdoc/check-param-names': 'warn', + 'jsdoc/check-property-names': 'warn', + 'jsdoc/check-types': 'warn', + 'jsdoc/empty-tags': 'warn', + 'jsdoc/implements-on-classes': 'warn', + 'jsdoc/no-defaults': 'warn', + 'jsdoc/no-multi-asterisks': 'warn', + 'jsdoc/require-param-name': 'warn', + 'jsdoc/require-property': 'warn', + 'jsdoc/require-property-description': 'warn', + 'jsdoc/require-property-name': 'warn', + 'jsdoc/require-returns-check': 'warn', + 'jsdoc/require-returns-description': 'warn', + 'jsdoc/require-yields-check': 'warn', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/jsonc.ts b/internal/lint-configs/eslint-config/src/configs/jsonc.ts new file mode 100644 index 0000000..4072e4c --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/jsonc.ts @@ -0,0 +1,258 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function jsonc(): Promise { + const [pluginJsonc, parserJsonc] = await Promise.all([ + interopDefault(import('eslint-plugin-jsonc')), + interopDefault(import('jsonc-eslint-parser')), + ] as const); + + return [ + { + files: ['**/*.json', '**/*.json5', '**/*.jsonc', '*.code-workspace'], + languageOptions: { + parser: parserJsonc as any, + }, + plugins: { + jsonc: pluginJsonc as any, + }, + rules: { + 'jsonc/no-bigint-literals': 'error', + 'jsonc/no-binary-expression': 'error', + 'jsonc/no-binary-numeric-literals': 'error', + 'jsonc/no-dupe-keys': 'error', + 'jsonc/no-escape-sequence-in-identifier': 'error', + 'jsonc/no-floating-decimal': 'error', + 'jsonc/no-hexadecimal-numeric-literals': 'error', + 'jsonc/no-infinity': 'error', + 'jsonc/no-multi-str': 'error', + 'jsonc/no-nan': 'error', + 'jsonc/no-number-props': 'error', + 'jsonc/no-numeric-separators': 'error', + 'jsonc/no-octal': 'error', + 'jsonc/no-octal-escape': 'error', + 'jsonc/no-octal-numeric-literals': 'error', + 'jsonc/no-parenthesized': 'error', + 'jsonc/no-plus-sign': 'error', + 'jsonc/no-regexp-literals': 'error', + 'jsonc/no-sparse-arrays': 'error', + 'jsonc/no-template-literals': 'error', + 'jsonc/no-undefined-value': 'error', + 'jsonc/no-unicode-codepoint-escapes': 'error', + 'jsonc/no-useless-escape': 'error', + 'jsonc/space-unary-ops': 'error', + 'jsonc/valid-json-number': 'error', + 'jsonc/vue-custom-block/no-parsing-error': 'error', + }, + }, + sortTsconfig(), + sortPackageJson(), + ]; +} + +function sortPackageJson(): Linter.Config { + return { + files: ['**/package.json'], + rules: { + 'jsonc/sort-array-values': [ + 'error', + { + order: { type: 'asc' }, + pathPattern: '^files$|^pnpm.neverBuiltDependencies$', + }, + ], + 'jsonc/sort-keys': [ + 'error', + { + order: [ + 'name', + 'version', + 'description', + 'private', + 'keywords', + 'homepage', + 'bugs', + 'repository', + 'license', + 'author', + 'contributors', + 'categories', + 'funding', + 'type', + 'scripts', + 'files', + 'sideEffects', + 'bin', + 'main', + 'module', + 'unpkg', + 'jsdelivr', + 'types', + 'typesVersions', + 'imports', + 'exports', + 'publishConfig', + 'icon', + 'activationEvents', + 'contributes', + 'peerDependencies', + 'peerDependenciesMeta', + 'dependencies', + 'optionalDependencies', + 'devDependencies', + 'engines', + 'packageManager', + 'pnpm', + 'overrides', + 'resolutions', + 'husky', + 'simple-git-hooks', + 'lint-staged', + 'eslintConfig', + ], + pathPattern: '^$', + }, + { + order: { type: 'asc' }, + pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$', + }, + { + order: { type: 'asc' }, + pathPattern: '^(?:resolutions|overrides|pnpm.overrides)$', + }, + { + order: ['types', 'import', 'require', 'default'], + pathPattern: '^exports.*$', + }, + ], + }, + }; +} + +function sortTsconfig(): Linter.Config { + return { + files: [ + '**/tsconfig.json', + '**/tsconfig.*.json', + 'internal/tsconfig/*.json', + ], + rules: { + 'jsonc/sort-keys': [ + 'error', + { + order: [ + 'extends', + 'compilerOptions', + 'references', + 'files', + 'include', + 'exclude', + ], + pathPattern: '^$', + }, + { + order: [ + /* Projects */ + 'incremental', + 'composite', + 'tsBuildInfoFile', + 'disableSourceOfProjectReferenceRedirect', + 'disableSolutionSearching', + 'disableReferencedProjectLoad', + /* Language and Environment */ + 'target', + 'jsx', + 'jsxFactory', + 'jsxFragmentFactory', + 'jsxImportSource', + 'lib', + 'moduleDetection', + 'noLib', + 'reactNamespace', + 'useDefineForClassFields', + 'emitDecoratorMetadata', + 'experimentalDecorators', + /* Modules */ + 'baseUrl', + 'rootDir', + 'rootDirs', + 'customConditions', + 'module', + 'moduleResolution', + 'moduleSuffixes', + 'noResolve', + 'paths', + 'resolveJsonModule', + 'resolvePackageJsonExports', + 'resolvePackageJsonImports', + 'typeRoots', + 'types', + 'allowArbitraryExtensions', + 'allowImportingTsExtensions', + 'allowUmdGlobalAccess', + /* JavaScript Support */ + 'allowJs', + 'checkJs', + 'maxNodeModuleJsDepth', + /* Type Checking */ + 'strict', + 'strictBindCallApply', + 'strictFunctionTypes', + 'strictNullChecks', + 'strictPropertyInitialization', + 'allowUnreachableCode', + 'allowUnusedLabels', + 'alwaysStrict', + 'exactOptionalPropertyTypes', + 'noFallthroughCasesInSwitch', + 'noImplicitAny', + 'noImplicitOverride', + 'noImplicitReturns', + 'noImplicitThis', + 'noPropertyAccessFromIndexSignature', + 'noUncheckedIndexedAccess', + 'noUnusedLocals', + 'noUnusedParameters', + 'useUnknownInCatchVariables', + /* Emit */ + 'declaration', + 'declarationDir', + 'declarationMap', + 'downlevelIteration', + 'emitBOM', + 'emitDeclarationOnly', + 'importHelpers', + 'importsNotUsedAsValues', + 'inlineSourceMap', + 'inlineSources', + 'mapRoot', + 'newLine', + 'noEmit', + 'noEmitHelpers', + 'noEmitOnError', + 'outDir', + 'outFile', + 'preserveConstEnums', + 'preserveValueImports', + 'removeComments', + 'sourceMap', + 'sourceRoot', + 'stripInternal', + /* Interop Constraints */ + 'allowSyntheticDefaultImports', + 'esModuleInterop', + 'forceConsistentCasingInFileNames', + 'isolatedModules', + 'preserveSymlinks', + 'verbatimModuleSyntax', + /* Completeness */ + 'skipDefaultLibCheck', + 'skipLibCheck', + ], + pathPattern: '^compilerOptions$', + }, + ], + }, + }; +} diff --git a/internal/lint-configs/eslint-config/src/configs/node.ts b/internal/lint-configs/eslint-config/src/configs/node.ts new file mode 100644 index 0000000..f8f2664 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/node.ts @@ -0,0 +1,57 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function node(): Promise { + const pluginNode = await interopDefault(import('eslint-plugin-n')); + + return [ + { + plugins: { + n: pluginNode, + }, + rules: { + 'n/handle-callback-err': ['error', '^(err|error)$'], + 'n/no-deprecated-api': 'error', + 'n/no-exports-assign': 'error', + 'n/no-extraneous-import': [ + 'error', + { + allowModules: [ + 'unbuild', + '@vben/vite-config', + 'vitest', + 'vite', + '@vue/test-utils', + '@vben/tailwind-config', + '@playwright/test', + ], + }, + ], + 'n/no-new-require': 'error', + 'n/no-path-concat': 'error', + // 'n/no-unpublished-import': 'off', + 'n/no-unsupported-features/es-syntax': [ + 'error', + { + ignores: [], + version: '>=20.12.0', + }, + ], + 'n/prefer-global/buffer': ['error', 'never'], + // 'n/no-missing-import': 'off', + 'n/prefer-global/process': ['error', 'never'], + 'n/process-exit-as-throw': 'error', + }, + }, + { + files: [ + 'scripts/**/*.?([cm])[jt]s?(x)', + 'internal/**/*.?([cm])[jt]s?(x)', + ], + rules: { + 'n/prefer-global/process': 'off', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/perfectionist.ts b/internal/lint-configs/eslint-config/src/configs/perfectionist.ts new file mode 100644 index 0000000..4a7d12f --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/perfectionist.ts @@ -0,0 +1,88 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function perfectionist(): Promise { + const perfectionistPlugin = await interopDefault( + import('eslint-plugin-perfectionist'), + ); + + return [ + perfectionistPlugin.configs['recommended-natural'], + { + rules: { + 'perfectionist/sort-exports': [ + 'error', + { + order: 'asc', + type: 'natural', + }, + ], + 'perfectionist/sort-imports': [ + 'error', + { + customGroups: { + type: { + 'vben-core-type': ['^@vben-core/.+'], + 'vben-type': ['^@vben/.+'], + 'vue-type': ['^vue$', '^vue-.+', '^@vue/.+'], + }, + value: { + vben: ['^@vben/.+'], + 'vben-core': ['^@vben-core/.+'], + vue: ['^vue$', '^vue-.+', '^@vue/.+'], + }, + }, + environment: 'node', + groups: [ + ['external-type', 'builtin-type', 'type'], + 'vue-type', + 'vben-type', + 'vben-core-type', + ['parent-type', 'sibling-type', 'index-type'], + ['internal-type'], + 'builtin', + 'vue', + 'vben', + 'vben-core', + 'external', + 'internal', + ['parent', 'sibling', 'index'], + 'side-effect', + 'side-effect-style', + 'style', + 'object', + 'unknown', + ], + internalPattern: ['^#/.+'], + newlinesBetween: 'always', + order: 'asc', + type: 'natural', + }, + ], + 'perfectionist/sort-modules': 'off', + 'perfectionist/sort-named-exports': [ + 'error', + { + order: 'asc', + type: 'natural', + }, + ], + 'perfectionist/sort-objects': [ + 'off', + { + customGroups: { + items: 'items', + list: 'list', + children: 'children', + }, + groups: ['unknown', 'items', 'list', 'children'], + ignorePattern: ['children'], + order: 'asc', + type: 'natural', + }, + ], + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/prettier.ts b/internal/lint-configs/eslint-config/src/configs/prettier.ts new file mode 100644 index 0000000..3cd7af4 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/prettier.ts @@ -0,0 +1,19 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function prettier(): Promise { + const [pluginPrettier] = await Promise.all([ + interopDefault(import('eslint-plugin-prettier')), + ] as const); + return [ + { + plugins: { + prettier: pluginPrettier, + }, + rules: { + 'prettier/prettier': 'error', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/regexp.ts b/internal/lint-configs/eslint-config/src/configs/regexp.ts new file mode 100644 index 0000000..c0f4c9f --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/regexp.ts @@ -0,0 +1,20 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function regexp(): Promise { + const [pluginRegexp] = await Promise.all([ + interopDefault(import('eslint-plugin-regexp')), + ] as const); + + return [ + { + plugins: { + regexp: pluginRegexp, + }, + rules: { + ...pluginRegexp.configs.recommended.rules, + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/test.ts b/internal/lint-configs/eslint-config/src/configs/test.ts new file mode 100644 index 0000000..ddfde2b --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/test.ts @@ -0,0 +1,45 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function test(): Promise { + const [pluginTest, pluginNoOnlyTests] = await Promise.all([ + interopDefault(import('eslint-plugin-vitest')), + // @ts-expect-error - no types + interopDefault(import('eslint-plugin-no-only-tests')), + ] as const); + + return [ + { + files: [ + `**/__tests__/**/*.?([cm])[jt]s?(x)`, + `**/*.spec.?([cm])[jt]s?(x)`, + `**/*.test.?([cm])[jt]s?(x)`, + `**/*.bench.?([cm])[jt]s?(x)`, + `**/*.benchmark.?([cm])[jt]s?(x)`, + ], + plugins: { + test: { + ...pluginTest, + rules: { + ...pluginTest.rules, + ...pluginNoOnlyTests.rules, + }, + }, + }, + rules: { + 'no-console': 'off', + 'node/prefer-global/process': 'off', + 'test/consistent-test-it': [ + 'error', + { fn: 'it', withinDescribe: 'it' }, + ], + 'test/no-identical-title': 'error', + 'test/no-import-node-test': 'error', + 'test/no-only-tests': 'error', + 'test/prefer-hooks-in-order': 'error', + 'test/prefer-lowercase-title': 'error', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/turbo.ts b/internal/lint-configs/eslint-config/src/configs/turbo.ts new file mode 100644 index 0000000..bcc27eb --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/turbo.ts @@ -0,0 +1,17 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function turbo(): Promise { + const [pluginTurbo] = await Promise.all([ + interopDefault(import('eslint-config-turbo')), + ] as const); + + return [ + { + plugins: { + turbo: pluginTurbo, + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/typescript.ts b/internal/lint-configs/eslint-config/src/configs/typescript.ts new file mode 100644 index 0000000..2f6f976 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/typescript.ts @@ -0,0 +1,71 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function typescript(): Promise { + const [pluginTs, parserTs] = await Promise.all([ + interopDefault(import('@typescript-eslint/eslint-plugin')), + interopDefault(import('@typescript-eslint/parser')), + ] as const); + + return [ + { + files: ['**/*.?([cm])[jt]s?(x)'], + languageOptions: { + parser: parserTs, + parserOptions: { + createDefaultProgram: false, + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 'latest', + extraFileExtensions: ['.vue'], + jsxPragma: 'React', + project: './tsconfig.*.json', + sourceType: 'module', + }, + }, + plugins: { + '@typescript-eslint': pluginTs as any, + }, + rules: { + ...pluginTs.configs['eslint-recommended']?.overrides?.[0]?.rules, + ...pluginTs.configs.strict?.rules, + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-check': false, + 'ts-expect-error': 'allow-with-description', + 'ts-ignore': 'allow-with-description', + 'ts-nocheck': 'allow-with-description', + }, + ], + + // '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], + '@typescript-eslint/consistent-type-definitions': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-empty-function': [ + 'error', + { + allow: ['arrowFunctions', 'functions', 'methods'], + }, + ], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/no-non-null-assertion': 'error', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-var-requires': 'error', + 'unused-imports/no-unused-vars': 'off', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/unicorn.ts b/internal/lint-configs/eslint-config/src/configs/unicorn.ts new file mode 100644 index 0000000..21b1902 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/unicorn.ts @@ -0,0 +1,45 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function unicorn(): Promise { + const [pluginUnicorn] = await Promise.all([ + interopDefault(import('eslint-plugin-unicorn')), + ] as const); + + return [ + { + plugins: { + unicorn: pluginUnicorn, + }, + rules: { + ...pluginUnicorn.configs.recommended.rules, + + 'unicorn/better-regex': 'off', + 'unicorn/consistent-destructuring': 'off', + 'unicorn/consistent-function-scoping': 'off', + 'unicorn/expiring-todo-comments': 'off', + 'unicorn/filename-case': 'off', + 'unicorn/import-style': 'off', + 'unicorn/no-array-for-each': 'off', + 'unicorn/no-null': 'off', + 'unicorn/no-useless-undefined': 'off', + 'unicorn/prefer-at': 'off', + 'unicorn/prefer-dom-node-text-content': 'off', + 'unicorn/prefer-export-from': ['error', { ignoreUsedVariables: true }], + 'unicorn/prefer-global-this': 'off', + 'unicorn/prefer-top-level-await': 'off', + 'unicorn/prevent-abbreviations': 'off', + }, + }, + { + files: [ + 'scripts/**/*.?([cm])[jt]s?(x)', + 'internal/**/*.?([cm])[jt]s?(x)', + ], + rules: { + 'unicorn/no-process-exit': 'off', + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/configs/vue.ts b/internal/lint-configs/eslint-config/src/configs/vue.ts new file mode 100644 index 0000000..5db7299 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/configs/vue.ts @@ -0,0 +1,152 @@ +import type { Linter } from 'eslint'; + +import { interopDefault } from '../util'; + +export async function vue(): Promise { + const [pluginVue, parserVue, parserTs] = await Promise.all([ + interopDefault(import('eslint-plugin-vue')), + interopDefault(import('vue-eslint-parser')), + interopDefault(import('@typescript-eslint/parser')), + ] as const); + + const flatEssential = pluginVue.configs?.['flat/essential'] || []; + const flatStronglyRecommended = + pluginVue.configs?.['flat/strongly-recommended'] || []; + const flatRecommended = pluginVue.configs?.['flat/recommended'] || []; + + return [ + ...flatEssential, + ...flatStronglyRecommended, + ...flatRecommended, + { + files: ['**/*.vue'], + languageOptions: { + // globals: { + // computed: 'readonly', + // defineEmits: 'readonly', + // defineExpose: 'readonly', + // defineProps: 'readonly', + // onMounted: 'readonly', + // onUnmounted: 'readonly', + // reactive: 'readonly', + // ref: 'readonly', + // shallowReactive: 'readonly', + // shallowRef: 'readonly', + // toRef: 'readonly', + // toRefs: 'readonly', + // watch: 'readonly', + // watchEffect: 'readonly', + // }, + parser: parserVue, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + extraFileExtensions: ['.vue'], + parser: parserTs, + sourceType: 'module', + }, + }, + plugins: { + vue: pluginVue, + }, + processor: pluginVue.processors?.['.vue'], + rules: { + ...pluginVue.configs?.base?.rules, + + 'vue/attribute-hyphenation': [ + 'error', + 'always', + { + ignore: [], + }, + ], + 'vue/attributes-order': 'off', + 'vue/block-order': [ + 'error', + { + order: ['script', 'template', 'style'], + }, + ], + 'vue/component-name-in-template-casing': ['error', 'PascalCase'], + 'vue/component-options-name-casing': ['error', 'PascalCase'], + 'vue/custom-event-name-casing': ['error', 'camelCase'], + 'vue/define-macros-order': [ + 'error', + { + order: [ + 'defineOptions', + 'defineProps', + 'defineEmits', + 'defineSlots', + ], + }, + ], + 'vue/dot-location': ['error', 'property'], + 'vue/dot-notation': ['error', { allowKeywords: true }], + 'vue/eqeqeq': ['error', 'smart'], + 'vue/html-closing-bracket-newline': 'error', + 'vue/html-indent': 'off', + // 'vue/html-indent': ['error', 2], + 'vue/html-quotes': ['error', 'double'], + 'vue/html-self-closing': [ + 'error', + { + html: { + component: 'always', + normal: 'never', + void: 'always', + }, + math: 'always', + svg: 'always', + }, + ], + 'vue/max-attributes-per-line': 'off', + 'vue/multi-word-component-names': 'off', + 'vue/multiline-html-element-content-newline': 'error', + 'vue/no-empty-pattern': 'error', + 'vue/no-extra-parens': ['error', 'functions'], + 'vue/no-irregular-whitespace': 'error', + 'vue/no-loss-of-precision': 'error', + 'vue/no-reserved-component-names': 'off', + 'vue/no-restricted-syntax': [ + 'error', + 'DebuggerStatement', + 'LabeledStatement', + 'WithStatement', + ], + 'vue/no-restricted-v-bind': ['error', '/^v-/'], + 'vue/no-sparse-arrays': 'error', + 'vue/no-unused-refs': 'error', + 'vue/no-useless-v-bind': 'error', + 'vue/object-shorthand': [ + 'error', + 'always', + { + avoidQuotes: true, + ignoreConstructors: false, + }, + ], + 'vue/one-component-per-file': 'error', + 'vue/prefer-import-from-vue': 'error', + 'vue/prefer-separate-static-class': 'error', + 'vue/prefer-template': 'error', + 'vue/prop-name-casing': ['error', 'camelCase'], + 'vue/require-default-prop': 'error', + 'vue/require-explicit-emits': 'error', + 'vue/require-prop-types': 'off', + 'vue/singleline-html-element-content-newline': 'off', + 'vue/space-infix-ops': 'error', + 'vue/space-unary-ops': ['error', { nonwords: false, words: true }], + 'vue/v-on-event-hyphenation': [ + 'error', + 'always', + { + autofix: true, + ignore: [], + }, + ], + }, + }, + ]; +} diff --git a/internal/lint-configs/eslint-config/src/custom-config.ts b/internal/lint-configs/eslint-config/src/custom-config.ts new file mode 100644 index 0000000..cf52492 --- /dev/null +++ b/internal/lint-configs/eslint-config/src/custom-config.ts @@ -0,0 +1,168 @@ +import type { Linter } from 'eslint'; + +const restrictedImportIgnores = [ + '**/vite.config.mts', + '**/tailwind.config.mjs', + '**/postcss.config.mjs', +]; + +const customConfig: Linter.Config[] = [ + // shadcn-ui 内部组件是自动生成的,不做太多限制 + { + files: ['packages/@core/ui-kit/shadcn-ui/**/**'], + rules: { + 'vue/require-default-prop': 'off', + }, + }, + { + files: [ + 'apps/**/**', + 'packages/effects/**/**', + 'packages/utils/**/**', + 'packages/types/**/**', + 'packages/locales/**/**', + ], + ignores: restrictedImportIgnores, + rules: { + 'perfectionist/sort-interfaces': 'off', + 'perfectionist/sort-objects': 'off', + }, + }, + { + files: ['**/**.vue'], + ignores: restrictedImportIgnores, + rules: { + 'perfectionist/sort-objects': 'off', + }, + }, + { + // apps内部的一些基础规则 + files: ['apps/**/**'], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['#/api/*'], + message: + 'The #/api package cannot be imported, please use the @core package itself', + }, + { + group: ['#/layouts/*'], + message: + 'The #/layouts package cannot be imported, please use the @core package itself', + }, + { + group: ['#/locales/*'], + message: + 'The #/locales package cannot be imported, please use the @core package itself', + }, + { + group: ['#/stores/*'], + message: + 'The #/stores package cannot be imported, please use the @core package itself', + }, + ], + }, + ], + 'perfectionist/sort-interfaces': 'off', + }, + }, + { + // @core内部组件,不能引入@vben/* 里面的包 + files: ['packages/@core/**/**'], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@vben/*'], + message: + 'The @core package cannot import the @vben package, please use the @core package itself', + }, + ], + }, + ], + }, + }, + { + // @core/shared内部组件,不能引入@vben/* 或者 @vben-core/* 里面的包 + files: ['packages/@core/base/**/**'], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@vben/*', '@vben-core/*'], + message: + 'The @vben-core/shared package cannot import the @vben package, please use the @core/shared package itself', + }, + ], + }, + ], + }, + }, + + { + // 不能引入@vben/*里面的包 + files: [ + 'packages/types/**/**', + 'packages/utils/**/**', + 'packages/icons/**/**', + 'packages/constants/**/**', + 'packages/styles/**/**', + 'packages/stores/**/**', + 'packages/preferences/**/**', + 'packages/locales/**/**', + ], + ignores: restrictedImportIgnores, + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@vben/*'], + message: + 'The @vben package cannot be imported, please use the @core package itself', + }, + ], + }, + ], + }, + }, + // 后端模拟代码,不需要太多规则 + { + files: ['docs/**/**'], + rules: { + '@typescript-eslint/no-extraneous-class': 'off', + 'n/no-extraneous-import': 'off', + 'n/prefer-global/buffer': 'off', + 'n/prefer-global/process': 'off', + 'no-console': 'off', + 'unicorn/prefer-module': 'off', + }, + }, + { + files: ['**/**/playwright.config.ts'], + rules: { + 'n/prefer-global/buffer': 'off', + 'n/prefer-global/process': 'off', + 'no-console': 'off', + }, + }, + { + files: ['internal/**/**', 'scripts/**/**'], + rules: { + 'no-console': 'off', + }, + }, +]; + +export { customConfig }; diff --git a/internal/lint-configs/eslint-config/src/index.ts b/internal/lint-configs/eslint-config/src/index.ts new file mode 100644 index 0000000..c9f08bd --- /dev/null +++ b/internal/lint-configs/eslint-config/src/index.ts @@ -0,0 +1,60 @@ +import type { Linter } from 'eslint'; + +import { + command, + comments, + disableds, + ignores, + importPluginConfig, + javascript, + jsdoc, + jsonc, + node, + perfectionist, + prettier, + regexp, + test, + turbo, + typescript, + unicorn, + vue, +} from './configs'; +import { customConfig } from './custom-config'; + +type FlatConfig = Linter.Config; + +type FlatConfigPromise = + | FlatConfig + | FlatConfig[] + | Promise + | Promise; + +async function defineConfig(config: FlatConfig[] = []) { + const configs: FlatConfigPromise[] = [ + vue(), + javascript(), + ignores(), + prettier(), + typescript(), + jsonc(), + disableds(), + importPluginConfig(), + node(), + perfectionist(), + comments(), + jsdoc(), + unicorn(), + test(), + regexp(), + command(), + turbo(), + ...customConfig, + ...config, + ]; + + const resolved = await Promise.all(configs); + + return resolved.flat(); +} + +export { defineConfig }; diff --git a/internal/lint-configs/eslint-config/src/util.ts b/internal/lint-configs/eslint-config/src/util.ts new file mode 100644 index 0000000..d1a10ad --- /dev/null +++ b/internal/lint-configs/eslint-config/src/util.ts @@ -0,0 +1,8 @@ +export type Awaitable = Promise | T; + +export async function interopDefault( + m: Awaitable, +): Promise { + const resolved = await m; + return (resolved as any).default || resolved; +} diff --git a/internal/lint-configs/eslint-config/tsconfig.json b/internal/lint-configs/eslint-config/tsconfig.json new file mode 100644 index 0000000..dbd3bcc --- /dev/null +++ b/internal/lint-configs/eslint-config/tsconfig.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/tsconfig/node.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/internal/lint-configs/prettier-config/index.mjs b/internal/lint-configs/prettier-config/index.mjs new file mode 100644 index 0000000..f6a20c8 --- /dev/null +++ b/internal/lint-configs/prettier-config/index.mjs @@ -0,0 +1,18 @@ +export default { + endOfLine: 'auto', + overrides: [ + { + files: ['*.json5'], + options: { + quoteProps: 'preserve', + singleQuote: false, + }, + }, + ], + plugins: ['prettier-plugin-tailwindcss'], + printWidth: 80, + proseWrap: 'never', + semi: true, + singleQuote: true, + trailingComma: 'all', +}; diff --git a/internal/lint-configs/prettier-config/package.json b/internal/lint-configs/prettier-config/package.json new file mode 100644 index 0000000..65e8b8f --- /dev/null +++ b/internal/lint-configs/prettier-config/package.json @@ -0,0 +1,28 @@ +{ + "name": "@vben/prettier-config", + "version": "5.0.0", + "private": true, + "homepage": "https://github.com/vbenjs/vue-vben-admin", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "internal/lint-configs/prettier-config" + }, + "license": "MIT", + "type": "module", + "files": [ + "dist" + ], + "main": "./index.mjs", + "module": "./index.mjs", + "exports": { + ".": { + "default": "./index.mjs" + } + }, + "dependencies": { + "prettier": "catalog:", + "prettier-plugin-tailwindcss": "catalog:" + } +} diff --git a/internal/lint-configs/stylelint-config/index.mjs b/internal/lint-configs/stylelint-config/index.mjs new file mode 100644 index 0000000..08ac823 --- /dev/null +++ b/internal/lint-configs/stylelint-config/index.mjs @@ -0,0 +1,141 @@ +export default { + extends: ['stylelint-config-standard', 'stylelint-config-recess-order'], + ignoreFiles: [ + '**/*.js', + '**/*.jsx', + '**/*.tsx', + '**/*.ts', + '**/*.json', + '**/*.md', + ], + overrides: [ + { + customSyntax: 'postcss-html', + files: ['*.(html|vue)', '**/*.(html|vue)'], + rules: { + 'selector-pseudo-class-no-unknown': [ + true, + { + ignorePseudoClasses: ['global', 'deep'], + }, + ], + 'selector-pseudo-element-no-unknown': [ + true, + { + ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'], + }, + ], + }, + }, + { + customSyntax: 'postcss-scss', + extends: [ + 'stylelint-config-recommended-scss', + 'stylelint-config-recommended-vue/scss', + ], + files: ['*.scss', '**/*.scss'], + }, + ], + plugins: [ + 'stylelint-order', + '@stylistic/stylelint-plugin', + 'stylelint-prettier', + 'stylelint-scss', + ], + rules: { + 'at-rule-no-deprecated': null, + 'at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'extends', + 'ignores', + 'include', + 'mixin', + 'if', + 'else', + 'media', + 'for', + 'at-root', + 'tailwind', + 'apply', + 'variants', + 'responsive', + 'screen', + 'function', + 'each', + 'use', + 'forward', + 'return', + ], + }, + ], + 'font-family-no-missing-generic-family-keyword': null, + 'function-no-unknown': null, + 'import-notation': null, + 'media-feature-range-notation': null, + 'named-grid-areas-no-invalid': null, + 'no-descending-specificity': null, + 'no-empty-source': null, + 'order/order': [ + [ + 'dollar-variables', + 'custom-properties', + 'at-rules', + 'declarations', + { + name: 'supports', + type: 'at-rule', + }, + { + name: 'media', + type: 'at-rule', + }, + { + name: 'include', + type: 'at-rule', + }, + 'rules', + ], + { severity: 'error' }, + ], + 'prettier/prettier': true, + 'rule-empty-line-before': [ + 'always', + { + ignore: ['after-comment', 'first-nested'], + }, + ], + 'scss/at-rule-no-unknown': [ + true, + { + ignoreAtRules: [ + 'extends', + 'ignores', + 'include', + 'mixin', + 'if', + 'else', + 'media', + 'for', + 'at-root', + 'tailwind', + 'apply', + 'variants', + 'responsive', + 'screen', + 'function', + 'each', + 'use', + 'forward', + 'return', + ], + }, + ], + 'scss/operator-no-newline-after': null, + 'selector-class-pattern': + '^(?:(?:o|c|u|t|s|is|has|_|js|qa)-)?[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*(?:__[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:--[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)?(?:[.+])?$', + + 'selector-not-notation': null, + }, +}; diff --git a/internal/lint-configs/stylelint-config/package.json b/internal/lint-configs/stylelint-config/package.json new file mode 100644 index 0000000..f8bc1cc --- /dev/null +++ b/internal/lint-configs/stylelint-config/package.json @@ -0,0 +1,43 @@ +{ + "name": "@vben/stylelint-config", + "version": "5.5.9", + "private": true, + "homepage": "https://github.com/vbenjs/vue-vben-admin", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "internal/lint-configs/stylelint-config" + }, + "license": "MIT", + "type": "module", + "files": [ + "dist" + ], + "main": "./index.mjs", + "module": "./index.mjs", + "exports": { + ".": { + "import": "./index.mjs", + "default": "./index.mjs" + } + }, + "dependencies": { + "@stylistic/stylelint-plugin": "catalog:", + "stylelint-config-recess-order": "catalog:", + "stylelint-scss": "catalog:" + }, + "devDependencies": { + "postcss": "catalog:", + "postcss-html": "catalog:", + "postcss-scss": "catalog:", + "prettier": "catalog:", + "stylelint": "catalog:", + "stylelint-config-recommended": "catalog:", + "stylelint-config-recommended-scss": "catalog:", + "stylelint-config-recommended-vue": "catalog:", + "stylelint-config-standard": "catalog:", + "stylelint-order": "catalog:", + "stylelint-prettier": "catalog:" + } +} diff --git a/internal/node-utils/build.config.ts b/internal/node-utils/build.config.ts new file mode 100644 index 0000000..97e572c --- /dev/null +++ b/internal/node-utils/build.config.ts @@ -0,0 +1,7 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index'], +}); diff --git a/internal/node-utils/package.json b/internal/node-utils/package.json new file mode 100644 index 0000000..fe4a6ba --- /dev/null +++ b/internal/node-utils/package.json @@ -0,0 +1,43 @@ +{ + "name": "@vben/node-utils", + "version": "5.5.9", + "private": true, + "homepage": "https://github.com/vbenjs/vue-vben-admin", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "internal/node-utils" + }, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./dist/index.mjs", + "default": "./dist/index.mjs" + } + }, + "dependencies": { + "@changesets/git": "catalog:", + "@manypkg/get-packages": "catalog:", + "chalk": "catalog:", + "consola": "catalog:", + "dayjs": "catalog:", + "execa": "catalog:", + "find-up": "catalog:", + "ora": "catalog:", + "pkg-types": "catalog:", + "prettier": "catalog:", + "rimraf": "catalog:" + } +} diff --git a/internal/node-utils/src/__tests__/hash.test.ts b/internal/node-utils/src/__tests__/hash.test.ts new file mode 100644 index 0000000..3851306 --- /dev/null +++ b/internal/node-utils/src/__tests__/hash.test.ts @@ -0,0 +1,52 @@ +import { createHash } from 'node:crypto'; + +import { describe, expect, it } from 'vitest'; + +import { generatorContentHash } from '../hash'; + +describe('generatorContentHash', () => { + it('should generate an MD5 hash for the content', () => { + const content = 'example content'; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex'); + const actualHash = generatorContentHash(content); + expect(actualHash).toBe(expectedHash); + }); + + it('should generate an MD5 hash with specified length', () => { + const content = 'example content'; + const hashLength = 10; + const generatedHash = generatorContentHash(content, hashLength); + expect(generatedHash).toHaveLength(hashLength); + }); + + it('should correctly generate the hash with specified length', () => { + const content = 'example content'; + const hashLength = 8; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex') + .slice(0, hashLength); + const generatedHash = generatorContentHash(content, hashLength); + expect(generatedHash).toBe(expectedHash); + }); + + it('should return full hash if hash length parameter is not provided', () => { + const content = 'example content'; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex'); + const actualHash = generatorContentHash(content); + expect(actualHash).toBe(expectedHash); + }); + + it('should handle empty content', () => { + const content = ''; + const expectedHash = createHash('md5') + .update(content, 'utf8') + .digest('hex'); + const actualHash = generatorContentHash(content); + expect(actualHash).toBe(expectedHash); + }); +}); diff --git a/internal/node-utils/src/__tests__/path.test.ts b/internal/node-utils/src/__tests__/path.test.ts new file mode 100644 index 0000000..3bab5a1 --- /dev/null +++ b/internal/node-utils/src/__tests__/path.test.ts @@ -0,0 +1,67 @@ +// pathUtils.test.ts + +import { describe, expect, it } from 'vitest'; + +import { toPosixPath } from '../path'; + +describe('toPosixPath', () => { + // 测试 Windows 风格路径到 POSIX 风格路径的转换 + it('converts Windows-style paths to POSIX paths', () => { + const windowsPath = String.raw`C:\Users\Example\file.txt`; + const expectedPosixPath = 'C:/Users/Example/file.txt'; + expect(toPosixPath(windowsPath)).toBe(expectedPosixPath); + }); + + // 确认 POSIX 风格路径不会被改变 + it('leaves POSIX-style paths unchanged', () => { + const posixPath = '/home/user/file.txt'; + expect(toPosixPath(posixPath)).toBe(posixPath); + }); + + // 测试带有多个分隔符的路径 + it('converts paths with mixed separators', () => { + const mixedPath = String.raw`C:/Users\Example\file.txt`; + const expectedPosixPath = 'C:/Users/Example/file.txt'; + expect(toPosixPath(mixedPath)).toBe(expectedPosixPath); + }); + + // 测试空字符串 + it('handles empty strings', () => { + const emptyPath = ''; + expect(toPosixPath(emptyPath)).toBe(''); + }); + + // 测试仅包含分隔符的路径 + it('handles path with only separators', () => { + const separatorsPath = '\\\\\\'; + const expectedPosixPath = '///'; + expect(toPosixPath(separatorsPath)).toBe(expectedPosixPath); + }); + + // 测试不包含任何分隔符的路径 + it('handles path without separators', () => { + const noSeparatorPath = 'file.txt'; + expect(toPosixPath(noSeparatorPath)).toBe('file.txt'); + }); + + // 测试以分隔符结尾的路径 + it('handles path ending with a separator', () => { + const endingSeparatorPath = 'C:\\Users\\Example\\'; + const expectedPosixPath = 'C:/Users/Example/'; + expect(toPosixPath(endingSeparatorPath)).toBe(expectedPosixPath); + }); + + // 测试以分隔符开头的路径 + it('handles path starting with a separator', () => { + const startingSeparatorPath = String.raw`\Users\Example`; + const expectedPosixPath = '/Users/Example'; + expect(toPosixPath(startingSeparatorPath)).toBe(expectedPosixPath); + }); + + // 测试包含非法字符的路径 + it('handles path with invalid characters', () => { + const invalidCharsPath = String.raw`C:\Us*?ers\Ex|file.txt`; + const expectedPosixPath = 'C:/Us*?ers/Ex|file.txt'; + expect(toPosixPath(invalidCharsPath)).toBe(expectedPosixPath); + }); +}); diff --git a/internal/node-utils/src/constants.ts b/internal/node-utils/src/constants.ts new file mode 100644 index 0000000..71d8a6c --- /dev/null +++ b/internal/node-utils/src/constants.ts @@ -0,0 +1,6 @@ +enum UNICODE { + FAILURE = '\u2716', // ✖ + SUCCESS = '\u2714', // ✔ +} + +export { UNICODE }; diff --git a/internal/node-utils/src/date.ts b/internal/node-utils/src/date.ts new file mode 100644 index 0000000..d36572d --- /dev/null +++ b/internal/node-utils/src/date.ts @@ -0,0 +1,12 @@ +import dayjs from 'dayjs'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; + +dayjs.extend(utc); +dayjs.extend(timezone); + +dayjs.tz.setDefault('Asia/Shanghai'); + +const dateUtil = dayjs; + +export { dateUtil }; diff --git a/internal/node-utils/src/fs.ts b/internal/node-utils/src/fs.ts new file mode 100644 index 0000000..8eec357 --- /dev/null +++ b/internal/node-utils/src/fs.ts @@ -0,0 +1,39 @@ +import { promises as fs } from 'node:fs'; +import { dirname } from 'node:path'; + +export async function outputJSON( + filePath: string, + data: any, + spaces: number = 2, +) { + try { + const dir = dirname(filePath); + await fs.mkdir(dir, { recursive: true }); + const jsonData = JSON.stringify(data, null, spaces); + await fs.writeFile(filePath, jsonData, 'utf8'); + } catch (error) { + console.error('Error writing JSON file:', error); + throw error; + } +} + +export async function ensureFile(filePath: string) { + try { + const dir = dirname(filePath); + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(filePath, '', { flag: 'a' }); + } catch (error) { + console.error('Error ensuring file:', error); + throw error; + } +} + +export async function readJSON(filePath: string) { + try { + const data = await fs.readFile(filePath, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Error reading JSON file:', error); + throw error; + } +} diff --git a/internal/node-utils/src/git.ts b/internal/node-utils/src/git.ts new file mode 100644 index 0000000..88f159c --- /dev/null +++ b/internal/node-utils/src/git.ts @@ -0,0 +1,34 @@ +import path from 'node:path'; + +import { execa } from 'execa'; + +export * from '@changesets/git'; + +/** + * 获取暂存区文件 + */ +async function getStagedFiles(): Promise { + try { + const { stdout } = await execa('git', [ + '-c', + 'submodule.recurse=false', + 'diff', + '--staged', + '--diff-filter=ACMR', + '--name-only', + '--ignore-submodules', + '-z', + ]); + + let changedList = stdout ? stdout.replace(/\0$/, '').split('\0') : []; + changedList = changedList.map((item) => path.resolve(process.cwd(), item)); + const changedSet = new Set(changedList); + changedSet.delete(''); + return [...changedSet]; + } catch (error) { + console.error('Failed to get staged files:', error); + return []; + } +} + +export { getStagedFiles }; diff --git a/internal/node-utils/src/hash.ts b/internal/node-utils/src/hash.ts new file mode 100644 index 0000000..81f6b05 --- /dev/null +++ b/internal/node-utils/src/hash.ts @@ -0,0 +1,18 @@ +import { createHash } from 'node:crypto'; + +/** + * 生产基于内容的 hash,可自定义长度 + * @param content + * @param hashLSize + */ +function generatorContentHash(content: string, hashLSize?: number) { + const hash = createHash('md5').update(content, 'utf8').digest('hex'); + + if (hashLSize) { + return hash.slice(0, hashLSize); + } + + return hash; +} + +export { generatorContentHash }; diff --git a/internal/node-utils/src/index.ts b/internal/node-utils/src/index.ts new file mode 100644 index 0000000..963cb87 --- /dev/null +++ b/internal/node-utils/src/index.ts @@ -0,0 +1,19 @@ +export * from './constants'; +export * from './date'; +export * from './fs'; +export * from './git'; +export { getStagedFiles, add as gitAdd } from './git'; +export { generatorContentHash } from './hash'; +export * from './monorepo'; +export { toPosixPath } from './path'; +export { prettierFormat } from './prettier'; +export * from './spinner'; +export type { Package } from '@manypkg/get-packages'; +export { default as colors } from 'chalk'; +export { consola } from 'consola'; +export * from 'execa'; + +export { default as fs } from 'node:fs/promises'; + +export { type PackageJson, readPackageJSON } from 'pkg-types'; +export { rimraf } from 'rimraf'; diff --git a/internal/node-utils/src/monorepo.ts b/internal/node-utils/src/monorepo.ts new file mode 100644 index 0000000..b6373e7 --- /dev/null +++ b/internal/node-utils/src/monorepo.ts @@ -0,0 +1,46 @@ +import { dirname } from 'node:path'; + +import { + getPackages as getPackagesFunc, + getPackagesSync as getPackagesSyncFunc, +} from '@manypkg/get-packages'; +import { findUpSync } from 'find-up'; + +/** + * 查找大仓的根目录 + * @param cwd + */ +function findMonorepoRoot(cwd: string = process.cwd()) { + const lockFile = findUpSync('pnpm-lock.yaml', { + cwd, + type: 'file', + }); + return dirname(lockFile || ''); +} + +/** + * 获取大仓的所有包 + */ +function getPackagesSync() { + const root = findMonorepoRoot(); + return getPackagesSyncFunc(root); +} + +/** + * 获取大仓的所有包 + */ +async function getPackages() { + const root = findMonorepoRoot(); + + return await getPackagesFunc(root); +} + +/** + * 获取大仓指定的包 + */ +async function getPackage(pkgName: string) { + const { packages } = await getPackages(); + return packages.find((pkg) => pkg.packageJson.name === pkgName); +} + +export { findMonorepoRoot, getPackage, getPackages, getPackagesSync }; diff --git a/internal/node-utils/src/path.ts b/internal/node-utils/src/path.ts new file mode 100644 index 0000000..e625fd2 --- /dev/null +++ b/internal/node-utils/src/path.ts @@ -0,0 +1,11 @@ +import { posix } from 'node:path'; + +/** + * 将给定的文件路径转换为 POSIX 风格。 + * @param {string} pathname - 原始文件路径。 + */ +function toPosixPath(pathname: string) { + return pathname.split(`\\`).join(posix.sep); +} + +export { toPosixPath }; diff --git a/internal/node-utils/src/prettier.ts b/internal/node-utils/src/prettier.ts new file mode 100644 index 0000000..1e1525d --- /dev/null +++ b/internal/node-utils/src/prettier.ts @@ -0,0 +1,21 @@ +import fs from 'node:fs/promises'; + +import { format, getFileInfo, resolveConfig } from 'prettier'; + +async function prettierFormat(filepath: string) { + const prettierOptions = await resolveConfig(filepath, {}); + + const fileInfo = await getFileInfo(filepath); + + const input = await fs.readFile(filepath, 'utf8'); + const output = await format(input, { + ...prettierOptions, + parser: fileInfo.inferredParser as any, + }); + if (output !== input) { + await fs.writeFile(filepath, output, 'utf8'); + } + return output; +} + +export { prettierFormat }; diff --git a/internal/node-utils/src/spinner.ts b/internal/node-utils/src/spinner.ts new file mode 100644 index 0000000..13ad6a4 --- /dev/null +++ b/internal/node-utils/src/spinner.ts @@ -0,0 +1,26 @@ +import type { Ora } from 'ora'; + +import ora from 'ora'; + +interface SpinnerOptions { + failedText?: string; + successText?: string; + title: string; +} +export async function spinner( + { failedText, successText, title }: SpinnerOptions, + callback: () => Promise, +): Promise { + const loading: Ora = ora(title).start(); + + try { + const result = await callback(); + loading.succeed(successText || 'Success!'); + return result; + } catch (error) { + loading.fail(failedText || 'Failed!'); + throw error; + } finally { + loading.stop(); + } +} diff --git a/internal/node-utils/tsconfig.json b/internal/node-utils/tsconfig.json new file mode 100644 index 0000000..b2ec3b6 --- /dev/null +++ b/internal/node-utils/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/tsconfig/node.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/internal/tailwind-config/build.config.ts b/internal/tailwind-config/build.config.ts new file mode 100644 index 0000000..1f3c3c2 --- /dev/null +++ b/internal/tailwind-config/build.config.ts @@ -0,0 +1,10 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index', './src/postcss.config'], + rollup: { + emitCJS: true, + }, +}); diff --git a/internal/tailwind-config/package.json b/internal/tailwind-config/package.json new file mode 100644 index 0000000..269558c --- /dev/null +++ b/internal/tailwind-config/package.json @@ -0,0 +1,67 @@ +{ + "name": "@vben/tailwind-config", + "version": "5.5.9", + "private": true, + "homepage": "https://github.com/vbenjs/vue-vben-admin", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "internal/tailwind-config" + }, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "./dist/*", + "./*" + ] + } + }, + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" + }, + "./postcss": { + "types": "./src/postcss.config.ts", + "import": "./dist/postcss.config.mjs", + "require": "./dist/postcss.config.cjs", + "default": "./dist/postcss.config.mjs" + }, + "./*": "./*" + }, + "peerDependencies": { + "tailwindcss": "^3.4.3" + }, + "dependencies": { + "@iconify/json": "catalog:", + "@iconify/tailwind": "catalog:", + "@manypkg/get-packages": "catalog:", + "@tailwindcss/nesting": "catalog:", + "@tailwindcss/typography": "catalog:", + "autoprefixer": "catalog:", + "cssnano": "catalog:", + "jiti": "catalog:", + "postcss": "catalog:", + "postcss-antd-fixes": "catalog:", + "postcss-import": "catalog:", + "postcss-preset-env": "catalog:", + "tailwindcss": "catalog:", + "tailwindcss-animate": "catalog:" + }, + "devDependencies": { + "@types/postcss-import": "catalog:" + } +} diff --git a/internal/tailwind-config/src/index.ts b/internal/tailwind-config/src/index.ts new file mode 100644 index 0000000..8bbf1f6 --- /dev/null +++ b/internal/tailwind-config/src/index.ts @@ -0,0 +1,266 @@ +import type { Config } from 'tailwindcss'; + +import path from 'node:path'; + +import { addDynamicIconSelectors } from '@iconify/tailwind'; +import { getPackagesSync } from '@manypkg/get-packages'; +import typographyPlugin from '@tailwindcss/typography'; +import animate from 'tailwindcss-animate'; + +import { enterAnimationPlugin } from './plugins/entry'; + +// import defaultTheme from 'tailwindcss/defaultTheme'; + +const { packages } = getPackagesSync(process.cwd()); + +const tailwindPackages: string[] = []; + +packages.forEach((pkg) => { + // apps目录下和 @vben-core/tailwind-ui 包需要使用到 tailwindcss ui + // if (fs.existsSync(path.join(pkg.dir, 'tailwind.config.mjs'))) { + tailwindPackages.push(pkg.dir); + // } +}); + +const shadcnUiColors = { + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))', + hover: 'hsl(var(--accent-hover))', + lighter: 'has(val(--accent-lighter))', + }, + background: { + deep: 'hsl(var(--background-deep))', + DEFAULT: 'hsl(var(--background))', + }, + border: { + DEFAULT: 'hsl(var(--border))', + }, + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))', + }, + destructive: { + ...createColorsPalette('destructive'), + DEFAULT: 'hsl(var(--destructive))', + }, + + foreground: { + DEFAULT: 'hsl(var(--foreground))', + }, + + input: { + background: 'hsl(var(--input-background))', + DEFAULT: 'hsl(var(--input))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))', + }, + primary: { + ...createColorsPalette('primary'), + DEFAULT: 'hsl(var(--primary))', + }, + + ring: 'hsl(var(--ring))', + secondary: { + DEFAULT: 'hsl(var(--secondary))', + desc: 'hsl(var(--secondary-desc))', + foreground: 'hsl(var(--secondary-foreground))', + }, +}; + +const customColors = { + green: { + ...createColorsPalette('green'), + foreground: 'hsl(var(--success-foreground))', + }, + header: { + DEFAULT: 'hsl(var(--header))', + }, + heavy: { + DEFAULT: 'hsl(var(--heavy))', + foreground: 'hsl(var(--heavy-foreground))', + }, + main: { + DEFAULT: 'hsl(var(--main))', + }, + overlay: { + content: 'hsl(var(--overlay-content))', + DEFAULT: 'hsl(var(--overlay))', + }, + red: { + ...createColorsPalette('red'), + foreground: 'hsl(var(--destructive-foreground))', + }, + sidebar: { + deep: 'hsl(var(--sidebar-deep))', + DEFAULT: 'hsl(var(--sidebar))', + }, + success: { + ...createColorsPalette('success'), + DEFAULT: 'hsl(var(--success))', + }, + warning: { + ...createColorsPalette('warning'), + DEFAULT: 'hsl(var(--warning))', + }, + yellow: { + ...createColorsPalette('yellow'), + foreground: 'hsl(var(--warning-foreground))', + }, +}; + +export default { + content: [ + './index.html', + ...tailwindPackages.map((item) => + path.join(item, 'src/**/*.{vue,js,ts,jsx,tsx,svelte,astro,html}'), + ), + ], + darkMode: 'selector', + plugins: [ + animate, + typographyPlugin, + addDynamicIconSelectors(), + enterAnimationPlugin, + ], + prefix: '', + theme: { + container: { + center: true, + padding: '2rem', + screens: { + '2xl': '1400px', + }, + }, + extend: { + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out', + 'collapsible-down': 'collapsible-down 0.2s ease-in-out', + 'collapsible-up': 'collapsible-up 0.2s ease-in-out', + float: 'float 5s linear 0ms infinite', + }, + + animationDuration: { + '2000': '2000ms', + '3000': '3000ms', + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + xl: 'calc(var(--radius) + 4px)', + }, + boxShadow: { + float: `0 6px 16px 0 rgb(0 0 0 / 8%), + 0 3px 6px -4px rgb(0 0 0 / 12%), + 0 9px 28px 8px rgb(0 0 0 / 5%)`, + }, + colors: { + ...customColors, + ...shadcnUiColors, + }, + fontFamily: { + sans: [ + 'var(--font-family)', + // ...defaultTheme.fontFamily.sans + ], + }, + keyframes: { + 'accordion-down': { + from: { height: '0' }, + to: { height: 'var(--reka-accordion-content-height)' }, + }, + 'accordion-up': { + from: { height: 'var(--reka-accordion-content-height)' }, + to: { height: '0' }, + }, + 'collapsible-down': { + from: { height: '0' }, + to: { height: 'var(--reka-collapsible-content-height)' }, + }, + 'collapsible-up': { + from: { height: 'var(--reka-collapsible-content-height)' }, + to: { height: '0' }, + }, + float: { + '0%': { transform: 'translateY(0)' }, + '50%': { transform: 'translateY(-20px)' }, + '100%': { transform: 'translateY(0)' }, + }, + }, + zIndex: { + '100': '100', + '1000': '1000', + }, + }, + }, + safelist: ['dark'], +} as Config; + +function createColorsPalette(name: string) { + // backgroundLightest: '#EFF6FF', // Tailwind CSS 默认的 `blue-50` + // backgroundLighter: '#DBEAFE', // Tailwind CSS 默认的 `blue-100` + // backgroundLight: '#BFDBFE', // Tailwind CSS 默认的 `blue-200` + // borderLight: '#93C5FD', // Tailwind CSS 默认的 `blue-300` + // border: '#60A5FA', // Tailwind CSS 默认的 `blue-400` + // main: '#3B82F6', // Tailwind CSS 默认的 `blue-500` + // hover: '#2563EB', // Tailwind CSS 默认的 `blue-600` + // active: '#1D4ED8', // Tailwind CSS 默认的 `blue-700` + // backgroundDark: '#1E40AF', // Tailwind CSS 默认的 `blue-800` + // backgroundDarker: '#1E3A8A', // Tailwind CSS 默认的 `blue-900` + // backgroundDarkest: '#172554', // Tailwind CSS 默认的 `blue-950` + + // • backgroundLightest (#EFF6FF): 适用于最浅的背景色,可能用于非常轻微的阴影或卡片的背景。 + // • backgroundLighter (#DBEAFE): 适用于略浅的背景色,通常用于次要背景或略浅的区域。 + // • backgroundLight (#BFDBFE): 适用于浅色背景,可能用于输入框或表单区域的背景。 + // • borderLight (#93C5FD): 适用于浅色边框,可能用于输入框或卡片的边框。 + // • border (#60A5FA): 适用于普通边框,可能用于按钮或卡片的边框。 + // • main (#3B82F6): 适用于主要的主题色,通常用于按钮、链接或主要的强调色。 + // • hover (#2563EB): 适用于鼠标悬停状态下的颜色,例如按钮悬停时的背景色或边框色。 + // • active (#1D4ED8): 适用于激活状态下的颜色,例如按钮按下时的背景色或边框色。 + // • backgroundDark (#1E40AF): 适用于深色背景,可能用于主要按钮或深色卡片背景。 + // • backgroundDarker (#1E3A8A): 适用于更深的背景,通常用于头部导航栏或页脚。 + // • backgroundDarkest (#172554): 适用于最深的背景,可能用于非常深色的区域或极端对比色。 + + return { + 50: `hsl(var(--${name}-50))`, + 100: `hsl(var(--${name}-100))`, + 200: `hsl(var(--${name}-200))`, + 300: `hsl(var(--${name}-300))`, + 400: `hsl(var(--${name}-400))`, + 500: `hsl(var(--${name}-500))`, + 600: `hsl(var(--${name}-600))`, + 700: `hsl(var(--${name}-700))`, + // 800: `hsl(var(--${name}-800))`, + // 900: `hsl(var(--${name}-900))`, + // 950: `hsl(var(--${name}-950))`, + // 激活状态下的颜色,适用于按钮按下时的背景色或边框色。 + active: `hsl(var(--${name}-700))`, + // 浅色背景,适用于输入框或表单区域的背景。 + 'background-light': `hsl(var(--${name}-200))`, + // 适用于略浅的背景色,通常用于次要背景或略浅的区域。 + 'background-lighter': `hsl(var(--${name}-100))`, + // 最浅的背景色,适用于非常轻微的阴影或卡片的背景。 + 'background-lightest': `hsl(var(--${name}-50))`, + // 适用于普通边框,可能用于按钮或卡片的边框。 + border: `hsl(var(--${name}-400))`, + // 浅色边框,适用于输入框或卡片的边框。 + 'border-light': `hsl(var(--${name}-300))`, + foreground: `hsl(var(--${name}-foreground))`, + // 鼠标悬停状态下的颜色,适用于按钮悬停时的背景色或边框色。 + hover: `hsl(var(--${name}-600))`, + // 主色文本 + text: `hsl(var(--${name}-500))`, + // 主色文本激活态 + 'text-active': `hsl(var(--${name}-700))`, + // 主色文本悬浮态 + 'text-hover': `hsl(var(--${name}-600))`, + }; +} diff --git a/internal/tailwind-config/src/module.d.ts b/internal/tailwind-config/src/module.d.ts new file mode 100644 index 0000000..a399653 --- /dev/null +++ b/internal/tailwind-config/src/module.d.ts @@ -0,0 +1,3 @@ +declare module '@tailwindcss/nesting' { + export default any; +} diff --git a/internal/tailwind-config/src/plugins/entry.ts b/internal/tailwind-config/src/plugins/entry.ts new file mode 100644 index 0000000..0d8e8ec --- /dev/null +++ b/internal/tailwind-config/src/plugins/entry.ts @@ -0,0 +1,53 @@ +import plugin from 'tailwindcss/plugin.js'; + +const enterAnimationPlugin = plugin(({ addUtilities }) => { + const maxChild = 5; + const utilities: Record = {}; + for (let i = 1; i <= maxChild; i++) { + const baseDelay = 0.1; + const delay = `${baseDelay * i}s`; + + utilities[`.enter-x:nth-child(${i})`] = { + animation: `enter-x-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateX(50px)`, + }; + + utilities[`.enter-y:nth-child(${i})`] = { + animation: `enter-y-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateY(50px)`, + }; + + utilities[`.-enter-x:nth-child(${i})`] = { + animation: `enter-x-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateX(-50px)`, + }; + + utilities[`.-enter-y:nth-child(${i})`] = { + animation: `enter-y-animation 0.3s ease-in-out ${delay} forwards`, + opacity: '0', + transform: `translateY(-50px)`, + }; + } + + // 添加动画关键帧 + addUtilities(utilities); + addUtilities({ + '@keyframes enter-x-animation': { + to: { + opacity: '1', + transform: 'translateX(0)', + }, + }, + '@keyframes enter-y-animation': { + to: { + opacity: '1', + transform: 'translateY(0)', + }, + }, + }); +}); + +export { enterAnimationPlugin }; diff --git a/internal/tailwind-config/src/postcss.config.ts b/internal/tailwind-config/src/postcss.config.ts new file mode 100644 index 0000000..43b30b3 --- /dev/null +++ b/internal/tailwind-config/src/postcss.config.ts @@ -0,0 +1,15 @@ +import config from '.'; + +export default { + plugins: { + ...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}), + // Specifying the config is not necessary in most cases, but it is included + autoprefixer: {}, + // 修复 element-plus 和 ant-design-vue 的样式和tailwindcss冲突问题 + 'postcss-antd-fixes': { prefixes: ['ant', 'el'] }, + 'postcss-import': {}, + 'postcss-preset-env': {}, + tailwindcss: { config }, + 'tailwindcss/nesting': {}, + }, +}; diff --git a/internal/tailwind-config/tsconfig.json b/internal/tailwind-config/tsconfig.json new file mode 100644 index 0000000..dbd3bcc --- /dev/null +++ b/internal/tailwind-config/tsconfig.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/tsconfig/node.json", + "compilerOptions": { + "moduleResolution": "bundler" + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/internal/tsconfig/base.json b/internal/tsconfig/base.json new file mode 100644 index 0000000..1e45a78 --- /dev/null +++ b/internal/tsconfig/base.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Base", + "compilerOptions": { + "composite": false, + "target": "ESNext", + + "moduleDetection": "force", + "experimentalDecorators": true, + + "baseUrl": ".", + "module": "ESNext", + + "moduleResolution": "node", + "resolveJsonModule": true, + + "strict": true, + "strictNullChecks": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitThis": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + + "inlineSources": false, + "noEmit": true, + "removeComments": true, + "sourceMap": false, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "preserveWatchOutput": true + }, + "exclude": ["**/node_modules/**", "**/dist/**", "**/.turbo/**"] +} diff --git a/internal/tsconfig/library.json b/internal/tsconfig/library.json new file mode 100644 index 0000000..7a976f0 --- /dev/null +++ b/internal/tsconfig/library.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Web Application", + "extends": "./base.json", + "compilerOptions": { + "jsx": "preserve", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "useDefineForClassFields": true, + "moduleResolution": "bundler", + "declaration": true, + "noEmit": false + } +} diff --git a/internal/tsconfig/node.json b/internal/tsconfig/node.json new file mode 100644 index 0000000..31ce8f1 --- /dev/null +++ b/internal/tsconfig/node.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Node Config", + "extends": "./base.json", + "compilerOptions": { + "composite": false, + "lib": ["ESNext"], + "baseUrl": "./", + "types": ["node"], + "noImplicitAny": true + } +} diff --git a/internal/tsconfig/package.json b/internal/tsconfig/package.json new file mode 100644 index 0000000..143bd32 --- /dev/null +++ b/internal/tsconfig/package.json @@ -0,0 +1,25 @@ +{ + "name": "@vben/tsconfig", + "version": "5.5.9", + "private": true, + "homepage": "https://github.com/vbenjs/vue-vben-admin", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "internal/tsconfig" + }, + "license": "MIT", + "type": "module", + "files": [ + "base.json", + "library.json", + "node.json", + "web-app.json", + "web.json" + ], + "dependencies": { + "@vben/types": "workspace:*", + "vite": "catalog:" + } +} diff --git a/internal/tsconfig/web-app.json b/internal/tsconfig/web-app.json new file mode 100644 index 0000000..00479cb --- /dev/null +++ b/internal/tsconfig/web-app.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Web Application", + "extends": "./web.json", + "compilerOptions": { + "types": ["vite/client", "@vben/types/global"] + } +} diff --git a/internal/tsconfig/web.json b/internal/tsconfig/web.json new file mode 100644 index 0000000..a4b60ce --- /dev/null +++ b/internal/tsconfig/web.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Web Package", + "extends": "./base.json", + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "vue", + "lib": ["ESNext", "DOM", "DOM.Iterable"], + "useDefineForClassFields": true, + "moduleResolution": "bundler", + "types": ["vite/client"], + "declaration": false + } +} diff --git a/internal/vite-config/build.config.ts b/internal/vite-config/build.config.ts new file mode 100644 index 0000000..97e572c --- /dev/null +++ b/internal/vite-config/build.config.ts @@ -0,0 +1,7 @@ +import { defineBuildConfig } from 'unbuild'; + +export default defineBuildConfig({ + clean: true, + declaration: true, + entries: ['src/index'], +}); diff --git a/internal/vite-config/package.json b/internal/vite-config/package.json new file mode 100644 index 0000000..db1fb49 --- /dev/null +++ b/internal/vite-config/package.json @@ -0,0 +1,59 @@ +{ + "name": "@vben/vite-config", + "version": "5.5.9", + "private": true, + "homepage": "https://github.com/vbenjs/vue-vben-admin", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "internal/vite-config" + }, + "license": "MIT", + "type": "module", + "scripts": { + "stub": "pnpm unbuild --stub" + }, + "files": [ + "dist" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./dist/index.mjs" + } + }, + "dependencies": { + "@intlify/unplugin-vue-i18n": "catalog:", + "@jspm/generator": "catalog:", + "archiver": "catalog:", + "cheerio": "catalog:", + "get-port": "catalog:", + "html-minifier-terser": "catalog:", + "nitropack": "catalog:", + "resolve.exports": "catalog:", + "vite-plugin-pwa": "catalog:", + "vite-plugin-vue-devtools": "catalog:" + }, + "devDependencies": { + "@pnpm/workspace.read-manifest": "catalog:", + "@types/archiver": "catalog:", + "@types/html-minifier-terser": "catalog:", + "@vben/node-utils": "workspace:*", + "@vitejs/plugin-vue": "catalog:", + "@vitejs/plugin-vue-jsx": "catalog:", + "dayjs": "catalog:", + "dotenv": "catalog:", + "rollup": "catalog:", + "rollup-plugin-visualizer": "catalog:", + "sass": "catalog:", + "vite": "catalog:", + "vite-plugin-compression": "catalog:", + "vite-plugin-dts": "catalog:", + "vite-plugin-html": "catalog:", + "vite-plugin-lazy-import": "catalog:" + } +} diff --git a/internal/vite-config/src/config/application.ts b/internal/vite-config/src/config/application.ts new file mode 100644 index 0000000..298cea0 --- /dev/null +++ b/internal/vite-config/src/config/application.ts @@ -0,0 +1,125 @@ +import type { CSSOptions, UserConfig } from 'vite'; + +import type { DefineApplicationOptions } from '../typing'; + +import path, { relative } from 'node:path'; + +import { findMonorepoRoot } from '@vben/node-utils'; + +import { NodePackageImporter } from 'sass'; +import { defineConfig, loadEnv, mergeConfig } from 'vite'; + +import { defaultImportmapOptions, getDefaultPwaOptions } from '../options'; +import { loadApplicationPlugins } from '../plugins'; +import { loadAndConvertEnv } from '../utils/env'; +import { getCommonConfig } from './common'; + +function defineApplicationConfig(userConfigPromise?: DefineApplicationOptions) { + return defineConfig(async (config) => { + const options = await userConfigPromise?.(config); + const { appTitle, base, port, ...envConfig } = await loadAndConvertEnv(); + const { command, mode } = config; + const { application = {}, vite = {} } = options || {}; + const root = process.cwd(); + const isBuild = command === 'build'; + const env = loadEnv(mode, root); + + const plugins = await loadApplicationPlugins({ + archiver: true, + archiverPluginOptions: {}, + compress: false, + compressTypes: ['brotli', 'gzip'], + devtools: true, + env, + extraAppConfig: true, + html: true, + i18n: true, + importmapOptions: defaultImportmapOptions, + injectAppLoading: true, + injectMetadata: true, + isBuild, + license: true, + mode, + nitroMock: !isBuild, + nitroMockOptions: {}, + print: !isBuild, + printInfoMap: { + Docs: 'https://doc.iocoder.cn/quick-start/', + }, + pwa: true, + pwaOptions: getDefaultPwaOptions(appTitle), + vxeTableLazyImport: true, + ...envConfig, + ...application, + }); + + const { injectGlobalScss = true } = application; + + const applicationConfig: UserConfig = { + base, + build: { + rollupOptions: { + output: { + assetFileNames: '[ext]/[name]-[hash].[ext]', + chunkFileNames: 'js/[name]-[hash].js', + entryFileNames: 'jse/index-[name]-[hash].js', + }, + }, + target: 'es2015', + }, + css: createCssOptions(injectGlobalScss), + esbuild: { + drop: isBuild + ? [ + // 'console', + 'debugger', + ] + : [], + legalComments: 'none', + }, + plugins, + server: { + host: true, + port, + warmup: { + // 预热文件 + clientFiles: [ + './index.html', + './src/bootstrap.ts', + './src/{views,layouts,router,store,api,adapter}/*', + ], + }, + }, + }; + + const mergedCommonConfig = mergeConfig( + await getCommonConfig(), + applicationConfig, + ); + return mergeConfig(mergedCommonConfig, vite); + }); +} + +function createCssOptions(injectGlobalScss = true): CSSOptions { + const root = findMonorepoRoot(); + return { + preprocessorOptions: injectGlobalScss + ? { + scss: { + additionalData: (content: string, filepath: string) => { + const relativePath = relative(root, filepath); + // apps下的包注入全局样式 + if (relativePath.startsWith(`apps${path.sep}`)) { + return `@use "@vben/styles/global" as *;\n${content}`; + } + return content; + }, + // api: 'modern', + importers: [new NodePackageImporter()], + }, + } + : {}, + }; +} + +export { defineApplicationConfig }; diff --git a/internal/vite-config/src/config/common.ts b/internal/vite-config/src/config/common.ts new file mode 100644 index 0000000..653f210 --- /dev/null +++ b/internal/vite-config/src/config/common.ts @@ -0,0 +1,13 @@ +import type { UserConfig } from 'vite'; + +async function getCommonConfig(): Promise { + return { + build: { + chunkSizeWarningLimit: 2000, + reportCompressedSize: false, + sourcemap: false, + }, + }; +} + +export { getCommonConfig }; diff --git a/internal/vite-config/src/config/index.ts b/internal/vite-config/src/config/index.ts new file mode 100644 index 0000000..d04a84a --- /dev/null +++ b/internal/vite-config/src/config/index.ts @@ -0,0 +1,37 @@ +import type { DefineConfig } from '../typing'; + +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; + +import { defineApplicationConfig } from './application'; +import { defineLibraryConfig } from './library'; + +export * from './application'; +export * from './library'; + +function defineConfig( + userConfigPromise?: DefineConfig, + type: 'application' | 'auto' | 'library' = 'auto', +) { + let projectType = type; + + // 根据包是否存在 index.html,自动判断类型 + if (projectType === 'auto') { + const htmlPath = join(process.cwd(), 'index.html'); + projectType = existsSync(htmlPath) ? 'application' : 'library'; + } + + switch (projectType) { + case 'application': { + return defineApplicationConfig(userConfigPromise); + } + case 'library': { + return defineLibraryConfig(userConfigPromise); + } + default: { + throw new Error(`Unsupported project type: ${projectType}`); + } + } +} + +export { defineConfig }; diff --git a/internal/vite-config/src/config/library.ts b/internal/vite-config/src/config/library.ts new file mode 100644 index 0000000..08b8135 --- /dev/null +++ b/internal/vite-config/src/config/library.ts @@ -0,0 +1,59 @@ +import type { ConfigEnv, UserConfig } from 'vite'; + +import type { DefineLibraryOptions } from '../typing'; + +import { readPackageJSON } from '@vben/node-utils'; + +import { defineConfig, mergeConfig } from 'vite'; + +import { loadLibraryPlugins } from '../plugins'; +import { getCommonConfig } from './common'; + +function defineLibraryConfig(userConfigPromise?: DefineLibraryOptions) { + return defineConfig(async (config: ConfigEnv) => { + const options = await userConfigPromise?.(config); + const { command, mode } = config; + const { library = {}, vite = {} } = options || {}; + const root = process.cwd(); + const isBuild = command === 'build'; + + const plugins = await loadLibraryPlugins({ + dts: false, + injectMetadata: true, + isBuild, + mode, + ...library, + }); + + const { dependencies = {}, peerDependencies = {} } = + await readPackageJSON(root); + + const externalPackages = [ + ...Object.keys(dependencies), + ...Object.keys(peerDependencies), + ]; + + const packageConfig: UserConfig = { + build: { + lib: { + entry: 'src/index.ts', + fileName: () => 'index.mjs', + formats: ['es'], + }, + rollupOptions: { + external: (id) => { + return externalPackages.some( + (pkg) => id === pkg || id.startsWith(`${pkg}/`), + ); + }, + }, + }, + plugins, + }; + const commonConfig = await getCommonConfig(); + const mergedConmonConfig = mergeConfig(commonConfig, packageConfig); + return mergeConfig(mergedConmonConfig, vite); + }); +} + +export { defineLibraryConfig }; diff --git a/internal/vite-config/src/index.ts b/internal/vite-config/src/index.ts new file mode 100644 index 0000000..352a323 --- /dev/null +++ b/internal/vite-config/src/index.ts @@ -0,0 +1,4 @@ +export * from './config'; +export * from './options'; +export * from './plugins'; +export { loadAndConvertEnv } from './utils/env'; diff --git a/internal/vite-config/src/options.ts b/internal/vite-config/src/options.ts new file mode 100644 index 0000000..f1e2401 --- /dev/null +++ b/internal/vite-config/src/options.ts @@ -0,0 +1,45 @@ +import type { Options as PwaPluginOptions } from 'vite-plugin-pwa'; + +import type { ImportmapPluginOptions } from './typing'; + +const isDevelopment = process.env.NODE_ENV === 'development'; + +const getDefaultPwaOptions = (name: string): Partial => ({ + manifest: { + description: + 'Vben Admin is a modern admin dashboard template based on Vue 3. ', + icons: [ + { + sizes: '192x192', + src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-192.png', + type: 'image/png', + }, + { + sizes: '512x512', + src: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/pwa-icon-512.png', + type: 'image/png', + }, + ], + name: `${name}${isDevelopment ? ' dev' : ''}`, + short_name: `${name}${isDevelopment ? ' dev' : ''}`, + }, +}); + +/** + * importmap CDN 暂时不开启,因为有些包不支持,且网络不稳定 + */ +const defaultImportmapOptions: ImportmapPluginOptions = { + // 通过 Importmap CDN 方式引入, + // 目前只有esm.sh源兼容性好一点,jspm.io对于 esm 入口要求高 + defaultProvider: 'esm.sh', + importmap: [ + { name: 'vue' }, + { name: 'pinia' }, + { name: 'vue-router' }, + // { name: 'vue-i18n' }, + { name: 'dayjs' }, + { name: 'vue-demi' }, + ], +}; + +export { defaultImportmapOptions, getDefaultPwaOptions }; diff --git a/internal/vite-config/src/plugins/archiver.ts b/internal/vite-config/src/plugins/archiver.ts new file mode 100644 index 0000000..8eec8a0 --- /dev/null +++ b/internal/vite-config/src/plugins/archiver.ts @@ -0,0 +1,75 @@ +import type { PluginOption } from 'vite'; + +import type { ArchiverPluginOptions } from '../typing'; + +import fs from 'node:fs'; +import fsp from 'node:fs/promises'; +import { join } from 'node:path'; + +import archiver from 'archiver'; + +export const viteArchiverPlugin = ( + options: ArchiverPluginOptions = {}, +): PluginOption => { + return { + apply: 'build', + closeBundle: { + handler() { + const { name = 'dist', outputDir = '.' } = options; + + setTimeout(async () => { + const folderToZip = 'dist'; + + const zipOutputDir = join(process.cwd(), outputDir); + const zipOutputPath = join(zipOutputDir, `${name}.zip`); + try { + await fsp.mkdir(zipOutputDir, { recursive: true }); + } catch { + // ignore + } + + try { + await zipFolder(folderToZip, zipOutputPath); + console.log(`Folder has been zipped to: ${zipOutputPath}`); + } catch (error) { + console.error('Error zipping folder:', error); + } + }, 0); + }, + order: 'post', + }, + enforce: 'post', + name: 'vite:archiver', + }; +}; + +async function zipFolder( + folderPath: string, + outputPath: string, +): Promise { + return new Promise((resolve, reject) => { + const output = fs.createWriteStream(outputPath); + const archive = archiver('zip', { + zlib: { level: 9 }, // 设置压缩级别为 9 以实现最高压缩率 + }); + + output.on('close', () => { + console.log( + `ZIP file created: ${outputPath} (${archive.pointer()} total bytes)`, + ); + resolve(); + }); + + archive.on('error', (err) => { + reject(err); + }); + + archive.pipe(output); + + // 使用 directory 方法以流的方式压缩文件夹,减少内存消耗 + archive.directory(folderPath, false); + + // 流式处理完成 + archive.finalize(); + }); +} diff --git a/internal/vite-config/src/plugins/extra-app-config.ts b/internal/vite-config/src/plugins/extra-app-config.ts new file mode 100644 index 0000000..813819b --- /dev/null +++ b/internal/vite-config/src/plugins/extra-app-config.ts @@ -0,0 +1,92 @@ +import type { PluginOption } from 'vite'; + +import { + colors, + generatorContentHash, + readPackageJSON, +} from '@vben/node-utils'; + +import { loadEnv } from '../utils/env'; + +interface PluginOptions { + isBuild: boolean; + root: string; +} + +const GLOBAL_CONFIG_FILE_NAME = '_app.config.js'; +const VBEN_ADMIN_PRO_APP_CONF = '_VBEN_ADMIN_PRO_APP_CONF_'; + +/** + * 用于将配置文件抽离出来并注入到项目中 + * @returns + */ + +async function viteExtraAppConfigPlugin({ + isBuild, + root, +}: PluginOptions): Promise { + let publicPath: string; + let source: string; + + if (!isBuild) { + return; + } + + const { version = '' } = await readPackageJSON(root); + + return { + async configResolved(config) { + publicPath = ensureTrailingSlash(config.base); + source = await getConfigSource(); + }, + async generateBundle() { + try { + this.emitFile({ + fileName: GLOBAL_CONFIG_FILE_NAME, + source, + type: 'asset', + }); + + console.log(colors.cyan(`✨configuration file is build successfully!`)); + } catch (error) { + console.log( + colors.red( + `configuration file configuration file failed to package:\n${error}`, + ), + ); + } + }, + name: 'vite:extra-app-config', + async transformIndexHtml(html) { + const hash = `v=${version}-${generatorContentHash(source, 8)}`; + + const appConfigSrc = `${publicPath}${GLOBAL_CONFIG_FILE_NAME}?${hash}`; + + return { + html, + tags: [{ attrs: { src: appConfigSrc }, tag: 'script' }], + }; + }, + }; +} + +async function getConfigSource() { + const config = await loadEnv(); + const windowVariable = `window.${VBEN_ADMIN_PRO_APP_CONF}`; + // 确保变量不会被修改 + let source = `${windowVariable}=${JSON.stringify(config)};`; + source += ` + Object.freeze(${windowVariable}); + Object.defineProperty(window, "${VBEN_ADMIN_PRO_APP_CONF}", { + configurable: false, + writable: false, + }); + `.replaceAll(/\s/g, ''); + return source; +} + +function ensureTrailingSlash(path: string) { + return path.endsWith('/') ? path : `${path}/`; +} + +export { viteExtraAppConfigPlugin }; diff --git a/internal/vite-config/src/plugins/importmap.ts b/internal/vite-config/src/plugins/importmap.ts new file mode 100644 index 0000000..0ccda99 --- /dev/null +++ b/internal/vite-config/src/plugins/importmap.ts @@ -0,0 +1,245 @@ +/** + * 参考 https://github.com/jspm/vite-plugin-jspm,调整为需要的功能 + */ +import type { GeneratorOptions } from '@jspm/generator'; +import type { Plugin } from 'vite'; + +import { Generator } from '@jspm/generator'; +import { load } from 'cheerio'; +import { minify } from 'html-minifier-terser'; + +const DEFAULT_PROVIDER = 'jspm.io'; + +type pluginOptions = GeneratorOptions & { + debug?: boolean; + defaultProvider?: 'esm.sh' | 'jsdelivr' | 'jspm.io'; + importmap?: Array<{ name: string; range?: string }>; +}; + +// async function getLatestVersionOfShims() { +// const result = await fetch('https://ga.jspm.io/npm:es-module-shims'); +// const version = result.text(); +// return version; +// } + +async function getShimsUrl(provide: string) { + // const version = await getLatestVersionOfShims(); + const version = '1.10.0'; + + const shimsSubpath = `dist/es-module-shims.js`; + const providerShimsMap: Record = { + 'esm.sh': `https://esm.sh/es-module-shims@${version}/${shimsSubpath}`, + // unpkg: `https://unpkg.com/es-module-shims@${version}/${shimsSubpath}`, + jsdelivr: `https://cdn.jsdelivr.net/npm/es-module-shims@${version}/${shimsSubpath}`, + + // 下面两个CDN不稳定,暂时不用 + 'jspm.io': `https://ga.jspm.io/npm:es-module-shims@${version}/${shimsSubpath}`, + }; + + return providerShimsMap[provide] || providerShimsMap[DEFAULT_PROVIDER]; +} + +let generator: Generator; + +async function viteImportMapPlugin( + pluginOptions?: pluginOptions, +): Promise { + const { importmap } = pluginOptions || {}; + + let isSSR = false; + let isBuild = false; + let installed = false; + let installError: Error | null = null; + + const options: pluginOptions = Object.assign( + {}, + { + debug: false, + defaultProvider: 'jspm.io', + env: ['production', 'browser', 'module'], + importmap: [], + }, + pluginOptions, + ); + + generator = new Generator({ + ...options, + baseUrl: process.cwd(), + }); + + if (options?.debug) { + (async () => { + for await (const { message, type } of generator.logStream()) { + console.log(`${type}: ${message}`); + } + })(); + } + + const imports = options.inputMap?.imports ?? {}; + const scopes = options.inputMap?.scopes ?? {}; + const firstLayerKeys = Object.keys(scopes); + const inputMapScopes: string[] = []; + firstLayerKeys.forEach((key) => { + inputMapScopes.push(...Object.keys(scopes[key] || {})); + }); + const inputMapImports = Object.keys(imports); + + const allDepNames: string[] = [ + ...(importmap?.map((item) => item.name) || []), + ...inputMapImports, + ...inputMapScopes, + ]; + const depNames = new Set(allDepNames); + + const installDeps = importmap?.map((item) => ({ + range: item.range, + target: item.name, + })); + + return [ + { + async config(_, { command, isSsrBuild }) { + isBuild = command === 'build'; + isSSR = !!isSsrBuild; + }, + enforce: 'pre', + name: 'importmap:external', + resolveId(id) { + if (isSSR || !isBuild) { + return null; + } + + if (!depNames.has(id)) { + return null; + } + return { external: true, id }; + }, + }, + { + enforce: 'post', + name: 'importmap:install', + async resolveId() { + if (isSSR || !isBuild || installed) { + return null; + } + try { + installed = true; + await Promise.allSettled( + (installDeps || []).map((dep) => generator.install(dep)), + ); + } catch (error: any) { + installError = error; + installed = false; + } + return null; + }, + }, + { + buildEnd() { + // 未生成importmap时,抛出错误,防止被turbo缓存 + if (!installed && !isSSR) { + installError && console.error(installError); + throw new Error('Importmap installation failed.'); + } + }, + enforce: 'post', + name: 'importmap:html', + transformIndexHtml: { + async handler(html) { + if (isSSR || !isBuild) { + return html; + } + + const importmapJson = generator.getMap(); + + if (!importmapJson) { + return html; + } + + const esModuleShimsSrc = await getShimsUrl( + options.defaultProvider || DEFAULT_PROVIDER, + ); + + const resultHtml = await injectShimsToHtml( + html, + esModuleShimsSrc || '', + ); + html = await minify(resultHtml || html, { + collapseWhitespace: true, + minifyCSS: true, + minifyJS: true, + removeComments: false, + }); + + return { + html, + tags: [ + { + attrs: { + type: 'importmap', + }, + injectTo: 'head-prepend', + tag: 'script', + children: `${JSON.stringify(importmapJson)}`, + }, + ], + }; + }, + order: 'post', + }, + }, + ]; +} + +async function injectShimsToHtml(html: string, esModuleShimUrl: string) { + const $ = load(html); + + const $script = $(`script[type='module']`); + + if (!$script) { + return; + } + + const entry = $script.attr('src'); + + $script.removeAttr('type'); + $script.removeAttr('crossorigin'); + $script.removeAttr('src'); + $script.html(` +if (!HTMLScriptElement.supports || !HTMLScriptElement.supports('importmap')) { + self.importShim = function () { + const promise = new Promise((resolve, reject) => { + document.head.appendChild( + Object.assign(document.createElement('script'), { + src: '${esModuleShimUrl}', + crossorigin: 'anonymous', + async: true, + onload() { + if (!importShim.$proxy) { + resolve(importShim); + } else { + reject(new Error('No globalThis.importShim found:' + esModuleShimUrl)); + } + }, + onerror(error) { + reject(error); + }, + }), + ); + }); + importShim.$proxy = true; + return promise.then((importShim) => importShim(...arguments)); + }; +} + +var modules = ['${entry}']; +typeof importShim === 'function' + ? modules.forEach((moduleName) => importShim(moduleName)) + : modules.forEach((moduleName) => import(moduleName)); + `); + $('body').after($script); + $('head').remove(`script[type='module']`); + return $.html(); +} + +export { viteImportMapPlugin }; diff --git a/internal/vite-config/src/plugins/index.ts b/internal/vite-config/src/plugins/index.ts new file mode 100644 index 0000000..da08db4 --- /dev/null +++ b/internal/vite-config/src/plugins/index.ts @@ -0,0 +1,247 @@ +import type { PluginOption } from 'vite'; + +import type { + ApplicationPluginOptions, + CommonPluginOptions, + ConditionPlugin, + LibraryPluginOptions, +} from '../typing'; + +import viteVueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'; +import viteVue from '@vitejs/plugin-vue'; +import viteVueJsx from '@vitejs/plugin-vue-jsx'; +import { visualizer as viteVisualizerPlugin } from 'rollup-plugin-visualizer'; +import viteCompressPlugin from 'vite-plugin-compression'; +import viteDtsPlugin from 'vite-plugin-dts'; +import { createHtmlPlugin as viteHtmlPlugin } from 'vite-plugin-html'; +import { VitePWA } from 'vite-plugin-pwa'; +import viteVueDevTools from 'vite-plugin-vue-devtools'; + +import { viteArchiverPlugin } from './archiver'; +import { viteExtraAppConfigPlugin } from './extra-app-config'; +import { viteImportMapPlugin } from './importmap'; +import { viteInjectAppLoadingPlugin } from './inject-app-loading'; +import { viteMetadataPlugin } from './inject-metadata'; +import { viteLicensePlugin } from './license'; +import { viteNitroMockPlugin } from './nitro-mock'; +import { vitePrintPlugin } from './print'; +import { viteVxeTableImportsPlugin } from './vxe-table'; + +/** + * 获取条件成立的 vite 插件 + * @param conditionPlugins + */ +async function loadConditionPlugins(conditionPlugins: ConditionPlugin[]) { + const plugins: PluginOption[] = []; + for (const conditionPlugin of conditionPlugins) { + if (conditionPlugin.condition) { + const realPlugins = await conditionPlugin.plugins(); + plugins.push(...realPlugins); + } + } + return plugins.flat(); +} + +/** + * 根据条件获取通用的vite插件 + */ +async function loadCommonPlugins( + options: CommonPluginOptions, +): Promise { + const { devtools, injectMetadata, isBuild, visualizer } = options; + return [ + { + condition: true, + plugins: () => [ + viteVue({ + script: { + defineModel: true, + // propsDestructure: true, + }, + }), + viteVueJsx(), + ], + }, + + { + condition: !isBuild && devtools, + plugins: () => [viteVueDevTools()], + }, + { + condition: injectMetadata, + plugins: async () => [await viteMetadataPlugin()], + }, + { + condition: isBuild && !!visualizer, + plugins: () => [viteVisualizerPlugin({ + filename: './node_modules/.cache/visualizer/stats.html', + gzipSize: true, + open: true, + })], + }, + ]; +} + +/** + * 根据条件获取应用类型的vite插件 + */ +async function loadApplicationPlugins( + options: ApplicationPluginOptions, +): Promise { + // 单独取,否则commonOptions拿不到 + const isBuild = options.isBuild; + const env = options.env; + + const { + archiver, + archiverPluginOptions, + compress, + compressTypes, + extraAppConfig, + html, + i18n, + importmap, + importmapOptions, + injectAppLoading, + license, + nitroMock, + nitroMockOptions, + print, + printInfoMap, + pwa, + pwaOptions, + vxeTableLazyImport, + ...commonOptions + } = options; + + const commonPlugins = await loadCommonPlugins(commonOptions); + + return await loadConditionPlugins([ + ...commonPlugins, + { + condition: i18n, + plugins: async () => { + return [ + viteVueI18nPlugin({ + compositionOnly: true, + fullInstall: true, + runtimeOnly: true, + }), + ]; + }, + }, + { + condition: print, + plugins: async () => { + return [await vitePrintPlugin({ infoMap: printInfoMap })]; + }, + }, + { + condition: vxeTableLazyImport, + plugins: async () => { + return [await viteVxeTableImportsPlugin()]; + }, + }, + { + condition: nitroMock, + plugins: async () => { + return [await viteNitroMockPlugin(nitroMockOptions)]; + }, + }, + + { + condition: injectAppLoading, + plugins: async () => [await viteInjectAppLoadingPlugin(!!isBuild, env)], + }, + { + condition: license, + plugins: async () => [await viteLicensePlugin()], + }, + { + condition: pwa, + plugins: () => + VitePWA({ + injectRegister: false, + workbox: { + globPatterns: [], + }, + ...pwaOptions, + manifest: { + display: 'standalone', + start_url: '/', + theme_color: '#ffffff', + ...pwaOptions?.manifest, + }, + }), + }, + { + condition: isBuild && !!compress, + plugins: () => { + const compressPlugins: PluginOption[] = []; + if (compressTypes?.includes('brotli')) { + compressPlugins.push( + viteCompressPlugin({ deleteOriginFile: false, ext: '.br' }), + ); + } + if (compressTypes?.includes('gzip')) { + compressPlugins.push( + viteCompressPlugin({ deleteOriginFile: false, ext: '.gz' }), + ); + } + return compressPlugins; + }, + }, + { + condition: !!html, + plugins: () => [viteHtmlPlugin({ minify: true })], + }, + { + condition: isBuild && importmap, + plugins: () => { + return [viteImportMapPlugin(importmapOptions)]; + }, + }, + { + condition: isBuild && extraAppConfig, + plugins: async () => [ + await viteExtraAppConfigPlugin({ isBuild: true, root: process.cwd() }), + ], + }, + { + condition: archiver, + plugins: async () => { + return [await viteArchiverPlugin(archiverPluginOptions)]; + }, + }, + ]); +} + +/** + * 根据条件获取库类型的vite插件 + */ +async function loadLibraryPlugins( + options: LibraryPluginOptions, +): Promise { + // 单独取,否则commonOptions拿不到 + const isBuild = options.isBuild; + const { dts, ...commonOptions } = options; + const commonPlugins = await loadCommonPlugins(commonOptions); + return await loadConditionPlugins([ + ...commonPlugins, + { + condition: isBuild && !!dts, + plugins: () => [viteDtsPlugin({ logLevel: 'error' })], + }, + ]); +} + +export { + loadApplicationPlugins, + loadLibraryPlugins, + viteArchiverPlugin, + viteCompressPlugin, + viteDtsPlugin, + viteHtmlPlugin, + viteVisualizerPlugin, + viteVxeTableImportsPlugin, +}; diff --git a/internal/vite-config/src/plugins/inject-app-loading/README.md b/internal/vite-config/src/plugins/inject-app-loading/README.md new file mode 100644 index 0000000..8d2358f --- /dev/null +++ b/internal/vite-config/src/plugins/inject-app-loading/README.md @@ -0,0 +1,3 @@ +# inject-app-loading + +用于在应用加载时显示加载动画的插件,可自行选择加载动画的样式。 diff --git a/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html b/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html new file mode 100644 index 0000000..20a21fb --- /dev/null +++ b/internal/vite-config/src/plugins/inject-app-loading/default-loading-antd.html @@ -0,0 +1,107 @@ + +
    + +
    <%= VITE_APP_TITLE %>
    +
    diff --git a/internal/vite-config/src/plugins/inject-app-loading/default-loading.html b/internal/vite-config/src/plugins/inject-app-loading/default-loading.html new file mode 100644 index 0000000..2895705 --- /dev/null +++ b/internal/vite-config/src/plugins/inject-app-loading/default-loading.html @@ -0,0 +1,113 @@ + +
    +
    +
    <%= VITE_APP_TITLE %>
    +
    diff --git a/internal/vite-config/src/plugins/inject-app-loading/index.ts b/internal/vite-config/src/plugins/inject-app-loading/index.ts new file mode 100644 index 0000000..c6a7983 --- /dev/null +++ b/internal/vite-config/src/plugins/inject-app-loading/index.ts @@ -0,0 +1,66 @@ +import type { PluginOption } from 'vite'; + +import fs from 'node:fs'; +import fsp from 'node:fs/promises'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { readPackageJSON } from '@vben/node-utils'; + +/** + * 用于生成将loading样式注入到项目中 + * 为多app提供loading样式,无需在每个 app -> index.html单独引入 + */ +async function viteInjectAppLoadingPlugin( + isBuild: boolean, + env: Record = {}, + loadingTemplate = 'loading.html', +): Promise { + const loadingHtml = await getLoadingRawByHtmlTemplate(loadingTemplate); + const { version } = await readPackageJSON(process.cwd()); + const envRaw = isBuild ? 'prod' : 'dev'; + const cacheName = `'${env.VITE_APP_NAMESPACE}-${version}-${envRaw}-preferences-theme'`; + + // 获取缓存的主题 + // 保证黑暗主题下,刷新页面时,loading也是黑暗主题 + const injectScript = ` + +`; + + if (!loadingHtml) { + return; + } + + return { + enforce: 'pre', + name: 'vite:inject-app-loading', + transformIndexHtml: { + handler(html) { + const re = //; + html = html.replace(re, `${injectScript}${loadingHtml}`); + return html; + }, + order: 'pre', + }, + }; +} + +/** + * 用于获取loading的html模板 + */ +async function getLoadingRawByHtmlTemplate(loadingTemplate: string) { + // 支持在app内自定义loading模板,模版参考default-loading.html即可 + let appLoadingPath = join(process.cwd(), loadingTemplate); + + if (!fs.existsSync(appLoadingPath)) { + const __dirname = fileURLToPath(new URL('.', import.meta.url)); + appLoadingPath = join(__dirname, './default-loading.html'); + } + + return await fsp.readFile(appLoadingPath, 'utf8'); +} + +export { viteInjectAppLoadingPlugin }; diff --git a/internal/vite-config/src/plugins/inject-metadata.ts b/internal/vite-config/src/plugins/inject-metadata.ts new file mode 100644 index 0000000..41c4db4 --- /dev/null +++ b/internal/vite-config/src/plugins/inject-metadata.ts @@ -0,0 +1,111 @@ +import type { PluginOption } from 'vite'; + +import { + dateUtil, + findMonorepoRoot, + getPackages, + readPackageJSON, +} from '@vben/node-utils'; + +import { readWorkspaceManifest } from '@pnpm/workspace.read-manifest'; + +function resolvePackageVersion( + pkgsMeta: Record, + name: string, + value: string, + catalog: Record, +) { + if (value.includes('catalog:')) { + return catalog[name]; + } + + if (value.includes('workspace')) { + return pkgsMeta[name]; + } + + return value; +} + +async function resolveMonorepoDependencies() { + const { packages } = await getPackages(); + const manifest = await readWorkspaceManifest(findMonorepoRoot()); + const catalog = manifest?.catalog || {}; + + const resultDevDependencies: Record = {}; + const resultDependencies: Record = {}; + const pkgsMeta: Record = {}; + + for (const { packageJson } of packages) { + pkgsMeta[packageJson.name] = packageJson.version; + } + + for (const { packageJson } of packages) { + const { dependencies = {}, devDependencies = {} } = packageJson; + for (const [key, value] of Object.entries(dependencies)) { + resultDependencies[key] = resolvePackageVersion( + pkgsMeta, + key, + value, + catalog, + ); + } + for (const [key, value] of Object.entries(devDependencies)) { + resultDevDependencies[key] = resolvePackageVersion( + pkgsMeta, + key, + value, + catalog, + ); + } + } + return { + dependencies: resultDependencies, + devDependencies: resultDevDependencies, + }; +} + +/** + * 用于注入项目信息 + */ +async function viteMetadataPlugin( + root = process.cwd(), +): Promise { + const { author, description, homepage, license, version } = + await readPackageJSON(root); + + const buildTime = dateUtil().format('YYYY-MM-DD HH:mm:ss'); + + return { + async config() { + const { dependencies, devDependencies } = + await resolveMonorepoDependencies(); + + const isAuthorObject = typeof author === 'object'; + const authorName = isAuthorObject ? author.name : author; + const authorEmail = isAuthorObject ? author.email : null; + const authorUrl = isAuthorObject ? author.url : null; + + return { + define: { + __VBEN_ADMIN_METADATA__: JSON.stringify({ + authorEmail, + authorName, + authorUrl, + buildTime, + dependencies, + description, + devDependencies, + homepage, + license, + version, + }), + 'import.meta.env.VITE_APP_VERSION': JSON.stringify(version), + }, + }; + }, + enforce: 'post', + name: 'vite:inject-metadata', + }; +} + +export { viteMetadataPlugin }; diff --git a/internal/vite-config/src/plugins/license.ts b/internal/vite-config/src/plugins/license.ts new file mode 100644 index 0000000..81fc4ff --- /dev/null +++ b/internal/vite-config/src/plugins/license.ts @@ -0,0 +1,63 @@ +import type { + NormalizedOutputOptions, + OutputBundle, + OutputChunk, +} from 'rollup'; +import type { PluginOption } from 'vite'; + +import { EOL } from 'node:os'; + +import { dateUtil, readPackageJSON } from '@vben/node-utils'; + +/** + * 用于注入版权信息 + * @returns + */ + +async function viteLicensePlugin( + root = process.cwd(), +): Promise { + const { + description = '', + homepage = '', + version = '', + } = await readPackageJSON(root); + + return { + apply: 'build', + enforce: 'post', + generateBundle: { + handler: (_options: NormalizedOutputOptions, bundle: OutputBundle) => { + const date = dateUtil().format('YYYY-MM-DD '); + const copyrightText = `/*! + * Vben Admin + * Version: ${version} + * Author: vben + * Copyright (C) 2024 Vben + * License: MIT License + * Description: ${description} + * Date Created: ${date} + * Homepage: ${homepage} + * Contact: ann.vben@gmail.com +*/ + `.trim(); + + for (const [, fileContent] of Object.entries(bundle)) { + if (fileContent.type === 'chunk' && fileContent.isEntry) { + const chunkContent = fileContent as OutputChunk; + // 插入版权信息 + const content = chunkContent.code; + const updatedContent = `${copyrightText}${EOL}${content}`; + + // 更新bundle + (fileContent as OutputChunk).code = updatedContent; + } + } + }, + order: 'post', + }, + name: 'vite:license', + }; +} + +export { viteLicensePlugin }; diff --git a/internal/vite-config/src/plugins/nitro-mock.ts b/internal/vite-config/src/plugins/nitro-mock.ts new file mode 100644 index 0000000..60d7327 --- /dev/null +++ b/internal/vite-config/src/plugins/nitro-mock.ts @@ -0,0 +1,98 @@ +import type { PluginOption } from 'vite'; + +import type { NitroMockPluginOptions } from '../typing'; + +import { colors, consola, getPackage } from '@vben/node-utils'; + +import getPort from 'get-port'; +import { build, createDevServer, createNitro, prepare } from 'nitropack'; + +const hmrKeyRe = /^runtimeConfig\.|routeRules\./; + +export const viteNitroMockPlugin = ({ + mockServerPackage = '@vben/backend-mock', + port = 5320, + verbose = true, +}: NitroMockPluginOptions = {}): PluginOption => { + return { + async configureServer(server) { + const availablePort = await getPort({ port }); + if (availablePort !== port) { + return; + } + + const pkg = await getPackage(mockServerPackage); + if (!pkg) { + consola.log( + `Package ${mockServerPackage} not found. Skip mock server.`, + ); + return; + } + + runNitroServer(pkg.dir, port, verbose); + + const _printUrls = server.printUrls; + server.printUrls = () => { + _printUrls(); + + consola.log( + ` ${colors.green('➜')} ${colors.bold('Nitro Mock Server')}: ${colors.cyan(`http://localhost:${port}/api`)}`, + ); + }; + }, + enforce: 'pre', + name: 'vite:mock-server', + }; +}; + +async function runNitroServer(rootDir: string, port: number, verbose: boolean) { + let nitro: any; + const reload = async () => { + if (nitro) { + consola.info('Restarting dev server...'); + if ('unwatch' in nitro.options._c12) { + await nitro.options._c12.unwatch(); + } + await nitro.close(); + } + nitro = await createNitro( + { + dev: true, + preset: 'nitro-dev', + rootDir, + }, + { + c12: { + async onUpdate({ getDiff, newConfig }) { + const diff = getDiff(); + if (diff.length === 0) { + return; + } + verbose && + consola.info( + `Nitro config updated:\n${diff + .map((entry) => ` ${entry.toString()}`) + .join('\n')}`, + ); + await (diff.every((e) => hmrKeyRe.test(e.key)) + ? nitro.updateConfig(newConfig.config) + : reload()); + }, + }, + watch: true, + }, + ); + nitro.hooks.hookOnce('restart', reload); + + const server = createDevServer(nitro); + await server.listen(port, { showURL: false }); + await prepare(nitro); + await build(nitro); + + if (verbose) { + console.log(''); + consola.success(colors.bold(colors.green('Nitro Mock Server started.'))); + } + }; + return await reload(); +} diff --git a/internal/vite-config/src/plugins/print.ts b/internal/vite-config/src/plugins/print.ts new file mode 100644 index 0000000..0146b8a --- /dev/null +++ b/internal/vite-config/src/plugins/print.ts @@ -0,0 +1,28 @@ +import type { PluginOption } from 'vite'; + +import type { PrintPluginOptions } from '../typing'; + +import { colors } from '@vben/node-utils'; + +export const vitePrintPlugin = ( + options: PrintPluginOptions = {}, +): PluginOption => { + const { infoMap = {} } = options; + + return { + configureServer(server) { + const _printUrls = server.printUrls; + server.printUrls = () => { + _printUrls(); + + for (const [key, value] of Object.entries(infoMap)) { + console.log( + ` ${colors.green('➜')} ${colors.bold(key)}: ${colors.cyan(value)}`, + ); + } + }; + }, + enforce: 'pre', + name: 'vite:print-info', + }; +}; diff --git a/internal/vite-config/src/plugins/vxe-table.ts b/internal/vite-config/src/plugins/vxe-table.ts new file mode 100644 index 0000000..3c107a7 --- /dev/null +++ b/internal/vite-config/src/plugins/vxe-table.ts @@ -0,0 +1,20 @@ +import type { PluginOption } from 'vite'; + +import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import'; + +async function viteVxeTableImportsPlugin(): Promise { + return [ + lazyImport({ + resolvers: [ + VxeResolver({ + libraryName: 'vxe-table', + }), + VxeResolver({ + libraryName: 'vxe-pc-ui', + }), + ], + }), + ]; +} + +export { viteVxeTableImportsPlugin }; diff --git a/internal/vite-config/src/typing.ts b/internal/vite-config/src/typing.ts new file mode 100644 index 0000000..f730bdb --- /dev/null +++ b/internal/vite-config/src/typing.ts @@ -0,0 +1,343 @@ +import type { PluginVisualizerOptions } from 'rollup-plugin-visualizer'; +import type { ConfigEnv, PluginOption, UserConfig } from 'vite'; +import type { PluginOptions } from 'vite-plugin-dts'; +import type { Options as PwaPluginOptions } from 'vite-plugin-pwa'; + +/** + * ImportMap 配置接口 + * @description 用于配置模块导入映射,支持自定义导入路径和范围 + * @example + * ```typescript + * { + * imports: { + * 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js' + * }, + * scopes: { + * 'https://site.com/': { + * 'vue': 'https://unpkg.com/vue@3.2.47/dist/vue.esm-browser.js' + * } + * } + * } + * ``` + */ +interface IImportMap { + /** 模块导入映射 */ + imports?: Record; + /** 作用域特定的导入映射 */ + scopes?: { + [scope: string]: Record; + }; +} + +/** + * 打印插件配置选项 + * @description 用于配置控制台打印信息 + */ +interface PrintPluginOptions { + /** + * 打印的数据映射 + * @description 键值对形式的数据,将在控制台打印 + * @example + * ```typescript + * { + * 'App Version': '1.0.0', + * 'Build Time': '2024-01-01' + * } + * ``` + */ + infoMap?: Record; +} + +/** + * Nitro Mock 插件配置选项 + * @description 用于配置 Nitro Mock 服务器的行为 + */ +interface NitroMockPluginOptions { + /** + * Mock 服务器包名 + * @default '@vbenjs/nitro-mock' + */ + mockServerPackage?: string; + + /** + * Mock 服务端口 + * @default 3000 + */ + port?: number; + + /** + * 是否打印 Mock 日志 + * @default false + */ + verbose?: boolean; +} + +/** + * 归档插件配置选项 + * @description 用于配置构建产物的压缩归档 + */ +interface ArchiverPluginOptions { + /** + * 输出文件名 + * @default 'dist' + */ + name?: string; + /** + * 输出目录 + * @default '.' + */ + outputDir?: string; +} + +/** + * ImportMap 插件配置 + * @description 用于配置模块的 CDN 导入 + */ +interface ImportmapPluginOptions { + /** + * CDN 供应商 + * @default 'jspm.io' + * @description 支持 esm.sh 和 jspm.io 两种 CDN 供应商 + */ + defaultProvider?: 'esm.sh' | 'jspm.io'; + /** + * ImportMap 配置数组 + * @description 配置需要从 CDN 导入的包 + * @example + * ```typescript + * [ + * { name: 'vue' }, + * { name: 'pinia', range: '^2.0.0' } + * ] + * ``` + */ + importmap?: Array<{ name: string; range?: string }>; + /** + * 手动配置 ImportMap + * @description 自定义 ImportMap 配置 + */ + inputMap?: IImportMap; +} + +/** + * 条件插件配置 + * @description 用于根据条件动态加载插件 + */ +interface ConditionPlugin { + /** + * 判断条件 + * @description 当条件为 true 时加载插件 + */ + condition?: boolean; + /** + * 插件对象 + * @description 返回插件数组或 Promise + */ + plugins: () => PluginOption[] | PromiseLike; +} + +/** + * 通用插件配置选项 + * @description 所有插件共用的基础配置 + */ +interface CommonPluginOptions { + /** + * 是否开启开发工具 + * @default false + */ + devtools?: boolean; + /** + * 环境变量 + * @description 自定义环境变量 + */ + env?: Record; + /** + * 是否注入元数据 + * @default true + */ + injectMetadata?: boolean; + /** + * 是否为构建模式 + * @default false + */ + isBuild?: boolean; + /** + * 构建模式 + * @default 'development' + */ + mode?: string; + /** + * 是否开启依赖分析 + * @default false + * @description 使用 rollup-plugin-visualizer 分析依赖 + */ + visualizer?: boolean | PluginVisualizerOptions; +} + +/** + * 应用插件配置选项 + * @description 用于配置应用构建时的插件选项 + */ +interface ApplicationPluginOptions extends CommonPluginOptions { + /** + * 是否开启压缩归档 + * @default false + * @description 开启后会在打包目录生成 zip 文件 + */ + archiver?: boolean; + /** + * 压缩归档插件配置 + * @description 配置压缩归档的行为 + */ + archiverPluginOptions?: ArchiverPluginOptions; + /** + * 是否开启压缩 + * @default false + * @description 支持 gzip 和 brotli 压缩 + */ + compress?: boolean; + /** + * 压缩类型 + * @default ['gzip'] + * @description 可选的压缩类型 + */ + compressTypes?: ('brotli' | 'gzip')[]; + /** + * 是否抽离配置文件 + * @default false + * @description 在构建时抽离配置文件 + */ + extraAppConfig?: boolean; + /** + * 是否开启 HTML 插件 + * @default true + */ + html?: boolean; + /** + * 是否开启国际化 + * @default false + */ + i18n?: boolean; + /** + * 是否开启 ImportMap CDN + * @default false + */ + importmap?: boolean; + /** + * ImportMap 插件配置 + */ + importmapOptions?: ImportmapPluginOptions; + /** + * 是否注入应用加载动画 + * @default true + */ + injectAppLoading?: boolean; + /** + * 是否注入全局 SCSS + * @default true + */ + injectGlobalScss?: boolean; + /** + * 是否注入版权信息 + * @default true + */ + license?: boolean; + /** + * 是否开启 Nitro Mock + * @default false + */ + nitroMock?: boolean; + /** + * Nitro Mock 插件配置 + */ + nitroMockOptions?: NitroMockPluginOptions; + /** + * 是否开启控制台打印 + * @default false + */ + print?: boolean; + /** + * 打印插件配置 + */ + printInfoMap?: PrintPluginOptions['infoMap']; + /** + * 是否开启 PWA + * @default false + */ + pwa?: boolean; + /** + * PWA 插件配置 + */ + pwaOptions?: Partial; + /** + * 是否开启 VXE Table 懒加载 + * @default false + */ + vxeTableLazyImport?: boolean; +} + +/** + * 库插件配置选项 + * @description 用于配置库构建时的插件选项 + */ +interface LibraryPluginOptions extends CommonPluginOptions { + /** + * 是否开启 DTS 输出 + * @default true + * @description 生成 TypeScript 类型声明文件 + */ + dts?: boolean | PluginOptions; +} + +/** + * 应用配置选项类型 + */ +type ApplicationOptions = ApplicationPluginOptions; + +/** + * 库配置选项类型 + */ +type LibraryOptions = LibraryPluginOptions; + +/** + * 应用配置定义函数类型 + * @description 用于定义应用构建配置 + */ +type DefineApplicationOptions = (config?: ConfigEnv) => Promise<{ + /** 应用插件配置 */ + application?: ApplicationOptions; + /** Vite 配置 */ + vite?: UserConfig; +}>; + +/** + * 库配置定义函数类型 + * @description 用于定义库构建配置 + */ +type DefineLibraryOptions = (config?: ConfigEnv) => Promise<{ + /** 库插件配置 */ + library?: LibraryOptions; + /** Vite 配置 */ + vite?: UserConfig; +}>; + +/** + * 配置定义类型 + * @description 应用或库的配置定义 + */ +type DefineConfig = DefineApplicationOptions | DefineLibraryOptions; + +export type { + ApplicationPluginOptions, + ArchiverPluginOptions, + CommonPluginOptions, + ConditionPlugin, + DefineApplicationOptions, + DefineConfig, + DefineLibraryOptions, + IImportMap, + ImportmapPluginOptions, + LibraryPluginOptions, + NitroMockPluginOptions, + PrintPluginOptions, +}; diff --git a/internal/vite-config/src/utils/env.ts b/internal/vite-config/src/utils/env.ts new file mode 100644 index 0000000..f342216 --- /dev/null +++ b/internal/vite-config/src/utils/env.ts @@ -0,0 +1,110 @@ +import type { ApplicationPluginOptions } from '../typing'; + +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; + +import { fs } from '@vben/node-utils'; + +import dotenv from 'dotenv'; + +const getBoolean = (value: string | undefined) => value === 'true'; + +const getString = (value: string | undefined, fallback: string) => + value ?? fallback; + +const getNumber = (value: string | undefined, fallback: number) => + Number(value) || fallback; + +/** + * 获取当前环境下生效的配置文件名 + */ +function getConfFiles() { + const script = process.env.npm_lifecycle_script as string; + const reg = /--mode ([\d_a-z]+)/; + const result = reg.exec(script); + let mode = 'production'; + if (result) { + mode = result[1] as string; + } + return ['.env', '.env.local', `.env.${mode}`, `.env.${mode}.local`]; +} + +/** + * Get the environment variables starting with the specified prefix + * @param match prefix + * @param confFiles ext + */ +async function loadEnv>( + match = 'VITE_GLOB_', + confFiles = getConfFiles(), +) { + let envConfig = {}; + + for (const confFile of confFiles) { + try { + const confFilePath = join(process.cwd(), confFile); + if (existsSync(confFilePath)) { + const envPath = await fs.readFile(confFilePath, { + encoding: 'utf8', + }); + const env = dotenv.parse(envPath); + envConfig = { ...envConfig, ...env }; + } + } catch (error) { + console.error(`Error while parsing ${confFile}`, error); + } + } + const reg = new RegExp(`^(${match})`); + Object.keys(envConfig).forEach((key) => { + if (!reg.test(key)) { + Reflect.deleteProperty(envConfig, key); + } + }); + return envConfig as T; +} + +async function loadAndConvertEnv( + match = 'VITE_', + confFiles = getConfFiles(), +): Promise< + Partial & { + appTitle: string; + base: string; + port: number; + } +> { + const envConfig = await loadEnv(match, confFiles); + + const { + VITE_APP_TITLE, + VITE_ARCHIVER, + VITE_BASE, + VITE_COMPRESS, + VITE_DEVTOOLS, + VITE_INJECT_APP_LOADING, + VITE_NITRO_MOCK, + VITE_PORT, + VITE_PWA, + VITE_VISUALIZER, + } = envConfig; + + const compressTypes = (VITE_COMPRESS ?? '') + .split(',') + .filter((item) => item === 'brotli' || item === 'gzip'); + + return { + appTitle: getString(VITE_APP_TITLE, 'Vben Admin'), + archiver: getBoolean(VITE_ARCHIVER), + base: getString(VITE_BASE, '/'), + compress: compressTypes.length > 0, + compressTypes, + devtools: getBoolean(VITE_DEVTOOLS), + injectAppLoading: getBoolean(VITE_INJECT_APP_LOADING), + nitroMock: getBoolean(VITE_NITRO_MOCK), + port: getNumber(VITE_PORT, 5173), + pwa: getBoolean(VITE_PWA), + visualizer: getBoolean(VITE_VISUALIZER), + }; +} + +export { loadAndConvertEnv, loadEnv }; diff --git a/internal/vite-config/tsconfig.json b/internal/vite-config/tsconfig.json new file mode 100644 index 0000000..b2ec3b6 --- /dev/null +++ b/internal/vite-config/tsconfig.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@vben/tsconfig/node.json", + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cb63ee0 --- /dev/null +++ b/package.json @@ -0,0 +1,122 @@ +{ + "name": "vben-admin-monorepo", + "version": "5.5.9", + "private": true, + "keywords": [ + "monorepo", + "turbo", + "vben", + "vben admin", + "vben pro", + "vue", + "vue admin", + "vue vben admin", + "vue vben admin pro", + "vue3" + ], + "homepage": "https://github.com/vbenjs/vue-vben-admin", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": "vbenjs/vue-vben-admin.git", + "license": "MIT", + "author": { + "name": "vben", + "email": "ann.vben@gmail.com", + "url": "https://github.com/anncwb" + }, + "type": "module", + "scripts": { + "build": "cross-env NODE_OPTIONS=--max-old-space-size=8192 turbo build", + "build:analyze": "turbo build:analyze", + "build:antd": "pnpm run build --filter=@vben/web-antd", + "build:docker": "./scripts/deploy/build-local-docker-image.sh", + "build:docs": "pnpm run build --filter=@vben/docs", + "build:ele": "pnpm run build --filter=@vben/web-ele", + "build:naive": "pnpm run build --filter=@vben/web-naive", + "build:tdesign": "pnpm run build --filter=@vben/web-tdesign", + "changeset": "pnpm exec changeset", + "check": "pnpm run check:circular && pnpm run check:dep && pnpm run check:type && pnpm check:cspell", + "check:circular": "vsh check-circular", + "check:cspell": "cspell lint **/*.ts **/README.md .changeset/*.md --no-progress", + "check:dep": "vsh check-dep", + "check:type": "turbo run typecheck", + "clean": "node ./scripts/clean.mjs", + "commit": "czg", + "dev": "turbo-run dev", + "dev:antd": "pnpm -F @vben/web-antd run dev", + "dev:docs": "pnpm -F @vben/docs run dev", + "dev:ele": "pnpm -F @vben/web-ele run dev", + "dev:naive": "pnpm -F @vben/web-naive run dev", + "dev:tdesign": "pnpm -F @vben/web-tdesign run dev", + "format": "vsh lint --format", + "lint": "vsh lint", + "postinstall": "pnpm -r run stub --if-present", + "preinstall": "npx only-allow pnpm", + "preview": "turbo-run preview", + "publint": "vsh publint", + "reinstall": "pnpm clean --del-lock && pnpm install", + "test:unit": "vitest run --dom", + "test:e2e": "turbo run test:e2e", + "update:deps": "npx taze -r -w", + "version": "pnpm exec changeset version && pnpm install --no-frozen-lockfile", + "catalog": "pnpx codemod pnpm/catalog" + }, + "devDependencies": { + "@changesets/changelog-github": "catalog:", + "@changesets/cli": "catalog:", + "@playwright/test": "catalog:", + "@types/node": "catalog:", + "@vben/commitlint-config": "workspace:*", + "@vben/eslint-config": "workspace:*", + "@vben/prettier-config": "workspace:*", + "@vben/stylelint-config": "workspace:*", + "@vben/tailwind-config": "workspace:*", + "@vben/tsconfig": "workspace:*", + "@vben/turbo-run": "workspace:*", + "@vben/vite-config": "workspace:*", + "@vben/vsh": "workspace:*", + "@vitejs/plugin-vue": "catalog:", + "@vitejs/plugin-vue-jsx": "catalog:", + "@vue/test-utils": "catalog:", + "autoprefixer": "catalog:", + "cross-env": "catalog:", + "cspell": "catalog:", + "happy-dom": "catalog:", + "is-ci": "catalog:", + "lefthook": "catalog:", + "playwright": "catalog:", + "rimraf": "catalog:", + "tailwindcss": "catalog:", + "turbo": "catalog:", + "typescript": "catalog:", + "unbuild": "catalog:", + "vite": "catalog:", + "vitest": "catalog:", + "vue": "catalog:", + "vue-tsc": "catalog:" + }, + "engines": { + "node": ">=20.12.0", + "pnpm": ">=10.14.0" + }, + "packageManager": "pnpm@10.22.0", + "pnpm": { + "peerDependencyRules": { + "allowedVersions": { + "eslint": "*" + } + }, + "overrides": { + "@ast-grep/napi": "catalog:", + "@ctrl/tinycolor": "catalog:", + "clsx": "catalog:", + "esbuild": "0.25.3", + "jiti": "catalog:", + "pinia": "catalog:", + "vue": "catalog:" + }, + "neverBuiltDependencies": [ + "canvas", + "node-gyp" + ] + } +} diff --git a/packages/@core/README.md b/packages/@core/README.md new file mode 100644 index 0000000..8eb201d --- /dev/null +++ b/packages/@core/README.md @@ -0,0 +1,3 @@ +# @vben-core + +系统一些比较基础的SDK和UI组件库,该目录后续完善后,可能会迁移出去或者发布到npm,请勿将任何业务逻辑和业务包放在该目录。 diff --git a/packages/@core/base/README.md b/packages/@core/base/README.md new file mode 100644 index 0000000..cc745b4 --- /dev/null +++ b/packages/@core/base/README.md @@ -0,0 +1,5 @@ +# base + +基础共享包,请勿引入 workspace 依赖 + +- diff --git a/packages/@core/base/design/package.json b/packages/@core/base/design/package.json new file mode 100644 index 0000000..ed40f4b --- /dev/null +++ b/packages/@core/base/design/package.json @@ -0,0 +1,41 @@ +{ + "name": "@vben-core/design", + "version": "5.5.9", + "homepage": "https://github.com/vbenjs/vue-vben-admin", + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", + "directory": "packages/@vben-core/base/design" + }, + "license": "MIT", + "type": "module", + "scripts": { + "build": "pnpm vite build", + "prepublishOnly": "npm run build" + }, + "files": [ + "dist", + "src" + ], + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "exports": { + "./bem": { + "development": "./src/scss-bem/bem.scss", + "default": "./dist/bem.scss" + }, + ".": { + "types": "./src/index.ts", + "development": "./src/index.ts", + "default": "./dist/design.css" + } + }, + "publishConfig": { + "exports": { + ".": { + "default": "./dist/index.mjs" + } + } + } +} diff --git a/packages/@core/base/design/src/css/global.css b/packages/@core/base/design/src/css/global.css new file mode 100644 index 0000000..d199909 --- /dev/null +++ b/packages/@core/base/design/src/css/global.css @@ -0,0 +1,160 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + *, + ::after, + ::before { + @apply border-border; + + box-sizing: border-box; + border-style: solid; + border-width: 0; + } + + html { + @apply text-foreground bg-background font-sans text-[100%]; + + font-variation-settings: normal; + line-height: 1.15; + text-size-adjust: 100%; + font-synthesis-weight: none; + scroll-behavior: smooth; + text-rendering: optimizelegibility; + -webkit-tap-highlight-color: transparent; + + /* -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; */ + } + + #app, + body, + html { + @apply size-full; + + /* scrollbar-gutter: stable; */ + } + + body { + min-height: 100vh; + + /* pointer-events: auto !important; */ + + /* overflow: overlay; */ + + /* -webkit-font-smoothing: antialiased; */ + + /* -moz-osx-font-smoothing: grayscale; */ + } + + a, + a:active, + a:hover, + a:link, + a:visited { + @apply no-underline; + } + + ::view-transition-new(root), + ::view-transition-old(root) { + @apply animate-none mix-blend-normal; + } + + ::view-transition-old(root) { + @apply z-[1]; + } + + ::view-transition-new(root) { + @apply z-[2147483646]; + } + + html.dark::view-transition-old(root) { + @apply z-[2147483646]; + } + + html.dark::view-transition-new(root) { + @apply z-[1]; + } + + input::placeholder, + textarea::placeholder { + @apply opacity-100; + } + + /* input:-webkit-autofill { + @apply border-none; + + box-shadow: 0 0 0 1000px transparent inset; + } */ + + input[type='number']::-webkit-inner-spin-button, + input[type='number']::-webkit-outer-spin-button { + @apply m-0 appearance-none; + } + + /* 只有非mac下才进行调整,mac下使用默认滚动条 */ + html:not([data-platform='macOs']) { + ::-webkit-scrollbar { + @apply h-[10px] w-[10px]; + } + + ::-webkit-scrollbar-thumb { + @apply bg-border rounded-sm border-none; + } + + ::-webkit-scrollbar-track { + @apply rounded-sm border-none bg-transparent shadow-none; + } + + ::-webkit-scrollbar-button { + @apply hidden; + } + } +} + +@layer components { + .flex-center { + @apply flex items-center justify-center; + } + + .flex-col-center { + @apply flex flex-col items-center justify-center; + } + + .outline-box { + @apply outline-border relative cursor-pointer rounded-md p-1 outline outline-1; + } + + .outline-box::after { + @apply absolute left-1/2 top-1/2 z-20 h-0 w-[1px] rounded-sm opacity-0 outline outline-2 outline-transparent transition-all duration-300 content-[""]; + } + + .outline-box.outline-box-active { + @apply outline-primary outline outline-2; + } + + .outline-box.outline-box-active::after { + display: none; + } + + .outline-box:not(.outline-box-active):hover::after { + @apply outline-primary left-0 top-0 h-full w-full p-1 opacity-100; + } + + .vben-link { + @apply text-primary hover:text-primary-hover active:text-primary-active cursor-pointer; + } + + .card-box { + @apply bg-card text-card-foreground border-border rounded-xl border; + } +} + +html.invert-mode { + @apply invert; +} + +html.grayscale-mode { + @apply grayscale; +} diff --git a/packages/@core/base/design/src/css/nprogress.css b/packages/@core/base/design/src/css/nprogress.css new file mode 100644 index 0000000..3503dab --- /dev/null +++ b/packages/@core/base/design/src/css/nprogress.css @@ -0,0 +1,59 @@ +/* Make clicks pass-through */ +#nprogress { + @apply pointer-events-none; +} + +#nprogress .bar { + @apply bg-primary fixed left-0 top-0 z-[1031] h-[2px] w-full; +} + +/* Fancy blur effect */ +#nprogress .peg { + @apply absolute right-0 block h-full w-[100px]; + + box-shadow: + 0 0 10px hsl(var(--primary)), + 0 0 5px hsl(var(--primary)); + opacity: 1; + transform: rotate(3deg) translate(0, -4px); +} + +/* Remove these to get rid of the spinner */ +#nprogress .spinner { + @apply fixed right-4 top-4 z-[1031] block; +} + +#nprogress .spinner-icon { + @apply border-t-primary border-l-primary size-4 rounded-full border-[2px] border-solid border-transparent; + + animation: nprogress-spinner 400ms linear infinite; +} + +.nprogress-custom-parent { + @apply relative overflow-hidden; +} + +.nprogress-custom-parent #nprogress .spinner, +.nprogress-custom-parent #nprogress .bar { + @apply absolute; +} + +@keyframes nprogress-spinner { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + +@keyframes nprogress-spinner { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} diff --git a/packages/@core/base/design/src/css/transition.css b/packages/@core/base/design/src/css/transition.css new file mode 100644 index 0000000..c1cb0e4 --- /dev/null +++ b/packages/@core/base/design/src/css/transition.css @@ -0,0 +1,236 @@ +.slide-up-enter-active, +.slide-up-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-up-move { + transition: transform 0.3s; +} + +.slide-up-enter-from, +.slide-up-leave-to { + opacity: 0; + transform: translateY(-15px); +} + +.slide-down-enter-active, +.slide-down-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-down-move { + transition: transform 0.3s; +} + +.slide-down-enter-from, +.slide-down-leave-to { + opacity: 0; + transform: translateY(15px); +} + +.slide-left-enter-active, +.slide-left-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-left-move { + transition: transform 0.3s; +} + +.slide-left-enter-from, +.slide-left-leave-to { + opacity: 0; + transform: translate(-15px); +} + +.slide-right-enter-active, +.slide-right-leave-active { + transition: 0.25s cubic-bezier(0.25, 0.8, 0.5, 1); +} + +.slide-right-move { + transition: transform 0.3s; +} + +.slide-right-enter-from, +.slide-right-leave-to { + opacity: 0; + transform: translate(15px); +} + +.fade-transition-enter-active, +.fade-transition-leave-active { + transition: opacity 0.2s ease-in-out; +} + +.fade-transition-enter-from, +.fade-transition-leave-to { + opacity: 0; +} + +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.2s ease-in-out; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +.fade-slide-leave-active, +.fade-slide-enter-active { + transition: all 0.3s; +} + +.fade-slide-enter-from { + opacity: 0; + transform: translate(-30px); +} + +.fade-slide-leave-to { + opacity: 0; + transform: translate(30px); +} + +.fade-down-enter-active, +.fade-down-leave-active { + transition: + opacity 0.25s, + transform 0.3s; +} + +.fade-down-enter-from { + opacity: 0; + transform: translateY(-10%); +} + +.fade-down-leave-to { + opacity: 0; + transform: translateY(10%); +} + +.fade-scale-leave-active, +.fade-scale-enter-active { + transition: all 0.28s; +} + +.fade-scale-enter-from { + opacity: 0; + transform: scale(1.2); +} + +.fade-scale-leave-to { + opacity: 0; + transform: scale(0.8); +} + +.fade-up-enter-active, +.fade-up-leave-active { + transition: + opacity 0.2s, + transform 0.25s; +} + +.fade-up-enter-from { + opacity: 0; + transform: translateY(10%); +} + +.fade-up-leave-to { + opacity: 0; + transform: translateY(-10%); +} + +@keyframes fade-slide { + 0% { + opacity: 0; + transform: translate(-30px); + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + transform: translate(30px); + } +} + +@keyframes fade { + 0% { + opacity: 0; + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + +@keyframes fade-up { + 0% { + opacity: 0; + transform: translateY(10%); + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + transform: translateY(-10%); + } +} + +@keyframes fade-down { + 0% { + opacity: 0; + transform: translateY(-10%); + } + + 50% { + opacity: 1; + } + + 100% { + opacity: 0; + transform: translateY(10%); + } +} + +.fade-slow { + animation: fade 3s infinite; +} + +.fade-slide-slow { + animation: fade-slide 3s infinite; +} + +.fade-up-slow { + animation: fade-up 3s infinite; +} + +.fade-down-slow { + animation: fade-down 3s infinite; +} + +.collapse-transition { + transition: + 0.2s height ease-in-out, + 0.2s padding-top ease-in-out, + 0.2s padding-bottom ease-in-out; +} + +.collapse-transition-leave-active, +.collapse-transition-enter-active { + transition: + 0.2s max-height ease-in-out, + 0.2s padding-top ease-in-out, + 0.2s margin-top ease-in-out; +} diff --git a/packages/@core/base/design/src/css/ui.css b/packages/@core/base/design/src/css/ui.css new file mode 100644 index 0000000..a1bf024 --- /dev/null +++ b/packages/@core/base/design/src/css/ui.css @@ -0,0 +1,101 @@ +.side-content { + animation-duration: 0.3s; + animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); +} + +.side-content[data-side='top'] { + animation-name: slide-up; +} + +.side-content[data-side='bottom'] { + animation-name: slide-down; +} + +.side-content[data-side='left'] { + animation-name: slide-left; +} + +.side-content[data-side='right'] { + animation-name: slide-right; +} + +.breadcrumb-transition-enter-active { + transition: + transform 0.4s cubic-bezier(0.76, 0, 0.24, 1), + opacity 0.4s cubic-bezier(0.76, 0, 0.24, 1); +} + +.breadcrumb-transition-leave-active { + display: none; +} + +.breadcrumb-transition-enter-from { + opacity: 0; + transform: translateX(30px) skewX(-30deg); +} + +@keyframes slide-down { + from { + opacity: 0; + transform: translateY(50px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slide-left { + from { + opacity: 0; + transform: translateX(-50px); + } + + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slide-right { + from { + opacity: 0; + transform: translateX(50px); + } + + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slide-up { + from { + opacity: 0; + transform: translateY(-50px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.z-popup { + z-index: var(--popup-z-index); +} + +@keyframes shrink { + 0% { + transform: scale(1); + } + + 50% { + transform: scale(0.9); + } + + 100% { + transform: scale(1); + } +} diff --git a/packages/@core/base/design/src/design-tokens/dark.css b/packages/@core/base/design/src/design-tokens/dark.css new file mode 100644 index 0000000..3881041 --- /dev/null +++ b/packages/@core/base/design/src/design-tokens/dark.css @@ -0,0 +1,446 @@ +.dark, +.dark[data-theme='custom'], +.dark[data-theme='default'] { + /* Default background color of ...etc */ + --background: 222.34deg 10.43% 12.27%; + + /* 主体区域背景色 */ + --background-deep: 220deg 13.06% 9%; + --foreground: 0 0% 95%; + + /* Background color for */ + --card: 222.34deg 10.43% 12.27%; + + /* --card: 222.2 84% 4.9%; */ + --card-foreground: 210 40% 98%; + + /* Background color for popovers such as , , */ + + /* --popover: 222.82deg 8.43% 12.27%; */ + + /* 弹出层的背景色与主题区域背景色太过接近 */ + --popover: 0 0% 14.2%; + --popover-foreground: 210 40% 98%; + + /* Muted backgrounds such as , and */ + + /* --muted: 220deg 6.82% 17.25%; */ + + /* --muted-foreground: 215 20.2% 65.1%; */ + + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + + /* 主题颜色 */ + + /* --primary: 245 82% 67%; */ + --primary-foreground: 0 0% 98%; + + /* Used for destructive actions such as