chore: merge main

This commit is contained in:
Vben 2021-06-20 21:50:38 +08:00
commit 99cabf048d
374 changed files with 9692 additions and 5019 deletions

View File

@ -22,6 +22,7 @@ module.exports = defineConfig({
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'prettier', 'prettier',
'plugin:prettier/recommended', 'plugin:prettier/recommended',
'plugin:jest/recommended',
], ],
rules: { rules: {
'@typescript-eslint/ban-ts-ignore': 'off', '@typescript-eslint/ban-ts-ignore': 'off',

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ test/server/static
# local env files # local env files
.env.local .env.local
.env.*.local .env.*.local
.eslintcache
# Log files # Log files
npm-debug.log* npm-debug.log*

44
.vscode/settings.json vendored
View File

@ -1,6 +1,6 @@
{ {
"cSpell.words": ["vben", "windi"],
"typescript.tsdk": "./node_modules/typescript/lib", "typescript.tsdk": "./node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"volar.tsPlugin": true, "volar.tsPlugin": true,
"volar.tsPluginStatus": false, "volar.tsPluginStatus": false,
//=========================================== //===========================================
@ -10,23 +10,14 @@
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"diffEditor.ignoreTrimWhitespace": false, "diffEditor.ignoreTrimWhitespace": false,
"editor.trimAutoWhitespace": true,
//=========================================== //===========================================
//============= Other ======================= //============= Other =======================
//=========================================== //===========================================
"breadcrumbs.enabled": true, "breadcrumbs.enabled": true,
"open-in-browser.default": "chrome", "open-in-browser.default": "chrome",
//=========================================== //===========================================
//============= emmet =======================
//===========================================
"emmet.triggerExpansionOnTab": true,
"emmet.showAbbreviationSuggestions": true,
//===========================================
//============= files ======================= //============= files =======================
//=========================================== //===========================================
"files.trimTrailingWhitespace": true,
// "files.insertFinalNewline": true,
// "files.trimFinalNewlines": true,
"files.eol": "\n", "files.eol": "\n",
"search.exclude": { "search.exclude": {
"**/node_modules": true, "**/node_modules": true,
@ -49,9 +40,14 @@
"CHANGELOG.md": true, "CHANGELOG.md": true,
"examples": true, "examples": true,
"res": true, "res": true,
"screenshots": true "screenshots": true,
"yarn-error.log": true,
"**/.yarn": true
}, },
"files.exclude": { "files.exclude": {
"**/.cache": true,
"**/.editorconfig": true,
"**/.eslintcache": true,
"**/bower_components": true, "**/bower_components": true,
"**/.idea": true, "**/.idea": true,
"**/tmp": true, "**/tmp": true,
@ -73,17 +69,6 @@
}, },
"stylelint.enable": true, "stylelint.enable": true,
"stylelint.packageManager": "yarn", "stylelint.packageManager": "yarn",
// ===========================================
// ================ Vetur ====================
// ===========================================
// "vetur.experimental.templateInterpolationService": true,
// "vetur.format.options.tabSize": 2,
// "vetur.languageFeatures.codeActions": false,
// "vetur.format.defaultFormatterOptions": {
// "js-beautify-html": {
// "wrap_attributes": "force-expand-multiline"
// }
// },
"liveServer.settings.donotShowInfoMsg": true, "liveServer.settings.donotShowInfoMsg": true,
"telemetry.enableCrashReporter": false, "telemetry.enableCrashReporter": false,
"workbench.settings.enableNaturalLanguageSearch": false, "workbench.settings.enableNaturalLanguageSearch": false,
@ -132,5 +117,18 @@
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}", "i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.enabledParsers": ["ts"], "i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en", "i18n-ally.sourceLanguage": "en",
"i18n-ally.enabledFrameworks": ["vue", "react"] "i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"vben",
"windi",
"browserslist",
"tailwindcss",
"esnext",
"antv",
"tinymce",
"qrcode",
"sider",
"pinia",
"sider"
]
} }

View File

@ -1,3 +1,37 @@
## 2.4.2(2021-06-10)
### ✨ Refactor
- `CountTo` component refactoring
### ✨ Features
- `radioButtonGroup` supports `boolean` value
- `useModalInner` added `redoModalHeight` to reset the height of `Modal` inside Modal
- `useECharts` added `getInstance` to obtain instances of `echart`
- `TableAction` added `stopButtonPropagation` to prevent the action button click event from bubbling
- `BasicTable` in the row edit mode, you can get or set the value of other editing components in the column
- The `ApiSelect` component will automatically re-fetch the data after the `params` is changed
- `TableImg` component improvement
- `BasicTable` added `columns-change` event to monitor the user to change the sorting, display, and fixed status of columns
- `Tinymce` supports dynamic modification readonly
- `BasicTable` added `updateTableDataRecord` method to update the specified row data
- `useModal` added `closeModal` method to close `Modal`
### 🐛 Bug Fixes
- Fix the problem that `redoModalHeight` cannot reduce the height
- Fix the problem that the schema data of `BasicForm` does not take effect
- Fix the problem that multiple tags may cause `KeepAlive` to fail
- Fix the problem that the default `axios` interceptor cannot handle custom code
- Fix the height issue of the lock screen pop-up window
- Fixed the problem that the half-selected state of the `Column Display` checkbox of `BaiscTable` was incorrectly displayed
- Fixed the problem that the preview list of the `BasicUpload` component could not be displayed in some cases
- Fix the problem that the `options` setting of ` RadioButtonGroup``disabled ` does not take effect
- Fix the problem that the button for uploading pictures in the read-only mode of the `Tinymce` component is still available
- Fix the stuttering problem of `BasicForm` under certain circumstances
- Fix the problem that "directory" routing does not work
## 2.4.1(2021-06-01) ## 2.4.1(2021-06-01)
### ✨ Features ### ✨ Features

View File

@ -1,3 +1,187 @@
# [2.5.0](https://github.com/anncwb/vue-vben-admin/compare/v2.4.0...v2.5.0) (2021-06-20)
### Bug Fixes
- **api:** select api type error ([b387681](https://github.com/anncwb/vue-vben-admin/commit/b387681c00ac018f5bc6a9251009ddffe37acae6))
- **api-select:** loss option data on event callback ([c5f2577](https://github.com/anncwb/vue-vben-admin/commit/c5f2577f515e7ae96b27b509e5dd4b3317fcb7b4)), closes [#733](https://github.com/anncwb/vue-vben-admin/issues/733)
- **ApiSelect demo:** add demo about ApiSelect's use ([#757](https://github.com/anncwb/vue-vben-admin/issues/757)) ([a03d3cc](https://github.com/anncwb/vue-vben-admin/commit/a03d3cc60c770eba644c1f3837850a2c1c015029))
- **avatar:** mock data and Account center style ([2066f66](https://github.com/anncwb/vue-vben-admin/commit/2066f669715491f3e91ac6d0e905cd2b3e80b58d))
- **axios:** make sure that the parameter is an object before processing, fix [#660](https://github.com/anncwb/vue-vben-admin/issues/660) ([834fa7e](https://github.com/anncwb/vue-vben-admin/commit/834fa7eb9c8aff252e083d38fdab4f6f53b4d43a))
- **axios:** transformRequestHook logic error ([b69dcd7](https://github.com/anncwb/vue-vben-admin/commit/b69dcd79d742fd171302ce0f48c7750d60da217f))
- **code-editor:** fix CodeEditor style problem, fix [#655](https://github.com/anncwb/vue-vben-admin/issues/655) ([5662804](https://github.com/anncwb/vue-vben-admin/commit/566280422de0537c4e31496eaaa95a9d51fe9458))
- **codeeditor:** empty value set failed.fixed:[#659](https://github.com/anncwb/vue-vben-admin/issues/659) ([ba2bebb](https://github.com/anncwb/vue-vben-admin/commit/ba2bebb4069085817a90d065ed5877fdb50a8039))
- **codeMirror:** fix the JsonEditor embedded in the bullet frame causing the style to be disordered ([#668](https://github.com/anncwb/vue-vben-admin/issues/668)) ([e1123a2](https://github.com/anncwb/vue-vben-admin/commit/e1123a2ccb5d5450a5072c19e5508a5dc0f14423))
- **demo:** `breadcrumb` route invalid redirect ([84d9300](https://github.com/anncwb/vue-vben-admin/commit/84d9300e52fa73da575591aa4b71858a7e459c8c))
- **demo:** account list page validate and save ([21f7a85](https://github.com/anncwb/vue-vben-admin/commit/21f7a854fe2455315287d04e895661ff739bce17))
- **demo:** fix basic form page style ([8b6e07b](https://github.com/anncwb/vue-vben-admin/commit/8b6e07b768f110f13b4f2efa6c46e03266667a8c))
- **demo:** make sure the map https resource is correct ([7b9cd09](https://github.com/anncwb/vue-vben-admin/commit/7b9cd09ad8a50c45b2e661e07953d786d82f367d))
- **flow-chart:** fix drag and drop menu loss ([fa828fd](https://github.com/anncwb/vue-vben-admin/commit/fa828fd972efeea87f364be76a1139ae53ec20d8))
- **form:** fix form update problem ([bcad95d](https://github.com/anncwb/vue-vben-admin/commit/bcad95d32a08a73f84ecbabab409cd64159f4077)), closes [#720](https://github.com/anncwb/vue-vben-admin/issues/720)
- **form:** loss args on component change event ([513823b](https://github.com/anncwb/vue-vben-admin/commit/513823bfbd3e8acc68098e0708c34bff2dd8dba6))
- **form:** radioButtonGroup value support boolean ([9e2aa20](https://github.com/anncwb/vue-vben-admin/commit/9e2aa20daa08d2902cb5d56c1560306947e44939))
- **form:** radioButtonGroup value support number ([bbddf30](https://github.com/anncwb/vue-vben-admin/commit/bbddf30e96feb1ab048323d93d3b8c1b18857acd))
- **form:** schemas update problem ([808328d](https://github.com/anncwb/vue-vben-admin/commit/808328dc7e56b1cc07b678d501d9589290173443)), closes [#688](https://github.com/anncwb/vue-vben-admin/issues/688)
- **keep-alive:** tablist cache updating effect ([d62d0ca](https://github.com/anncwb/vue-vben-admin/commit/d62d0ca08cff442c23eb9265851b066a2f24afa8)), closes [#695](https://github.com/anncwb/vue-vben-admin/issues/695)
- **layout:** fix class loss ([d018363](https://github.com/anncwb/vue-vben-admin/commit/d018363ddcd68189a18829a2b2560f3b98da58a6))
- **layout:** fix style compatibility issues ([905e5b7](https://github.com/anncwb/vue-vben-admin/commit/905e5b714b582548f32feca723012124343686a6))
- **layout:** props warn ([#756](https://github.com/anncwb/vue-vben-admin/issues/756)) ([bbce002](https://github.com/anncwb/vue-vben-admin/commit/bbce002be170c52db984647c931db88d7724cb52))
- **lock:** fix lock modal height ([40e3cb0](https://github.com/anncwb/vue-vben-admin/commit/40e3cb043c90a8343fa44a32acad2cb77de732da)), closes [#701](https://github.com/anncwb/vue-vben-admin/issues/701)
- **log:** fix Wrong version number ([#653](https://github.com/anncwb/vue-vben-admin/issues/653)) ([4f0d45f](https://github.com/anncwb/vue-vben-admin/commit/4f0d45f1df48755eadc0b09fa19762ee68f9abd1))
- **login:** login page modal style fixed: [#662](https://github.com/anncwb/vue-vben-admin/issues/662) ([#666](https://github.com/anncwb/vue-vben-admin/issues/666)) ([b218f10](https://github.com/anncwb/vue-vben-admin/commit/b218f10e25a9364c399a5fe42eedb549f57c01ea))
- **menu:** fix the jitter problem of menu folding animation,fix [#732](https://github.com/anncwb/vue-vben-admin/issues/732) ([4c89ea7](https://github.com/anncwb/vue-vben-admin/commit/4c89ea7474f4315870df1790f99f3e431f343b90))
- **mock:** make sure ignore matches the file correctly, fix [#745](https://github.com/anncwb/vue-vben-admin/issues/745) ([a222ec8](https://github.com/anncwb/vue-vben-admin/commit/a222ec8553f9b4477a43a8f7d113b5646fbfc373))
- **mock:** menu list api loss `type` field ([4185412](https://github.com/anncwb/vue-vben-admin/commit/41854121f3713dbde236afd3a416e9f27bd0c673))
- **mock:** type error ([7c1ffa3](https://github.com/anncwb/vue-vben-admin/commit/7c1ffa3d23de508a8d1590985806cb7a484b24e5))
- **modal:** add v-model support for visible ([de12bab](https://github.com/anncwb/vue-vben-admin/commit/de12babd314ac831d3cb645f42dbf8a476075623))
- **modal:** ensure that the full screen height is calculated correctly ([1c1755c](https://github.com/anncwb/vue-vben-admin/commit/1c1755cf5b4ada7263c05ddf4105abb52a2abb2f))
- **modal:** ensure that the shutdown event is not triggered multiple times ([655b743](https://github.com/anncwb/vue-vben-admin/commit/655b74323653147943cbde2352208cb765c82b8a))
- **store:** fix type error after pinia version upgrade ([e8d6f88](https://github.com/anncwb/vue-vben-admin/commit/e8d6f8851efd7076946486864936f1797280d3ba))
- **use-message:** `content` not support vNode ([154ebc3](https://github.com/anncwb/vue-vben-admin/commit/154ebc3d96f73bb3ceab99ea0229a3619d585aba))
- build error ([5212ea7](https://github.com/anncwb/vue-vben-admin/commit/5212ea79b43c832a5136354b549de8f89b6e2156))
- fix darkModeSwitch switch failure ([34a8054](https://github.com/anncwb/vue-vben-admin/commit/34a80542de670f0385dffaf5bf64bb9c3f6b90da))
- fix if getDropdownList.length==0 show Dropdown component ([21c771b](https://github.com/anncwb/vue-vben-admin/commit/21c771b59cb45defbff37de21c5c1950370b8f92))
- fix Login Page LocalePicker showLocale condition ([d683b0f](https://github.com/anncwb/vue-vben-admin/commit/d683b0f1e85b85b07090feba4ac7f741bd3bd482))
- **modal:** redoModalHeight not work as expected ([5d554f1](https://github.com/anncwb/vue-vben-admin/commit/5d554f184f7b61774d1a1b2e61451677b38505de))
- **page:** `basic form` action btns should be in line ([6c4f947](https://github.com/anncwb/vue-vben-admin/commit/6c4f947386c181f45253c94e4ef735d29a253053))
- **radio-button:** fix RadioButton `disabled` support ([ee384b1](https://github.com/anncwb/vue-vben-admin/commit/ee384b1fa7e387b3680e9d54cbe4a1e2f15ec750)), closes [#710](https://github.com/anncwb/vue-vben-admin/issues/710)
- **route:** dynamically introduce components error ([c6b766d](https://github.com/anncwb/vue-vben-admin/commit/c6b766d8ea902294ab1f7e4a06781f2bcfdd1f0b))
- **router:** loss `directory` route ([df8cd86](https://github.com/anncwb/vue-vben-admin/commit/df8cd860514f32f44847dcf724f0737ed4d8b9e0)), closes [#722](https://github.com/anncwb/vue-vben-admin/issues/722)
- **table:** wrong indeterminate state ([495b1da](https://github.com/anncwb/vue-vben-admin/commit/495b1da385e9b6428d2b994669d2065722445923))
- **table:** make sure the table width is correct, fix [#593](https://github.com/anncwb/vue-vben-admin/issues/593) ([d73d43e](https://github.com/anncwb/vue-vben-admin/commit/d73d43ed91f30957cfd202c51552ca40a19cef08))
- **table:** settings indeterminate state effect ([4fd2051](https://github.com/anncwb/vue-vben-admin/commit/4fd2051bc0403bfc5345ed6a5fc283a372ef7a92))
- **table:** support change event ([9f4d171](https://github.com/anncwb/vue-vben-admin/commit/9f4d1719caa76de94e6362c16e4df3ac28df253c)), closes [#677](https://github.com/anncwb/vue-vben-admin/issues/677)
- **table:** try to get close to the form stuck ([d81481c](https://github.com/anncwb/vue-vben-admin/commit/d81481c52186145dac130aaa1594f0ba8db4d392))
- **table:** useTable support onChange ([9f5085c](https://github.com/anncwb/vue-vben-admin/commit/9f5085c9f9f46b09391156b17091c1771bc13026))
- **table-action:** fix the split line style is missing,fix [#674](https://github.com/anncwb/vue-vben-admin/issues/674) ([b1cb863](https://github.com/anncwb/vue-vben-admin/commit/b1cb86350253dc5be095466966d9469775f4395d))
- **Tinymce:** Read only status upload button can also be used ([#718](https://github.com/anncwb/vue-vben-admin/issues/718)) ([966571b](https://github.com/anncwb/vue-vben-admin/commit/966571bdcb11c2729ab9ce212bd3e195f7bf3a59))
- **tree:** support defaultExpandAll prop ([3ed2339](https://github.com/anncwb/vue-vben-admin/commit/3ed2339a6d75abbd6ccf723b6eaa762f9921409e))
- **upload:** ensure preview items valid ([4376928](https://github.com/anncwb/vue-vben-admin/commit/437692869a232ee65c300c65ee473557ae0913c7))
- **useViewHeight:** Fix the problem that useContentViewHeight does not calculate the footer ([#747](https://github.com/anncwb/vue-vben-admin/issues/747)) ([33cd8fe](https://github.com/anncwb/vue-vben-admin/commit/33cd8fe6533830176ab63ddfc4d74f75a384366c))
- ensure that roleList is not empty ([aebad61](https://github.com/anncwb/vue-vben-admin/commit/aebad61b3d3e11aaf720b37e762e53e2e6999d3c))
- fix node12 version data mock error ([644dbe3](https://github.com/anncwb/vue-vben-admin/commit/644dbe315bb03ea1641a682359873237208a5303))
- Fix the problem that the `lang` attribute of `HTML` will not be set when it is first loaded ([#682](https://github.com/anncwb/vue-vben-admin/issues/682)) ([eca8907](https://github.com/anncwb/vue-vben-admin/commit/eca8907a11c28d816c3da5a0667f45a38a499012))
- login failed ([035f55a](https://github.com/anncwb/vue-vben-admin/commit/035f55af9778819d72adc1700d9de56a6569b58f))
- session timeout login logic error ([#678](https://github.com/anncwb/vue-vben-admin/issues/678)) ([132c7fb](https://github.com/anncwb/vue-vben-admin/commit/132c7fb944df255c4d76a25d6d924439f91f9c54)), closes [#673](https://github.com/anncwb/vue-vben-admin/issues/673)
- theme switching fails ([7e2ca79](https://github.com/anncwb/vue-vben-admin/commit/7e2ca79ece2f5209cb7ce4b0f5ee15012f9f51de))
### Features
- optimize error message for api failure ([ea6834a](https://github.com/anncwb/vue-vben-admin/commit/ea6834aeec3ef56d411b2c10a474f75d3d7bfdfc))
- **api-select:** auto refetch after params changed ([50207ad](https://github.com/anncwb/vue-vben-admin/commit/50207ad702ef3faca1e27c873c89132ab92fae8e))
- **app-search:** auto focus on show ([1ae6362](https://github.com/anncwb/vue-vben-admin/commit/1ae636296df2cf99e8a777f053c539c50e6ad49a))
- **axios:** added authenticationScheme configuration,fix [#774](https://github.com/anncwb/vue-vben-admin/issues/774) ([b6d5b07](https://github.com/anncwb/vue-vben-admin/commit/b6d5b0796de4d0b66c0f33c335ec991d44f64ef2))
- **demo:** `switch` use in table ([46899aa](https://github.com/anncwb/vue-vben-admin/commit/46899aa3cd6b1616c42ac263a28af75be839f6a0))
- **demo:** added guide page example ([d196340](https://github.com/anncwb/vue-vben-admin/commit/d196340d270d2becbf2cc81b7d4f09273381bd09))
- **echarts:** add getInstance for useECharts ([fb6c76d](https://github.com/anncwb/vue-vben-admin/commit/fb6c76db535bd0c6305d03c0cff876a1f079100b))
- **modal:** add closeModal for useModal ([6d5f9aa](https://github.com/anncwb/vue-vben-admin/commit/6d5f9aa699c5da8af6bf5841baddc4a8bd603917))
- **modal:** add redoModalHeight for useModalInner ([f732b56](https://github.com/anncwb/vue-vben-admin/commit/f732b569042f7fe77c85cb295538ddd85561f7e9))
- **preview:** added createImgPreview picture preview function ([305630e](https://github.com/anncwb/vue-vben-admin/commit/305630e3fd886b3f690f890a934a8a6ba224fba1))
- **project-setting:** added sessionTimeoutProcessing project configuration item,fix [#772](https://github.com/anncwb/vue-vben-admin/issues/772) ([0d07084](https://github.com/anncwb/vue-vben-admin/commit/0d0708409c4adbe7a0c5e33abf5307031147eaeb))
- **table:** add editable DatePicker & TimePicker ([#654](https://github.com/anncwb/vue-vben-admin/issues/654)) ([93006c7](https://github.com/anncwb/vue-vben-admin/commit/93006c7dc7b5243b26637f444c8057c95935e622))
- **table:** add updateTableDataRecord method ([8e4f486](https://github.com/anncwb/vue-vben-admin/commit/8e4f486fcf835f0b6f2a95676dba268ffdd0566e))
- **table:** editable component text align ([8eaf575](https://github.com/anncwb/vue-vben-admin/commit/8eaf57562610a833c8083ae9957f458319d1cc93))
- **table:** support columns-change event ([125a7d1](https://github.com/anncwb/vue-vben-admin/commit/125a7d14831642c9cbb2e4b3e75953c3b2e2cdef))
- **table:** support custom update on row editing ([fe2bcfc](https://github.com/anncwb/vue-vben-admin/commit/fe2bcfc6f74159c355f3be153a316869fdb8b644)), closes [#646](https://github.com/anncwb/vue-vben-admin/issues/646)
- **table:** updateTableDataRecord support functional rowKey ([448a4c2](https://github.com/anncwb/vue-vben-admin/commit/448a4c2809672480f8f635d7cc4661554112598a))
- **table-action:** add stopButtonPropagation prop ([808012b](https://github.com/anncwb/vue-vben-admin/commit/808012b544b8c6f3cf467f42653c2783dbe8be6b)), closes [#699](https://github.com/anncwb/vue-vben-admin/issues/699)
- **table-img:** support simple show mode and more props ([19d8e01](https://github.com/anncwb/vue-vben-admin/commit/19d8e01e11644c66222f137abd05940cbdec0bb6))
- **test:** add jest test suite ([f6fe1dd](https://github.com/anncwb/vue-vben-admin/commit/f6fe1dd62df231ccbd063db0d32359b48aa5c76b))
- **use-drawer:** add closeDrawer function ([639520a](https://github.com/anncwb/vue-vben-admin/commit/639520ad5ddf829875ab517067abf2b45ebc04c2))
- add CropperAvatar component ([8e410fc](https://github.com/anncwb/vue-vben-admin/commit/8e410fc6401847d8e5545468b5ce6fd7ce9fc5cc))
- **tabs:** add setTabTitle method ([#680](https://github.com/anncwb/vue-vben-admin/issues/680)) ([5ddccf6](https://github.com/anncwb/vue-vben-admin/commit/5ddccf6ba28453b9a35355d53d0db65f1a8876bc))
- **tinymce:** support dark theme and I18n ([83c9cd7](https://github.com/anncwb/vue-vben-admin/commit/83c9cd77421e9c0888a41e2d8dcbca816da67488))
- **Tinymce:** add dynamics to the read-only state of the rich text editor ([#725](https://github.com/anncwb/vue-vben-admin/issues/725)) ([efce482](https://github.com/anncwb/vue-vben-admin/commit/efce482b3215ddf9ed588f63a218d5f76939e947))
- **tree:** add defaultExpandLevel prop ([6edca1c](https://github.com/anncwb/vue-vben-admin/commit/6edca1c19c3b0772f9ab82a7b09251a74fff2173)), closes [#672](https://github.com/anncwb/vue-vben-admin/issues/672)
### Performance Improvements
- **component:** optimize tree and upload components ([3f6920f](https://github.com/anncwb/vue-vben-admin/commit/3f6920f7a9775fc06a34dead90b1724b23b7759c))
- **cropper-avatar:** code optimization ([6dbbdba](https://github.com/anncwb/vue-vben-admin/commit/6dbbdbac76c2c3795e12dd346f6310d1b70f6a7d))
- **i18n:** improve circular dependencies ([d677729](https://github.com/anncwb/vue-vben-admin/commit/d677729acbe2c024ab13cf490b205528507c4823))
- **i18n:** improve warning prompt ([6ef62ba](https://github.com/anncwb/vue-vben-admin/commit/6ef62ba6ea7f5613a1fec982b30fe6b0f478bf59))
- **locale:** reduce the number of multilingual files ([0acc4ab](https://github.com/anncwb/vue-vben-admin/commit/0acc4ab2dd70a239bd13929edede02b283feb7c2))
- **PageWrapper:** fix the height calculation problem when footer and global footer are opened at the same time ([#760](https://github.com/anncwb/vue-vben-admin/issues/760)) ([ab2c7ef](https://github.com/anncwb/vue-vben-admin/commit/ab2c7efe6994dacfe0ff407783f2c3b246427bfc))
- **utils:** mitt default export is changed from Class to Function ([d3d620f](https://github.com/anncwb/vue-vben-admin/commit/d3d620f4fc75dd69270e4d090a71d426701272ef))
- add createImgPreview func ([#713](https://github.com/anncwb/vue-vben-admin/issues/713)) ([b7c7c46](https://github.com/anncwb/vue-vben-admin/commit/b7c7c46853d332641d116d818e657447884784f3))
- optimize components and add comments ([55e9d9f](https://github.com/anncwb/vue-vben-admin/commit/55e9d9fc2953643cec95c74b6ed34b0e68641fb6))
## [2.4.2](https://github.com/anncwb/vue-vben-admin/compare/v2.4.0...v2.4.2) (2021-06-09)
### Bug Fixes
- fix darkModeSwitch switch failure ([34a8054](https://github.com/anncwb/vue-vben-admin/commit/34a80542de670f0385dffaf5bf64bb9c3f6b90da))
- **api-select:** loss option data on event callback ([c5f2577](https://github.com/anncwb/vue-vben-admin/commit/c5f2577f515e7ae96b27b509e5dd4b3317fcb7b4)), closes [#733](https://github.com/anncwb/vue-vben-admin/issues/733)
- **avatar:** mock data and Account center style ([2066f66](https://github.com/anncwb/vue-vben-admin/commit/2066f669715491f3e91ac6d0e905cd2b3e80b58d))
- **axios:** transformRequestHook logic error ([b69dcd7](https://github.com/anncwb/vue-vben-admin/commit/b69dcd79d742fd171302ce0f48c7750d60da217f))
- **codeMirror:** fix the JsonEditor embedded in the bullet frame causing the style to be disordered ([#668](https://github.com/anncwb/vue-vben-admin/issues/668)) ([e1123a2](https://github.com/anncwb/vue-vben-admin/commit/e1123a2ccb5d5450a5072c19e5508a5dc0f14423))
- **demo:** `breadcrumb` route invalid redirect ([84d9300](https://github.com/anncwb/vue-vben-admin/commit/84d9300e52fa73da575591aa4b71858a7e459c8c))
- **demo:** account list page validate and save ([21f7a85](https://github.com/anncwb/vue-vben-admin/commit/21f7a854fe2455315287d04e895661ff739bce17))
- **demo:** fix basic form page style ([8b6e07b](https://github.com/anncwb/vue-vben-admin/commit/8b6e07b768f110f13b4f2efa6c46e03266667a8c))
- **demo:** make sure the map https resource is correct ([7b9cd09](https://github.com/anncwb/vue-vben-admin/commit/7b9cd09ad8a50c45b2e661e07953d786d82f367d))
- **form:** fix form update problem ([bcad95d](https://github.com/anncwb/vue-vben-admin/commit/bcad95d32a08a73f84ecbabab409cd64159f4077)), closes [#720](https://github.com/anncwb/vue-vben-admin/issues/720)
- **form:** radioButtonGroup value support boolean ([9e2aa20](https://github.com/anncwb/vue-vben-admin/commit/9e2aa20daa08d2902cb5d56c1560306947e44939))
- **form:** radioButtonGroup value support number ([bbddf30](https://github.com/anncwb/vue-vben-admin/commit/bbddf30e96feb1ab048323d93d3b8c1b18857acd))
- **form:** schemas update problem ([808328d](https://github.com/anncwb/vue-vben-admin/commit/808328dc7e56b1cc07b678d501d9589290173443)), closes [#688](https://github.com/anncwb/vue-vben-admin/issues/688)
- **keep-alive:** tablist cache updating effect ([d62d0ca](https://github.com/anncwb/vue-vben-admin/commit/d62d0ca08cff442c23eb9265851b066a2f24afa8)), closes [#695](https://github.com/anncwb/vue-vben-admin/issues/695)
- **lock:** fix lock modal height ([40e3cb0](https://github.com/anncwb/vue-vben-admin/commit/40e3cb043c90a8343fa44a32acad2cb77de732da)), closes [#701](https://github.com/anncwb/vue-vben-admin/issues/701)
- **login:** login page modal style fixed: [#662](https://github.com/anncwb/vue-vben-admin/issues/662) ([#666](https://github.com/anncwb/vue-vben-admin/issues/666)) ([b218f10](https://github.com/anncwb/vue-vben-admin/commit/b218f10e25a9364c399a5fe42eedb549f57c01ea))
- **mock:** menu list api loss `type` field ([4185412](https://github.com/anncwb/vue-vben-admin/commit/41854121f3713dbde236afd3a416e9f27bd0c673))
- **mock:** type error ([7c1ffa3](https://github.com/anncwb/vue-vben-admin/commit/7c1ffa3d23de508a8d1590985806cb7a484b24e5))
- **router:** loss `directory` route ([df8cd86](https://github.com/anncwb/vue-vben-admin/commit/df8cd860514f32f44847dcf724f0737ed4d8b9e0)), closes [#722](https://github.com/anncwb/vue-vben-admin/issues/722)
- build error ([5212ea7](https://github.com/anncwb/vue-vben-admin/commit/5212ea79b43c832a5136354b549de8f89b6e2156))
- **axios:** make sure that the parameter is an object before processing, fix [#660](https://github.com/anncwb/vue-vben-admin/issues/660) ([834fa7e](https://github.com/anncwb/vue-vben-admin/commit/834fa7eb9c8aff252e083d38fdab4f6f53b4d43a))
- **code-editor:** fix CodeEditor style problem, fix [#655](https://github.com/anncwb/vue-vben-admin/issues/655) ([5662804](https://github.com/anncwb/vue-vben-admin/commit/566280422de0537c4e31496eaaa95a9d51fe9458))
- **codeeditor:** empty value set failed.fixed:[#659](https://github.com/anncwb/vue-vben-admin/issues/659) ([ba2bebb](https://github.com/anncwb/vue-vben-admin/commit/ba2bebb4069085817a90d065ed5877fdb50a8039))
- **layout:** fix class loss ([d018363](https://github.com/anncwb/vue-vben-admin/commit/d018363ddcd68189a18829a2b2560f3b98da58a6))
- **layout:** fix style compatibility issues ([905e5b7](https://github.com/anncwb/vue-vben-admin/commit/905e5b714b582548f32feca723012124343686a6))
- **log:** fix Wrong version number ([#653](https://github.com/anncwb/vue-vben-admin/issues/653)) ([4f0d45f](https://github.com/anncwb/vue-vben-admin/commit/4f0d45f1df48755eadc0b09fa19762ee68f9abd1))
- **modal:** redoModalHeight not work as expected ([5d554f1](https://github.com/anncwb/vue-vben-admin/commit/5d554f184f7b61774d1a1b2e61451677b38505de))
- **page:** `basic form` action btns should be in line ([6c4f947](https://github.com/anncwb/vue-vben-admin/commit/6c4f947386c181f45253c94e4ef735d29a253053))
- **radio-button:** fix RadioButton `disabled` support ([ee384b1](https://github.com/anncwb/vue-vben-admin/commit/ee384b1fa7e387b3680e9d54cbe4a1e2f15ec750)), closes [#710](https://github.com/anncwb/vue-vben-admin/issues/710)
- **table:** wrong indeterminate state ([495b1da](https://github.com/anncwb/vue-vben-admin/commit/495b1da385e9b6428d2b994669d2065722445923))
- **table:** make sure the table width is correct, fix [#593](https://github.com/anncwb/vue-vben-admin/issues/593) ([d73d43e](https://github.com/anncwb/vue-vben-admin/commit/d73d43ed91f30957cfd202c51552ca40a19cef08))
- **table:** settings indeterminate state effect ([4fd2051](https://github.com/anncwb/vue-vben-admin/commit/4fd2051bc0403bfc5345ed6a5fc283a372ef7a92))
- **table:** support change event ([9f4d171](https://github.com/anncwb/vue-vben-admin/commit/9f4d1719caa76de94e6362c16e4df3ac28df253c)), closes [#677](https://github.com/anncwb/vue-vben-admin/issues/677)
- **table:** try to get close to the form stuck ([d81481c](https://github.com/anncwb/vue-vben-admin/commit/d81481c52186145dac130aaa1594f0ba8db4d392))
- **Tinymce:** Read only status upload button can also be used ([#718](https://github.com/anncwb/vue-vben-admin/issues/718)) ([966571b](https://github.com/anncwb/vue-vben-admin/commit/966571bdcb11c2729ab9ce212bd3e195f7bf3a59))
- **upload:** ensure preview items valid ([4376928](https://github.com/anncwb/vue-vben-admin/commit/437692869a232ee65c300c65ee473557ae0913c7))
- ensure that roleList is not empty ([aebad61](https://github.com/anncwb/vue-vben-admin/commit/aebad61b3d3e11aaf720b37e762e53e2e6999d3c))
- fix node12 version data mock error ([644dbe3](https://github.com/anncwb/vue-vben-admin/commit/644dbe315bb03ea1641a682359873237208a5303))
- Fix the problem that the `lang` attribute of `HTML` will not be set when it is first loaded ([#682](https://github.com/anncwb/vue-vben-admin/issues/682)) ([eca8907](https://github.com/anncwb/vue-vben-admin/commit/eca8907a11c28d816c3da5a0667f45a38a499012))
- **table:** useTable support onChange ([9f5085c](https://github.com/anncwb/vue-vben-admin/commit/9f5085c9f9f46b09391156b17091c1771bc13026))
- **table-action:** fix the split line style is missing,fix [#674](https://github.com/anncwb/vue-vben-admin/issues/674) ([b1cb863](https://github.com/anncwb/vue-vben-admin/commit/b1cb86350253dc5be095466966d9469775f4395d))
- login failed ([035f55a](https://github.com/anncwb/vue-vben-admin/commit/035f55af9778819d72adc1700d9de56a6569b58f))
- session timeout login logic error ([#678](https://github.com/anncwb/vue-vben-admin/issues/678)) ([132c7fb](https://github.com/anncwb/vue-vben-admin/commit/132c7fb944df255c4d76a25d6d924439f91f9c54)), closes [#673](https://github.com/anncwb/vue-vben-admin/issues/673)
- **tree:** support defaultExpandAll prop ([3ed2339](https://github.com/anncwb/vue-vben-admin/commit/3ed2339a6d75abbd6ccf723b6eaa762f9921409e))
- theme switching fails ([7e2ca79](https://github.com/anncwb/vue-vben-admin/commit/7e2ca79ece2f5209cb7ce4b0f5ee15012f9f51de))
### Features
- **api-select:** auto refetch after params changed ([50207ad](https://github.com/anncwb/vue-vben-admin/commit/50207ad702ef3faca1e27c873c89132ab92fae8e))
- **app-search:** auto focus on show ([1ae6362](https://github.com/anncwb/vue-vben-admin/commit/1ae636296df2cf99e8a777f053c539c50e6ad49a))
- **demo:** `switch` use in table ([46899aa](https://github.com/anncwb/vue-vben-admin/commit/46899aa3cd6b1616c42ac263a28af75be839f6a0))
- **echarts:** add getInstance for useECharts ([fb6c76d](https://github.com/anncwb/vue-vben-admin/commit/fb6c76db535bd0c6305d03c0cff876a1f079100b))
- **modal:** add closeModal for useModal ([6d5f9aa](https://github.com/anncwb/vue-vben-admin/commit/6d5f9aa699c5da8af6bf5841baddc4a8bd603917))
- **modal:** add redoModalHeight for useModalInner ([f732b56](https://github.com/anncwb/vue-vben-admin/commit/f732b569042f7fe77c85cb295538ddd85561f7e9))
- **table:** add editable DatePicker & TimePicker ([#654](https://github.com/anncwb/vue-vben-admin/issues/654)) ([93006c7](https://github.com/anncwb/vue-vben-admin/commit/93006c7dc7b5243b26637f444c8057c95935e622))
- **table:** add updateTableDataRecord method ([8e4f486](https://github.com/anncwb/vue-vben-admin/commit/8e4f486fcf835f0b6f2a95676dba268ffdd0566e))
- **table:** editable component text align ([8eaf575](https://github.com/anncwb/vue-vben-admin/commit/8eaf57562610a833c8083ae9957f458319d1cc93))
- **table:** support columns-change event ([125a7d1](https://github.com/anncwb/vue-vben-admin/commit/125a7d14831642c9cbb2e4b3e75953c3b2e2cdef))
- **table:** support custom update on row editing ([fe2bcfc](https://github.com/anncwb/vue-vben-admin/commit/fe2bcfc6f74159c355f3be153a316869fdb8b644)), closes [#646](https://github.com/anncwb/vue-vben-admin/issues/646)
- **table:** updateTableDataRecord support functional rowKey ([448a4c2](https://github.com/anncwb/vue-vben-admin/commit/448a4c2809672480f8f635d7cc4661554112598a))
- **table-action:** add stopButtonPropagation prop ([808012b](https://github.com/anncwb/vue-vben-admin/commit/808012b544b8c6f3cf467f42653c2783dbe8be6b)), closes [#699](https://github.com/anncwb/vue-vben-admin/issues/699)
- **table-img:** support simple show mode and more props ([19d8e01](https://github.com/anncwb/vue-vben-admin/commit/19d8e01e11644c66222f137abd05940cbdec0bb6))
- **tabs:** add setTabTitle method ([#680](https://github.com/anncwb/vue-vben-admin/issues/680)) ([5ddccf6](https://github.com/anncwb/vue-vben-admin/commit/5ddccf6ba28453b9a35355d53d0db65f1a8876bc))
- **tinymce:** support dark theme and I18n ([83c9cd7](https://github.com/anncwb/vue-vben-admin/commit/83c9cd77421e9c0888a41e2d8dcbca816da67488))
- **Tinymce:** add dynamics to the read-only state of the rich text editor ([#725](https://github.com/anncwb/vue-vben-admin/issues/725)) ([efce482](https://github.com/anncwb/vue-vben-admin/commit/efce482b3215ddf9ed588f63a218d5f76939e947))
- **tree:** add defaultExpandLevel prop ([6edca1c](https://github.com/anncwb/vue-vben-admin/commit/6edca1c19c3b0772f9ab82a7b09251a74fff2173)), closes [#672](https://github.com/anncwb/vue-vben-admin/issues/672)
### Performance Improvements
- optimize components and add comments ([55e9d9f](https://github.com/anncwb/vue-vben-admin/commit/55e9d9fc2953643cec95c74b6ed34b0e68641fb6))
- **i18n:** improve circular dependencies ([d677729](https://github.com/anncwb/vue-vben-admin/commit/d677729acbe2c024ab13cf490b205528507c4823))
- **i18n:** improve warning prompt ([6ef62ba](https://github.com/anncwb/vue-vben-admin/commit/6ef62ba6ea7f5613a1fec982b30fe6b0f478bf59))
## [2.4.1](https://github.com/anncwb/vue-vben-admin/compare/v2.4.0...v2.4.1) (2021-06-01) ## [2.4.1](https://github.com/anncwb/vue-vben-admin/compare/v2.4.0...v2.4.1) (2021-06-01)
### Bug Fixes ### Bug Fixes

View File

@ -1,3 +1,78 @@
## 2.5.0(2021-06-20)
## (破坏性更新) Breaking changes
- 将项目`windicss`改为`tailwindcss`,解决内存溢出问题
- 目前项目不兼容地方有
- `!xl:m-4` 之类的写法需要改为`xl:!m-4`,注意只有`!`这个不兼容,没用到则不用改
- `windicss`自身新增的特性需要调整,比如`Attribute`模式不兼容
### ✨ Refactor
- 移除`useExpose`,使用组件自身提供的`expose`代替
### ⚡ Performance Improvements
- **Locale** 合并多语言文件,减少文件数量
- **Utils** Mitt 默认导出由 `Class` 改为 `Function`
- **Axios** `isTransformRequestResult`更名为`isTransformResponse`
### ✨ Features
- **CropperImage** `Cropper` 头像裁剪新增圆形裁剪功能
- **CropperAvatar** 新增头像上传组件
- **Drawer** `useDrawer`新增`closeDrawer`函数
- **Preview** 新增`createImgPreview`图片预览函数
- **Setup** 新增引导页示例
- **Tests** 添加 jest 测试套件,暂不支持 Vue 组件单测
- **Axios** 新增`authenticationScheme`配置,用于指定认证方案
- **Setting** 新增 `sessionTimeoutProcessing` 项目配置项,用于配置会话超时如何处理
### 🐛 Bug Fixes
- **Modal** 修复全屏高度计算错误
- **Modal** 修复关闭事件触发多次问题
- **PageWrapper** 修复高度计算问题
- **FlowChart** 修复拖放菜单丢失
- 修复后台模式下Iframe 路由错误
- **PageWrapper** 修复 footer 与全局页脚同时开启时的高度计算问题
- **Menu** 修复菜单折叠动画抖动问题
- **Store**修复 pinia 版本升级之后类型错误
## 2.4.2(2021-06-10)
### ✨ Refactor
- `CountTo`组件重构
### ✨ Features
- `radioButtonGroup` 支持`boolean`值
- `useModalInner` 新增 `redoModalHeight`用于在 Modal 内部重设`Modal`高度
- `useECharts` 新增`getInstance`用于获取`echart`实例
- `TableAction` 新增 `stopButtonPropagation` 阻止操作按钮点击事件冒泡
- `BasicTable` 在行编辑模式下,可以获取或设置其它处于列的编辑组件的值
- `ApiSelect` 组件在`params`改变后会自动重新`fetch`数据
- `TableImg` 组件改进
- `BasicTable` 新增 `columns-change` 事件用于监听用户改变列排序、展示、固定状态
- `Tinymce`支持动态修改 readonly
- `BasicTable`新增`updateTableDataRecord`方法用于更新指定行数据
- `useModal`新增`closeModal`方法用于关闭`Modal`
### 🐛 Bug Fixes
- 修复`redoModalHeight`不能减小高度的问题
- 修复 `BasicForm`设置 schemas 数据不生效的问题
- 修复多标签可能导致`KeepAlive`失效的问题
- 修复默认的`axios`拦截器不能处理自定义 code 的问题
- 修复锁屏弹窗的高度问题
- 修复`BaiscTable`的`列展示`复选框的半选状态显示不正确的问题
- 修复`BasicUpload`组件的预览列表某些情况下不能显示的问题
- 修复`RadioButtonGroup`的`options`设置`disabled`不生效的问题
- 修复`Tinymce`组件在只读模式下上传图片的按钮仍然可用的问题
- 修复`BasicForm`特定情况下的卡顿问题
- 修复"目录"路由不起作用的问题
## 2.4.1(2021-06-01) ## 2.4.1(2021-06-01)
### ✨ Features ### ✨ Features

View File

@ -5,7 +5,6 @@ import vueJsx from '@vitejs/plugin-vue-jsx';
import legacy from '@vitejs/plugin-legacy'; import legacy from '@vitejs/plugin-legacy';
import purgeIcons from 'vite-plugin-purge-icons'; import purgeIcons from 'vite-plugin-purge-icons';
import windiCSS from 'vite-plugin-windicss';
import { configHtmlPlugin } from './html'; import { configHtmlPlugin } from './html';
import { configPwaConfig } from './pwa'; import { configPwaConfig } from './pwa';
@ -46,9 +45,6 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
// vite-plugin-svg-icons // vite-plugin-svg-icons
vitePlugins.push(configSvgIconsPlugin(isBuild)); vitePlugins.push(configSvgIconsPlugin(isBuild));
// vite-plugin-windicss
vitePlugins.push(windiCSS());
// vite-plugin-mock // vite-plugin-mock
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild)); VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild));

36
jest.config.mjs Normal file
View File

@ -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$'],
};

View File

@ -1,12 +1,15 @@
import { MockMethod } from 'vite-plugin-mock'; import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess } from '../_util'; import { resultSuccess } from '../_util';
const list: any[] = [];
const demoList = (() => { const demoList = (() => {
const result: any[] = []; const result = {
list: list,
};
for (let index = 0; index < 20; index++) { for (let index = 0; index < 20; index++) {
result.push({ result.list.push({
label: `选项${index}`, name: `选项${index}`,
value: `${index}`, id: `${index}`,
}); });
} }
return result; return result;
@ -15,8 +18,8 @@ const demoList = (() => {
export default [ export default [
{ {
url: '/basic-api/select/getDemoOptions', url: '/basic-api/select/getDemoOptions',
timeout: 2000, timeout: 1000,
method: 'get', method: 'post',
response: ({ query }) => { response: ({ query }) => {
console.log(query); console.log(query);
return resultSuccess(demoList); return resultSuccess(demoList);

View File

@ -168,6 +168,34 @@ const sysRoute = {
], ],
}; };
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 [ export default [
{ {
url: '/basic-api/getMenuList', url: '/basic-api/getMenuList',
@ -184,10 +212,10 @@ export default [
} }
const id = checkUser.userId; const id = checkUser.userId;
if (!id || id === '1') { if (!id || id === '1') {
return resultSuccess([dashboardRoute, authRoute, levelRoute, sysRoute]); return resultSuccess([dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute]);
} }
if (id === '2') { if (id === '2') {
return resultSuccess([dashboardRoute, authRoute, levelRoute]); return resultSuccess([dashboardRoute, authRoute, levelRoute, linkRoute]);
} }
}, },
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "vben-admin", "name": "vben-admin",
"version": "2.4.1", "version": "2.5.0",
"author": { "author": {
"name": "vben", "name": "vben",
"email": "anncwb@126.com", "email": "anncwb@126.com",
@ -62,11 +62,13 @@
"log": "conventional-changelog -p angular -i CHANGELOG.md -s", "log": "conventional-changelog -p angular -i CHANGELOG.md -s",
"clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite", "clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
"clean:lib": "rimraf node_modules", "clean:lib": "rimraf node_modules",
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix", "lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"", "lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", "lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js", "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
"lint:pretty": "pretty-quick --staged", "lint:pretty": "pretty-quick --staged",
"test:unit": "jest",
"test:unit-coverage": "jest --coverage",
"test:gzip": "http-server dist --cors --gzip -c-1", "test:gzip": "http-server dist --cors --gzip -c-1",
"test:br": "http-server dist --cors --brotli -c-1", "test:br": "http-server dist --cors --brotli -c-1",
"reinstall": "rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run bootstrap", "reinstall": "rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
@ -75,23 +77,24 @@
"postinstall": "npm run install:husky" "postinstall": "npm run install:husky"
}, },
"dependencies": { "dependencies": {
"@iconify/iconify": "^2.0.1", "@iconify/iconify": "^2.0.2",
"@logicflow/core": "^0.4.11", "@logicflow/core": "^0.5.0",
"@logicflow/extension": "^0.4.12", "@logicflow/extension": "^0.5.0",
"@vueuse/core": "^4.11.2", "@vueuse/core": "^5.0.3",
"@zxcvbn-ts/core": "^0.3.0", "@zxcvbn-ts/core": "^0.3.0",
"ant-design-vue": "2.1.2", "ant-design-vue": "2.1.6",
"axios": "^0.21.1", "axios": "^0.21.1",
"codemirror": "^5.61.1", "codemirror": "^5.61.1",
"cropperjs": "^1.5.11", "cropperjs": "^1.5.12",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"echarts": "^5.1.1",
"electron-is-dev": "^1.2.0", "electron-is-dev": "^1.2.0",
"echarts": "^5.1.2",
"intro.js": "^4.1.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"pinia": "2.0.0-alpha.13", "pinia": "2.0.0-beta.3",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"sortablejs": "^1.13.0", "sortablejs": "^1.13.0",
@ -100,14 +103,14 @@
"vue": "3.0.11", "vue": "3.0.11",
"vue-i18n": "9.1.6", "vue-i18n": "9.1.6",
"vue-json-pretty": "^2.0.2", "vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.8", "vue-router": "^4.0.9",
"vue-types": "^3.0.2", "vue-types": "^3.0.2",
"xlsx": "^0.17.0" "xlsx": "^0.17.0"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^12.1.4", "@commitlint/cli": "^12.1.4",
"@commitlint/config-conventional": "^12.1.4", "@commitlint/config-conventional": "^12.1.4",
"@iconify/json": "^1.1.353", "@iconify/json": "^1.1.358",
"@purge-icons/generated": "^0.7.0", "@purge-icons/generated": "^0.7.0",
"@rollup/plugin-alias": "^3.1.1", "@rollup/plugin-alias": "^3.1.1",
"@rollup/plugin-commonjs": "^15.0.0", "@rollup/plugin-commonjs": "^15.0.0",
@ -116,20 +119,23 @@
"@types/codemirror": "^5.60.0", "@types/codemirror": "^5.60.0",
"@types/crypto-js": "^4.0.1", "@types/crypto-js": "^4.0.1",
"@types/fs-extra": "^9.0.11", "@types/fs-extra": "^9.0.11",
"@types/inquirer": "^7.3.1", "@types/inquirer": "^7.3.2",
"@types/intro.js": "^3.0.1",
"@types/jest": "^26.0.23",
"@types/lodash-es": "^4.17.4", "@types/lodash-es": "^4.17.4",
"@types/mockjs": "^1.0.3", "@types/mockjs": "^1.0.3",
"@types/node": "^15.12.1", "@types/node": "^15.12.4",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.0", "@types/qrcode": "^1.4.0",
"@types/qs": "^6.9.6", "@types/qs": "^6.9.6",
"@types/sortablejs": "^1.10.6", "@types/sortablejs": "^1.10.6",
"@typescript-eslint/eslint-plugin": "^4.26.0", "@typescript-eslint/eslint-plugin": "^4.27.0",
"@typescript-eslint/parser": "^4.26.0", "@typescript-eslint/parser": "^4.27.0",
"@vitejs/plugin-legacy": "^1.4.1", "@vitejs/plugin-legacy": "^1.4.1",
"@vitejs/plugin-vue": "^1.2.3", "@vitejs/plugin-vue": "^1.2.3",
"@vitejs/plugin-vue-jsx": "^1.1.5", "@vitejs/plugin-vue-jsx": "^1.1.5",
"@vue/compiler-sfc": "3.0.11", "@vue/compiler-sfc": "3.0.11",
"@vue/test-utils": "^2.0.0-rc.7",
"autoprefixer": "^10.2.6", "autoprefixer": "^10.2.6",
"commitizen": "^4.2.4", "commitizen": "^4.2.4",
"conventional-changelog-cli": "^2.1.1", "conventional-changelog-cli": "^2.1.1",
@ -140,22 +146,25 @@
"electron-connect": "^0.6.3", "electron-connect": "^0.6.3",
"electron-contextmenu-middleware": "^1.0.3", "electron-contextmenu-middleware": "^1.0.3",
"electron-input-menu": "^2.1.0", "electron-input-menu": "^2.1.0",
"eslint": "^7.28.0", "eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-define-config": "^1.0.8", "eslint-define-config": "^1.0.8",
"eslint-plugin-jest": "^24.3.6",
"eslint-plugin-prettier": "^3.4.0", "eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.10.0", "eslint-plugin-vue": "^7.11.1",
"esno": "^0.7.0", "esno": "^0.7.3",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"http-server": "^0.12.3", "http-server": "^0.12.3",
"husky": "^6.0.0", "husky": "^6.0.0",
"inquirer": "^8.1.0", "inquirer": "^8.1.1",
"is-ci": "^3.0.0", "is-ci": "^3.0.0",
"jest": "^27.0.4",
"less": "^4.1.1", "less": "^4.1.1",
"lint-staged": "^11.0.0", "lint-staged": "^11.0.0",
"postcss": "^8.3.0", "npm-run-all": "^4.1.5",
"postcss": "^8.3.5",
"prettier": "^2.3.1", "prettier": "^2.3.1",
"pretty-quick": "^3.1.0", "pretty-quick": "^3.1.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup-plugin-esbuild": "^3.0.2", "rollup-plugin-esbuild": "^3.0.2",
"rollup-plugin-visualizer": "5.5.0", "rollup-plugin-visualizer": "5.5.0",
@ -163,19 +172,20 @@
"stylelint-config-prettier": "^8.0.2", "stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^22.0.0", "stylelint-config-standard": "^22.0.0",
"stylelint-order": "^4.1.0", "stylelint-order": "^4.1.0",
"tailwindcss": "^2.2.2",
"ts-jest": "^27.0.3",
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "4.3.2", "typescript": "4.3.4",
"vite": "2.3.6", "vite": "2.3.8",
"vite-plugin-compression": "^0.2.5", "vite-plugin-compression": "^0.2.5",
"vite-plugin-html": "^2.0.7", "vite-plugin-html": "^2.0.7",
"vite-plugin-imagemin": "^0.3.2", "vite-plugin-imagemin": "^0.3.2",
"vite-plugin-mock": "^2.7.0", "vite-plugin-mock": "^2.8.0",
"vite-plugin-purge-icons": "^0.7.0", "vite-plugin-purge-icons": "^0.7.0",
"vite-plugin-pwa": "^0.7.3", "vite-plugin-pwa": "^0.8.1",
"vite-plugin-style-import": "^0.10.1", "vite-plugin-style-import": "^1.0.0",
"vite-plugin-svg-icons": "^0.7.0", "vite-plugin-svg-icons": "^0.7.1",
"vite-plugin-theme": "^0.8.1", "vite-plugin-theme": "^0.8.1",
"vite-plugin-windicss": "^1.0.1",
"vue-eslint-parser": "^7.6.0", "vue-eslint-parser": "^7.6.0",
"vue-tsc": "^0.1.7", "vue-tsc": "^0.1.7",
"wait-on": "^5.2.1" "wait-on": "^5.2.1"
@ -183,7 +193,7 @@
"resolutions": { "resolutions": {
"//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it", "//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it",
"bin-wrapper": "npm:bin-wrapper-china", "bin-wrapper": "npm:bin-wrapper-china",
"rollup": "^2.51.0" "rollup": "^2.52.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

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

View File

@ -5,7 +5,11 @@ export interface DemoOptionsItem {
value: string; value: string;
} }
export interface selectParams {
id: number | string;
}
/** /**
* @description: Request list return value * @description: Request list return value
*/ */
export type DemoOptionsGetResultModel = BasicFetchResult<DemoOptionsItem[]>; export type DemoOptionsGetResultModel = BasicFetchResult<DemoOptionsItem>;

View File

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

View File

@ -4,6 +4,6 @@ export interface BasicPageParams {
} }
export interface BasicFetchResult<T extends any> { export interface BasicFetchResult<T extends any> {
items: T; items: T[];
total: number; total: number;
} }

View File

@ -1,4 +1,4 @@
import { RouteMeta } from '/@/router/types'; import type { RouteMeta } from 'vue-router';
export interface RouteItem { export interface RouteItem {
path: string; path: string;
component: any; component: any;

View File

@ -1,6 +1,6 @@
import { UploadApiResult } from './model/uploadModel'; import { UploadApiResult } from './model/uploadModel';
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios';
import { UploadFileParams } from '/@/utils/http/axios/types'; import { UploadFileParams } from '/#/axios';
import { useGlobSetting } from '/@/hooks/setting'; import { useGlobSetting } from '/@/hooks/setting';
const { uploadUrl = '' } = useGlobSetting(); const { uploadUrl = '' } = useGlobSetting();

View File

@ -1,7 +1,7 @@
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios';
import { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel'; import { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel';
import { ErrorMessageMode } from '/@/utils/http/axios/types'; import { ErrorMessageMode } from '/#/axios';
enum Api { enum Api {
Login = '/login', Login = '/login',

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595306944988" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1820" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M1464.3 279.7" p-id="1821" fill="#ffffff"></path><path d="M512 960c-60.5 0-119.1-11.9-174.4-35.2-53.4-22.6-101.3-54.9-142.4-96s-73.4-89-96-142.4C75.9 631.1 64 572.5 64 512s11.9-119.1 35.2-174.4c22.6-53.4 54.9-101.3 96-142.4s89-73.4 142.4-96C392.9 75.9 451.5 64 512 64s119.1 11.9 174.4 35.2c53.4 22.6 101.3 54.9 142.4 96s73.4 89 96 142.4C948.1 392.9 960 451.5 960 512c0 19.1-15.5 34.6-34.6 34.6s-34.6-15.5-34.6-34.6c0-51.2-10-100.8-29.8-147.4-19.1-45.1-46.4-85.6-81.2-120.4C745 209.4 704.5 182 659.4 163c-46.7-19.7-96.3-29.8-147.4-29.8-51.2 0-100.8 10-147.4 29.8-45.1 19.1-85.6 46.4-120.4 81.2S182 319.5 163 364.6c-19.7 46.7-29.8 96.3-29.8 147.4 0 51.2 10 100.8 29.8 147.4 19.1 45.1 46.4 85.6 81.2 120.4C279 814.6 319.5 842 364.6 861c46.7 19.7 96.3 29.8 147.4 29.8 64.6 0 128.4-16.5 184.4-47.8 54.4-30.4 100.9-74.1 134.6-126.6 10.3-16.1 31.7-20.8 47.8-10.4 16.1 10.3 20.8 31.7 10.4 47.8-39.8 62-94.8 113.7-159.1 149.6-66.2 37-141.7 56.6-218.1 56.6z" p-id="1822" fill="#ffffff"></path><path d="M924 552c-19.8 0-36-16.2-36-36V228c0-19.8 16.2-36 36-36s36 16.2 36 36v288c0 19.8-16.2 36-36 36zM275.4 575.5c9.5-2.5 19.1 2.9 22.3 12.2 3.5 10.2 9.9 17.7 19.1 22.6 7.1 3.9 15.1 5.8 24 5.8 16.6 0 30.8-6.9 42.5-20.8 11.7-13.8 20-32.7 24.9-75.1-7.7 12.2-17.3 20.8-28.7 25.8-11.4 5-23.7 7.4-36.8 7.4-26.7 0-47.7-8.3-63.3-24.9-15.5-16.6-23.3-37.9-23.3-64.1 0-25.1 7.7-47.1 23-66.2 15.3-19 37.9-28.6 67.8-28.6 40.3 0 68.1 18.1 83.4 54.4 8.5 19.9 12.7 44.9 12.7 74.9 0 33.8-5.1 63.8-15.3 89.9-16.9 43.5-45.5 65.2-85.8 65.2-27 0-47.6-7.1-61.6-21.2-10-10.1-16.4-22-19.3-35.8-2-9.6 4-19.1 13.5-21.6l0.9 0.1z m103-74.4c9.4-7.5 14.1-20.6 14.1-39.3 0-16.8-4.2-29.3-12.7-37.5S360.6 412 347.5 412c-14 0-25.2 4.7-33.4 14.1-8.2 9.4-12.4 22-12.4 37.7 0 14.9 3.6 26.7 10.9 35.5 7.2 8.8 18.8 13.1 34.6 13.1 11.4 0 21.8-3.8 31.2-11.3zM646.6 414.4c12.4 22.8 18.5 54 18.5 93.7 0 37.6-5.6 68.7-16.8 93.3-16.2 35.3-42.8 52.9-79.6 52.9-33.2 0-57.9-14.4-74.2-43.3-13.5-24.1-20.3-56.4-20.3-97 0-31.4 4.1-58.4 12.2-80.9 15.2-42 42.7-63 82.5-63 35.9 0 61.8 14.8 77.7 44.3z m-40.2 173.3c9.4-13.9 14-39.9 14-78 0-27.4-3.4-50-10.1-67.7-6.8-17.7-19.9-26.6-39.4-26.6-17.9 0-31 8.4-39.3 25.2-8.3 16.8-12.4 41.6-12.4 74.3 0 24.6 2.6 44.4 7.9 59.4 8.1 22.8 22 34.3 41.6 34.3 15.7 0 28.3-7 37.7-20.9zM803.3 387.2c11.2 11.3 16.8 25 16.8 41.2 0 16.7-5.8 30.7-17.5 41.8C791 481.4 777.4 487 762 487c-17.1 0-31.2-5.8-42.1-17.4-10.9-11.6-16.4-25.1-16.4-40.6 0-16.5 5.8-30.4 17.3-41.7 11.5-11.3 25.3-17 41.2-17 16.3 0 30.1 5.7 41.3 16.9zM739.5 451c6.2 6.2 13.7 9.3 22.5 9.3 8.4 0 15.8-3.1 22.1-9.3 6.3-6.2 9.4-13.7 9.4-22.6 0-8.5-3.1-15.9-9.3-22.1-6.2-6.2-13.6-9.3-22.2-9.3s-16.1 3.1-22.4 9.3c-6.3 6.2-9.4 13.7-9.4 22.6-0.1 8.4 3 15.8 9.3 22.1z" p-id="1823" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595307154239" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7317" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M316 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8zM512 622c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39zM512 482c22.1 0 40-17.9 40-39 0-23.1-17.9-41-40-41s-40 17.9-40 41c0 21.1 17.9 39 40 39z" p-id="7318" fill="#ffffff"></path><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32z m-40 728H184V184h656v656z" p-id="7319" fill="#ffffff"></path><path d="M648 672h60c4.4 0 8-3.6 8-8V360c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v304c0 4.4 3.6 8 8 8z" p-id="7320" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 996 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595307195033" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8116" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M887.081 904.791a25.8 25.8 0 0 1-18.376-7.619L705.618 734.075l-4.163 3.369c-58.255 47.18-131.522 73.16-206.32 73.16-181.07 0-328.377-147.308-328.377-328.367 0-181.068 147.308-328.376 328.377-328.376 181.063 0 328.376 147.308 328.376 328.376 0 77.072-27.412 152.07-77.169 211.17l-3.522 4.173 162.719 162.744a25.846 25.846 0 0 1 7.639 18.432 26.081 26.081 0 0 1-26.051 26.045l-0.046-0.01zM495.13 205.957c-152.336 0-276.27 123.935-276.27 276.27 0 152.33 123.934 276.27 276.27 276.27 152.34 0 276.275-123.94 276.275-276.27 0-152.335-123.935-276.27-276.275-276.27z" fill="#ffffff" p-id="8117"></path><path d="M626.545 508.355h-262.83a26.127 26.127 0 0 1 0-52.255h262.83a26.127 26.127 0 0 1 0 52.255z" fill="#ffffff" p-id="8118"></path><path d="M495.13 639.77a26.127 26.127 0 0 1-26.128-26.128v-262.83a26.127 26.127 0 0 1 52.255 0v262.835a26.127 26.127 0 0 1-26.127 26.123z" fill="#ffffff" p-id="8119"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595306911635" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1352" width="48" height="48" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M924.8 337.6c-22.6-53.4-54.9-101.3-96-142.4s-89-73.4-142.4-96C631.1 75.9 572.5 64 512 64S392.9 75.9 337.6 99.2c-53.4 22.6-101.3 54.9-142.4 96-22.4 22.4-42.2 46.8-59.2 73.1V228c0-19.8-16.2-36-36-36s-36 16.2-36 36v288c0 19.8 16.2 36 36 36s36-16.2 36-36v-50.2c4.2-34.8 13.2-68.7 27-101.2 19.1-45.1 46.4-85.6 81.2-120.4C279 209.4 319.5 182 364.6 163c46.7-19.7 96.3-29.8 147.4-29.8 51.2 0 100.8 10 147.4 29.8 45.1 19.1 85.6 46.4 120.4 81.2C814.6 279 842 319.5 861 364.6c19.7 46.7 29.8 96.3 29.8 147.4 0 51.2-10 100.8-29.8 147.4-19.1 45.1-46.4 85.6-81.2 120.4C745 814.6 704.5 842 659.4 861c-46.7 19.7-96.3 29.8-147.4 29.8-64.6 0-128.4-16.5-184.4-47.8-54.4-30.4-100.9-74.1-134.6-126.6-10.3-16.1-31.7-20.8-47.8-10.4-16.1 10.3-20.8 31.7-10.4 47.8 39.8 62 94.8 113.7 159.1 149.6 66.2 37 141.7 56.6 218.1 56.6 60.5 0 119.1-11.9 174.4-35.2 53.4-22.6 101.3-54.9 142.4-96 41.1-41.1 73.4-89 96-142.4C948.1 631.1 960 572.5 960 512s-11.9-119.1-35.2-174.4z" p-id="1353" fill="#ffffff"></path><path d="M275.4 575.5c9.5-2.5 19.1 2.9 22.3 12.2 3.5 10.2 9.9 17.7 19.1 22.6 7.1 3.9 15.1 5.8 24 5.8 16.6 0 30.8-6.9 42.5-20.8 11.7-13.8 20-32.7 24.9-75.1-7.7 12.2-17.3 20.8-28.7 25.8-11.4 5-23.7 7.4-36.8 7.4-26.7 0-47.7-8.3-63.3-24.9-15.5-16.6-23.3-37.9-23.3-64.1 0-25.1 7.7-47.1 23-66.2 15.3-19 37.9-28.6 67.8-28.6 40.3 0 68.1 18.1 83.4 54.4 8.5 19.9 12.7 44.9 12.7 74.9 0 33.8-5.1 63.8-15.3 89.9-16.9 43.5-45.5 65.2-85.8 65.2-27 0-47.6-7.1-61.6-21.2-10-10.1-16.4-22-19.3-35.8-2-9.6 4-19.1 13.5-21.6l0.9 0.1z m103-74.4c9.4-7.5 14.1-20.6 14.1-39.3 0-16.8-4.2-29.3-12.7-37.5S360.6 412 347.5 412c-14 0-25.2 4.7-33.4 14.1-8.2 9.4-12.4 22-12.4 37.7 0 14.9 3.6 26.7 10.9 35.5 7.2 8.8 18.8 13.1 34.6 13.1 11.4 0 21.8-3.8 31.2-11.3zM646.6 414.4c12.4 22.8 18.5 54 18.5 93.7 0 37.6-5.6 68.7-16.8 93.3-16.2 35.3-42.8 52.9-79.6 52.9-33.2 0-57.9-14.4-74.2-43.3-13.5-24.1-20.3-56.4-20.3-97 0-31.4 4.1-58.4 12.2-80.9 15.2-42 42.7-63 82.5-63 35.9 0 61.8 14.8 77.7 44.3z m-40.2 173.3c9.4-13.9 14-39.9 14-78 0-27.4-3.4-50-10.1-67.7-6.8-17.7-19.9-26.6-39.4-26.6-17.9 0-31 8.4-39.3 25.2-8.3 16.8-12.4 41.6-12.4 74.3 0 24.6 2.6 44.4 7.9 59.4 8.1 22.8 22 34.3 41.6 34.3 15.7 0 28.3-7 37.7-20.9zM803.3 387.2c11.2 11.3 16.8 25 16.8 41.2 0 16.7-5.8 30.7-17.5 41.8C791 481.4 777.4 487 762 487c-17.1 0-31.2-5.8-42.1-17.4-10.9-11.6-16.4-25.1-16.4-40.6 0-16.5 5.8-30.4 17.3-41.7 11.5-11.3 25.3-17 41.2-17 16.3 0 30.1 5.7 41.3 16.9zM739.5 451c6.2 6.2 13.7 9.3 22.5 9.3 8.4 0 15.8-3.1 22.1-9.3 6.3-6.2 9.4-13.7 9.4-22.6 0-8.5-3.1-15.9-9.3-22.1-6.2-6.2-13.6-9.3-22.2-9.3s-16.1 3.1-22.4 9.3c-6.3 6.2-9.4 13.7-9.4 22.6-0.1 8.4 3 15.8 9.3 22.1z" p-id="1354" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1595308005241" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9878" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><defs><style type="text/css"></style></defs><path d="M750.3 198.7C598 46.4 351.1 46.4 198.7 198.7s-152.3 399.2 0 551.5C345.1 896.6 578.8 902.3 732 767.3l172.1 172.1 35.4-35.4-172.1-171.9c135-153.2 129.3-387-17.1-533.4z m39.3 403.8c-17.1 42.1-42.2 80-74.7 112.4-32.5 32.5-70.3 57.6-112.4 74.7-40.7 16.5-83.8 24.9-128 24.9s-87.2-8.4-128-24.9c-42.1-17.1-80-42.2-112.4-74.7s-57.6-70.3-74.7-112.4c-16.5-40.7-24.9-83.8-24.9-128s8.4-87.2 24.9-128c17.1-42.1 42.2-80 74.7-112.4s70.3-57.6 112.4-74.7c40.7-16.5 83.8-24.9 128-24.9s87.2 8.4 128 24.9c42.1 17.1 80 42.2 112.4 74.7 32.5 32.5 57.6 70.3 74.7 112.4 16.5 40.7 24.9 83.8 24.9 128s-8.4 87.3-24.9 128zM671 502H271v-50h400v50z" fill="#ffffff" p-id="9879"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,8 +1,15 @@
import AppLogo from './src/AppLogo.vue'; import { withInstall } from '/@/utils';
import AppProvider from './src/AppProvider.vue';
import AppSearch from './src/search/AppSearch.vue'; import appLogo from './src/AppLogo.vue';
import AppLocalePicker from './src/AppLocalePicker.vue'; import appProvider from './src/AppProvider.vue';
import AppDarkModeToggle from './src/AppDarkModeToggle.vue'; import appSearch from './src/search/AppSearch.vue';
import appLocalePicker from './src/AppLocalePicker.vue';
import appDarkModeToggle from './src/AppDarkModeToggle.vue';
export { useAppProviderContext } from './src/useAppContext'; export { useAppProviderContext } from './src/useAppContext';
export { AppLogo, AppProvider, AppSearch, AppLocalePicker, AppDarkModeToggle };
export const AppLogo = withInstall(appLogo);
export const AppProvider = withInstall(appProvider);
export const AppSearch = withInstall(appSearch);
export const AppLocalePicker = withInstall(appLocalePicker);
export const AppDarkModeToggle = withInstall(appDarkModeToggle);

View File

@ -1,40 +1,35 @@
<template> <template>
<div <div v-if="getShowDarkModeToggle" :class="getClass" @click="toggleDarkMode">
v-if="getShowDarkModeToggle"
:class="[
prefixCls,
{
[`${prefixCls}--dark`]: isDark,
},
]"
@click="toggleDarkMode"
>
<div :class="`${prefixCls}-inner`"> </div> <div :class="`${prefixCls}-inner`"> </div>
<SvgIcon size="14" name="sun" /> <SvgIcon size="14" name="sun" />
<SvgIcon size="14" name="moon" /> <SvgIcon size="14" name="moon" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed } from 'vue'; import { defineComponent, computed, unref } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { SvgIcon } from '/@/components/Icon'; import { SvgIcon } from '/@/components/Icon';
import { useDesign } from '/@/hooks/web/useDesign';
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground'; import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground';
import { updateDarkTheme } from '/@/logics/theme/dark'; import { updateDarkTheme } from '/@/logics/theme/dark';
import { ThemeEnum } from '/@/enums/appEnum'; import { ThemeEnum } from '/@/enums/appEnum';
export default defineComponent({ export default defineComponent({
name: 'DarkModeToggle', name: 'DarkModeToggle',
components: { SvgIcon }, components: { SvgIcon },
setup() { setup() {
const { prefixCls } = useDesign('dark-mode-toggle'); const { prefixCls } = useDesign('dark-switch');
const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting(); const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting();
const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK); const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK);
const getClass = computed(() => [
prefixCls,
{
[`${prefixCls}--dark`]: unref(isDark),
},
]);
function toggleDarkMode() { function toggleDarkMode() {
const darkMode = getDarkMode.value === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK; const darkMode = getDarkMode.value === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK;
setDarkMode(darkMode); setDarkMode(darkMode);
@ -44,6 +39,7 @@
} }
return { return {
getClass,
isDark, isDark,
prefixCls, prefixCls,
toggleDarkMode, toggleDarkMode,
@ -53,7 +49,7 @@
}); });
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@prefix-cls: ~'@{namespace}-dark-mode-toggle'; @prefix-cls: ~'@{namespace}-dark-switch';
html[data-theme='dark'] { html[data-theme='dark'] {
.@{prefix-cls} { .@{prefix-cls} {

View File

@ -13,39 +13,44 @@
> >
<span class="cursor-pointer flex items-center"> <span class="cursor-pointer flex items-center">
<Icon icon="ion:language" /> <Icon icon="ion:language" />
<span v-if="showText" class="ml-1">{{ getLangText }}</span> <span v-if="showText" class="ml-1">{{ getLocaleText }}</span>
</span> </span>
</Dropdown> </Dropdown>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { LocaleType } from '/#/config'; import type { LocaleType } from '/#/config';
import type { DropMenu } from '/@/components/Dropdown'; import type { DropMenu } from '/@/components/Dropdown';
import { defineComponent, ref, watchEffect, unref, computed } from 'vue'; import { defineComponent, ref, watchEffect, unref, computed } from 'vue';
import { Dropdown } from '/@/components/Dropdown'; import { Dropdown } from '/@/components/Dropdown';
import Icon from '/@/components/Icon'; import { Icon } from '/@/components/Icon';
import { useLocale } from '/@/locales/useLocale'; import { useLocale } from '/@/locales/useLocale';
import { localeList } from '/@/settings/localeSetting'; import { localeList } from '/@/settings/localeSetting';
import { propTypes } from '/@/utils/propTypes';
const props = {
/**
* Whether to display text
*/
showText: { type: Boolean, default: true },
/**
* Whether to refresh the interface when changing
*/
reload: { type: Boolean },
};
export default defineComponent({ export default defineComponent({
name: 'AppLocalPicker', name: 'AppLocalPicker',
components: { Dropdown, Icon }, components: { Dropdown, Icon },
props: { props,
// Whether to display text
showText: propTypes.bool.def(true),
// Whether to refresh the interface when changing
reload: propTypes.bool,
},
setup(props) { setup(props) {
const selectedKeys = ref<string[]>([]); const selectedKeys = ref<string[]>([]);
const { changeLocale, getLocale } = useLocale(); const { changeLocale, getLocale } = useLocale();
const getLangText = computed(() => { const getLocaleText = computed(() => {
const key = selectedKeys.value[0]; const key = selectedKeys.value[0];
if (!key) return ''; if (!key) {
return '';
}
return localeList.find((item) => item.event === key)?.text; return localeList.find((item) => item.event === key)?.text;
}); });
@ -60,11 +65,13 @@
} }
function handleMenuEvent(menu: DropMenu) { function handleMenuEvent(menu: DropMenu) {
if (unref(getLocale) === menu.event) return; if (unref(getLocale) === menu.event) {
return;
}
toggleLocale(menu.event as string); toggleLocale(menu.event as string);
} }
return { localeList, handleMenuEvent, selectedKeys, getLangText }; return { localeList, handleMenuEvent, selectedKeys, getLocaleText };
}, },
}); });
</script> </script>

View File

@ -3,63 +3,69 @@
* @Description: logo component * @Description: logo component
--> -->
<template> <template>
<div <div class="anticon" :class="getAppLogoClass" @click="goHome">
class="anticon"
:class="[prefixCls, theme, { 'collapsed-show-title': getCollapsedShowTitle }]"
@click="handleGoHome"
>
<img src="../../../assets/images/logo.png" /> <img src="../../../assets/images/logo.png" />
<div <div class="ml-2 truncate md:opacity-100" :class="getTitleClass" v-show="showTitle">
class="ml-2 truncate md:opacity-100"
:class="[
`${prefixCls}__title`,
{
'xs:opacity-0': !alwaysShowTitle,
},
]"
v-show="showTitle"
>
{{ title }} {{ title }}
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, computed, unref } from 'vue';
import { useGlobSetting } from '/@/hooks/setting'; import { useGlobSetting } from '/@/hooks/setting';
import { useGo } from '/@/hooks/web/usePage'; import { useGo } from '/@/hooks/web/usePage';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { PageEnum } from '/@/enums/pageEnum'; import { PageEnum } from '/@/enums/pageEnum';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({ const props = {
name: 'AppLogo',
props: {
/** /**
* The theme of the current parent component * The theme of the current parent component
*/ */
theme: propTypes.oneOf(['light', 'dark']), theme: { type: String, validator: (v) => ['light', 'dark'].includes(v) },
// Whether to show title /**
showTitle: propTypes.bool.def(true), * Whether to show title
alwaysShowTitle: propTypes.bool.def(false), */
}, showTitle: { type: Boolean, default: true },
setup() { /**
* The title is also displayed when the menu is collapsed
*/
alwaysShowTitle: { type: Boolean },
};
export default defineComponent({
name: 'AppLogo',
props: props,
setup(props) {
const { prefixCls } = useDesign('app-logo'); const { prefixCls } = useDesign('app-logo');
const { getCollapsedShowTitle } = useMenuSetting(); const { getCollapsedShowTitle } = useMenuSetting();
const { title } = useGlobSetting(); const { title } = useGlobSetting();
const go = useGo(); const go = useGo();
function handleGoHome(): void { const getAppLogoClass = computed(() => [
prefixCls,
props.theme,
{ 'collapsed-show-title': unref(getCollapsedShowTitle) },
]);
const getTitleClass = computed(() => [
`${prefixCls}__title`,
{
'xs:opacity-0': !props.alwaysShowTitle,
},
]);
function goHome() {
go(PageEnum.BASE_HOME); go(PageEnum.BASE_HOME);
} }
return { return {
handleGoHome, getAppLogoClass,
getTitleClass,
getCollapsedShowTitle,
goHome,
title, title,
prefixCls, prefixCls,
getCollapsedShowTitle,
}; };
}, },
}); });

View File

@ -1,26 +1,29 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, toRefs, ref, unref } from 'vue'; import { defineComponent, toRefs, ref, unref } from 'vue';
import { createAppProviderContext } from './useAppContext'; import { createAppProviderContext } from './useAppContext';
import { prefixCls } from '/@/settings/designSetting';
import { createBreakpointListen } from '/@/hooks/event/useBreakpoint'; import { createBreakpointListen } from '/@/hooks/event/useBreakpoint';
import { propTypes } from '/@/utils/propTypes'; import { prefixCls } from '/@/settings/designSetting';
import { useAppStore } from '/@/store/modules/app'; import { useAppStore } from '/@/store/modules/app';
import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum'; import { MenuModeEnum, MenuTypeEnum } from '/@/enums/menuEnum';
const props = {
/**
* class style prefix
*/
prefixCls: { type: String, default: prefixCls },
};
export default defineComponent({ export default defineComponent({
name: 'AppProvider', name: 'AppProvider',
inheritAttrs: false, inheritAttrs: false,
props: { props,
prefixCls: propTypes.string.def(prefixCls),
},
setup(props, { slots }) { setup(props, { slots }) {
const isMobile = ref(false); const isMobile = ref(false);
const isSetState = ref(false); const isSetState = ref(false);
const appStore = useAppStore(); const appStore = useAppStore();
// Monitor screen breakpoint information changes
createBreakpointListen(({ screenMap, sizeEnum, width }) => { createBreakpointListen(({ screenMap, sizeEnum, width }) => {
const lgWidth = screenMap.get(sizeEnum.LG); const lgWidth = screenMap.get(sizeEnum.LG);
if (lgWidth) { if (lgWidth) {
@ -30,8 +33,13 @@
}); });
const { prefixCls } = toRefs(props); const { prefixCls } = toRefs(props);
// Inject variables into the global
createAppProviderContext({ prefixCls, isMobile }); createAppProviderContext({ prefixCls, isMobile });
/**
* Used to maintain the state before the window changes
*/
function handleRestoreState() { function handleRestoreState() {
if (unref(isMobile)) { if (unref(isMobile)) {
if (!unref(isSetState)) { if (!unref(isSetState)) {

View File

@ -4,11 +4,11 @@
import { SearchOutlined } from '@ant-design/icons-vue'; import { SearchOutlined } from '@ant-design/icons-vue';
import AppSearchModal from './AppSearchModal.vue'; import AppSearchModal from './AppSearchModal.vue';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({ export default defineComponent({
name: 'AppSearch', name: 'AppSearch',
setup() { setup() {
const showModal = ref(false); const showModal = ref(false);
const { t } = useI18n(); const { t } = useI18n();
function changeModal(show: boolean) { function changeModal(show: boolean) {

View File

@ -1,11 +1,11 @@
<template> <template>
<div :class="`${prefixCls}`"> <div :class="`${prefixCls}`">
<AppSearchKeyItem :class="`${prefixCls}__item`" icon="ant-design:enter-outlined" /> <AppSearchKeyItem :class="`${prefixCls}-item`" icon="ant-design:enter-outlined" />
<span>{{ t('component.app.toSearch') }}</span> <span>{{ t('component.app.toSearch') }}</span>
<AppSearchKeyItem :class="`${prefixCls}__item`" icon="ion:arrow-up-outline" /> <AppSearchKeyItem :class="`${prefixCls}-item`" icon="ion:arrow-up-outline" />
<AppSearchKeyItem :class="`${prefixCls}__item`" icon="ion:arrow-down-outline" /> <AppSearchKeyItem :class="`${prefixCls}-item`" icon="ion:arrow-down-outline" />
<span>{{ t('component.app.toNavigate') }}</span> <span>{{ t('component.app.toNavigate') }}</span>
<AppSearchKeyItem :class="`${prefixCls}__item`" icon="mdi:keyboard-esc" /> <AppSearchKeyItem :class="`${prefixCls}-item`" icon="mdi:keyboard-esc" />
<span>{{ t('common.closeText') }}</span> <span>{{ t('common.closeText') }}</span>
</div> </div>
</template> </template>
@ -21,10 +21,7 @@
setup() { setup() {
const { prefixCls } = useDesign('app-search-footer'); const { prefixCls } = useDesign('app-search-footer');
const { t } = useI18n(); const { t } = useI18n();
return { return { prefixCls, t };
prefixCls,
t,
};
}, },
}); });
</script> </script>
@ -44,7 +41,7 @@
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
&__item { &-item {
display: flex; display: flex;
width: 20px; width: 20px;
height: 18px; height: 18px;

View File

@ -8,8 +8,6 @@
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon';
export default defineComponent({ export default defineComponent({
components: { Icon }, components: { Icon },
props: { props: { icon: String },
icon: String,
},
}); });
</script> </script>

View File

@ -4,7 +4,7 @@
<div :class="getClass" @click.stop v-if="visible"> <div :class="getClass" @click.stop v-if="visible">
<div :class="`${prefixCls}-content`" v-click-outside="handleClose"> <div :class="`${prefixCls}-content`" v-click-outside="handleClose">
<div :class="`${prefixCls}-input__wrapper`"> <div :class="`${prefixCls}-input__wrapper`">
<Input <a-input
:class="`${prefixCls}-input`" :class="`${prefixCls}-input`"
:placeholder="t('common.searchText')" :placeholder="t('common.searchText')"
ref="inputRef" ref="inputRef"
@ -12,10 +12,9 @@
@change="handleSearch" @change="handleSearch"
> >
<template #prefix> <template #prefix>
<!-- <Icon icon="ion:search"/> -->
<SearchOutlined /> <SearchOutlined />
</template> </template>
</Input> </a-input>
<span :class="`${prefixCls}-cancel`" @click="handleClose"> <span :class="`${prefixCls}-cancel`" @click="handleClose">
{{ t('common.cancelText') }} {{ t('common.cancelText') }}
</span> </span>
@ -59,39 +58,36 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed, unref, ref, watch, nextTick } from 'vue'; import { defineComponent, computed, unref, ref, watch, nextTick } from 'vue';
import { SearchOutlined } from '@ant-design/icons-vue'; import { SearchOutlined } from '@ant-design/icons-vue';
import { Input } from 'ant-design-vue';
import AppSearchFooter from './AppSearchFooter.vue'; import AppSearchFooter from './AppSearchFooter.vue';
import Icon from '/@/components/Icon'; import Icon from '/@/components/Icon';
import clickOutside from '/@/directives/clickOutside'; import clickOutside from '/@/directives/clickOutside';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { useRefs } from '/@/hooks/core/useRefs'; import { useRefs } from '/@/hooks/core/useRefs';
import { useMenuSearch } from './useMenuSearch'; import { useMenuSearch } from './useMenuSearch';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { useAppInject } from '/@/hooks/web/useAppInject'; import { useAppInject } from '/@/hooks/web/useAppInject';
import { propTypes } from '/@/utils/propTypes'; const props = {
visible: { type: Boolean },
};
export default defineComponent({ export default defineComponent({
name: 'AppSearchModal', name: 'AppSearchModal',
components: { Icon, SearchOutlined, AppSearchFooter, Input }, components: { Icon, SearchOutlined, AppSearchFooter },
directives: { directives: {
clickOutside, clickOutside,
}, },
props: { props,
visible: propTypes.bool,
},
emits: ['close'], emits: ['close'],
setup(props, { emit }) { setup(props, { emit }) {
const scrollWrap = ref<ElRef>(null); const scrollWrap = ref<ElRef>(null);
const { prefixCls } = useDesign('app-search-modal'); const inputRef = ref<Nullable<HTMLElement>>(null);
const { t } = useI18n(); const { t } = useI18n();
const { prefixCls } = useDesign('app-search-modal');
const [refs, setRefs] = useRefs(); const [refs, setRefs] = useRefs();
const { getIsMobile } = useAppInject(); const { getIsMobile } = useAppInject();
const inputRef = ref<Nullable<HTMLElement>>(null);
const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } = const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } =
useMenuSearch(refs, scrollWrap, emit); useMenuSearch(refs, scrollWrap, emit);
@ -109,8 +105,8 @@
watch( watch(
() => props.visible, () => props.visible,
(v: boolean) => { (visible: boolean) => {
v && visible &&
nextTick(() => { nextTick(() => {
unref(inputRef)?.focus(); unref(inputRef)?.focus();
}); });

View File

@ -1,12 +1,8 @@
import type { Menu } from '/@/router/types'; import type { Menu } from '/@/router/types';
import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue'; import { ref, onBeforeMount, unref, Ref, nextTick } from 'vue';
import { getMenus } from '/@/router/menus'; import { getMenus } from '/@/router/menus';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { filter, forEach } from '/@/utils/helper/treeHelper'; import { filter, forEach } from '/@/utils/helper/treeHelper';
import { useGo } from '/@/hooks/web/usePage'; import { useGo } from '/@/hooks/web/usePage';
import { useScrollTo } from '/@/hooks/event/useScrollTo'; import { useScrollTo } from '/@/hooks/event/useScrollTo';
import { onKeyStroke, useDebounceFn } from '@vueuse/core'; import { onKeyStroke, useDebounceFn } from '@vueuse/core';
@ -67,7 +63,6 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) { function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) {
const ret: SearchResult[] = []; const ret: SearchResult[] = [];
filterMenu.forEach((item) => { filterMenu.forEach((item) => {
const { name, path, icon, children } = item; const { name, path, icon, children } = item;
if (reg.test(name) && !children?.length) { if (reg.test(name) && !children?.length) {
@ -84,11 +79,13 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
return ret; return ret;
} }
// Activate when the mouse moves to a certain line
function handleMouseenter(e: any) { function handleMouseenter(e: any) {
const index = e.target.dataset.index; const index = e.target.dataset.index;
activeIndex.value = Number(index); activeIndex.value = Number(index);
} }
// Arrow key up
function handleUp() { function handleUp() {
if (!searchResult.value.length) return; if (!searchResult.value.length) return;
activeIndex.value--; activeIndex.value--;
@ -98,6 +95,7 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
handleScroll(); handleScroll();
} }
// Arrow key down
function handleDown() { function handleDown() {
if (!searchResult.value.length) return; if (!searchResult.value.length) return;
activeIndex.value++; activeIndex.value++;
@ -107,15 +105,23 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
handleScroll(); handleScroll();
} }
// When the keyboard up and down keys move to an invisible place
// the scroll bar needs to scroll automatically
function handleScroll() { function handleScroll() {
const refList = unref(refs); const refList = unref(refs);
if (!refList || !Array.isArray(refList) || refList.length === 0 || !unref(scrollWrap)) return; if (!refList || !Array.isArray(refList) || refList.length === 0 || !unref(scrollWrap)) {
return;
}
const index = unref(activeIndex); const index = unref(activeIndex);
const currentRef = refList[index]; const currentRef = refList[index];
if (!currentRef) return; if (!currentRef) {
return;
}
const wrapEl = unref(scrollWrap); const wrapEl = unref(scrollWrap);
if (!wrapEl) return; if (!wrapEl) {
return;
}
const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight; const scrollHeight = currentRef.offsetTop + currentRef.offsetHeight;
const wrapHeight = wrapEl.offsetHeight; const wrapHeight = wrapEl.offsetHeight;
const { start } = useScrollTo({ const { start } = useScrollTo({
@ -126,8 +132,11 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
start(); start();
} }
// enter keyboard event
async function handleEnter() { async function handleEnter() {
if (!searchResult.value.length) return; if (!searchResult.value.length) {
return;
}
const result = unref(searchResult); const result = unref(searchResult);
const index = unref(activeIndex); const index = unref(activeIndex);
if (result.length === 0 || index < 0) { if (result.length === 0 || index < 0) {
@ -139,14 +148,18 @@ export function useMenuSearch(refs: Ref<HTMLElement[]>, scrollWrap: Ref<ElRef>,
go(to.path); go(to.path);
} }
// close search modal
function handleClose() { function handleClose() {
searchResult.value = []; searchResult.value = [];
emit('close'); emit('close');
} }
// enter search
onKeyStroke('Enter', handleEnter); onKeyStroke('Enter', handleEnter);
// Monitor keyboard arrow keys
onKeyStroke('ArrowUp', handleUp); onKeyStroke('ArrowUp', handleUp);
onKeyStroke('ArrowDown', handleDown); onKeyStroke('ArrowDown', handleDown);
// esc close
onKeyStroke('Escape', handleClose); onKeyStroke('Escape', handleClose);
return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter }; return { handleSearch, searchResult, keyword, activeIndex, handleMouseenter, handleEnter };

View File

@ -3,7 +3,6 @@ import { createContext, useContext } from '/@/hooks/core/useContext';
export interface AppProviderContextProps { export interface AppProviderContextProps {
prefixCls: Ref<string>; prefixCls: Ref<string>;
isMobile: Ref<boolean>; isMobile: Ref<boolean>;
} }

View File

@ -1,3 +1,4 @@
import Authority from './src/Authority.vue'; import { withInstall } from '/@/utils';
import authority from './src/Authority.vue';
export { Authority }; export const Authority = withInstall(authority);

View File

@ -4,11 +4,8 @@
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { RoleEnum } from '/@/enums/roleEnum'; import { RoleEnum } from '/@/enums/roleEnum';
import { usePermission } from '/@/hooks/web/usePermission'; import { usePermission } from '/@/hooks/web/usePermission';
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper';
export default defineComponent({ export default defineComponent({

View File

@ -1,5 +1,8 @@
import BasicArrow from './src/BasicArrow.vue'; import { withInstall } from '/@/utils';
import BasicTitle from './src/BasicTitle.vue'; import basicArrow from './src/BasicArrow.vue';
import BasicHelp from './src/BasicHelp.vue'; import basicTitle from './src/BasicTitle.vue';
import basicHelp from './src/BasicHelp.vue';
export { BasicArrow, BasicTitle, BasicHelp }; export const BasicArrow = withInstall(basicArrow);
export const BasicTitle = withInstall(basicTitle);
export const BasicHelp = withInstall(basicHelp);

View File

@ -9,41 +9,50 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed } from 'vue'; import { defineComponent, computed } from 'vue';
import { Icon } from '/@/components/Icon';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes'; const props = {
/**
import { Icon } from '/@/components/Icon'; * Arrow expand state
*/
expand: { type: Boolean },
/**
* Arrow up by default
*/
up: { type: Boolean },
/**
* Arrow down by default
*/
down: { type: Boolean },
/**
* Cancel padding/margin for inline
*/
inset: { type: Boolean },
};
export default defineComponent({ export default defineComponent({
name: 'BasicArrow', name: 'BasicArrow',
components: { Icon }, components: { Icon },
props: { props,
expand: propTypes.bool,
top: propTypes.bool,
bottom: propTypes.bool,
inset: propTypes.bool,
},
setup(props) { setup(props) {
const { prefixCls } = useDesign('basic-arrow'); const { prefixCls } = useDesign('basic-arrow');
// get component class
const getClass = computed(() => { const getClass = computed(() => {
const { expand, top, bottom, inset } = props; const { expand, up, down, inset } = props;
return [ return [
prefixCls, prefixCls,
{ {
[`${prefixCls}--active`]: expand, [`${prefixCls}--active`]: expand,
top, up,
inset, inset,
bottom, down,
}, },
]; ];
}); });
return { return { getClass };
getClass,
};
}, },
}); });
</script> </script>
@ -65,19 +74,19 @@
line-height: 0px; line-height: 0px;
} }
&.top { &.up {
transform: rotate(-90deg); transform: rotate(-90deg);
} }
&.bottom { &.down {
transform: rotate(90deg); transform: rotate(90deg);
} }
&.top.@{prefix-cls}--active { &.up.@{prefix-cls}--active {
transform: rotate(90deg); transform: rotate(90deg);
} }
&.bottom.@{prefix-cls}--active { &.down.@{prefix-cls}--active {
transform: rotate(-90deg); transform: rotate(-90deg);
} }
} }

View File

@ -1,104 +1,90 @@
<script lang="tsx"> <script lang="tsx">
import type { CSSProperties, PropType } from 'vue'; import type { CSSProperties, PropType } from 'vue';
import { defineComponent, computed, unref } from 'vue'; import { defineComponent, computed, unref } from 'vue';
import { Tooltip } from 'ant-design-vue'; import { Tooltip } from 'ant-design-vue';
import { InfoCircleOutlined } from '@ant-design/icons-vue'; import { InfoCircleOutlined } from '@ant-design/icons-vue';
import { getPopupContainer } from '/@/utils'; import { getPopupContainer } from '/@/utils';
import { isString, isArray } from '/@/utils/is'; import { isString, isArray } from '/@/utils/is';
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper';
import { propTypes } from '/@/utils/propTypes';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
const props = {
/**
* Help text max-width
* @default: 600px
*/
maxWidth: { type: String, default: '600px' },
/**
* Whether to display the serial number
* @default: false
*/
showIndex: { type: Boolean },
/**
* Help text font color
* @default: #ffffff
*/
color: { type: String, default: '#ffffff' },
/**
* Help text font size
* @default: 14px
*/
fontSize: { type: String, default: '14px' },
/**
* Help text list
*/
placement: { type: String, default: 'right' },
/**
* Help text list
*/
text: { type: [Array, String] as PropType<string[] | string> },
};
export default defineComponent({ export default defineComponent({
name: 'BasicHelp', name: 'BasicHelp',
components: { Tooltip }, components: { Tooltip },
props: { props,
// max-width
maxWidth: propTypes.string.def('600px'),
// Whether to display the serial number
showIndex: propTypes.bool,
// color
color: propTypes.string.def('#ffffff'),
fontSize: propTypes.string.def('14px'),
placement: propTypes.string.def('right'),
absolute: propTypes.bool,
// Text list
text: {
type: [Array, String] as PropType<string[] | string>,
},
//
position: {
type: [Object] as PropType<any>,
default: () => ({
position: 'absolute',
left: 0,
bottom: 0,
}),
},
},
setup(props, { slots }) { setup(props, { slots }) {
const { prefixCls } = useDesign('basic-help'); const { prefixCls } = useDesign('basic-help');
const getOverlayStyle = computed( const getTooltipStyle = computed(
(): CSSProperties => { (): CSSProperties => ({ color: props.color, fontSize: props.fontSize })
return {
maxWidth: props.maxWidth,
};
}
); );
const getWrapStyle = computed( const getOverlayStyle = computed((): CSSProperties => ({ maxWidth: props.maxWidth }));
(): CSSProperties => {
return {
color: props.color,
fontSize: props.fontSize,
};
}
);
const getMainStyleRef = computed(() => { function renderTitle() {
return props.absolute ? props.position : {}; const textList = props.text;
});
const renderTitle = () => { if (isString(textList)) {
const list = props.text; return <p>{textList}</p>;
if (isString(list)) {
return <p>{list}</p>;
} }
if (isArray(list)) { if (isArray(textList)) {
return list.map((item, index) => { return textList.map((text, index) => {
return ( return (
<p key={item}> <p key={text}>
<> <>
{props.showIndex ? `${index + 1}. ` : ''} {props.showIndex ? `${index + 1}. ` : ''}
{item} {text}
</> </>
</p> </p>
); );
}); });
} }
return null; return null;
}; }
return () => { return () => {
return ( return (
<Tooltip <Tooltip
title={<div style={unref(getWrapStyle)}>{renderTitle()}</div>}
overlayClassName={`${prefixCls}__wrap`} overlayClassName={`${prefixCls}__wrap`}
title={<div style={unref(getTooltipStyle)}>{renderTitle()}</div>}
autoAdjustOverflow={true} autoAdjustOverflow={true}
overlayStyle={unref(getOverlayStyle)} overlayStyle={unref(getOverlayStyle)}
placement={props.placement as 'left'} placement={props.placement as 'right'}
getPopupContainer={() => getPopupContainer()} getPopupContainer={() => getPopupContainer()}
> >
<span class={prefixCls} style={unref(getMainStyleRef)}> <span class={prefixCls}>{getSlot(slots) || <InfoCircleOutlined />}</span>
{getSlot(slots) || <InfoCircleOutlined />}
</span>
</Tooltip> </Tooltip>
); );
}; };

View File

@ -1,30 +1,40 @@
<template> <template>
<span :class="getClass"> <span :class="getClass">
<slot></slot> <slot></slot>
<BasicHelp :class="`${prefixCls}__help`" v-if="helpMessage" :text="helpMessage" /> <BasicHelp :class="`${prefixCls}-help`" v-if="helpMessage" :text="helpMessage" />
</span> </span>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { defineComponent, computed } from 'vue'; import { defineComponent, computed } from 'vue';
import BasicHelp from './BasicHelp.vue'; import BasicHelp from './BasicHelp.vue';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes'; const props = {
/**
export default defineComponent({ * Help text list or string
name: 'BasicTitle', * @default: ''
components: { BasicHelp }, */
props: {
helpMessage: { helpMessage: {
type: [String, Array] as PropType<string | string[]>, type: [String, Array] as PropType<string | string[]>,
default: '', default: '',
}, },
span: propTypes.bool, /**
normal: propTypes.bool.def(false), * Whether the color block on the left side of the title
}, * @default: false
*/
span: { type: Boolean },
/**
* Whether to default the text, that is, not bold
* @default: false
*/
normal: { type: Boolean },
};
export default defineComponent({
name: 'BasicTitle',
components: { BasicHelp },
props,
setup(props, { slots }) { setup(props, { slots }) {
const { prefixCls } = useDesign('basic-title'); const { prefixCls } = useDesign('basic-title');
@ -33,6 +43,7 @@
{ [`${prefixCls}-show-span`]: props.span && slots.default }, { [`${prefixCls}-show-span`]: props.span && slots.default },
{ [`${prefixCls}-normal`]: props.normal }, { [`${prefixCls}-normal`]: props.normal },
]); ]);
return { prefixCls, getClass }; return { prefixCls, getClass };
}, },
}); });
@ -67,7 +78,7 @@
content: ''; content: '';
} }
&__help { &-help {
margin-left: 10px; margin-left: 10px;
} }
} }

View File

@ -1,4 +1,6 @@
import Button from './src/BasicButton.vue'; import { withInstall } from '/@/utils';
import PopConfirmButton from './src/PopConfirmButton.vue'; import button from './src/BasicButton.vue';
import popConfirmButton from './src/PopConfirmButton.vue';
export { Button, PopConfirmButton }; export const Button = withInstall(button);
export const PopConfirmButton = withInstall(popConfirmButton);

View File

@ -13,11 +13,21 @@
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon';
const props = { const props = {
color: { type: String, validate: (v) => ['error', 'warning', 'success', ''].includes(v) }, color: { type: String, validator: (v) => ['error', 'warning', 'success', ''].includes(v) },
loading: { type: Boolean }, loading: { type: Boolean },
disabled: { type: Boolean }, disabled: { type: Boolean },
/**
* Text before icon.
*/
preIcon: { type: String }, preIcon: { type: String },
/**
* Text after icon.
*/
postIcon: { type: String }, postIcon: { type: String },
/**
* preIcon and postIcon icon size.
* @default: 14
*/
iconSize: { type: Number, default: 14 }, iconSize: { type: Number, default: 14 },
onClick: { type: Function as PropType<(...args) => any>, default: null }, onClick: { type: Function as PropType<(...args) => any>, default: null },
}; };

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, h, unref, computed } from 'vue'; import { defineComponent, h, unref, computed } from 'vue';
import { Popconfirm } from 'ant-design-vue';
import BasicButton from './BasicButton.vue'; import BasicButton from './BasicButton.vue';
import { Popconfirm } from 'ant-design-vue';
import { extendSlots } from '/@/utils/helper/tsxHelper'; import { extendSlots } from '/@/utils/helper/tsxHelper';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs';

View File

@ -1,3 +1,4 @@
import ClickOutSide from './src/ClickOutSide.vue'; import { withInstall } from '/@/utils';
import clickOutSide from './src/ClickOutSide.vue';
export { ClickOutSide }; export const ClickOutSide = withInstall(clickOutSide);

View File

@ -1,15 +1,6 @@
import type { App } from 'vue'; import { withInstall } from '/@/utils';
import codeEditor from './src/CodeEditor.vue'; import codeEditor from './src/CodeEditor.vue';
import jsonPreview from './src/json-preview/JsonPreview.vue'; import jsonPreview from './src/json-preview/JsonPreview.vue';
export const CodeEditor = Object.assign(codeEditor, { export const CodeEditor = withInstall(codeEditor);
install(app: App) { export const JsonPreview = withInstall(jsonPreview);
app.component(codeEditor.name, codeEditor);
},
});
export const JsonPreview = Object.assign(jsonPreview, {
install(app: App) {
app.component(jsonPreview.name, jsonPreview);
},
});

View File

@ -1,6 +1,11 @@
<template> <template>
<div class="h-full"> <div class="h-full">
<CodeMirrorEditor :value="getValue" @change="handleValueChange" :mode="mode" :readonly="readonly" /> <CodeMirrorEditor
:value="getValue"
@change="handleValueChange"
:mode="mode"
:readonly="readonly"
/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -13,43 +18,34 @@
html: 'htmlmixed', html: 'htmlmixed',
js: 'javascript', js: 'javascript',
}; };
const props = {
value: { type: [Object, String] as PropType<Record<string, any> | string> },
mode: { type: String, default: MODE.JSON },
readonly: { type: Boolean },
};
export default defineComponent({ export default defineComponent({
name: 'CodeEditor', name: 'CodeEditor',
components: { CodeMirrorEditor }, components: { CodeMirrorEditor },
props: { props,
value: {
type: [Object, String],
},
mode: {
type: String,
default: MODE.JSON,
},
readonly: {
type: Boolean,
default: false,
},
},
emits: ['change'], emits: ['change'],
setup(props, { emit }) { setup(props, { emit }) {
const getValue = computed(() => { const getValue = computed(() => {
const { value, mode } = props; const { value, mode } = props;
if (mode !== MODE.JSON) {
if (mode === MODE.JSON) { return value as string;
}
return isString(value) return isString(value)
? JSON.stringify(JSON.parse(value), null, 2) ? JSON.stringify(JSON.parse(value), null, 2)
: JSON.stringify(value, null, 2); : JSON.stringify(value, null, 2);
}
return value;
}); });
function handleValueChange(v) { function handleValueChange(v) {
emit('change', v); emit('change', v);
} }
return { return { handleValueChange, getValue };
handleValueChange,
getValue,
};
}, },
}); });
</script> </script>

View File

@ -15,31 +15,25 @@
} from 'vue'; } from 'vue';
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';
import { useAppStore } from '/@/store/modules/app'; import { useAppStore } from '/@/store/modules/app';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import CodeMirror from 'codemirror'; import CodeMirror from 'codemirror';
// css
import './codemirror.css'; import './codemirror.css';
import 'codemirror/theme/idea.css'; import 'codemirror/theme/idea.css';
import 'codemirror/theme/material-palenight.css'; import 'codemirror/theme/material-palenight.css';
// modes // modes
import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/css/css'; import 'codemirror/mode/css/css';
import 'codemirror/mode/htmlmixed/htmlmixed'; import 'codemirror/mode/htmlmixed/htmlmixed';
const props = {
mode: { type: String, default: 'application/json' },
value: { type: String, default: '' },
readonly: { type: Boolean, default: false },
};
export default defineComponent({ export default defineComponent({
props: { props,
mode: {
type: String,
default: 'application/json',
},
value: {
type: String,
default: '',
},
readonly: {
type: Boolean,
default: false,
},
},
emits: ['change'], emits: ['change'],
setup(props, { emit }) { setup(props, { emit }) {
const el = ref(); const el = ref();
@ -50,11 +44,11 @@
watch( watch(
() => props.value, () => props.value,
async (v) => { async (value) => {
await nextTick(); await nextTick();
const oldValue = editor?.getValue(); const oldValue = editor?.getValue();
if (v !== oldValue) { if (value !== oldValue) {
editor?.setValue(v ? v : ''); editor?.setValue(value ? value : '');
} }
}, },
{ flush: 'post' } { flush: 'post' }
@ -113,13 +107,13 @@
onMounted(async () => { onMounted(async () => {
await nextTick(); await nextTick();
init(); init();
window.addEventListener('resize', debounceRefresh); useWindowSizeFn(debounceRefresh);
}); });
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('resize', debounceRefresh);
editor = null; editor = null;
}); });
return { el }; return { el };
}, },
}); });

View File

@ -12,20 +12,21 @@
--qualifier: #ff6032; --qualifier: #ff6032;
--important: var(--string); --important: var(--string);
position: relative;
height: auto; height: auto;
height: 100%; height: 100%;
overflow: hidden;
font-family: var(--font-code); font-family: var(--font-code);
background: white;
direction: ltr; direction: ltr;
} }
/* PADDING */ /* PADDING */
.CodeMirror-lines { .CodeMirror-lines {
min-height: 1px; /* prevents collapsing before first draw */
padding: 4px 0; /* Vertical padding around content */ padding: 4px 0; /* Vertical padding around content */
} cursor: text;
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
} }
.CodeMirror-scrollbar-filler, .CodeMirror-scrollbar-filler,
@ -36,6 +37,11 @@
/* GUTTER */ /* GUTTER */
.CodeMirror-gutters { .CodeMirror-gutters {
position: absolute;
top: 0;
left: 0;
z-index: 3;
min-height: 100%;
white-space: nowrap; white-space: nowrap;
background-color: transparent; background-color: transparent;
border-right: 1px solid #ddd; border-right: 1px solid #ddd;
@ -96,7 +102,9 @@
/* CURSOR */ /* CURSOR */
.CodeMirror-cursor { .CodeMirror-cursor {
position: absolute;
width: 0; width: 0;
pointer-events: none;
border-right: none; border-right: none;
border-left: 1px solid black; border-left: 1px solid black;
} }
@ -132,37 +140,19 @@
animation: blink 1.06s steps(1) infinite; animation: blink 1.06s steps(1) infinite;
} }
@-moz-keyframes blink { @-moz-keyframes blink {
0% {
}
50% { 50% {
background-color: transparent; background-color: transparent;
} }
100% {
}
} }
@-webkit-keyframes blink { @-webkit-keyframes blink {
0% {
}
50% { 50% {
background-color: transparent; background-color: transparent;
} }
100% {
}
} }
@keyframes blink { @keyframes blink {
0% {
}
50% { 50% {
background-color: transparent; background-color: transparent;
} }
100% {
}
} }
.cm-tab { .cm-tab {
@ -316,12 +306,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
/* The rest of this file contains styles related to the mechanics of /* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */ the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll { .CodeMirror-scroll {
position: relative; position: relative;
height: 100%; height: 100%;
@ -378,14 +362,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
left: 0; left: 0;
} }
.CodeMirror-gutters {
position: absolute;
top: 0;
left: 0;
z-index: 3;
min-height: 100%;
}
.CodeMirror-gutter { .CodeMirror-gutter {
display: inline-block; display: inline-block;
height: 100%; height: 100%;
@ -422,14 +398,10 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
background-color: transparent; background-color: transparent;
} }
.CodeMirror-lines {
min-height: 1px; /* prevents collapsing before first draw */
cursor: text;
}
.CodeMirror pre { .CodeMirror pre {
position: relative; position: relative;
z-index: 2; z-index: 2;
padding: 0 4px; /* Horizontal padding of content */
margin: 0; margin: 0;
overflow: visible; overflow: visible;
font-family: inherit; font-family: inherit;
@ -497,11 +469,6 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {
visibility: hidden; visibility: hidden;
} }
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { .CodeMirror-measure pre {
position: static; position: static;
} }

View File

@ -8,11 +8,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'JsonPreview', name: 'JsonPreview',
components: { components: { VueJsonPretty },
VueJsonPretty, props: { data: Object },
},
props: {
data: Object,
},
}); });
</script> </script>

View File

@ -1,6 +1,10 @@
import CollapseContainer from './src/collapse/CollapseContainer.vue'; import { withInstall } from '/@/utils';
import ScrollContainer from './src/ScrollContainer.vue'; import collapseContainer from './src/collapse/CollapseContainer.vue';
import LazyContainer from './src/LazyContainer.vue'; import scrollContainer from './src/ScrollContainer.vue';
import lazyContainer from './src/LazyContainer.vue';
export { CollapseContainer, ScrollContainer, LazyContainer }; export const CollapseContainer = withInstall(collapseContainer);
export * from './src/types'; export const ScrollContainer = withInstall(scrollContainer);
export const LazyContainer = withInstall(lazyContainer);
export * from './src/typing';

View File

@ -18,13 +18,10 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue'; import { defineComponent, reactive, onMounted, ref, toRef, toRefs } from 'vue';
import { Skeleton } from 'ant-design-vue'; import { Skeleton } from 'ant-design-vue';
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver'; import { useIntersectionObserver } from '/@/hooks/event/useIntersectionObserver';
import { propTypes } from '/@/utils/propTypes';
interface State { interface State {
isInit: boolean; isInit: boolean;
@ -32,36 +29,47 @@
intersectionObserverInstance: IntersectionObserver | null; intersectionObserverInstance: IntersectionObserver | null;
} }
const props = {
/**
* Waiting time, if the time is specified, whether visible or not, it will be automatically loaded after the specified time
*/
timeout: { type: Number },
/**
* The viewport where the component is located.
* If the component is scrolling in the page container, the viewport is the container
*/
viewport: {
type: (typeof window !== 'undefined' ? window.HTMLElement : Object) as PropType<HTMLElement>,
default: () => null,
},
/**
* Preload threshold, css unit
*/
threshold: { type: String, default: '0px' },
/**
* The scroll direction of the viewport, vertical represents the vertical direction, horizontal represents the horizontal direction
*/
direction: {
type: String,
default: 'vertical',
validator: (v) => ['vertical', 'horizontal'].includes(v),
},
/**
* The label name of the outer container that wraps the component
*/
tag: { type: String, default: 'div' },
maxWaitingTime: { type: Number, default: 80 },
/**
* transition name
*/
transitionName: { type: String, default: 'lazy-container' },
};
export default defineComponent({ export default defineComponent({
name: 'LazyContainer', name: 'LazyContainer',
components: { Skeleton }, components: { Skeleton },
inheritAttrs: false, inheritAttrs: false,
props: { props,
// Waiting time, if the time is specified, whether visible or not, it will be automatically loaded after the specified time
timeout: propTypes.number,
// The viewport where the component is located. If the component is scrolling in the page container, the viewport is the container
viewport: {
type: (typeof window !== 'undefined'
? window.HTMLElement
: Object) as PropType<HTMLElement>,
default: () => null,
},
// Preload threshold, css unit
threshold: propTypes.string.def('0px'),
// The scroll direction of the viewport, vertical represents the vertical direction, horizontal represents the horizontal direction
direction: propTypes.oneOf(['vertical', 'horizontal']).def('vertical'),
// The label name of the outer container that wraps the component
tag: propTypes.string.def('div'),
maxWaitingTime: propTypes.number.def(80),
// transition name
transitionName: propTypes.string.def('lazy-container'),
},
emits: ['init'], emits: ['init'],
setup(props, { emit }) { setup(props, { emit }) {
const elRef = ref(); const elRef = ref();

View File

@ -7,7 +7,6 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, unref, nextTick } from 'vue'; import { defineComponent, ref, unref, nextTick } from 'vue';
import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar'; import { Scrollbar, ScrollbarType } from '/@/components/Scrollbar';
import { useScrollTo } from '/@/hooks/event/useScrollTo'; import { useScrollTo } from '/@/hooks/event/useScrollTo';
export default defineComponent({ export default defineComponent({
@ -16,12 +15,14 @@
setup() { setup() {
const scrollbarRef = ref<Nullable<ScrollbarType>>(null); const scrollbarRef = ref<Nullable<ScrollbarType>>(null);
/**
* Scroll to the specified position
*/
function scrollTo(to: number, duration = 500) { function scrollTo(to: number, duration = 500) {
const scrollbar = unref(scrollbarRef); const scrollbar = unref(scrollbarRef);
if (!scrollbar) { if (!scrollbar) {
return; return;
} }
nextTick(() => { nextTick(() => {
const wrap = unref(scrollbar.wrap); const wrap = unref(scrollbar.wrap);
if (!wrap) { if (!wrap) {
@ -44,12 +45,14 @@
return scrollbar.wrap; return scrollbar.wrap;
} }
/**
* Scroll to the bottom
*/
function scrollBottom() { function scrollBottom() {
const scrollbar = unref(scrollbarRef); const scrollbar = unref(scrollbarRef);
if (!scrollbar) { if (!scrollbar) {
return; return;
} }
nextTick(() => { nextTick(() => {
const wrap = unref(scrollbar.wrap); const wrap = unref(scrollbar.wrap);
if (!wrap) { if (!wrap) {

View File

@ -1,11 +1,6 @@
<template> <template>
<div :class="prefixCls"> <div :class="prefixCls">
<CollapseHeader <CollapseHeader v-bind="$props" :prefixCls="prefixCls" :show="show" @expand="handleExpand">
v-bind="getBindValues"
:prefixCls="prefixCls"
:show="show"
@expand="handleExpand"
>
<template #title> <template #title>
<slot name="title"></slot> <slot name="title"></slot>
</template> </template>
@ -16,13 +11,12 @@
<div class="p-2"> <div class="p-2">
<CollapseTransition :enable="canExpan"> <CollapseTransition :enable="canExpan">
<Skeleton v-if="loading" :active="active" /> <Skeleton v-if="loading" :active="loading" />
<div :class="`${prefixCls}__body`" v-else v-show="show"> <div :class="`${prefixCls}__body`" v-else v-show="show">
<slot></slot> <slot></slot>
</div> </div>
</CollapseTransition> </CollapseTransition>
</div> </div>
<div :class="`${prefixCls}__footer`" v-if="$slots.footer"> <div :class="`${prefixCls}__footer`" v-if="$slots.footer">
<slot name="footer"></slot> <slot name="footer"></slot>
</div> </div>
@ -30,20 +24,41 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { defineComponent, ref } from 'vue';
import { defineComponent, ref, computed } from 'vue';
// component // component
import { Skeleton } from 'ant-design-vue'; import { Skeleton } from 'ant-design-vue';
import { CollapseTransition } from '/@/components/Transition'; import { CollapseTransition } from '/@/components/Transition';
import CollapseHeader from './CollapseHeader.vue'; import CollapseHeader from './CollapseHeader.vue';
import { triggerWindowResize } from '/@/utils/event'; import { triggerWindowResize } from '/@/utils/event';
// hook // hook
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import { propTypes } from '/@/utils/propTypes';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
const props = {
title: { type: String, default: '' },
loading: { type: Boolean },
/**
* Can it be expanded
*/
canExpan: { type: Boolean, default: true },
/**
* Warm reminder on the right side of the title
*/
helpMessage: {
type: [Array, String] as PropType<string[] | string>,
default: '',
},
/**
* Whether to trigger window.resize when expanding and contracting,
* Can adapt to tables and forms, when the form shrinks, the form triggers resize to adapt to the height
*/
triggerWindowResize: { type: Boolean },
/**
* Delayed loading time
*/
lazyTime: { type: Number, default: 0 },
};
export default defineComponent({ export default defineComponent({
name: 'CollapseContainer', name: 'CollapseContainer',
components: { components: {
@ -51,23 +66,7 @@
CollapseHeader, CollapseHeader,
CollapseTransition, CollapseTransition,
}, },
props: { props,
title: propTypes.string.def(''),
// Can it be expanded
canExpan: propTypes.bool.def(true),
// Warm reminder on the right side of the title
helpMessage: {
type: [Array, String] as PropType<string[] | string>,
default: '',
},
// Whether to trigger window.resize when expanding and contracting,
// Can adapt to tables and forms, when the form shrinks, the form triggers resize to adapt to the height
triggerWindowResize: propTypes.bool,
loading: propTypes.bool.def(false),
active: propTypes.bool.def(true),
// Delayed loading time
lazyTime: propTypes.number.def(0),
},
setup(props) { setup(props) {
const show = ref(true); const show = ref(true);
@ -84,15 +83,10 @@
} }
} }
const getBindValues = computed((): any => {
return props;
});
return { return {
show, show,
handleExpand, handleExpand,
prefixCls, prefixCls,
getBindValues,
}; };
}, },
}); });

View File

@ -8,28 +8,31 @@
<slot name="title"></slot> <slot name="title"></slot>
</template> </template>
</BasicTitle> </BasicTitle>
<div :class="`${prefixCls}__action`"> <div :class="`${prefixCls}__action`">
<slot name="action"></slot> <slot name="action"></slot>
<BasicArrow v-if="canExpan" top :expand="show" @click="$emit('expand')" /> <BasicArrow v-if="canExpan" up :expand="show" @click="$emit('expand')" />
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { BasicArrow, BasicTitle } from '/@/components/Basic'; import { BasicArrow, BasicTitle } from '/@/components/Basic';
import { propTypes } from '/@/utils/propTypes';
const props = {
prefixCls: { type: String },
helpMessage: {
type: [Array, String] as PropType<string[] | string>,
default: '',
},
title: { type: String },
show: { type: Boolean },
canExpan: { type: Boolean },
};
export default defineComponent({ export default defineComponent({
components: { BasicArrow, BasicTitle }, components: { BasicArrow, BasicTitle },
inheritAttrs: false, inheritAttrs: false,
props: { props,
prefixCls: propTypes.string,
helpMessage: propTypes.string,
title: propTypes.string,
show: propTypes.bool,
canExpan: propTypes.bool,
},
emits: ['expand'], emits: ['expand'],
}); });
</script> </script>

View File

@ -1,3 +1,3 @@
export { createContextMenu, destroyContextMenu } from './src/createContextMenu'; export { createContextMenu, destroyContextMenu } from './src/createContextMenu';
export * from './src/types'; export * from './src/typing';

View File

@ -1,117 +0,0 @@
import './index.less';
import type { ContextMenuItem, ItemContentProps } from './types';
import type { FunctionalComponent, CSSProperties } from 'vue';
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue';
import Icon from '/@/components/Icon';
import { Menu, Divider } from 'ant-design-vue';
import { contextMenuProps } from './props';
const prefixCls = 'context-menu';
const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
const { item } = props;
return (
<span
style="display: inline-block; width: 100%; "
class="px-4"
onClick={props.handler.bind(null, item)}
>
{props.showIcon && item.icon && <Icon class="mr-2" icon={item.icon} />}
<span>{item.label}</span>
</span>
);
};
export default defineComponent({
name: 'ContextMenu',
props: contextMenuProps,
setup(props) {
const wrapRef = ref<ElRef>(null);
const showRef = ref(false);
const getStyle = computed((): CSSProperties => {
const { axis, items, styles, width } = props;
const { x, y } = axis || { x: 0, y: 0 };
const menuHeight = (items || []).length * 40;
const menuWidth = width;
const body = document.body;
const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;
return {
...styles,
width: `${width}px`,
left: `${left + 1}px`,
top: `${top + 1}px`,
};
});
onMounted(() => {
nextTick(() => (showRef.value = true));
});
onUnmounted(() => {
const el = unref(wrapRef);
el && document.body.removeChild(el);
});
function handleAction(item: ContextMenuItem, e: MouseEvent) {
const { handler, disabled } = item;
if (disabled) return;
showRef.value = false;
e?.stopPropagation();
e?.preventDefault();
handler?.();
}
function renderMenuItem(items: ContextMenuItem[]) {
return items.map((item) => {
const { disabled, label, children, divider = false } = item;
const DividerComp = divider ? <Divider key={`d-${label}`} /> : null;
if (!children || children.length === 0) {
return (
<>
<Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
<ItemContent showIcon={props.showIcon} item={item} handler={handleAction} />
</Menu.Item>
{DividerComp}
</>
);
}
if (!unref(showRef)) return null;
return (
<Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}>
{{
title: () => (
<ItemContent showIcon={props.showIcon} item={item} handler={handleAction} />
),
default: () => renderMenuItem(children),
}}
</Menu.SubMenu>
);
});
}
return () => {
const { items } = props;
if (!unref(showRef)) return null;
return (
<Menu
inlineIndent={12}
mode="vertical"
class={prefixCls}
ref={wrapRef}
style={unref(getStyle)}
>
{renderMenuItem(items)}
</Menu>
);
};
},
});

View File

@ -0,0 +1,207 @@
<script lang="tsx">
import type { ContextMenuItem, ItemContentProps, Axis } from './typing';
import type { FunctionalComponent, CSSProperties } from 'vue';
import { defineComponent, nextTick, onMounted, computed, ref, unref, onUnmounted } from 'vue';
import Icon from '/@/components/Icon';
import { Menu, Divider } from 'ant-design-vue';
const prefixCls = 'context-menu';
const props = {
width: { type: Number, default: 156 },
customEvent: { type: Object as PropType<Event>, default: null },
styles: { type: Object as PropType<CSSProperties> },
showIcon: { type: Boolean, default: true },
axis: {
// The position of the right mouse button click
type: Object as PropType<Axis>,
default() {
return { x: 0, y: 0 };
},
},
items: {
// The most important list, if not, will not be displayed
type: Array as PropType<ContextMenuItem[]>,
default() {
return [];
},
},
};
const ItemContent: FunctionalComponent<ItemContentProps> = (props) => {
const { item } = props;
return (
<span
style="display: inline-block; width: 100%; "
class="px-4"
onClick={props.handler.bind(null, item)}
>
{props.showIcon && item.icon && <Icon class="mr-2" icon={item.icon} />}
<span>{item.label}</span>
</span>
);
};
export default defineComponent({
name: 'ContextMenu',
props,
setup(props) {
const wrapRef = ref<ElRef>(null);
const showRef = ref(false);
const getStyle = computed((): CSSProperties => {
const { axis, items, styles, width } = props;
const { x, y } = axis || { x: 0, y: 0 };
const menuHeight = (items || []).length * 40;
const menuWidth = width;
const body = document.body;
const left = body.clientWidth < x + menuWidth ? x - menuWidth : x;
const top = body.clientHeight < y + menuHeight ? y - menuHeight : y;
return {
...styles,
width: `${width}px`,
left: `${left + 1}px`,
top: `${top + 1}px`,
};
});
onMounted(() => {
nextTick(() => (showRef.value = true));
});
onUnmounted(() => {
const el = unref(wrapRef);
el && document.body.removeChild(el);
});
function handleAction(item: ContextMenuItem, e: MouseEvent) {
const { handler, disabled } = item;
if (disabled) {
return;
}
showRef.value = false;
e?.stopPropagation();
e?.preventDefault();
handler?.();
}
function renderMenuItem(items: ContextMenuItem[]) {
return items.map((item) => {
const { disabled, label, children, divider = false } = item;
const contentProps = {
item,
handler: handleAction,
showIcon: props.showIcon,
};
if (!children || children.length === 0) {
return (
<>
<Menu.Item disabled={disabled} class={`${prefixCls}__item`} key={label}>
<ItemContent {...contentProps} />
</Menu.Item>
{divider ? <Divider key={`d-${label}`} /> : null}
</>
);
}
if (!unref(showRef)) return null;
return (
<Menu.SubMenu key={label} disabled={disabled} popupClassName={`${prefixCls}__popup`}>
{{
title: () => <ItemContent {...contentProps} />,
default: () => renderMenuItem(children),
}}
</Menu.SubMenu>
);
});
}
return () => {
if (!unref(showRef)) {
return null;
}
const { items } = props;
return (
<Menu
inlineIndent={12}
mode="vertical"
class={prefixCls}
ref={wrapRef}
style={unref(getStyle)}
>
{renderMenuItem(items)}
</Menu>
);
};
},
});
</script>
<style lang="less">
@default-height: 42px !important;
@small-height: 36px !important;
@large-height: 36px !important;
.item-style() {
li {
display: inline-block;
width: 100%;
height: @default-height;
margin: 0 !important;
line-height: @default-height;
span {
line-height: @default-height;
}
> div {
margin: 0 !important;
}
&:not(.ant-menu-item-disabled):hover {
color: @text-color-base;
background-color: @item-hover-bg;
}
}
}
.context-menu {
position: fixed;
top: 0;
left: 0;
z-index: 200;
display: block;
width: 156px;
margin: 0;
list-style: none;
background-color: @component-background;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 0.25rem;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.1),
0 1px 5px 0 rgba(0, 0, 0, 0.06);
background-clip: padding-box;
user-select: none;
.item-style();
.ant-divider {
margin: 0 0;
}
&__popup {
.ant-divider {
margin: 0 0;
}
.item-style();
}
.ant-menu-submenu-title,
.ant-menu-item {
padding: 0 !important;
}
}
</style>

View File

@ -1,6 +1,6 @@
import contextMenuVue from './ContextMenu'; import contextMenuVue from './ContextMenu.vue';
import { isClient } from '/@/utils/is'; import { isClient } from '/@/utils/is';
import { CreateContextOptions, ContextMenuProps } from './types'; import { CreateContextOptions, ContextMenuProps } from './typing';
import { createVNode, render } from 'vue'; import { createVNode, render } from 'vue';
const menuManager: { const menuManager: {
@ -16,7 +16,9 @@ export const createContextMenu = function (options: CreateContextOptions) {
event && event?.preventDefault(); event && event?.preventDefault();
if (!isClient) return; if (!isClient) {
return;
}
return new Promise((resolve) => { return new Promise((resolve) => {
const body = document.body; const body = document.body;
@ -54,9 +56,9 @@ export const createContextMenu = function (options: CreateContextOptions) {
body.removeEventListener('scroll', handleClick); body.removeEventListener('scroll', handleClick);
}; };
menuManager.resolve = function (...arg: any) { menuManager.resolve = function (arg) {
remove(); remove();
resolve(arg[0]); resolve(arg);
}; };
remove(); remove();
body.appendChild(container); body.appendChild(container);

View File

@ -1,65 +0,0 @@
@default-height: 42px !important;
@small-height: 36px !important;
@large-height: 36px !important;
.item-style() {
li {
display: inline-block;
width: 100%;
height: @default-height;
margin: 0 !important;
line-height: @default-height;
span {
line-height: @default-height;
}
> div {
margin: 0 !important;
}
&:not(.ant-menu-item-disabled):hover {
color: @text-color-base;
background-color: @item-hover-bg;
}
}
}
.context-menu {
position: fixed;
top: 0;
left: 0;
z-index: 200;
display: block;
width: 156px;
margin: 0;
list-style: none;
background-color: @component-background;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 0.25rem;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.1),
0 1px 5px 0 rgba(0, 0, 0, 0.06);
background-clip: padding-box;
user-select: none;
.item-style();
.ant-divider {
margin: 0 0;
}
&__popup {
.ant-divider {
margin: 0 0;
}
.item-style();
}
.ant-menu-submenu-title,
.ant-menu-item {
padding: 0 !important;
}
}

View File

@ -1,26 +0,0 @@
import type { PropType } from 'vue';
import type { Axis, ContextMenuItem } from './types';
import { propTypes } from '/@/utils/propTypes';
export const contextMenuProps = {
width: propTypes.number.def(156),
customEvent: {
type: Object as PropType<Event>,
default: null,
},
styles: propTypes.style,
showIcon: propTypes.bool.def(true),
axis: {
// The position of the right mouse button click
type: Object as PropType<Axis>,
default() {
return { x: 0, y: 0 };
},
},
items: {
// The most important list, if not, will not be displayed
type: Array as PropType<ContextMenuItem[]>,
default() {
return [];
},
},
};

View File

@ -1,4 +1,6 @@
import CountButton from './src/CountButton.vue'; import { withInstall } from '/@/utils';
import CountdownInput from './src/CountdownInput.vue'; import countButton from './src/CountButton.vue';
import countdownInput from './src/CountdownInput.vue';
export { CountdownInput, CountButton }; export const CountdownInput = withInstall(countdownInput);
export const CountButton = withInstall(countButton);

View File

@ -1,42 +1,44 @@
<template> <template>
<Button v-bind="$attrs" :disabled="isStart" @click="handleStart" :loading="loading"> <Button v-bind="$attrs" :disabled="isStart" @click="handleStart" :loading="loading">
{{ {{ getButtonText }}
!isStart
? t('component.countdown.normalText')
: t('component.countdown.sendText', [currentCount])
}}
</Button> </Button>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, PropType, watchEffect } from 'vue'; import { defineComponent, ref, watchEffect, computed, unref } from 'vue';
import { Button } from 'ant-design-vue'; import { Button } from 'ant-design-vue';
import { useCountdown } from './useCountdown'; import { useCountdown } from './useCountdown';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes';
const props = {
value: { type: [Object, Number, String, Array] },
count: { type: Number, default: 60 },
beforeStartFunc: {
type: Function as PropType<() => Promise<boolean>>,
default: null,
},
};
export default defineComponent({ export default defineComponent({
name: 'CountButton', name: 'CountButton',
components: { Button }, components: { Button },
props: { props,
value: propTypes.any,
count: propTypes.number.def(60),
beforeStartFunc: {
type: Function as PropType<() => boolean>,
default: null,
},
},
setup(props) { setup(props) {
const loading = ref(false); const loading = ref(false);
const { currentCount, isStart, start, reset } = useCountdown(props.count); const { currentCount, isStart, start, reset } = useCountdown(props.count);
const { t } = useI18n(); const { t } = useI18n();
const getButtonText = computed(() => {
return !unref(isStart)
? t('component.countdown.normalText')
: t('component.countdown.sendText', [unref(currentCount)]);
});
watchEffect(() => { watchEffect(() => {
props.value === undefined && reset(); props.value === undefined && reset();
}); });
/** /**
* @description: Judge whether there is an external function before execution, and decide whether to start after execution * @description: Judge whether there is an external function before execution, and decide whether to start after execution
*/ */
@ -54,7 +56,7 @@
start(); start();
} }
} }
return { handleStart, isStart, currentCount, loading, t }; return { handleStart, currentCount, loading, getButtonText, isStart };
}, },
}); });
</script> </script>

View File

@ -1,37 +1,35 @@
<template> <template>
<AInput v-bind="$attrs" :class="prefixCls" :size="size" :value="state"> <a-input v-bind="$attrs" :class="prefixCls" :size="size" :value="state">
<template #addonAfter> <template #addonAfter>
<CountButton :size="size" :count="count" :value="state" :beforeStartFunc="sendCodeApi" /> <CountButton :size="size" :count="count" :value="state" :beforeStartFunc="sendCodeApi" />
</template> </template>
</AInput> </a-input>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType } from 'vue'; import { defineComponent, PropType } from 'vue';
import { Input } from 'ant-design-vue';
import CountButton from './CountButton.vue'; import CountButton from './CountButton.vue';
import { propTypes } from '/@/utils/propTypes';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { useRuleFormItem } from '/@/hooks/component/useFormItem'; import { useRuleFormItem } from '/@/hooks/component/useFormItem';
const props = {
value: { type: String },
size: { type: String, validator: (v) => ['default', 'large', 'small'].includes(v) },
count: { type: Number, default: 60 },
sendCodeApi: {
type: Function as PropType<() => Promise<boolean>>,
default: null,
},
};
export default defineComponent({ export default defineComponent({
name: 'CountDownInput', name: 'CountDownInput',
components: { [Input.name]: Input, CountButton }, components: { CountButton },
inheritAttrs: false, inheritAttrs: false,
props: { props,
value: propTypes.string,
size: propTypes.oneOf(['default', 'large', 'small']),
count: propTypes.number.def(60),
sendCodeApi: {
type: Function as PropType<() => boolean>,
default: null,
},
},
setup(props) { setup(props) {
const { prefixCls } = useDesign('countdown-input'); const { prefixCls } = useDesign('countdown-input');
const [state] = useRuleFormItem(props); const [state] = useRuleFormItem(props);
return { prefixCls, state }; return { prefixCls, state };
}, },
}); });

View File

@ -1,4 +1,4 @@
// Transform vue-count-to to support vue3 version import { withInstall } from '/@/utils';
import countTo from './src/CountTo.vue';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; export const CountTo = withInstall(countTo);
export const CountTo = createAsyncComponent(() => import('./src/CountTo.vue'));

View File

@ -1,50 +1,56 @@
<template> <template>
<span :style="{ color: color }"> <span :style="{ color }">
{{ displayValue }} {{ value }}
</span> </span>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, reactive, computed, watch, onMounted, unref, toRef } from 'vue'; import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
import { countToProps } from './props'; import { useTransition, TransitionPresets } from '@vueuse/core';
import { isNumber } from '/@/utils/is'; import { isNumber } from '/@/utils/is';
const props = {
startVal: { type: Number, default: 0 },
endVal: { type: Number, default: 2021 },
duration: { type: Number, default: 1500 },
autoplay: { type: Boolean, default: true },
decimals: {
type: Number,
default: 0,
validator(value: number) {
return value >= 0;
},
},
prefix: { type: String, default: '' },
suffix: { type: String, default: '' },
separator: { type: String, default: ',' },
decimal: { type: String, default: '.' },
/**
* font color
*/
color: { type: String },
/**
* Turn on digital animation
*/
useEasing: { type: Boolean, default: true },
/**
* Digital animation
*/
transition: { type: String, default: 'linear' },
};
export default defineComponent({ export default defineComponent({
name: 'CountTo', name: 'CountTo',
props: countToProps, props,
emits: ['mounted', 'callback'], emits: ['onStarted', 'onFinished'],
setup(props, { emit }) { setup(props, { emit }) {
const state = reactive<{ const source = ref(props.startVal);
localStartVal: number; const disabled = ref(false);
printVal: number | null; let outputValue = useTransition(source);
displayValue: string;
paused: boolean;
localDuration: number | null;
startTime: number | null;
timestamp: number | null;
rAF: any;
remaining: number | null;
color: any;
}>({
localStartVal: props.startVal,
displayValue: formatNumber(props.startVal),
printVal: null,
paused: false,
localDuration: props.duration,
startTime: null,
timestamp: null,
remaining: null,
rAF: null,
color: null,
});
onMounted(() => { const value = computed(() => formatNumber(unref(outputValue)));
if (props.autoplay) {
start();
}
emit('mounted');
});
const getCountDown = computed(() => { watchEffect(() => {
return props.startVal > props.endVal; source.value = props.startVal;
}); });
watch([() => props.startVal, () => props.endVal], () => { watch([() => props.startVal, () => props.endVal], () => {
@ -53,93 +59,42 @@
} }
}); });
onMounted(() => {
props.autoplay && start();
});
function start() { function start() {
const { startVal, duration, color } = props; run();
state.localStartVal = startVal; source.value = props.endVal;
state.startTime = null;
state.localDuration = duration;
state.color = color;
state.paused = false;
state.rAF = requestAnimationFrame(count);
}
function pauseResume() {
if (state.paused) {
resume();
state.paused = false;
} else {
pause();
state.paused = true;
}
}
function pause() {
cancelAnimationFrame(state.rAF);
}
function resume() {
state.startTime = null;
state.localDuration = +(state.remaining as number);
state.localStartVal = +(state.printVal as number);
requestAnimationFrame(count);
} }
function reset() { function reset() {
state.startTime = null; source.value = props.startVal;
cancelAnimationFrame(state.rAF); run();
state.displayValue = formatNumber(props.startVal);
} }
function count(timestamp: number) { function run() {
const { useEasing, easingFn, endVal } = props; outputValue = useTransition(source, {
if (!state.startTime) state.startTime = timestamp; disabled,
state.timestamp = timestamp; duration: props.duration,
const progress = timestamp - state.startTime; onFinished: () => emit('onFinished'),
state.remaining = (state.localDuration as number) - progress; onStarted: () => emit('onStarted'),
if (useEasing) { ...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
if (unref(getCountDown)) { });
state.printVal =
state.localStartVal -
easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number);
} else {
state.printVal = easingFn(
progress,
state.localStartVal,
endVal - state.localStartVal,
state.localDuration as number
);
}
} else {
if (unref(getCountDown)) {
state.printVal =
state.localStartVal -
(state.localStartVal - endVal) * (progress / (state.localDuration as number));
} else {
state.printVal =
state.localStartVal +
(endVal - state.localStartVal) * (progress / (state.localDuration as number));
}
}
if (unref(getCountDown)) {
state.printVal = state.printVal < endVal ? endVal : state.printVal;
} else {
state.printVal = state.printVal > endVal ? endVal : state.printVal;
}
state.displayValue = formatNumber(state.printVal);
if (progress < (state.localDuration as number)) {
state.rAF = requestAnimationFrame(count);
} else {
emit('callback');
}
} }
function formatNumber(num: number | string) { function formatNumber(num: number | string) {
if (!num) {
return '';
}
const { decimals, decimal, separator, suffix, prefix } = props; const { decimals, decimal, separator, suffix, prefix } = props;
num = Number(num).toFixed(decimals); num = Number(num).toFixed(decimals);
num += ''; num += '';
const x = num.split('.'); const x = num.split('.');
let x1 = x[0]; let x1 = x[0];
const x2 = x.length > 1 ? decimal + x[1] : ''; const x2 = x.length > 1 ? decimal + x[1] : '';
const rgx = /(\d+)(\d{3})/; const rgx = /(\d+)(\d{3})/;
if (separator && !isNumber(separator)) { if (separator && !isNumber(separator)) {
while (rgx.test(x1)) { while (rgx.test(x1)) {
@ -149,14 +104,7 @@
return prefix + x1 + x2 + suffix; return prefix + x1 + x2 + suffix;
} }
return { return { value, start, reset };
count,
reset,
resume,
start,
pauseResume,
displayValue: toRef(state, 'displayValue'),
};
}, },
}); });
</script> </script>

View File

@ -1,31 +0,0 @@
import { PropType } from 'vue';
import { propTypes } from '/@/utils/propTypes';
export const countToProps = {
startVal: propTypes.number.def(0),
endVal: propTypes.number.def(2020),
duration: propTypes.number.def(1300),
autoplay: propTypes.bool.def(true),
decimals: {
type: Number as PropType<number>,
required: false,
default: 0,
validator(value: number) {
return value >= 0;
},
},
color: {
type: String as PropType<string>,
require: false,
},
decimal: propTypes.string.def('.'),
separator: propTypes.string.def(','),
prefix: propTypes.string.def(''),
suffix: propTypes.string.def(''),
useEasing: propTypes.bool.def(true),
easingFn: {
type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
default(t: number, b: number, c: number, d: number) {
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
},
},
};

View File

@ -1,4 +1,7 @@
import type Cropper from 'cropperjs'; import { withInstall } from '/@/utils';
import cropperImage from './src/Cropper.vue';
import avatarCropper from './src/CropperAvatar.vue';
export type { Cropper }; export * from './src/typing';
export { default as CropperImage } from './src/Cropper.vue'; export const CropperImage = withInstall(cropperImage);
export const CropperAvatar = withInstall(avatarCropper);

View File

@ -1,15 +0,0 @@
<template>
<div :class="$attrs.class" :style="$attrs.style"> </div>
</template>
<script lang="ts">
// TODO
import { defineComponent } from 'vue';
export default defineComponent({
name: 'AvatarCropper',
props: {},
setup() {
return {};
},
});
</script>

View File

@ -0,0 +1,258 @@
<template>
<BasicModal
v-bind="$attrs"
@register="register"
:title="t('component.cropper.modalTitle')"
width="800px"
:canFullscreen="false"
@ok="handleOk"
:okText="t('component.cropper.okText')"
>
<div :class="prefixCls">
<div :class="`${prefixCls}-left`">
<div :class="`${prefixCls}-cropper`">
<CropperImage
v-if="src"
:src="src"
height="300px"
:circled="circled"
@cropend="handleCropend"
@ready="handleReady"
/>
</div>
<div :class="`${prefixCls}-toolbar`">
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
</Upload>
<Space>
<a-button
type="primary"
preIcon="ant-design:reload-outlined"
size="small"
@click="handlerToolbar('reset')"
/>
<a-button
type="primary"
preIcon="ant-design:rotate-left-outlined"
size="small"
@click="handlerToolbar('rotate', -45)"
/>
<a-button
type="primary"
preIcon="ant-design:rotate-right-outlined"
size="small"
@click="handlerToolbar('rotate', 45)"
/>
<a-button
type="primary"
preIcon="vaadin:arrows-long-h"
size="small"
@click="handlerToolbar('scaleX')"
/>
<a-button
type="primary"
preIcon="vaadin:arrows-long-v"
size="small"
@click="handlerToolbar('scaleY')"
/>
<a-button
type="primary"
preIcon="ant-design:zoom-in-outlined"
size="small"
@click="handlerToolbar('zoom', 0.1)"
/>
<a-button
type="primary"
preIcon="ant-design:zoom-out-outlined"
size="small"
@click="handlerToolbar('zoom', -0.1)"
/>
</Space>
</div>
</div>
<div :class="`${prefixCls}-right`">
<div :class="`${prefixCls}-preview`">
<img :src="previewSource" v-if="previewSource" />
</div>
<template v-if="previewSource">
<div :class="`${prefixCls}-group`">
<Avatar :src="previewSource" size="large" />
<Avatar :src="previewSource" :size="48" />
<Avatar :src="previewSource" :size="64" />
<Avatar :src="previewSource" :size="80" />
</div>
</template>
</div>
</div>
</BasicModal>
</template>
<script lang="ts">
import type { CropendResult, Cropper } from './typing';
import { defineComponent, ref } from 'vue';
import CropperImage from './Cropper.vue';
import { Space, Upload, Avatar } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { dataURLtoBlob } from '/@/utils/file/base64Conver';
import { isFunction } from '/@/utils/is';
import { useI18n } from '/@/hooks/web/useI18n';
const props = {
circled: { type: Boolean, default: true },
uploadApi: {
type: Function as PropType<({ file: Blob, name: stirng, filename: string }) => Promise<any>>,
},
};
export default defineComponent({
name: 'CropperAvatar',
components: { BasicModal, Space, CropperImage, Upload, Avatar },
props,
emits: ['uploadSuccess', 'register'],
setup(props, { emit }) {
let filename = '';
const src = ref('');
const previewSource = ref('');
const cropper = ref<Cropper>();
let scaleX = 1;
let scaleY = 1;
const { prefixCls } = useDesign('cropper-am');
const [register, { closeModal, setModalProps }] = useModalInner();
const { t } = useI18n();
// Block upload
function handleBeforeUpload(file: File) {
const reader = new FileReader();
reader.readAsDataURL(file);
src.value = '';
previewSource.value = '';
reader.onload = function (e) {
src.value = (e.target?.result as string) ?? '';
filename = file.name;
};
return false;
}
function handleCropend({ imgBase64 }: CropendResult) {
previewSource.value = imgBase64;
}
function handleReady(cropperInstance: Cropper) {
cropper.value = cropperInstance;
}
function handlerToolbar(event: string, arg?: number) {
if (event === 'scaleX') {
scaleX = arg = scaleX === -1 ? 1 : -1;
}
if (event === 'scaleY') {
scaleY = arg = scaleY === -1 ? 1 : -1;
}
cropper?.value?.[event]?.(arg);
}
async function handleOk() {
const uploadApi = props.uploadApi;
if (uploadApi && isFunction(uploadApi)) {
const blob = dataURLtoBlob(previewSource.value);
try {
setModalProps({ confirmLoading: true });
const result = await uploadApi({ name: 'file', file: blob, filename });
emit('uploadSuccess', { source: previewSource.value, data: result.data });
closeModal();
} finally {
setModalProps({ confirmLoading: false });
}
}
}
return {
t,
prefixCls,
src,
register,
previewSource,
handleBeforeUpload,
handleCropend,
handleReady,
handlerToolbar,
handleOk,
};
},
});
</script>
<style lang="less">
@prefix-cls: ~'@{namespace}-cropper-am';
.@{prefix-cls} {
display: flex;
&-left,
&-right {
height: 340px;
}
&-left {
width: 55%;
}
&-right {
width: 45%;
}
&-cropper {
height: 300px;
background: #eee;
background-image: linear-gradient(
45deg,
rgba(0, 0, 0, 0.25) 25%,
transparent 0,
transparent 75%,
rgba(0, 0, 0, 0.25) 0
),
linear-gradient(
45deg,
rgba(0, 0, 0, 0.25) 25%,
transparent 0,
transparent 75%,
rgba(0, 0, 0, 0.25) 0
);
background-position: 0 0, 12px 12px;
background-size: 24px 24px;
}
&-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
}
&-preview {
width: 220px;
height: 220px;
margin: 0 auto;
overflow: hidden;
border: 1px solid @border-color-base;
border-radius: 50%;
img {
width: 100%;
height: 100%;
}
}
&-group {
display: flex;
padding-top: 8px;
margin-top: 8px;
border-top: 1px solid @border-color-base;
justify-content: space-around;
align-items: center;
}
}
</style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div :class="$attrs.class" :style="getWrapperStyle"> <div :class="getClass" :style="getWrapperStyle">
<img <img
v-show="isReady" v-show="isReady"
ref="imgElRef" ref="imgElRef"
@ -12,16 +12,16 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue';
import { defineComponent, onMounted, ref, unref, computed } from 'vue'; import { defineComponent, onMounted, ref, unref, computed } from 'vue';
import Cropper from 'cropperjs'; import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css'; import 'cropperjs/dist/cropper.css';
import { useDesign } from '/@/hooks/web/useDesign';
import { useDebounceFn } from '@vueuse/shared';
type Options = Cropper.Options; type Options = Cropper.Options;
const defaultOptions: Cropper.Options = { const defaultOptions: Options = {
aspectRatio: 16 / 9, aspectRatio: 1,
zoomable: true, zoomable: true,
zoomOnTouch: true, zoomOnTouch: true,
zoomOnWheel: true, zoomOnWheel: true,
@ -42,40 +42,33 @@
movable: true, movable: true,
rotatable: true, rotatable: true,
}; };
export default defineComponent({
name: 'CropperImage', const props = {
props: { src: { type: String, required: true },
src: { alt: { type: String },
type: String, circled: { type: Boolean, default: false },
required: true, realTimePreview: { type: Boolean, default: true },
}, height: { type: [String, Number], default: '360px' },
alt: {
type: String,
},
height: {
type: [String, Number],
default: '360px',
},
crossorigin: { crossorigin: {
type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>, type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>,
default: undefined, default: undefined,
}, },
imageStyle: { imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
type: Object as PropType<CSSProperties>, options: { type: Object as PropType<Options>, default: () => ({}) },
default: () => ({}), };
},
options: {
type: Object as PropType<Options>,
default: () => ({}),
},
},
emits: ['cropperedInfo'],
setup(props, ctx) {
const imgElRef = ref<ElRef<HTMLImageElement>>(null);
const cropper: any = ref<Nullable<Cropper>>(null);
export default defineComponent({
name: 'CropperImage',
props,
emits: ['cropend', 'ready', 'cropendError'],
setup(props, { attrs, emit }) {
const imgElRef = ref<ElRef<HTMLImageElement>>();
const cropper = ref<Nullable<Cropper>>();
const isReady = ref(false); const isReady = ref(false);
const { prefixCls } = useDesign('cropper-image');
const debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80);
const getImageStyle = computed((): CSSProperties => { const getImageStyle = computed((): CSSProperties => {
return { return {
height: props.height, height: props.height,
@ -84,11 +77,22 @@
}; };
}); });
const getWrapperStyle = computed((): CSSProperties => { const getClass = computed(() => {
const { height } = props; return [
return { height: `${height}`.replace(/px/, '') + 'px' }; prefixCls,
attrs.class,
{
[`${prefixCls}--circled`]: props.circled,
},
];
}); });
const getWrapperStyle = computed((): CSSProperties => {
return { height: `${props.height}`.replace(/px/, '') + 'px' };
});
onMounted(init);
async function init() { async function init() {
const imgEl = unref(imgElRef); const imgEl = unref(imgElRef);
if (!imgEl) { if (!imgEl) {
@ -98,29 +102,83 @@
...defaultOptions, ...defaultOptions,
ready: () => { ready: () => {
isReady.value = true; isReady.value = true;
realTimeCroppered();
emit('ready', cropper.value);
},
crop() {
debounceRealTimeCroppered();
},
zoom() {
debounceRealTimeCroppered();
},
cropmove() {
debounceRealTimeCroppered();
}, },
...props.options, ...props.options,
}); });
} }
// Real-time display preview
function realTimeCroppered() {
props.realTimePreview && croppered();
}
// event: return base64 and width and height information after cropping // event: return base64 and width and height information after cropping
const croppered = (): void => { function croppered() {
if (!cropper.value) {
return;
}
let imgInfo = cropper.value.getData(); let imgInfo = cropper.value.getData();
cropper.value.getCroppedCanvas().toBlob((blob) => { const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas();
canvas.toBlob((blob) => {
if (!blob) {
return;
}
let fileReader: FileReader = new FileReader(); let fileReader: FileReader = new FileReader();
fileReader.onloadend = (e: any) => { fileReader.readAsDataURL(blob);
ctx.emit('cropperedInfo', { fileReader.onloadend = (e) => {
imgBase64: e.target.result, emit('cropend', {
imgBase64: e.target?.result ?? '',
imgInfo, imgInfo,
}); });
}; };
fileReader.readAsDataURL(blob); fileReader.onerror = () => {
}, 'image/jpeg'); emit('cropendError');
}; };
}, 'image/png');
}
onMounted(init); // Get a circular picture canvas
function getRoundedCanvas() {
const sourceCanvas = cropper.value!.getCroppedCanvas();
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d')!;
const width = sourceCanvas.width;
const height = sourceCanvas.height;
canvas.width = width;
canvas.height = height;
context.imageSmoothingEnabled = true;
context.drawImage(sourceCanvas, 0, 0, width, height);
context.globalCompositeOperation = 'destination-in';
context.beginPath();
context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
context.fill();
return canvas;
}
return { imgElRef, getWrapperStyle, getImageStyle, isReady, croppered }; return { getClass, imgElRef, getWrapperStyle, getImageStyle, isReady, croppered };
}, },
}); });
</script> </script>
<style lang="less">
@prefix-cls: ~'@{namespace}-cropper-image';
.@{prefix-cls} {
&--circled {
.cropper-view-box,
.cropper-face {
border-radius: 50%;
}
}
}
</style>

View File

@ -0,0 +1,89 @@
<template>
<div :class="getClass" :style="getStyle">
<div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal">
<img :src="sourceValue" v-if="sourceValue" alt="avatar" />
</div>
<a-button :class="`${prefixCls}-upload-btn`" @click="openModal">
{{ t('component.cropper.selectImage') }}
</a-button>
<CopperModal @register="register" @uploadSuccess="handleUploadSuccess" :uploadApi="uploadApi" />
</div>
</template>
<script lang="ts">
import { defineComponent, computed, CSSProperties, unref, ref } from 'vue';
import CopperModal from './CopperModal.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
const props = {
width: { type: [String, Number], default: '200px' },
uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> },
};
export default defineComponent({
name: 'CropperAvatar',
components: { CopperModal },
props,
setup(props) {
const sourceValue = ref('');
const { prefixCls } = useDesign('cropper-avatar');
const [register, { openModal }] = useModal();
const { createMessage } = useMessage();
const { t } = useI18n();
const getClass = computed(() => [prefixCls]);
const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px');
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
const getImageWrapperStyle = computed(
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) })
);
function handleUploadSuccess({ source }) {
sourceValue.value = source;
createMessage.success(t('component.cropper.uploadSuccess'));
}
return {
t,
prefixCls,
register,
openModal,
sourceValue,
getClass,
getImageWrapperStyle,
getStyle,
handleUploadSuccess,
};
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-cropper-avatar';
.@{prefix-cls} {
display: inline-block;
text-align: center;
&-image-wrapper {
overflow: hidden;
cursor: pointer;
background: @component-background;
border: 1px solid @border-color-base;
border-radius: 50%;
img {
width: 100%;
}
}
&-upload-btn {
margin: 10px auto;
}
}
</style>

View File

@ -0,0 +1,8 @@
import type Cropper from 'cropperjs';
export interface CropendResult {
imgBase64: string;
imgInfo: Cropper.Data;
}
export type { Cropper };

View File

@ -1,5 +1,6 @@
import Description from './src/Description.vue'; import { withInstall } from '/@/utils';
import description from './src/Description.vue';
export { Description }; export * from './src/typing';
export * from './src/types';
export { useDescription } from './src/useDescription'; export { useDescription } from './src/useDescription';
export const Description = withInstall(description);

View File

@ -1,28 +1,49 @@
<script lang="tsx"> <script lang="tsx">
import type { DescOptions, DescInstance, DescItem } from './types'; import type { DescriptionProps, DescInstance, DescItem } from './typing';
import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index'; import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index';
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue';
import type { CollapseContainerOptions } from '/@/components/Container/index'; import type { CollapseContainerOptions } from '/@/components/Container/index';
import { defineComponent, computed, ref, unref } from 'vue'; import { defineComponent, computed, ref, unref } from 'vue';
import { get } from 'lodash-es'; import { get } from 'lodash-es';
import { Descriptions } from 'ant-design-vue'; import { Descriptions } from 'ant-design-vue';
import { CollapseContainer } from '/@/components/Container/index'; import { CollapseContainer } from '/@/components/Container/index';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper';
import descProps from './props';
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs';
const props = {
useCollapse: { type: Boolean, default: true },
title: { type: String, default: '' },
size: {
type: String,
validator: (v) => ['small', 'default', 'middle', undefined].includes(v),
default: 'small',
},
bordered: { type: Boolean, default: true },
column: {
type: [Number, Object] as PropType<number | Recordable>,
default: () => {
return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 };
},
},
collapseOptions: {
type: Object as PropType<CollapseContainerOptions>,
default: null,
},
schema: {
type: Array as PropType<DescItem[]>,
default: () => [],
},
data: { type: Object },
};
export default defineComponent({ export default defineComponent({
name: 'Description', name: 'Description',
props: descProps, props,
emits: ['register'], emits: ['register'],
setup(props, { slots, emit }) { setup(props, { slots, emit }) {
const propsRef = ref<Partial<DescOptions> | null>(null); const propsRef = ref<Partial<DescriptionProps> | null>(null);
const { prefixCls } = useDesign('description'); const { prefixCls } = useDesign('description');
const attrs = useAttrs(); const attrs = useAttrs();
@ -32,7 +53,7 @@
return { return {
...props, ...props,
...(unref(propsRef) as Recordable), ...(unref(propsRef) as Recordable),
} as DescOptions; } as DescriptionProps;
}); });
const getProps = computed(() => { const getProps = computed(() => {
@ -40,7 +61,7 @@
...unref(getMergeProps), ...unref(getMergeProps),
title: undefined, title: undefined,
}; };
return opt as DescOptions; return opt as DescriptionProps;
}); });
/** /**
@ -66,7 +87,7 @@
/** /**
* @description:设置desc * @description:设置desc
*/ */
function setDescProps(descProps: Partial<DescOptions>): void { function setDescProps(descProps: Partial<DescriptionProps>): void {
// Keep the last setDrawerProps // Keep the last setDrawerProps
propsRef.value = { ...(unref(propsRef) as Recordable), ...descProps } as Recordable; propsRef.value = { ...(unref(propsRef) as Recordable), ...descProps } as Recordable;
} }
@ -79,7 +100,6 @@
const labelStyles: CSSProperties = { const labelStyles: CSSProperties = {
...labelStyle, ...labelStyle,
minWidth: `${labelMinWidth}px `, minWidth: `${labelMinWidth}px `,
}; };
return <div style={labelStyles}>{label}</div>; return <div style={labelStyles}>{label}</div>;
@ -97,7 +117,9 @@
const getContent = () => { const getContent = () => {
const _data = unref(getProps)?.data; const _data = unref(getProps)?.data;
if (!_data) return null; if (!_data) {
return null;
}
const getField = get(_data, field); const getField = get(_data, field);
return isFunction(render) ? render(getField, _data) : getField ?? ''; return isFunction(render) ? render(getField, _data) : getField ?? '';
}; };
@ -131,7 +153,6 @@
const renderContainer = () => { const renderContainer = () => {
const content = props.useCollapse ? renderDesc() : <div>{renderDesc()}</div>; const content = props.useCollapse ? renderDesc() : <div>{renderDesc()}</div>;
// Reduce the dom level // Reduce the dom level
if (!props.useCollapse) { if (!props.useCollapse) {
return content; return content;
} }

View File

@ -1,25 +0,0 @@
import type { PropType } from 'vue';
import type { CollapseContainerOptions } from '/@/components/Container';
import type { DescItem } from './types';
import { propTypes } from '/@/utils/propTypes';
export default {
useCollapse: propTypes.bool.def(true),
title: propTypes.string.def(''),
size: propTypes.oneOf(['small', 'default', 'middle', undefined]).def('small'),
bordered: propTypes.bool.def(true),
column: {
type: [Number, Object] as PropType<number | Recordable>,
default: () => {
return { xxl: 4, xl: 3, lg: 3, md: 3, sm: 2, xs: 1 };
},
},
collapseOptions: {
type: Object as PropType<CollapseContainerOptions>,
default: null,
},
schema: {
type: Array as PropType<Array<DescItem>>,
default: () => [],
},
data: propTypes.object,
};

View File

@ -4,11 +4,8 @@ import type { DescriptionsProps } from 'ant-design-vue/es/descriptions/index';
export interface DescItem { export interface DescItem {
labelMinWidth?: number; labelMinWidth?: number;
contentMinWidth?: number; contentMinWidth?: number;
labelStyle?: CSSProperties; labelStyle?: CSSProperties;
field: string; field: string;
label: string | VNode | JSX.Element; label: string | VNode | JSX.Element;
// Merge column // Merge column
@ -21,7 +18,7 @@ export interface DescItem {
) => VNode | undefined | JSX.Element | Element | string | number; ) => VNode | undefined | JSX.Element | Element | string | number;
} }
export interface DescOptions extends DescriptionsProps { export interface DescriptionProps extends DescriptionsProps {
// Whether to include the collapse component // Whether to include the collapse component
useCollapse?: boolean; useCollapse?: boolean;
/** /**
@ -42,7 +39,7 @@ export interface DescOptions extends DescriptionsProps {
} }
export interface DescInstance { export interface DescInstance {
setDescProps(descProps: Partial<DescOptions>): void; setDescProps(descProps: Partial<DescriptionProps>): void;
} }
export type Register = (descInstance: DescInstance) => void; export type Register = (descInstance: DescInstance) => void;

View File

@ -1,25 +1,26 @@
import type { DescriptionProps, DescInstance, UseDescReturnType } from './typing';
import { ref, getCurrentInstance, unref } from 'vue'; import { ref, getCurrentInstance, unref } from 'vue';
import { isProdMode } from '/@/utils/env'; import { isProdMode } from '/@/utils/env';
import type { DescOptions, DescInstance, UseDescReturnType } from './types'; export function useDescription(props?: Partial<DescriptionProps>): UseDescReturnType {
export function useDescription(props?: Partial<DescOptions>): UseDescReturnType {
if (!getCurrentInstance()) { if (!getCurrentInstance()) {
throw new Error('Please put useDescription function in the setup function!'); throw new Error('useDescription() can only be used inside setup() or functional components!');
} }
const descRef = ref<Nullable<DescInstance>>(null); const desc = ref<Nullable<DescInstance>>(null);
const loadedRef = ref(false); const loaded = ref(false);
function register(instance: DescInstance) { function register(instance: DescInstance) {
if (unref(loadedRef) && isProdMode()) return; if (unref(loaded) && isProdMode()) {
descRef.value = instance; return;
}
desc.value = instance;
props && instance.setDescProps(props); props && instance.setDescProps(props);
loadedRef.value = true; loaded.value = true;
} }
const methods: DescInstance = { const methods: DescInstance = {
setDescProps: (descProps: Partial<DescOptions>): void => { setDescProps: (descProps: Partial<DescriptionProps>): void => {
unref(descRef)?.setDescProps(descProps); unref(desc)?.setDescProps(descProps);
}, },
}; };

View File

@ -1,5 +1,6 @@
import BasicDrawer from './src/BasicDrawer.vue'; import { withInstall } from '/@/utils';
import basicDrawer from './src/BasicDrawer.vue';
export { BasicDrawer }; export const BasicDrawer = withInstall(basicDrawer);
export * from './src/types'; export * from './src/typing';
export { useDrawer, useDrawerInner } from './src/useDrawer'; export { useDrawer, useDrawerInner } from './src/useDrawer';

View File

@ -31,9 +31,8 @@
</Drawer> </Drawer>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { DrawerInstance, DrawerProps } from './types'; import type { DrawerInstance, DrawerProps } from './typing';
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue';
import { import {
defineComponent, defineComponent,
ref, ref,
@ -46,15 +45,12 @@
getCurrentInstance, getCurrentInstance,
} from 'vue'; } from 'vue';
import { Drawer } from 'ant-design-vue'; import { Drawer } from 'ant-design-vue';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { isFunction, isNumber } from '/@/utils/is'; import { isFunction, isNumber } from '/@/utils/is';
import { deepMerge } from '/@/utils'; import { deepMerge } from '/@/utils';
import DrawerFooter from './components/DrawerFooter.vue'; import DrawerFooter from './components/DrawerFooter.vue';
import DrawerHeader from './components/DrawerHeader.vue'; import DrawerHeader from './components/DrawerHeader.vue';
import { ScrollContainer } from '/@/components/Container'; import { ScrollContainer } from '/@/components/Container';
import { basicProps } from './props'; import { basicProps } from './props';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs';
@ -81,14 +77,11 @@
instance && emit('register', drawerInstance, instance.uid); instance && emit('register', drawerInstance, instance.uid);
const getMergeProps = computed( const getMergeProps = computed((): DrawerProps => {
(): DrawerProps => {
return deepMerge(toRaw(props), unref(propsRef)); return deepMerge(toRaw(props), unref(propsRef));
} });
);
const getProps = computed( const getProps = computed((): DrawerProps => {
(): DrawerProps => {
const opt = { const opt = {
placement: 'right', placement: 'right',
...unref(attrs), ...unref(attrs),
@ -110,17 +103,14 @@
} }
} }
return opt as DrawerProps; return opt as DrawerProps;
} });
);
const getBindValues = computed( const getBindValues = computed((): DrawerProps => {
(): DrawerProps => {
return { return {
...attrs, ...attrs,
...unref(getProps), ...unref(getProps),
}; };
} });
);
// Custom implementation of the bottom button, // Custom implementation of the bottom button,
const getFooterHeight = computed(() => { const getFooterHeight = computed(() => {
@ -133,15 +123,13 @@
return `0px`; return `0px`;
}); });
const getScrollContentStyle = computed( const getScrollContentStyle = computed((): CSSProperties => {
(): CSSProperties => {
const footerHeight = unref(getFooterHeight); const footerHeight = unref(getFooterHeight);
return { return {
position: 'relative', position: 'relative',
height: `calc(100% - ${footerHeight})`, height: `calc(100% - ${footerHeight})`,
}; };
} });
);
const getLoading = computed(() => { const getLoading = computed(() => {
return !!unref(getProps)?.loading; return !!unref(getProps)?.loading;
@ -175,7 +163,7 @@
function setDrawerProps(props: Partial<DrawerProps>): void { function setDrawerProps(props: Partial<DrawerProps>): void {
// Keep the last setDrawerProps // Keep the last setDrawerProps
propsRef.value = deepMerge(unref(propsRef) || {}, props); propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
if (Reflect.has(props, 'visible')) { if (Reflect.has(props, 'visible')) {
visibleRef.value = !!props.visible; visibleRef.value = !!props.visible;

View File

@ -43,15 +43,13 @@
setup(props, { emit }) { setup(props, { emit }) {
const { prefixCls } = useDesign('basic-drawer-footer'); const { prefixCls } = useDesign('basic-drawer-footer');
const getStyle = computed( const getStyle = computed((): CSSProperties => {
(): CSSProperties => {
const heightStr = `${props.height}`; const heightStr = `${props.height}`;
return { return {
height: heightStr, height: heightStr,
lineHeight: heightStr, lineHeight: heightStr,
}; };
} });
);
function handleOk() { function handleOk() {
emit('ok'); emit('ok');

View File

@ -40,6 +40,7 @@
function handleClose() { function handleClose() {
emit('close'); emit('close');
} }
return { prefixCls, handleClose }; return { prefixCls, handleClose };
}, },
}); });

View File

@ -1,50 +1,44 @@
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes';
const { t } = useI18n(); const { t } = useI18n();
export const footerProps = { export const footerProps = {
confirmLoading: propTypes.bool, confirmLoading: { type: Boolean },
/** /**
* @description: Show close button * @description: Show close button
*/ */
showCancelBtn: propTypes.bool.def(true), showCancelBtn: { type: Boolean, default: true },
cancelButtonProps: Object as PropType<Recordable>, cancelButtonProps: Object as PropType<Recordable>,
cancelText: propTypes.string.def(t('common.cancelText')), cancelText: { type: String, default: t('common.cancelText') },
/** /**
* @description: Show confirmation button * @description: Show confirmation button
*/ */
showOkBtn: propTypes.bool.def(true), showOkBtn: { type: Boolean, default: true },
okButtonProps: Object as PropType<Recordable>, okButtonProps: Object as PropType<Recordable>,
okText: propTypes.string.def(t('common.okText')), okText: { type: String, default: t('common.okText') },
okType: propTypes.string.def('primary'), okType: { type: String, default: 'primary' },
showFooter: propTypes.bool, showFooter: { type: Boolean },
footerHeight: { footerHeight: {
type: [String, Number] as PropType<string | number>, type: [String, Number] as PropType<string | number>,
default: 60, default: 60,
}, },
}; };
export const basicProps = { export const basicProps = {
isDetail: propTypes.bool, isDetail: { type: Boolean },
title: propTypes.string.def(''), title: { type: String, default: '' },
loadingText: propTypes.string, loadingText: { type: String },
showDetailBack: propTypes.bool.def(true), showDetailBack: { type: Boolean, default: true },
visible: propTypes.bool, visible: { type: Boolean },
loading: propTypes.bool, loading: { type: Boolean },
maskClosable: propTypes.bool.def(true), maskClosable: { type: Boolean, default: true },
getContainer: { getContainer: {
type: [Object, String] as PropType<any>, type: [Object, String] as PropType<any>,
}, },
scrollOptions: {
type: Object as PropType<any>,
default: null,
},
closeFunc: { closeFunc: {
type: [Function, Object] as PropType<any>, type: [Function, Object] as PropType<any>,
default: null, default: null,
}, },
triggerWindowResize: propTypes.bool, destroyOnClose: { type: Boolean },
destroyOnClose: propTypes.bool,
...footerProps, ...footerProps,
}; };

View File

@ -9,6 +9,7 @@ export interface DrawerInstance {
export interface ReturnMethods extends DrawerInstance { export interface ReturnMethods extends DrawerInstance {
openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void; openDrawer: <T = any>(visible?: boolean, data?: T, openOnSet?: boolean) => void;
closeDrawer: () => void;
getVisible?: ComputedRef<boolean>; getVisible?: ComputedRef<boolean>;
} }
@ -181,7 +182,6 @@ export interface DrawerProps extends DrawerFooterProps {
placement?: 'top' | 'right' | 'bottom' | 'left'; placement?: 'top' | 'right' | 'bottom' | 'left';
afterVisibleChange?: (visible?: boolean) => void; afterVisibleChange?: (visible?: boolean) => void;
keyboard?: boolean; keyboard?: boolean;
/** /**
* Specify a callback that will be called when a user clicks mask, close button or Cancel button. * Specify a callback that will be called when a user clicks mask, close button or Cancel button.
*/ */

View File

@ -4,8 +4,7 @@ import type {
ReturnMethods, ReturnMethods,
DrawerProps, DrawerProps,
UseDrawerInnerReturnType, UseDrawerInnerReturnType,
} from './types'; } from './typing';
import { import {
ref, ref,
getCurrentInstance, getCurrentInstance,
@ -16,11 +15,9 @@ import {
toRaw, toRaw,
computed, computed,
} from 'vue'; } from 'vue';
import { isProdMode } from '/@/utils/env'; import { isProdMode } from '/@/utils/env';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { tryOnUnmounted } from '@vueuse/core'; import { tryOnUnmounted } from '@vueuse/core';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { error } from '/@/utils/log'; import { error } from '/@/utils/log';
@ -32,24 +29,27 @@ const visibleData = reactive<{ [key: number]: boolean }>({});
* @description: Applicable to separate drawer and call outside * @description: Applicable to separate drawer and call outside
*/ */
export function useDrawer(): UseDrawerReturnType { export function useDrawer(): UseDrawerReturnType {
const drawerRef = ref<DrawerInstance | null>(null); if (!getCurrentInstance()) {
const loadedRef = ref<Nullable<boolean>>(false); throw new Error('useDrawer() can only be used inside setup() or functional components!');
const uidRef = ref<string>(''); }
const drawer = ref<DrawerInstance | null>(null);
const loaded = ref<Nullable<boolean>>(false);
const uid = ref<string>('');
function register(drawerInstance: DrawerInstance, uuid: string) { function register(drawerInstance: DrawerInstance, uuid: string) {
isProdMode() && isProdMode() &&
tryOnUnmounted(() => { tryOnUnmounted(() => {
drawerRef.value = null; drawer.value = null;
loadedRef.value = null; loaded.value = null;
dataTransferRef[unref(uidRef)] = null; dataTransferRef[unref(uid)] = null;
}); });
if (unref(loadedRef) && isProdMode() && drawerInstance === unref(drawerRef)) { if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) {
return; return;
} }
uidRef.value = uuid; uid.value = uuid;
drawerRef.value = drawerInstance; drawer.value = drawerInstance;
loadedRef.value = true; loaded.value = true;
drawerInstance.emitVisible = (visible: boolean, uid: number) => { drawerInstance.emitVisible = (visible: boolean, uid: number) => {
visibleData[uid] = visible; visibleData[uid] = visible;
@ -57,7 +57,7 @@ export function useDrawer(): UseDrawerReturnType {
} }
const getInstance = () => { const getInstance = () => {
const instance = unref(drawerRef); const instance = unref(drawer);
if (!instance) { if (!instance) {
error('useDrawer instance is undefined!'); error('useDrawer instance is undefined!');
} }
@ -70,7 +70,7 @@ export function useDrawer(): UseDrawerReturnType {
}, },
getVisible: computed((): boolean => { getVisible: computed((): boolean => {
return visibleData[~~unref(uidRef)]; return visibleData[~~unref(uid)];
}), }),
openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => { openDrawer: <T = any>(visible = true, data?: T, openOnSet = true): void => {
@ -80,15 +80,18 @@ export function useDrawer(): UseDrawerReturnType {
if (!data) return; if (!data) return;
if (openOnSet) { if (openOnSet) {
dataTransferRef[unref(uidRef)] = null; dataTransferRef[unref(uid)] = null;
dataTransferRef[unref(uidRef)] = toRaw(data); dataTransferRef[unref(uid)] = toRaw(data);
return; return;
} }
const equal = isEqual(toRaw(dataTransferRef[unref(uidRef)]), toRaw(data)); const equal = isEqual(toRaw(dataTransferRef[unref(uid)]), toRaw(data));
if (!equal) { if (!equal) {
dataTransferRef[unref(uidRef)] = toRaw(data); dataTransferRef[unref(uid)] = toRaw(data);
} }
}, },
closeDrawer: () => {
getInstance()?.setDrawerProps({ visible: false });
},
}; };
return [register, methods]; return [register, methods];
@ -99,8 +102,8 @@ export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
const currentInstance = getCurrentInstance(); const currentInstance = getCurrentInstance();
const uidRef = ref<string>(''); const uidRef = ref<string>('');
if (!currentInstance) { if (!getCurrentInstance()) {
error('useDrawerInner instance is undefined!'); throw new Error('useDrawerInner() can only be used inside setup() or functional components!');
} }
const getInstance = () => { const getInstance = () => {

View File

@ -1,4 +1,5 @@
import Dropdown from './src/Dropdown.vue'; import { withInstall } from '/@/utils';
import dropdown from './src/Dropdown.vue';
export * from './src/types'; export * from './src/typing';
export { Dropdown }; export const Dropdown = withInstall(dropdown);

View File

@ -1,12 +1,12 @@
<template> <template>
<a-dropdown :trigger="trigger" v-bind="$attrs"> <Dropdown :trigger="trigger" v-bind="$attrs">
<span> <span>
<slot></slot> <slot></slot>
</span> </span>
<template #overlay> <template #overlay>
<a-menu :selectedKeys="selectedKeys"> <Menu :selectedKeys="selectedKeys">
<template v-for="item in dropMenuList" :key="`${item.event}`"> <template v-for="item in dropMenuList" :key="`${item.event}`">
<a-menu-item <MenuItem
v-bind="getAttr(item.event)" v-bind="getAttr(item.event)"
@click="handleClickMenu(item)" @click="handleClickMenu(item)"
:disabled="item.disabled" :disabled="item.disabled"
@ -19,17 +19,17 @@
<Icon :icon="item.icon" v-if="item.icon" /> <Icon :icon="item.icon" v-if="item.icon" />
<span class="ml-1">{{ item.text }}</span> <span class="ml-1">{{ item.text }}</span>
</template> </template>
</a-menu-item> </MenuItem>
<a-menu-divider v-if="item.divider" :key="`d-${item.event}`" /> <MenuDivider v-if="item.divider" :key="`d-${item.event}`" />
</template> </template>
</a-menu> </Menu>
</template> </template>
</a-dropdown> </Dropdown>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import type { DropMenu } from './types'; import type { DropMenu } from './typing';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { Dropdown, Menu, Popconfirm } from 'ant-design-vue'; import { Dropdown, Menu, Popconfirm } from 'ant-design-vue';
@ -38,10 +38,10 @@
export default defineComponent({ export default defineComponent({
name: 'BasicDropdown', name: 'BasicDropdown',
components: { components: {
[Dropdown.name]: Dropdown, Dropdown,
[Menu.name]: Menu, Menu,
[Menu.Item.name]: Menu.Item, MenuItem: Menu.Item,
[Menu.Divider.name]: Menu.Divider, MenuDivider: Menu.Divider,
Icon, Icon,
Popconfirm, Popconfirm,
}, },
@ -53,7 +53,7 @@
* @type string[] * @type string[]
*/ */
trigger: { trigger: {
type: [Array] as PropType<string[]>, type: [Array] as PropType<('contextmenu' | 'click' | 'hover')[]>,
default: () => { default: () => {
return ['contextmenu']; return ['contextmenu'];
}, },
@ -75,6 +75,7 @@
emit('menuEvent', menu); emit('menuEvent', menu);
item.onClick?.(); item.onClick?.();
} }
return { return {
handleClickMenu, handleClickMenu,
getAttr: (key: string | number) => ({ key }), getAttr: (key: string | number) => ({ key }),

View File

@ -7,5 +7,3 @@ export interface DropMenu {
disabled?: boolean; disabled?: boolean;
divider?: boolean; divider?: boolean;
} }
// export type Trigger = 'click' | 'hover' | 'contextMenu';

View File

@ -1,8 +1,8 @@
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; import { withInstall } from '/@/utils';
import impExcel from './src/ImportExcel.vue';
export const ImpExcel = createAsyncComponent(() => import('./src/ImportExcel.vue')); import expExcelModal from './src/ExportExcelModal.vue';
export const ExpExcelModel = createAsyncComponent(() => import('./src/ExportExcelModel.vue'));
export * from './src/types';
export const ImpExcel = withInstall(impExcel);
export const ExpExcelModal = withInstall(expExcelModal);
export * from './src/typing';
export { jsonToSheetXlsx, aoaToSheetXlsx } from './src/Export2Excel'; export { jsonToSheetXlsx, aoaToSheetXlsx } from './src/Export2Excel';

View File

@ -1,10 +1,11 @@
import xlsx from 'xlsx'; import xlsx from 'xlsx';
import type { WorkBook } from 'xlsx'; import type { WorkBook } from 'xlsx';
import type { JsonToSheet, AoAToSheet } from './types'; import type { JsonToSheet, AoAToSheet } from './typing';
const { utils, writeFile } = xlsx; const { utils, writeFile } = xlsx;
const DEF_FILE_NAME = 'excel-list.xlsx'; const DEF_FILE_NAME = 'excel-list.xlsx';
export function jsonToSheetXlsx<T = any>({ export function jsonToSheetXlsx<T = any>({
data, data,
header, header,

View File

@ -14,7 +14,7 @@
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { ExportModalResult } from './types'; import type { ExportModalResult } from './typing';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal'; import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, FormSchema, useForm } from '/@/components/Form/index'; import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';

View File

@ -16,7 +16,7 @@
import { defineComponent, ref, unref } from 'vue'; import { defineComponent, ref, unref } from 'vue';
import XLSX from 'xlsx'; import XLSX from 'xlsx';
import type { ExcelData } from './types'; import type { ExcelData } from './typing';
export default defineComponent({ export default defineComponent({
name: 'ImportExcel', name: 'ImportExcel',
emits: ['success', 'error'], emits: ['success', 'error'],

View File

@ -6,10 +6,6 @@ export interface ExcelData<T = any> {
meta: { sheetName: string }; meta: { sheetName: string };
} }
// export interface ImportProps {
// beforeUpload: (file: File) => boolean;
// }
export interface JsonToSheet<T = any> { export interface JsonToSheet<T = any> {
data: T[]; data: T[];
header?: T; header?: T;

View File

@ -1,8 +1,4 @@
import type { App } from 'vue'; import { withInstall } from '/@/utils';
import flowChart from './src/FlowChart.vue'; import flowChart from './src/FlowChart.vue';
export const FlowChart = Object.assign(flowChart, { export const FlowChart = withInstall(flowChart);
install(app: App) {
app.component(flowChart.name, flowChart);
},
});

View File

@ -9,23 +9,20 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import type { Definition } from '@logicflow/core'; import type { Definition } from '@logicflow/core';
import { defineComponent, ref, onMounted, unref, nextTick, computed, watch } from 'vue'; import { defineComponent, ref, onMounted, unref, nextTick, computed, watch } from 'vue';
import FlowChartToolbar from './FlowChartToolbar.vue'; import FlowChartToolbar from './FlowChartToolbar.vue';
import LogicFlow from '@logicflow/core'; import LogicFlow from '@logicflow/core';
import { Snapshot, BpmnElement, Menu, DndPanel } from '@logicflow/extension'; import { Snapshot, BpmnElement, Menu, DndPanel, SelectionSelect } from '@logicflow/extension';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { useAppStore } from '/@/store/modules/app'; import { useAppStore } from '/@/store/modules/app';
import { createFlowChartContext } from './useFlowContext'; import { createFlowChartContext } from './useFlowContext';
import { toLogicFlowData } from './adpterForTurbo'; import { toLogicFlowData } from './adpterForTurbo';
import { useModal, BasicModal } from '/@/components/Modal'; import { useModal, BasicModal } from '/@/components/Modal';
import { JsonPreview } from '/@/components/CodeEditor'; import { JsonPreview } from '/@/components/CodeEditor';
import { configDefaultDndPanel } from './config';
import '@logicflow/core/dist/style/index.css'; import '@logicflow/core/dist/style/index.css';
import '@logicflow/extension/lib/style/index.css'; import '@logicflow/extension/lib/style/index.css';
export default defineComponent({ export default defineComponent({
name: 'FlowChart', name: 'FlowChart',
components: { BasicModal, FlowChartToolbar, JsonPreview }, components: { BasicModal, FlowChartToolbar, JsonPreview },
@ -44,6 +41,9 @@
type: Boolean, type: Boolean,
default: true, default: true,
}, },
patternItems: {
type: Array,
},
}, },
setup(props) { setup(props) {
const lfElRef = ref<ElRef>(null); const lfElRef = ref<ElRef>(null);
@ -104,6 +104,7 @@
if (!lfEl) { if (!lfEl) {
return; return;
} }
LogicFlow.use(DndPanel);
// Canvas configuration // Canvas configuration
LogicFlow.use(Snapshot); LogicFlow.use(Snapshot);
@ -111,14 +112,16 @@
LogicFlow.use(BpmnElement); LogicFlow.use(BpmnElement);
// Start the right-click menu // Start the right-click menu
LogicFlow.use(Menu); LogicFlow.use(Menu);
LogicFlow.use(DndPanel); LogicFlow.use(SelectionSelect);
lfInstance.value = new LogicFlow({ lfInstance.value = new LogicFlow({
...unref(getFlowOptions), ...unref(getFlowOptions),
container: lfEl, container: lfEl,
}); });
unref(lfInstance)?.setDefaultEdgeType('line'); const lf = unref(lfInstance)!;
lf?.setDefaultEdgeType('line');
onRender(); onRender();
lf?.setPatternItems(props.patternItems || configDefaultDndPanel(lf));
} }
async function onRender() { async function onRender() {
@ -137,7 +140,6 @@
return; return;
} }
graphData.value = unref(lf).getGraphData(); graphData.value = unref(lf).getGraphData();
openModal(); openModal();
} }

View File

@ -1,6 +1,6 @@
<template> <template>
<div :class="`${prefixCls}-toolbar`" class="flex items-center px-2 py-1"> <div :class="`${prefixCls}-toolbar`" class="flex items-center px-2 py-1">
<template v-for="(item, index) in toolbarItemList" :key="item.type || index"> <template v-for="item in toolbarItemList" :key="item.type">
<Tooltip placement="bottom" v-bind="item.disabled ? { visible: false } : {}"> <Tooltip placement="bottom" v-bind="item.disabled ? { visible: false } : {}">
<template #title>{{ item.tooltip }}</template> <template #title>{{ item.tooltip }}</template>
<span :class="`${prefixCls}-toolbar__icon`" v-if="item.icon" @click="onControl(item)"> <span :class="`${prefixCls}-toolbar__icon`" v-if="item.icon" @click="onControl(item)">

View File

@ -53,3 +53,44 @@ export const BpmnNode = [
class: 'bpmn-user', class: 'bpmn-user',
}, },
]; ];
export function configDefaultDndPanel(lf) {
return [
{
text: '选区',
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAOVJREFUOBGtVMENwzAIjKP++2026ETdpv10iy7WFbqFyyW6GBywLCv5gI+Dw2Bluj1znuSjhb99Gkn6QILDY2imo60p8nsnc9bEo3+QJ+AKHfMdZHnl78wyTnyHZD53Zzx73MRSgYvnqgCUHj6gwdck7Zsp1VOrz0Uz8NbKunzAW+Gu4fYW28bUYutYlzSa7B84Fh7d1kjLwhcSdYAYrdkMQVpsBr5XgDGuXwQfQr0y9zwLda+DUYXLaGKdd2ZTtvbolaO87pdo24hP7ov16N0zArH1ur3iwJpXxm+v7oAJNR4JEP8DoAuSFEkYH7cAAAAASUVORK5CYII=',
callback: () => {
lf.updateEditConfig({
stopMoveGraph: true,
});
},
},
{
type: 'circle',
text: '开始',
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAnBJREFUOBGdVL1rU1EcPfdGBddmaZLiEhdx1MHZQXApraCzQ7GKLgoRBxMfcRELuihWKcXFRcEWF8HBf0DdDCKYRZpnl7p0svLe9Zzbd29eQhTbC8nv+9zf130AT63jvooOGS8Vf9Nt5zxba7sXQwODfkWpkbjTQfCGUd9gIp3uuPP8bZ946g56dYQvnBg+b1HB8VIQmMFrazKcKSvFW2dQTxJnJdQ77urmXWOMBCmXM2Rke4S7UAW+/8ywwFoewmBps2tu7mbTdp8VMOkIRAkKfrVawalJTtIliclFbaOBqa0M2xImHeVIfd/nKAfVq/LGnPss5Kh00VEdSzfwnBXPUpmykNss4lUI9C1ga+8PNrBD5YeqRY2Zz8PhjooIbfJXjowvQJBqkmEkVnktWhwu2SM7SMx7Cj0N9IC0oQXRo8xwAGzQms+xrB/nNSUWVveI48ayrFGyC2+E2C+aWrZHXvOuz+CiV6iycWe1Rd1Q6+QUG07nb5SbPrL4426d+9E1axKjY3AoRrlEeSQo2Eu0T6BWAAr6COhTcWjRaYfKG5csnvytvUr/WY4rrPMB53Uo7jZRjXaG6/CFfNMaXEu75nG47X+oepU7PKJvvzGDY1YLSKHJrK7vFUwXKkaxwhCW3u+sDFMVrIju54RYYbFKpALZAo7sB6wcKyyrd+aBMryMT2gPyD6GsQoRFkGHr14TthZni9ck0z+Pnmee460mHXbRAypKNy3nuMdrWgVKj8YVV8E7PSzp1BZ9SJnJAsXdryw/h5ctboUVi4AFiCd+lQaYMw5z3LGTBKjLQOeUF35k89f58Vv/tGh+l+PE/wG0rgfIUbZK5AAAAABJRU5ErkJggg==',
},
{
type: 'rect',
text: '用户任务',
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0D+g1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQ+BVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeA+YeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQy+JjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTby+r31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0+HAd2GelJsZz1OJegCxqzl+CLVHa/IibuHeJ1HAKzhuDR+ymNaRFM+4jU6UWKXorRmbyqkq/D76FffevwdCp+jN3UAN/C9JRVTDuOxC/oh+EdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg==',
cls: 'important-node',
},
{
type: 'rect',
text: '系统任务',
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0D+g1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQ+BVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeA+YeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQy+JjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTby+r31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0+HAd2GelJsZz1OJegCxqzl+CLVHa/IibuHeJ1HAKzhuDR+ymNaRFM+4jU6UWKXorRmbyqkq/D76FffevwdCp+jN3UAN/C9JRVTDuOxC/oh+EdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg==',
cls: 'import_icon',
},
{
type: 'diamond',
text: '条件判断',
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAAHeEJUAAAAABGdBTUEAALGPC/xhBQAAAvVJREFUOBGNVEFrE0EU/mY3bQoiFlOkaUJrQUQoWMGePLX24EH0IIoHKQiCV0G8iE1covgLiqA/QTzVm1JPogc9tIJYFaQtlhQxqYjSpunu+L7JvmUTU3AgmTfvffPNN++9WSA1DO182f6xwILzD5btfAoQmwL5KJEwiQyVbSVZ0IgRyV6PTpIJ81E5ZvqfHQR0HUOBHW4L5Et2kQ6Zf7iAOhTFAA8s0pEP7AXO1uAA52SbqGk6h/6J45LaLhO64ByfcUzM39V7ZiAdS2yCePPEIQYvTUHqM/n7dgQNfBKWPjpF4ISk8q3J4nB11qw6X8l+FsF3EhlkEMfrjIer3wJTLwS2aCNcj4DbGxXTw00JmAuO+Ni6bBxVUCvS5d9aa04+so4pHW5jLTywuXAL7jJ+D06sl82Sgl2JuVBQn498zkc2bGKxULHjCnSMadBKYDYYHAtsby1EQ5lNGrQd4Y3v4Zo0XdGEmDno46yCM9Tk+RiJmUYHS/aXHPNTcjxcbTFna000PFJHIVZ5lFRqRpJWk9/+QtlOUYJj9HG5pVFEU7zqIYDVsw2s+AJaD8wTd2umgSCCyUxgGsS1Y6TBwXQQTFuZaHcd8gAGioE90hlsY+wMcs30RduYtxanjMGal8H5dMW67dmT1JFtYUEe8LiQLRsPZ6IIc7A4J5tqco3T0pnv/4u0kyzrYUq7gASuEyI8VXKvB9Odytv6jS/PNaZBln0nioJG/AVQRZvApOdhjj3Jt8QC8Im09SafwdBdvIpztpxWxpeKCC+EsFdS8DCyuCn2munFpL7ctHKp+Xc5cMybeIyMAN33SPL3ZR9QV1XVwLyzHm6Iv0/yeUuUb7PPlZC4D4HZkeu6dpF4v9j9MreGtMbxMMRLIcjJic9yHi7WQ3yVKzZVWUr5UrViJvn1FfUlwe/KYVfYyWRLSGNu16hR01U9IacajXPei0wx/5BqgInvJN+MMNtNme7ReU9SBbgntovn0kKHpFg7UogZvaZiOue/q1SBo9ktHzQAAAAASUVORK5CYII=',
},
{
type: 'circle',
text: '结束',
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAA1BJREFUOBFtVE1IVUEYPXOf+tq40Y3vPcmFIdSjIorWoRG0ERWUgnb5FwVhYQSl72oUoZAboxKNFtWiwKRN0M+jpfSzqJAQclHo001tKkjl3emc8V69igP3znzfnO/M9zcDcKT67azmjYWTwl9Vn7Vumeqzj1DVb6cleQY4oAVnIOPb+mKAGxQmKI5CWNJ2aLPatxWa3aB9K7/fB+/Z0jUF6TmMlFLQqrkECWQzOZxYGjTlOl8eeKaIY5yHnFn486xBustDjWT6dG7pmjHOJd+33t0iitTPkK6tEvjxq4h2MozQ6WFSX/LkDUGfFwfhEZj1Auz/U4pyAi5Sznd7uKzznXeVHlI/Aywmk6j7fsUsEuCGADrWARXXwjxWQsUbIupDHJI7kF5dRktg0eN81IbiZXiTESic50iwS+t1oJgL83jAiBupLDCQqwziaWSoAFSeIR3P5Xv5az00wyIn35QRYTwdSYbz8pH8fxUUAtxnFvYmEmgI0wYXUXcCCSpeEVpXlsRhBnCEATxWylL9+EKCAYhe1NGstUa6356kS9NVvt3DU2fd+Wtbm/+lSbylJqsqkSm9CRhvoJVlvKPvF1RKY/FcPn5j4UfIMLn8D4UYb54BNsilTDXKnF4CfTobA0FpoW/LSp306wkXM+XaOJhZaFkcNM82ASNAWMrhrUbRfmyeI1FvRBTpN06WKxa9BK0o2E4Pd3zfBBEwPsv9sQBnmLVbLEIZ/Xe9LYwJu/Er17W6HYVBc7vmuk0xUQ+pqxdom5Fnp55SiytXLPYoMXNM4u4SNSCFWnrVIzKG3EGyMXo6n/BQOe+bX3FClY4PwydVhthOZ9NnS+ntiLh0fxtlUJHAuGaFoVmttpVMeum0p3WEXbcll94l1wM/gZ0Ccczop77VvN2I7TlsZCsuXf1WHvWEhjO8DPtyOVg2/mvK9QqboEth+7pD6NUQC1HN/TwvydGBARi9MZSzLE4b8Ru3XhX2PBxf8E1er2A6516o0w4sIA+lwURhAON82Kwe2iDAC1Watq4XHaGQ7skLcFOtI5lDxuM2gZe6WFIotPAhbaeYlU4to5cuarF1QrcZ/lwrLaCJl66JBocYZnrNlvm2+MBCTmUymPrYZVbjdlr/BxlMjmNmNI3SAAAAAElFTkSuQmCC',
},
];
}

View File

@ -1,12 +1,12 @@
<template> <template>
<Form <Form
v-bind="{ ...$attrs, ...$props, ...getProps }" v-bind="getBindValue"
:class="getFormClass" :class="getFormClass"
ref="formElRef" ref="formElRef"
:model="formModel" :model="formModel"
@keypress.enter="handleEnterPress" @keypress.enter="handleEnterPress"
> >
<Row v-bind="{ ...getRow }"> <Row v-bind="getRow">
<slot name="formHeader"></slot> <slot name="formHeader"></slot>
<template v-for="schema in getSchema" :key="schema.field"> <template v-for="schema in getSchema" :key="schema.field">
<FormItem <FormItem
@ -39,7 +39,7 @@
<script lang="ts"> <script lang="ts">
import type { FormActionType, FormProps, FormSchema } from './types/form'; import type { FormActionType, FormProps, FormSchema } from './types/form';
import type { AdvanceState } from './types/hooks'; import type { AdvanceState } from './types/hooks';
import type { CSSProperties, Ref } from 'vue'; import type { Ref } from 'vue';
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue'; import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue';
import { Form, Row } from 'ant-design-vue'; import { Form, Row } from 'ant-design-vue';
@ -69,7 +69,7 @@
components: { FormItem, Form, Row, FormAction }, components: { FormItem, Form, Row, FormAction },
props: basicProps, props: basicProps,
emits: ['advanced-change', 'reset', 'submit', 'register'], emits: ['advanced-change', 'reset', 'submit', 'register'],
setup(props, { emit }) { setup(props, { emit, attrs }) {
const formModel = reactive<Recordable>({}); const formModel = reactive<Recordable>({});
const modalFn = useModalContext(); const modalFn = useModalContext();
@ -103,7 +103,7 @@
}); });
// Get uniform row style and Row configuration for the entire form // Get uniform row style and Row configuration for the entire form
const getRow = computed((): CSSProperties | RowProps => { const getRow = computed((): RowProps => {
const { baseRowStyle = {}, rowProps } = unref(getProps); const { baseRowStyle = {}, rowProps } = unref(getProps);
return { return {
style: baseRowStyle, style: baseRowStyle,
@ -111,6 +111,10 @@
}; };
}); });
const getBindValue = computed(
() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable)
);
const getSchema = computed((): FormSchema[] => { const getSchema = computed((): FormSchema[] => {
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any); const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
for (const schema of schemas) { for (const schema of schemas) {
@ -260,6 +264,7 @@
}); });
return { return {
getBindValue,
handleToggleAdvanced, handleToggleAdvanced,
handleEnterPress, handleEnterPress,
formModel, formModel,

View File

@ -25,8 +25,7 @@
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { useRuleFormItem } from '/@/hooks/component/useFormItem'; import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { useAttrs } from '/@/hooks/core/useAttrs'; import { useAttrs } from '/@/hooks/core/useAttrs';
import { get } from 'lodash-es'; import { get, omit } from 'lodash-es';
import { LoadingOutlined } from '@ant-design/icons-vue'; import { LoadingOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
@ -83,6 +82,7 @@
prev.push({ prev.push({
label: next[labelField], label: next[labelField],
value: numberToString ? `${value}` : value, value: numberToString ? `${value}` : value,
...omit(next, [labelField, valueField]),
}); });
} }
return prev; return prev;

Some files were not shown because too many files have changed in this diff Show More