├── .DS_Store ├── .env ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .lintstagedrc ├── .npmrc ├── .prettierignore ├── .prettierrc ├── .stylelintignore ├── .stylelintrc.js ├── .vscode └── settings.json ├── README.md ├── config ├── config.ts ├── proxy.ts └── routes.ts ├── mock └── userAPI.ts ├── package.json ├── pnpm-lock.yaml ├── public ├── default.png ├── favicon.ico ├── scripts │ └── loading.js └── tinymce │ ├── icons │ └── default │ │ └── icons.min.js │ ├── langs │ ├── readme.md │ └── zh_CN.js │ ├── license.txt │ ├── plugins │ ├── advlist │ │ └── plugin.min.js │ ├── anchor │ │ └── plugin.min.js │ ├── autolink │ │ └── plugin.min.js │ ├── autoresize │ │ └── plugin.min.js │ ├── autosave │ │ └── plugin.min.js │ ├── bbcode │ │ └── plugin.min.js │ ├── charmap │ │ └── plugin.min.js │ ├── code │ │ └── plugin.min.js │ ├── codesample │ │ └── plugin.min.js │ ├── colorpicker │ │ └── plugin.min.js │ ├── contextmenu │ │ └── plugin.min.js │ ├── directionality │ │ └── plugin.min.js │ ├── emoticons │ │ ├── js │ │ │ ├── emojiimages.js │ │ │ ├── emojiimages.min.js │ │ │ ├── emojis.js │ │ │ └── emojis.min.js │ │ └── plugin.min.js │ ├── formatpainter │ │ └── plugin.min.js │ ├── fullpage │ │ └── plugin.min.js │ ├── fullscreen │ │ └── plugin.min.js │ ├── help │ │ └── plugin.min.js │ ├── hr │ │ └── plugin.min.js │ ├── image │ │ └── plugin.min.js │ ├── imagetools │ │ └── plugin.min.js │ ├── importcss │ │ └── plugin.min.js │ ├── indent2em │ │ ├── plugin.js │ │ └── plugin.min.js │ ├── insertdatetime │ │ └── plugin.min.js │ ├── legacyoutput │ │ └── plugin.min.js │ ├── link │ │ └── plugin.min.js │ ├── lists │ │ └── plugin.min.js │ ├── media │ │ └── plugin.min.js │ ├── multipleimage │ │ └── plugin.min.js │ ├── nonbreaking │ │ └── plugin.min.js │ ├── noneditable │ │ └── plugin.min.js │ ├── pagebreak │ │ └── plugin.min.js │ ├── paste │ │ └── plugin.min.js │ ├── preview │ │ └── plugin.min.js │ ├── print │ │ └── plugin.min.js │ ├── quickbars │ │ └── plugin.min.js │ ├── save │ │ └── plugin.min.js │ ├── searchreplace │ │ └── plugin.min.js │ ├── spellchecker │ │ └── plugin.min.js │ ├── tabfocus │ │ └── plugin.min.js │ ├── table │ │ └── plugin.min.js │ ├── template │ │ └── plugin.min.js │ ├── textcolor │ │ └── plugin.min.js │ ├── textpattern │ │ └── plugin.min.js │ ├── toc │ │ └── plugin.min.js │ ├── visualblocks │ │ └── plugin.min.js │ ├── visualchars │ │ └── plugin.min.js │ └── wordcount │ │ └── plugin.min.js │ ├── skins │ ├── content │ │ ├── dark │ │ │ └── content.min.css │ │ ├── default │ │ │ └── content.min.css │ │ ├── document │ │ │ └── content.min.css │ │ └── writer │ │ │ └── content.min.css │ └── ui │ │ ├── oxide-dark │ │ ├── content.inline.min.css │ │ ├── content.min.css │ │ ├── content.mobile.min.css │ │ ├── fonts │ │ │ └── tinymce-mobile.woff │ │ ├── skin.min.css │ │ ├── skin.mobile.min.css │ │ └── skin.shadowdom.min.css │ │ └── oxide │ │ ├── content.inline.min.css │ │ ├── content.min.css │ │ ├── content.mobile.min.css │ │ ├── fonts │ │ └── tinymce-mobile.woff │ │ ├── skin.min.css │ │ ├── skin.mobile.min.css │ │ └── skin.shadowdom.min.css │ ├── themes │ ├── mobile │ │ └── theme.min.js │ └── silver │ │ └── theme.min.js │ ├── tinymce.d.ts │ └── tinymce.min.js ├── src ├── app.ts ├── assets │ ├── .gitkeep │ └── logo.png ├── components │ ├── AMap │ │ └── AutoComplete.tsx │ ├── Action │ │ ├── Item │ │ │ ├── Ajax.tsx │ │ │ ├── Back.tsx │ │ │ ├── Cancel.tsx │ │ │ ├── Js.tsx │ │ │ ├── Link.tsx │ │ │ ├── Reset.tsx │ │ │ ├── Step.tsx │ │ │ ├── Submit.tsx │ │ │ └── Switch.tsx │ │ └── index.tsx │ ├── Chart │ │ ├── Line.tsx │ │ └── index.tsx │ ├── Col │ │ └── index.tsx │ ├── Container │ │ └── index.tsx │ ├── CropBox │ │ └── index.tsx │ ├── Descriptions │ │ └── index.tsx │ ├── Divider │ │ └── index.tsx │ ├── Drawer │ │ └── index.tsx │ ├── Dropdown │ │ └── index.tsx │ ├── Engine │ │ └── index.tsx │ ├── Form │ │ ├── Field.tsx │ │ ├── Field │ │ │ ├── Cascader.tsx │ │ │ ├── Editor.tsx │ │ │ ├── FileUploader.tsx │ │ │ ├── Geofence.tsx │ │ │ ├── Icon.tsx │ │ │ ├── ImageCaptcha.tsx │ │ │ ├── ImagePicker.tsx │ │ │ ├── ImageUploader.tsx │ │ │ ├── Map.tsx │ │ │ ├── Search.tsx │ │ │ ├── Selects.tsx │ │ │ ├── Sku.tsx │ │ │ └── Transfer.tsx │ │ ├── Form.tsx │ │ ├── ProField │ │ │ ├── ProFormCascader.tsx │ │ │ ├── ProFormEditor.tsx │ │ │ ├── ProFormFileUploader.tsx │ │ │ ├── ProFormGeofence.tsx │ │ │ ├── ProFormIcon.tsx │ │ │ ├── ProFormImageCaptcha.tsx │ │ │ ├── ProFormImagePicker.tsx │ │ │ ├── ProFormImageUploader.tsx │ │ │ ├── ProFormMap.tsx │ │ │ ├── ProFormSearch.tsx │ │ │ ├── ProFormSku.tsx │ │ │ ├── ProFormSmsCaptcha.tsx │ │ │ └── ProFormTransfer.tsx │ │ └── index.tsx │ ├── Icon │ │ └── index.tsx │ ├── ImageBox │ │ └── index.tsx │ ├── Layout │ │ ├── index.tsx │ │ └── menu.ts │ ├── List │ │ └── index.tsx │ ├── Login │ │ └── index.tsx │ ├── Menu │ │ └── index.tsx │ ├── Message │ │ └── index.tsx │ ├── Modal │ │ └── index.tsx │ ├── Order │ │ └── index.tsx │ ├── Page │ │ └── index.tsx │ ├── PageContainer │ │ └── index.tsx │ ├── Paragraph │ │ └── index.tsx │ ├── Render │ │ └── index.tsx │ ├── RightContent │ │ └── index.tsx │ ├── Row │ │ └── index.tsx │ ├── Statistic │ │ └── index.tsx │ ├── StatisticCard │ │ └── index.tsx │ ├── Table │ │ ├── Editable.less │ │ ├── Editable.tsx │ │ ├── index.less │ │ └── index.tsx │ ├── Tabs │ │ └── index.tsx │ ├── Text │ │ └── index.tsx │ ├── Title │ │ └── index.tsx │ ├── Typography │ │ └── index.tsx │ ├── View │ │ └── index.tsx │ ├── When │ │ └── index.tsx │ └── index.tsx ├── global.less ├── global.tsx ├── layouts │ └── index.tsx ├── models │ ├── buttonLoading.ts │ ├── component.ts │ ├── formFields.ts │ ├── object.ts │ ├── pageLoading.ts │ ├── submit.ts │ └── tabs.ts ├── pages │ ├── Index │ │ ├── index.less │ │ └── index.tsx │ └── Test │ │ ├── index.less │ │ └── index.tsx ├── requestConfig.ts ├── services │ └── action.ts └── utils │ ├── component.ts │ ├── reload.ts │ ├── template.ts │ ├── trim.ts │ └── url.ts ├── tsconfig.json ├── typings.d.ts └── unocss.config.ts /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarkcloudio/quark-ui/8dc37e388c16feadfb71d9727fcd219f4fc87194/.DS_Store -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # 默认登录接口地址 2 | UMI_APP_DEFAULT_URL='/api/admin/login/index/index' 3 | 4 | # 默认布局接口地址 5 | UMI_APP_LAYOUT_URL='/api/admin/layout/index/index' -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /lambda/ 2 | /scripts 3 | /config 4 | .history 5 | public 6 | dist 7 | .umi 8 | mock -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@umijs/max/eslint'), 3 | rules: { 4 | 'no-underscore-dangle': 0, 5 | 'import/prefer-default-export': 0, 6 | 'import/no-extraneous-dependencies': 0, 7 | 'import/extensions': 0, 8 | 'import/no-unresolved': 0, 9 | 'no-unused-vars': 0, 10 | 'no-use-before-define': 0, 11 | 'no-undef': 0, 12 | 'no-shadow': 0, 13 | 'no-console': 0, 14 | 'no-plusplus': 0, 15 | 'no-script-url': 0, 16 | 'no-restricted-syntax': 0, 17 | 'no-unused-expressions': 0, 18 | 'no-promise-executor-return': 0, 19 | 'import/no-mutable-exports': 0, 20 | 'jsx-a11y/click-events-have-key-events': 0, 21 | 'jsx-a11y/no-noninteractive-element-interactions': 0, 22 | '@typescript-eslint/no-empty-function': 0, 23 | '@typescript-eslint/no-unused-vars': 0, 24 | '@typescript-eslint/no-var-requires': 0, 25 | '@typescript-eslint/no-use-before-define': 0, 26 | 'react/prop-types': 0, 27 | 'react/no-array-index-key': 0, 28 | 'react/static-property-placement': 0, 29 | 'react/destructuring-assignment': 0, 30 | 'react/jsx-filename-extension': 0, 31 | 'react/jsx-props-no-spreading': 0, 32 | 'jsx-a11y/no-static-element-interactions': 0, 33 | 'no-param-reassign': 0, 34 | 'prefer-destructuring': 0, 35 | 'jsx-a11y/no-autofocus': 0, 36 | 'react/no-children-prop': 0, 37 | 'jsx-a11y/aria-proptypes': 0, 38 | 'react/require-default-props': 0, 39 | '@typescript-eslint/ban-types': [ 40 | 'error', 41 | { 42 | extendDefaults: true, 43 | types: { 44 | '{}': false, 45 | }, 46 | }, 47 | ], 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.env.local 3 | /.umirc.local.ts 4 | /config/config.local.ts 5 | /src/.umi 6 | /src/.umi-production 7 | /src/.umi-test 8 | /.umi 9 | /.umi-production 10 | /.umi-test 11 | /dist 12 | /.mfsu 13 | .swc 14 | /.idea 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install max verify-commit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged --quiet 5 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{md,json}": [ 3 | "prettier --cache --write" 4 | ], 5 | "*.{js,jsx}": [ 6 | "max lint --fix --eslint-only", 7 | "prettier --cache --write" 8 | ], 9 | "*.{css,less}": [ 10 | "max lint --fix --stylelint-only", 11 | "prettier --cache --write" 12 | ], 13 | "*.ts?(x)": [ 14 | "max lint --fix --eslint-only", 15 | "prettier --cache --parser=typescript --write" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .umi 3 | .umi-production 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "proseWrap": "never", 6 | "overrides": [{ "files": ".prettierrc", "options": { "parser": "json" } }], 7 | "plugins": ["prettier-plugin-organize-imports", "prettier-plugin-packagejson"] 8 | } 9 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | public/tinymce/* -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@umijs/max/stylelint'), 3 | }; 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.css": "postcss" 4 | }, 5 | 6 | // Enable the ESlint flat config support 7 | "eslint.experimental.useFlatConfig": true, 8 | 9 | // Disable the default formatter 10 | "prettier.enable": false, 11 | "editor.formatOnSave": false, 12 | 13 | // Auto fix 14 | "editor.codeActionsOnSave": { 15 | "source.fixAll.eslint": "explicit", 16 | "source.organizeImports": "never" 17 | }, 18 | 19 | // Silent the stylistic rules in you IDE, but still auto fix them 20 | "eslint.rules.customizations": [ 21 | { "rule": "style/*", "severity": "off" }, 22 | { "rule": "*-indent", "severity": "off" }, 23 | { "rule": "*-spacing", "severity": "off" }, 24 | { "rule": "*-spaces", "severity": "off" }, 25 | { "rule": "*-order", "severity": "off" }, 26 | { "rule": "*-dangle", "severity": "off" }, 27 | { "rule": "*-newline", "severity": "off" }, 28 | { "rule": "*quotes", "severity": "off" }, 29 | { "rule": "*semi", "severity": "off" } 30 | ], 31 | 32 | // The following is optional. 33 | // It's better to put under project setting `.vscode/settings.json` 34 | // to avoid conflicts with working with different eslint configs 35 | // that does not support all formats. 36 | "eslint.validate": [ 37 | "javascript", 38 | "javascriptreact", 39 | "typescript", 40 | "typescriptreact", 41 | "vue", 42 | "html", 43 | "markdown", 44 | "json", 45 | "jsonc", 46 | "yaml" 47 | ], 48 | 49 | /** 50 | * 解决 unocss @apply 警告问题 51 | * @see https://github.com/tailwindlabs/tailwindcss/discussions/5258#discussioncomment-1979394 52 | */ 53 | "scss.lint.unknownAtRules": "ignore", 54 | 55 | "editor.tabSize": 2, 56 | "editor.insertSpaces": true 57 | } 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quark-UI 2 | 3 | 基于 ant-design 的低代码 UI 引擎 4 | 5 | ## 启动项目 6 | 7 | ```shell 8 | 9 | pnpm dev 10 | 11 | ``` 12 | 13 | ## 打包部署 14 | 15 | ```shell 16 | 17 | pnpm build 18 | 19 | ``` 20 | 21 | 脚手架参考 [Umi Max](https://umijs.org/docs/max/introduce) 22 | -------------------------------------------------------------------------------- /config/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@umijs/max'; 2 | import proxy from './proxy'; 3 | import routes from './routes'; 4 | 5 | export default defineConfig({ 6 | antd: {}, 7 | access: {}, 8 | model: {}, 9 | initialState: {}, 10 | request: {}, 11 | headScripts: [ 12 | { src: './scripts/loading.js', async: true }, // 解决首次加载时白屏的问题 13 | { src: './tinymce/tinymce.min.js', async: true }, 14 | ], 15 | publicPath: process.env.NODE_ENV === 'production' ? './' : '/', 16 | history: { type: 'hash' }, 17 | title: 'Engine Loading', 18 | favicons: ['/favicon.ico'], 19 | layout: { 20 | title: false, 21 | }, 22 | routes: routes, 23 | proxy: proxy, 24 | plugins: [require.resolve('@umijs/plugins/dist/unocss')], 25 | unocss: { 26 | watch: ['src/**/*.tsx'], 27 | }, 28 | npmClient: 'pnpm', 29 | }); 30 | -------------------------------------------------------------------------------- /config/proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 代理的配置 3 | * @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 4 | * ------------------------------- 5 | * The agent cannot take effect in the production environment 6 | * so there is no configuration of the production environment 7 | * For details, please see 8 | * https://pro.ant.design/docs/deploy 9 | * 10 | * @doc https://umijs.org/docs/guides/proxy 11 | */ 12 | export default { 13 | '/api/': { 14 | target: 'http://127.0.0.1:3000', 15 | // target: 'http://127.0.0.1:8080', 16 | changeOrigin: true, 17 | pathRewrite: { '^/api': '/api' }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /config/routes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @name umi 的路由配置 3 | * @description 只支持 path,component,routes,redirect,wrappers,name,icon 的配置 4 | * @param path path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。 5 | * @param component 配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。 6 | * @param routes 配置子路由,通常在需要为多个路径增加 layout 组件时使用。 7 | * @param redirect 配置路由跳转 8 | * @param wrappers 配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验 9 | * @param name 配置路由的标题,默认读取国际化文件 menu.ts 中 menu.xxxx 的值,如配置 name 为 login,则读取 menu.ts 中 menu.login 的取值作为标题 10 | * @param icon 配置路由的图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 则取值应为 user 或者 User 11 | * @doc https://umijs.org/docs/guides/routes 12 | */ 13 | export default [ 14 | { 15 | path: '/', 16 | component: './Index', 17 | layout: false, 18 | }, 19 | { 20 | name: '引擎页', 21 | path: '/index', 22 | component: './Index', 23 | layout: false, 24 | }, 25 | { 26 | name: '布局页', 27 | path: '/layout', 28 | component: '@/layouts/index', 29 | routes: [ 30 | { path: '/layout/index', component: './Index' }, // 携带布局的引擎页 31 | { path: '/layout/test', component: './Test' }, 32 | ], 33 | layout: false, 34 | }, 35 | ]; 36 | -------------------------------------------------------------------------------- /mock/userAPI.ts: -------------------------------------------------------------------------------- 1 | const users = [ 2 | { id: 0, name: 'Umi', nickName: 'U', gender: 'MALE' }, 3 | { id: 1, name: 'Fish', nickName: 'B', gender: 'FEMALE' }, 4 | ]; 5 | 6 | export default { 7 | 'GET /api/v1/queryUserList': (req: any, res: any) => { 8 | res.json({ 9 | success: true, 10 | data: { list: users }, 11 | errorCode: 0, 12 | }); 13 | }, 14 | 'PUT /api/v1/user/': (req: any, res: any) => { 15 | res.json({ 16 | success: true, 17 | errorCode: 0, 18 | }); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "author": "tangtanglove ", 4 | "scripts": { 5 | "build": "max build", 6 | "dev": "max dev", 7 | "format": "prettier --cache --write .", 8 | "postinstall": "max setup", 9 | "prepare": "husky install", 10 | "setup": "max setup", 11 | "start": "npm run dev" 12 | }, 13 | "dependencies": { 14 | "@ant-design/charts": "^1.4.2", 15 | "@ant-design/icons": "^4.7.0", 16 | "@ant-design/pro-components": "^2.0.1", 17 | "@ant-design/use-emotion-css": "^1.0.4", 18 | "@dnd-kit/core": "^6.3.1", 19 | "@dnd-kit/sortable": "^10.0.0", 20 | "@dnd-kit/utilities": "^3.2.2", 21 | "@tinymce/tinymce-react": "^4.3.0", 22 | "@types/lodash.template": "^4.5.1", 23 | "@umijs/max": "^4.0.47", 24 | "@unocss/cli": "^0.62.3", 25 | "antd": "^5.0.0", 26 | "cropperjs": "^1.6.2", 27 | "dayjs": "^1.11.13", 28 | "js-base64": "^3.7.4", 29 | "lodash-es": "^4.17.21", 30 | "lodash.template": "^4.5.0", 31 | "query-string": "^8.1.0", 32 | "react-amap": "^1.2.8", 33 | "react-amap-plugin-autocomplete": "^0.0.4", 34 | "react-cropper": "^2.1.8", 35 | "unocss": "^0.62.3" 36 | }, 37 | "devDependencies": { 38 | "@types/lodash-es": "^4.17.12", 39 | "@types/react": "^18.0.0", 40 | "@types/react-dom": "^18.0.0", 41 | "husky": "^8.0.1", 42 | "lint-staged": "^13.0.3", 43 | "prettier": "^2.7.1", 44 | "prettier-plugin-organize-imports": "^2", 45 | "prettier-plugin-packagejson": "^2", 46 | "typescript": "^4.1.2" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /public/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarkcloudio/quark-ui/8dc37e388c16feadfb71d9727fcd219f4fc87194/public/default.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarkcloudio/quark-ui/8dc37e388c16feadfb71d9727fcd219f4fc87194/public/favicon.ico -------------------------------------------------------------------------------- /public/tinymce/langs/readme.md: -------------------------------------------------------------------------------- 1 | This is where language files should be placed. 2 | 3 | Please DO NOT translate these directly use this service: https://www.transifex.com/projects/p/tinymce/ 4 | -------------------------------------------------------------------------------- /public/tinymce/plugins/autolink/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | function k(e) { 12 | return /^[(\[{ \u00a0]$/.test(e); 13 | } 14 | function w(e) { 15 | return 3 === e.nodeType; 16 | } 17 | function i(e) { 18 | return 1 === e.nodeType; 19 | } 20 | function o(e, t) { 21 | var n; 22 | return t < 0 && (t = 0), !w(e) || ((n = e.data.length) < t && (t = n)), t; 23 | } 24 | function y(e, t, n) { 25 | !i(t) || t.hasChildNodes() ? e.setStart(t, o(t, n)) : e.setStartBefore(t); 26 | } 27 | function v(e, t, n) { 28 | !i(t) || t.hasChildNodes() ? e.setEnd(t, o(t, n)) : e.setEndAfter(t); 29 | } 30 | function r(e, t) { 31 | var n, 32 | i, 33 | o, 34 | r, 35 | a, 36 | f = e.getParam('autolink_pattern', A), 37 | s = e.getParam('default_link_target', !1); 38 | if (null === e.dom.getParent(e.selection.getNode(), 'a[href]')) { 39 | var d = e.selection.getRng().cloneRange(); 40 | if (d.startOffset < 5) { 41 | if (!(r = d.endContainer.previousSibling)) { 42 | if ( 43 | !d.endContainer.firstChild || 44 | !d.endContainer.firstChild.nextSibling 45 | ) 46 | return; 47 | r = d.endContainer.firstChild.nextSibling; 48 | } 49 | if ((y(d, r, (a = r.length)), v(d, r, a), d.endOffset < 5)) return; 50 | (n = d.endOffset), (i = r); 51 | } else { 52 | if (!w((i = d.endContainer)) && i.firstChild) { 53 | for (; !w(i) && i.firstChild; ) i = i.firstChild; 54 | w(i) && (y(d, i, 0), v(d, i, i.nodeValue.length)); 55 | } 56 | n = 1 === d.endOffset ? 2 : d.endOffset - 1 - t; 57 | } 58 | for ( 59 | var l = n; 60 | y(d, i, 2 <= n ? n - 2 : 0), 61 | v(d, i, 1 <= n ? n - 1 : 0), 62 | --n, 63 | !k(d.toString()) && 0 <= n - 2; 64 | 65 | ); 66 | k(d.toString()) 67 | ? (y(d, i, n), v(d, i, l), (n += 1)) 68 | : (0 === d.startOffset ? y(d, i, 0) : y(d, i, n), v(d, i, l)), 69 | (u = d.toString()), 70 | /[?!,.;:]/.test(u.charAt(u.length - 1)) && v(d, i, l - 1); 71 | var u, 72 | c, 73 | g, 74 | h, 75 | C = (u = d.toString().trim()).match(f), 76 | m = e.getParam('link_default_protocol', 'http', 'string'); 77 | C && 78 | ((g = c = C[0]).length >= (h = 'www.').length && 79 | g.substr(0, 0 + h.length) === h 80 | ? (c = m + '://' + c) 81 | : -1 === c.indexOf('@') || 82 | /^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(c) || 83 | (c = 'mailto:' + c), 84 | (o = e.selection.getBookmark()), 85 | e.selection.setRng(d), 86 | e.execCommand('createlink', !1, c), 87 | !1 !== s && e.dom.setAttrib(e.selection.getNode(), 'target', s), 88 | e.selection.moveToBookmark(o), 89 | e.nodeChanged()); 90 | } 91 | } 92 | var e = tinymce.util.Tools.resolve('tinymce.PluginManager'), 93 | a = tinymce.util.Tools.resolve('tinymce.Env'), 94 | A = new RegExp( 95 | '^' + 96 | /(?:[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 97 | .source + 98 | '$', 99 | 'i', 100 | ); 101 | e.add('autolink', function (e) { 102 | var t, n; 103 | (t = e).on('keydown', function (e) { 104 | if (13 === e.keyCode) return r(t, -1); 105 | }), 106 | a.browser.isIE() 107 | ? t.on('focus', function () { 108 | if (!n) { 109 | n = !0; 110 | try { 111 | t.execCommand('AutoUrlDetect', !1, !0); 112 | } catch (e) {} 113 | } 114 | }) 115 | : (t.on('keypress', function (e) { 116 | if (41 === e.keyCode || 93 === e.keyCode || 125 === e.keyCode) 117 | return r(t, -1); 118 | }), 119 | t.on('keyup', function (e) { 120 | if (32 === e.keyCode) return r(t, 0); 121 | })); 122 | }); 123 | })(); 124 | -------------------------------------------------------------------------------- /public/tinymce/plugins/autoresize/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | function y(e) { 12 | return e.getParam('min_height', e.getElement().offsetHeight, 'number'); 13 | } 14 | function p(e, t) { 15 | var n = e.getBody(); 16 | n && ((n.style.overflowY = t ? '' : 'hidden'), t || (n.scrollTop = 0)); 17 | } 18 | function v(e, t, n, i) { 19 | var o = parseInt(e.getStyle(t, n, i), 10); 20 | return isNaN(o) ? 0 : o; 21 | } 22 | var l = Object.hasOwnProperty, 23 | e = tinymce.util.Tools.resolve('tinymce.PluginManager'), 24 | b = tinymce.util.Tools.resolve('tinymce.Env'), 25 | r = tinymce.util.Tools.resolve('tinymce.util.Delay'), 26 | u = function (e, t, n, i, o) { 27 | r.setEditorTimeout( 28 | e, 29 | function () { 30 | C(e, t), n-- ? u(e, t, n, i, o) : o && o(); 31 | }, 32 | i, 33 | ); 34 | }, 35 | C = function (e, t, n) { 36 | var i, 37 | o, 38 | r, 39 | s, 40 | a, 41 | l, 42 | u, 43 | g, 44 | c, 45 | m, 46 | f, 47 | d = e.dom, 48 | h = e.getDoc(); 49 | h && 50 | (e.plugins.fullscreen && e.plugins.fullscreen.isFullscreen() 51 | ? p(e, !0) 52 | : ((i = h.documentElement), 53 | (o = e.getParam('autoresize_bottom_margin', 50, 'number')), 54 | (r = y(e)), 55 | (s = v(d, i, 'margin-top', !0)), 56 | (a = v(d, i, 'margin-bottom', !0)), 57 | (l = (l = i.offsetHeight + s + a + o) < 0 ? 0 : l) + 58 | (u = 59 | e.getContainer().offsetHeight - 60 | e.getContentAreaContainer().offsetHeight) > 61 | y(e) && (r = l + u), 62 | (g = e.getParam('max_height', 0, 'number')) && g < r 63 | ? ((r = g), p(e, !0)) 64 | : p(e, !1), 65 | r !== t.get() && 66 | ((c = r - t.get()), 67 | d.setStyle(e.getContainer(), 'height', r + 'px'), 68 | t.set(r), 69 | e.fire('ResizeEditor'), 70 | b.browser.isSafari() && 71 | b.mac && 72 | (m = e.getWin()).scrollTo(m.pageXOffset, m.pageYOffset), 73 | !e.hasFocus() || 74 | 'setcontent' !== 75 | (null == (f = n) ? void 0 : f.type.toLowerCase()) || 76 | (!0 !== f.selection && !0 !== f.paste) || 77 | e.selection.scrollIntoView(), 78 | b.webkit && c < 0 && C(e, t, n)))); 79 | }; 80 | e.add('autoresize', function (e) { 81 | var t, 82 | n, 83 | i, 84 | o, 85 | r, 86 | s, 87 | a = e.settings; 88 | l.call(a, 'resize') || (e.settings.resize = !1), 89 | e.inline || 90 | ((s = 0), 91 | (r = t = 92 | { 93 | get: function () { 94 | return s; 95 | }, 96 | set: function (e) { 97 | s = e; 98 | }, 99 | }), 100 | (o = e).addCommand('mceAutoResize', function () { 101 | C(o, r); 102 | }), 103 | (i = t), 104 | (n = e).on('init', function () { 105 | var e = n.getParam('autoresize_overflow_padding', 1, 'number'), 106 | t = n.dom; 107 | t.setStyles(n.getDoc().documentElement, { height: 'auto' }), 108 | t.setStyles(n.getBody(), { 109 | paddingLeft: e, 110 | paddingRight: e, 111 | 'min-height': 0, 112 | }); 113 | }), 114 | n.on( 115 | 'NodeChange SetContent keyup FullscreenStateChanged ResizeContent', 116 | function (e) { 117 | C(n, i, e); 118 | }, 119 | ), 120 | n.getParam('autoresize_on_init', !0, 'boolean') && 121 | n.on('init', function () { 122 | u(n, i, 20, 100, function () { 123 | u(n, i, 5, 1e3); 124 | }); 125 | })); 126 | }); 127 | })(); 128 | -------------------------------------------------------------------------------- /public/tinymce/plugins/code/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | tinymce.util.Tools.resolve('tinymce.PluginManager').add('code', function (e) { 12 | var t, o; 13 | function n() { 14 | return o.execCommand('mceCodeEditor'); 15 | } 16 | return ( 17 | (t = e).addCommand('mceCodeEditor', function () { 18 | var n, e; 19 | (e = (n = t).getContent({ source_view: !0 })), 20 | n.windowManager.open({ 21 | title: 'Source Code', 22 | size: 'large', 23 | body: { 24 | type: 'panel', 25 | items: [{ type: 'textarea', name: 'code' }], 26 | }, 27 | buttons: [ 28 | { type: 'cancel', name: 'cancel', text: 'Cancel' }, 29 | { type: 'submit', name: 'save', text: 'Save', primary: !0 }, 30 | ], 31 | initialData: { code: e }, 32 | onSubmit: function (e) { 33 | var t = n, 34 | o = e.getData().code; 35 | t.focus(), 36 | t.undoManager.transact(function () { 37 | t.setContent(o); 38 | }), 39 | t.selection.setCursorLocation(), 40 | t.nodeChanged(), 41 | e.close(); 42 | }, 43 | }); 44 | }), 45 | (o = e).ui.registry.addButton('code', { 46 | icon: 'sourcecode', 47 | tooltip: 'Source code', 48 | onAction: n, 49 | }), 50 | o.ui.registry.addMenuItem('code', { 51 | icon: 'sourcecode', 52 | text: 'Source code', 53 | onAction: n, 54 | }), 55 | {} 56 | ); 57 | }); 58 | })(); 59 | -------------------------------------------------------------------------------- /public/tinymce/plugins/colorpicker/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | tinymce.util.Tools.resolve('tinymce.PluginManager').add( 12 | 'colorpicker', 13 | function () {}, 14 | ); 15 | })(); 16 | -------------------------------------------------------------------------------- /public/tinymce/plugins/contextmenu/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | tinymce.util.Tools.resolve('tinymce.PluginManager').add( 12 | 'contextmenu', 13 | function () {}, 14 | ); 15 | })(); 16 | -------------------------------------------------------------------------------- /public/tinymce/plugins/hr/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | tinymce.util.Tools.resolve('tinymce.PluginManager').add('hr', function (n) { 12 | var o, t; 13 | function e() { 14 | return t.execCommand('InsertHorizontalRule'); 15 | } 16 | (o = n).addCommand('InsertHorizontalRule', function () { 17 | o.execCommand('mceInsertContent', !1, '
'); 18 | }), 19 | (t = n).ui.registry.addButton('hr', { 20 | icon: 'horizontal-rule', 21 | tooltip: 'Horizontal line', 22 | onAction: e, 23 | }), 24 | t.ui.registry.addMenuItem('hr', { 25 | icon: 'horizontal-rule', 26 | text: 'Horizontal line', 27 | onAction: e, 28 | }); 29 | }); 30 | })(); 31 | -------------------------------------------------------------------------------- /public/tinymce/plugins/indent2em/plugin.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add('indent2em', function (editor, url) { 2 | var pluginName = '首行缩进'; 3 | var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools'); 4 | var indent2em_val = editor.getParam('indent2em_val', '2em'); 5 | var doAct = function () { 6 | var dom = editor.dom; 7 | var blocks = editor.selection.getSelectedBlocks(); 8 | var act = ''; 9 | global$1.each(blocks, function (block) { 10 | if (act == '') { 11 | act = 12 | dom.getStyle(block, 'text-indent') == indent2em_val 13 | ? 'remove' 14 | : 'add'; 15 | } 16 | if (act == 'add') { 17 | dom.setStyle(block, 'text-indent', indent2em_val); 18 | } else { 19 | var style = dom.getAttrib(block, 'style'); 20 | var reg = new RegExp('text-indent:[\\s]*' + indent2em_val + ';', 'ig'); 21 | style = style.replace(reg, ''); 22 | dom.setAttrib(block, 'style', style); 23 | } 24 | }); 25 | }; 26 | 27 | editor.ui.registry.getAll().icons.indent2em || 28 | editor.ui.registry.addIcon( 29 | 'indent2em', 30 | '', 31 | ); 32 | 33 | var stateSelectorAdapter = function (editor, selector) { 34 | return function (buttonApi) { 35 | return editor.selection.selectorChangedWithUnbind( 36 | selector.join(','), 37 | buttonApi.setActive, 38 | ).unbind; 39 | }; 40 | }; 41 | 42 | editor.ui.registry.addToggleButton('indent2em', { 43 | icon: 'indent2em', 44 | tooltip: pluginName, 45 | onAction: function () { 46 | doAct(); 47 | }, 48 | onSetup: stateSelectorAdapter(editor, [ 49 | '*[style*="text-indent"]', 50 | '*[data-mce-style*="text-indent"]', 51 | ]), 52 | }); 53 | 54 | editor.ui.registry.addMenuItem('indent2em', { 55 | text: pluginName, 56 | onAction: function () { 57 | doAct(); 58 | }, 59 | }); 60 | 61 | editor.addCommand('indent2em', doAct); 62 | 63 | return { 64 | getMetadata: function () { 65 | return { 66 | name: pluginName, 67 | url: 'http://tinymce.ax-z.cn/more-plugins/indent2em.php', 68 | }; 69 | }, 70 | }; 71 | }); 72 | -------------------------------------------------------------------------------- /public/tinymce/plugins/indent2em/plugin.min.js: -------------------------------------------------------------------------------- 1 | tinymce.PluginManager.add('indent2em', function (editor, url) { 2 | var pluginName = '首行缩进'; 3 | var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools'); 4 | var indent2em_val = editor.getParam('indent2em_val', '2em'); 5 | var doAct = function () { 6 | var dom = editor.dom; 7 | var blocks = editor.selection.getSelectedBlocks(); 8 | var act = ''; 9 | global$1.each(blocks, function (block) { 10 | if (act == '') { 11 | act = 12 | dom.getStyle(block, 'text-indent') == indent2em_val 13 | ? 'remove' 14 | : 'add'; 15 | } 16 | if (act == 'add') { 17 | dom.setStyle(block, 'text-indent', indent2em_val); 18 | } else { 19 | var style = dom.getAttrib(block, 'style'); 20 | var reg = new RegExp('text-indent:[\\s]*' + indent2em_val + ';', 'ig'); 21 | style = style.replace(reg, ''); 22 | dom.setAttrib(block, 'style', style); 23 | } 24 | }); 25 | }; 26 | 27 | editor.ui.registry.getAll().icons.indent2em || 28 | editor.ui.registry.addIcon( 29 | 'indent2em', 30 | '', 31 | ); 32 | 33 | var stateSelectorAdapter = function (editor, selector) { 34 | return function (buttonApi) { 35 | return editor.selection.selectorChangedWithUnbind( 36 | selector.join(','), 37 | buttonApi.setActive, 38 | ).unbind; 39 | }; 40 | }; 41 | 42 | editor.ui.registry.addToggleButton('indent2em', { 43 | icon: 'indent2em', 44 | tooltip: pluginName, 45 | onAction: function () { 46 | doAct(); 47 | }, 48 | onSetup: stateSelectorAdapter(editor, [ 49 | '*[style*="text-indent"]', 50 | '*[data-mce-style*="text-indent"]', 51 | ]), 52 | }); 53 | 54 | editor.ui.registry.addMenuItem('indent2em', { 55 | text: pluginName, 56 | onAction: function () { 57 | doAct(); 58 | }, 59 | }); 60 | 61 | editor.addCommand('indent2em', doAct); 62 | 63 | return { 64 | getMetadata: function () { 65 | return { 66 | name: pluginName, 67 | url: 'http://tinymce.ax-z.cn/more-plugins/indent2em.php', 68 | }; 69 | }, 70 | }; 71 | }); 72 | -------------------------------------------------------------------------------- /public/tinymce/plugins/multipleimage/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.2.1 (2020-03-25) 8 | */ 9 | (function () { 10 | 'use strict'; 11 | 12 | var global = tinymce.util.Tools.resolve('tinymce.PluginManager'); 13 | 14 | var register = function (editor) { 15 | editor.addCommand('multipleimage', function () { 16 | // 记录登录凭据 17 | console.log('open multipleimage'); 18 | sessionStorage.setItem('editorCommand', 'multipleimage'); 19 | }); 20 | }; 21 | var Commands = { register: register }; 22 | 23 | var register$1 = function (editor) { 24 | editor.ui.registry.addButton('multipleimage', { 25 | icon: 'image', 26 | tooltip: '图片', 27 | onAction: function () { 28 | return editor.execCommand('multipleimage'); 29 | }, 30 | }); 31 | editor.ui.registry.addMenuItem('multipleimage', { 32 | icon: 'image', 33 | text: '图片', 34 | onAction: function () { 35 | return editor.execCommand('multipleimage'); 36 | }, 37 | }); 38 | }; 39 | var Buttons = { register: register$1 }; 40 | 41 | function Plugin() { 42 | global.add('multipleimage', function (editor) { 43 | Commands.register(editor); 44 | Buttons.register(editor); 45 | }); 46 | } 47 | 48 | Plugin(); 49 | })(); 50 | -------------------------------------------------------------------------------- /public/tinymce/plugins/nonbreaking/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | function o(n, e) { 12 | for (var a = '', o = 0; o < e; o++) a += n; 13 | return a; 14 | } 15 | function s(n, e) { 16 | var a = 17 | n.getParam('nonbreaking_wrap', !0, 'boolean') || n.plugins.visualchars 18 | ? '' + 23 | o(' ', e) + 24 | '' 25 | : o(' ', e); 26 | n.undoManager.transact(function () { 27 | return n.insertContent(a); 28 | }); 29 | } 30 | var n = tinymce.util.Tools.resolve('tinymce.PluginManager'), 31 | c = tinymce.util.Tools.resolve('tinymce.util.VK'); 32 | n.add('nonbreaking', function (n) { 33 | var e, a, o, t, i; 34 | function r() { 35 | return a.execCommand('mceNonBreaking'); 36 | } 37 | (e = n).addCommand('mceNonBreaking', function () { 38 | s(e, 1); 39 | }), 40 | (a = n).ui.registry.addButton('nonbreaking', { 41 | icon: 'non-breaking', 42 | tooltip: 'Nonbreaking space', 43 | onAction: r, 44 | }), 45 | a.ui.registry.addMenuItem('nonbreaking', { 46 | icon: 'non-breaking', 47 | text: 'Nonbreaking space', 48 | onAction: r, 49 | }), 50 | 0 < 51 | (i = 52 | 'boolean' == typeof (t = (o = n).getParam('nonbreaking_force_tab', 0)) 53 | ? !0 === t 54 | ? 3 55 | : 0 56 | : t) && 57 | o.on('keydown', function (n) { 58 | n.keyCode !== c.TAB || 59 | n.isDefaultPrevented() || 60 | n.shiftKey || 61 | (n.preventDefault(), n.stopImmediatePropagation(), s(o, i)); 62 | }); 63 | }); 64 | })(); 65 | -------------------------------------------------------------------------------- /public/tinymce/plugins/noneditable/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | function l(t) { 12 | return t.getParam('noneditable_noneditable_class', 'mceNonEditable'); 13 | } 14 | function u(e) { 15 | return function (t) { 16 | return -1 !== (' ' + t.attr('class') + ' ').indexOf(e); 17 | }; 18 | } 19 | function e(e) { 20 | var t, 21 | r = 'contenteditable', 22 | n = 23 | ' ' + 24 | f.trim(e.getParam('noneditable_editable_class', 'mceEditable')) + 25 | ' ', 26 | a = ' ' + f.trim(l(e)) + ' ', 27 | i = u(n), 28 | o = u(a), 29 | c = 30 | (t = e.getParam('noneditable_regexp', [])) && t.constructor === RegExp 31 | ? [t] 32 | : t; 33 | e.on('PreInit', function () { 34 | 0 < c.length && 35 | e.on('BeforeSetContent', function (t) { 36 | !(function (t, e, n) { 37 | var r = e.length, 38 | a = n.content; 39 | if ('raw' !== n.format) { 40 | for (; r--; ) 41 | a = a.replace( 42 | e[r], 43 | (function (i, o, c) { 44 | return function (t) { 45 | var e = arguments, 46 | n = e[e.length - 2], 47 | r = 0 < n ? o.charAt(n - 1) : ''; 48 | if ('"' === r) return t; 49 | if ('>' === r) { 50 | var a = o.lastIndexOf('<', n); 51 | if ( 52 | -1 !== a && 53 | -1 !== 54 | o.substring(a, n).indexOf('contenteditable="false"') 55 | ) 56 | return t; 57 | } 58 | return ( 59 | '' + 64 | i.dom.encode('string' == typeof e[1] ? e[1] : e[0]) + 65 | '' 66 | ); 67 | }; 68 | })(t, a, l(t)), 69 | ); 70 | n.content = a; 71 | } 72 | })(e, c, t); 73 | }), 74 | e.parser.addAttributeFilter('class', function (t) { 75 | for (var e, n = t.length; n--; ) 76 | (e = t[n]), i(e) ? e.attr(r, 'true') : o(e) && e.attr(r, 'false'); 77 | }), 78 | e.serializer.addAttributeFilter(r, function (t) { 79 | for (var e, n = t.length; n--; ) 80 | (e = t[n]), 81 | (i(e) || o(e)) && 82 | (0 < c.length && e.attr('data-mce-content') 83 | ? ((e.name = '#text'), 84 | (e.type = 3), 85 | (e.raw = !0), 86 | (e.value = e.attr('data-mce-content'))) 87 | : e.attr(r, null)); 88 | }); 89 | }); 90 | } 91 | var t = tinymce.util.Tools.resolve('tinymce.PluginManager'), 92 | f = tinymce.util.Tools.resolve('tinymce.util.Tools'); 93 | t.add('noneditable', function (t) { 94 | e(t); 95 | }); 96 | })(); 97 | -------------------------------------------------------------------------------- /public/tinymce/plugins/pagebreak/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | function u(e) { 12 | return e.getParam('pagebreak_split_block', !1); 13 | } 14 | function l(e) { 15 | var a = 16 | ''; 21 | return e ? '

' + a + '

' : a; 22 | } 23 | var e = tinymce.util.Tools.resolve('tinymce.PluginManager'), 24 | n = tinymce.util.Tools.resolve('tinymce.Env'), 25 | m = 'mce-pagebreak'; 26 | e.add('pagebreak', function (e) { 27 | var a, n, o, i, t, r; 28 | function c() { 29 | return n.execCommand('mcePageBreak'); 30 | } 31 | function g() { 32 | return u(o); 33 | } 34 | (a = e).addCommand('mcePageBreak', function () { 35 | a.insertContent(l(u(a))); 36 | }), 37 | (n = e).ui.registry.addButton('pagebreak', { 38 | icon: 'page-break', 39 | tooltip: 'Page break', 40 | onAction: c, 41 | }), 42 | n.ui.registry.addMenuItem('pagebreak', { 43 | text: 'Page break', 44 | icon: 'page-break', 45 | onAction: c, 46 | }), 47 | (i = (o = e).getParam('pagebreak_separator', '\x3c!-- pagebreak --\x3e')), 48 | (t = new RegExp( 49 | i.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g, function (e) { 50 | return '\\' + e; 51 | }), 52 | 'gi', 53 | )), 54 | o.on('BeforeSetContent', function (e) { 55 | e.content = e.content.replace(t, l(g())); 56 | }), 57 | o.on('PreInit', function () { 58 | o.serializer.addNodeFilter('img', function (e) { 59 | for (var a, n, t, r = e.length; r--; ) 60 | (t = (n = e[r]).attr('class')) && 61 | -1 !== t.indexOf(m) && 62 | ((a = n.parent), 63 | o.schema.getBlockElements()[a.name] && g() 64 | ? ((a.type = 3), (a.value = i), (a.raw = !0), n.remove()) 65 | : ((n.type = 3), (n.value = i), (n.raw = !0))); 66 | }); 67 | }), 68 | (r = e).on('ResolveName', function (e) { 69 | 'IMG' === e.target.nodeName && 70 | r.dom.hasClass(e.target, m) && 71 | (e.name = 'pagebreak'); 72 | }); 73 | }); 74 | })(); 75 | -------------------------------------------------------------------------------- /public/tinymce/plugins/preview/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | var e = tinymce.util.Tools.resolve('tinymce.PluginManager'), 12 | f = tinymce.util.Tools.resolve('tinymce.Env'), 13 | w = tinymce.util.Tools.resolve('tinymce.util.Tools'); 14 | e.add('preview', function (e) { 15 | var n, t; 16 | function i() { 17 | return t.execCommand('mcePreview'); 18 | } 19 | (n = e).addCommand('mcePreview', function () { 20 | var e, t; 21 | (t = (function (t) { 22 | var n = '', 23 | i = t.dom.encode, 24 | e = t.getParam('content_style', '', 'string'); 25 | n += ''; 26 | var o = t.getParam('content_css_cors', !1, 'boolean') 27 | ? ' crossorigin="anonymous"' 28 | : ''; 29 | w.each(t.contentCSS, function (e) { 30 | n += 31 | ''; 36 | }), 37 | e && (n += ''); 38 | var a, 39 | r, 40 | s, 41 | c, 42 | d, 43 | l, 44 | m, 45 | y = 46 | -1 === 47 | (c = (a = t).getParam('body_id', 'tinymce', 'string')).indexOf('=') 48 | ? c 49 | : (s = (r = a).getParam('body_id', '', 'hash'))[r.id] || s, 50 | u = 51 | -1 === 52 | (m = (d = t).getParam('body_class', '', 'string')).indexOf('=') 53 | ? m 54 | : (l = d).getParam('body_class', '', 'hash')[l.id] || '', 55 | v = 56 | ' ', 59 | g = t.getBody().dir, 60 | p = g ? ' dir="' + i(g) + '"' : ''; 61 | return ( 62 | '' + 63 | n + 64 | '' + 71 | t.getContent() + 72 | v + 73 | '' 74 | ); 75 | })((e = n))), 76 | e.windowManager 77 | .open({ 78 | title: 'Preview', 79 | size: 'large', 80 | body: { 81 | type: 'panel', 82 | items: [{ name: 'preview', type: 'iframe', sandboxed: !0 }], 83 | }, 84 | buttons: [ 85 | { type: 'cancel', name: 'close', text: 'Close', primary: !0 }, 86 | ], 87 | initialData: { preview: t }, 88 | }) 89 | .focus('close'); 90 | }), 91 | (t = e).ui.registry.addButton('preview', { 92 | icon: 'preview', 93 | tooltip: 'Preview', 94 | onAction: i, 95 | }), 96 | t.ui.registry.addMenuItem('preview', { 97 | icon: 'preview', 98 | text: 'Preview', 99 | onAction: i, 100 | }); 101 | }); 102 | })(); 103 | -------------------------------------------------------------------------------- /public/tinymce/plugins/print/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | var n = tinymce.util.Tools.resolve('tinymce.PluginManager'), 12 | r = tinymce.util.Tools.resolve('tinymce.Env'); 13 | n.add('print', function (n) { 14 | var t, i; 15 | function e() { 16 | return i.execCommand('mcePrint'); 17 | } 18 | (t = n).addCommand('mcePrint', function () { 19 | r.browser.isIE() 20 | ? t.getDoc().execCommand('print', !1, null) 21 | : t.getWin().print(); 22 | }), 23 | (i = n).ui.registry.addButton('print', { 24 | icon: 'print', 25 | tooltip: 'Print', 26 | onAction: e, 27 | }), 28 | i.ui.registry.addMenuItem('print', { 29 | text: 'Print...', 30 | icon: 'print', 31 | onAction: e, 32 | }), 33 | n.addShortcut('Meta+P', '', 'mcePrint'); 34 | }); 35 | })(); 36 | -------------------------------------------------------------------------------- /public/tinymce/plugins/save/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | function o(e) { 12 | return e.getParam('save_enablewhendirty', !0); 13 | } 14 | function a(e, n) { 15 | e.notificationManager.open({ text: n, type: 'error' }); 16 | } 17 | function t(t) { 18 | t.addCommand('mceSave', function () { 19 | !(function (e) { 20 | var n = c.DOM.getParent(e.id, 'form'); 21 | if (!o(e) || e.isDirty()) { 22 | if ((e.save(), e.getParam('save_onsavecallback'))) 23 | return e.execCallback('save_onsavecallback', e), e.nodeChanged(); 24 | n 25 | ? (e.setDirty(!1), 26 | (n.onsubmit && !n.onsubmit()) || 27 | ('function' == typeof n.submit 28 | ? n.submit() 29 | : a(e, 'Error: Form submit field collision.')), 30 | e.nodeChanged()) 31 | : a(e, 'Error: No form element found.'); 32 | } 33 | })(t); 34 | }), 35 | t.addCommand('mceCancel', function () { 36 | var e = t, 37 | n = r.trim(e.startContent); 38 | e.getParam('save_oncancelcallback') 39 | ? e.execCallback('save_oncancelcallback', e) 40 | : e.resetContent(n); 41 | }); 42 | } 43 | function i(t) { 44 | return function (e) { 45 | function n() { 46 | e.setDisabled(o(t) && !t.isDirty()); 47 | } 48 | return ( 49 | n(), 50 | t.on('NodeChange dirty', n), 51 | function () { 52 | return t.off('NodeChange dirty', n); 53 | } 54 | ); 55 | }; 56 | } 57 | var e = tinymce.util.Tools.resolve('tinymce.PluginManager'), 58 | c = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils'), 59 | r = tinymce.util.Tools.resolve('tinymce.util.Tools'); 60 | e.add('save', function (e) { 61 | var n; 62 | (n = e).ui.registry.addButton('save', { 63 | icon: 'save', 64 | tooltip: 'Save', 65 | disabled: !0, 66 | onAction: function () { 67 | return n.execCommand('mceSave'); 68 | }, 69 | onSetup: i(n), 70 | }), 71 | n.ui.registry.addButton('cancel', { 72 | icon: 'cancel', 73 | tooltip: 'Cancel', 74 | disabled: !0, 75 | onAction: function () { 76 | return n.execCommand('mceCancel'); 77 | }, 78 | onSetup: i(n), 79 | }), 80 | n.addShortcut('Meta+S', '', 'mceSave'), 81 | t(e); 82 | }); 83 | })(); 84 | -------------------------------------------------------------------------------- /public/tinymce/plugins/tabfocus/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | function n(e) { 12 | e.keyCode !== y.TAB || 13 | e.ctrlKey || 14 | e.altKey || 15 | e.metaKey || 16 | e.preventDefault(); 17 | } 18 | var e = tinymce.util.Tools.resolve('tinymce.PluginManager'), 19 | t = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils'), 20 | s = tinymce.util.Tools.resolve('tinymce.EditorManager'), 21 | o = tinymce.util.Tools.resolve('tinymce.Env'), 22 | a = tinymce.util.Tools.resolve('tinymce.util.Delay'), 23 | c = tinymce.util.Tools.resolve('tinymce.util.Tools'), 24 | y = tinymce.util.Tools.resolve('tinymce.util.VK'), 25 | d = t.DOM; 26 | e.add('tabfocus', function (e) { 27 | function t(l) { 28 | var r, e, t, n, i; 29 | l.keyCode !== y.TAB || 30 | l.ctrlKey || 31 | l.altKey || 32 | l.metaKey || 33 | l.isDefaultPrevented() || 34 | ((e = function (e) { 35 | function t(e) { 36 | return ( 37 | /INPUT|TEXTAREA|BUTTON/.test(e.tagName) && 38 | s.get(l.id) && 39 | -1 !== e.tabIndex && 40 | i(e) 41 | ); 42 | } 43 | var n = d.select(':input:enabled,*[tabindex]:not(iframe)'), 44 | i = function (e) { 45 | return ( 46 | 'BODY' === e.nodeName || 47 | ('hidden' !== e.type && 48 | 'none' !== e.style.display && 49 | 'hidden' !== e.style.visibility && 50 | i(e.parentNode)) 51 | ); 52 | }; 53 | if ( 54 | (c.each(n, function (e, t) { 55 | if (e.id === u.id) return (r = t), !1; 56 | }), 57 | 0 < e) 58 | ) { 59 | for (var o = r + 1; o < n.length; o++) if (t(n[o])) return n[o]; 60 | } else for (o = r - 1; 0 <= o; o--) if (t(n[o])) return n[o]; 61 | return null; 62 | }), 63 | 1 === 64 | (t = c.explode( 65 | u.getParam( 66 | 'tab_focus', 67 | u.getParam('tabfocus_elements', ':prev,:next'), 68 | ), 69 | )).length && ((t[1] = t[0]), (t[0] = ':prev')), 70 | (n = l.shiftKey 71 | ? ':prev' === t[0] 72 | ? e(-1) 73 | : d.get(t[0]) 74 | : ':next' === t[1] 75 | ? e(1) 76 | : d.get(t[1])) && 77 | ((i = s.get(n.id || n.name)), 78 | n.id && i 79 | ? i.focus() 80 | : a.setTimeout(function () { 81 | o.webkit || window.focus(), n.focus(); 82 | }, 10), 83 | l.preventDefault())); 84 | } 85 | var u; 86 | (u = e).on('init', function () { 87 | u.inline && d.setAttrib(u.getBody(), 'tabIndex', null), 88 | u.on('keyup', n), 89 | o.gecko ? u.on('keypress keydown', t) : u.on('keydown', t); 90 | }); 91 | }); 92 | })(); 93 | -------------------------------------------------------------------------------- /public/tinymce/plugins/textcolor/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | tinymce.util.Tools.resolve('tinymce.PluginManager').add( 12 | 'textcolor', 13 | function () {}, 14 | ); 15 | })(); 16 | -------------------------------------------------------------------------------- /public/tinymce/plugins/visualblocks/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | * 7 | * Version: 5.10.2 (2021-11-17) 8 | */ 9 | !(function () { 10 | 'use strict'; 11 | function f(t, o, e) { 12 | var n, i; 13 | t.dom.toggleClass(t.getBody(), 'mce-visualblocks'), 14 | e.set(!e.get()), 15 | (n = t), 16 | (i = e.get()), 17 | n.fire('VisualBlocks', { state: i }); 18 | } 19 | function g(e, n) { 20 | return function (o) { 21 | function t(t) { 22 | return o.setActive(t.state); 23 | } 24 | return ( 25 | o.setActive(n.get()), 26 | e.on('VisualBlocks', t), 27 | function () { 28 | return e.off('VisualBlocks', t); 29 | } 30 | ); 31 | }; 32 | } 33 | tinymce.util.Tools.resolve('tinymce.PluginManager').add( 34 | 'visualblocks', 35 | function (t, o) { 36 | var e, 37 | n, 38 | i, 39 | s, 40 | c, 41 | u, 42 | l, 43 | a = 44 | ((e = !1), 45 | { 46 | get: function () { 47 | return e; 48 | }, 49 | set: function (t) { 50 | e = t; 51 | }, 52 | }); 53 | function r() { 54 | return s.execCommand('mceVisualBlocks'); 55 | } 56 | (i = a), 57 | (n = t).addCommand('mceVisualBlocks', function () { 58 | f(n, 0, i); 59 | }), 60 | (s = t).ui.registry.addToggleButton('visualblocks', { 61 | icon: 'visualblocks', 62 | tooltip: 'Show blocks', 63 | onAction: r, 64 | onSetup: g(s, (c = a)), 65 | }), 66 | s.ui.registry.addToggleMenuItem('visualblocks', { 67 | text: 'Show blocks', 68 | icon: 'visualblocks', 69 | onAction: r, 70 | onSetup: g(s, c), 71 | }), 72 | (l = a), 73 | (u = t).on('PreviewFormats AfterPreviewFormats', function (t) { 74 | l.get() && 75 | u.dom.toggleClass( 76 | u.getBody(), 77 | 'mce-visualblocks', 78 | 'afterpreviewformats' === t.type, 79 | ); 80 | }), 81 | u.on('init', function () { 82 | u.getParam('visualblocks_default_state', !1, 'boolean') && f(u, 0, l); 83 | }); 84 | }, 85 | ); 86 | })(); 87 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/dark/content.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body { 8 | background-color: #2f3742; 9 | color: #dfe0e4; 10 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 11 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 12 | line-height: 1.4; 13 | margin: 1rem; 14 | } 15 | a { 16 | color: #4099ff; 17 | } 18 | table { 19 | border-collapse: collapse; 20 | } 21 | table:not([cellpadding]) td, 22 | table:not([cellpadding]) th { 23 | padding: 0.4rem; 24 | } 25 | table[border]:not([border='0']):not([style*='border-width']) td, 26 | table[border]:not([border='0']):not([style*='border-width']) th { 27 | border-width: 1px; 28 | } 29 | table[border]:not([border='0']):not([style*='border-style']) td, 30 | table[border]:not([border='0']):not([style*='border-style']) th { 31 | border-style: solid; 32 | } 33 | table[border]:not([border='0']):not([style*='border-color']) td, 34 | table[border]:not([border='0']):not([style*='border-color']) th { 35 | border-color: #6d737b; 36 | } 37 | figure { 38 | display: table; 39 | margin: 1rem auto; 40 | } 41 | figure figcaption { 42 | color: #8a8f97; 43 | display: block; 44 | margin-top: 0.25rem; 45 | text-align: center; 46 | } 47 | hr { 48 | border-color: #6d737b; 49 | border-style: solid; 50 | border-width: 1px 0 0; 51 | } 52 | code { 53 | background-color: #6d737b; 54 | border-radius: 3px; 55 | padding: 0.1rem 0.2rem; 56 | } 57 | .mce-content-body:not([dir='rtl']) blockquote { 58 | border-left: 2px solid #6d737b; 59 | margin-left: 1.5rem; 60 | padding-left: 1rem; 61 | } 62 | .mce-content-body[dir='rtl'] blockquote { 63 | border-right: 2px solid #6d737b; 64 | margin-right: 1.5rem; 65 | padding-right: 1rem; 66 | } 67 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/default/content.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body { 8 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 9 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 10 | line-height: 1.4; 11 | margin: 1rem; 12 | } 13 | table { 14 | border-collapse: collapse; 15 | } 16 | table:not([cellpadding]) td, 17 | table:not([cellpadding]) th { 18 | padding: 0.4rem; 19 | } 20 | table[border]:not([border='0']):not([style*='border-width']) td, 21 | table[border]:not([border='0']):not([style*='border-width']) th { 22 | border-width: 1px; 23 | } 24 | table[border]:not([border='0']):not([style*='border-style']) td, 25 | table[border]:not([border='0']):not([style*='border-style']) th { 26 | border-style: solid; 27 | } 28 | table[border]:not([border='0']):not([style*='border-color']) td, 29 | table[border]:not([border='0']):not([style*='border-color']) th { 30 | border-color: #ccc; 31 | } 32 | figure { 33 | display: table; 34 | margin: 1rem auto; 35 | } 36 | figure figcaption { 37 | color: #999; 38 | display: block; 39 | margin-top: 0.25rem; 40 | text-align: center; 41 | } 42 | hr { 43 | border-color: #ccc; 44 | border-style: solid; 45 | border-width: 1px 0 0; 46 | } 47 | code { 48 | background-color: #e8e8e8; 49 | border-radius: 3px; 50 | padding: 0.1rem 0.2rem; 51 | } 52 | .mce-content-body:not([dir='rtl']) blockquote { 53 | border-left: 2px solid #ccc; 54 | margin-left: 1.5rem; 55 | padding-left: 1rem; 56 | } 57 | .mce-content-body[dir='rtl'] blockquote { 58 | border-right: 2px solid #ccc; 59 | margin-right: 1.5rem; 60 | padding-right: 1rem; 61 | } 62 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/document/content.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | @media screen { 8 | html { 9 | background: #f4f4f4; 10 | min-height: 100%; 11 | } 12 | } 13 | body { 14 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 15 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 16 | } 17 | @media screen { 18 | body { 19 | background-color: #fff; 20 | box-shadow: 0 0 4px rgb(0 0 0 / 15%); 21 | box-sizing: border-box; 22 | margin: 1rem auto 0; 23 | max-width: 820px; 24 | min-height: calc(100vh - 1rem); 25 | padding: 4rem 6rem 6rem; 26 | } 27 | } 28 | table { 29 | border-collapse: collapse; 30 | } 31 | table:not([cellpadding]) td, 32 | table:not([cellpadding]) th { 33 | padding: 0.4rem; 34 | } 35 | table[border]:not([border='0']):not([style*='border-width']) td, 36 | table[border]:not([border='0']):not([style*='border-width']) th { 37 | border-width: 1px; 38 | } 39 | table[border]:not([border='0']):not([style*='border-style']) td, 40 | table[border]:not([border='0']):not([style*='border-style']) th { 41 | border-style: solid; 42 | } 43 | table[border]:not([border='0']):not([style*='border-color']) td, 44 | table[border]:not([border='0']):not([style*='border-color']) th { 45 | border-color: #ccc; 46 | } 47 | figure figcaption { 48 | color: #999; 49 | margin-top: 0.25rem; 50 | text-align: center; 51 | } 52 | hr { 53 | border-color: #ccc; 54 | border-style: solid; 55 | border-width: 1px 0 0; 56 | } 57 | .mce-content-body:not([dir='rtl']) blockquote { 58 | border-left: 2px solid #ccc; 59 | margin-left: 1.5rem; 60 | padding-left: 1rem; 61 | } 62 | .mce-content-body[dir='rtl'] blockquote { 63 | border-right: 2px solid #ccc; 64 | margin-right: 1.5rem; 65 | padding-right: 1rem; 66 | } 67 | -------------------------------------------------------------------------------- /public/tinymce/skins/content/writer/content.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body { 8 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 9 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 10 | line-height: 1.4; 11 | margin: 1rem auto; 12 | max-width: 900px; 13 | } 14 | table { 15 | border-collapse: collapse; 16 | } 17 | table:not([cellpadding]) td, 18 | table:not([cellpadding]) th { 19 | padding: 0.4rem; 20 | } 21 | table[border]:not([border='0']):not([style*='border-width']) td, 22 | table[border]:not([border='0']):not([style*='border-width']) th { 23 | border-width: 1px; 24 | } 25 | table[border]:not([border='0']):not([style*='border-style']) td, 26 | table[border]:not([border='0']):not([style*='border-style']) th { 27 | border-style: solid; 28 | } 29 | table[border]:not([border='0']):not([style*='border-color']) td, 30 | table[border]:not([border='0']):not([style*='border-color']) th { 31 | border-color: #ccc; 32 | } 33 | figure { 34 | display: table; 35 | margin: 1rem auto; 36 | } 37 | figure figcaption { 38 | color: #999; 39 | display: block; 40 | margin-top: 0.25rem; 41 | text-align: center; 42 | } 43 | hr { 44 | border-color: #ccc; 45 | border-style: solid; 46 | border-width: 1px 0 0; 47 | } 48 | code { 49 | background-color: #e8e8e8; 50 | border-radius: 3px; 51 | padding: 0.1rem 0.2rem; 52 | } 53 | .mce-content-body:not([dir='rtl']) blockquote { 54 | border-left: 2px solid #ccc; 55 | margin-left: 1.5rem; 56 | padding-left: 1rem; 57 | } 58 | .mce-content-body[dir='rtl'] blockquote { 59 | border-right: 2px solid #ccc; 60 | margin-right: 1.5rem; 61 | padding-right: 1rem; 62 | } 63 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide-dark/content.mobile.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection { 8 | background-color: green; 9 | display: inline-block; 10 | opacity: 0.5; 11 | position: absolute; 12 | } 13 | body { 14 | text-size-adjust: none; 15 | } 16 | body img { 17 | max-width: 96vw; 18 | } 19 | body table img { 20 | max-width: 95%; 21 | } 22 | body { 23 | font-family: sans-serif; 24 | } 25 | table { 26 | border-collapse: collapse; 27 | } 28 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide-dark/fonts/tinymce-mobile.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarkcloudio/quark-ui/8dc37e388c16feadfb71d9727fcd219f4fc87194/public/tinymce/skins/ui/oxide-dark/fonts/tinymce-mobile.woff -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide-dark/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body.tox-dialog__disable-scroll { 8 | overflow: hidden; 9 | } 10 | .tox-fullscreen { 11 | border: 0; 12 | height: 100%; 13 | margin: 0; 14 | overflow: hidden; 15 | -ms-scroll-chaining: none; 16 | overscroll-behavior: none; 17 | padding: 0; 18 | touch-action: pinch-zoom; 19 | width: 100%; 20 | } 21 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 22 | display: none; 23 | } 24 | .tox-shadowhost.tox-fullscreen, 25 | .tox.tox-tinymce.tox-fullscreen { 26 | left: 0; 27 | position: fixed; 28 | top: 0; 29 | z-index: 1200; 30 | } 31 | .tox.tox-tinymce.tox-fullscreen { 32 | background-color: transparent; 33 | } 34 | .tox-fullscreen .tox.tox-tinymce-aux, 35 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 36 | z-index: 1201; 37 | } 38 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide/content.mobile.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | .tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection { 8 | background-color: green; 9 | display: inline-block; 10 | opacity: 0.5; 11 | position: absolute; 12 | } 13 | body { 14 | text-size-adjust: none; 15 | } 16 | body img { 17 | max-width: 96vw; 18 | } 19 | body table img { 20 | max-width: 95%; 21 | } 22 | body { 23 | font-family: sans-serif; 24 | } 25 | table { 26 | border-collapse: collapse; 27 | } 28 | -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarkcloudio/quark-ui/8dc37e388c16feadfb71d9727fcd219f4fc87194/public/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff -------------------------------------------------------------------------------- /public/tinymce/skins/ui/oxide/skin.shadowdom.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Tiny Technologies, Inc. All rights reserved. 3 | * Licensed under the LGPL or a commercial license. 4 | * For LGPL see License.txt in the project root for license information. 5 | * For commercial licenses see https://www.tiny.cloud/ 6 | */ 7 | body.tox-dialog__disable-scroll { 8 | overflow: hidden; 9 | } 10 | .tox-fullscreen { 11 | border: 0; 12 | height: 100%; 13 | margin: 0; 14 | overflow: hidden; 15 | -ms-scroll-chaining: none; 16 | overscroll-behavior: none; 17 | padding: 0; 18 | touch-action: pinch-zoom; 19 | width: 100%; 20 | } 21 | .tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle { 22 | display: none; 23 | } 24 | .tox-shadowhost.tox-fullscreen, 25 | .tox.tox-tinymce.tox-fullscreen { 26 | left: 0; 27 | position: fixed; 28 | top: 0; 29 | z-index: 1200; 30 | } 31 | .tox.tox-tinymce.tox-fullscreen { 32 | background-color: transparent; 33 | } 34 | .tox-fullscreen .tox.tox-tinymce-aux, 35 | .tox-fullscreen ~ .tox.tox-tinymce-aux { 36 | z-index: 1201; 37 | } 38 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import { requestConfig } from './requestConfig'; 2 | import { Base64 } from 'js-base64'; 3 | 4 | type AccountInfoType = 5 | | { 6 | avatar?: string; 7 | nickname?: string; 8 | } 9 | | undefined; 10 | 11 | export interface InitialStateProps { 12 | accountInfo?: AccountInfoType; 13 | getAccountInfo?: () => AccountInfoType; 14 | } 15 | 16 | // 全局初始化数据配置,用于 Layout 用户信息和权限初始化 17 | // 更多信息见文档:https://next.umijs.org/docs/api/runtime-config#getinitialstate 18 | export async function getInitialState(): Promise { 19 | const getAccountInfo = () => { 20 | const token = localStorage.getItem(process.env.UMI_APP_TOKEN ?? 'token'); 21 | if (token) { 22 | // 返回解析的用户信息 23 | return JSON.parse(Base64.decode(token.split('.')[1])); 24 | } 25 | 26 | return undefined; 27 | }; 28 | 29 | const token = localStorage.getItem(process.env.UMI_APP_TOKEN ?? 'token'); 30 | if (token) { 31 | const accountInfo = getAccountInfo(); 32 | return { accountInfo: accountInfo, getAccountInfo }; 33 | } 34 | 35 | return { getAccountInfo }; 36 | } 37 | 38 | export const layout = () => { 39 | return { 40 | logo: 'https://img.alicdn.com/tfs/TB1YHEpwUT1gK0jSZFhXXaAtVXa-28-27.svg', 41 | menu: { 42 | locale: false, 43 | }, 44 | }; 45 | }; 46 | 47 | /** 48 | * @name request 配置,可以配置错误处理 49 | * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。 50 | * @doc https://umijs.org/docs/max/request#配置 51 | */ 52 | export const request = { 53 | ...requestConfig, 54 | }; 55 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarkcloudio/quark-ui/8dc37e388c16feadfb71d9727fcd219f4fc87194/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarkcloudio/quark-ui/8dc37e388c16feadfb71d9727fcd219f4fc87194/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/AMap/AutoComplete.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | 3 | declare const window: any; 4 | 5 | export interface AutocompleteProps { 6 | __map__?: any; 7 | options: any; 8 | placeholder?: any; 9 | onSelect: Function; 10 | style?: any; 11 | } 12 | 13 | const Autocomplete: React.FC & AutocompleteProps> = (props) => { 14 | const { __map__, options, placeholder, onSelect, style } = { ...props }; 15 | const [geoloc, setGeoloc]:any = useState({}); 16 | const [auto, setAuto]:any = useState({}); 17 | 18 | useEffect(() => { 19 | loadWithOptions(); 20 | }, []); 21 | 22 | // 加载配置 23 | const loadWithOptions = ()=>{ 24 | const map = __map__; 25 | if (!map) { 26 | console.log('组件必须作为 Map 的子组件使用'); 27 | return; 28 | } 29 | if(!props.options){ 30 | console.log('必须指定Autocomplete插件的配置参数'); 31 | return; 32 | } 33 | if(typeof props.onSelect !== 'function') { 34 | console.log('必须指定onSelect回调函数'); 35 | return; 36 | } 37 | map.plugin(['AMap.Autocomplete', 'AMap.Geocoder'], () => { 38 | let opts ={input: 'autoinput'}; 39 | Object.assign(opts, options); 40 | const getAuto = new window.AMap.Autocomplete(opts); 41 | map.addControl(getAuto); 42 | window.AMap.event.addListener(getAuto, "select", onSelectChange); // 注册监听,当选中某条记录时会触发 43 | const getGeoloc = new window.AMap.Geocoder({}); 44 | map.addControl(getGeoloc); 45 | setGeoloc(getGeoloc) 46 | setAuto(getAuto) 47 | }); 48 | } 49 | 50 | const onSelectChange =(e:any) => { 51 | if(!e.poi.location) { 52 | const poi = e.poi; 53 | geoloc && geoloc.getLocation(poi.district + poi.address, (status:any, result:any) =>{ 54 | if(result && result.info === "OK"){ 55 | e.poi.location = result.geocodes[0].location; 56 | } 57 | onSelect(e); 58 | }) 59 | return ; 60 | } 61 | onSelect(e); 62 | } 63 | 64 | return ( 65 | 66 | ); 67 | }; 68 | 69 | export default Autocomplete; 70 | -------------------------------------------------------------------------------- /src/components/Action/Item/Back.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { history } from '@umijs/max'; 3 | import { Button, Popconfirm, Modal as AntModal } from 'antd'; 4 | import { 5 | ExclamationCircleOutlined, 6 | createFromIconfontCN, 7 | } from '@ant-design/icons'; 8 | import tplEngine from '@/utils/template'; 9 | 10 | const Back: React.FC = (props) => { 11 | const [modal, contextHolder] = AntModal.useModal(); 12 | const IconFont = createFromIconfontCN({ 13 | scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js', 14 | }); 15 | const { confirm } = modal; 16 | 17 | // 确认弹框 18 | const showConfirm = async () => { 19 | confirm({ 20 | title: tplEngine(props.confirmTitle, props.data), 21 | icon: , 22 | content: tplEngine(props.confirmText, props.data), 23 | onOk() { 24 | history.go(-1); 25 | }, 26 | }); 27 | }; 28 | 29 | const label = tplEngine(props.label, props.data); 30 | if (!label || label === 'false') { 31 | return; 32 | } 33 | 34 | let component = ( 35 | 55 | ); 56 | 57 | if (props.confirmType === 'pop') { 58 | component = ( 59 | { 63 | history.go(-1); 64 | }} 65 | > 66 | 79 | 80 | ); 81 | } 82 | 83 | return ( 84 | <> 85 | {contextHolder} 86 | {component} 87 | 88 | ); 89 | }; 90 | 91 | export default Back; 92 | -------------------------------------------------------------------------------- /src/components/Action/Item/Cancel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Popconfirm, Modal as AntModal } from 'antd'; 3 | import { 4 | ExclamationCircleOutlined, 5 | createFromIconfontCN, 6 | } from '@ant-design/icons'; 7 | import tplEngine from '@/utils/template'; 8 | 9 | const Cancel: React.FC = (props) => { 10 | const [modal, contextHolder] = AntModal.useModal(); 11 | const IconFont = createFromIconfontCN({ 12 | scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js', 13 | }); 14 | const { confirm } = modal; 15 | 16 | // 确认弹框 17 | const showConfirm = async () => { 18 | confirm({ 19 | title: tplEngine(props.confirmTitle, props.data), 20 | icon: , 21 | content: tplEngine(props.confirmText, props.data), 22 | onOk() { 23 | props?.callback?.(); 24 | }, 25 | }); 26 | }; 27 | 28 | const label = tplEngine(props.label, props.data); 29 | if (!label || label === 'false') { 30 | return; 31 | } 32 | 33 | let component = ( 34 | 54 | ); 55 | 56 | if (props.confirmType === 'pop') { 57 | component = ( 58 | { 62 | props?.callback?.(); 63 | }} 64 | > 65 | 78 | 79 | ); 80 | } 81 | 82 | return ( 83 | <> 84 | {contextHolder} 85 | {component} 86 | 87 | ); 88 | }; 89 | 90 | export default Cancel; 91 | -------------------------------------------------------------------------------- /src/components/Action/Item/Js.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Popconfirm, Modal as AntModal } from 'antd'; 3 | import { 4 | ExclamationCircleOutlined, 5 | createFromIconfontCN, 6 | } from '@ant-design/icons'; 7 | import tplEngine from '@/utils/template'; 8 | 9 | const Js: React.FC = (props) => { 10 | const [modal, contextHolder] = AntModal.useModal(); 11 | const IconFont = createFromIconfontCN({ 12 | scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js', 13 | }); 14 | const { confirm } = modal; 15 | 16 | // 确认弹框 17 | const showConfirm = async () => { 18 | confirm({ 19 | title: tplEngine(props.confirmTitle, props.data), 20 | icon: , 21 | content: tplEngine(props.confirmText, props.data), 22 | onOk() { 23 | // eslint-disable-next-line 24 | eval(props.js); 25 | }, 26 | }); 27 | }; 28 | 29 | const label = tplEngine(props.label, props.data); 30 | if (!label || label === 'false') { 31 | return; 32 | } 33 | 34 | let component = ( 35 | 56 | ); 57 | 58 | if (props.confirmType === 'pop') { 59 | component = ( 60 | { 64 | // eslint-disable-next-line 65 | eval(props.js); 66 | }} 67 | > 68 | 81 | 82 | ); 83 | } 84 | 85 | return ( 86 | <> 87 | {contextHolder} 88 | {component} 89 | 90 | ); 91 | }; 92 | 93 | export default Js; 94 | -------------------------------------------------------------------------------- /src/components/Action/Item/Link.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import { createFromIconfontCN } from '@ant-design/icons'; 4 | import tplEngine from '@/utils/template'; 5 | 6 | const Link: React.FC = (props) => { 7 | const IconFont = createFromIconfontCN({ 8 | scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js', 9 | }); 10 | const label = tplEngine(props.label, props.data); 11 | if (!label || label === 'false') { 12 | return; 13 | } 14 | 15 | return ( 16 | 35 | ); 36 | }; 37 | 38 | export default Link; 39 | -------------------------------------------------------------------------------- /src/components/Action/Item/Reset.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useModel } from '@umijs/max'; 3 | import { Button, Popconfirm, Modal as AntModal } from 'antd'; 4 | import { 5 | ExclamationCircleOutlined, 6 | createFromIconfontCN, 7 | } from '@ant-design/icons'; 8 | import tplEngine from '@/utils/template'; 9 | 10 | const Reset: React.FC = (props) => { 11 | const [modal, contextHolder] = AntModal.useModal(); 12 | const IconFont = createFromIconfontCN({ 13 | scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js', 14 | }); 15 | let { object } = useModel('object'); 16 | const formKey = props.submitForm ? props.submitForm : 'form'; 17 | const { confirm } = modal; 18 | 19 | // 确认弹框 20 | const showConfirm = async () => { 21 | confirm({ 22 | title: tplEngine(props.confirmTitle, props.data), 23 | icon: , 24 | content: tplEngine(props.confirmText, props.data), 25 | onOk() { 26 | object[formKey]?.current?.resetFields?.(); 27 | }, 28 | }); 29 | }; 30 | 31 | const label = tplEngine(props.label, props.data); 32 | if (!label || label === 'false') { 33 | return; 34 | } 35 | 36 | let component = ( 37 | 57 | ); 58 | 59 | if (props.confirmType === 'pop') { 60 | component = ( 61 | { 65 | object[formKey]?.current?.resetFields?.(); 66 | }} 67 | > 68 | 81 | 82 | ); 83 | } 84 | 85 | return ( 86 | <> 87 | {contextHolder} 88 | {component} 89 | 90 | ); 91 | }; 92 | 93 | export default Reset; 94 | -------------------------------------------------------------------------------- /src/components/Action/Item/Step.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { history, useModel } from '@umijs/max'; 3 | import { Button, Space } from 'antd'; 4 | import { 5 | ExclamationCircleOutlined, 6 | createFromIconfontCN, 7 | } from '@ant-design/icons'; 8 | import tplEngine from '@/utils/template'; 9 | 10 | const Step: React.FC = (props) => { 11 | const IconFont = createFromIconfontCN({ 12 | scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js', 13 | }); 14 | const { tabs, setTabs } = useModel('tabs'); 15 | 16 | return ( 17 | 18 | 34 | 50 | 51 | ); 52 | }; 53 | 54 | export default Step; 55 | -------------------------------------------------------------------------------- /src/components/Action/Item/Submit.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useModel } from '@umijs/max'; 3 | import { Button, Popconfirm, Modal as AntModal } from 'antd'; 4 | import { 5 | ExclamationCircleOutlined, 6 | createFromIconfontCN, 7 | } from '@ant-design/icons'; 8 | import tplEngine from '@/utils/template'; 9 | import reload from '@/utils/reload'; 10 | 11 | const Action: React.FC = (props) => { 12 | const [modal, contextHolder] = AntModal.useModal(); 13 | const { buttonLoadings, setButtonLoadings } = useModel('buttonLoading'); 14 | const IconFont = createFromIconfontCN({ 15 | scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js', 16 | }); 17 | let { object } = useModel('object'); 18 | let { submit } = useModel('submit'); 19 | const [random, setRandom] = useState(0); // hack 20 | const formKey = props.submitForm ? props.submitForm : 'form'; 21 | const { confirm } = modal; 22 | 23 | // 确认弹框 24 | const showConfirm = async () => { 25 | confirm({ 26 | title: tplEngine(props.confirmTitle, props.data), 27 | icon: , 28 | content: tplEngine(props.confirmText, props.data), 29 | onOk: () => { 30 | handle(); 31 | }, 32 | }); 33 | }; 34 | 35 | // 提交表单 36 | const handle = async () => { 37 | // 设置按钮提交状态 38 | buttonLoadings[formKey] = true; 39 | setButtonLoadings(buttonLoadings); 40 | setRandom(Math.random); 41 | 42 | // 提交表单 43 | object[formKey]?.current 44 | ?.validateFieldsReturnFormatValue() 45 | ?.then(async (values: any) => { 46 | await submit[formKey]?.(values); 47 | if (props.reload) { 48 | if (props.reload === 'window') { 49 | reload(); 50 | } else { 51 | object[props.reload]?.current?.reload(); 52 | } 53 | } 54 | }) 55 | .catch((errorInfo: any) => { 56 | // 重置按钮提交状态 57 | buttonLoadings[formKey] = false; 58 | setButtonLoadings(buttonLoadings); 59 | setRandom(Math.random); 60 | }); 61 | }; 62 | 63 | const label = tplEngine(props.label, props.data); 64 | if (!label || label === 'false') { 65 | return; 66 | } 67 | 68 | let component = ( 69 | 90 | ); 91 | 92 | if (props.confirmType === 'pop') { 93 | component = ( 94 | { 98 | handle(); 99 | }} 100 | > 101 | 115 | 116 | ); 117 | } 118 | 119 | return ( 120 | <> 121 | {contextHolder} 122 | {component} 123 | 124 | ); 125 | }; 126 | 127 | export default Action; 128 | -------------------------------------------------------------------------------- /src/components/Action/Item/Switch.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { history, useModel } from '@umijs/max'; 3 | import { Switch as AntSwitch, message, Modal as AntModal } from 'antd'; 4 | import { 5 | ExclamationCircleOutlined, 6 | createFromIconfontCN, 7 | } from '@ant-design/icons'; 8 | import { get } from '@/services/action'; 9 | import tplEngine from '@/utils/template'; 10 | import reload from '@/utils/reload'; 11 | import Render from '@/components/Render'; 12 | import qs from 'query-string'; 13 | 14 | const Switch: React.FC = (props) => { 15 | const [modal, contextHolder] = AntModal.useModal(); 16 | const { buttonLoadings, setButtonLoadings } = useModel('buttonLoading'); 17 | const [random, setRandom] = useState(0); // hack 18 | const [submitResult, setSubmitResult] = useState(null); 19 | const IconFont = createFromIconfontCN({ 20 | scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js', 21 | }); 22 | let { object } = useModel('object'); 23 | const { confirm } = modal; 24 | 25 | // 确认弹框 26 | const showConfirm = async (api: any, checked: boolean) => { 27 | confirm({ 28 | title: tplEngine(props.confirmTitle, props.data), 29 | icon: , 30 | content: tplEngine(props.confirmText, props.data), 31 | onOk() { 32 | handle(api, checked); 33 | }, 34 | }); 35 | }; 36 | 37 | // 执行行为 38 | const handle = async (api: string, checked: boolean) => { 39 | buttonLoadings[props.componentkey] = true; 40 | setButtonLoadings(buttonLoadings); 41 | setRandom(Math.random); 42 | 43 | const result = await get({ 44 | url: tplEngine(api, props.data) + `&${props.fieldName}=${checked}`, 45 | }); 46 | 47 | buttonLoadings[props.componentkey] = false; 48 | setButtonLoadings(buttonLoadings); 49 | setRandom(Math.random); 50 | 51 | if (result.component === 'message') { 52 | if (result.type === 'error') { 53 | message.error(result.content); 54 | return; 55 | } 56 | if (props.callback) { 57 | props.callback(); 58 | } 59 | if (result.content) { 60 | message.success(result.content); 61 | } 62 | if (result.url) { 63 | const returnUrl = tplEngine(result.url, props.data); 64 | if (returnUrl === 'reload') { 65 | reload(); 66 | return; 67 | } 68 | if (returnUrl?.indexOf('http') !== -1) { 69 | const values: any['token'] = localStorage.getItem( 70 | process.env.UMI_APP_TOKEN ?? 'token', 71 | ); 72 | window.open(`${returnUrl}?${qs.stringify(values)}`); 73 | } else { 74 | history.push(result.url); 75 | } 76 | } 77 | if (props.redirect) { 78 | const redirectUrl = tplEngine(props.redirect, props.data); 79 | if (redirectUrl === 'reload') { 80 | reload(); 81 | return; 82 | } 83 | if (redirectUrl?.indexOf('http') !== -1) { 84 | const values: any['token'] = localStorage.getItem( 85 | process.env.UMI_APP_TOKEN ?? 'token', 86 | ); 87 | window.open(`${redirectUrl}?${qs.stringify(values)}`); 88 | } else { 89 | history.push(redirectUrl); 90 | } 91 | } 92 | if (props.reload) { 93 | if (props.reload === 'window') { 94 | reload(); 95 | } else { 96 | object[props.reload]?.current?.reload(); 97 | } 98 | } 99 | return; 100 | } 101 | 102 | setSubmitResult(result); 103 | }; 104 | 105 | let component = ( 106 | { 117 | void (props.confirmTitle 118 | ? showConfirm(props.api, checked) 119 | : handle(props.api, checked)); 120 | }} 121 | /> 122 | ); 123 | 124 | if (submitResult) { 125 | return ( 126 | <> 127 | {contextHolder} 128 | {component} 129 | {submitResult && ( 130 | 135 | )} 136 | 137 | ); 138 | } 139 | 140 | return ( 141 | <> 142 | {contextHolder} 143 | {component} 144 | 145 | ); 146 | }; 147 | 148 | export default Switch; 149 | -------------------------------------------------------------------------------- /src/components/Action/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import { createFromIconfontCN } from '@ant-design/icons'; 4 | import tplEngine from '@/utils/template'; 5 | import Modal from '@/components/Modal'; 6 | import Drawer from '@/components/Drawer'; 7 | import Ajax from '@/components/Action/Item/Ajax'; 8 | import Back from '@/components/Action/Item/Back'; 9 | import Cancel from '@/components/Action/Item/Cancel'; 10 | import Js from '@/components/Action/Item/Js'; 11 | import Link from '@/components/Action/Item/Link'; 12 | import Reset from '@/components/Action/Item/Reset'; 13 | import Submit from '@/components/Action/Item/Submit'; 14 | import Step from '@/components/Action/Item/Step'; 15 | import Switch from '@/components/Action/Item/Switch'; 16 | 17 | const Action: React.FC = (props) => { 18 | const IconFont = createFromIconfontCN({ 19 | scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js', 20 | }); 21 | let component; 22 | 23 | if (props.actionType==='switch') { 24 | return ; 25 | } 26 | 27 | // 如果label为空,直接返回 28 | if ( 29 | !tplEngine(props.label, props.data) || 30 | tplEngine(props.label, props.data) === '' 31 | ) { 32 | return component; 33 | } 34 | 35 | // 解析行为 36 | switch (props.actionType) { 37 | case 'js': 38 | component = ; 39 | break; 40 | case 'ajax': 41 | component = ( 42 | 43 | ); 44 | break; 45 | case 'submit': 46 | component = ( 47 | 48 | ); 49 | break; 50 | case 'reset': 51 | component = ( 52 | 53 | ); 54 | break; 55 | case 'cancel': 56 | component = ( 57 | 58 | ); 59 | break; 60 | case 'back': 61 | component = ( 62 | 63 | ); 64 | break; 65 | case 'link': 66 | component = ( 67 | 68 | ); 69 | break; 70 | case 'step': 71 | component = ( 72 | 73 | ); 74 | break; 75 | case 'modal': 76 | component = ( 77 | 78 | ); 79 | break; 80 | case 'drawer': 81 | component = ( 82 | 83 | ); 84 | break; 85 | default: 86 | component = ( 87 | 100 | ); 101 | break; 102 | } 103 | 104 | return component; 105 | }; 106 | 107 | export default Action; 108 | -------------------------------------------------------------------------------- /src/components/Chart/Line.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Line as AntLine } from '@ant-design/charts'; 3 | import { get } from '@/services/action'; 4 | 5 | const Line: React.FC = (props: any) => { 6 | const [data, setData] = useState(props.data); 7 | 8 | useEffect(() => { 9 | getData(); 10 | }, []); 11 | 12 | const getData = async () => { 13 | if (props.api) { 14 | const result = await get({ 15 | actionUrl: props.api, 16 | }); 17 | 18 | if (result) { 19 | setData(result.data); 20 | } 21 | } 22 | }; 23 | 24 | return ; 25 | }; 26 | 27 | export default Line; 28 | -------------------------------------------------------------------------------- /src/components/Chart/index.tsx: -------------------------------------------------------------------------------- 1 | import Line from './Line'; 2 | 3 | export { Line }; 4 | -------------------------------------------------------------------------------- /src/components/Col/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Render from '@/components/Render'; 3 | import { Col as AntCol, ColProps } from 'antd'; 4 | 5 | export interface ColExtendProps { 6 | body?: any; 7 | data?: any; 8 | callback?: any; 9 | } 10 | 11 | const Col: React.FC< 12 | ColProps & React.RefAttributes & ColExtendProps 13 | > = (props) => { 14 | const { body, data, callback } = { ...props }; 15 | return ( 16 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default Col; 23 | -------------------------------------------------------------------------------- /src/components/Container/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Render from '@/components/Render'; 3 | 4 | const Container: React.FC = (props: any) => { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | }; 11 | 12 | export default Container; 13 | -------------------------------------------------------------------------------- /src/components/Descriptions/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState, useRef } from 'react'; 2 | import { Space, Divider, Spin } from 'antd'; 3 | import { ProDescriptions } from '@ant-design/pro-components'; 4 | import { get } from '@/services/action'; 5 | import Action from '@/components/Action'; 6 | import Render from '@/components/Render'; 7 | import tplEngine from '@/utils/template'; 8 | 9 | export interface DescriptionsProps { 10 | title?: any; 11 | tooltip?: any; 12 | extra?: any; 13 | bordered?: boolean; 14 | column?: any; 15 | size?: 'middle' | 'small' | 'default'; 16 | layout?: 'horizontal' | 'vertical'; 17 | colon?: boolean; 18 | columns?: any; 19 | initApi?: any; 20 | items?: any; 21 | actions?: any; 22 | dataSource?: any; 23 | data?: any; 24 | callback?: any; 25 | } 26 | 27 | const defaultProps = { 28 | column: 2, 29 | layout: 'horizontal', 30 | colon: true, 31 | } as DescriptionsProps; 32 | 33 | const Descriptions: React.FC = (props) => { 34 | const { 35 | title, 36 | tooltip, 37 | extra, 38 | bordered, 39 | column, 40 | size, 41 | layout, 42 | colon, 43 | columns, 44 | items, 45 | actions, 46 | initApi, 47 | data, 48 | callback, 49 | } = { 50 | ...defaultProps, 51 | ...props, 52 | }; 53 | const [spinning, setLoading] = useState(false); 54 | const [random, setRandom] = useState(0); // hack 55 | const [dataSource, setDataSource] = useState(props.dataSource); // 初始化表单数据 56 | 57 | useEffect(() => { 58 | setInitialValues(); 59 | }, [columns, initApi, items]); 60 | 61 | const parseColumns = (columns: any) => { 62 | columns.forEach((item: any, key: any) => { 63 | if (item.valueType === 'text') { 64 | item.render = (text: any, row: any) => { 65 | if (typeof text === 'string' || typeof text === 'number') { 66 | return ; 67 | } 68 | return text; 69 | }; 70 | } 71 | if (item.valueType === 'action') { 72 | item.render = (text: any, row: any) => { 73 | return ; 74 | }; 75 | } 76 | columns[key] = item; 77 | }); 78 | return columns; 79 | }; 80 | 81 | const setInitialValues = async () => { 82 | setLoading(true); 83 | // 从接口获取初始值 84 | if (initApi) { 85 | let result = await get({ 86 | url: tplEngine(initApi, data), 87 | }); 88 | setDataSource(result.data); 89 | } 90 | // 更新组件状态 91 | setRandom(Math.random); 92 | setLoading(false); 93 | }; 94 | 95 | return ( 96 | 97 | 109 | {items?.map((item: any, index: number) => { 110 | return ( 111 | 121 | 125 | 126 | ); 127 | })} 128 | 129 | {actions?.length > 0 && ( 130 | <> 131 | 132 |
133 | 134 | {actions?.map((action: any, index: number) => { 135 | return ( 136 | 142 | ); 143 | })} 144 | 145 |
146 | 147 | )} 148 |
149 | ); 150 | }; 151 | 152 | export default Descriptions; 153 | -------------------------------------------------------------------------------- /src/components/Divider/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Render from '@/components/Render'; 3 | import { Divider as AntDivider } from 'antd'; 4 | 5 | const Divider: React.FC = (props: any) => { 6 | return ( 7 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default Divider; 20 | -------------------------------------------------------------------------------- /src/components/Drawer/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Drawer as AntDrawer, Button, Space } from 'antd'; 3 | import { createFromIconfontCN } from '@ant-design/icons'; 4 | import { get } from '@/services/action'; 5 | import Action from '@/components/Action'; 6 | import Render from '@/components/Render'; 7 | import tplEngine from '@/utils/template'; 8 | 9 | const Drawer: React.FC = (props: any) => { 10 | const IconFont = createFromIconfontCN({ scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js' }); 11 | const [open, setOpen] = useState(props.drawer.open); 12 | const [data, setData] = useState(props.data); // 初始化表单数据 13 | 14 | const setInitialData = async () => { 15 | // 从接口获取初始值 16 | if (props.initApi) { 17 | let result = await get({ 18 | url: tplEngine(props.initApi, props.data), 19 | }); 20 | setData(result.data); 21 | } 22 | }; 23 | 24 | return ( 25 | <> 26 | 42 | setOpen(false)} 47 | footer={ 48 | props?.drawer?.actions && ( 49 | 50 | {props.drawer.actions?.map((action: any, index: number) => { 51 | return ( 52 | setOpen(false)} 57 | /> 58 | ); 59 | })} 60 | 61 | ) 62 | } 63 | > 64 | setOpen(false)} 68 | /> 69 | 70 | 71 | ); 72 | }; 73 | 74 | export default Drawer; 75 | -------------------------------------------------------------------------------- /src/components/Dropdown/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { MenuProps } from 'antd'; 3 | import { Dropdown as BaseDropdown, Button, ConfigProvider } from 'antd'; 4 | import Action from '@/components/Action'; 5 | import tplEngine from '@/utils/template'; 6 | import { DownOutlined, createFromIconfontCN } from '@ant-design/icons'; 7 | 8 | const IconFont = createFromIconfontCN({ 9 | scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js', 10 | }); 11 | 12 | type MenuItem = Required['items'][number]; 13 | 14 | function getItem( 15 | label: React.ReactNode, 16 | key: React.Key, 17 | icon?: React.ReactNode, 18 | children?: MenuItem[], 19 | type?: 'group', 20 | ): MenuItem { 21 | return { 22 | key, 23 | icon, 24 | children, 25 | label, 26 | type, 27 | } as MenuItem; 28 | } 29 | 30 | const Dropdown: React.FC = (props: any) => { 31 | // 渲染组件 32 | const menuItemRender = (item: any, index: number) => { 33 | let component = null; 34 | if (item.component === 'menuSubMenu') { 35 | component = getItem( 36 | item.title, 37 | index, 38 | props.icon && , 39 | [...componentRender(item.items)], 40 | ); 41 | } 42 | if (item.component === 'menuItemGroup') { 43 | component = getItem( 44 | item.title, 45 | index, 46 | null, 47 | [...componentRender(item.items)], 48 | 'group', 49 | ); 50 | } 51 | if (item.component === 'menuItem' || item.component === 'action') { 52 | if ( 53 | !tplEngine(item.label, props.data) || 54 | tplEngine(item.label, props.data) === 'false' 55 | ) { 56 | return; 57 | } 58 | const action = ( 59 | 72 | 77 | 78 | ); 79 | component = getItem( 80 | action, 81 | index, 82 | props.icon && , 83 | ); 84 | } 85 | if (item.component === 'menuDivider') { 86 | component = { type: 'divider' }; 87 | } 88 | 89 | return component; 90 | }; 91 | 92 | // 渲染组件 93 | const componentRender = (items: any) => { 94 | return items.map((item: any, index: number) => { 95 | return menuItemRender(item, index); 96 | }); 97 | }; 98 | 99 | return ( 100 | 117 | 131 | 132 | ); 133 | }; 134 | 135 | export default Dropdown; 136 | -------------------------------------------------------------------------------- /src/components/Engine/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useLocation, useModel } from '@umijs/max'; 3 | import { get } from '@/services/action'; 4 | import Render from '@/components/Render'; 5 | import qs from 'query-string'; 6 | 7 | export interface EngineProps { 8 | api: string; 9 | } 10 | 11 | const defaultProps = { 12 | api: '', 13 | } as EngineProps; 14 | 15 | const Engine: React.FC = (props) => { 16 | const location = useLocation(); 17 | const query = qs.parse(location.search); 18 | const { api } = { ...defaultProps, ...props }; 19 | const [components, setComponents] = useState(''); 20 | const { setPageLoading } = useModel('pageLoading'); 21 | 22 | const getComponents = async () => { 23 | if (!api) { 24 | setComponents('The initialization API cannot be null!'); 25 | return; 26 | } 27 | 28 | let data: any = {}; 29 | Object.keys(query).forEach((key) => { 30 | if (key !== 'api') { 31 | data[key] = query[key]; 32 | } 33 | }); 34 | 35 | // 设置页面loading状态 36 | setPageLoading(true); 37 | const result = await get({ 38 | url: api, 39 | data: data, 40 | }); 41 | 42 | // 设置组件 43 | setComponents(result); 44 | 45 | // 取消页面loading状态 46 | setPageLoading(false); 47 | }; 48 | 49 | useEffect(() => { 50 | getComponents(); 51 | }, [api, query.timestamp]); 52 | 53 | return ; 54 | }; 55 | 56 | export default Engine; 57 | -------------------------------------------------------------------------------- /src/components/Form/Field/Cascader.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Cascader as AntCascader, Spin } from 'antd'; 3 | import { get } from '@/services/action'; 4 | 5 | export interface CascaderProps { 6 | api?: any; 7 | size?: any; 8 | value?: any; 9 | placeholder?: any; 10 | style?: any; 11 | options?: any; 12 | allowClear?: any; 13 | onChange?: (value: any) => void; 14 | } 15 | 16 | const Cascader: React.FC = ({ 17 | api = null, 18 | size = undefined, 19 | value = null, 20 | placeholder = null, 21 | style = [], 22 | options = [], 23 | allowClear = false, 24 | onChange, 25 | }) => { 26 | const [selectOptions, setSelectOptions] = useState(options); 27 | const [level, setLevel] = useState(0); 28 | const [spinning, setSpinning] = useState(true); 29 | const [random, setRandom] = useState(0); 30 | 31 | useEffect(() => { 32 | initOptions(); 33 | }, []); 34 | 35 | useEffect(() => { 36 | initValueOptions(); 37 | }, [value]); 38 | 39 | const initOptions = async () => { 40 | setSpinning(true); 41 | if (api) { 42 | const getOptions = await loadOptions(); 43 | setSelectOptions(getOptions); 44 | } 45 | setSpinning(false); 46 | }; 47 | 48 | const initValueOptions = async () => { 49 | setSpinning(true); 50 | if (api && value && level < value.length - 1) { 51 | const getOptions = await loadOptions(); 52 | setSelectOptions(getOptions); 53 | } 54 | setSpinning(false); 55 | }; 56 | 57 | const loadOptions = async (level: any = 0) => { 58 | const result = await get({ 59 | url: api, 60 | data: { 61 | search: level === 0 ? 0 : value[level - 1], 62 | level: level, 63 | }, 64 | }); 65 | 66 | let data = result.data; 67 | setLevel(level); 68 | if (!value) { 69 | return data; 70 | } 71 | if (level >= value.length) { 72 | return []; 73 | } 74 | 75 | await Promise.all( 76 | data?.map(async (item: any) => { 77 | if (item.value === value[level]) { 78 | let children = await loadOptions(level + 1); 79 | if (children.length > 0) { 80 | return (item.children = children); 81 | } 82 | } 83 | }), 84 | ); 85 | 86 | return data; 87 | }; 88 | 89 | const triggerChange = (changedValue: any) => { 90 | if (onChange) { 91 | onChange(changedValue); 92 | } 93 | }; 94 | 95 | const onSelectChange = (value: any) => { 96 | triggerChange(value); 97 | }; 98 | 99 | const loadData = (selectedOptions: any) => { 100 | const targetOption = selectedOptions[selectedOptions.length - 1]; 101 | targetOption.loading = true; 102 | 103 | setTimeout(async () => { 104 | targetOption.loading = false; 105 | const result = await get({ 106 | url: api, 107 | data: { 108 | search: targetOption.value, 109 | level: selectedOptions.length, 110 | }, 111 | }); 112 | 113 | targetOption.children = result.data; 114 | setSelectOptions([...selectOptions]); 115 | setRandom(Math.random); 116 | }, 300); 117 | }; 118 | 119 | let getSelectOptions = selectOptions || options; 120 | 121 | if (api) { 122 | return ( 123 | 128 | 139 | 140 | ); 141 | } else { 142 | return ( 143 | 152 | ); 153 | } 154 | }; 155 | 156 | export default Cascader; 157 | -------------------------------------------------------------------------------- /src/components/Form/Field/FileUploader.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Upload, message, Button } from 'antd'; 3 | import { UploadOutlined } from '@ant-design/icons'; 4 | 5 | export interface FileUploaderProps { 6 | button: string; 7 | action: string; 8 | limitType: []; 9 | limitSize: number; 10 | limitNum: number; 11 | value?: any; 12 | onChange?: (value: any) => void; 13 | } 14 | 15 | const FileUploader: React.FC = ({ 16 | button, 17 | action, 18 | limitType, 19 | limitSize, 20 | limitNum, 21 | value = null, 22 | onChange, 23 | }) => { 24 | const [getFileList, setGetFileList] = useState(undefined); 25 | 26 | const triggerChange = (changedValue: any) => { 27 | if (onChange) { 28 | onChange(changedValue); 29 | } 30 | }; 31 | 32 | const onFileListChange = (value: any) => { 33 | setGetFileList(value); 34 | let fileList: any = []; 35 | value.forEach((file: any, key: number) => { 36 | let fileInfo = { 37 | uid: null, 38 | id: null, 39 | name: null, 40 | url: null, 41 | size: null, 42 | status: '', 43 | }; 44 | fileInfo.uid = file.uid; 45 | fileInfo.id = file.id; 46 | fileInfo.name = file.name; 47 | fileInfo.url = file.url; 48 | fileInfo.size = file.size; 49 | fileInfo.status = 'done'; 50 | fileList[key] = fileInfo; 51 | }); 52 | triggerChange(fileList); 53 | }; 54 | 55 | const uploadButton = (title: string) => { 56 | return ; 57 | }; 58 | 59 | // 判断是否符合上传条件 60 | const checkUpload = (file: any) => { 61 | let canUpload = false; 62 | 63 | console.log('当前文件类型:', file.type); 64 | for (let i = 0; i < limitType.length; i++) { 65 | if (file.type.indexOf(limitType[i]) !== -1) { 66 | canUpload = true; 67 | } 68 | } 69 | if (!canUpload) { 70 | message.error('请上传正确格式的文件!'); 71 | return false; 72 | } 73 | 74 | const isLtSize = file.size / 1024 / 1024 < limitSize; 75 | if (!isLtSize) { 76 | message.error('文件大小不可超过' + limitSize + 'MB!'); 77 | return false; 78 | } 79 | 80 | return true; 81 | }; 82 | 83 | return ( 84 | { 94 | return checkUpload(file); 95 | }} 96 | onChange={(info: any) => { 97 | let fileList = info.fileList; 98 | 99 | // 限制上传数量 100 | fileList = fileList.slice(-limitNum); 101 | 102 | // 只保存上传成功的数据 103 | fileList = fileList.filter((file: any) => { 104 | if (file.response) { 105 | return file.response.type === 'success'; 106 | } 107 | if (file.status) { 108 | return true; 109 | } else { 110 | return false; 111 | } 112 | }); 113 | 114 | // 重组数据 115 | fileList = fileList.map((file: any, key: number) => { 116 | if (file.response) { 117 | file.id = file.response.data.id; 118 | file.name = file.response.data.name; 119 | file.url = file.response.data.url; 120 | file.size = file.response.data.size; 121 | } 122 | return file; 123 | }); 124 | 125 | onFileListChange(fileList); 126 | }} 127 | > 128 | {uploadButton(button)} 129 | 130 | ); 131 | }; 132 | 133 | export default FileUploader; 134 | -------------------------------------------------------------------------------- /src/components/Form/Field/Icon.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Select, Col } from 'antd'; 3 | import { createFromIconfontCN } from '@ant-design/icons'; 4 | import { ProFormItem } from '@ant-design/pro-components'; 5 | 6 | export interface IconProps { 7 | label?: any; 8 | name?: any; 9 | tooltip?: any; 10 | rules?: any; 11 | help?: any; 12 | extra?: any; 13 | addonAfter?: any; 14 | addonBefore?: any; 15 | wrapperCol?: any; 16 | colProps?: any; 17 | secondary?: any; 18 | fieldProps?: any; 19 | onChange?: (value: any) => void; 20 | } 21 | 22 | const Icon: React.FC = ({ 23 | label = null, 24 | name = undefined, 25 | tooltip = undefined, 26 | rules = undefined, 27 | help = undefined, 28 | extra = undefined, 29 | addonAfter = undefined, 30 | addonBefore = undefined, 31 | wrapperCol = undefined, 32 | colProps = undefined, 33 | secondary = undefined, 34 | fieldProps = undefined, 35 | onChange, 36 | }) => { 37 | const Option = Select.Option; 38 | const IconFont = createFromIconfontCN({ 39 | scriptUrl: '//at.alicdn.com/t/font_1615691_3pgkh5uyob.js', 40 | }); 41 | 42 | const triggerChange = (changedValue: any) => { 43 | if (onChange) { 44 | onChange(changedValue); 45 | } 46 | }; 47 | 48 | const onSelectChange = (value: any) => { 49 | triggerChange(value); 50 | }; 51 | 52 | return ( 53 | 64 | 88 | 89 | ); 90 | }; 91 | 92 | export default Icon; 93 | -------------------------------------------------------------------------------- /src/components/Form/Field/ImageCaptcha.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Input, InputProps, message } from 'antd'; 3 | import { useModel } from '@umijs/max'; 4 | import { get } from '@/services/action'; 5 | 6 | export interface ImageCaptchaProps { 7 | value?: any; 8 | captchaUrl?: any; 9 | captchaIdUrl?: any; 10 | onChange?: (value: any) => void; 11 | } 12 | 13 | const ImageCaptcha: React.FC = (props) => { 14 | const { 15 | value, 16 | captchaIdUrl, 17 | captchaUrl, 18 | placeholder, 19 | style, 20 | width, 21 | size, 22 | maxLength, 23 | prefix, 24 | onChange, 25 | } = { ...props }; 26 | const { object, setObject } = useModel('object'); // 全局对象 27 | const [innerCaptchaUrl, setInnerCaptchaUrl] = useState(captchaUrl); 28 | const [captchaId, setCaptchaId] = useState(''); 29 | useEffect(() => { 30 | refreshCaptcha(); 31 | }, [captchaUrl]); 32 | 33 | // 刷新图形验证码 34 | const refreshCaptcha = async () => { 35 | let captchaId = ''; 36 | let timestamp = new Date().getTime().toString(); 37 | const result = await get({ 38 | url: captchaIdUrl, 39 | }); 40 | if (result.type === 'error') { 41 | message.error(result.content); 42 | return; 43 | } 44 | captchaId = result.data['captchaId']; 45 | setCaptchaId(captchaId); 46 | let getCaptchaUrl = 47 | captchaUrl 48 | .replace(/:id/g, captchaId) 49 | .replace('${id}', captchaId) 50 | .replace('{id}', captchaId) + 51 | '?t=' + 52 | timestamp; 53 | setInnerCaptchaUrl(getCaptchaUrl); 54 | }; 55 | 56 | // 将验证码对象存储到全局 57 | object['refreshCaptcha'] = refreshCaptcha; 58 | setObject(object); 59 | 60 | const triggerChange = (changedValue: { id?: string; value?: any }) => { 61 | onChange?.({ ...value, ...changedValue }); 62 | }; 63 | 64 | const onInputChange = (e: any) => { 65 | triggerChange({ id: captchaId, value: e.target.value }); 66 | }; 67 | 68 | return ( 69 | { 78 | refreshCaptcha(); 79 | }} 80 | style={{ cursor: 'pointer', width: 110 }} 81 | /> 82 | } 83 | style={style} 84 | prefix={prefix} 85 | onChange={onInputChange} 86 | /> 87 | ); 88 | }; 89 | 90 | export default ImageCaptcha; 91 | -------------------------------------------------------------------------------- /src/components/Form/Field/Map.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Map as AMap, Marker } from 'react-amap'; 3 | import Autocomplete from '../../AMap/AutoComplete'; 4 | import { Input } from 'antd'; 5 | 6 | export interface MapProps { 7 | zoom?: any; 8 | mapKey?: any; 9 | mapSecurityJsCode?: any; 10 | value?: any; 11 | style?: any; 12 | onChange?: (value: any) => void; 13 | } 14 | 15 | const Map: React.FC = ({ 16 | zoom = null, 17 | mapKey = undefined, 18 | mapSecurityJsCode = undefined, 19 | value = { longitude: undefined, latitude: undefined }, 20 | style = [], 21 | onChange, 22 | }) => { 23 | const [position, setMapPosition] = useState({ 24 | longitude: undefined, 25 | latitude: undefined, 26 | }); 27 | 28 | window._AMapSecurityConfig = { 29 | securityJsCode: mapSecurityJsCode, 30 | }; 31 | 32 | const markerEvents = { 33 | dragend: (instance: any) => { 34 | let position = { 35 | longitude: undefined, 36 | latitude: undefined, 37 | }; 38 | position.longitude = instance.lnglat.lng; 39 | position.latitude = instance.lnglat.lat; 40 | setMapPosition(position); 41 | triggerChange({ ...position }); 42 | }, 43 | }; 44 | 45 | const triggerChange = (changedValue: any) => { 46 | if (onChange) { 47 | onChange({ ...value, ...changedValue }); 48 | } 49 | }; 50 | 51 | const onMapSelect = (e: any) => { 52 | if (e.poi.location) { 53 | let position = { 54 | longitude: undefined, 55 | latitude: undefined, 56 | }; 57 | position.longitude = e.poi.location.lng; 58 | position.latitude = e.poi.location.lat; 59 | setMapPosition(position); 60 | triggerChange({ ...position }); 61 | } 62 | }; 63 | 64 | let getPosition: any = { longitude: undefined, latitude: undefined }; 65 | 66 | if (position.latitude && position.longitude) { 67 | getPosition = position; 68 | } else { 69 | getPosition = value; 70 | } 71 | 72 | return ( 73 | <> 74 | 83 | 91 | - 92 | 93 | 102 |
103 | 116 | onMapSelect(e)} 119 | style={{ 120 | position: 'absolute', 121 | top: 20, 122 | right: 10, 123 | borderRadius: 4, 124 | border: '1px solid #1890FF', 125 | height: 34, 126 | width: 200, 127 | color: 'rgba(0, 0, 0, 0.65)', 128 | padding: '4px 11px', 129 | }} 130 | placeholder="请输入关键字" 131 | /> 132 | 146 | 147 |
148 | 149 | ); 150 | }; 151 | 152 | export default Map; 153 | -------------------------------------------------------------------------------- /src/components/Form/Field/Search.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Select, Spin } from 'antd'; 3 | import { LoadingOutlined } from '@ant-design/icons'; 4 | import { get } from '@/services/action'; 5 | 6 | const antIcon = ; 7 | 8 | export interface SearchProps { 9 | api?: any; 10 | size?: any; 11 | mode?: any; 12 | value?: any; 13 | placeholder?: any; 14 | style?: any; 15 | options?: any; 16 | allowClear?: any; 17 | onChange?: (value: any) => void; 18 | } 19 | 20 | const Search: React.FC = ({ 21 | api = null, 22 | mode = undefined, 23 | size = undefined, 24 | value = null, 25 | placeholder = null, 26 | style = [], 27 | options = [], 28 | allowClear = false, 29 | onChange, 30 | }) => { 31 | const [selectOptions, setSelectOptions] = useState(undefined); 32 | const [loading, setLoading] = useState(false); 33 | let getSelectOptions = selectOptions || options; 34 | 35 | useEffect(() => { 36 | if ( 37 | value && 38 | (getSelectOptions?.length === 0 || getSelectOptions === null) 39 | ) { 40 | setLoading(true); 41 | onInputSearch(value, 'value'); 42 | } else if (value && getSelectOptions?.length > 0) { 43 | if (getSelectOptions.indexOf(value) === -1) { 44 | setLoading(true); 45 | onInputSearch(value, 'value'); 46 | } 47 | } 48 | }, [value]); 49 | 50 | const triggerChange = (changedValue: any) => { 51 | if (onChange) { 52 | onChange(changedValue); 53 | } 54 | }; 55 | 56 | const onSelectChange = (value: any) => { 57 | triggerChange(value); 58 | }; 59 | 60 | const onInputSearch = (value: any, type = 'label') => { 61 | let timeout: any = null; 62 | 63 | if(!api) { 64 | if (timeout) { 65 | clearTimeout(timeout); 66 | timeout = null; 67 | } 68 | 69 | setLoading(false); 70 | return; 71 | } 72 | 73 | if (value) { 74 | if (timeout) { 75 | clearTimeout(timeout); 76 | timeout = null; 77 | } 78 | 79 | timeout = setTimeout(async function () { 80 | const result = await get({ 81 | url: api, 82 | data: { 83 | search: value, 84 | type: type, 85 | }, 86 | }); 87 | setSelectOptions(result.data); 88 | setLoading(false); 89 | }, 300); 90 | } 91 | }; 92 | 93 | return ( 94 | 95 | 119 | 120 | ); 121 | }; 122 | 123 | export default Search; 124 | -------------------------------------------------------------------------------- /src/components/Form/Field/Selects.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useModel } from 'umi'; 3 | import { get } from '@/services/action'; 4 | import { Form, Select } from 'antd'; 5 | import Render from '@/components/Render'; 6 | 7 | const Selects: React.FC = (props: any) => { 8 | const [random, setRandom] = useState(0); 9 | const [items, setItems] = useState(props.body); 10 | let { object } = useModel('object'); 11 | 12 | useEffect(() => { 13 | setInitialFields(); 14 | }, []); 15 | 16 | const getFields = async () => { 17 | // 深拷贝一份 body 来防止直接修改 props 18 | const fieldsCopy = [...items]; 19 | 20 | // 遍历每个字段项 21 | for (const item of fieldsCopy) { 22 | const value = object[props.data.componentkey]?.current?.getFieldValue( 23 | item.name, 24 | ); 25 | if (value && item.load) { 26 | for (const [key, subItem] of fieldsCopy.entries()) { 27 | if (item.load.field === subItem.name && item.load.api) { 28 | const result = await get({ 29 | url: item.load.api, 30 | data: { search: value }, 31 | }); 32 | fieldsCopy[key].options = result.data; 33 | } 34 | } 35 | } 36 | } 37 | 38 | return fieldsCopy; 39 | }; 40 | 41 | // 初始化字段 42 | const setInitialFields = async () => { 43 | if (!Array.isArray(items)) { 44 | return; 45 | } 46 | let newFields = await getFields(); 47 | let updatedItems = await Promise.all(newFields); 48 | setItems(updatedItems); 49 | }; 50 | 51 | const onSelectChange = async (value: any, name: string, load: any = null) => { 52 | let fieldsValue: any = {}; 53 | if (load) { 54 | const promises = items.map(async (item: any, key: any) => { 55 | if (load.field === item.name && load.api) { 56 | const result = await get({ 57 | url: load.api, 58 | data: { 59 | search: value, 60 | }, 61 | }); 62 | 63 | item.options = result.data; 64 | } 65 | return item; 66 | }); 67 | const getItems = await Promise.all(promises); 68 | setItems(getItems); 69 | fieldsValue[load.field] = undefined; 70 | } 71 | 72 | fieldsValue[name] = value; 73 | 74 | object[props.data.componentkey]?.current?.setFieldsValue(fieldsValue); 75 | setRandom(Math.random); 76 | }; 77 | 78 | // 渲染组件 79 | const fieldRender = (item: any) => { 80 | let component = null; 81 | 82 | switch (item.component) { 83 | case 'selectField': 84 | component = ( 85 | 93 |