chore: merge branch 'main' of github.com:anncwb/vue-vben-admin into main

This commit is contained in:
vben 2021-06-20 22:01:31 +08:00
commit b49e2ed35d
247 changed files with 8303 additions and 3383 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',

View File

@ -18,12 +18,13 @@ jobs:
run: | run: |
sed -i 's#VITE_PUBLIC_PATH\s*=.*#VITE_PUBLIC_PATH = /thin/next/#g' ./.env.production sed -i 's#VITE_PUBLIC_PATH\s*=.*#VITE_PUBLIC_PATH = /thin/next/#g' ./.env.production
sed -i "s#VITE_BUILD_COMPRESS\s*=.*#VITE_BUILD_COMPRESS = 'gzip'#g" ./.env.production sed -i "s#VITE_BUILD_COMPRESS\s*=.*#VITE_BUILD_COMPRESS = 'gzip'#g" ./.env.production
sed -i "s#VITE_DROP_CONSOLE\s*=.*#VITE_DROP_CONSOLE = true#g" ./.env.production
cat ./.env.production cat ./.env.production
- name: use Node.js 14 - name: use Node.js 15
uses: actions/setup-node@v2.1.2 uses: actions/setup-node@v2.1.2
with: with:
node-version: '14.x' node-version: '15.x'
- name: Get yarn cache - name: Get yarn cache
id: yarn-cache id: yarn-cache

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,110 @@
# [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) ## [2.4.2](https://github.com/anncwb/vue-vben-admin/compare/v2.4.0...v2.4.2) (2021-06-09)
### Bug Fixes ### Bug Fixes

View File

@ -1,3 +1,44 @@
## 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) ## 2.4.2(2021-06-10)
### ✨ Refactor ### ✨ Refactor

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.2", "version": "2.5.0",
"author": { "author": {
"name": "vben", "name": "vben",
"email": "anncwb@126.com", "email": "anncwb@126.com",
@ -19,11 +19,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",
@ -32,10 +34,10 @@
"postinstall": "npm run install:husky" "postinstall": "npm run install:husky"
}, },
"dependencies": { "dependencies": {
"@iconify/iconify": "^2.0.1", "@iconify/iconify": "^2.0.2",
"@vueuse/core": "^5.0.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",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
"echarts": "^5.1.2", "echarts": "^5.1.2",
@ -43,83 +45,91 @@
"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",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"sortablejs": "^1.13.0", "sortablejs": "^1.13.0",
"vue": "3.0.11", "vue": "3.0.11",
"vue-i18n": "9.1.6", "vue-i18n": "9.1.6",
"vue-router": "^4.0.8", "vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.9",
"vue-types": "^3.0.2" "vue-types": "^3.0.2"
}, },
"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.354", "@iconify/json": "^1.1.358",
"@purge-icons/generated": "^0.7.0", "@purge-icons/generated": "^0.7.0",
"@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.2", "@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.1", "@typescript-eslint/eslint-plugin": "^4.27.0",
"@typescript-eslint/parser": "^4.26.1", "@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",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dotenv": "^10.0.0", "dotenv": "^10.0.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.1", "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-visualizer": "5.5.0", "rollup-plugin-visualizer": "5.5.0",
"stylelint": "^13.13.1", "stylelint": "^13.13.1",
"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.3", "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.1", "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.3",
"vue-eslint-parser": "^7.6.0", "vue-eslint-parser": "^7.6.0",
"vue-tsc": "^0.1.7" "vue-tsc": "^0.1.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.1" "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

@ -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

@ -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"
@ -14,7 +14,7 @@
<template #prefix> <template #prefix>
<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,7 +59,6 @@
<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';
@ -75,7 +74,7 @@
export default defineComponent({ export default defineComponent({
name: 'AppSearchModal', name: 'AppSearchModal',
components: { Icon, SearchOutlined, AppSearchFooter, Input }, components: { Icon, SearchOutlined, AppSearchFooter },
directives: { directives: {
clickOutside, clickOutside,
}, },

View File

@ -1,13 +1,12 @@
<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 { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { useRuleFormItem } from '/@/hooks/component/useFormItem'; import { useRuleFormItem } from '/@/hooks/component/useFormItem';
@ -24,7 +23,7 @@
export default defineComponent({ export default defineComponent({
name: 'CountDownInput', name: 'CountDownInput',
components: { [Input.name]: Input, CountButton }, components: { CountButton },
inheritAttrs: false, inheritAttrs: false,
props, props,
setup(props) { setup(props) {

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: Options = { const defaultOptions: Options = {
aspectRatio: 16 / 9, aspectRatio: 1,
zoomable: true, zoomable: true,
zoomOnTouch: true, zoomOnTouch: true,
zoomOnWheel: true, zoomOnWheel: true,
@ -43,40 +43,32 @@
rotatable: true, rotatable: true,
}; };
const props = {
src: { type: String, required: true },
alt: { type: String },
circled: { type: Boolean, default: false },
realTimePreview: { type: Boolean, default: true },
height: { type: [String, Number], default: '360px' },
crossorigin: {
type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>,
default: undefined,
},
imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
options: { type: Object as PropType<Options>, default: () => ({}) },
};
export default defineComponent({ export default defineComponent({
name: 'CropperImage', name: 'CropperImage',
props: { props,
src: { emits: ['cropend', 'ready', 'cropendError'],
type: String, setup(props, { attrs, emit }) {
required: true, const imgElRef = ref<ElRef<HTMLImageElement>>();
}, const cropper = ref<Nullable<Cropper>>();
alt: {
type: String,
},
height: {
type: [String, Number],
default: '360px',
},
crossorigin: {
type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>,
default: undefined,
},
imageStyle: {
type: Object as PropType<CSSProperties>,
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);
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,
@ -85,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) {
@ -99,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.readAsDataURL(blob);
fileReader.onloadend = (e) => { fileReader.onloadend = (e) => {
ctx.emit('cropperedInfo', { emit('cropend', {
imgBase64: e.target?.result ?? '', 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';
@ -167,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

@ -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

@ -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

@ -26,7 +26,6 @@
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, omit } 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';

View File

@ -40,13 +40,11 @@
<script lang="ts"> <script lang="ts">
import type { ColEx } from '../types/index'; import type { ColEx } from '../types/index';
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import { defineComponent, computed, PropType } from 'vue'; import { defineComponent, computed, PropType } from 'vue';
import { Form, Col } from 'ant-design-vue'; import { Form, Col } from 'ant-design-vue';
import { Button } from '/@/components/Button'; import { Button } from '/@/components/Button';
import { BasicArrow } from '/@/components/Basic/index'; import { BasicArrow } from '/@/components/Basic/index';
import { useFormContext } from '../hooks/useFormContext'; import { useFormContext } from '../hooks/useFormContext';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';

View File

@ -4,17 +4,14 @@
import type { FormSchema } from '../types/form'; import type { FormSchema } from '../types/form';
import type { ValidationRule } from 'ant-design-vue/lib/form/Form'; import type { ValidationRule } from 'ant-design-vue/lib/form/Form';
import type { TableActionType } from '/@/components/Table'; import type { TableActionType } from '/@/components/Table';
import { defineComponent, computed, unref, toRefs } from 'vue'; import { defineComponent, computed, unref, toRefs } from 'vue';
import { Form, Col } from 'ant-design-vue'; import { Form, Col } from 'ant-design-vue';
import { componentMap } from '../componentMap'; import { componentMap } from '../componentMap';
import { BasicHelp } from '/@/components/Basic'; import { BasicHelp } from '/@/components/Basic';
import { isBoolean, isFunction, isNull } from '/@/utils/is'; import { isBoolean, isFunction, isNull } from '/@/utils/is';
import { getSlot } from '/@/utils/helper/tsxHelper'; import { getSlot } from '/@/utils/helper/tsxHelper';
import { createPlaceholderMessage, setComponentRuleType } from '../helper'; import { createPlaceholderMessage, setComponentRuleType } from '../helper';
import { upperFirst, cloneDeep } from 'lodash-es'; import { upperFirst, cloneDeep } from 'lodash-es';
import { useItemLabelWidth } from '../hooks/useLabelWidth'; import { useItemLabelWidth } from '../hooks/useLabelWidth';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
@ -91,7 +88,6 @@
if (isBoolean(dynamicDisabled)) { if (isBoolean(dynamicDisabled)) {
disabled = dynamicDisabled; disabled = dynamicDisabled;
} }
if (isFunction(dynamicDisabled)) { if (isFunction(dynamicDisabled)) {
disabled = dynamicDisabled(unref(getValues)); disabled = dynamicDisabled(unref(getValues));
} }
@ -229,9 +225,10 @@
const eventKey = `on${upperFirst(changeEvent)}`; const eventKey = `on${upperFirst(changeEvent)}`;
const on = { const on = {
[eventKey]: (e: Nullable<Recordable>) => { [eventKey]: (...args: Nullable<Recordable>[]) => {
const [e] = args;
if (propsData[eventKey]) { if (propsData[eventKey]) {
propsData[eventKey](e); propsData[eventKey](...args);
} }
const target = e ? e.target : null; const target = e ? e.target : null;
const value = target ? (isCheck ? target.checked : target.value) : e; const value = target ? (isCheck ? target.checked : target.value) : e;
@ -276,7 +273,6 @@
: { : {
default: () => renderComponentContent, default: () => renderComponentContent,
}; };
return <Comp {...compAttr}>{compSlot}</Comp>; return <Comp {...compAttr}>{compSlot}</Comp>;
} }
@ -317,7 +313,6 @@
}; };
const showSuffix = !!suffix; const showSuffix = !!suffix;
const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix; const getSuffix = isFunction(suffix) ? suffix(unref(getValues)) : suffix;
return ( return (
@ -338,16 +333,18 @@
</Form.Item> </Form.Item>
); );
} }
return () => { return () => {
const { colProps = {}, colSlot, renderColContent, component } = props.schema; const { colProps = {}, colSlot, renderColContent, component } = props.schema;
if (!componentMap.has(component)) return null; if (!componentMap.has(component)) {
return null;
}
const { baseColProps = {} } = props.formProps; const { baseColProps = {} } = props.formProps;
const realColProps = { ...baseColProps, ...colProps }; const realColProps = { ...baseColProps, ...colProps };
const { isIfShow, isShow } = getShow(); const { isIfShow, isShow } = getShow();
const values = unref(getValues); const values = unref(getValues);
const getContent = () => { const getContent = () => {
return colSlot return colSlot
? getSlot(slots, colSlot, values) ? getSlot(slots, colSlot, values)

View File

@ -1,7 +1,6 @@
<!-- <!--
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component * @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
--> -->
<template> <template>
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid"> <RadioGroup v-bind="attrs" v-model:value="state" button-style="solid">
<template v-for="item in getOptions" :key="`${item.value}`"> <template v-for="item in getOptions" :key="`${item.value}`">
@ -17,6 +16,7 @@
import { isString } from '/@/utils/is'; import { isString } 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';
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean }; type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
type RadioItem = string | OptionsItem; type RadioItem = string | OptionsItem;
@ -39,6 +39,7 @@
const attrs = useAttrs(); const attrs = useAttrs();
// Embedded in the form, just use the hook binding to perform form verification // Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props); const [state] = useRuleFormItem(props);
// Processing options value // Processing options value
const getOptions = computed((): OptionsItem[] => { const getOptions = computed((): OptionsItem[] => {
const { options } = props; const { options } = props;

View File

@ -2,10 +2,8 @@ import type { ColEx } from '../types';
import type { AdvanceState } from '../types/hooks'; import type { AdvanceState } from '../types/hooks';
import type { ComputedRef, Ref } from 'vue'; import type { ComputedRef, Ref } from 'vue';
import type { FormProps, FormSchema } from '../types/form'; import type { FormProps, FormSchema } from '../types/form';
import { computed, unref, watch } from 'vue'; import { computed, unref, watch } from 'vue';
import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is'; import { isBoolean, isFunction, isNumber, isObject } from '/@/utils/is';
import { useBreakpoint } from '/@/hooks/event/useBreakpoint'; import { useBreakpoint } from '/@/hooks/event/useBreakpoint';
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';

View File

@ -16,16 +16,22 @@ export async function useAutoFocus({
isInitedDefault, isInitedDefault,
}: UseAutoFocusContext) { }: UseAutoFocusContext) {
watchEffect(async () => { watchEffect(async () => {
if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) return; if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) {
return;
}
await nextTick(); await nextTick();
const schemas = unref(getSchema); const schemas = unref(getSchema);
const formEl = unref(formElRef); const formEl = unref(formElRef);
const el = (formEl as any)?.$el as HTMLElement; const el = (formEl as any)?.$el as HTMLElement;
if (!formEl || !el || !schemas || schemas.length === 0) return; if (!formEl || !el || !schemas || schemas.length === 0) {
return;
}
const firstItem = schemas[0]; const firstItem = schemas[0];
// Only open when the first form item is input type // Only open when the first form item is input type
if (!firstItem.component.includes('Input')) return; if (!firstItem.component.includes('Input')) {
return;
}
const inputEl = el.querySelector('.ant-row:first-child input') as Nullable<HTMLInputElement>; const inputEl = el.querySelector('.ant-row:first-child input') as Nullable<HTMLInputElement>;
if (!inputEl) return; if (!inputEl) return;

View File

@ -1,12 +1,10 @@
import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
import { isProdMode } from '/@/utils/env';
import { error } from '/@/utils/log';
import { getDynamicProps } from '/@/utils';
import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form'; import type { FormProps, FormActionType, UseFormReturnType, FormSchema } from '../types/form';
import type { NamePath } from 'ant-design-vue/lib/form/interface'; import type { NamePath } from 'ant-design-vue/lib/form/interface';
import type { DynamicProps } from '/#/utils'; import type { DynamicProps } from '/#/utils';
import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
import { isProdMode } from '/@/utils/env';
import { error } from '/@/utils/log';
import { getDynamicProps } from '/@/utils';
export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>; export declare type ValidateFields = (nameList?: NamePath[]) => Promise<Recordable>;

View File

@ -1,9 +1,7 @@
import type { ComputedRef, Ref } from 'vue'; import type { ComputedRef, Ref } from 'vue';
import type { FormProps, FormSchema, FormActionType } from '../types/form'; import type { FormProps, FormSchema, FormActionType } from '../types/form';
import type { NamePath } from 'ant-design-vue/lib/form/interface'; import type { NamePath } from 'ant-design-vue/lib/form/interface';
import { unref, toRaw } from 'vue'; import { unref, toRaw } from 'vue';
import { isArray, isFunction, isObject, isString } from '/@/utils/is'; import { isArray, isFunction, isObject, isString } from '/@/utils/is';
import { deepMerge } from '/@/utils'; import { deepMerge } from '/@/utils';
import { dateItemType, handleInputNumberValue } from '../helper'; import { dateItemType, handleInputNumberValue } from '../helper';

View File

@ -1,10 +1,8 @@
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is'; import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '/@/utils/is';
import { dateUtil } from '/@/utils/dateUtil'; import { dateUtil } from '/@/utils/dateUtil';
import { unref } from 'vue'; import { unref } from 'vue';
import type { Ref, ComputedRef } from 'vue'; import type { Ref, ComputedRef } from 'vue';
import type { FormProps, FormSchema } from '../types/form'; import type { FormProps, FormSchema } from '../types/form';
import { set } from 'lodash-es'; import { set } from 'lodash-es';
interface UseFormValuesContext { interface UseFormValuesContext {

View File

@ -1,7 +1,6 @@
import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface'; import type { NamePath, RuleObject } from 'ant-design-vue/lib/form/interface';
import type { VNode } from 'vue'; import type { VNode } from 'vue';
import type { ButtonProps as AntdButtonProps } from 'ant-design-vue/es/button/buttonTypes'; import type { ButtonProps as AntdButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import type { FormItem } from './formItem'; import type { FormItem } from './formItem';
import type { ColEx, ComponentType } from './index'; import type { ColEx, ComponentType } from './index';
import type { TableActionType } from '/@/components/Table/src/types/table'; import type { TableActionType } from '/@/components/Table/src/types/table';

View File

@ -90,9 +90,7 @@ export type ComponentType =
| 'InputCountDown' | 'InputCountDown'
| 'Select' | 'Select'
| 'ApiSelect' | 'ApiSelect'
| 'SelectOptGroup'
| 'TreeSelect' | 'TreeSelect'
| 'Transfer'
| 'RadioButtonGroup' | 'RadioButtonGroup'
| 'RadioGroup' | 'RadioGroup'
| 'Checkbox' | 'Checkbox'

View File

@ -5,10 +5,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { PropType } from 'vue'; import { PropType } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { Spin } from 'ant-design-vue'; import { Spin } from 'ant-design-vue';
import { SizeEnum } from '/@/enums/sizeEnum'; import { SizeEnum } from '/@/enums/sizeEnum';
export default defineComponent({ export default defineComponent({

View File

@ -1,5 +1,5 @@
import { VNode, defineComponent } from 'vue'; import { VNode, defineComponent } from 'vue';
import type { LoadingProps } from './types'; import type { LoadingProps } from './typing';
import { createVNode, render, reactive, h } from 'vue'; import { createVNode, render, reactive, h } from 'vue';
import Loading from './Loading.vue'; import Loading from './Loading.vue';
@ -20,8 +20,8 @@ export function createLoading(props?: Partial<LoadingProps>, target?: HTMLElemen
vm = createVNode(LoadingWrap); vm = createVNode(LoadingWrap);
// TODO fix https://github.com/anncwb/vue-vben-admin/issues/438
if (wait) { if (wait) {
// TODO fix https://github.com/anncwb/vue-vben-admin/issues/438
setTimeout(() => { setTimeout(() => {
render(vm, document.createElement('div')); render(vm, document.createElement('div'));
}, 0); }, 0);

View File

@ -1,6 +1,6 @@
import { unref } from 'vue'; import { unref } from 'vue';
import { createLoading } from './createLoading'; import { createLoading } from './createLoading';
import type { LoadingProps } from './types'; import type { LoadingProps } from './typing';
import type { Ref } from 'vue'; import type { Ref } from 'vue';
export interface UseLoadingOptions { export interface UseLoadingOptions {

View File

@ -1,4 +1,5 @@
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent'; import { withInstall } from '/@/utils';
export const MarkDown = createAsyncComponent(() => import('./src/Markdown.vue')); import markDown from './src/Markdown.vue';
export * from './src/types'; export const MarkDown = withInstall(markDown);
export * from './src/typing';

View File

@ -14,18 +14,17 @@
} from 'vue'; } from 'vue';
import Vditor from 'vditor'; import Vditor from 'vditor';
import 'vditor/dist/index.css'; import 'vditor/dist/index.css';
import { propTypes } from '/@/utils/propTypes';
import { useLocale } from '/@/locales/useLocale'; import { useLocale } from '/@/locales/useLocale';
import { useModalContext } from '../../Modal'; import { useModalContext } from '../../Modal';
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { useRootSetting } from '/@/hooks/setting/useRootSetting';
type Lang = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' | undefined; type Lang = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' | undefined;
export default defineComponent({ export default defineComponent({
inheritAttrs: false, inheritAttrs: false,
props: { props: {
height: propTypes.number.def(360), height: { type: Number, default: 360 },
value: propTypes.string.def(''), value: { type: String, default: '' },
}, },
emits: ['change', 'get'], emits: ['change', 'get'],
setup(props, { attrs, emit }) { setup(props, { attrs, emit }) {

View File

@ -1,7 +1,8 @@
import { withInstall } from '/@/utils';
import './src/index.less'; import './src/index.less';
import BasicModal from './src/BasicModal.vue'; import basicModal from './src/BasicModal.vue';
export { BasicModal }; export const BasicModal = withInstall(basicModal);
export { useModalContext } from './src/hooks/useModalContext'; export { useModalContext } from './src/hooks/useModalContext';
export { useModal, useModalInner } from './src/hooks/useModal'; export { useModal, useModalInner } from './src/hooks/useModal';
export * from './src/types'; export * from './src/typing';

View File

@ -1,5 +1,5 @@
<template> <template>
<Modal @cancel="handleCancel" v-bind="getBindValue"> <Modal v-bind="getBindValue">
<template #closeIcon v-if="!$slots.closeIcon"> <template #closeIcon v-if="!$slots.closeIcon">
<ModalClose <ModalClose
:canFullscreen="getProps.canFullscreen" :canFullscreen="getProps.canFullscreen"
@ -49,7 +49,7 @@
</Modal> </Modal>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { ModalProps, ModalMethods } from './types'; import type { ModalProps, ModalMethods } from './typing';
import { import {
defineComponent, defineComponent,
@ -62,26 +62,23 @@
getCurrentInstance, getCurrentInstance,
nextTick, nextTick,
} from 'vue'; } from 'vue';
import Modal from './components/Modal'; import Modal from './components/Modal';
import ModalWrapper from './components/ModalWrapper.vue'; import ModalWrapper from './components/ModalWrapper.vue';
import ModalClose from './components/ModalClose.vue'; import ModalClose from './components/ModalClose.vue';
import ModalFooter from './components/ModalFooter.vue'; import ModalFooter from './components/ModalFooter.vue';
import ModalHeader from './components/ModalHeader.vue'; import ModalHeader from './components/ModalHeader.vue';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { deepMerge } from '/@/utils'; import { deepMerge } from '/@/utils';
import { basicProps } from './props'; import { basicProps } from './props';
import { useFullScreen } from './hooks/useModalFullScreen'; import { useFullScreen } from './hooks/useModalFullScreen';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
export default defineComponent({ export default defineComponent({
name: 'BasicModal', name: 'BasicModal',
components: { Modal, ModalWrapper, ModalClose, ModalFooter, ModalHeader }, components: { Modal, ModalWrapper, ModalClose, ModalFooter, ModalHeader },
inheritAttrs: false, inheritAttrs: false,
props: basicProps, props: basicProps,
emits: ['visible-change', 'height-change', 'cancel', 'ok', 'register'], emits: ['visible-change', 'height-change', 'cancel', 'ok', 'register', 'update:visible'],
setup(props, { emit, attrs }) { setup(props, { emit, attrs }) {
const visibleRef = ref(false); const visibleRef = ref(false);
const propsRef = ref<Partial<ModalProps> | null>(null); const propsRef = ref<Partial<ModalProps> | null>(null);
@ -157,6 +154,7 @@
() => unref(visibleRef), () => unref(visibleRef),
(v) => { (v) => {
emit('visible-change', v); emit('visible-change', v);
emit('update:visible', v);
instance && modalMethods.emitVisible?.(v, instance.uid); instance && modalMethods.emitVisible?.(v, instance.uid);
nextTick(() => { nextTick(() => {
if (props.scrollTop && v && unref(modalWrapperRef)) { if (props.scrollTop && v && unref(modalWrapperRef)) {
@ -180,7 +178,7 @@
} }
visibleRef.value = false; visibleRef.value = false;
emit('cancel'); emit('cancel', e);
} }
/** /**
@ -188,13 +186,13 @@
*/ */
function setModalProps(props: Partial<ModalProps>): void { function setModalProps(props: Partial<ModalProps>): void {
// Keep the last setModalProps // Keep the last setModalProps
propsRef.value = deepMerge(unref(propsRef) || {}, props); propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
if (!Reflect.has(props, 'visible')) return; if (!Reflect.has(props, 'visible')) return;
visibleRef.value = !!props.visible; visibleRef.value = !!props.visible;
} }
function handleOk() { function handleOk(e: Event) {
emit('ok'); emit('ok', e);
} }
function handleHeightChange(height: string) { function handleHeightChange(height: string) {

View File

@ -20,7 +20,6 @@ export default defineComponent({
return () => { return () => {
const propsData = { ...unref(attrs), ...props } as Recordable; const propsData = { ...unref(attrs), ...props } as Recordable;
return <Modal {...propsData}>{extendSlots(slots)}</Modal>; return <Modal {...propsData}>{extendSlots(slots)}</Modal>;
}; };
}, },

View File

@ -2,7 +2,6 @@
<div :class="getClass"> <div :class="getClass">
<template v-if="canFullscreen"> <template v-if="canFullscreen">
<FullscreenExitOutlined role="full" @click="handleFullScreen" v-if="fullScreen" /> <FullscreenExitOutlined role="full" @click="handleFullScreen" v-if="fullScreen" />
<FullscreenOutlined role="close" @click="handleFullScreen" v-else /> <FullscreenOutlined role="close" @click="handleFullScreen" v-else />
</template> </template>
<CloseOutlined @click="handleCancel" /> <CloseOutlined @click="handleCancel" />
@ -12,14 +11,13 @@
import { defineComponent, computed } from 'vue'; import { defineComponent, computed } from 'vue';
import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue'; import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({ export default defineComponent({
name: 'ModalClose', name: 'ModalClose',
components: { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined }, components: { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined },
props: { props: {
canFullscreen: propTypes.bool.def(true), canFullscreen: { type: Boolean, default: true },
fullScreen: propTypes.bool, fullScreen: { type: Boolean },
}, },
emits: ['cancel', 'fullscreen'], emits: ['cancel', 'fullscreen'],
setup(props, { emit }) { setup(props, { emit }) {
@ -35,9 +33,10 @@
]; ];
}); });
function handleCancel() { function handleCancel(e: Event) {
emit('cancel'); emit('cancel', e);
} }
function handleFullScreen(e: Event) { function handleFullScreen(e: Event) {
e?.stopPropagation(); e?.stopPropagation();
e?.preventDefault(); e?.preventDefault();

View File

@ -26,13 +26,14 @@
props: basicProps, props: basicProps,
emits: ['ok', 'cancel'], emits: ['ok', 'cancel'],
setup(_, { emit }) { setup(_, { emit }) {
function handleOk() { function handleOk(e: Event) {
emit('ok'); emit('ok', e);
} }
function handleCancel() { function handleCancel(e: Event) {
emit('cancel'); emit('cancel', e);
} }
return { handleOk, handleCancel }; return { handleOk, handleCancel };
}, },
}); });

View File

@ -8,7 +8,6 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { BasicTitle } from '/@/components/Basic'; import { BasicTitle } from '/@/components/Basic';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({ export default defineComponent({
name: 'BasicModalHeader', name: 'BasicModalHeader',
components: { BasicTitle }, components: { BasicTitle },
@ -16,7 +15,7 @@
helpMessage: { helpMessage: {
type: [String, Array] as PropType<string | string[]>, type: [String, Array] as PropType<string | string[]>,
}, },
title: propTypes.string, title: { type: String },
}, },
}); });
</script> </script>

View File

@ -6,9 +6,7 @@
</ScrollContainer> </ScrollContainer>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { ModalWrapperProps } from '../types';
import type { CSSProperties } from 'vue'; import type { CSSProperties } from 'vue';
import { import {
defineComponent, defineComponent,
computed, computed,
@ -20,31 +18,31 @@
nextTick, nextTick,
onUnmounted, onUnmounted,
} from 'vue'; } from 'vue';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { ScrollContainer } from '/@/components/Container'; import { ScrollContainer } from '/@/components/Container';
import { propTypes } from '/@/utils/propTypes';
import { createModalContext } from '../hooks/useModalContext'; import { createModalContext } from '../hooks/useModalContext';
import { useMutationObserver } from '@vueuse/core';
const props = {
loading: { type: Boolean },
useWrapper: { type: Boolean, default: true },
modalHeaderHeight: { type: Number, default: 57 },
modalFooterHeight: { type: Number, default: 74 },
minHeight: { type: Number, default: 200 },
height: { type: Number },
footerOffset: { type: Number, default: 0 },
visible: { type: Boolean },
fullScreen: { type: Boolean },
loadingTip: { type: String },
};
export default defineComponent({ export default defineComponent({
name: 'ModalWrapper', name: 'ModalWrapper',
components: { ScrollContainer }, components: { ScrollContainer },
inheritAttrs: false, inheritAttrs: false,
props: { props,
loading: propTypes.bool,
useWrapper: propTypes.bool.def(true),
modalHeaderHeight: propTypes.number.def(57),
modalFooterHeight: propTypes.number.def(74),
minHeight: propTypes.number.def(200),
height: propTypes.number,
footerOffset: propTypes.number.def(0),
visible: propTypes.bool,
fullScreen: propTypes.bool,
loadingTip: propTypes.string,
},
emits: ['height-change', 'ext-height'], emits: ['height-change', 'ext-height'],
setup(props: ModalWrapperProps, { emit }) { setup(props, { emit }) {
const wrapperRef = ref<ComponentRef>(null); const wrapperRef = ref<ComponentRef>(null);
const spinRef = ref<ElRef>(null); const spinRef = ref<ElRef>(null);
const realHeightRef = ref(0); const realHeightRef = ref(0);
@ -56,6 +54,17 @@
useWindowSizeFn(setModalHeight.bind(null, false)); useWindowSizeFn(setModalHeight.bind(null, false));
useMutationObserver(
spinRef,
() => {
setModalHeight();
},
{
attributes: true,
subtree: true,
}
);
createModalContext({ createModalContext({
redoModalHeight: setModalHeight, redoModalHeight: setModalHeight,
}); });
@ -63,8 +72,7 @@
const spinStyle = computed((): CSSProperties => { const spinStyle = computed((): CSSProperties => {
return { return {
minHeight: `${props.minHeight}px`, minHeight: `${props.minHeight}px`,
// padding 28 [props.fullScreen ? 'height' : 'maxHeight']: `${unref(realHeightRef)}px`,
maxHeight: `${unref(realHeightRef)}px`,
}; };
}); });
@ -87,7 +95,6 @@
onMounted(() => { onMounted(() => {
const { modalHeaderHeight, modalFooterHeight } = props; const { modalHeaderHeight, modalFooterHeight } = props;
emit('ext-height', modalHeaderHeight + modalFooterHeight); emit('ext-height', modalHeaderHeight + modalFooterHeight);
// listenElResize();
}); });
onUnmounted(() => { onUnmounted(() => {

View File

@ -4,8 +4,7 @@ import type {
ModalProps, ModalProps,
ReturnMethods, ReturnMethods,
UseModalInnerReturnType, UseModalInnerReturnType,
} from '../types'; } from '../typing';
import { import {
ref, ref,
onUnmounted, onUnmounted,
@ -20,10 +19,10 @@ import { isProdMode } from '/@/utils/env';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { tryOnUnmounted } from '@vueuse/core'; import { tryOnUnmounted } from '@vueuse/core';
import { error } from '/@/utils/log'; import { error } from '/@/utils/log';
import { computed } from 'vue'; import { computed } from 'vue';
const dataTransferRef = reactive<any>({});
const dataTransfer = reactive<any>({});
const visibleData = reactive<{ [key: number]: boolean }>({}); const visibleData = reactive<{ [key: number]: boolean }>({});
@ -31,29 +30,31 @@ const visibleData = reactive<{ [key: number]: boolean }>({});
* @description: Applicable to independent modal and call outside * @description: Applicable to independent modal and call outside
*/ */
export function useModal(): UseModalReturnType { export function useModal(): UseModalReturnType {
const modalRef = ref<Nullable<ModalMethods>>(null); const modal = ref<Nullable<ModalMethods>>(null);
const loadedRef = ref<Nullable<boolean>>(false); const loaded = ref<Nullable<boolean>>(false);
const uidRef = ref<string>(''); const uid = ref<string>('');
function register(modalMethod: ModalMethods, uuid: string) { function register(modalMethod: ModalMethods, uuid: string) {
uidRef.value = uuid; if (!getCurrentInstance()) {
throw new Error('useModal() can only be used inside setup() or functional components!');
}
uid.value = uuid;
isProdMode() && isProdMode() &&
onUnmounted(() => { onUnmounted(() => {
modalRef.value = null; modal.value = null;
loadedRef.value = false; loaded.value = false;
dataTransferRef[unref(uidRef)] = null; dataTransfer[unref(uid)] = null;
}); });
if (unref(loadedRef) && isProdMode() && modalMethod === unref(modalRef)) return; if (unref(loaded) && isProdMode() && modalMethod === unref(modal)) return;
modalRef.value = modalMethod; modal.value = modalMethod;
modalMethod.emitVisible = (visible: boolean, uid: number) => { modalMethod.emitVisible = (visible: boolean, uid: number) => {
visibleData[uid] = visible; visibleData[uid] = visible;
}; };
} }
const getInstance = () => { const getInstance = () => {
const instance = unref(modalRef); const instance = unref(modal);
if (!instance) { if (!instance) {
error('useModal instance is undefined!'); error('useModal instance is undefined!');
} }
@ -66,7 +67,7 @@ export function useModal(): UseModalReturnType {
}, },
getVisible: computed((): boolean => { getVisible: computed((): boolean => {
return visibleData[~~unref(uidRef)]; return visibleData[~~unref(uid)];
}), }),
redoModalHeight: () => { redoModalHeight: () => {
@ -79,15 +80,15 @@ export function useModal(): UseModalReturnType {
}); });
if (!data) return; if (!data) return;
const id = unref(uid);
if (openOnSet) { if (openOnSet) {
dataTransferRef[unref(uidRef)] = null; dataTransfer[id] = null;
dataTransferRef[unref(uidRef)] = toRaw(data); dataTransfer[id] = toRaw(data);
return; return;
} }
const equal = isEqual(toRaw(dataTransferRef[unref(uidRef)]), toRaw(data)); const equal = isEqual(toRaw(dataTransfer[id]), toRaw(data));
if (!equal) { if (!equal) {
dataTransferRef[unref(uidRef)] = toRaw(data); dataTransfer[id] = toRaw(data);
} }
}, },
@ -103,9 +104,6 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
const currentInstance = getCurrentInstance(); const currentInstance = getCurrentInstance();
const uidRef = ref<string>(''); const uidRef = ref<string>('');
// currentInstall.type.emits = [...currentInstall.type.emits, 'register'];
// Object.assign(currentInstall.type.emits, ['register']);
const getInstance = () => { const getInstance = () => {
const instance = unref(modalInstanceRef); const instance = unref(modalInstanceRef);
if (!instance) { if (!instance) {
@ -125,7 +123,7 @@ export const useModalInner = (callbackFn?: Fn): UseModalInnerReturnType => {
}; };
watchEffect(() => { watchEffect(() => {
const data = dataTransferRef[unref(uidRef)]; const data = dataTransfer[unref(uidRef)];
if (!data) return; if (!data) return;
if (!callbackFn || !isFunction(callbackFn)) return; if (!callbackFn || !isFunction(callbackFn)) return;
nextTick(() => { nextTick(() => {

View File

@ -12,7 +12,6 @@ export function useFullScreen(context: UseFullScreenContext) {
const getWrapClassName = computed(() => { const getWrapClassName = computed(() => {
const clsName = unref(context.wrapClassName) || ''; const clsName = unref(context.wrapClassName) || '';
return unref(fullScreenRef) ? `fullscreen-modal ${clsName} ` : unref(clsName); return unref(fullScreenRef) ? `fullscreen-modal ${clsName} ` : unref(clsName);
}); });

View File

@ -1,45 +1,44 @@
import type { PropType, CSSProperties } from 'vue'; import type { PropType, CSSProperties } from 'vue';
import type { ModalWrapperProps } from './typing';
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { propTypes, VueNode } from '/@/utils/propTypes';
import type { ModalWrapperProps } from './types';
const { t } = useI18n(); const { t } = useI18n();
export const modalProps = { export const modalProps = {
visible: propTypes.bool, visible: { type: Boolean },
scrollTop: propTypes.bool.def(true), scrollTop: { type: Boolean, default: true },
height: propTypes.number, height: { type: Number },
minHeight: propTypes.number, minHeight: { type: Number },
// open drag // open drag
draggable: propTypes.bool.def(true), draggable: { type: Boolean, default: true },
centered: propTypes.bool, centered: { type: Boolean },
cancelText: propTypes.string.def(t('common.cancelText')), cancelText: { type: String, default: t('common.cancelText') },
okText: propTypes.string.def(t('common.okText')), okText: { type: String, default: t('common.okText') },
closeFunc: Function as PropType<() => Promise<boolean>>, closeFunc: Function as PropType<() => Promise<boolean>>,
}; };
export const basicProps = Object.assign({}, modalProps, { export const basicProps = Object.assign({}, modalProps, {
defaultFullscreen: propTypes.bool, defaultFullscreen: { type: Boolean },
// Can it be full screen // Can it be full screen
canFullscreen: propTypes.bool.def(true), canFullscreen: { type: Boolean, default: true },
// After enabling the wrapper, the bottom can be increased in height // After enabling the wrapper, the bottom can be increased in height
wrapperFooterOffset: propTypes.number.def(0), wrapperFooterOffset: { type: Number, default: 0 },
// Warm reminder message // Warm reminder message
helpMessage: [String, Array] as PropType<string | string[]>, helpMessage: [String, Array] as PropType<string | string[]>,
// Whether to setting wrapper // Whether to setting wrapper
useWrapper: propTypes.bool.def(true), useWrapper: { type: Boolean, default: true },
loading: propTypes.bool, loading: { type: Boolean },
loadingTip: propTypes.string, loadingTip: { type: String },
/** /**
* @description: Show close button * @description: Show close button
*/ */
showCancelBtn: propTypes.bool.def(true), showCancelBtn: { type: Boolean, default: true },
/** /**
* @description: Show confirmation button * @description: Show confirmation button
*/ */
showOkBtn: propTypes.bool.def(true), showOkBtn: { type: Boolean, default: true },
wrapperProps: Object as PropType<Partial<ModalWrapperProps>>, wrapperProps: Object as PropType<Partial<ModalWrapperProps>>,
@ -47,38 +46,38 @@ export const basicProps = Object.assign({}, modalProps, {
bodyStyle: Object as PropType<CSSProperties>, bodyStyle: Object as PropType<CSSProperties>,
closable: propTypes.bool.def(true), closable: { type: Boolean, default: true },
closeIcon: Object as PropType<VueNode>, closeIcon: Object as PropType<VueNode>,
confirmLoading: propTypes.bool, confirmLoading: { type: Boolean },
destroyOnClose: propTypes.bool, destroyOnClose: { type: Boolean },
footer: Object as PropType<VueNode>, footer: Object as PropType<VueNode>,
getContainer: Function as PropType<() => any>, getContainer: Function as PropType<() => any>,
mask: propTypes.bool.def(true), mask: { type: Boolean, default: true },
maskClosable: propTypes.bool.def(true), maskClosable: { type: Boolean, default: true },
keyboard: propTypes.bool.def(true), keyboard: { type: Boolean, default: true },
maskStyle: Object as PropType<CSSProperties>, maskStyle: Object as PropType<CSSProperties>,
okType: propTypes.string.def('primary'), okType: { type: String, default: 'primary' },
okButtonProps: Object as PropType<ButtonProps>, okButtonProps: Object as PropType<ButtonProps>,
cancelButtonProps: Object as PropType<ButtonProps>, cancelButtonProps: Object as PropType<ButtonProps>,
title: propTypes.string, title: { type: String },
visible: propTypes.bool, visible: { type: Boolean },
width: [String, Number] as PropType<string | number>, width: [String, Number] as PropType<string | number>,
wrapClassName: propTypes.string, wrapClassName: { type: String },
zIndex: propTypes.number, zIndex: { type: Number },
}); });

View File

@ -1,2 +1,7 @@
export { default as PageFooter } from './src/PageFooter.vue'; import { withInstall } from '/@/utils';
export { default as PageWrapper } from './src/PageWrapper.vue';
import pageFooter from './src/PageFooter.vue';
import pageWrapper from './src/PageWrapper.vue';
export const PageFooter = withInstall(pageFooter);
export const PageWrapper = withInstall(pageWrapper);

View File

@ -11,7 +11,6 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';

View File

@ -44,6 +44,8 @@
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
import { PageHeader } from 'ant-design-vue'; import { PageHeader } from 'ant-design-vue';
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated'; import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
import { useLayoutHeight } from '/@/layouts/default/content/useContentViewHeight';
export default defineComponent({ export default defineComponent({
name: 'PageWrapper', name: 'PageWrapper',
components: { PageFooter, PageHeader }, components: { PageFooter, PageHeader },
@ -67,6 +69,7 @@
const footerHeight = ref(0); const footerHeight = ref(0);
const { prefixCls, prefixVar } = useDesign('page-wrapper'); const { prefixCls, prefixVar } = useDesign('page-wrapper');
const { contentHeight, setPageHeight, pageHeight } = usePageContext(); const { contentHeight, setPageHeight, pageHeight } = usePageContext();
const { footerHeightRef } = useLayoutHeight();
const getClass = computed(() => { const getClass = computed(() => {
return [ return [
@ -93,7 +96,6 @@
...contentStyle, ...contentStyle,
minHeight: height, minHeight: height,
...(fixedHeight ? { height } : {}), ...(fixedHeight ? { height } : {}),
paddingBottom: `${unref(footerHeight)}px`,
}; };
}); });
@ -109,7 +111,7 @@
}); });
watch( watch(
() => [contentHeight?.value, getShowFooter.value], () => [contentHeight?.value, getShowFooter.value, footerHeightRef.value],
() => { () => {
calcContentHeight(); calcContentHeight();
}, },
@ -163,7 +165,36 @@
const contentMarginTop = Number(marginTop.replace(/[^\d]/g, '')); const contentMarginTop = Number(marginTop.replace(/[^\d]/g, ''));
subtractHeight += contentMarginTop; subtractHeight += contentMarginTop;
} }
setPageHeight?.(unref(contentHeight) - unref(footerHeight) - headerHeight - subtractHeight);
// fix: wrapper marginTop and marginBottom value
let wrapperSubtractHeight = 0;
let wrapperMarginBottom = ZERO_PX;
let wrapperMarginTop = ZERO_PX;
const wrapperClassElments = document.querySelectorAll(`.${prefixVar}-page-wrapper`);
if (wrapperClassElments && wrapperClassElments.length > 0) {
const contentEl = wrapperClassElments[0];
const cssStyle = getComputedStyle(contentEl);
wrapperMarginBottom = cssStyle?.marginBottom ?? ZERO_PX;
wrapperMarginTop = cssStyle?.marginTop ?? ZERO_PX;
}
if (wrapperMarginBottom) {
const contentMarginBottom = Number(wrapperMarginBottom.replace(/[^\d]/g, ''));
wrapperSubtractHeight += contentMarginBottom;
}
if (wrapperMarginTop) {
const contentMarginTop = Number(wrapperMarginTop.replace(/[^\d]/g, ''));
wrapperSubtractHeight += contentMarginTop;
}
let height =
unref(contentHeight) -
unref(footerHeight) -
headerHeight -
subtractHeight -
wrapperSubtractHeight;
if (unref(getShowFooter)) {
height -= unref(footerHeightRef);
}
setPageHeight?.(height);
} }
return { return {

View File

@ -1 +1,2 @@
export { default as ImagePreview } from './src/Preview.vue'; export { default as ImagePreview } from './src/Preview.vue';
export { createImgPreview } from './src/functional';

View File

@ -0,0 +1,436 @@
<script lang="tsx">
import { defineComponent, ref, unref, computed, reactive, watchEffect } from 'vue';
import { Props } from './typing';
import { CloseOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
import resumeSvg from '/@/assets/svg/preview/resume.svg';
import rotateSvg from '/@/assets/svg/preview/p-rotate.svg';
import scaleSvg from '/@/assets/svg/preview/scale.svg';
import unScaleSvg from '/@/assets/svg/preview/unscale.svg';
import unRotateSvg from '/@/assets/svg/preview/unrotate.svg';
enum StatueEnum {
LOADING,
DONE,
FAIL,
}
interface ImgState {
currentUrl: string;
imgScale: number;
imgRotate: number;
imgTop: number;
imgLeft: number;
currentIndex: number;
status: StatueEnum;
moveX: number;
moveY: number;
show: boolean;
}
const props = {
show: {
type: Boolean as PropType<boolean>,
default: false,
},
imageList: {
type: [Array] as PropType<string[]>,
default: null,
},
index: {
type: Number as PropType<number>,
default: 0,
},
};
const prefixCls = 'img-preview';
export default defineComponent({
name: 'ImagePreview',
props,
setup(props: Props) {
const imgState = reactive<ImgState>({
currentUrl: '',
imgScale: 1,
imgRotate: 0,
imgTop: 0,
imgLeft: 0,
status: StatueEnum.LOADING,
currentIndex: 0,
moveX: 0,
moveY: 0,
show: props.show,
});
const wrapElRef = ref<HTMLDivElement | null>(null);
const imgElRef = ref<HTMLImageElement | null>(null);
//
function init() {
initMouseWheel();
const { index, imageList } = props;
if (!imageList || !imageList.length) {
throw new Error('imageList is undefined');
}
imgState.currentIndex = index;
handleIChangeImage(imageList[index]);
}
//
function initState() {
imgState.imgScale = 1;
imgState.imgRotate = 0;
imgState.imgTop = 0;
imgState.imgLeft = 0;
}
//
function initMouseWheel() {
const wrapEl = unref(wrapElRef);
if (!wrapEl) {
return;
}
(wrapEl as any).onmousewheel = scrollFunc;
// onmousewheelDOMMouseScroll
document.body.addEventListener('DOMMouseScroll', scrollFunc);
//
document.ondragstart = function () {
return false;
};
}
//
function scrollFunc(e: any) {
e = e || window.event;
e.delta = e.wheelDelta || -e.detail;
e.preventDefault();
if (e.delta > 0) {
//
scaleFunc(0.015);
}
if (e.delta < 0) {
//
scaleFunc(-0.015);
}
}
//
function scaleFunc(num: number) {
if (imgState.imgScale <= 0.2 && num < 0) return;
imgState.imgScale += num;
}
//
function rotateFunc(deg: number) {
imgState.imgRotate += deg;
}
//
function handleMouseUp() {
const imgEl = unref(imgElRef);
if (!imgEl) return;
imgEl.onmousemove = null;
}
//
function handleIChangeImage(url: string) {
imgState.status = StatueEnum.LOADING;
const img = new Image();
img.src = url;
img.onload = () => {
imgState.currentUrl = url;
imgState.status = StatueEnum.DONE;
};
img.onerror = () => {
imgState.status = StatueEnum.FAIL;
};
}
//
function handleClose(e: MouseEvent) {
e && e.stopPropagation();
imgState.show = false;
//
document.body.removeEventListener('DOMMouseScroll', scrollFunc);
// Safari
document.ondragstart = null;
}
//
function resume() {
initState();
}
//
function handleChange(direction: 'left' | 'right') {
const { currentIndex } = imgState;
const { imageList } = props;
if (direction === 'left') {
imgState.currentIndex--;
if (currentIndex <= 0) {
imgState.currentIndex = imageList.length - 1;
}
}
if (direction === 'right') {
imgState.currentIndex++;
if (currentIndex >= imageList.length - 1) {
imgState.currentIndex = 0;
}
}
handleIChangeImage(imageList[imgState.currentIndex]);
}
function handleAddMoveListener(e: MouseEvent) {
e = e || window.event;
imgState.moveX = e.clientX;
imgState.moveY = e.clientY;
const imgEl = unref(imgElRef);
if (imgEl) {
imgEl.onmousemove = moveFunc;
}
}
function moveFunc(e: MouseEvent) {
e = e || window.event;
e.preventDefault();
const movementX = e.clientX - imgState.moveX;
const movementY = e.clientY - imgState.moveY;
imgState.imgLeft += movementX;
imgState.imgTop += movementY;
imgState.moveX = e.clientX;
imgState.moveY = e.clientY;
}
//
const getImageStyle = computed(() => {
const { imgScale, imgRotate, imgTop, imgLeft } = imgState;
return {
transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
marginTop: `${imgTop}px`,
marginLeft: `${imgLeft}px`,
};
});
const getIsMultipleImage = computed(() => {
const { imageList } = props;
return imageList.length > 1;
});
watchEffect(() => {
if (props.show) {
init();
}
if (props.imageList) {
initState();
}
});
const renderClose = () => {
return (
<div class={`${prefixCls}__close`} onClick={handleClose}>
<CloseOutlined class={`${prefixCls}__close-icon`} />
</div>
);
};
const renderIndex = () => {
if (!unref(getIsMultipleImage)) {
return null;
}
const { currentIndex } = imgState;
const { imageList } = props;
return (
<div class={`${prefixCls}__index`}>
{currentIndex + 1} / {imageList.length}
</div>
);
};
const renderController = () => {
return (
<div class={`${prefixCls}__controller`}>
<div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}>
<img src={unScaleSvg} />
</div>
<div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}>
<img src={scaleSvg} />
</div>
<div class={`${prefixCls}__controller-item`} onClick={resume}>
<img src={resumeSvg} />
</div>
<div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(-90)}>
<img src={unRotateSvg} />
</div>
<div class={`${prefixCls}__controller-item`} onClick={() => rotateFunc(90)}>
<img src={rotateSvg} />
</div>
</div>
);
};
const renderArrow = (direction: 'left' | 'right') => {
if (!unref(getIsMultipleImage)) {
return null;
}
return (
<div class={[`${prefixCls}__arrow`, direction]} onClick={() => handleChange(direction)}>
{direction === 'left' ? <LeftOutlined /> : <RightOutlined />}
</div>
);
};
return () => {
return (
imgState.show && (
<div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}>
<div class={`${prefixCls}-content`}>
{/*<Spin*/}
{/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/}
{/* spinning={true}*/}
{/* class={[*/}
{/* `${prefixCls}-image`,*/}
{/* {*/}
{/* hidden: imgState.status !== StatueEnum.LOADING,*/}
{/* },*/}
{/* ]}*/}
{/*/>*/}
<img
style={unref(getImageStyle)}
class={[
`${prefixCls}-image`,
imgState.status === StatueEnum.DONE ? '' : 'hidden',
]}
ref={imgElRef}
src={imgState.currentUrl}
onMousedown={handleAddMoveListener}
/>
{renderClose()}
{renderIndex()}
{renderController()}
{renderArrow('left')}
{renderArrow('right')}
</div>
</div>
)
);
};
},
});
</script>
<style lang="less">
.img-preview {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: @preview-comp-z-index;
background: rgba(0, 0, 0, 0.5);
user-select: none;
&-content {
display: flex;
width: 100%;
height: 100%;
color: @white;
justify-content: center;
align-items: center;
}
&-image {
cursor: pointer;
transition: transform 0.3s;
}
&__close {
position: absolute;
top: -40px;
right: -40px;
width: 80px;
height: 80px;
overflow: hidden;
color: @white;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
transition: all 0.2s;
&-icon {
position: absolute;
top: 46px;
left: 16px;
font-size: 16px;
}
&:hover {
background-color: rgba(0, 0, 0, 0.8);
}
}
&__index {
position: absolute;
bottom: 5%;
left: 50%;
padding: 0 22px;
font-size: 16px;
background: rgba(109, 109, 109, 0.6);
border-radius: 15px;
transform: translateX(-50%);
}
&__controller {
position: absolute;
bottom: 10%;
left: 50%;
display: flex;
width: 260px;
height: 44px;
padding: 0 22px;
margin-left: -139px;
background: rgba(109, 109, 109, 0.6);
border-radius: 22px;
justify-content: center;
&-item {
display: flex;
height: 100%;
padding: 0 9px;
font-size: 24px;
cursor: pointer;
transition: all 0.2s;
&:hover {
transform: scale(1.2);
}
img {
width: 1em;
}
}
}
&__arrow {
position: absolute;
top: 50%;
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
font-size: 28px;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
transition: all 0.2s;
&:hover {
background-color: rgba(0, 0, 0, 0.8);
}
&.left {
left: 50px;
}
&.right {
right: 50px;
}
}
}
</style>

View File

@ -0,0 +1,20 @@
import type { Options, Props } from './typing';
import ImgPreview from './Functional.vue';
import { isClient } from '/@/utils/is';
import { createVNode, render } from 'vue';
let instance: ReturnType<typeof createVNode> | null = null;
export function createImgPreview(options: Options) {
if (!isClient) return;
const { imageList, show = true, index = 0 } = options;
const propsData: Partial<Props> = {};
const container = document.createElement('div');
propsData.imageList = imageList;
propsData.show = show;
propsData.index = index;
instance = createVNode(ImgPreview, propsData);
render(instance, container);
document.body.appendChild(container);
}

View File

@ -0,0 +1,30 @@
export interface Options {
show?: boolean;
imageList: string[];
index?: number;
}
export interface Props {
show: boolean;
instance: Props;
imageList: string[];
index: number;
}
export interface ImageProps {
alt?: string;
fallback?: string;
src: string;
width: string | number;
height?: string | number;
placeholder?: string | boolean;
preview?:
| boolean
| {
visible?: boolean;
onVisibleChange?: (visible: boolean, prevVisible: boolean) => void;
getContainer: string | HTMLElement | (() => HTMLElement);
};
}
export type ImageItem = string | ImageProps;

View File

@ -1,3 +1,5 @@
export { default as QrCode } from './src/Qrcode.vue'; import { withInstall } from '/@/utils';
import qrCode from './src/Qrcode.vue';
export * from './src/types'; export const QrCode = withInstall(qrCode);
export * from './src/typing';

View File

@ -8,7 +8,7 @@
import { toCanvas, QRCodeRenderersOptions, LogoType } from './qrcodePlus'; import { toCanvas, QRCodeRenderersOptions, LogoType } from './qrcodePlus';
import { toDataURL } from 'qrcode'; import { toDataURL } from 'qrcode';
import { downloadByUrl } from '/@/utils/file/download'; import { downloadByUrl } from '/@/utils/file/download';
import { QrcodeDoneEventParams } from './types'; import { QrcodeDoneEventParams } from './typing';
export default defineComponent({ export default defineComponent({
name: 'QrCode', name: 'QrCode',

View File

@ -1,6 +1,6 @@
import { toCanvas } from 'qrcode'; import { toCanvas } from 'qrcode';
import type { QRCodeRenderersOptions } from 'qrcode'; import type { QRCodeRenderersOptions } from 'qrcode';
import { RenderQrCodeParams, ContentType } from './types'; import { RenderQrCodeParams, ContentType } from './typing';
export const renderQrCode = ({ canvas, content, width = 0, options = {} }: RenderQrCodeParams) => { export const renderQrCode = ({ canvas, content, width = 0, options = {} }: RenderQrCodeParams) => {
// 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率 // 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(content); options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(content);

View File

@ -1,12 +1,11 @@
import { isString } from '/@/utils/is'; import { isString } from '/@/utils/is';
import { RenderQrCodeParams, LogoType } from './types'; import { RenderQrCodeParams, LogoType } from './typing';
export const drawLogo = ({ canvas, logo }: RenderQrCodeParams) => { export const drawLogo = ({ canvas, logo }: RenderQrCodeParams) => {
if (!logo) { if (!logo) {
return new Promise((resolve) => { return new Promise((resolve) => {
resolve((canvas as HTMLCanvasElement).toDataURL()); resolve((canvas as HTMLCanvasElement).toDataURL());
}); });
} }
const canvasWidth = (canvas as HTMLCanvasElement).width; const canvasWidth = (canvas as HTMLCanvasElement).width;
const { const {
logoSize = 0.15, logoSize = 0.15,

View File

@ -1,5 +1,4 @@
// 参考 qr-code-with-logo 进行ts版本修改 // 参考 qr-code-with-logo 进行ts版本修改
import { toCanvas } from './toCanvas'; import { toCanvas } from './toCanvas';
export * from './types'; export * from './typing';
export { toCanvas }; export { toCanvas };

View File

@ -1,6 +1,6 @@
import { renderQrCode } from './drawCanvas'; import { renderQrCode } from './drawCanvas';
import { drawLogo } from './drawLogo'; import { drawLogo } from './drawLogo';
import { RenderQrCodeParams } from './types'; import { RenderQrCodeParams } from './typing';
export const toCanvas = (options: RenderQrCodeParams) => { export const toCanvas = (options: RenderQrCodeParams) => {
return renderQrCode(options) return renderQrCode(options)
.then(() => { .then(() => {

View File

@ -22,7 +22,7 @@
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
import { createSimpleRootMenuContext } from './useSimpleMenuContext'; import { createSimpleRootMenuContext } from './useSimpleMenuContext';
import Mitt from '/@/utils/mitt'; import mitt from '/@/utils/mitt';
export default defineComponent({ export default defineComponent({
name: 'Menu', name: 'Menu',
props: { props: {
@ -44,7 +44,7 @@
}, },
emits: ['select', 'open-change'], emits: ['select', 'open-change'],
setup(props, { emit }) { setup(props, { emit }) {
const rootMenuEmitter = new Mitt(); const rootMenuEmitter = mitt();
const instance = getCurrentInstance(); const instance = getCurrentInstance();
const currentActiveName = ref<string | number>(''); const currentActiveName = ref<string | number>('');

View File

@ -77,7 +77,7 @@
import Icon from '/@/components/Icon'; import Icon from '/@/components/Icon';
import { Popover } from 'ant-design-vue'; import { Popover } from 'ant-design-vue';
import { isBoolean, isObject } from '/@/utils/is'; import { isBoolean, isObject } from '/@/utils/is';
import Mitt from '/@/utils/mitt'; import mitt from '/@/utils/mitt';
const DELAY = 200; const DELAY = 200;
export default defineComponent({ export default defineComponent({
@ -114,7 +114,7 @@
const { prefixCls } = useDesign('menu'); const { prefixCls } = useDesign('menu');
const subMenuEmitter = new Mitt(); const subMenuEmitter = mitt();
const { rootMenuEmitter } = useSimpleRootMenuContext(); const { rootMenuEmitter } = useSimpleRootMenuContext();
@ -273,8 +273,7 @@
state.opened = data; state.opened = data;
return; return;
} }
if (isObject(data) && rootProps.accordion) {
if (isObject(data)) {
const { opend, parent, uidList } = data as Recordable; const { opend, parent, uidList } = data as Recordable;
if (parent === instance?.parent) { if (parent === instance?.parent) {
state.opened = opend; state.opened = opend;

View File

@ -1,9 +1,9 @@
import type { InjectionKey, Ref } from 'vue'; import type { InjectionKey, Ref } from 'vue';
import { createContext, useContext } from '/@/hooks/core/useContext'; import { createContext, useContext } from '/@/hooks/core/useContext';
import Mitt from '/@/utils/mitt'; import mitt from '/@/utils/mitt';
export interface SimpleRootMenuContextProps { export interface SimpleRootMenuContextProps {
rootMenuEmitter: Mitt; rootMenuEmitter: typeof mitt;
activeName: Ref<string | number>; activeName: Ref<string | number>;
} }

View File

@ -1 +1,4 @@
export { default as StrengthMeter } from './src/StrengthMeter.vue'; import { withInstall } from '/@/utils';
import strengthMeter from './src/StrengthMeter.vue';
export const StrengthMeter = withInstall(strengthMeter);

View File

@ -20,10 +20,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, computed, ref, watch, unref, watchEffect } from 'vue'; import { defineComponent, computed, ref, watch, unref, watchEffect } from 'vue';
import { Input } from 'ant-design-vue'; import { Input } from 'ant-design-vue';
// @ts-ignore
import { zxcvbn } from '@zxcvbn-ts/core'; import { zxcvbn } from '@zxcvbn-ts/core';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';

View File

@ -2,13 +2,9 @@ export { default as BasicTable } from './src/BasicTable.vue';
export { default as TableAction } from './src/components/TableAction.vue'; export { default as TableAction } from './src/components/TableAction.vue';
export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue'; export { default as EditTableHeaderIcon } from './src/components/EditTableHeaderIcon.vue';
export { default as TableImg } from './src/components/TableImg.vue'; export { default as TableImg } from './src/components/TableImg.vue';
export * from './src/types/table'; export * from './src/types/table';
export * from './src/types/pagination'; export * from './src/types/pagination';
export * from './src/types/tableAction'; export * from './src/types/tableAction';
export { useTable } from './src/hooks/useTable'; export { useTable } from './src/hooks/useTable';
export type { FormSchema, FormProps } from '/@/components/Form/src/types/form'; export type { FormSchema, FormProps } from '/@/components/Form/src/types/form';
export type { EditRecordRow } from './src/components/editable'; export type { EditRecordRow } from './src/components/editable';

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