Selaa lähdekoodia

前端和后端源码,合并到一个git仓库中,方便用户下载,避免前后端不匹配的问题

JEECG 9 kuukautta sitten
vanhempi
commit
0325e34dcb
100 muutettua tiedostoa jossa 27261 lisäystä ja 0 poistoa
  1. 19 0
      jeecgboot-vue3/.editorconfig
  2. 22 0
      jeecgboot-vue3/.env
  3. 21 0
      jeecgboot-vue3/.env.development
  4. 22 0
      jeecgboot-vue3/.env.production
  5. 15 0
      jeecgboot-vue3/.eslintignore
  6. 78 0
      jeecgboot-vue3/.eslintrc.js
  7. 36 0
      jeecgboot-vue3/.gitignore
  8. 6 0
      jeecgboot-vue3/.gitpod.yml
  9. 9 0
      jeecgboot-vue3/.prettierignore
  10. 3 0
      jeecgboot-vue3/.stylelintignore
  11. 48 0
      jeecgboot-vue3/.yarnclean
  12. 30 0
      jeecgboot-vue3/Dockerfile
  13. 37 0
      jeecgboot-vue3/LICENSE
  14. 444 0
      jeecgboot-vue3/README.md
  15. 79 0
      jeecgboot-vue3/build/config/themeConfig.ts
  16. 6 0
      jeecgboot-vue3/build/constant.ts
  17. 49 0
      jeecgboot-vue3/build/generate/generateModifyVars.ts
  18. 68 0
      jeecgboot-vue3/build/generate/icon/index.ts
  19. 7 0
      jeecgboot-vue3/build/getConfigFileName.ts
  20. 47 0
      jeecgboot-vue3/build/script/buildConf.ts
  21. 23 0
      jeecgboot-vue3/build/script/postBuild.ts
  22. 94 0
      jeecgboot-vue3/build/utils.ts
  23. 36 0
      jeecgboot-vue3/build/vite/plugin/compress.ts
  24. 40 0
      jeecgboot-vue3/build/vite/plugin/html.ts
  25. 35 0
      jeecgboot-vue3/build/vite/plugin/imagemin.ts
  26. 70 0
      jeecgboot-vue3/build/vite/plugin/index.ts
  27. 19 0
      jeecgboot-vue3/build/vite/plugin/mock.ts
  28. 82 0
      jeecgboot-vue3/build/vite/plugin/styleImport.ts
  29. 17 0
      jeecgboot-vue3/build/vite/plugin/svgSprite.ts
  30. 100 0
      jeecgboot-vue3/build/vite/plugin/theme.ts
  31. 17 0
      jeecgboot-vue3/build/vite/plugin/visualizer.ts
  32. 34 0
      jeecgboot-vue3/build/vite/proxy.ts
  33. 32 0
      jeecgboot-vue3/commitlint.config.js
  34. 181 0
      jeecgboot-vue3/index.html
  35. 36 0
      jeecgboot-vue3/jest.config.mjs
  36. 18 0
      jeecgboot-vue3/mock/_createProductionServer.ts
  37. 63 0
      jeecgboot-vue3/mock/_util.ts
  38. 70 0
      jeecgboot-vue3/mock/demo/account.ts
  39. 37 0
      jeecgboot-vue3/mock/demo/select-demo.ts
  40. 298 0
      jeecgboot-vue3/mock/demo/system.ts
  41. 54 0
      jeecgboot-vue3/mock/demo/table-demo.ts
  42. 38 0
      jeecgboot-vue3/mock/demo/tree-demo.ts
  43. 270 0
      jeecgboot-vue3/mock/sys/menu.ts
  44. 124 0
      jeecgboot-vue3/mock/sys/user.ts
  45. 0 0
      jeecgboot-vue3/npm
  46. 173 0
      jeecgboot-vue3/package.json
  47. 14103 0
      jeecgboot-vue3/pnpm-lock.yaml
  48. 5 0
      jeecgboot-vue3/postcss.config.js
  49. 20 0
      jeecgboot-vue3/prettier.config.js
  50. BIN
      jeecgboot-vue3/public/favicon.ico
  51. BIN
      jeecgboot-vue3/public/logo.png
  52. BIN
      jeecgboot-vue3/public/resource/img/logo.png
  53. 1 0
      jeecgboot-vue3/public/resource/js/iconfont.js
  54. 419 0
      jeecgboot-vue3/public/resource/tinymce/langs/en.js
  55. 389 0
      jeecgboot-vue3/public/resource/tinymce/langs/zh_CN.js
  56. 711 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.css
  57. 705 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.inline.css
  58. 7 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.inline.min.css
  59. 7 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.min.css
  60. 29 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.mobile.css
  61. 7 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.mobile.min.css
  62. BIN
      jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/fonts/tinymce-mobile.woff
  63. 3045 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/skin.css
  64. 7 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/skin.min.css
  65. 677 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/skin.mobile.css
  66. 7 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/skin.mobile.min.css
  67. 239 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide-dark/content.inline.min.css
  68. 235 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide-dark/content.min.css
  69. 17 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide-dark/content.mobile.min.css
  70. 875 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide-dark/skin.min.css
  71. 239 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide-dark/skin.mobile.min.css
  72. 239 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/content.inline.min.css
  73. 235 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/content.min.css
  74. 17 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/content.mobile.min.css
  75. BIN
      jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff
  76. 875 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/skin.min.css
  77. 239 0
      jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/skin.mobile.min.css
  78. 105 0
      jeecgboot-vue3/src/App.vue
  79. 150 0
      jeecgboot-vue3/src/api/common/api.ts
  80. 16 0
      jeecgboot-vue3/src/api/demo/account.ts
  81. 12 0
      jeecgboot-vue3/src/api/demo/error.ts
  82. 7 0
      jeecgboot-vue3/src/api/demo/model/accountModel.ts
  83. 15 0
      jeecgboot-vue3/src/api/demo/model/optionsModel.ts
  84. 103 0
      jeecgboot-vue3/src/api/demo/model/systemModel.ts
  85. 20 0
      jeecgboot-vue3/src/api/demo/model/tableModel.ts
  86. 10 0
      jeecgboot-vue3/src/api/demo/select.ts
  87. 45 0
      jeecgboot-vue3/src/api/demo/system.ts
  88. 19 0
      jeecgboot-vue3/src/api/demo/table.ts
  89. 10 0
      jeecgboot-vue3/src/api/demo/tree.ts
  90. 14 0
      jeecgboot-vue3/src/api/model/baseModel.ts
  91. 43 0
      jeecgboot-vue3/src/api/sys/menu.ts
  92. 16 0
      jeecgboot-vue3/src/api/sys/model/menuModel.ts
  93. 5 0
      jeecgboot-vue3/src/api/sys/model/uploadModel.ts
  94. 58 0
      jeecgboot-vue3/src/api/sys/model/userModel.ts
  95. 32 0
      jeecgboot-vue3/src/api/sys/upload.ts
  96. 222 0
      jeecgboot-vue3/src/api/sys/user.ts
  97. 1 0
      jeecgboot-vue3/src/assets/icons/download-count.svg
  98. 1 0
      jeecgboot-vue3/src/assets/icons/dynamic-avatar-1.svg
  99. 1 0
      jeecgboot-vue3/src/assets/icons/dynamic-avatar-2.svg
  100. 0 0
      jeecgboot-vue3/src/assets/icons/dynamic-avatar-3.svg

+ 19 - 0
jeecgboot-vue3/.editorconfig

@@ -0,0 +1,19 @@
+root = true
+
+[*]
+charset=utf-8
+end_of_line=lf
+insert_final_newline=true
+indent_style=space
+indent_size=2
+max_line_length = 100
+
+[*.{yml,yaml,json}]
+indent_style = space
+indent_size = 2
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab

+ 22 - 0
jeecgboot-vue3/.env

@@ -0,0 +1,22 @@
+# port
+VITE_PORT = 3100
+
+#  网站标题
+VITE_GLOB_APP_TITLE = JeecgBoot 企业级低代码平台
+
+# 简称,此变量只能是字符/下划线
+VITE_GLOB_APP_SHORT_NAME = JeecgBoot_Pro
+
+# 单点登录服务端地址
+VITE_GLOB_APP_CAS_BASE_URL=http://cas.test.com:8443/cas
+
+# 是否开启单点登录
+VITE_GLOB_APP_OPEN_SSO = false
+
+# 开启微前端模式
+VITE_GLOB_APP_OPEN_QIANKUN=true
+
+# 文件预览地址
+VITE_GLOB_ONLINE_VIEW_URL=http://fileview.jeecg.com/onlinePreview
+
+

+ 21 - 0
jeecgboot-vue3/.env.development

@@ -0,0 +1,21 @@
+# 是否打开mock
+VITE_USE_MOCK = true
+
+# 发布路径
+VITE_PUBLIC_PATH = /
+
+
+# 跨域代理,您可以配置多个 ,请注意,没有换行符
+VITE_PROXY = [["/jeecgboot","http://localhost:8080/jeecg-boot"],["/upload","http://localhost:3300/upload"]]
+
+#后台接口全路径地址(必填)
+VITE_GLOB_DOMAIN_URL=http://localhost:8080/jeecg-boot
+
+#后台接口父地址(必填)
+VITE_GLOB_API_URL=/jeecgboot
+
+# 接口前缀
+VITE_GLOB_API_URL_PREFIX=
+
+#微前端qiankun应用,命名必须以VITE_APP_SUB_开头,jeecg-app-1为子应用的项目名称,也是子应用的路由父路径
+VITE_APP_SUB_jeecg-app-1 = '//localhost:8092'

+ 22 - 0
jeecgboot-vue3/.env.production

@@ -0,0 +1,22 @@
+# 是否启用mock
+VITE_USE_MOCK = true
+
+# 发布路径
+VITE_PUBLIC_PATH = /
+
+# 是否启用gzip或brotli压缩
+# 选项值: gzip | brotli | none
+# 如果需要多个可以使用“,”分隔
+VITE_BUILD_COMPRESS = 'gzip'
+
+# 使用压缩时是否删除原始文件,默认为false
+VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
+
+#后台接口父地址(必填)
+VITE_GLOB_API_URL=/jeecgboot
+
+#后台接口全路径地址(必填)
+VITE_GLOB_DOMAIN_URL=http://jeecg-boot-system:8080/jeecg-boot
+
+# 接口父路径前缀
+VITE_GLOB_API_URL_PREFIX=

+ 15 - 0
jeecgboot-vue3/.eslintignore

@@ -0,0 +1,15 @@
+
+*.sh
+node_modules
+*.md
+*.woff
+*.ttf
+.vscode
+.idea
+dist
+/public
+/docs
+.husky
+.local
+/bin
+Dockerfile

+ 78 - 0
jeecgboot-vue3/.eslintrc.js

@@ -0,0 +1,78 @@
+// @ts-check
+const { defineConfig } = require('eslint-define-config');
+module.exports = defineConfig({
+  root: true,
+  env: {
+    browser: true,
+    node: true,
+    es6: true,
+  },
+  parser: 'vue-eslint-parser',
+  parserOptions: {
+    parser: '@typescript-eslint/parser',
+    ecmaVersion: 2020,
+    sourceType: 'module',
+    jsxPragma: 'React',
+    ecmaFeatures: {
+      jsx: true,
+    },
+  },
+  extends: [
+    'plugin:vue/vue3-recommended',
+    'plugin:@typescript-eslint/recommended',
+    'prettier',
+    'plugin:prettier/recommended',
+    'plugin:jest/recommended',
+  ],
+  rules: {
+    'vue/script-setup-uses-vars': 'error',
+    '@typescript-eslint/ban-ts-ignore': 'off',
+    '@typescript-eslint/explicit-function-return-type': 'off',
+    '@typescript-eslint/no-explicit-any': 'off',
+    '@typescript-eslint/no-var-requires': 'off',
+    '@typescript-eslint/no-empty-function': 'off',
+    'vue/custom-event-name-casing': 'off',
+    'no-use-before-define': 'off',
+    '@typescript-eslint/no-use-before-define': 'off',
+    '@typescript-eslint/ban-ts-comment': 'off',
+    '@typescript-eslint/ban-types': 'off',
+    '@typescript-eslint/no-non-null-assertion': 'off',
+    '@typescript-eslint/explicit-module-boundary-types': 'off',
+    '@typescript-eslint/no-unused-vars': [
+      'error',
+      {
+        argsIgnorePattern: '^_',
+        varsIgnorePattern: '^_',
+      },
+    ],
+    'no-unused-vars': [
+      'error',
+      {
+        argsIgnorePattern: '^_',
+        varsIgnorePattern: '^_',
+      },
+    ],
+    'space-before-function-paren': 'off',
+
+    'vue/attributes-order': 'off',
+    'vue/one-component-per-file': 'off',
+    'vue/html-closing-bracket-newline': 'off',
+    'vue/max-attributes-per-line': 'off',
+    'vue/multiline-html-element-content-newline': 'off',
+    'vue/singleline-html-element-content-newline': 'off',
+    'vue/attribute-hyphenation': 'off',
+    'vue/require-default-prop': 'off',
+    'vue/html-self-closing': [
+      'error',
+      {
+        html: {
+          void: 'always',
+          normal: 'never',
+          component: 'always',
+        },
+        svg: 'always',
+        math: 'always',
+      },
+    ],
+  },
+});

+ 36 - 0
jeecgboot-vue3/.gitignore

@@ -0,0 +1,36 @@
+node_modules
+.DS_Store
+.github
+dist
+.npmrc
+.cache
+
+tests/server/static
+tests/server/static/upload
+
+.local
+# local env files
+.env.local
+.env.*.local
+.eslintcache
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.svn
+# .vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+/os_del.cmd
+os_del.cmd
+/.vscode/
+/.history/
+/svn clear.bat

+ 6 - 0
jeecgboot-vue3/.gitpod.yml

@@ -0,0 +1,6 @@
+ports:
+  - port: 3344
+    onOpen: open-preview
+tasks:
+  - init: yarn
+    command: yarn dev

+ 9 - 0
jeecgboot-vue3/.prettierignore

@@ -0,0 +1,9 @@
+/dist/*
+.local
+.output.js
+/node_modules/**
+
+**/*.svg
+**/*.sh
+
+/public/*

+ 3 - 0
jeecgboot-vue3/.stylelintignore

@@ -0,0 +1,3 @@
+/dist/*
+/public/*
+public/*

+ 48 - 0
jeecgboot-vue3/.yarnclean

@@ -0,0 +1,48 @@
+# test directories
+__tests__
+test
+tests
+powered-test
+
+# asset directories
+docs
+doc
+website
+images
+assets
+
+# examples
+example
+examples
+
+# code coverage directories
+coverage
+.nyc_output
+
+# build scripts
+Makefile
+Gulpfile.js
+Gruntfile.js
+
+# configs
+appveyor.yml
+circle.yml
+codeship-services.yml
+codeship-steps.yml
+wercker.yml
+.tern-project
+.gitattributes
+.editorconfig
+.*ignore
+.eslintrc
+.jshintrc
+.flowconfig
+.documentup.json
+.yarn-metadata.json
+.travis.yml
+
+# misc
+*.md
+
+!istanbul-reports/lib/html/assets
+!istanbul-api/node_modules/istanbul-reports/lib/html/assets

+ 30 - 0
jeecgboot-vue3/Dockerfile

@@ -0,0 +1,30 @@
+FROM nginx
+MAINTAINER jeecgos@163.com
+VOLUME /tmp
+ENV LANG en_US.UTF-8
+RUN echo "server {  \
+                      listen       80; \
+                      location   /jeecgboot/ { \
+                      proxy_pass              http://jeecg-boot-system:8080/jeecg-boot/; \
+                      proxy_redirect          off; \
+                      proxy_set_header        Host jeecg-boot-system; \
+                      proxy_set_header        X-Real-IP \$remote_addr; \
+                      proxy_set_header        X-Forwarded-For \$proxy_add_x_forwarded_for; \
+                  } \
+                  #解决Router(mode: 'history')模式下,刷新路由地址不能找到页面的问题 \
+                  location / { \
+                     root   /var/www/html/; \
+                      index  index.html index.htm; \
+                      if (!-e \$request_filename) { \
+                          rewrite ^(.*)\$ /index.html?s=\$1 last; \
+                          break; \
+                      } \
+                  } \
+                  access_log  /var/log/nginx/access.log ; \
+              } " > /etc/nginx/conf.d/default.conf \
+    &&  mkdir  -p  /var/www \
+    &&  mkdir -p /var/www/html
+
+ADD dist/ /var/www/html/
+EXPOSE 80
+EXPOSE 443

+ 37 - 0
jeecgboot-vue3/LICENSE

@@ -0,0 +1,37 @@
+MIT License
+
+Copyright (c) 2020-present, Jeecg
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+
+  开源协议补充
+    JeecgBoot 是由 北京国炬信息技术有限公司 发行的软件。 总部位于北京,地址:中国·北京·朝阳区科荟前街1号院奥林佳泰大厦。邮箱:jeecgos@163.com
+    本软件受适用的国家软件著作权法(包括国际条约)和双重保护许可。
+  
+   1.允许基于本平台软件开展业务系统开发。
+   2.JeecgBoot底层依赖的非开源功能:online lib依赖、仪表盘lib依赖等,统一采用LGPL开源协议(不二次改造、不拆分出jeecgboot之外使用,就不产生侵权)
+   3.不得基于该平台软件的基础,修改包装成一个与JeecgBoot平台软件功能类似的产品进行发布、销售,或与JeecgBoot参与同类软件产品市场的竞争。
+	 违反此条款属于侵权行为,须赔偿侵权经济损失,同时立即停止著作权侵权行为。
+	 
+   总结:在遵循Apache开源协议和开源协议补充条款下,允许商用使用,不会造成侵权行为!
+	 解释权归:
+   http://www.jeecg.com
+   http://guojusoft.com

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 444 - 0
jeecgboot-vue3/README.md


+ 79 - 0
jeecgboot-vue3/build/config/themeConfig.ts

@@ -0,0 +1,79 @@
+import { generate } from '@ant-design/colors';
+
+export const primaryColor = '#1890FF';
+
+export const darkMode = 'light';
+
+type Fn = (...arg: any) => any;
+
+type GenerateTheme = 'default' | 'dark';
+
+export interface GenerateColorsParams {
+  mixLighten: Fn;
+  mixDarken: Fn;
+  tinycolor: any;
+  color?: string;
+}
+
+export function generateAntColors(color: string, theme: GenerateTheme = 'default') {
+  return generate(color, {
+    theme,
+  });
+}
+
+export function getThemeColors(color?: string) {
+  const tc = color || primaryColor;
+  const lightColors = generateAntColors(tc);
+  const primary = lightColors[5];
+  const modeColors = generateAntColors(primary, 'dark');
+
+  return [...lightColors, ...modeColors];
+}
+
+export function generateColors({
+  color = primaryColor,
+  mixLighten,
+  mixDarken,
+  tinycolor,
+}: GenerateColorsParams) {
+  const arr = new Array(19).fill(0);
+  const lightens = arr.map((_t, i) => {
+    return mixLighten(color, i / 5);
+  });
+
+  const darkens = arr.map((_t, i) => {
+    return mixDarken(color, i / 5);
+  });
+
+  const alphaColors = arr.map((_t, i) => {
+    return tinycolor(color)
+      .setAlpha(i / 20)
+      .toRgbString();
+  });
+
+  const shortAlphaColors = alphaColors.map((item) => item.replace(/\s/g, '').replace(/0\./g, '.'));
+
+  const tinycolorLightens = arr
+    .map((_t, i) => {
+      return tinycolor(color)
+        .lighten(i * 5)
+        .toHexString();
+    })
+    .filter((item) => item !== '#ffffff');
+
+  const tinycolorDarkens = arr
+    .map((_t, i) => {
+      return tinycolor(color)
+        .darken(i * 5)
+        .toHexString();
+    })
+    .filter((item) => item !== '#000000');
+  return [
+    ...lightens,
+    ...darkens,
+    ...alphaColors,
+    ...shortAlphaColors,
+    ...tinycolorDarkens,
+    ...tinycolorLightens,
+  ].filter((item) => !item.includes('-'));
+}

+ 6 - 0
jeecgboot-vue3/build/constant.ts

@@ -0,0 +1,6 @@
+/**
+ * The name of the configuration file entered in the production environment
+ */
+export const GLOB_CONFIG_FILE_NAME = '_app.config.js';
+
+export const OUTPUT_DIR = 'dist';

+ 49 - 0
jeecgboot-vue3/build/generate/generateModifyVars.ts

@@ -0,0 +1,49 @@
+import { primaryColor } from '../config/themeConfig';
+// import { getThemeVariables } from 'ant-design-vue/dist/theme';
+import { resolve } from 'path';
+import { generate } from '@ant-design/colors';
+import { theme } from 'ant-design-vue/lib';
+import convertLegacyToken from 'ant-design-vue/lib/theme/convertLegacyToken';
+const { defaultAlgorithm, defaultSeed } = theme;
+
+function generateAntColors(color: string, theme: 'default' | 'dark' = 'default') {
+  return generate(color, {
+    theme,
+  });
+}
+
+/**
+ * less global variable
+ */
+export function generateModifyVars() {
+  const palettes = generateAntColors(primaryColor);
+  const primary = palettes[5];
+
+  const primaryColorObj: Record<string, string> = {};
+
+  for (let index = 0; index < 10; index++) {
+    primaryColorObj[`primary-${index + 1}`] = palettes[index];
+  }
+
+  const mapToken = defaultAlgorithm(defaultSeed);
+  const v3Token = convertLegacyToken(mapToken);
+  return {
+    ...v3Token,
+    // ...modifyVars,
+    // Used for global import to avoid the need to import each style file separately
+    // reference:  Avoid repeated references
+    hack: `true; @import (reference) "${resolve('src/design/config.less')}";`,
+    'primary-color': primary,
+    ...primaryColorObj,
+    'info-color': primary,
+    'processing-color': primary,
+    'success-color': '#55D187', //  Success color
+    'error-color': '#ED6F6F', //  False color
+    'warning-color': '#EFBD47', //   Warning color
+    //'border-color-base': '#EEEEEE',
+    'font-size-base': '14px', //  Main font size
+    'border-radius-base': '2px', //  Component/float fillet
+    'link-color': primary, //   Link color
+    'app-content-background': '#fafafa', //   Link color
+  };
+}

+ 68 - 0
jeecgboot-vue3/build/generate/icon/index.ts

@@ -0,0 +1,68 @@
+import path from 'path';
+import fs from 'fs-extra';
+import inquirer from 'inquirer';
+import colors from 'picocolors';
+import pkg from '../../../package.json';
+
+async function generateIcon() {
+  const dir = path.resolve(process.cwd(), 'node_modules/@iconify/json');
+
+  const raw = await fs.readJSON(path.join(dir, 'collections.json'));
+
+  const collections = Object.entries(raw).map(([id, v]) => ({
+    ...(v as any),
+    id,
+  }));
+
+  const choices = collections.map((item) => ({ key: item.id, value: item.id, name: item.name }));
+
+  inquirer
+    .prompt([
+      {
+        type: 'list',
+        name: 'useType',
+        choices: [
+          { key: 'local', value: 'local', name: 'Local' },
+          { key: 'onLine', value: 'onLine', name: 'OnLine' },
+        ],
+        message: 'How to use icons?',
+      },
+      {
+        type: 'list',
+        name: 'iconSet',
+        choices: choices,
+        message: 'Select the icon set that needs to be generated?',
+      },
+      {
+        type: 'input',
+        name: 'output',
+        message: 'Select the icon set that needs to be generated?',
+        default: 'src/components/Icon/data',
+      },
+    ])
+    .then(async (answers) => {
+      const { iconSet, output, useType } = answers;
+      const outputDir = path.resolve(process.cwd(), output);
+      fs.ensureDir(outputDir);
+      const genCollections = collections.filter((item) => [iconSet].includes(item.id));
+      const prefixSet: string[] = [];
+      for (const info of genCollections) {
+        const data = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`));
+        if (data) {
+          const { prefix } = data;
+          const isLocal = useType === 'local';
+          const icons = Object.keys(data.icons).map((item) => `${isLocal ? prefix + ':' : ''}${item}`);
+
+          await fs.writeFileSync(
+            path.join(output, `icons.data.ts`),
+            `export default ${isLocal ? JSON.stringify(icons) : JSON.stringify({ prefix, icons })}`
+          );
+          prefixSet.push(prefix);
+        }
+      }
+      fs.emptyDir(path.join(process.cwd(), 'node_modules/.vite'));
+      console.log(`✨ ${colors.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`);
+    });
+}
+
+generateIcon();

+ 7 - 0
jeecgboot-vue3/build/getConfigFileName.ts

@@ -0,0 +1,7 @@
+/**
+ * Get the configuration file variable name
+ * @param env
+ */
+export const getConfigFileName = (env: Record<string, any>) => {
+  return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`.toUpperCase().replace(/\s/g, '');
+};

+ 47 - 0
jeecgboot-vue3/build/script/buildConf.ts

@@ -0,0 +1,47 @@
+/**
+ * 生成外部配置文件,用于生产发布后配置,无需重新打包
+ */
+import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant';
+import fs, { writeFileSync } from 'fs-extra';
+import colors from 'picocolors';
+
+import { getEnvConfig, getRootPath } from '../utils';
+import { getConfigFileName } from '../getConfigFileName';
+
+import pkg from '../../package.json';
+
+interface CreateConfigParams {
+  configName: string;
+  config: any;
+  configFileName?: string;
+}
+
+function createConfig(params: CreateConfigParams) {
+  const { configName, config, configFileName } = params;
+  try {
+    const windowConf = `window.${configName}`;
+    // Ensure that the variable will not be modified
+    let configStr = `${windowConf}=${JSON.stringify(config)};`;
+    configStr += `
+      Object.freeze(${windowConf});
+      Object.defineProperty(window, "${configName}", {
+        configurable: false,
+        writable: false,
+      });
+    `.replace(/\s/g, '');
+
+    fs.mkdirp(getRootPath(OUTPUT_DIR));
+    writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);
+
+    console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
+    console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n');
+  } catch (error) {
+    console.log(colors.red('configuration file configuration file failed to package:\n' + error));
+  }
+}
+
+export function runBuildConfig() {
+  const config = getEnvConfig();
+  const configFileName = getConfigFileName(config);
+  createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME });
+}

+ 23 - 0
jeecgboot-vue3/build/script/postBuild.ts

@@ -0,0 +1,23 @@
+// #!/usr/bin/env node
+
+import { runBuildConfig } from './buildConf';
+import colors from 'picocolors';
+
+import pkg from '../../package.json';
+
+export const runBuild = async () => {
+  try {
+    const argvList = process.argv.splice(2);
+
+    // Generate configuration file
+    if (!argvList.includes('disabled-config')) {
+      runBuildConfig();
+    }
+
+    console.log(`✨ ${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!');
+  } catch (error) {
+    console.log(colors.red('vite build error:\n' + error));
+    process.exit(1);
+  }
+};
+runBuild();

+ 94 - 0
jeecgboot-vue3/build/utils.ts

@@ -0,0 +1,94 @@
+import fs from 'fs';
+import path from 'path';
+import dotenv from 'dotenv';
+
+export function isDevFn(mode: string): boolean {
+  return mode === 'development';
+}
+
+export function isProdFn(mode: string): boolean {
+  return mode === 'production';
+}
+
+/**
+ * Whether to generate package preview
+ */
+export function isReportMode(): boolean {
+  return process.env.REPORT === 'true';
+}
+
+// Read all environment variable configuration files to process.env
+export function wrapperEnv(envConf: Recordable): ViteEnv {
+  const ret: any = {};
+
+  for (const envName of Object.keys(envConf)) {
+    let realName = envConf[envName].replace(/\\n/g, '\n');
+    realName = realName === 'true' ? true : realName === 'false' ? false : realName;
+
+    if (envName === 'VITE_PORT') {
+      realName = Number(realName);
+    }
+    if (envName === 'VITE_PROXY' && realName) {
+      try {
+        realName = JSON.parse(realName.replace(/'/g, '"'));
+      } catch (error) {
+        realName = '';
+      }
+    }
+    ret[envName] = realName;
+    if (typeof realName === 'string') {
+      process.env[envName] = realName;
+    } else if (typeof realName === 'object') {
+      process.env[envName] = JSON.stringify(realName);
+    }
+  }
+  return ret;
+}
+
+/**
+ * 获取当前环境下生效的配置文件名
+ */
+function getConfFiles() {
+  const script = process.env.npm_lifecycle_script;
+  // update-begin--author:liaozhiyang---date:20240326---for:【QQYUN-8690】修正获取当前环境下的文件名
+  const reg = new RegExp('NODE_ENV=([a-z_\\d]+)');
+  // update-end--author:liaozhiyang---date:20240326---for:【QQYUN-8690】修正获取当前环境下的文件名
+  const result = reg.exec(script as string) as any;
+  if (result) {
+    const mode = result[1] as string;
+    return ['.env', `.env.${mode}`];
+  }
+  return ['.env', '.env.production'];
+}
+
+/**
+ * Get the environment variables starting with the specified prefix
+ * @param match prefix
+ * @param confFiles ext
+ */
+export function getEnvConfig(match = 'VITE_GLOB_', confFiles = getConfFiles()) {
+  let envConfig = {};
+  confFiles.forEach((item) => {
+    try {
+      const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
+      envConfig = { ...envConfig, ...env };
+    } catch (e) {
+      console.error(`Error in parsing ${item}`, e);
+    }
+  });
+  const reg = new RegExp(`^(${match})`);
+  Object.keys(envConfig).forEach((key) => {
+    if (!reg.test(key)) {
+      Reflect.deleteProperty(envConfig, key);
+    }
+  });
+  return envConfig;
+}
+
+/**
+ * Get user root directory
+ * @param dir file path
+ */
+export function getRootPath(...dir: string[]) {
+  return path.resolve(process.cwd(), ...dir);
+}

+ 36 - 0
jeecgboot-vue3/build/vite/plugin/compress.ts

@@ -0,0 +1,36 @@
+/**
+ * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
+ * https://github.com/anncwb/vite-plugin-compression
+ */
+import type { PluginOption } from 'vite';
+import compressPlugin from 'vite-plugin-compression';
+
+export function configCompressPlugin(compress: 'gzip' | 'brotli' | 'none', deleteOriginFile = false): PluginOption | PluginOption[] {
+  const compressList = compress.split(',');
+
+  const plugins: PluginOption[] = [];
+
+  if (compressList.includes('gzip')) {
+    plugins.push(
+      compressPlugin({
+        verbose: true,
+        disable: false,
+        threshold: 10240,
+        algorithm: 'gzip',
+        ext: '.gz',
+        deleteOriginFile,
+      })
+    );
+  }
+
+  if (compressList.includes('brotli')) {
+    plugins.push(
+      compressPlugin({
+        ext: '.br',
+        algorithm: 'brotliCompress',
+        deleteOriginFile,
+      })
+    );
+  }
+  return plugins;
+}

+ 40 - 0
jeecgboot-vue3/build/vite/plugin/html.ts

@@ -0,0 +1,40 @@
+/**
+ * Plugin to minimize and use ejs template syntax in index.html.
+ * https://github.com/anncwb/vite-plugin-html
+ */
+import type { PluginOption } from 'vite';
+import { createHtmlPlugin } from 'vite-plugin-html';
+import pkg from '../../../package.json';
+import { GLOB_CONFIG_FILE_NAME } from '../../constant';
+
+export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
+  const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;
+
+  const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`;
+
+  const getAppConfigSrc = () => {
+    return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
+  };
+
+  const htmlPlugin: PluginOption[] = createHtmlPlugin({
+    minify: isBuild,
+    inject: {
+      // 修改模板html的标题
+      data: {
+        title: VITE_GLOB_APP_TITLE,
+      },
+      // 将app.config.js文件注入到模板html中
+      tags: isBuild
+        ? [
+            {
+              tag: 'script',
+              attrs: {
+                src: getAppConfigSrc(),
+              },
+            },
+          ]
+        : [],
+    },
+  });
+  return htmlPlugin;
+}

+ 35 - 0
jeecgboot-vue3/build/vite/plugin/imagemin.ts

@@ -0,0 +1,35 @@
+// 【图片压缩插件】
+// Image resource files used to compress the output of the production environment
+// https://github.com/anncwb/vite-plugin-imagemin
+import viteImagemin from 'vite-plugin-imagemin';
+
+export function configImageminPlugin() {
+  const plugin = viteImagemin({
+    gifsicle: {
+      optimizationLevel: 7,
+      interlaced: false,
+    },
+    optipng: {
+      optimizationLevel: 7,
+    },
+    mozjpeg: {
+      quality: 20,
+    },
+    pngquant: {
+      quality: [0.8, 0.9],
+      speed: 4,
+    },
+    svgo: {
+      plugins: [
+        {
+          name: 'removeViewBox',
+        },
+        {
+          name: 'removeEmptyAttrs',
+          active: false,
+        },
+      ],
+    },
+  });
+  return plugin;
+}

+ 70 - 0
jeecgboot-vue3/build/vite/plugin/index.ts

@@ -0,0 +1,70 @@
+import { PluginOption } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import vueJsx from '@vitejs/plugin-vue-jsx';
+import purgeIcons from 'vite-plugin-purge-icons';
+import UnoCSS from 'unocss/vite';
+import { presetTypography, presetUno } from 'unocss';
+
+// 本地调试https配置方法
+import VitePluginCertificate from 'vite-plugin-mkcert';
+//[issues/555]开发环境,vscode断点调试,文件或行数对不上
+import vueSetupExtend from 'vite-plugin-vue-setup-extend-plus';
+import { configHtmlPlugin } from './html';
+import { configMockPlugin } from './mock';
+import { configCompressPlugin } from './compress';
+import { configVisualizerConfig } from './visualizer';
+import { configThemePlugin } from './theme';
+import { configSvgIconsPlugin } from './svgSprite';
+// //预编译加载插件(不支持vite3作废)
+// import OptimizationPersist from 'vite-plugin-optimize-persist';
+// import PkgConfig from 'vite-plugin-package-config';
+
+export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
+  const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
+
+  const vitePlugins: (PluginOption | PluginOption[])[] = [
+    // have to
+    vue(),
+    // have to
+    vueJsx(),
+    // support name
+    vueSetupExtend(),
+    // @ts-ignore
+    VitePluginCertificate({
+      source: 'coding',
+    }),
+  ];
+
+  vitePlugins.push(UnoCSS({ presets: [presetUno(), presetTypography()] }));
+
+  // vite-plugin-html
+  vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
+
+  // vite-plugin-svg-icons
+  vitePlugins.push(configSvgIconsPlugin(isBuild));
+
+  // vite-plugin-mock
+  VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild));
+
+  // vite-plugin-purge-icons
+  vitePlugins.push(purgeIcons());
+
+  // rollup-plugin-visualizer
+  vitePlugins.push(configVisualizerConfig());
+
+  // vite-plugin-theme
+  vitePlugins.push(configThemePlugin(isBuild));
+
+  // The following plugins only work in the production environment
+  if (isBuild) {
+    
+    // rollup-plugin-gzip
+    vitePlugins.push(configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE));
+
+  }
+
+  // //vite-plugin-theme【预编译加载插件,解决vite首次打开界面加载慢问题】
+  // vitePlugins.push(PkgConfig());
+  // vitePlugins.push(OptimizationPersist());
+  return vitePlugins;
+}

+ 19 - 0
jeecgboot-vue3/build/vite/plugin/mock.ts

@@ -0,0 +1,19 @@
+/**
+ * Mock plugin for development and production.
+ * https://github.com/anncwb/vite-plugin-mock
+ */
+import { viteMockServe } from 'vite-plugin-mock';
+
+export function configMockPlugin(isBuild: boolean) {
+  return viteMockServe({
+    ignore: /^\_/,
+    mockPath: 'mock',
+    localEnabled: !isBuild,
+    prodEnabled: isBuild,
+    injectCode: `
+      import { setupProdMockServer } from '../mock/_createProductionServer';
+
+      setupProdMockServer();
+      `,
+  });
+}

+ 82 - 0
jeecgboot-vue3/build/vite/plugin/styleImport.ts

@@ -0,0 +1,82 @@
+/**
+ * 【样式按需加载插件 ——主要处理antd的样式】
+ *  Introduces component library styles on demand.
+ * https://github.com/anncwb/vite-plugin-style-import
+ */
+import { createStyleImportPlugin } from 'vite-plugin-style-import';
+
+export function configStyleImportPlugin(_isBuild: boolean) {
+  if (!_isBuild) {
+    return [];
+  }
+  const styleImportPlugin = createStyleImportPlugin({
+    libs: [
+      {
+        libraryName: 'ant-design-vue',
+        esModule: true,
+        resolveStyle: (name) => {
+          // 这里是无需额外引入样式文件的“子组件”列表
+          const ignoreList = [
+            'anchor-link',
+            'sub-menu',
+            'menu-item',
+            'menu-divider',
+            'menu-item-group',
+            'breadcrumb-item',
+            'breadcrumb-separator',
+            'form-item',
+            'step',
+            'select-option',
+            'select-opt-group',
+            'card-grid',
+            'card-meta',
+            'collapse-panel',
+            'descriptions-item',
+            'list-item',
+            'list-item-meta',
+            'table-column',
+            'table-column-group',
+            'tab-pane',
+            'tab-content',
+            'timeline-item',
+            'tree-node',
+            'skeleton-input',
+            'skeleton-avatar',
+            'skeleton-title',
+            'skeleton-paragraph',
+            'skeleton-image',
+            'skeleton-button',
+          ];
+          // 这里是需要额外引入样式的子组件列表
+          // 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失
+          const replaceList = {
+            'typography-text': 'typography',
+            'typography-title': 'typography',
+            'typography-paragraph': 'typography',
+            'typography-link': 'typography',
+            'dropdown-button': 'dropdown',
+            'input-password': 'input',
+            'input-search': 'input',
+            'input-group': 'input',
+            'radio-group': 'radio',
+            'checkbox-group': 'checkbox',
+            'layout-sider': 'layout',
+            'layout-content': 'layout',
+            'layout-footer': 'layout',
+            'layout-header': 'layout',
+            'month-picker': 'date-picker',
+            'range-picker': 'date-picker',
+            'image-preview-group': 'image',
+          };
+
+          return ignoreList.includes(name)
+            ? ''
+            : replaceList.hasOwnProperty(name)
+            ? `ant-design-vue/es/${replaceList[name]}/style/index`
+            : `ant-design-vue/es/${name}/style/index`;
+        },
+      },
+    ],
+  });
+  return styleImportPlugin;
+}

+ 17 - 0
jeecgboot-vue3/build/vite/plugin/svgSprite.ts

@@ -0,0 +1,17 @@
+/**
+ *  Vite Plugin for fast creating SVG sprites.
+ * https://github.com/anncwb/vite-plugin-svg-icons
+ */
+
+import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
+import path from 'path';
+
+export function configSvgIconsPlugin(isBuild: boolean) {
+  const svgIconsPlugin = createSvgIconsPlugin({
+    iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
+    svgoOptions: isBuild,
+    // default
+    symbolId: 'icon-[dir]-[name]',
+  });
+  return svgIconsPlugin;
+}

+ 100 - 0
jeecgboot-vue3/build/vite/plugin/theme.ts

@@ -0,0 +1,100 @@
+/**
+ * Vite plugin for website theme color switching
+ * https://github.com/anncwb/vite-plugin-theme
+ */
+import type { PluginOption } from 'vite';
+import path from 'path';
+import { viteThemePlugin, antdDarkThemePlugin, mixLighten, mixDarken, tinycolor } from '@rys-fe/vite-plugin-theme';
+import { getThemeColors, generateColors } from '../../config/themeConfig';
+import { generateModifyVars } from '../../generate/generateModifyVars';
+
+export function configThemePlugin(isBuild: boolean): PluginOption[] {
+  const colors = generateColors({
+    mixDarken,
+    mixLighten,
+    tinycolor,
+  });
+
+  // update-begin-修复编译后主题色切换不生效黑屏的问题-----------------------
+  // https://github.com/vbenjs/vue-vben-admin/issues/1445
+  // 抽取出viteThemePlugin插件,下方会根据不同环境设置enforce
+  const vite_theme_plugin = viteThemePlugin({
+    resolveSelector: (s) => {
+      s = s.trim();
+      switch (s) {
+        case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
+          return '.ant-steps-item-icon > .ant-steps-icon';
+        case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)':
+        case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover':
+        case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active':
+          return s;
+        case '.ant-steps-item-icon > .ant-steps-icon':
+          return s;
+        case '.ant-select-item-option-selected:not(.ant-select-item-option-disabled)':
+          return s;
+        default:
+          if (s.indexOf('.ant-btn') >= -1) {
+            // 按钮被重新定制过,需要过滤掉class防止覆盖
+            return s;
+          }
+      }
+      return s.startsWith('[data-theme') ? s : `[data-theme] ${s}`;
+    },
+    colorVariables: [...getThemeColors(), ...colors],
+  });
+  vite_theme_plugin.forEach(function (item) {
+    //对vite:theme插件特殊配置
+    if ('vite:theme' === item.name) {
+      // 打包时去除enforce: "post",vite 2.6.x适配,否则生成app-theme-style为空,因为async transform(code, id) {的code没有正确获取
+      if (isBuild) {
+        delete item.enforce;
+      }
+    }
+  });
+  // update-end-修复编译后主题色切换不生效黑屏的问题-----------------------
+
+  const plugin = [
+    vite_theme_plugin,
+    antdDarkThemePlugin({
+      preloadFiles: [
+        // path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/reset.css'),
+        //path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/antd.dark.less'),
+        path.resolve(process.cwd(), 'src/design/index.less'),
+      ],
+      filter: (id) => (isBuild ? !id.endsWith('antd.less') : true),
+      // extractCss: false,
+      darkModifyVars: {
+        ...generateModifyVars(true),
+        'text-color': '#c9d1d9',
+        'primary-1': 'rgb(255 255 255 / 8%)',
+        'text-color-base': '#c9d1d9',
+        'component-background': '#151515',
+        'heading-color': 'rgb(255 255 255 / 65%)',
+        // black: '#0e1117',
+        // #8b949e
+        'text-color-secondary': '#8b949e',
+        'border-color-base': '#303030',
+        'header-light-bottom-border-color': '#303030',
+        // 'border-color-split': '#30363d',
+        'item-active-bg': '#111b26',
+        'app-content-background': '#1e1e1e',
+        'tree-node-selected-bg': '#11263c',
+
+        'alert-success-border-color': '#274916',
+        'alert-success-bg-color': '#162312',
+        'alert-success-icon-color': '#49aa19',
+        'alert-info-border-color': '#153450',
+        'alert-info-bg-color': '#111b26',
+        'alert-info-icon-color': '#177ddc',
+        'alert-warning-border-color': '#594214',
+        'alert-warning-bg-color': '#2b2111',
+        'alert-warning-icon-color': '#d89614',
+        'alert-error-border-color': '#58181c',
+        'alert-error-bg-color': '#2a1215',
+        'alert-error-icon-color': '#a61d24',
+      },
+    }),
+  ];
+
+  return plugin as unknown as PluginOption[];
+}

+ 17 - 0
jeecgboot-vue3/build/vite/plugin/visualizer.ts

@@ -0,0 +1,17 @@
+/**
+ * Package file volume analysis
+ */
+import visualizer from 'rollup-plugin-visualizer';
+import { isReportMode } from '../../utils';
+
+export function configVisualizerConfig() {
+  if (isReportMode()) {
+    return visualizer({
+      filename: './node_modules/.cache/visualizer/stats.html',
+      open: true,
+      gzipSize: true,
+      brotliSize: true,
+    }) as Plugin;
+  }
+  return [];
+}

+ 34 - 0
jeecgboot-vue3/build/vite/proxy.ts

@@ -0,0 +1,34 @@
+/**
+ * Used to parse the .env.development proxy configuration
+ */
+import type { ProxyOptions } from 'vite';
+
+type ProxyItem = [string, string];
+
+type ProxyList = ProxyItem[];
+
+type ProxyTargetList = Record<string, ProxyOptions>;
+
+const httpsRE = /^https:\/\//;
+
+/**
+ * Generate proxy
+ * @param list
+ */
+export function createProxy(list: ProxyList = []) {
+  const ret: ProxyTargetList = {};
+  for (const [prefix, target] of list) {
+    const isHttps = httpsRE.test(target);
+
+    // https://github.com/http-party/node-http-proxy#options
+    ret[prefix] = {
+      target: target,
+      changeOrigin: true,
+      ws: true,
+      rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''),
+      // https is require secure=false
+      ...(isHttps ? { secure: false } : {}),
+    };
+  }
+  return ret;
+}

+ 32 - 0
jeecgboot-vue3/commitlint.config.js

@@ -0,0 +1,32 @@
+module.exports = {
+  ignores: [(commit) => commit.includes('init')],
+  extends: ['@commitlint/config-conventional'],
+  rules: {
+    'body-leading-blank': [2, 'always'],
+    'footer-leading-blank': [1, 'always'],
+    'header-max-length': [2, 'always', 108],
+    'subject-empty': [2, 'never'],
+    'type-empty': [2, 'never'],
+    'type-enum': [
+      2,
+      'always',
+      [
+        'feat',
+        'fix',
+        'perf',
+        'style',
+        'docs',
+        'test',
+        'refactor',
+        'build',
+        'ci',
+        'chore',
+        'revert',
+        'wip',
+        'workflow',
+        'types',
+        'release',
+      ],
+    ],
+  },
+};

+ 181 - 0
jeecgboot-vue3/index.html

@@ -0,0 +1,181 @@
+<!DOCTYPE html>
+<html lang="zh_CN" id="htmlRoot">
+  <head>
+    <meta charset="UTF-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <meta name="renderer" content="webkit" />
+    <meta
+      name="viewport"
+      content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
+    />
+
+    <title><%= title %></title>
+    <link rel="icon" href="/logo.png" />
+    <!-- 全局配置 -->
+    <script>
+      window._CONFIG = {};
+    </script>
+  </head>
+  <body>
+    <script>
+      (() => {
+        var htmlRoot = document.getElementById('htmlRoot');
+        var theme = window.localStorage.getItem('__APP__DARK__MODE__');
+        if (htmlRoot && theme) {
+          htmlRoot.setAttribute('data-theme', theme);
+          theme = htmlRoot = null;
+        }
+      })();
+    </script>
+    <div id="app">
+      <style>
+        html[data-theme='dark'] .app-loading {
+          background-color: #2c344a;
+        }
+
+        html[data-theme='dark'] .app-loading .app-loading-title {
+          color: rgba(255, 255, 255, 0.85);
+        }
+
+        .app-loading {
+          display: flex;
+          width: 100%;
+          height: 100%;
+          justify-content: center;
+          align-items: center;
+          flex-direction: column;
+          background-color: #f4f7f9;
+        }
+
+        .app-loading .app-loading-wrap {
+          position: absolute;
+          top: 50%;
+          left: 50%;
+          display: flex;
+          -webkit-transform: translate3d(-50%, -50%, 0);
+          transform: translate3d(-50%, -50%, 0);
+          justify-content: center;
+          align-items: center;
+          flex-direction: column;
+        }
+
+        .app-loading .dots {
+          display: flex;
+          padding: 98px;
+          justify-content: center;
+          align-items: center;
+        }
+
+        .app-loading .app-loading-title {
+          display: flex;
+          margin-top: 30px;
+          font-size: 30px;
+          color: rgba(0, 0, 0, 0.85);
+          justify-content: center;
+          align-items: center;
+        }
+
+        .app-loading .app-loading-logo {
+          display: block;
+          width: 90px;
+          margin: 0 auto;
+          margin-bottom: 20px;
+        }
+
+        .dot {
+          position: relative;
+          display: inline-block;
+          width: 48px;
+          height: 48px;
+          margin-top: 30px;
+          font-size: 32px;
+          transform: rotate(45deg);
+          box-sizing: border-box;
+          animation: antRotate 1.2s infinite linear;
+        }
+
+        .dot i {
+          position: absolute;
+          display: block;
+          width: 20px;
+          height: 20px;
+          background-color: #0065cc;
+          border-radius: 100%;
+          opacity: 0.3;
+          transform: scale(0.75);
+          animation: antSpinMove 1s infinite linear alternate;
+          transform-origin: 50% 50%;
+        }
+
+        .dot i:nth-child(1) {
+          top: 0;
+          left: 0;
+        }
+
+        .dot i:nth-child(2) {
+          top: 0;
+          right: 0;
+          -webkit-animation-delay: 0.4s;
+          animation-delay: 0.4s;
+        }
+
+        .dot i:nth-child(3) {
+          right: 0;
+          bottom: 0;
+          -webkit-animation-delay: 0.8s;
+          animation-delay: 0.8s;
+        }
+
+        .dot i:nth-child(4) {
+          bottom: 0;
+          left: 0;
+          -webkit-animation-delay: 1.2s;
+          animation-delay: 1.2s;
+        }
+        @keyframes antRotate {
+          to {
+            -webkit-transform: rotate(405deg);
+            transform: rotate(405deg);
+          }
+        }
+        @-webkit-keyframes antRotate {
+          to {
+            -webkit-transform: rotate(405deg);
+            transform: rotate(405deg);
+          }
+        }
+        @keyframes antSpinMove {
+          to {
+            opacity: 1;
+          }
+        }
+        @-webkit-keyframes antSpinMove {
+          to {
+            opacity: 1;
+          }
+        }
+      </style>
+      <div class="app-loading">
+        <div class="app-loading-wrap">
+          <img src="/resource/img/logo.png" class="app-loading-logo" alt="Logo" />
+          <div class="app-loading-dots">
+            <span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
+          </div>
+          <div class="app-loading-title"><%= title %></div>
+        </div>
+      </div>
+    </div>
+    <script type="module" src="/src/main.ts"></script>
+    <!-- 百度统计 -->
+    <script>
+      var _hmt = _hmt || [];
+      (function() {
+        var hm = document.createElement("script");
+        hm.src = "https://hm.baidu.com/hm.js?0febd9e3cacb3f627ddac64d52caac39";
+        var s = document.getElementsByTagName("script")[0];
+        s.parentNode.insertBefore(hm, s);
+      })();
+    </script>
+
+  </body>
+</html>

+ 36 - 0
jeecgboot-vue3/jest.config.mjs

@@ -0,0 +1,36 @@
+export default {
+  preset: 'ts-jest',
+  roots: ['<rootDir>/tests/'],
+  clearMocks: true,
+  moduleDirectories: ['node_modules', 'src'],
+  moduleFileExtensions: ['js', 'ts', 'vue', 'tsx', 'jsx', 'json', 'node'],
+  modulePaths: ['<rootDir>/src', '<rootDir>/node_modules'],
+  testMatch: [
+    '**/tests/**/*.[jt]s?(x)',
+    '**/?(*.)+(spec|test).[tj]s?(x)',
+    '(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$',
+  ],
+  testPathIgnorePatterns: [
+    '<rootDir>/tests/server/',
+    '<rootDir>/tests/__mocks__/',
+    '/node_modules/',
+  ],
+  transform: {
+    '^.+\\.tsx?$': 'ts-jest',
+  },
+  transformIgnorePatterns: ['<rootDir>/tests/__mocks__/', '/node_modules/'],
+  // A map from regular expressions to module names that allow to stub out resources with a single module
+  moduleNameMapper: {
+    '\\.(vs|fs|vert|frag|glsl|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
+      '<rootDir>/tests/__mocks__/fileMock.ts',
+    '\\.(sass|s?css|less)$': '<rootDir>/tests/__mocks__/styleMock.ts',
+    '\\?worker$': '<rootDir>/tests/__mocks__/workerMock.ts',
+    '^/@/(.*)$': '<rootDir>/src/$1',
+  },
+  testEnvironment: 'jsdom',
+  verbose: true,
+  collectCoverage: false,
+  coverageDirectory: 'coverage',
+  collectCoverageFrom: ['src/**/*.{js,ts,vue}'],
+  coveragePathIgnorePatterns: ['^.+\\.d\\.ts$'],
+};

+ 18 - 0
jeecgboot-vue3/mock/_createProductionServer.ts

@@ -0,0 +1,18 @@
+import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
+
+const modules = import.meta.glob('./**/*.ts', { eager: true });
+
+const mockModules: any[] = [];
+Object.keys(modules).forEach((key) => {
+  if (key.includes('/_')) {
+    return;
+  }
+  mockModules.push(...(modules as Recordable)[key].default);
+});
+
+/**
+ * Used in a production environment. Need to manually import all modules
+ */
+export function setupProdMockServer() {
+  createProdMockServer(mockModules);
+}

+ 63 - 0
jeecgboot-vue3/mock/_util.ts

@@ -0,0 +1,63 @@
+// Interface data format used to return a unified format
+
+export function resultSuccess<T = Recordable>(result: T, { message = 'ok' } = {}) {
+  return {
+    code: 0,
+    result,
+    message,
+    type: 'success',
+  };
+}
+
+export function resultPageSuccess<T = any>(
+  pageNo: number,
+  pageSize: number,
+  list: T[],
+  { message = 'ok' } = {}
+) {
+  const pageData = pagination(pageNo, pageSize, list);
+
+  return {
+    ...resultSuccess({
+      records: pageData,
+      total: list.length,
+    }),
+    message,
+  };
+}
+
+export function resultError(message = 'Request failed', { code = -1, result = null } = {}) {
+  return {
+    code,
+    result,
+    message,
+    type: 'error',
+  };
+}
+
+export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]): T[] {
+  const offset = (pageNo - 1) * Number(pageSize);
+  const ret =
+    offset + Number(pageSize) >= array.length
+      ? array.slice(offset, array.length)
+      : array.slice(offset, offset + Number(pageSize));
+  return ret;
+}
+
+export interface requestParams {
+  method: string;
+  body: any;
+  headers?: { authorization?: string };
+  query: any;
+}
+
+/**
+ * @description 本函数用于从request数据中获取token,请根据项目的实际情况修改
+ *
+ */
+export function getRequestToken({ headers }: requestParams): string | undefined {
+  return headers?.authorization;
+}
+
+//TODO 接口父路径(写死不够灵活)
+export const baseUrl = '/jeecgboot/mock';

+ 70 - 0
jeecgboot-vue3/mock/demo/account.ts

@@ -0,0 +1,70 @@
+import { MockMethod } from 'vite-plugin-mock';
+import { resultSuccess, resultError, baseUrl } from '../_util';
+import { ResultEnum } from '../../src/enums/httpEnum';
+const userInfo = {
+  name: 'Jeecg',
+  userid: '00000001',
+  email: 'test@gmail.com',
+  signature: '海纳百川,有容乃大',
+  introduction: '微笑着,努力着,欣赏着',
+  title: '交互专家',
+  group: '某某某事业群-某某平台部-某某技术部-UED',
+  tags: [
+    {
+      key: '0',
+      label: '很有想法的',
+    },
+    {
+      key: '1',
+      label: '专注设计',
+    },
+    {
+      key: '2',
+      label: '辣~',
+    },
+    {
+      key: '3',
+      label: '大长腿',
+    },
+    {
+      key: '4',
+      label: '川妹子',
+    },
+    {
+      key: '5',
+      label: '海纳百川',
+    },
+  ],
+  notifyCount: 12,
+  unreadCount: 11,
+  country: 'China',
+  address: 'Xiamen City 77',
+  phone: '0592-268888888',
+};
+
+export default [
+  {
+    url: `${baseUrl}/account/getAccountInfo`,
+    timeout: 1000,
+    method: 'get',
+    response: () => {
+      return resultSuccess(userInfo);
+    },
+  },
+  {
+    url: `${baseUrl}/user/sessionTimeout`,
+    method: 'post',
+    statusCode: 401,
+    response: () => {
+      return resultError();
+    },
+  },
+  {
+    url: '/basic-api/user/tokenExpired',
+    method: 'post',
+    statusCode: 200,
+    response: () => {
+      return resultError('Token Expired!', { code: ResultEnum.TIMEOUT as number });
+    },
+  },
+] as MockMethod[];

+ 37 - 0
jeecgboot-vue3/mock/demo/select-demo.ts

@@ -0,0 +1,37 @@
+import { MockMethod } from 'vite-plugin-mock';
+import { resultSuccess, baseUrl } from '../_util';
+
+const demoList = (keyword, count = 20) => {
+  const result = {
+    list: [] as any[],
+  };
+  for (let index = 0; index < count; index++) {
+    //根据搜索关键词做一下匹配
+    let name = `选项${index}`;
+    if(keyword && name.indexOf(keyword)!=-1){
+      result.list.push({
+        name: `选项${index}`,
+        id: `${index}`,
+      });
+    }else if(!keyword){
+      result.list.push({
+        name: `选项${index}`,
+        id: `${index}`,
+      });
+    }
+  }
+  return result;
+};
+
+export default [
+  {
+    url: `${baseUrl}/select/getDemoOptions`,
+    timeout: 1000,
+    method: 'get',
+    response: ({ query }) => {
+      const { keyword,count} = query;
+      console.log("查询条件:", keyword);
+      return resultSuccess(demoList(keyword,count));
+    },
+  },
+] as MockMethod[];

+ 298 - 0
jeecgboot-vue3/mock/demo/system.ts

@@ -0,0 +1,298 @@
+import { MockMethod } from 'vite-plugin-mock';
+import { resultError, resultPageSuccess, resultSuccess, baseUrl } from '../_util';
+
+const accountList = (() => {
+    const result: any[] = [];
+    for (let index = 0; index < 20; index++) {
+        result.push({
+            id: `${index}`,
+            account: '@first',
+            email: '@email',
+            nickname: '@cname()',
+            role: '@first',
+            createTime: '@datetime',
+            remark: '@cword(10,20)',
+            'status|1': ['0', '1'],
+        });
+    }
+    return result;
+})();
+
+const userList = (() => {
+    const result: any[] = [];
+    for (let index = 0; index < 20; index++) {
+        result.push({
+            id: `${index}`,
+            username: '@first',
+            email: '@email',
+            realname: '@cname()',
+            createTime: '@datetime',
+            remark: '@cword(10,20)',
+            avatar: 'https://q1.qlogo.cn/g?b=qq&nk=190848757&s=640'
+        });
+    }
+    return result;
+})();
+
+const roleList = (() => {
+    const result: any[] = [];
+    for (let index = 0; index < 4; index++) {
+        result.push({
+            id: index + 1,
+            orderNo: `${index + 1}`,
+            roleName: ['超级管理员', '管理员', '文章管理员', '普通用户'][index],
+            roleValue: '@first',
+            createTime: '@datetime',
+            remark: '@cword(10,20)',
+            menu: [['0', '1', '2'], ['0', '1'], ['0', '2'], ['2']][index],
+            'status|1': ['0', '1'],
+        });
+    }
+    return result;
+})();
+
+const newRoleList = (() => {
+    const result: any[] = [];
+    for (let index = 0; index < 4; index++) {
+        result.push({
+            id: index + 1,
+            orderNo: `${index + 1}`,
+            roleName: ['超级管理员', '管理员', '文章管理员', '普通用户'][index],
+            roleCode: '@first',
+            createTime: '@datetime',
+            remark: '@cword(10,20)'
+        });
+    }
+    return result;
+})();
+
+const testList = (() => {
+    const result: any[] = [];
+    for (let index = 0; index < 4; index++) {
+        result.push({
+            id: index + 1,
+            orderNo: `${index + 1}`,
+            testName: ['数据1', '数据2', '数据3', '数据4'][index],
+            testValue: '@first',
+            createTime: '@datetime'
+        });
+    }
+    return result;
+})();
+
+const tableDemoList = (() => {
+    const result: any[] = [];
+    for (let index = 0; index < 4; index++) {
+        result.push({
+            id: index + 1,
+            orderCode: '2008200' + `${index + 1}`,
+            orderMoney: '@natural(1000,3000)',
+            ctype: '@natural(1,2)',
+            content: '@cword(10,20)',
+            orderDate: '@datetime'
+        });
+    }
+    return result;
+})();
+
+const deptList = (() => {
+    const result: any[] = [];
+    for (let index = 0; index < 3; index++) {
+        result.push({
+            id: `${index}`,
+            deptName: ['华东分部', '华南分部', '西北分部'][index],
+            orderNo: index + 1,
+            createTime: '@datetime',
+            remark: '@cword(10,20)',
+            'status|1': ['0', '0', '1'],
+            children: (() => {
+                const children: any[] = [];
+                for (let j = 0; j < 4; j++) {
+                    children.push({
+                        id: `${index}-${j}`,
+                        deptName: ['研发部', '市场部', '商务部', '财务部'][j],
+                        orderNo: j + 1,
+                        createTime: '@datetime',
+                        remark: '@cword(10,20)',
+                        'status|1': ['0', '1'],
+                        parentDept: `${index}`,
+                        children: undefined,
+                    });
+                }
+                return children;
+            })(),
+        });
+    }
+    return result;
+})();
+
+const menuList = (() => {
+    const result: any[] = [];
+    for (let index = 0; index < 3; index++) {
+        result.push({
+            id: `${index}`,
+            icon: ['ion:layers-outline', 'ion:git-compare-outline', 'ion:tv-outline'][index],
+            component: 'LAYOUT',
+            type: '0',
+            menuName: ['Dashboard', '权限管理', '功能'][index],
+            permission: '',
+            orderNo: index + 1,
+            createTime: '@datetime',
+            'status|1': ['0', '0', '1'],
+            children: (() => {
+                const children: any[] = [];
+                for (let j = 0; j < 4; j++) {
+                    children.push({
+                        id: `${index}-${j}`,
+                        type: '1',
+                        menuName: ['菜单1', '菜单2', '菜单3', '菜单4'][j],
+                        icon: 'ion:document',
+                        permission: ['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index],
+                        component: [
+                            '/dashboard/welcome/index',
+                            '/dashboard/Analysis/index',
+                            '/dashboard/workbench/index',
+                            '/dashboard/test/index',
+                        ][j],
+                        orderNo: j + 1,
+                        createTime: '@datetime',
+                        'status|1': ['0', '1'],
+                        parentMenu: `${index}`,
+                        children: (() => {
+                            const children: any[] = [];
+                            for (let k = 0; k < 4; k++) {
+                                children.push({
+                                    id: `${index}-${j}-${k}`,
+                                    type: '2',
+                                    menuName: '按钮' + (j + 1) + '-' + (k + 1),
+                                    icon: '',
+                                    permission:
+                                        ['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index] +
+                                        ':btn' +
+                                        (k + 1),
+                                    component: [
+                                        '/dashboard/welcome/index',
+                                        '/dashboard/Analysis/index',
+                                        '/dashboard/workbench/index',
+                                        '/dashboard/test/index',
+                                    ][j],
+                                    orderNo: j + 1,
+                                    createTime: '@datetime',
+                                    'status|1': ['0', '1'],
+                                    parentMenu: `${index}-${j}`,
+                                    children: undefined,
+                                });
+                            }
+                            return children;
+                        })(),
+                    });
+                }
+                return children;
+            })(),
+        });
+    }
+    return result;
+})();
+
+export default [
+  {
+    url: `${baseUrl}/system/getAccountList`,
+    timeout: 100,
+    method: 'get',
+    response: ({ query }) => {
+      const { page = 1, pageSize = 20 } = query;
+      return resultPageSuccess(page, pageSize, accountList);
+    },
+  },
+  {
+    url: `${baseUrl}/sys/user/list`,
+    timeout: 100,
+    method: 'get',
+    response: ({ query }) => {
+      const { page = 1, pageSize = 20 } = query;
+      return resultPageSuccess(page, pageSize, userList);
+    },
+  },
+  {
+    url: `${baseUrl}/system/getRoleListByPage`,
+    timeout: 100,
+    method: 'get',
+    response: ({ query }) => {
+      const { page = 1, pageSize = 20 } = query;
+      return resultPageSuccess(page, pageSize, roleList);
+    },
+  },
+  {
+    url: `${baseUrl}/sys/role/list`,
+    timeout: 100,
+    method: 'get',
+    response: ({ query }) => {
+      const { page = 1, pageSize = 20 } = query;
+      return resultPageSuccess(page, pageSize, newRoleList);
+    },
+  },
+  {
+    url: `${baseUrl}/system/getTestListByPage`,
+    timeout: 100,
+    method: 'get',
+    response: ({ query }) => {
+      const { page = 1, pageSize = 20 } = query;
+      return resultPageSuccess(page, pageSize, testList);
+    },
+  },
+  {
+    url: `${baseUrl}/system/getDemoTableListByPage`,
+    timeout: 100,
+    method: 'get',
+    response: ({ query }) => {
+      const { page = 1, pageSize = 20 } = query;
+      return resultPageSuccess(page, pageSize, tableDemoList);
+    },
+  },
+  {
+    url: `${baseUrl}/system/setRoleStatus`,
+    timeout: 500,
+    method: 'post',
+    response: ({ query }) => {
+      const { id, status } = query;
+      return resultSuccess({ id, status });
+    },
+  },
+  {
+    url: `${baseUrl}/system/getAllRoleList`,
+    timeout: 100,
+    method: 'get',
+    response: () => {
+      return resultSuccess(roleList);
+    },
+  },
+  {
+    url: `${baseUrl}/system/getDeptList`,
+    timeout: 100,
+    method: 'get',
+    response: () => {
+      return resultSuccess(deptList);
+    },
+  },
+  {
+    url: `${baseUrl}/system/getMenuList`,
+    timeout: 100,
+    method: 'get',
+    response: () => {
+      return resultSuccess(menuList);
+    },
+  },
+  {
+    url: `${baseUrl}/system/accountExist`,
+    timeout: 500,
+    method: 'post',
+    response: ({ body }) => {
+      const { account } = body || {};
+      if (account && account.indexOf('admin') !== -1) {
+        return resultError('该字段不能包含admin');
+      } else {
+        return resultSuccess(`${account} can use`);
+      }
+    },
+  },
+] as MockMethod[];

+ 54 - 0
jeecgboot-vue3/mock/demo/table-demo.ts

@@ -0,0 +1,54 @@
+import { MockMethod } from 'vite-plugin-mock';
+import { Random } from 'mockjs';
+import { resultPageSuccess, baseUrl } from '../_util';
+
+function getRandomPics(count = 10): string[] {
+  const arr: string[] = [];
+  for (let i = 0; i < count; i++) {
+    arr.push(Random.image('800x600', Random.color(), Random.color(), Random.title()));
+  }
+  return arr;
+}
+
+const demoList = (() => {
+  const result: any[] = [];
+  for (let index = 0; index < 200; index++) {
+    result.push({
+      id: `${index}`,
+      beginTime: '@datetime',
+      endTime: '@datetime',
+      address: '@city()',
+      name: '@cname()',
+      name1: '@cname()',
+      name2: '@cname()',
+      name3: '@cname()',
+      name4: '@cname()',
+      name5: '@cname()',
+      name6: '@cname()',
+      name7: '@cname()',
+      name8: '@cname()',
+      avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()),
+      imgArr: getRandomPics(Math.ceil(Math.random() * 3) + 1),
+      imgs: getRandomPics(Math.ceil(Math.random() * 3) + 1),
+      age: Math.ceil(Math.random() * 30) + 1,
+      score: Math.ceil(Math.random() * 80) + 1,
+      date: `@date('yyyy-MM-dd')`,
+      time: `@time('HH:mm')`,
+      'no|100000-10000000': 100000,
+      'status|1': ['normal', 'enable', 'disable'],
+    });
+  }
+  return result;
+})();
+
+export default [
+  {
+    url: `${baseUrl}/table/getDemoList`,
+    timeout: 100,
+    method: 'get',
+    response: ({ query }) => {
+      const { page = 1, pageSize = 20 } = query;
+      return resultPageSuccess(page, pageSize, demoList);
+    },
+  },
+] as MockMethod[];

+ 38 - 0
jeecgboot-vue3/mock/demo/tree-demo.ts

@@ -0,0 +1,38 @@
+import { MockMethod } from 'vite-plugin-mock';
+import { resultSuccess, baseUrl } from '../_util';
+
+const demoTreeList = (keyword) => {
+  const result = {
+    list: [] as Recordable[],
+  };
+  for (let index = 0; index < 5; index++) {
+    const children: Recordable[] = [];
+    for (let j = 0; j < 3; j++) {
+      children.push({
+        title: `${keyword ?? ''}选项${index}-${j}`,
+        value: `${index}-${j}`,
+        key: `${index}-${j}`,
+      });
+    }
+    result.list.push({
+      title: `${keyword ?? ''}选项${index}`,
+      value: `${index}`,
+      key: `${index}`,
+      children,
+    });
+  }
+  return result;
+};
+
+export default [
+  {
+    url: `${baseUrl}/tree/getDemoOptions`,
+    timeout: 1000,
+    method: 'get',
+    response: ({ query }) => {
+      const { keyword } = query;
+      console.log("查询条件:", keyword);
+      return resultSuccess(demoTreeList(keyword));
+    },
+  },
+] as MockMethod[];

+ 270 - 0
jeecgboot-vue3/mock/sys/menu.ts

@@ -0,0 +1,270 @@
+import { resultSuccess, resultError, getRequestToken, requestParams,baseUrl} from '../_util';
+import { MockMethod } from 'vite-plugin-mock';
+import { createFakeUserList } from './user';
+
+// single
+const dashboardRoute = {
+  path: '/dashboard',
+  name: 'Dashboard',
+  component: 'LAYOUT',
+  redirect: '/dashboard/analysis',
+  meta: {
+    title: 'routes.dashboard.dashboard',
+    hideChildrenInMenu: true,
+    icon: 'bx:bx-home',
+  },
+  children: [
+    {
+      path: 'analysis',
+      name: 'Analysis',
+      component: '/dashboard/Analysis/index',
+      meta: {
+        hideMenu: true,
+        hideBreadcrumb: true,
+        title: 'routes.dashboard.analysis',
+        currentActiveMenu: '/dashboard',
+        icon: 'bx:bx-home',
+      },
+    },
+    {
+      path: 'workbench',
+      name: 'Workbench',
+      component: '/dashboard/workbench/index',
+      meta: {
+        hideMenu: true,
+        hideBreadcrumb: true,
+        title: 'routes.dashboard.workbench',
+        currentActiveMenu: '/dashboard',
+        icon: 'bx:bx-home',
+      },
+    },
+  ],
+};
+
+const backRoute = {
+  path: 'back',
+  name: 'PermissionBackDemo',
+  meta: {
+    title: 'routes.demo.permission.back',
+  },
+
+  children: [
+    {
+      path: 'page',
+      name: 'BackAuthPage',
+      component: '/demo/permission/back/index',
+      meta: {
+        title: 'routes.demo.permission.backPage',
+      },
+    },
+    {
+      path: 'btn',
+      name: 'BackAuthBtn',
+      component: '/demo/permission/back/Btn',
+      meta: {
+        title: 'routes.demo.permission.backBtn',
+      },
+    },
+  ],
+};
+
+const authRoute = {
+  path: '/permission',
+  name: 'Permission',
+  component: 'LAYOUT',
+  redirect: '/permission/front/page',
+  meta: {
+    icon: 'carbon:user-role',
+    title: 'routes.demo.permission.permission',
+  },
+  children: [backRoute],
+};
+
+const levelRoute = {
+  path: '/level',
+  name: 'Level',
+  component: 'LAYOUT',
+  redirect: '/level/menu1/menu1-1',
+  meta: {
+    icon: 'carbon:user-role',
+    title: 'routes.demo.level.level',
+  },
+
+  children: [
+    {
+      path: 'menu1',
+      name: 'Menu1Demo',
+      meta: {
+        title: 'Menu1',
+      },
+      children: [
+        {
+          path: 'menu1-1',
+          name: 'Menu11Demo',
+          meta: {
+            title: 'Menu1-1',
+          },
+          children: [
+            {
+              path: 'menu1-1-1',
+              name: 'Menu111Demo',
+              component: '/demo/level/Menu111',
+              meta: {
+                title: 'Menu111',
+              },
+            },
+          ],
+        },
+        {
+          path: 'menu1-2',
+          name: 'Menu12Demo',
+          component: '/demo/level/Menu12',
+          meta: {
+            title: 'Menu1-2',
+          },
+        },
+      ],
+    },
+    {
+      path: 'menu2',
+      name: 'Menu2Demo',
+      component: '/demo/level/Menu2',
+      meta: {
+        title: 'Menu2',
+      },
+    },
+  ],
+};
+
+const sysRoute = {
+  path: '/system',
+  name: 'System',
+  component: 'LAYOUT',
+  redirect: '/system/account',
+  meta: {
+    icon: 'ion:settings-outline',
+    title: 'routes.demo.system.moduleName',
+  },
+  children: [
+    {
+      path: 'account',
+      name: 'AccountManagement',
+      meta: {
+        title: 'routes.demo.system.account',
+        ignoreKeepAlive: true,
+      },
+      component: '/demo/system/account/index',
+    },
+    {
+      path: 'account_detail/:id',
+      name: 'AccountDetail',
+      meta: {
+        hideMenu: true,
+        title: 'routes.demo.system.account_detail',
+        ignoreKeepAlive: true,
+        showMenu: false,
+        currentActiveMenu: '/system/account',
+      },
+      component: '/demo/system/account/AccountDetail',
+    },
+    {
+      path: 'role',
+      name: 'RoleManagement',
+      meta: {
+        title: 'routes.demo.system.role',
+        ignoreKeepAlive: true,
+      },
+      component: '/demo/system/role/index',
+    },
+
+    {
+      path: 'menu',
+      name: 'MenuManagement',
+      meta: {
+        title: 'routes.demo.system.menu',
+        ignoreKeepAlive: true,
+      },
+      component: '/demo/system/menu/index',
+    },
+    {
+      path: 'dept',
+      name: 'DeptManagement',
+      meta: {
+        title: 'routes.demo.system.dept',
+        ignoreKeepAlive: true,
+      },
+      component: '/demo/system/dept/index',
+    },
+    {
+      path: 'changePassword',
+      name: 'ChangePassword',
+      meta: {
+        title: 'routes.demo.system.password',
+        ignoreKeepAlive: true,
+      },
+      component: '/demo/system/password/index',
+    },
+  ],
+};
+
+const linkRoute = {
+  path: '/link',
+  name: 'Link',
+  component: 'LAYOUT',
+  meta: {
+    icon: 'ion:tv-outline',
+    title: 'routes.demo.iframe.frame',
+  },
+  children: [
+    {
+      path: 'doc',
+      name: 'Doc',
+      meta: {
+        title: 'routes.demo.iframe.doc',
+        frameSrc: 'https://vvbin.cn/doc-next/',
+      },
+    },
+    {
+      path: 'https://vvbin.cn/doc-next/',
+      name: 'DocExternal',
+      component: 'LAYOUT',
+      meta: {
+        title: 'routes.demo.iframe.docExternal',
+      },
+    },
+  ],
+};
+
+export default [
+  {
+    url: `${baseUrl}/sys/permission/getUserPermissionByToken`,
+    timeout: 1000,
+    method: 'get',
+    response: (request: requestParams) => {
+      const token = getRequestToken(request);
+      if (!token) {
+        return resultError('Invalid token!');
+      }
+      const checkUser = createFakeUserList().find((item) => item.token === token);
+      if (!checkUser) {
+        return resultError('Invalid user token!');
+      }
+      const id = checkUser.userId;
+      let menu: Object[];
+      switch (id) {
+        case '1':
+          dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[0].path;
+          menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute];
+          break;
+        case '2':
+          dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[1].path;
+          menu = [dashboardRoute, authRoute, levelRoute, linkRoute];
+          break;
+        default:
+          menu = [];
+      }
+
+      return resultSuccess(menu);
+    },
+  },
+] as MockMethod[];

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 124 - 0
jeecgboot-vue3/mock/sys/user.ts


+ 0 - 0
jeecgboot-vue3/npm


+ 173 - 0
jeecgboot-vue3/package.json

@@ -0,0 +1,173 @@
+{
+  "name": "jeecgboot-vue3",
+  "version": "3.7.0",
+  "author": {
+    "name": "北京国炬信息技术有限公司",
+    "email": "jeecgos@163.com",
+    "url": "https://www.jeecg.com"
+  },
+  "scripts": {
+    "pinstall": "pnpm install",
+    "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
+    "dev": "vite",
+    "build": "cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=8192 vite build && esno ./build/script/postBuild.ts",
+    "build:report": "pnpm clean:cache && cross-env REPORT=true npm run build",
+    "preview": "npm run build && vite preview",
+    "reinstall": "rimraf pnpm-lock.yaml && rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run install",
+    "clean:lib": "rimraf node_modules",
+    "gen:icon": "esno ./build/generate/icon/index.ts",
+    "batch:prettier": "prettier --write  \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
+    "upgrade:log": "conventional-changelog -p angular -i CHANGELOG.md -s",
+    "husky:install": "husky install"
+  },
+  "dependencies": {
+    "@jeecg/online": "3.7.0-beta",
+    "@iconify/iconify": "^3.1.1",
+    "@ant-design/colors": "^7.0.2",
+    "@ant-design/icons-vue": "^7.0.1",
+    "@vue/shared": "^3.4.19",
+    "@vueuse/core": "^10.8.0",
+    "@tinymce/tinymce-vue": "4.0.7",
+    "@zxcvbn-ts/core": "^3.0.4",
+    "ant-design-vue": "^4.1.2",
+    "axios": "^1.6.7",
+    "china-area-data": "^5.0.1",
+    "clipboard": "^2.0.11",
+    "codemirror": "^5.65.3",
+    "cron-parser": "^4.9.0",
+    "cropperjs": "^1.6.1",
+    "crypto-js": "^4.2.0",
+    "dayjs": "^1.11.10",
+    "dom-align": "^1.12.4",
+    "echarts": "^5.4.3",
+    "emoji-mart-vue-fast": "^15.0.1",
+    "enquire.js": "^2.1.6",
+    "intro.js": "^7.2.0",
+    "lodash-es": "^4.17.21",
+    "lodash.get": "^4.4.2",
+    "markdown-it": "^14.0.0",
+    "markdown-it-link-attributes": "^4.0.1",
+    "event-source-polyfill": "^1.0.31",
+    "highlight.js": "^11.9.0",
+    "@traptitech/markdown-it-katex": "^3.6.0",
+    "md5": "^2.3.0",
+    "mockjs": "^1.1.0",
+    "nprogress": "^0.2.0",
+    "path-to-regexp": "^6.2.1",
+    "pinia": "2.1.7",
+    "print-js": "^1.6.0",
+    "qs": "^6.11.2",
+    "qrcode": "^1.5.3",
+    "resize-observer-polyfill": "^1.5.1",
+    "showdown": "^2.1.0",
+    "sortablejs": "^1.15.2",
+    "tinymce": "6.6.2",
+    "vditor": "^3.9.9",
+    "vue": "^3.4.19",
+    "vue-cropper": "^0.6.4",
+    "vue-cropperjs": "^5.0.0",
+    "vue-i18n": "^9.9.1",
+    "vue-infinite-scroll": "^2.0.2",
+    "vue-print-nb-jeecg": "^1.0.12",
+    "vue-router": "^4.3.0",
+    "vue-types": "^5.1.1",
+    "vuedraggable": "^4.1.0",
+    "vxe-table": "4.6.17",
+    "vxe-table-plugin-antd": "4.0.7",
+    "xe-utils": "3.5.26",
+    "xss": "^1.0.14"
+  },
+  "devDependencies": {
+    "@commitlint/cli": "^18.6.1",
+    "@commitlint/config-conventional": "^18.6.2",
+    "@iconify/json": "^2.2.185",
+    "@purge-icons/generated": "^0.10.0",
+    "@types/codemirror": "^5.60.15",
+    "@types/crypto-js": "^4.2.2",
+    "@types/fs-extra": "^11.0.4",
+    "@types/inquirer": "^9.0.7",
+    "@types/intro.js": "^5.1.5",
+    "@types/jest": "^29.5.12",
+    "@types/lodash-es": "^4.17.12",
+    "@types/mockjs": "^1.0.10",
+    "@types/node": "^20.11.19",
+    "@types/nprogress": "^0.2.3",
+    "@types/qrcode": "^1.5.5",
+    "@types/qs": "^6.9.11",
+    "@types/showdown": "^2.0.6",
+    "@types/sortablejs": "^1.15.8",
+    "@typescript-eslint/eslint-plugin": "^6.17.0",
+    "@typescript-eslint/parser": "^6.17.0",
+    "@vitejs/plugin-vue": "^4.3.3",
+    "@vitejs/plugin-vue-jsx": "^3.1.0",
+    "@vue/compiler-sfc": "^3.4.4",
+    "@vue/test-utils": "^2.4.4",
+    "autoprefixer": "^10.4.17",
+    "commitizen": "^4.3.0",
+    "conventional-changelog-cli": "^4.1.0",
+    "cross-env": "^7.0.3",
+    "cz-git": "^1.8.0",
+    "czg": "^1.8.0",
+    "dotenv": "^16.3.1",
+    "eslint": "^8.56.0",
+    "eslint-config-prettier": "^9.1.0",
+    "eslint-define-config": "^2.1.0",
+    "eslint-plugin-jest": "^27.9.0",
+    "eslint-plugin-prettier": "^5.1.3",
+    "eslint-plugin-vue": "^9.21.1",
+    "esno": "^4.0.0",
+    "fs-extra": "^11.2.0",
+    "http-server": "^14.1.1",
+    "husky": "^8.0.3",
+    "inquirer": "^9.2.15",
+    "is-ci": "^3.0.1",
+    "jest": "^29.7.0",
+    "less": "^4.2.0",
+    "lint-staged": "15.2.2",
+    "npm-run-all": "^4.1.5",
+    "picocolors": "^1.0.0",
+    "postcss": "^8.4.35",
+    "postcss-html": "^1.6.0",
+    "postcss-less": "^6.0.0",
+    "prettier": "^3.2.5",
+    "pretty-quick": "^4.0.0",
+    "rimraf": "^5.0.5",
+    "rollup": "^4.12.0",
+    "rollup-plugin-visualizer": "^5.12.0",
+    "stylelint": "^16.2.1",
+    "stylelint-config-prettier": "^9.0.5",
+    "stylelint-config-recommended": "^14.0.0",
+    "stylelint-config-recommended-vue": "^1.5.0",
+    "stylelint-config-standard": "^36.0.0",
+    "stylelint-order": "^6.0.4",
+    "ts-jest": "^29.1.2",
+    "ts-node": "^10.9.2",
+    "typescript": "^4.9.5",
+    "vite": "^5.2.11",
+    "vite-plugin-compression": "^0.5.1",
+    "vite-plugin-html": "^3.2.2",
+    "vite-plugin-mkcert": "^1.17.3",
+    "vite-plugin-mock": "^2.9.6",
+    "vite-plugin-optimize-persist": "^0.1.2",
+    "vite-plugin-package-config": "^0.1.1",
+    "vite-plugin-purge-icons": "^0.10.0",
+    "vite-plugin-svg-icons": "^2.0.1",
+    "@rys-fe/vite-plugin-theme": "^0.8.6",
+    "vite-plugin-vue-setup-extend-plus": "^0.1.0",
+    "unocss": "^0.58.5",
+    "vue-eslint-parser": "^9.4.2",
+    "vue-tsc": "^1.8.27"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/jeecgboot/JeecgBoot.git"
+  },
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/jeecgboot/JeecgBoot/issues"
+  },
+  "homepage": "https://www.jeecg.com",
+  "engines": {
+    "node": "^12 || >=14"
+  }
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 14103 - 0
jeecgboot-vue3/pnpm-lock.yaml


+ 5 - 0
jeecgboot-vue3/postcss.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  plugins: {
+    autoprefixer: {},
+  },
+};

+ 20 - 0
jeecgboot-vue3/prettier.config.js

@@ -0,0 +1,20 @@
+module.exports = {
+  printWidth: 150,
+  tabWidth: 2,
+  useTabs: false,
+  semi: true, //语句末尾使用分号
+  vueIndentScriptAndStyle: true,
+  singleQuote: true, // 使用单引号
+  quoteProps: 'as-needed',
+  bracketSpacing: true,
+  trailingComma: 'es5',
+  jsxBracketSameLine: false,
+  jsxSingleQuote: false,
+  arrowParens: 'always',
+  insertPragma: false,
+  requirePragma: false,
+  proseWrap: 'never',
+  htmlWhitespaceSensitivity: 'strict',
+  endOfLine: 'auto',
+  rangeStart: 0,
+};

BIN
jeecgboot-vue3/public/favicon.ico


BIN
jeecgboot-vue3/public/logo.png


BIN
jeecgboot-vue3/public/resource/img/logo.png


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
jeecgboot-vue3/public/resource/js/iconfont.js


+ 419 - 0
jeecgboot-vue3/public/resource/tinymce/langs/en.js

@@ -0,0 +1,419 @@
+tinymce.addI18n('es', {
+  Redo: 'Rehacer',
+  Undo: 'Deshacer',
+  Cut: 'Cortar',
+  Copy: 'Copiar',
+  Paste: 'Pegar',
+  'Select all': 'Seleccionar todo',
+  'New document': 'Nuevo documento',
+  Ok: 'Ok',
+  Cancel: 'Cancelar',
+  'Visual aids': 'Ayudas visuales',
+  Bold: 'Negrita',
+  Italic: 'Cursiva',
+  Underline: 'Subrayado',
+  Strikethrough: 'Tachado',
+  Superscript: 'Super\u00edndice',
+  Subscript: 'Sub\u00edndice',
+  'Clear formatting': 'Limpiar formato',
+  'Align left': 'Alinear a la izquierda',
+  'Align center': 'Alinear al centro',
+  'Align right': 'Alinear a la derecha',
+  Justify: 'Justificar',
+  'Bullet list': 'Lista de vi\u00f1etas',
+  'Numbered list': 'Lista numerada',
+  'Decrease indent': 'Disminuir sangr\u00eda',
+  'Increase indent': 'Incrementar sangr\u00eda',
+  Close: 'Cerrar',
+  Formats: 'Formatos',
+  "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": 'Su navegador no es compatible con el acceso directo al portapapeles. Use las teclas Crtl+X\/C\/V de su teclado.',
+  Headers: 'Encabezados',
+  'Header 1': 'Encabezado 1',
+  'Header 2': 'Encabezado 2',
+  'Header 3': 'Encabezado 3',
+  'Header 4': 'Encabezado 4',
+  'Header 5': 'Encabezado 5',
+  'Header 6': 'Encabezado 6',
+  Headings: 'Encabezados',
+  'Heading 1': 'Encabezado 1',
+  'Heading 2': 'Encabezado 2',
+  'Heading 3': 'Encabezado 3',
+  'Heading 4': 'Encabezado 4',
+  'Heading 5': 'Encabezado 5',
+  'Heading 6': 'Encabezado 6',
+  Preformatted: 'Con formato previo',
+  Div: 'Div',
+  Pre: 'Pre',
+  Code: 'C\u00f3digo',
+  Paragraph: 'P\u00e1rrafo',
+  Blockquote: 'Blockquote',
+  Inline: 'Alineado',
+  Blocks: 'Bloques',
+  'Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.': 'Pegar est\u00e1 ahora en modo de texto plano. El contenido se pegar\u00e1 como texto plano hasta que desactive esta opci\u00f3n.',
+  Fonts: 'Fuentes',
+  'Font Sizes': 'Tama\u00f1os de fuente',
+  Class: 'Clase',
+  'Browse for an image': 'Buscar una imagen',
+  OR: 'OR',
+  'Drop an image here': 'Arrastre una imagen aqu\u00ed',
+  Upload: 'Cargar',
+  Block: 'Bloque',
+  Align: 'Alinear',
+  Default: 'Por defecto',
+  Circle: 'C\u00edrculo',
+  Disc: 'Disco',
+  Square: 'Cuadrado',
+  'Lower Alpha': 'Inferior Alfa',
+  'Lower Greek': 'Inferior Griega',
+  'Lower Roman': 'Inferior Romana',
+  'Upper Alpha': 'Superior Alfa',
+  'Upper Roman': 'Superior Romana',
+  'Anchor...': 'Anclaje...',
+  Name: 'Nombre',
+  Id: 'Id',
+  'Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.': 'Deber\u00eda comenzar por una letra, seguida solo de letras, n\u00fameros, guiones, puntos, dos puntos o guiones bajos.',
+  'You have unsaved changes are you sure you want to navigate away?': 'Tiene cambios sin guardar. \u00bfEst\u00e1 seguro de que quiere salir?',
+  'Restore last draft': 'Restaurar el \u00faltimo borrador',
+  'Special character...': 'Car\u00e1cter especial...',
+  'Source code': 'C\u00f3digo fuente',
+  'Insert\/Edit code sample': 'Insertar\/editar c\u00f3digo de prueba',
+  Language: 'Idioma',
+  'Code sample...': 'Ejemplo de c\u00f3digo...',
+  'Color Picker': 'Selector de colores',
+  R: 'R',
+  G: 'V',
+  B: 'A',
+  'Left to right': 'De izquierda a derecha',
+  'Right to left': 'De derecha a izquierda',
+  'Emoticons...': 'Emoticones...',
+  'Metadata and Document Properties': 'Metadatos y propiedades del documento',
+  Title: 'T\u00edtulo',
+  Keywords: 'Palabras clave',
+  Description: 'Descripci\u00f3n',
+  Robots: 'Robots',
+  Author: 'Autor',
+  Encoding: 'Codificaci\u00f3n',
+  Fullscreen: 'Pantalla completa',
+  Action: 'Acci\u00f3n',
+  Shortcut: 'Atajo',
+  Help: 'Ayuda',
+  Address: 'Direcci\u00f3n',
+  'Focus to menubar': 'Enfocar la barra del men\u00fa',
+  'Focus to toolbar': 'Enfocar la barra de herramientas',
+  'Focus to element path': 'Enfocar la ruta del elemento',
+  'Focus to contextual toolbar': 'Enfocar la barra de herramientas contextual',
+  'Insert link (if link plugin activated)': 'Insertar enlace (si el complemento de enlace est\u00e1 activado)',
+  'Save (if save plugin activated)': 'Guardar (si el componente de salvar est\u00e1 activado)',
+  'Find (if searchreplace plugin activated)': 'Buscar (si el complemento buscar-remplazar est\u00e1 activado)',
+  'Plugins installed ({0}):': 'Plugins instalados ({0}):',
+  'Premium plugins:': 'Complementos premium:',
+  'Learn more...': 'Aprende m\u00e1s...',
+  'You are using {0}': 'Estas usando {0}',
+  Plugins: 'Complementos',
+  'Handy Shortcuts': 'Accesos directos',
+  'Horizontal line': 'L\u00ednea horizontal',
+  'Insert\/edit image': 'Insertar\/editar imagen',
+  'Image description': 'Descripci\u00f3n de la imagen',
+  Source: 'Enlace',
+  Dimensions: 'Dimensiones',
+  'Constrain proportions': 'Restringir proporciones',
+  General: 'General',
+  Advanced: 'Avanzado',
+  Style: 'Estilo',
+  'Vertical space': 'Espacio vertical',
+  'Horizontal space': 'Espacio horizontal',
+  Border: 'Borde',
+  'Insert image': 'Insertar imagen',
+  'Image...': 'Imagen...',
+  'Image list': 'Lista de im\u00e1genes',
+  'Rotate counterclockwise': 'Girar a la izquierda',
+  'Rotate clockwise': 'Girar a la derecha',
+  'Flip vertically': 'Invertir verticalmente',
+  'Flip horizontally': 'Invertir horizontalmente',
+  'Edit image': 'Editar imagen',
+  'Image options': 'Opciones de imagen',
+  'Zoom in': 'Acercar',
+  'Zoom out': 'Alejar',
+  Crop: 'Recortar',
+  Resize: 'Redimensionar',
+  Orientation: 'Orientaci\u00f3n',
+  Brightness: 'Brillo',
+  Sharpen: 'Forma',
+  Contrast: 'Contraste',
+  'Color levels': 'Niveles de color',
+  Gamma: 'Gamma',
+  Invert: 'Invertir',
+  Apply: 'Aplicar',
+  Back: 'Atr\u00e1s',
+  'Insert date\/time': 'Insertar fecha\/hora',
+  'Date\/time': 'Fecha\/hora',
+  'Insert\/Edit Link': 'Insertar\/editar enlace',
+  'Insert\/edit link': 'Insertar\/editar enlace',
+  'Text to display': 'Texto para mostrar',
+  Url: 'URL',
+  'Open link in...': 'Abrir enlace en...',
+  'Current window': 'Ventana actual',
+  None: 'Ninguno',
+  'New window': 'Nueva ventana',
+  'Remove link': 'Quitar enlace',
+  Anchors: 'Anclas',
+  'Link...': 'Enlace...',
+  'Paste or type a link': 'Pega o introduce un enlace',
+  'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?': 'El enlace que has introducido no parece ser una direcci\u00f3n de correo electr\u00f3nico. Quieres a\u00f1adir el prefijo necesario mailto: ?',
+  'The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?': 'El enlace que has introducido no parece ser una enlace externo. Quieres a\u00f1adir el prefijo necesario http:\/\/ ?',
+  'Link list': 'Lista de enlaces',
+  'Insert video': 'Insertar video',
+  'Insert\/edit video': 'Insertar\/editar video',
+  'Insert\/edit media': 'Insertar\/editar medio',
+  'Alternative source': 'Enlace alternativo',
+  'Alternative source URL': 'Origen de URL alternativo',
+  'Media poster (Image URL)': 'P\u00f3ster de medio (URL de imagen)',
+  'Paste your embed code below:': 'Pega tu c\u00f3digo embebido debajo',
+  Embed: 'Incrustado',
+  'Media...': 'Medios...',
+  'Nonbreaking space': 'Espacio fijo',
+  'Page break': 'Salto de p\u00e1gina',
+  'Paste as text': 'Pegar como texto',
+  Preview: 'Previsualizar',
+  'Print...': 'Imprimir...',
+  Save: 'Guardar',
+  Find: 'Buscar',
+  'Replace with': 'Reemplazar con',
+  Replace: 'Reemplazar',
+  'Replace all': 'Reemplazar todo',
+  Previous: 'Anterior',
+  Next: 'Siguiente',
+  'Find and replace...': 'Buscar y reemplazar...',
+  'Could not find the specified string.': 'No se encuentra la cadena de texto especificada',
+  'Match case': 'Coincidencia exacta',
+  'Find whole words only': 'Solo palabras completas',
+  'Spell check': 'Revisar ortograf\u00eda',
+  Ignore: 'Ignorar',
+  'Ignore all': 'Ignorar todos',
+  Finish: 'Finalizar',
+  'Add to Dictionary': 'A\u00f1adir al Diccionario',
+  'Insert table': 'Insertar tabla',
+  'Table properties': 'Propiedades de la tabla',
+  'Delete table': 'Eliminar tabla',
+  Cell: 'Celda',
+  Row: 'Fila',
+  Column: 'Columna',
+  'Cell properties': 'Propiedades de la celda',
+  'Merge cells': 'Combinar celdas',
+  'Split cell': 'Dividir celdas',
+  'Insert row before': 'Insertar fila antes',
+  'Insert row after': 'Insertar fila despu\u00e9s ',
+  'Delete row': 'Eliminar fila',
+  'Row properties': 'Propiedades de la fila',
+  'Cut row': 'Cortar fila',
+  'Copy row': 'Copiar fila',
+  'Paste row before': 'Pegar la fila antes',
+  'Paste row after': 'Pegar la fila despu\u00e9s',
+  'Insert column before': 'Insertar columna antes',
+  'Insert column after': 'Insertar columna despu\u00e9s',
+  'Delete column': 'Eliminar columna',
+  Cols: 'Columnas',
+  Rows: 'Filas',
+  Width: 'Ancho',
+  Height: 'Alto',
+  'Cell spacing': 'Espacio entre celdas',
+  'Cell padding': 'Relleno de celda',
+  'Show caption': 'Mostrar t\u00edtulo',
+  Left: 'Izquierda',
+  Center: 'Centrado',
+  Right: 'Derecha',
+  'Cell type': 'Tipo de celda',
+  Scope: '\u00c1mbito',
+  Alignment: 'Alineaci\u00f3n',
+  'H Align': 'Alineamiento Horizontal',
+  'V Align': 'Alineamiento Vertical',
+  Top: 'Arriba',
+  Middle: 'Centro',
+  Bottom: 'Abajo',
+  'Header cell': 'Celda de la cebecera',
+  'Row group': 'Grupo de filas',
+  'Column group': 'Grupo de columnas',
+  'Row type': 'Tipo de fila',
+  Header: 'Cabecera',
+  Body: 'Cuerpo',
+  Footer: 'Pie de p\u00e1gina',
+  'Border color': 'Color del borde',
+  'Insert template...': 'Insertar plantilla...',
+  Templates: 'Plantillas',
+  Template: 'Plantilla',
+  'Text color': 'Color del texto',
+  'Background color': 'Color de fondo',
+  'Custom...': 'Personalizar...',
+  'Custom color': 'Color personalizado',
+  'No color': 'Sin color',
+  'Remove color': 'Quitar color',
+  'Table of Contents': 'Tabla de contenidos',
+  'Show blocks': 'Mostrar bloques',
+  'Show invisible characters': 'Mostrar caracteres invisibles',
+  'Word count': 'Contar palabras',
+  Count: 'Recuento',
+  Document: 'Documento',
+  Selection: 'Selecci\u00f3n',
+  Words: 'Palabras',
+  'Words: {0}': 'Palabras: {0}',
+  '{0} words': '{0} palabras',
+  File: 'Archivo',
+  Edit: 'Editar',
+  Insert: 'Insertar',
+  View: 'Ver',
+  Format: 'Formato',
+  Table: 'Tabla',
+  Tools: 'Herramientas',
+  'Powered by {0}': 'Desarrollado por {0}',
+  'Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help': '\u00c1rea de texto enriquecido. Pulse ALT-F9 para el menu. Pulse ALT-F10 para la barra de herramientas. Pulse ALT-0 para ayuda',
+  'Image title': 'Titulo de imagen',
+  'Border width': 'Ancho de borde',
+  'Border style': 'Estilo de borde',
+  Error: 'Error',
+  Warn: 'Advertencia',
+  Valid: 'V\u00e1lido',
+  'To open the popup, press Shift+Enter': 'Para abrir el elemento emergente, pulse May\u00fas+Intro',
+  'Rich Text Area. Press ALT-0 for help.': '\u00c1rea de texto enriquecido. Pulse ALT-0 para abrir la ayuda.',
+  'System Font': 'Fuente de sistema',
+  'Failed to upload image: {0}': 'Fallo al cargar imagen: {0}',
+  'Failed to load plugin: {0} from url {1}': 'Fallo al cargar complemento: {0} desde URL {1}',
+  'Failed to load plugin url: {0}': 'Fallo al cargar URL del complemento: {0}',
+  'Failed to initialize plugin: {0}': 'Fallo al iniciar el complemento: {0}',
+  example: 'ejemplo',
+  Search: 'Buscar',
+  All: 'Todo',
+  Currency: 'Divisa',
+  Text: 'Texto',
+  Quotations: 'Comillas',
+  Mathematical: 'S\u00edmbolo matem\u00e1tico',
+  'Extended Latin': 'Latino extendido A',
+  Symbols: 'S\u00edmbolos',
+  Arrows: 'Flechas',
+  'User Defined': 'Definido por el usuario',
+  'dollar sign': 'signo de d\u00f3lar',
+  'currency sign': 'signo de divisa',
+  'euro-currency sign': 'signo de euro',
+  'colon sign': 'signo de dos puntos',
+  'cruzeiro sign': 'signo de cruceiro',
+  'french franc sign': 'signo de franco franc\u00e9s',
+  'lira sign': 'signo de lira',
+  'mill sign': 'signo de mill',
+  'naira sign': 'signo de naira',
+  'peseta sign': 'signo de peseta',
+  'rupee sign': 'signo de rupia',
+  'won sign': 'signo de won',
+  'new sheqel sign': 'signo de nuevo s\u00e9quel',
+  'dong sign': 'signo de dong',
+  'kip sign': 'signo de kip',
+  'tugrik sign': 'signo de tugrik',
+  'drachma sign': 'signo de dracma',
+  'german penny symbol': 'signo de penique alem\u00e1n',
+  'peso sign': 'signo de peso',
+  'guarani sign': 'signo de guaran\u00ed',
+  'austral sign': 'signo de austral',
+  'hryvnia sign': 'signo de grivna',
+  'cedi sign': 'signo de cedi',
+  'livre tournois sign': 'signo de libra tornesa',
+  'spesmilo sign': 'signo de spesmilo',
+  'tenge sign': 'signo de tenge',
+  'indian rupee sign': 'signo de rupia india',
+  'turkish lira sign': 'signo de lira turca',
+  'nordic mark sign': 'signo de marco n\u00f3rdico',
+  'manat sign': 'signo de manat',
+  'ruble sign': 'signo de rublo',
+  'yen character': 'car\u00e1cter de yen',
+  'yuan character': 'car\u00e1cter de yuan',
+  'yuan character, in hong kong and taiwan': 'car\u00e1cter de yuan en Hong Kong y Taiw\u00e1n',
+  'yen\/yuan character variant one': 'Variante uno de car\u00e1cter de yen\/yuan',
+  'Loading emoticons...': 'Cargando emoticonos...',
+  'Could not load emoticons': 'No se han podido cargar los emoticonos',
+  People: 'Personas',
+  'Animals and Nature': 'Animales y naturaleza',
+  'Food and Drink': 'Comida y bebida',
+  Activity: 'Actividad',
+  'Travel and Places': 'Viajes y lugares',
+  Objects: 'Objetos',
+  Flags: 'Banderas',
+  Characters: 'Caracteres',
+  'Characters (no spaces)': 'Caracteres (sin espacios)',
+  '{0} characters': '{0} caracteres',
+  'Error: Form submit field collision.': 'Error: Colisi\u00f3n de campo al enviar formulario.',
+  'Error: No form element found.': 'Error: No se encuentra ning\u00fan elemento de formulario.',
+  Update: 'Actualizar',
+  'Color swatch': 'Muestrario de colores',
+  Turquoise: 'Turquesa',
+  Green: 'Verde',
+  Blue: 'Azul',
+  Purple: 'P\u00farpura',
+  'Navy Blue': 'Azul marino',
+  'Dark Turquoise': 'Turquesa oscuro',
+  'Dark Green': 'Verde oscuro',
+  'Medium Blue': 'Azul medio',
+  'Medium Purple': 'P\u00farpura medio',
+  'Midnight Blue': 'Azul medio',
+  Yellow: 'Amarillo',
+  Orange: 'Naranja',
+  Red: 'Rojo',
+  'Light Gray': 'Gris claro',
+  Gray: 'Gris',
+  'Dark Yellow': 'Amarillo oscuro',
+  'Dark Orange': 'Naranja oscuro',
+  'Dark Red': 'Rojo oscuro',
+  'Medium Gray': 'Gris medio',
+  'Dark Gray': 'Gris oscuro',
+  'Light Green': 'Verde claro',
+  'Light Yellow': 'Amarillo claro',
+  'Light Red': 'Rojo claro',
+  'Light Purple': 'Morado claro',
+  'Light Blue': 'Azul claro',
+  'Dark Purple': 'Morado oscuro',
+  'Dark Blue': 'Azul oscuro',
+  Black: 'Negro',
+  White: 'Blanco',
+  'Switch to or from fullscreen mode': 'Activar o desactivar modo pantalla completa',
+  'Open help dialog': 'Abrir di\u00e1logo de ayuda',
+  history: 'historial',
+  styles: 'estilos',
+  formatting: 'formato',
+  alignment: 'alineaci\u00f3n',
+  indentation: 'sangr\u00eda',
+  'permanent pen': 'bol\u00edgrafo permanente',
+  comments: 'comentarios',
+  'Format Painter': 'Copiar formato',
+  'Insert\/edit iframe': 'Insertar\/editar iframe',
+  Capitalization: 'Uso de may\u00fasculas',
+  lowercase: 'min\u00fasculas',
+  UPPERCASE: 'MAY\u00daSCULAS',
+  'Title Case': 'Tipo T\u00edtulo',
+  'Permanent Pen Properties': 'Propiedades del bol\u00edgrafo permanente',
+  'Permanent pen properties...': 'Propiedades del bol\u00edgrafo permanente...',
+  Font: 'Fuente',
+  Size: 'Tama\u00f1o',
+  'More...': 'M\u00e1s...',
+  'Spellcheck Language': 'Corrector',
+  'Select...': 'Seleccionar...',
+  Preferences: 'Preferencias',
+  Yes: 'S\u00ed',
+  No: 'No',
+  'Keyboard Navigation': 'Navegaci\u00f3n con el teclado',
+  Version: 'Versi\u00f3n',
+  Anchor: 'Ancla',
+  'Special character': 'Car\u00e1cter especial',
+  'Code sample': 'Ejemplo de c\u00f3digo',
+  Color: 'Color',
+  Emoticons: 'Emoticonos',
+  'Document properties': 'Propiedades del documento',
+  Image: 'Imagen',
+  'Insert link': 'Insertar enlace',
+  Target: 'Destino',
+  Link: 'Enlace',
+  Poster: 'Miniatura',
+  Media: 'Media',
+  Print: 'Imprimir',
+  Prev: 'Anterior',
+  'Find and replace': 'Buscar y reemplazar',
+  'Whole words': 'Palabras completas',
+  Spellcheck: 'Corrector ortogr\u00e1fico',
+  Caption: 'Subt\u00edtulo',
+  'Insert template': 'Insertar plantilla'
+})

+ 389 - 0
jeecgboot-vue3/public/resource/tinymce/langs/zh_CN.js

@@ -0,0 +1,389 @@
+tinymce.addI18n('zh_CN',{
+"Redo": "\u91cd\u505a",
+"Undo": "\u64a4\u9500",
+"Cut": "\u526a\u5207",
+"Copy": "\u590d\u5236",
+"Paste": "\u7c98\u8d34",
+"Select all": "\u5168\u9009",
+"New document": "\u65b0\u6587\u4ef6",
+"Ok": "\u786e\u5b9a",
+"Cancel": "\u53d6\u6d88",
+"Visual aids": "\u7f51\u683c\u7ebf",
+"Bold": "\u7c97\u4f53",
+"Italic": "\u659c\u4f53",
+"Underline": "\u4e0b\u5212\u7ebf",
+"Strikethrough": "\u5220\u9664\u7ebf",
+"Superscript": "\u4e0a\u6807",
+"Subscript": "\u4e0b\u6807",
+"Clear formatting": "\u6e05\u9664\u683c\u5f0f",
+"Align left": "\u5de6\u8fb9\u5bf9\u9f50",
+"Align center": "\u4e2d\u95f4\u5bf9\u9f50",
+"Align right": "\u53f3\u8fb9\u5bf9\u9f50",
+"Justify": "\u4e24\u7aef\u5bf9\u9f50",
+"Bullet list": "\u9879\u76ee\u7b26\u53f7",
+"Numbered list": "\u7f16\u53f7\u5217\u8868",
+"Decrease indent": "\u51cf\u5c11\u7f29\u8fdb",
+"Increase indent": "\u589e\u52a0\u7f29\u8fdb",
+"Close": "\u5173\u95ed",
+"Formats": "\u683c\u5f0f",
+"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u6253\u5f00\u526a\u8d34\u677f\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u7b49\u5feb\u6377\u952e\u3002",
+"Headers": "\u6807\u9898",
+"Header 1": "\u6807\u98981",
+"Header 2": "\u6807\u98982",
+"Header 3": "\u6807\u98983",
+"Header 4": "\u6807\u98984",
+"Header 5": "\u6807\u98985",
+"Header 6": "\u6807\u98986",
+"Headings": "\u6807\u9898",
+"Heading 1": "\u6807\u98981",
+"Heading 2": "\u6807\u98982",
+"Heading 3": "\u6807\u98983",
+"Heading 4": "\u6807\u98984",
+"Heading 5": "\u6807\u98985",
+"Heading 6": "\u6807\u98986",
+"Preformatted": "\u9884\u5148\u683c\u5f0f\u5316\u7684",
+"Div": "Div",
+"Pre": "Pre",
+"Code": "\u4ee3\u7801",
+"Paragraph": "\u6bb5\u843d",
+"Blockquote": "\u5f15\u6587\u533a\u5757",
+"Inline": "\u6587\u672c",
+"Blocks": "\u57fa\u5757",
+"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002",
+"Fonts": "\u5b57\u4f53",
+"Font Sizes": "\u5b57\u53f7",
+"Class": "\u7c7b\u578b",
+"Browse for an image": "\u6d4f\u89c8\u56fe\u50cf",
+"OR": "\u6216",
+"Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64",
+"Upload": "\u4e0a\u4f20",
+"Block": "\u5757",
+"Align": "\u5bf9\u9f50",
+"Default": "\u9ed8\u8ba4",
+"Circle": "\u7a7a\u5fc3\u5706",
+"Disc": "\u5b9e\u5fc3\u5706",
+"Square": "\u65b9\u5757",
+"Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd",
+"Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd",
+"Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd",
+"Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd",
+"Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd",
+"Anchor...": "\u951a\u70b9...",
+"Name": "\u540d\u79f0",
+"Id": "\u6807\u8bc6\u7b26",
+"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002",
+"You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f",
+"Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f",
+"Special characters...": "\u7279\u6b8a\u5b57\u7b26...",
+"Source code": "\u6e90\u4ee3\u7801",
+"Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b",
+"Language": "\u8bed\u8a00",
+"Code sample...": "\u793a\u4f8b\u4ee3\u7801...",
+"Color Picker": "\u9009\u8272\u5668",
+"R": "R",
+"G": "G",
+"B": "B",
+"Left to right": "\u4ece\u5de6\u5230\u53f3",
+"Right to left": "\u4ece\u53f3\u5230\u5de6",
+"Emoticons...": "\u8868\u60c5\u7b26\u53f7...",
+"Metadata and Document Properties": "\u5143\u6570\u636e\u548c\u6587\u6863\u5c5e\u6027",
+"Title": "\u6807\u9898",
+"Keywords": "\u5173\u952e\u8bcd",
+"Description": "\u63cf\u8ff0",
+"Robots": "\u673a\u5668\u4eba",
+"Author": "\u4f5c\u8005",
+"Encoding": "\u7f16\u7801",
+"Fullscreen": "\u5168\u5c4f",
+"Action": "\u64cd\u4f5c",
+"Shortcut": "\u5feb\u6377\u952e",
+"Help": "\u5e2e\u52a9",
+"Address": "\u5730\u5740",
+"Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f",
+"Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f",
+"Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84",
+"Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355",
+"Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+"Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+"Find (if searchreplace plugin activated)": "\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
+"Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):",
+"Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a",
+"Learn more...": "\u4e86\u89e3\u66f4\u591a...",
+"You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}",
+"Plugins": "\u63d2\u4ef6",
+"Handy Shortcuts": "\u5feb\u6377\u952e",
+"Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf",
+"Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247",
+"Image description": "\u56fe\u7247\u63cf\u8ff0",
+"Source": "\u5730\u5740",
+"Dimensions": "\u5927\u5c0f",
+"Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4",
+"General": "\u666e\u901a",
+"Advanced": "\u9ad8\u7ea7",
+"Style": "\u6837\u5f0f",
+"Vertical space": "\u5782\u76f4\u8fb9\u8ddd",
+"Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd",
+"Border": "\u8fb9\u6846",
+"Insert image": "\u63d2\u5165\u56fe\u7247",
+"Image...": "\u56fe\u7247...",
+"Image list": "\u56fe\u7247\u5217\u8868",
+"Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c",
+"Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c",
+"Flip vertically": "\u5782\u76f4\u7ffb\u8f6c",
+"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c",
+"Edit image": "\u7f16\u8f91\u56fe\u7247",
+"Image options": "\u56fe\u7247\u9009\u9879",
+"Zoom in": "\u653e\u5927",
+"Zoom out": "\u7f29\u5c0f",
+"Crop": "\u88c1\u526a",
+"Resize": "\u8c03\u6574\u5927\u5c0f",
+"Orientation": "\u65b9\u5411",
+"Brightness": "\u4eae\u5ea6",
+"Sharpen": "\u9510\u5316",
+"Contrast": "\u5bf9\u6bd4\u5ea6",
+"Color levels": "\u989c\u8272\u5c42\u6b21",
+"Gamma": "\u4f3d\u9a6c\u503c",
+"Invert": "\u53cd\u8f6c",
+"Apply": "\u5e94\u7528",
+"Back": "\u540e\u9000",
+"Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4",
+"Date\/time": "\u65e5\u671f\/\u65f6\u95f4",
+"Insert\/Edit Link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
+"Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
+"Text to display": "\u663e\u793a\u6587\u5b57",
+"Url": "\u5730\u5740",
+"Open link in...": "\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...",
+"Current window": "\u5f53\u524d\u7a97\u53e3",
+"None": "\u65e0",
+"New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00",
+"Remove link": "\u5220\u9664\u94fe\u63a5",
+"Anchors": "\u951a\u70b9",
+"Link...": "\u94fe\u63a5...",
+"Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5",
+"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f",
+"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f",
+"Link list": "\u94fe\u63a5\u5217\u8868",
+"Insert video": "\u63d2\u5165\u89c6\u9891",
+"Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891",
+"Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53",
+"Alternative source": "\u955c\u50cf",
+"Alternative source URL": "\u66ff\u4ee3\u6765\u6e90\u7f51\u5740",
+"Media poster (Image URL)": "\u5c01\u9762(\u56fe\u7247\u5730\u5740)",
+"Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:",
+"Embed": "\u5185\u5d4c",
+"Media...": "\u591a\u5a92\u4f53...",
+"Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c",
+"Page break": "\u5206\u9875\u7b26",
+"Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c",
+"Preview": "\u9884\u89c8",
+"Print...": "\u6253\u5370...",
+"Save": "\u4fdd\u5b58",
+"Find": "\u67e5\u627e",
+"Replace with": "\u66ff\u6362\u4e3a",
+"Replace": "\u66ff\u6362",
+"Replace all": "\u5168\u90e8\u66ff\u6362",
+"Previous": "\u4e0a\u4e00\u4e2a",
+"Next": "\u4e0b\u4e00\u4e2a",
+"Find and replace...": "\u67e5\u627e\u5e76\u66ff\u6362...",
+"Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.",
+"Match case": "\u533a\u5206\u5927\u5c0f\u5199",
+"Find whole words only": "\u5168\u5b57\u5339\u914d",
+"Spell check": "\u62fc\u5199\u68c0\u67e5",
+"Ignore": "\u5ffd\u7565",
+"Ignore all": "\u5168\u90e8\u5ffd\u7565",
+"Finish": "\u5b8c\u6210",
+"Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178",
+"Insert table": "\u63d2\u5165\u8868\u683c",
+"Table properties": "\u8868\u683c\u5c5e\u6027",
+"Delete table": "\u5220\u9664\u8868\u683c",
+"Cell": "\u5355\u5143\u683c",
+"Row": "\u884c",
+"Column": "\u5217",
+"Cell properties": "\u5355\u5143\u683c\u5c5e\u6027",
+"Merge cells": "\u5408\u5e76\u5355\u5143\u683c",
+"Split cell": "\u62c6\u5206\u5355\u5143\u683c",
+"Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165",
+"Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165",
+"Delete row": "\u5220\u9664\u884c",
+"Row properties": "\u884c\u5c5e\u6027",
+"Cut row": "\u526a\u5207\u884c",
+"Copy row": "\u590d\u5236\u884c",
+"Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9",
+"Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9",
+"Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165",
+"Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165",
+"Delete column": "\u5220\u9664\u5217",
+"Cols": "\u5217",
+"Rows": "\u884c",
+"Width": "\u5bbd",
+"Height": "\u9ad8",
+"Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd",
+"Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd",
+"Show caption": "\u663e\u793a\u6807\u9898",
+"Left": "\u5de6\u5bf9\u9f50",
+"Center": "\u5c45\u4e2d",
+"Right": "\u53f3\u5bf9\u9f50",
+"Cell type": "\u5355\u5143\u683c\u7c7b\u578b",
+"Scope": "\u8303\u56f4",
+"Alignment": "\u5bf9\u9f50\u65b9\u5f0f",
+"H Align": "\u6c34\u5e73\u5bf9\u9f50",
+"V Align": "\u5782\u76f4\u5bf9\u9f50",
+"Top": "\u9876\u90e8\u5bf9\u9f50",
+"Middle": "\u5782\u76f4\u5c45\u4e2d",
+"Bottom": "\u5e95\u90e8\u5bf9\u9f50",
+"Header cell": "\u8868\u5934\u5355\u5143\u683c",
+"Row group": "\u884c\u7ec4",
+"Column group": "\u5217\u7ec4",
+"Row type": "\u884c\u7c7b\u578b",
+"Header": "\u8868\u5934",
+"Body": "\u8868\u4f53",
+"Footer": "\u8868\u5c3e",
+"Border color": "\u8fb9\u6846\u989c\u8272",
+"Insert template...": "\u63d2\u5165\u6a21\u677f...",
+"Templates": "\u6a21\u677f",
+"Template": "\u6a21\u677f",
+"Text color": "\u6587\u5b57\u989c\u8272",
+"Background color": "\u80cc\u666f\u8272",
+"Custom...": "\u81ea\u5b9a\u4e49...",
+"Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272",
+"No color": "\u65e0",
+"Remove color": "\u79fb\u9664\u989c\u8272",
+"Table of Contents": "\u5185\u5bb9\u5217\u8868",
+"Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846",
+"Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26",
+"Word count": "\u5b57\u6570",
+"Words: {0}": "\u5b57\u6570\uff1a{0}",
+"{0} words": "{0} \u5b57",
+"File": "\u6587\u4ef6",
+"Edit": "\u7f16\u8f91",
+"Insert": "\u63d2\u5165",
+"View": "\u89c6\u56fe",
+"Format": "\u683c\u5f0f",
+"Table": "\u8868\u683c",
+"Tools": "\u5de5\u5177",
+"Powered by {0}": "\u7531{0}\u9a71\u52a8",
+"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9",
+"Image title": "\u56fe\u7247\u6807\u9898",
+"Border width": "\u8fb9\u6846\u5bbd\u5ea6",
+"Border style": "\u8fb9\u6846\u6837\u5f0f",
+"Error": "\u9519\u8bef",
+"Warn": "\u8b66\u544a",
+"Valid": "\u6709\u6548",
+"To open the popup, press Shift+Enter": "\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846",
+"Rich Text Area. Press ALT-0 for help.": "\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002",
+"System Font": "\u7cfb\u7edf\u5b57\u4f53",
+"Failed to upload image: {0}": "\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}",
+"Failed to load plugin: {0} from url {1}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}",
+"Failed to load plugin url: {0}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}",
+"Failed to initialize plugin: {0}": "\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}",
+"example": "\u793a\u4f8b",
+"Search": "\u641c\u7d22",
+"All": "\u5168\u90e8",
+"Currency": "\u8d27\u5e01",
+"Text": "\u6587\u5b57",
+"Quotations": "\u5f15\u7528",
+"Mathematical": "\u6570\u5b66",
+"Extended Latin": "\u62c9\u4e01\u8bed\u6269\u5145",
+"Symbols": "\u7b26\u53f7",
+"Arrows": "\u7bad\u5934",
+"User Defined": "\u81ea\u5b9a\u4e49",
+"dollar sign": "\u7f8e\u5143\u7b26\u53f7",
+"currency sign": "\u8d27\u5e01\u7b26\u53f7",
+"euro-currency sign": "\u6b27\u5143\u7b26\u53f7",
+"colon sign": "\u5192\u53f7",
+"cruzeiro sign": "\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7",
+"french franc sign": "\u6cd5\u90ce\u7b26\u53f7",
+"lira sign": "\u91cc\u62c9\u7b26\u53f7",
+"mill sign": "\u5bc6\u5c14\u7b26\u53f7",
+"naira sign": "\u5948\u62c9\u7b26\u53f7",
+"peseta sign": "\u6bd4\u585e\u5854\u7b26\u53f7",
+"rupee sign": "\u5362\u6bd4\u7b26\u53f7",
+"won sign": "\u97e9\u5143\u7b26\u53f7",
+"new sheqel sign": "\u65b0\u8c22\u514b\u5c14\u7b26\u53f7",
+"dong sign": "\u8d8a\u5357\u76fe\u7b26\u53f7",
+"kip sign": "\u8001\u631d\u57fa\u666e\u7b26\u53f7",
+"tugrik sign": "\u56fe\u683c\u91cc\u514b\u7b26\u53f7",
+"drachma sign": "\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7",
+"german penny symbol": "\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7",
+"peso sign": "\u6bd4\u7d22\u7b26\u53f7",
+"guarani sign": "\u74dc\u62c9\u5c3c\u7b26\u53f7",
+"austral sign": "\u6fb3\u5143\u7b26\u53f7",
+"hryvnia sign": "\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7",
+"cedi sign": "\u585e\u5730\u7b26\u53f7",
+"livre tournois sign": "\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7",
+"spesmilo sign": "spesmilo\u7b26\u53f7",
+"tenge sign": "\u575a\u6208\u7b26\u53f7",
+"indian rupee sign": "\u5370\u5ea6\u5362\u6bd4",
+"turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9",
+"nordic mark sign": "\u5317\u6b27\u9a6c\u514b",
+"manat sign": "\u9a6c\u7eb3\u7279\u7b26\u53f7",
+"ruble sign": "\u5362\u5e03\u7b26\u53f7",
+"yen character": "\u65e5\u5143\u5b57\u6837",
+"yuan character": "\u4eba\u6c11\u5e01\u5143\u5b57\u6837",
+"yuan character, in hong kong and taiwan": "\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09",
+"yen\/yuan character variant one": "\u5143\u5b57\u6837\uff08\u5927\u5199\uff09",
+"Loading emoticons...": "\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7...",
+"Could not load emoticons": "\u4e0d\u80fd\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7",
+"People": "\u4eba\u7c7b",
+"Animals and Nature": "\u52a8\u7269\u548c\u81ea\u7136",
+"Food and Drink": "\u98df\u7269\u548c\u996e\u54c1",
+"Activity": "\u6d3b\u52a8",
+"Travel and Places": "\u65c5\u6e38\u548c\u5730\u70b9",
+"Objects": "\u7269\u4ef6",
+"Flags": "\u65d7\u5e1c",
+"Characters": "\u5b57\u7b26",
+"Characters (no spaces)": "\u5b57\u7b26(\u65e0\u7a7a\u683c)",
+"Error: Form submit field collision.": "\u9519\u8bef: \u8868\u5355\u63d0\u4ea4\u5b57\u6bb5\u51b2\u7a81\u3002",
+"Error: No form element found.": "\u9519\u8bef: \u6ca1\u6709\u8868\u5355\u63a7\u4ef6\u3002",
+"Update": "\u66f4\u65b0",
+"Color swatch": "\u989c\u8272\u6837\u672c",
+"Turquoise": "\u9752\u7eff\u8272",
+"Green": "\u7eff\u8272",
+"Blue": "\u84dd\u8272",
+"Purple": "\u7d2b\u8272",
+"Navy Blue": "\u6d77\u519b\u84dd",
+"Dark Turquoise": "\u6df1\u84dd\u7eff\u8272",
+"Dark Green": "\u6df1\u7eff\u8272",
+"Medium Blue": "\u4e2d\u84dd\u8272",
+"Medium Purple": "\u4e2d\u7d2b\u8272",
+"Midnight Blue": "\u6df1\u84dd\u8272",
+"Yellow": "\u9ec4\u8272",
+"Orange": "\u6a59\u8272",
+"Red": "\u7ea2\u8272",
+"Light Gray": "\u6d45\u7070\u8272",
+"Gray": "\u7070\u8272",
+"Dark Yellow": "\u6697\u9ec4\u8272",
+"Dark Orange": "\u6df1\u6a59\u8272",
+"Dark Red": "\u6df1\u7ea2\u8272",
+"Medium Gray": "\u4e2d\u7070\u8272",
+"Dark Gray": "\u6df1\u7070\u8272",
+"Black": "\u9ed1\u8272",
+"White": "\u767d\u8272",
+"Switch to or from fullscreen mode": "\u5207\u6362\u5168\u5c4f\u6a21\u5f0f",
+"Open help dialog": "\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846",
+"history": "\u5386\u53f2",
+"styles": "\u6837\u5f0f",
+"formatting": "\u683c\u5f0f\u5316",
+"alignment": "\u5bf9\u9f50",
+"indentation": "\u7f29\u8fdb",
+"permanent pen": "\u8bb0\u53f7\u7b14",
+"comments": "\u5907\u6ce8",
+"Anchor": "\u951a\u70b9",
+"Special character": "\u7279\u6b8a\u7b26\u53f7",
+"Code sample": "\u4ee3\u7801\u793a\u4f8b",
+"Color": "\u989c\u8272",
+"Emoticons": "\u8868\u60c5",
+"Document properties": "\u6587\u6863\u5c5e\u6027",
+"Image": "\u56fe\u7247",
+"Insert link": "\u63d2\u5165\u94fe\u63a5",
+"Target": "\u6253\u5f00\u65b9\u5f0f",
+"Link": "\u94fe\u63a5",
+"Poster": "\u5c01\u9762",
+"Media": "\u5a92\u4f53",
+"Print": "\u6253\u5370",
+"Prev": "\u4e0a\u4e00\u4e2a",
+"Find and replace": "\u67e5\u627e\u548c\u66ff\u6362",
+"Whole words": "\u5168\u5b57\u5339\u914d",
+"Spellcheck": "\u62fc\u5199\u68c0\u67e5",
+"Caption": "\u6807\u9898",
+"Insert template": "\u63d2\u5165\u6a21\u677f"
+});

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 711 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 705 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.inline.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 7 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.inline.min.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 7 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.min.css


+ 29 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.mobile.css

@@ -0,0 +1,29 @@
+/**
+* Copyright (c) Tiny Technologies, Inc. All rights reserved.
+* Licensed under the LGPL or a commercial license.
+* For LGPL see License.txt in the project root for license information.
+* For commercial licenses see https://www.tiny.cloud/
+*/
+.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection {
+  /* Note: this file is used inside the content, so isn't part of theming */
+  background-color: green;
+  display: inline-block;
+  opacity: 0.5;
+  position: absolute;
+}
+body {
+  -webkit-text-size-adjust: none;
+}
+body img {
+  /* this is related to the content margin */
+  max-width: 96vw;
+}
+body table img {
+  max-width: 95%;
+}
+body {
+  font-family: sans-serif;
+}
+table {
+  border-collapse: collapse;
+}

+ 7 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/content.mobile.min.css

@@ -0,0 +1,7 @@
+/**
+* Copyright (c) Tiny Technologies, Inc. All rights reserved.
+* Licensed under the LGPL or a commercial license.
+* For LGPL see License.txt in the project root for license information.
+* For commercial licenses see https://www.tiny.cloud/
+*/
+.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{background-color:green;display:inline-block;opacity:.5;position:absolute}body{-webkit-text-size-adjust:none}body img{max-width:96vw}body table img{max-width:95%}body{font-family:sans-serif}table{border-collapse:collapse}

BIN
jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/fonts/tinymce-mobile.woff


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 3045 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/skin.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 7 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/skin.min.css


+ 677 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/skin.mobile.css

@@ -0,0 +1,677 @@
+/**
+* Copyright (c) Tiny Technologies, Inc. All rights reserved.
+* Licensed under the LGPL or a commercial license.
+* For LGPL see License.txt in the project root for license information.
+* For commercial licenses see https://www.tiny.cloud/
+*/
+/* RESET all the things! */
+.tinymce-mobile-outer-container {
+  all: initial;
+  display: block;
+}
+.tinymce-mobile-outer-container * {
+  border: 0;
+  box-sizing: initial;
+  cursor: inherit;
+  float: none;
+  line-height: 1;
+  margin: 0;
+  outline: 0;
+  padding: 0;
+  -webkit-tap-highlight-color: transparent;
+  /* TBIO-3691, stop the gray flicker on touch. */
+  text-shadow: none;
+  white-space: nowrap;
+}
+.tinymce-mobile-icon-arrow-back::before {
+  content: "\e5cd";
+}
+.tinymce-mobile-icon-image::before {
+  content: "\e412";
+}
+.tinymce-mobile-icon-cancel-circle::before {
+  content: "\e5c9";
+}
+.tinymce-mobile-icon-full-dot::before {
+  content: "\e061";
+}
+.tinymce-mobile-icon-align-center::before {
+  content: "\e234";
+}
+.tinymce-mobile-icon-align-left::before {
+  content: "\e236";
+}
+.tinymce-mobile-icon-align-right::before {
+  content: "\e237";
+}
+.tinymce-mobile-icon-bold::before {
+  content: "\e238";
+}
+.tinymce-mobile-icon-italic::before {
+  content: "\e23f";
+}
+.tinymce-mobile-icon-unordered-list::before {
+  content: "\e241";
+}
+.tinymce-mobile-icon-ordered-list::before {
+  content: "\e242";
+}
+.tinymce-mobile-icon-font-size::before {
+  content: "\e245";
+}
+.tinymce-mobile-icon-underline::before {
+  content: "\e249";
+}
+.tinymce-mobile-icon-link::before {
+  content: "\e157";
+}
+.tinymce-mobile-icon-unlink::before {
+  content: "\eca2";
+}
+.tinymce-mobile-icon-color::before {
+  content: "\e891";
+}
+.tinymce-mobile-icon-previous::before {
+  content: "\e314";
+}
+.tinymce-mobile-icon-next::before {
+  content: "\e315";
+}
+.tinymce-mobile-icon-large-font::before,
+.tinymce-mobile-icon-style-formats::before {
+  content: "\e264";
+}
+.tinymce-mobile-icon-undo::before {
+  content: "\e166";
+}
+.tinymce-mobile-icon-redo::before {
+  content: "\e15a";
+}
+.tinymce-mobile-icon-removeformat::before {
+  content: "\e239";
+}
+.tinymce-mobile-icon-small-font::before {
+  content: "\e906";
+}
+.tinymce-mobile-icon-readonly-back::before,
+.tinymce-mobile-format-matches::after {
+  content: "\e5ca";
+}
+.tinymce-mobile-icon-small-heading::before {
+  content: "small";
+}
+.tinymce-mobile-icon-large-heading::before {
+  content: "large";
+}
+.tinymce-mobile-icon-small-heading::before,
+.tinymce-mobile-icon-large-heading::before {
+  font-family: sans-serif;
+  font-size: 80%;
+}
+.tinymce-mobile-mask-edit-icon::before {
+  content: "\e254";
+}
+.tinymce-mobile-icon-back::before {
+  content: "\e5c4";
+}
+.tinymce-mobile-icon-heading::before {
+  /* TODO: Translate */
+  content: "Headings";
+  font-family: sans-serif;
+  font-size: 80%;
+  font-weight: bold;
+}
+.tinymce-mobile-icon-h1::before {
+  content: "H1";
+  font-weight: bold;
+}
+.tinymce-mobile-icon-h2::before {
+  content: "H2";
+  font-weight: bold;
+}
+.tinymce-mobile-icon-h3::before {
+  content: "H3";
+  font-weight: bold;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  background: rgba(51, 51, 51, 0.5);
+  height: 100%;
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container {
+  align-items: center;
+  border-radius: 50%;
+  display: flex;
+  flex-direction: column;
+  font-family: sans-serif;
+  font-size: 1em;
+  justify-content: space-between;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .mixin-menu-item {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  border-radius: 50%;
+  height: 2.1em;
+  width: 2.1em;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  flex-direction: column;
+  font-size: 1em;
+}
+@media only screen and (min-device-width:700px) {
+  .tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section {
+    font-size: 1.2em;
+  }
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+  border-radius: 50%;
+  height: 2.1em;
+  width: 2.1em;
+  background-color: white;
+  color: #207ab7;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section .tinymce-mobile-mask-tap-icon::before {
+  content: "\e900";
+  font-family: 'tinymce-mobile', sans-serif;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-disabled-mask .tinymce-mobile-content-container .tinymce-mobile-content-tap-section:not(.tinymce-mobile-mask-tap-icon-selected) .tinymce-mobile-mask-tap-icon {
+  z-index: 2;
+}
+.tinymce-mobile-android-container.tinymce-mobile-android-maximized {
+  background: #ffffff;
+  border: none;
+  bottom: 0;
+  display: flex;
+  flex-direction: column;
+  left: 0;
+  position: fixed;
+  right: 0;
+  top: 0;
+}
+.tinymce-mobile-android-container:not(.tinymce-mobile-android-maximized) {
+  position: relative;
+}
+.tinymce-mobile-android-container .tinymce-mobile-editor-socket {
+  display: flex;
+  flex-grow: 1;
+}
+.tinymce-mobile-android-container .tinymce-mobile-editor-socket iframe {
+  display: flex !important;
+  flex-grow: 1;
+  height: auto !important;
+}
+.tinymce-mobile-android-scroll-reload {
+  overflow: hidden;
+}
+:not(.tinymce-mobile-readonly-mode) > .tinymce-mobile-android-selection-context-toolbar {
+  margin-top: 23px;
+}
+.tinymce-mobile-toolstrip {
+  background: #fff;
+  display: flex;
+  flex: 0 0 auto;
+  z-index: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar {
+  align-items: center;
+  background-color: #fff;
+  border-bottom: 1px solid #cccccc;
+  display: flex;
+  flex: 1;
+  height: 2.5em;
+  width: 100%;
+  /* Make it no larger than the toolstrip, so that it needs to scroll */
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex-shrink: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group > div {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-exit-container {
+  background: #f44336;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group.tinymce-mobile-toolbar-scrollable-group {
+  flex-grow: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item {
+  padding-left: 0.5em;
+  padding-right: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button {
+  align-items: center;
+  display: flex;
+  height: 80%;
+  margin-left: 2px;
+  margin-right: 2px;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item.tinymce-mobile-toolbar-button.tinymce-mobile-toolbar-button-selected {
+  background: #d4dbd7;
+  color: #cccccc;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:first-of-type,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar:not(.tinymce-mobile-context-toolbar) .tinymce-mobile-toolbar-group:last-of-type {
+  background: #207ab7;
+  color: #eceff1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar {
+  /* Note, this file is imported inside .tinymce-mobile-context-toolbar, so that prefix is on everything here. */
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex: 1;
+  padding-bottom: 0.4em;
+  padding-top: 0.4em;
+  /* Make any buttons appearing on the left and right display in the centre (e.g. color edges) */
+  /* For widgets like the colour picker, use the whole height */
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog {
+  display: flex;
+  min-height: 1.5em;
+  overflow: hidden;
+  padding-left: 0;
+  padding-right: 0;
+  position: relative;
+  width: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain {
+  display: flex;
+  height: 100%;
+  transition: left cubic-bezier(0.4, 0, 1, 1) 0.15s;
+  width: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen {
+  display: flex;
+  flex: 0 0 auto;
+  justify-content: space-between;
+  width: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen input {
+  font-family: Sans-serif;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container {
+  display: flex;
+  flex-grow: 1;
+  position: relative;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container .tinymce-mobile-input-container-x {
+  -ms-grid-row-align: center;
+      align-self: center;
+  background: inherit;
+  border: none;
+  border-radius: 50%;
+  color: #888;
+  font-size: 0.6em;
+  font-weight: bold;
+  height: 100%;
+  padding-right: 2px;
+  position: absolute;
+  right: 0;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-input-container.tinymce-mobile-input-container-empty .tinymce-mobile-input-container-x {
+  display: none;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next {
+  align-items: center;
+  display: flex;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next::before {
+  align-items: center;
+  display: flex;
+  font-weight: bold;
+  height: 100%;
+  padding-left: 0.5em;
+  padding-right: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-previous.tinymce-mobile-toolbar-navigation-disabled::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serialised-dialog .tinymce-mobile-serialised-dialog-chain .tinymce-mobile-serialised-dialog-screen .tinymce-mobile-icon-next.tinymce-mobile-toolbar-navigation-disabled::before {
+  visibility: hidden;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item {
+  color: #cccccc;
+  font-size: 10px;
+  line-height: 10px;
+  margin: 0 2px;
+  padding-top: 3px;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-item.tinymce-mobile-dot-active {
+  color: #d4dbd7;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-font::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-large-heading::before {
+  margin-left: 0.5em;
+  margin-right: 0.9em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-font::before,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-icon-small-heading::before {
+  margin-left: 0.9em;
+  margin-right: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider {
+  display: flex;
+  flex: 1;
+  margin-left: 0;
+  margin-right: 0;
+  padding: 0.28em 0;
+  position: relative;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container {
+  align-items: center;
+  display: flex;
+  flex-grow: 1;
+  height: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-size-container .tinymce-mobile-slider-size-line {
+  background: #cccccc;
+  display: flex;
+  flex: 1;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container {
+  padding-left: 2em;
+  padding-right: 2em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container {
+  align-items: center;
+  display: flex;
+  flex-grow: 1;
+  height: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-slider-gradient-container .tinymce-mobile-slider-gradient {
+  background: linear-gradient(to right, hsl(0, 100%, 50%) 0%, hsl(60, 100%, 50%) 17%, hsl(120, 100%, 50%) 33%, hsl(180, 100%, 50%) 50%, hsl(240, 100%, 50%) 67%, hsl(300, 100%, 50%) 83%, hsl(0, 100%, 50%) 100%);
+  display: flex;
+  flex: 1;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-black {
+  /* Not part of theming */
+  background: black;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+  width: 1.2em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider.tinymce-mobile-hue-slider-container .tinymce-mobile-hue-slider-white {
+  /* Not part of theming */
+  background: white;
+  height: 0.2em;
+  margin-bottom: 0.3em;
+  margin-top: 0.3em;
+  width: 1.2em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb {
+  /* vertically centering trick (margin: auto, top: 0, bottom: 0). On iOS and Safari, if you leave
+     * out these values, then it shows the thumb at the top of the spectrum. This is probably because it is
+     * absolutely positioned with only a left value, and not a top. Note, on Chrome it seems to be fine without
+     * this approach.
+    */
+  align-items: center;
+  background-clip: padding-box;
+  background-color: #455a64;
+  border: 0.5em solid rgba(136, 136, 136, 0);
+  border-radius: 3em;
+  bottom: 0;
+  color: #fff;
+  display: flex;
+  height: 0.5em;
+  justify-content: center;
+  left: -10px;
+  margin: auto;
+  position: absolute;
+  top: 0;
+  transition: border 120ms cubic-bezier(0.39, 0.58, 0.57, 1);
+  width: 0.5em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-slider .tinymce-mobile-slider-thumb.tinymce-mobile-thumb-active {
+  border: 0.5em solid rgba(136, 136, 136, 0.39);
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper,
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group > div {
+  align-items: center;
+  display: flex;
+  height: 100%;
+  flex: 1;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-serializer-wrapper {
+  flex-direction: column;
+  justify-content: center;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item {
+  align-items: center;
+  display: flex;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-toolbar-group-item:not(.tinymce-mobile-serialised-dialog) {
+  height: 100%;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group .tinymce-mobile-dot-container {
+  display: flex;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input {
+  background: #ffffff;
+  border: none;
+  border-radius: 0;
+  color: #455a64;
+  flex-grow: 1;
+  font-size: 0.85em;
+  padding-bottom: 0.1em;
+  padding-left: 5px;
+  padding-top: 0.1em;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::-webkit-input-placeholder {
+  /* WebKit, Blink, Edge */
+  color: #888;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input:-ms-input-placeholder {
+  /* WebKit, Blink, Edge */
+  color: #888;
+}
+.tinymce-mobile-toolstrip .tinymce-mobile-toolbar.tinymce-mobile-context-toolbar .tinymce-mobile-toolbar-group input::placeholder {
+  /* WebKit, Blink, Edge */
+  color: #888;
+}
+/* dropup */
+.tinymce-mobile-dropup {
+  background: white;
+  display: flex;
+  overflow: hidden;
+  width: 100%;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-shrinking {
+  transition: height 0.3s ease-out;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-growing {
+  transition: height 0.3s ease-in;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-closed {
+  flex-grow: 0;
+}
+.tinymce-mobile-dropup.tinymce-mobile-dropup-open:not(.tinymce-mobile-dropup-growing) {
+  flex-grow: 1;
+}
+/* TODO min-height for device size and orientation */
+.tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) {
+  min-height: 200px;
+}
+@media only screen and (orientation: landscape) {
+  .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) {
+    min-height: 200px;
+  }
+}
+@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) {
+  .tinymce-mobile-ios-container .tinymce-mobile-dropup:not(.tinymce-mobile-dropup-closed) {
+    min-height: 150px;
+  }
+}
+/* styles menu */
+.tinymce-mobile-styles-menu {
+  font-family: sans-serif;
+  outline: 4px solid black;
+  overflow: hidden;
+  position: relative;
+  width: 100%;
+}
+.tinymce-mobile-styles-menu [role="menu"] {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  position: absolute;
+  width: 100%;
+}
+.tinymce-mobile-styles-menu [role="menu"].transitioning {
+  transition: transform 0.5s ease-in-out;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-item {
+  border-bottom: 1px solid #ddd;
+  color: #455a64;
+  cursor: pointer;
+  display: flex;
+  padding: 1em 1em;
+  position: relative;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser .tinymce-mobile-styles-collapse-icon::before {
+  color: #455a64;
+  content: "\e314";
+  font-family: 'tinymce-mobile', sans-serif;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-styles-item-is-menu::after {
+  color: #455a64;
+  content: "\e315";
+  font-family: 'tinymce-mobile', sans-serif;
+  padding-left: 1em;
+  padding-right: 1em;
+  position: absolute;
+  right: 0;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-item.tinymce-mobile-format-matches::after {
+  font-family: 'tinymce-mobile', sans-serif;
+  padding-left: 1em;
+  padding-right: 1em;
+  position: absolute;
+  right: 0;
+}
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-separator,
+.tinymce-mobile-styles-menu .tinymce-mobile-styles-collapser {
+  align-items: center;
+  background: #fff;
+  border-top: #455a64;
+  color: #455a64;
+  display: flex;
+  min-height: 2.5em;
+  padding-left: 1em;
+  padding-right: 1em;
+}
+.tinymce-mobile-styles-menu [data-transitioning-destination="before"][data-transitioning-state],
+.tinymce-mobile-styles-menu [data-transitioning-state="before"] {
+  transform: translate(-100%);
+}
+.tinymce-mobile-styles-menu [data-transitioning-destination="current"][data-transitioning-state],
+.tinymce-mobile-styles-menu [data-transitioning-state="current"] {
+  transform: translate(0%);
+}
+.tinymce-mobile-styles-menu [data-transitioning-destination="after"][data-transitioning-state],
+.tinymce-mobile-styles-menu [data-transitioning-state="after"] {
+  transform: translate(100%);
+}
+@font-face {
+  font-family: 'tinymce-mobile';
+  font-style: normal;
+  font-weight: normal;
+  src: url('fonts/tinymce-mobile.woff?8x92w3') format('woff');
+}
+@media (min-device-width: 700px) {
+  .tinymce-mobile-outer-container,
+  .tinymce-mobile-outer-container input {
+    font-size: 25px;
+  }
+}
+@media (max-device-width: 700px) {
+  .tinymce-mobile-outer-container,
+  .tinymce-mobile-outer-container input {
+    font-size: 18px;
+  }
+}
+.tinymce-mobile-icon {
+  font-family: 'tinymce-mobile', sans-serif;
+}
+.mixin-flex-and-centre {
+  align-items: center;
+  display: flex;
+  justify-content: center;
+}
+.mixin-flex-bar {
+  align-items: center;
+  display: flex;
+  height: 100%;
+}
+.tinymce-mobile-outer-container .tinymce-mobile-editor-socket iframe {
+  background-color: #fff;
+  width: 100%;
+}
+.tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon {
+  /* Note, on the iPod touch in landscape, this isn't visible when the navbar appears */
+  background-color: #207ab7;
+  border-radius: 50%;
+  bottom: 1em;
+  color: white;
+  font-size: 1em;
+  height: 2.1em;
+  position: fixed;
+  right: 2em;
+  width: 2.1em;
+  align-items: center;
+  display: flex;
+  justify-content: center;
+}
+@media only screen and (min-device-width:700px) {
+  .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon {
+    font-size: 1.2em;
+  }
+}
+.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket {
+  height: 300px;
+  overflow: hidden;
+}
+.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-editor-socket iframe {
+  height: 100%;
+}
+.tinymce-mobile-outer-container:not(.tinymce-mobile-fullscreen-maximized) .tinymce-mobile-toolstrip {
+  display: none;
+}
+/*
+  Note, that if you don't include this (::-webkit-file-upload-button), the toolbar width gets
+  increased and the whole body becomes scrollable. It's important!
+ */
+input[type="file"]::-webkit-file-upload-button {
+  display: none;
+}
+@media only screen and (min-device-width : 320px) and (max-device-width : 568px) and (orientation : landscape) {
+  .tinymce-mobile-ios-container .tinymce-mobile-editor-socket .tinymce-mobile-mask-edit-icon {
+    bottom: 50%;
+  }
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 7 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/jeecg/skin.mobile.min.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 239 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide-dark/content.inline.min.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 235 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide-dark/content.min.css


+ 17 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide-dark/content.mobile.min.css

@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{position: absolute;display: inline-block;background-color: green;opacity: .5;}
+
+body{-webkit-text-size-adjust: none;}
+
+body img{max-width: 96vw;}
+
+body table img{max-width: 95%;}
+
+body{font-family: sans-serif;}
+
+table{border-collapse: collapse;}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 875 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide-dark/skin.min.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 239 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide-dark/skin.mobile.min.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 239 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/content.inline.min.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 235 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/content.min.css


+ 17 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/content.mobile.min.css

@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) Tiny Technologies, Inc. All rights reserved.
+ * Licensed under the LGPL or a commercial license.
+ * For LGPL see License.txt in the project root for license information.
+ * For commercial licenses see https://www.tiny.cloud/
+ */
+.tinymce-mobile-unfocused-selections .tinymce-mobile-unfocused-selection{position: absolute;display: inline-block;background-color: green;opacity: .5;}
+
+body{-webkit-text-size-adjust: none;}
+
+body img{max-width: 96vw;}
+
+body table img{max-width: 95%;}
+
+body{font-family: sans-serif;}
+
+table{border-collapse: collapse;}

BIN
jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/fonts/tinymce-mobile.woff


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 875 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/skin.min.css


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 239 - 0
jeecgboot-vue3/public/resource/tinymce/skins/ui/oxide/skin.mobile.min.css


+ 105 - 0
jeecgboot-vue3/src/App.vue

@@ -0,0 +1,105 @@
+<template>
+  <ConfigProvider :theme="appTheme" :locale="getAntdLocale">
+    <AppProvider>
+      <RouterView />
+    </AppProvider>
+  </ConfigProvider>
+</template>
+
+<script lang="ts" setup>
+  import { watch, ref } from 'vue';
+  import { theme } from 'ant-design-vue';
+  import { ConfigProvider } from 'ant-design-vue';
+  import { AppProvider } from '/@/components/Application';
+  import { useTitle } from '/@/hooks/web/useTitle';
+  import { useLocale } from '/@/locales/useLocale';
+  import { useAppStore } from '/@/store/modules/app';
+  import { useRootSetting } from '/@/hooks/setting/useRootSetting';
+  import { ThemeEnum } from '/@/enums/appEnum';
+  import { changeTheme } from '/@/logics/theme/index';
+
+  const appStore = useAppStore();
+  // 解决日期时间国际化问题
+  import 'dayjs/locale/zh-cn';
+  // support Multi-language
+  const { getAntdLocale } = useLocale();
+
+  useTitle();
+  /**
+   * 2024-04-07
+   * liaozhiyang
+   * 暗黑模式下默认文字白色,白天模式默认文字 #333
+   * */
+  const modeAction = (data) => {
+    if (data.token) {
+      if (getDarkMode.value === ThemeEnum.DARK) {
+        Object.assign(data.token, { colorTextBase: 'fff' });
+      } else {
+        Object.assign(data.token, { colorTextBase: '#333' });
+      }
+    }
+  };
+  // update-begin--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x
+  const appTheme: any = ref({});
+  const { getDarkMode } = useRootSetting();
+  watch(
+    () => getDarkMode.value,
+    (newValue) => {
+      delete appTheme.value.algorithm;
+      if (newValue === ThemeEnum.DARK) {
+        appTheme.value.algorithm = theme.darkAlgorithm;
+      }
+      // update-begin--author:liaozhiyang---date:20240322---for:【QQYUN-8570】生产环境暗黑模式下主题色不生效
+      if (import.meta.env.PROD) {
+        changeTheme(appStore.getProjectConfig.themeColor);
+      }
+      // update-end--author:liaozhiyang---date:20240322---for:【QQYUN-8570】生产环境暗黑模式下主题色不生效
+      modeAction(appTheme.value);
+      appTheme.value = {
+        ...appTheme.value,
+      };
+    },
+    { immediate: true }
+  );
+  watch(
+    appStore.getProjectConfig,
+    (newValue) => {
+      const primary = newValue.themeColor;
+      const result = {
+        ...appTheme.value,
+        ...{
+          token: {
+            colorPrimary: primary,
+            wireframe: true,
+            fontSize: 14,
+            colorTextBase: '#333',
+            colorSuccess: '#55D187',
+            colorInfo: primary,
+            borderRadius: 4,
+            sizeStep: 4,
+            sizeUnit: 4,
+            colorWarning: '#EFBD47',
+            colorError: '#ED6F6F',
+            fontFamily:
+              '-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol',
+          },
+        },
+      };
+      appTheme.value = result;
+      modeAction(result);
+    },
+    { immediate: true }
+  );
+  setTimeout(() => {
+    appStore.getProjectConfig?.themeColor && changeTheme(appStore.getProjectConfig.themeColor);
+  }, 300);
+  // update-end--author:liaozhiyang---date:20231218---for:【QQYUN-6366】升级到antd4.x
+
+</script>
+<style lang="less">
+  // update-begin--author:liaozhiyang---date:20230803---for:【QQYUN-5839】windi会影响到html2canvas绘制的图片样式
+  img {
+    display: inline-block;
+  }
+  // update-end--author:liaozhiyang---date:20230803---for:【QQYUN-5839】windi会影响到html2canvas绘制的图片样式
+</style>

+ 150 - 0
jeecgboot-vue3/src/api/common/api.ts

@@ -0,0 +1,150 @@
+import { defHttp } from '/@/utils/http/axios';
+import { message } from 'ant-design-vue';
+import { useGlobSetting } from '/@/hooks/setting';
+const globSetting = useGlobSetting();
+const baseUploadUrl = globSetting.uploadUrl;
+enum Api {
+  positionList = '/sys/position/list',
+  userList = '/sys/user/list',
+  roleList = '/sys/role/list',
+  queryDepartTreeSync = '/sys/sysDepart/queryDepartTreeSync',
+  queryTreeList = '/sys/sysDepart/queryTreeList',
+  loadTreeData = '/sys/category/loadTreeData',
+  loadDictItem = '/sys/category/loadDictItem/',
+  getDictItems = '/sys/dict/getDictItems/',
+  getTableList = '/sys/user/queryUserComponentData',
+  getCategoryData = '/sys/category/loadAllData',
+}
+
+/**
+ * 上传父路径
+ */
+export const uploadUrl = `${baseUploadUrl}/sys/common/upload`;
+
+/**
+ * 职务列表
+ * @param params
+ */
+export const getPositionList = (params) => {
+  return defHttp.get({ url: Api.positionList, params });
+};
+
+/**
+ * 用户列表
+ * @param params
+ */
+export const getUserList = (params) => {
+  return defHttp.get({ url: Api.userList, params });
+};
+
+/**
+ * 角色列表
+ * @param params
+ */
+export const getRoleList = (params) => {
+  return defHttp.get({ url: Api.roleList, params });
+};
+
+/**
+ * 异步获取部门树列表
+ */
+export const queryDepartTreeSync = (params?) => {
+  return defHttp.get({ url: Api.queryDepartTreeSync, params });
+};
+/**
+ * 获取部门树列表
+ */
+export const queryTreeList = (params?) => {
+  return defHttp.get({ url: Api.queryTreeList, params });
+};
+
+/**
+ * 分类字典树控件 加载节点
+ */
+export const loadTreeData = (params?) => {
+  return defHttp.get({ url: Api.loadTreeData, params });
+};
+
+/**
+ * 根据字典code加载字典text
+ */
+export const loadDictItem = (params?) => {
+  return defHttp.get({ url: Api.loadDictItem, params });
+};
+
+/**
+ * 根据字典code加载字典text
+ */
+export const getDictItems = (dictCode) => {
+  return defHttp.get({ url: Api.getDictItems + dictCode }, { joinTime: false });
+};
+/**
+ * 部门用户modal选择列表加载list
+ */
+export const getTableList = (params) => {
+  return defHttp.get({ url: Api.getTableList, params });
+};
+/**
+ * 加载全部分类字典数据
+ */
+export const loadCategoryData = (params) => {
+  return defHttp.get({ url: Api.getCategoryData, params });
+};
+/**
+ * 文件上传
+ */
+export const uploadFile = (params, success) => {
+  return defHttp.uploadFile({ url: uploadUrl }, params, { success });
+};
+/**
+ * 下载文件
+ * @param url 文件路径
+ * @param fileName 文件名
+ * @param parameter
+ * @returns {*}
+ */
+export const downloadFile = (url, fileName?, parameter?) => {
+  return getFileblob(url, parameter).then((data) => {
+    if (!data || data.size === 0) {
+      message.warning('文件下载失败');
+      return;
+    }
+    if (typeof window.navigator.msSaveBlob !== 'undefined') {
+      window.navigator.msSaveBlob(new Blob([data]), fileName);
+    } else {
+      let url = window.URL.createObjectURL(new Blob([data]));
+      let link = document.createElement('a');
+      link.style.display = 'none';
+      link.href = url;
+      link.setAttribute('download', fileName);
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link); //下载完成移除元素
+      window.URL.revokeObjectURL(url); //释放掉blob对象
+    }
+  });
+};
+
+/**
+ * 下载文件 用于excel导出
+ * @param url
+ * @param parameter
+ * @returns {*}
+ */
+export const getFileblob = (url, parameter) => {
+  return defHttp.get(
+    {
+      url: url,
+      params: parameter,
+      responseType: 'blob',
+    },
+    { isTransformResponse: false }
+  );
+};
+
+/**
+ * 【用于评论功能】自定义文件上传-方法
+ */
+export const uploadMyFile = (url, data) => {
+  return defHttp.uploadMyFile(url, data);
+};

+ 16 - 0
jeecgboot-vue3/src/api/demo/account.ts

@@ -0,0 +1,16 @@
+import { defHttp } from '/@/utils/http/axios';
+import { GetAccountInfoModel } from './model/accountModel';
+
+enum Api {
+  ACCOUNT_INFO = '/mock/account/getAccountInfo',
+  SESSION_TIMEOUT = '/mock/user/sessionTimeout',
+  TOKEN_EXPIRED = '/mock/user/tokenExpired',
+}
+
+// Get personal center-basic settings
+
+export const accountInfoApi = () => defHttp.get<GetAccountInfoModel>({ url: Api.ACCOUNT_INFO });
+
+export const sessionTimeoutApi = () => defHttp.post<void>({ url: Api.SESSION_TIMEOUT });
+
+export const tokenExpiredApi = () => defHttp.post<void>({ url: Api.TOKEN_EXPIRED });

+ 12 - 0
jeecgboot-vue3/src/api/demo/error.ts

@@ -0,0 +1,12 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+  // The address does not exist
+  Error = '/error',
+}
+
+/**
+ * @description: Trigger ajax error
+ */
+
+export const fireErrorApi = () => defHttp.get({ url: Api.Error });

+ 7 - 0
jeecgboot-vue3/src/api/demo/model/accountModel.ts

@@ -0,0 +1,7 @@
+export interface GetAccountInfoModel {
+  email: string;
+  name: string;
+  introduction: string;
+  phone: string;
+  address: string;
+}

+ 15 - 0
jeecgboot-vue3/src/api/demo/model/optionsModel.ts

@@ -0,0 +1,15 @@
+import { BasicFetchResult } from '/@/api/model/baseModel';
+
+export interface DemoOptionsItem {
+  label: string;
+  value: string;
+}
+
+export interface selectParams {
+  id: number | string;
+}
+
+/**
+ * @description: Request list return value
+ */
+export type DemoOptionsGetResultModel = BasicFetchResult<DemoOptionsItem>;

+ 103 - 0
jeecgboot-vue3/src/api/demo/model/systemModel.ts

@@ -0,0 +1,103 @@
+import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
+
+export type AccountParams = BasicPageParams & {
+  account?: string;
+  nickname?: string;
+};
+
+export type RoleParams = {
+  roleName?: string;
+  status?: string;
+};
+
+export type TestParams = {
+  testName?: string;
+};
+
+export type RolePageParams = BasicPageParams & RoleParams;
+
+export type TestPageParams = BasicPageParams & TestParams;
+
+export type UserPageParams = BasicPageParams & UserParams;
+
+export type DeptParams = {
+  deptName?: string;
+  status?: string;
+};
+
+export type UserParams = {
+  username?: string;
+};
+
+export type MenuParams = {
+  menuName?: string;
+  status?: string;
+};
+
+export interface AccountListItem {
+  id: string;
+  account: string;
+  email: string;
+  nickname: string;
+  role: number;
+  createTime: string;
+  remark: string;
+  status: number;
+}
+
+export interface DeptListItem {
+  id: string;
+  orderNo: string;
+  createTime: string;
+  remark: string;
+  status: number;
+}
+
+export interface MenuListItem {
+  id: string;
+  orderNo: string;
+  createTime: string;
+  status: number;
+  icon: string;
+  component: string;
+  permission: string;
+}
+
+export interface RoleListItem {
+  id: string;
+  roleName: string;
+  roleValue: string;
+  status: number;
+  orderNo: string;
+  createTime: string;
+}
+export interface TestListItem {
+  id: string;
+  testName: string;
+  testValue: string;
+  createTime: string;
+}
+
+export interface UserListItem {
+  id: string;
+  username: string;
+  password: string;
+  realname: string;
+}
+
+/**
+ * @description: Request list return value
+ */
+export type AccountListGetResultModel = BasicFetchResult<AccountListItem>;
+
+export type DeptListGetResultModel = BasicFetchResult<DeptListItem>;
+
+export type MenuListGetResultModel = BasicFetchResult<MenuListItem>;
+
+export type RolePageListGetResultModel = BasicFetchResult<RoleListItem>;
+
+export type RoleListGetResultModel = RoleListItem[];
+
+export type TestListGetResultModel = TestListItem[];
+
+export type UserListGetResultModel = UserListItem[];

+ 20 - 0
jeecgboot-vue3/src/api/demo/model/tableModel.ts

@@ -0,0 +1,20 @@
+import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';
+/**
+ * @description: Request list interface parameters
+ */
+export type DemoParams = BasicPageParams;
+
+export interface DemoListItem {
+  id: string;
+  beginTime: string;
+  endTime: string;
+  address: string;
+  name: string;
+  no: number;
+  status: number;
+}
+
+/**
+ * @description: Request list return value
+ */
+export type DemoListGetResultModel = BasicFetchResult<DemoListItem>;

+ 10 - 0
jeecgboot-vue3/src/api/demo/select.ts

@@ -0,0 +1,10 @@
+import { defHttp } from '/@/utils/http/axios';
+import { DemoOptionsItem, selectParams } from './model/optionsModel';
+enum Api {
+  OPTIONS_LIST = '/mock/select/getDemoOptions',
+}
+
+/**
+ * @description: Get sample options value
+ */
+export const optionsListApi = (params?: selectParams) => defHttp.get<DemoOptionsItem[]>({ url: Api.OPTIONS_LIST, params });

+ 45 - 0
jeecgboot-vue3/src/api/demo/system.ts

@@ -0,0 +1,45 @@
+import {
+  AccountParams,
+  DeptListItem,
+  MenuParams,
+  RoleParams,
+  TestPageParams,
+  RolePageParams,
+  MenuListGetResultModel,
+  DeptListGetResultModel,
+  AccountListGetResultModel,
+  RolePageListGetResultModel,
+  RoleListGetResultModel,
+  TestListGetResultModel,
+} from './model/systemModel';
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+  AccountList = '/mock/system/getAccountList',
+  IsAccountExist = '/mock/system/accountExist',
+  DeptList = '/mock/system/getDeptList',
+  setRoleStatus = '/mock/system/setRoleStatus',
+  MenuList = '/mock/system/getMenuList',
+  RolePageList = '/mock/system/getRoleListByPage',
+  DemoTableList = '/mock/system/getDemoTableListByPage',
+  TestPageList = '/mock/system/getTestListByPage',
+  GetAllRoleList = '/mock/system/getAllRoleList',
+}
+
+export const getAccountList = (params: AccountParams) => defHttp.get<AccountListGetResultModel>({ url: Api.AccountList, params });
+
+export const getDeptList = (params?: DeptListItem) => defHttp.get<DeptListGetResultModel>({ url: Api.DeptList, params });
+
+export const getMenuList = (params?: MenuParams) => defHttp.get<MenuListGetResultModel>({ url: Api.MenuList, params });
+
+export const getRoleListByPage = (params?: RolePageParams) => defHttp.get<RolePageListGetResultModel>({ url: Api.RolePageList, params });
+
+export const getAllRoleList = (params?: RoleParams) => defHttp.get<RoleListGetResultModel>({ url: Api.GetAllRoleList, params });
+
+export const setRoleStatus = (id: number, status: string) => defHttp.post({ url: Api.setRoleStatus, params: { id, status } });
+
+export const getTestListByPage = (params?: TestPageParams) => defHttp.get<TestListGetResultModel>({ url: Api.TestPageList, params });
+
+export const getDemoTableListByPage = (params) => defHttp.get({ url: Api.DemoTableList, params });
+
+export const isAccountExist = (account: string) => defHttp.post({ url: Api.IsAccountExist, params: { account } }, { errorMessageMode: 'none' });

+ 19 - 0
jeecgboot-vue3/src/api/demo/table.ts

@@ -0,0 +1,19 @@
+import { defHttp } from '/@/utils/http/axios';
+import { DemoParams, DemoListGetResultModel } from './model/tableModel';
+
+enum Api {
+  DEMO_LIST = '/mock/table/getDemoList',
+}
+
+/**
+ * @description: Get sample list value
+ */
+
+export const demoListApi = (params: DemoParams) =>
+  defHttp.get<DemoListGetResultModel>({
+    url: Api.DEMO_LIST,
+    params,
+    headers: {
+      ignoreCancelToken: true,
+    },
+  });

+ 10 - 0
jeecgboot-vue3/src/api/demo/tree.ts

@@ -0,0 +1,10 @@
+import { defHttp } from '/@/utils/http/axios';
+
+enum Api {
+  TREE_OPTIONS_LIST = '/mock/tree/getDemoOptions',
+}
+
+/**
+ * @description: Get sample options value
+ */
+export const treeOptionsListApi = (params?: Recordable) => defHttp.get<Recordable[]>({ url: Api.TREE_OPTIONS_LIST, params });

+ 14 - 0
jeecgboot-vue3/src/api/model/baseModel.ts

@@ -0,0 +1,14 @@
+export interface BasicPageParams {
+  page: number;
+  pageSize: number;
+}
+
+export interface BasicFetchResult<T extends any> {
+  items: T[];
+  total: number;
+}
+
+export interface BasicResult<T extends any> {
+  records: T[];
+  total: number;
+}

+ 43 - 0
jeecgboot-vue3/src/api/sys/menu.ts

@@ -0,0 +1,43 @@
+import { defHttp } from '/@/utils/http/axios';
+import { getMenuListResultModel } from './model/menuModel';
+
+enum Api {
+  GetMenuList = '/sys/permission/getUserPermissionByToken',
+  // 【QQYUN-8487】
+  // SwitchVue3Menu = '/sys/switchVue3Menu',
+}
+
+/**
+ * @description: Get user menu based on id
+ */
+
+export const getMenuList = () => {
+  return new Promise((resolve) => {
+    //为了兼容mock和接口数据
+    defHttp.get<getMenuListResultModel>({ url: Api.GetMenuList }).then((res) => {
+      if (Array.isArray(res)) {
+        resolve(res);
+      } else {
+        resolve(res['menu']);
+      }
+    });
+  });
+};
+
+/**
+ * @description: 获取后台菜单权限和按钮权限
+ */
+export function getBackMenuAndPerms() {
+  return defHttp.get({ url: Api.GetMenuList });
+}
+
+/**
+ * 切换成vue3菜单
+ */
+ // update-begin--author:liaozhiyang---date:20240313---for:【QQYUN-8487】注释掉判断菜单是否vue2版本逻辑代码
+// export const switchVue3Menu = () => {
+//   return new Promise((resolve) => {
+//     defHttp.get({ url: Api.SwitchVue3Menu });
+//   });
+// };
+// update-end--author:liaozhiyang---date:20240313---for:【QQYUN-8487】注释掉判断菜单是否vue2版本逻辑代码

+ 16 - 0
jeecgboot-vue3/src/api/sys/model/menuModel.ts

@@ -0,0 +1,16 @@
+import type { RouteMeta } from 'vue-router';
+export interface RouteItem {
+  path: string;
+  component: any;
+  meta: RouteMeta;
+  name?: string;
+  alias?: string | string[];
+  redirect?: string;
+  caseSensitive?: boolean;
+  children?: RouteItem[];
+}
+
+/**
+ * @description: Get menu return value
+ */
+export type getMenuListResultModel = RouteItem[];

+ 5 - 0
jeecgboot-vue3/src/api/sys/model/uploadModel.ts

@@ -0,0 +1,5 @@
+export interface UploadApiResult {
+  message: string;
+  code: number;
+  url: string;
+}

+ 58 - 0
jeecgboot-vue3/src/api/sys/model/userModel.ts

@@ -0,0 +1,58 @@
+/**
+ * @description: Login interface parameters
+ */
+export interface LoginParams {
+  username: string;
+  password: string;
+}
+
+export interface ThirdLoginParams {
+  token: string;
+  thirdType: string;
+}
+
+export interface RoleInfo {
+  roleName: string;
+  value: string;
+}
+
+/**
+ * @description: Login interface return value
+ */
+export interface LoginResultModel {
+  userId: string | number;
+  token: string;
+  role: RoleInfo;
+  userInfo?: any
+}
+
+/**
+ * @description: Get user information return value
+ */
+export interface GetUserInfoModel {
+  roles: RoleInfo[];
+  // 用户id
+  userId: string | number;
+  // 用户名
+  username: string;
+  // 真实名字
+  realname: string;
+  // 头像
+  avatar: string;
+  // 介绍
+  desc?: string;
+  // 用户信息
+  userInfo?: any;
+  // 缓存字典项
+  sysAllDictItems?: any;
+}
+
+/**
+ * @description: Get user information return value
+ */
+export interface GetResultModel {
+  code: number;
+  message: string;
+  result: object;
+  success: Boolean;
+}

+ 32 - 0
jeecgboot-vue3/src/api/sys/upload.ts

@@ -0,0 +1,32 @@
+import { UploadApiResult } from './model/uploadModel';
+import { defHttp } from '/@/utils/http/axios';
+import { UploadFileParams } from '/#/axios';
+import { useGlobSetting } from '/@/hooks/setting';
+
+const { uploadUrl = '' } = useGlobSetting();
+
+/**
+ * @description: Upload interface
+ */
+export function uploadApi(params: UploadFileParams, onUploadProgress: (progressEvent: ProgressEvent) => void) {
+  return defHttp.uploadFile<UploadApiResult>(
+    {
+      url: uploadUrl,
+      onUploadProgress,
+    },
+    params
+  );
+}
+/**
+ * @description: Upload interface
+ */
+export function uploadImg(params: UploadFileParams, onUploadProgress: (progressEvent: ProgressEvent) => void) {
+  return defHttp.uploadFile<UploadApiResult>(
+    {
+      url: `${uploadUrl}/sys/common/upload`,
+      onUploadProgress,
+    },
+    params,
+    { isReturnResponse: true }
+  );
+}

+ 222 - 0
jeecgboot-vue3/src/api/sys/user.ts

@@ -0,0 +1,222 @@
+import { defHttp } from '/@/utils/http/axios';
+import { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel';
+
+import { ErrorMessageMode } from '/#/axios';
+import { useMessage } from '/@/hooks/web/useMessage';
+import { useUserStoreWithOut } from '/@/store/modules/user';
+import { setAuthCache } from '/@/utils/auth';
+import { TOKEN_KEY } from '/@/enums/cacheEnum';
+import { router } from '/@/router';
+import { PageEnum } from '/@/enums/pageEnum';
+import { ExceptionEnum } from "@/enums/exceptionEnum";
+
+const { createErrorModal } = useMessage();
+enum Api {
+  Login = '/sys/login',
+  phoneLogin = '/sys/phoneLogin',
+  Logout = '/sys/logout',
+  GetUserInfo = '/sys/user/getUserInfo',
+  // 获取系统权限
+  // 1、查询用户拥有的按钮/表单访问权限
+  // 2、所有权限
+  // 3、系统安全模式
+  GetPermCode = '/sys/permission/getPermCode',
+  //新加的获取图形验证码的接口
+  getInputCode = '/sys/randomImage',
+  //获取短信验证码的接口
+  getCaptcha = '/sys/sms',
+  //注册接口
+  registerApi = '/sys/user/register',
+  //校验用户接口
+  checkOnlyUser = '/sys/user/checkOnlyUser',
+  //SSO登录校验
+  validateCasLogin = '/sys/cas/client/validateLogin',
+  //校验手机号
+  phoneVerify = '/sys/user/phoneVerification',
+  //修改密码
+  passwordChange = '/sys/user/passwordChange',
+  //第三方登录
+  thirdLogin = '/sys/thirdLogin/getLoginUser',
+  //第三方登录
+  getThirdCaptcha = '/sys/thirdSms',
+  //获取二维码信息
+  getLoginQrcode = '/sys/getLoginQrcode',
+  //监控二维码扫描状态
+  getQrcodeToken = '/sys/getQrcodeToken',
+}
+
+/**
+ * @description: user login api
+ */
+export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') {
+  return defHttp.post<LoginResultModel>(
+    {
+      url: Api.Login,
+      params,
+    },
+    {
+      errorMessageMode: mode,
+    }
+  );
+}
+
+/**
+ * @description: user phoneLogin api
+ */
+export function phoneLoginApi(params: LoginParams, mode: ErrorMessageMode = 'modal') {
+  return defHttp.post<LoginResultModel>(
+    {
+      url: Api.phoneLogin,
+      params,
+    },
+    {
+      errorMessageMode: mode,
+    }
+  );
+}
+
+/**
+ * @description: getUserInfo
+ */
+export function getUserInfo() {
+  return defHttp.get<GetUserInfoModel>({ url: Api.GetUserInfo }, {}).catch((e) => {
+    // update-begin--author:zyf---date:20220425---for:【VUEN-76】捕获接口超时异常,跳转到登录界面
+    if (e && (e.message.includes('timeout') || e.message.includes('401'))) {
+      //接口不通时跳转到登录界面
+      const userStore = useUserStoreWithOut();
+      userStore.setToken('');
+      setAuthCache(TOKEN_KEY, null);
+
+      // update-begin-author:sunjianlei date:20230306 for: 修复登录成功后,没有正确重定向的问题
+      router.push({
+        path: PageEnum.BASE_LOGIN,
+        query: {
+          // 传入当前的路由,登录成功后跳转到当前路由
+          redirect: router.currentRoute.value.fullPath,
+        }
+      });
+      // update-end-author:sunjianlei date:20230306 for: 修复登录成功后,没有正确重定向的问题
+
+    }
+    // update-end--author:zyf---date:20220425---for:【VUEN-76】捕获接口超时异常,跳转到登录界面
+  });
+}
+
+export function getPermCode() {
+  return defHttp.get({ url: Api.GetPermCode });
+}
+
+export function doLogout() {
+  return defHttp.get({ url: Api.Logout });
+}
+
+export function getCodeInfo(currdatetime) {
+  let url = Api.getInputCode + `/${currdatetime}`;
+  return defHttp.get({ url: url });
+}
+/**
+ * @description: 获取短信验证码
+ */
+export function getCaptcha(params) {
+  return new Promise((resolve, reject) => {
+    defHttp.post({ url: Api.getCaptcha, params }, { isTransformResponse: false }).then((res) => {
+      console.log(res);
+      if (res.success) {
+        resolve(true);
+      } else {
+        //update-begin---author:wangshuai---date:2024-04-18---for:【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
+        if(res.code != ExceptionEnum.PHONE_SMS_FAIL_CODE){
+          createErrorModal({ title: '错误提示', content: res.message || '未知问题' });
+          reject();
+        }
+        reject(res);
+        //update-end---author:wangshuai---date:2024-04-18---for:【QQYUN-9005】同一个IP,1分钟超过5次短信,则提示需要验证码---
+      }
+    }).catch((res)=>{
+      createErrorModal({ title: '错误提示', content: res.message || '未知问题' });
+      reject();
+    });
+  });
+}
+
+/**
+ * @description: 注册接口
+ */
+export function register(params) {
+  return defHttp.post({ url: Api.registerApi, params }, { isReturnNativeResponse: true });
+}
+
+/**
+ *校验用户是否存在
+ * @param params
+ */
+export const checkOnlyUser = (params) => defHttp.get({ url: Api.checkOnlyUser, params }, { isTransformResponse: false });
+/**
+ *校验手机号码
+ * @param params
+ */
+export const phoneVerify = (params) => defHttp.post({ url: Api.phoneVerify, params }, { isTransformResponse: false });
+/**
+ *密码修改
+ * @param params
+ */
+export const passwordChange = (params) => defHttp.get({ url: Api.passwordChange, params }, { isTransformResponse: false });
+/**
+ * @description: 第三方登录
+ */
+export function thirdLogin(params, mode: ErrorMessageMode = 'modal') {
+  //==========begin 第三方登录/auth2登录需要传递租户id===========
+  let tenantId = "0";
+  if(!params.tenantId){
+    tenantId = params.tenantId;
+  }
+  //==========end 第三方登录/auth2登录需要传递租户id===========
+  return defHttp.get<LoginResultModel>(
+    {
+      url: `${Api.thirdLogin}/${params.token}/${params.thirdType}/${tenantId}`,
+    },
+    {
+      errorMessageMode: mode,
+    }
+  );
+}
+/**
+ * @description: 获取第三方短信验证码
+ */
+export function setThirdCaptcha(params) {
+  return new Promise((resolve, reject) => {
+    defHttp.post({ url: Api.getThirdCaptcha, params }, { isTransformResponse: false }).then((res) => {
+      console.log(res);
+      if (res.success) {
+        resolve(true);
+      } else {
+        createErrorModal({ title: '错误提示', content: res.message || '未知问题' });
+        reject();
+      }
+    });
+  });
+}
+
+/**
+ * 获取登录二维码信息
+ */
+export function getLoginQrcode() {
+  let url = Api.getLoginQrcode;
+  return defHttp.get({ url: url });
+}
+
+/**
+ * 监控扫码状态
+ */
+export function getQrcodeToken(params) {
+  let url = Api.getQrcodeToken;
+  return defHttp.get({ url: url, params });
+}
+
+/**
+ * SSO登录校验
+ */
+export async function validateCasLogin(params) {
+  let url = Api.validateCasLogin;
+  return defHttp.get({ url: url, params });
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
jeecgboot-vue3/src/assets/icons/download-count.svg


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
jeecgboot-vue3/src/assets/icons/dynamic-avatar-1.svg


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
jeecgboot-vue3/src/assets/icons/dynamic-avatar-2.svg


+ 0 - 0
jeecgboot-vue3/src/assets/icons/dynamic-avatar-3.svg


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä