Commit cb81d9a9 authored by Jobin's avatar Jobin 🤞🏻

ci(update): update version

parents 181ff181 c8204042
---
name: 🐛 Bug 报告
about: 向我们报告一个Bug以帮助我们改进
title: ''
labels: 'bug: pending triage'
assignees: ''
---
**⚠️ 重要 ⚠️ 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭**
- [ ] 已阅读 [文档](https://anncwb.github.io/vue-vben-admin-doc/).
- [ ] 确保您的代码已是最新或者所报告的 Bug 在最新版本中可以重现. (部分 Bug 可能已经在最近的代码中修复)
- [ ] 已在 Issues 中搜索了相关的关键词
- [ ] 不是 ant design vue 组件库的 Bug
### 描述 Bug
请清晰地描述此 Bug 的具体表现。
### 复现 Bug
请描述在演示页面中复现 Bug 的详细步骤,以确保我们可以理解并定位问题。部分 Bug 如果未在 Demo 中涉及,请务必提供关键代码
## 系统信息
- 操作系统:
- Node 版本:
- 包管理器 (npm/yarn/pnpm) 及其版本:
...@@ -23,8 +23,7 @@ if [ -z "$husky_skip_init" ]; then ...@@ -23,8 +23,7 @@ if [ -z "$husky_skip_init" ]; then
if [ $exitCode != 0 ]; then if [ $exitCode != 0 ]; then
echo "husky - $hook_name hook exited with code $exitCode (error)" echo "husky - $hook_name hook exited with code $exitCode (error)"
exit $exitCode
fi fi
exit 0 exit $exitCode
fi fi
...@@ -130,6 +130,25 @@ ...@@ -130,6 +130,25 @@
"sider", "sider",
"pinia", "pinia",
"sider", "sider",
"nprogress" "nprogress",
"INTLIFY",
"stylelint",
"esno",
"vitejs",
"sortablejs",
"mockjs",
"codemirror",
"iconify",
"commitlint",
"vditor",
"echarts",
"cropperjs",
"logicflow",
"vueuse",
"zxcvbn",
"lintstagedrc",
"brotli",
"tailwindcss",
"sider"
] ]
} }
## 2.7.0(2021-08-03)
## (Breaking changes) Breaking changes
- Restore the project `tailwindcss` back to `windicss`, tried `tailwindcss`, there may be a lot of problems, first switch back to `windicss` to improve development efficiency and lower switching costs.
- There are currently incompatible areas of the project
- The wording of `xl:!m-4` needs to be changed to `!xl:m-4`, note that only `!` is incompatible. If you don’t use it, you don’t need to change it.
- The memory overflow problem may still exist (low frequency, just restart, restart vite faster)
### ✨ Features
- **Preview** Add new properties and events
- **Dark Theme** added support for tailwindcss night mode
- **Others** add setTip method for useLoading
### 🐛 Bug Fixes
- **ApiTreeSelect** Fixed the problem of failing to monitor `params` changes correctly
- **ImgRotateDragVerify** Fix the problem that the component `resume` method cannot be called
- **TableAction** Fix the problem that the stopButtonPropagation property does not work in some cases
- **PageWrapper** Fix the problem of invalid `class` attribute
- **BasicTree** Fix the problem that the `checkAll` method will affect the `disabled` state node
- **BasicTable**
- Fix the issue that editable cells do not support `ellipsis` configuration
- Fixed the problem that the pop-up layer of sub-components (popconfirm and edit components such as select and treeSelect) cannot be seen in full-screen mode
- Fixed an issue where when `expandRowByClick` is enabled, clicking non-expandable rows may cause style errors
- Fix the problem that the dynamic change of `pagination` property does not take effect
- Fix the problem that `getSelectRows` does not support the child data of the tree table -**Dark Theme** Fix the color matching problem under the dark theme
- Fix the background color of the selected node of the `Tree` component
- Fix the color configuration of the `Alert` component
- Fix the problem of the button color of `link` type in the disabled state
- Fix the style problem of checked checkboxes in `Tree` -**Others** Fix the problem that useScript failed to automatically remove the script node
## 2.6.1(2021-07-19)
### ✨ Features
- **NoticeList** Add pagination, auto omit for overlength, title click event, title strikethrough, etc.
- **MixSider** Optimize the style of the bottom collapse button in the Mix menu layout to be consistent with the style of other menu layouts
- **ApiTreeSelect** Extend `TreeSelect` component of `antdv` to support remote data source, similar to `ApiSelect`.
- **BasicTable** New `ApiTreeSelect` editing component
- Different backend home pages can be specified for different users.
- Add `homePath` field (optional) to the user information returned by the `getUserInfo` interface to customize the home page path for the current user
### 🐛 Bug Fixes
- **BasicTable**
- Fix scrollbar style issue (removed scroll style patch)
- Fix the alignment problem of cells with expanded icons in tree tables
- Add `headerTop` slot.
- Fix the color display of the operation column button in disabled state.
- Repair the problem that the values of editable cells cannot be updated by modifying `dataSource` directly.
- Repair the problem of data replay when using `ApiSelect` to edit components.
- Repair the problem that editing components may report `onXXX` type error in some scenarios.
- **TableAction**
- Create Tooltip component only if `action.tooltip` exists.
- Fix the problem that the content of the round button inside the component is not centered
- **AppSearch** Fix the problem that the hidden menu may be searched.
- **BasicUpload** Repair the problem of error when handling non-`array` values.
- **Form** Repair the `suffix` slot style problem of `FormItem`.
- **Menu**
- Repair the hovering trigger logic of the left mixed menu
- Repair the problem that the top bar menu is wrong when displaying menu items that need to be hidden.
- Fix the left mixed menu in hover trigger mode will jump to route directly when there is no submenu and it is activated
- **Breadcrumb** Repair the problem that the menu with redirection cannot be jumped when clicked
- **Markdown** fixes an initialization exception and an issue where value was not set dynamically correctly
- **Modal** Make sure props are passed correctly
- **MultipleTab** fixes an issue that could accidentally create login route tabs
- **BasicTree** Fix the problem that the search function may cause `checkedKeys` to be lost
- **CodeEditor** Fix the problem that value does not support v-model usage.
- **CountdownInput** Fix the problem that `input` slot is not supported.
- **ApiSelect** Fix the problem that the `options-change` event parameter is not the standard `options` data used by `select
- **Other**
- Fix the problem that the configuration of default menu collapse does not work
- Repair the problem that `safari` browser reports an error and the website cannot be opened.
- Repair the problem that eslint keeps error due to endOfLine after pulling the code on window.
- Fix `Vue Router warn` caused by dynamic routing
### 🎫 Chores
- Add test environment test command
## 2.6.0(2021-07-04) ## 2.6.0(2021-07-04)
### ✨ Features ### ✨ Features
......
# [2.7.0](https://github.com/anncwb/vue-vben-admin/compare/v2.5.9...v2.7.0) (2021-08-03)
### Bug Fixes
- The Style of tableTitle slot ([#1023](https://github.com/anncwb/vue-vben-admin/issues/1023)) ([02e7756](https://github.com/anncwb/vue-vben-admin/commit/02e77560624cc4a95a5a20ffd5e0601f1f88c6f4))
- **api-select:** ensure that the onchange function parameters are correct ([fa64fc8](https://github.com/anncwb/vue-vben-admin/commit/fa64fc8a622832b87fdf672965d55d543b5929a2))
- **app-search:** exclude hidden items ([faf5c9f](https://github.com/anncwb/vue-vben-admin/commit/faf5c9fd7ea40c407419a5a5c473f9b0c32c2a53))
- **app-search:** exclude items by `hideChildrenInMenu` ([02d3dca](https://github.com/anncwb/vue-vben-admin/commit/02d3dca57efedc1322ae38e3f432cf1f6c2cf839))
- **axios:** option `withToken` not work ([d509e89](https://github.com/anncwb/vue-vben-admin/commit/d509e897be5753c852e912112e70dac6247ba467))
- **breadcrumb:** `redirect` not worked ([f5e31fe](https://github.com/anncwb/vue-vben-admin/commit/f5e31febbd18372a34166cac390b1d9b914fe80e))
- **comp-tree:** support comp-tree-foreach stop,add insertNodesByKey ([#818](https://github.com/anncwb/vue-vben-admin/issues/818)) ([d97aa92](https://github.com/anncwb/vue-vben-admin/commit/d97aa927417bf45a7c127ecfa9b8e835b6b68855))
- **CountTo:** Fix displaying empty string when the value is 0 ([#864](https://github.com/anncwb/vue-vben-admin/issues/864)) ([82eb72b](https://github.com/anncwb/vue-vben-admin/commit/82eb72bbced931ba7f50069211f9511035ad09f4))
- **demo:** `setup` page route config ([d5d5c4b](https://github.com/anncwb/vue-vben-admin/commit/d5d5c4b4bfb3e3a5e54f9993966adc46a09a8b90))
- **demo:** account list fetch loss param ([424b171](https://github.com/anncwb/vue-vben-admin/commit/424b171e0db727f5e0157cbcfd5460f15f8ea609)), closes [#830](https://github.com/anncwb/vue-vben-admin/issues/830)
- **demo:** add mock data `account detail` route ([993e19d](https://github.com/anncwb/vue-vben-admin/commit/993e19dcc319e2b4c68df2ab76174b7b4d7b0428)), closes [#858](https://github.com/anncwb/vue-vben-admin/issues/858)
- **demo:** fix async tree demo, fixed: [#823](https://github.com/anncwb/vue-vben-admin/issues/823) ([5637588](https://github.com/anncwb/vue-vben-admin/commit/5637588fce880b01137191cc82c73e0fce621e8c))
- **demo:** form pages support `keepAlive` ([9228282](https://github.com/anncwb/vue-vben-admin/commit/9228282ae27daaa246f42e441e27b1b05eb30464))
- **demo:** resolve `key not exist` warnings ([45a94e4](https://github.com/anncwb/vue-vben-admin/commit/45a94e41c1397b84d08373f84f766204d2488714))
- **demo:** style error,fix [#806](https://github.com/anncwb/vue-vben-admin/issues/806) ([a2d8be3](https://github.com/anncwb/vue-vben-admin/commit/a2d8be3ab29da88126f3ba971f6893cb12327759))
- **demo-form:** add fieldMapToTime example,fix [#807](https://github.com/anncwb/vue-vben-admin/issues/807) ([a2a75a0](https://github.com/anncwb/vue-vben-admin/commit/a2a75a097ff6c9df12471eff0d62d44d2b88cfff))
- **design:** correct tailwind configuration,fix [#800](https://github.com/anncwb/vue-vben-admin/issues/800) ([aec230c](https://github.com/anncwb/vue-vben-admin/commit/aec230ca19d541079b64c54ba00596ef9cd92ca0))
- **drawer:** openDrawer is not normal in some cases ([941ad59](https://github.com/anncwb/vue-vben-admin/commit/941ad59759cbd5a5e39bcdf29783d8eea85caf72))
- **dropdown:** icon and trigger work unexpected ([60b80c9](https://github.com/anncwb/vue-vben-admin/commit/60b80c96e82da9101d56b2e195e9e7571de11f0a)), closes [#796](https://github.com/anncwb/vue-vben-admin/issues/796) [#787](https://github.com/anncwb/vue-vben-admin/issues/787)
- **form:** fix `suffix` slot style ([a9bbed1](https://github.com/anncwb/vue-vben-admin/commit/a9bbed19739376ab2bf67a14b04e872f14ca84cc))
- **form:** fix some prop declaration ([b5046f0](https://github.com/anncwb/vue-vben-admin/commit/b5046f07a27e8ca7fc8b961b74fa5e1b0d715149))
- **lock-screen:** ensure lock info is saved ([d38ff66](https://github.com/anncwb/vue-vben-admin/commit/d38ff6670a37478b31447f8058e786c4b044e218))
- **lock-screen:** fix lock-screen can skip on new window ([d7b84c7](https://github.com/anncwb/vue-vben-admin/commit/d7b84c78744f7d0077a779b232e1358040b50383))
- **markdown:** resolving markdown exceptions ([d95815b](https://github.com/anncwb/vue-vben-admin/commit/d95815b5031984e224140eb1b1d46e2dbf80abc1))
- **markdown:** set `value` error ([35e1347](https://github.com/anncwb/vue-vben-admin/commit/35e1347029e29a83a9648b6b398e6863cc40fca9))
- **menu:** display error when contains hidden items ([5ceeefd](https://github.com/anncwb/vue-vben-admin/commit/5ceeefd17d9ddc0e8844b900069b100f24d9c00e))
- **menu:** fix mix-menu incorrect jumping in `hover` mode ([cad021c](https://github.com/anncwb/vue-vben-admin/commit/cad021c34b71fa109640af75a0c2b72179e9e257))
- **menu:** make sure the menu is activated correctly ([cdb10cc](https://github.com/anncwb/vue-vben-admin/commit/cdb10cc4ac5e5e8f9cce3ff18d8fbd29ef10c86f))
- **mix-sider:** fix mix-sider hover logic ([0595a72](https://github.com/anncwb/vue-vben-admin/commit/0595a72da9c666af81a0916663e8e6a014e6fa69))
- **modal:** `setModalProps` support `defaultFullscreen` ([c7de65e](https://github.com/anncwb/vue-vben-admin/commit/c7de65ebba53941771153f18b184d3d4d31c0dbf))
- **modal:** maskClosable not work ([f750ff4](https://github.com/anncwb/vue-vben-admin/commit/f750ff435fee06acee78d6b9633e6e18d91685f8))
- **modal:** remove console log ([3dbbde2](https://github.com/anncwb/vue-vben-admin/commit/3dbbde2662352780377a9b216598d9348522f6ba))
- **pop-confirm:** fix event working unexpected ([a6ef771](https://github.com/anncwb/vue-vben-admin/commit/a6ef771fcce14c3644c965afaa69b3a17d0a7087))
- **popconfirm-button:** remove button excess `title` ([73654b7](https://github.com/anncwb/vue-vben-admin/commit/73654b7862c59d623d6d5dc7dcf6ff2704564d9a))
- **sider:** bottom trigger not work ([1bde404](https://github.com/anncwb/vue-vben-admin/commit/1bde4041211229d5d9d01ce0ca806fa99356b6de)), closes [#820](https://github.com/anncwb/vue-vben-admin/issues/820)
- **sider:** custom trigger does not take effect ([5005e6e](https://github.com/anncwb/vue-vben-admin/commit/5005e6e56b1cc7763a1cc23e1162dfb49452013b))
- **svg-icon:** fix SvgIcon style ([99829c7](https://github.com/anncwb/vue-vben-admin/commit/99829c79ab41a2319f40c5595a7d82d9e406ba18))
- **table:** fix index column style ([c7c0a7e](https://github.com/anncwb/vue-vben-admin/commit/c7c0a7e4c88a895000b1621981e4d4b2020c64b1))
- **table:** auto hide unnecessary scrollbar ([735028c](https://github.com/anncwb/vue-vben-admin/commit/735028c43055e8e80ebc7344af0cd0f51c744f98))
- **table:** editComponentProps support onChange ([829b366](https://github.com/anncwb/vue-vben-admin/commit/829b366cb2abf27e69d9665e5be022b3d3f15655))
- **table:** event editCancel loss params ([8d22231](https://github.com/anncwb/vue-vben-admin/commit/8d22231a5fa4afed19201a4a4e5c29d674498516))
- **table:** fix rowSelection.onChange not work ([df0f000](https://github.com/anncwb/vue-vben-admin/commit/df0f00085c1113eddd7a15954818ccece3538068)), closes [#825](https://github.com/anncwb/vue-vben-admin/issues/825)
- **table:** fix table jitter problem ([8eba7fb](https://github.com/anncwb/vue-vben-admin/commit/8eba7fb52786d1977e4cb7b67673d74c91c5c827))
- **table:** fix tree node align ([1e61da6](https://github.com/anncwb/vue-vben-admin/commit/1e61da644f65a79ce10fde98ee017aba7d36be10)), closes [#829](https://github.com/anncwb/vue-vben-admin/issues/829)
- **table:** getDataSource not worked on empty data ([e78af6f](https://github.com/anncwb/vue-vben-admin/commit/e78af6f228e25f052dc4c5a1859a6db50e0b112e)), closes [#752](https://github.com/anncwb/vue-vben-admin/issues/752)
- **table:** global configuration accidentally modified ([b4a3f93](https://github.com/anncwb/vue-vben-admin/commit/b4a3f936cd19bf1fff3a331bacad60e79d2d6c22))
- **table:** param of `handleSearchInfoFn` ([791b323](https://github.com/anncwb/vue-vben-admin/commit/791b323dbd30acd7fabfe9c3fb6e528916311ffd))
- **table:** recursive updateTableDataRecord ([#1024](https://github.com/anncwb/vue-vben-admin/issues/1024)) ([72f953c](https://github.com/anncwb/vue-vben-admin/commit/72f953c8d3413a7f5482793258503017a81cc759))
- auto remove script dom in `useScript` ([a544dd3](https://github.com/anncwb/vue-vben-admin/commit/a544dd3e589329339177dad3d5c1f75dd6e6f0ca))
- fix iframe heigth error ([#1012](https://github.com/anncwb/vue-vben-admin/issues/1012)) ([d76cfd7](https://github.com/anncwb/vue-vben-admin/commit/d76cfd7f809ba48880c950a64cb43a5c9c44176c))
- Fix the invalid hot update of BasicButton when changing style outside ([#1016](https://github.com/anncwb/vue-vben-admin/issues/1016)) ([be2d11d](https://github.com/anncwb/vue-vben-admin/commit/be2d11d5d344a508e94abe3534726c80e1f1f271))
- style property of actionColOpt is invalid ([#997](https://github.com/anncwb/vue-vben-admin/issues/997)) ([225bd4c](https://github.com/anncwb/vue-vben-admin/commit/225bd4c39de377d93c605f33bfdf3d8fd565f12b))
- the position of tinymce upload image is wrong ([#1015](https://github.com/anncwb/vue-vben-admin/issues/1015)) ([2fd0fd2](https://github.com/anncwb/vue-vben-admin/commit/2fd0fd281e65d6d2551478c5f19250347dc14062))
- **api-select:** fix `options-change` event data ([897bed9](https://github.com/anncwb/vue-vben-admin/commit/897bed97295a0b9101d33102340749689a4368de))
- **api-tree-select:** auto load data if necessary ([1b3058f](https://github.com/anncwb/vue-vben-admin/commit/1b3058f8253effe974feaf08a12250a111ab58c0))
- **api-tree-select:** auto reload while `params` changed ([c734f68](https://github.com/anncwb/vue-vben-admin/commit/c734f6858daea6d11cd517463b06fcce58744947))
- **api-tree-select:** fix `event` checked in form ([d9d0071](https://github.com/anncwb/vue-vben-admin/commit/d9d00714011fa7914c61f990ce1159351ee21a1a))
- **basic-tree:** `checkedKeys` not worked with `search` ([b06a7ab](https://github.com/anncwb/vue-vben-admin/commit/b06a7ab77b99abee63dd55770ffd55b594ee42f9)), closes [#915](https://github.com/anncwb/vue-vben-admin/issues/915)
- **code-editor:** `value` not support use as `v-model` ([8832a07](https://github.com/anncwb/vue-vben-admin/commit/8832a074dceb44f057c87289d3a99feef58c08fd)), closes [#933](https://github.com/anncwb/vue-vben-admin/issues/933)
- **countdown-input:** add `slots` support ([a764a95](https://github.com/anncwb/vue-vben-admin/commit/a764a95ae9a6cff831f75aa97b00724cadc48e92))
- **dark-theme:** alert color in dark-theme ([9b7ede0](https://github.com/anncwb/vue-vben-admin/commit/9b7ede09b9efe4d5a15ab0cdeadac480a29c0f62))
- **dark-theme:** bgcolor of `selected tree node` in dark theme ([8cf004a](https://github.com/anncwb/vue-vben-admin/commit/8cf004a5f59895e2487c3a350c83000e585b897e)), closes [#949](https://github.com/anncwb/vue-vben-admin/issues/949)
- **dark-theme:** disabled link `button` color ([4281216](https://github.com/anncwb/vue-vben-admin/commit/42812162c46832ce4d3e332bd579c042309115bc))
- **dark-theme:** fixed `TreeSelect` & `DatePicker` theme ([d1e0e8b](https://github.com/anncwb/vue-vben-admin/commit/d1e0e8bcea1c168631222989969e14f7d0d1b6a4)), closes [#955](https://github.com/anncwb/vue-vben-admin/issues/955)
- **dark-theme:** style for checked tree nodes ([662b576](https://github.com/anncwb/vue-vben-admin/commit/662b576ac2088247cb58e295378f228462508a37))
- **demo:** account page form validation ([8702965](https://github.com/anncwb/vue-vben-admin/commit/87029650570e470431fb94d35a273c5d07a73135))
- **demo:** fix display problem of editable table with `apiSelect` ([535bddd](https://github.com/anncwb/vue-vben-admin/commit/535bdddf91785e20295c18cf80c8a22cc2172681))
- **demo:** fix roles mock data ([c375e32](https://github.com/anncwb/vue-vben-admin/commit/c375e32305eae5128e09ad1bda39ce0cc6afd790))
- **demo:** menu `error-log` link to 404 page ([341bd63](https://github.com/anncwb/vue-vben-admin/commit/341bd633d8ed38a5a357db8f97166c2eba2895d3))
- **demo:** multi-modal used with dynamic component ([e1c4723](https://github.com/anncwb/vue-vben-admin/commit/e1c47233edf7675aede6d5f023726945a510ddf7))
- **echarts:** fix graphic config cannot be used in echarts options ([#959](https://github.com/anncwb/vue-vben-admin/issues/959)) ([525484e](https://github.com/anncwb/vue-vben-admin/commit/525484e7a409b032d22231f90a92e700ef4290ae))
- **form:** fix `validate` promise catch ([571f281](https://github.com/anncwb/vue-vben-admin/commit/571f28138f782553eb39cda2d632e5ac1aa1e145))
- **form:** remove console error for `setFieldsValue` ([8d185bb](https://github.com/anncwb/vue-vben-admin/commit/8d185bb5841c83eb49c78e8342e65067aa6f9b80)), closes [#952](https://github.com/anncwb/vue-vben-admin/issues/952)
- **formItem:** Fix labelcol type mismatch ([#903](https://github.com/anncwb/vue-vben-admin/issues/903)) ([03b17a8](https://github.com/anncwb/vue-vben-admin/commit/03b17a8f8bdb50322aa10e3b614bcc40b9e9dcc8))
- **img-rotate-drag-verify:** fix `resume` method support ([32d64db](https://github.com/anncwb/vue-vben-admin/commit/32d64dbe816a0afda6ee9e91863199afb3e7b48e)), closes [#946](https://github.com/anncwb/vue-vben-admin/issues/946)
- **login:** fix `auto fill` style in dark-theme ([cebc6a5](https://github.com/anncwb/vue-vben-admin/commit/cebc6a590e1a19af7380a55aed43b23af274df0a))
- **modal:** ensure that props are passed correctly,fix [#897](https://github.com/anncwb/vue-vben-admin/issues/897) ([ae7821e](https://github.com/anncwb/vue-vben-admin/commit/ae7821e29690bea8934ea724bfd0ae4e2cf30c77))
- **modal:** fixed `fullscreen` not worked ([5baaa58](https://github.com/anncwb/vue-vben-admin/commit/5baaa58581f22a915cda9fa39e4cb9f094254d3b)), closes [#918](https://github.com/anncwb/vue-vben-admin/issues/918)
- **model:** auto validate on value change ([f844017](https://github.com/anncwb/vue-vben-admin/commit/f8440175f35076073c9f53483cf6c0164d427ff4)), closes [#920](https://github.com/anncwb/vue-vben-admin/issues/920)
- **multiple-tab:** ignore login page ([1e63379](https://github.com/anncwb/vue-vben-admin/commit/1e63379088e1d7c823f29f607ab49d62ca22cb25))
- **page-wrapper:** fix `class` not working ([8879ae8](https://github.com/anncwb/vue-vben-admin/commit/8879ae8d773e8dc4c252c4234eefeab9bc135a30))
- **perm-guard:** Fix the problem that the routing query is lost after refreshing the page ([#941](https://github.com/anncwb/vue-vben-admin/issues/941)) ([9c4889f](https://github.com/anncwb/vue-vben-admin/commit/9c4889f0859bc60decf0ef40c383c1946de1d68a))
- **qrcode:** Fix the problem that the QR code cannot be dynamically generated ([#974](https://github.com/anncwb/vue-vben-admin/issues/974)) ([fe4eae3](https://github.com/anncwb/vue-vben-admin/commit/fe4eae37146068f01ba08f033e0c2e8bd03e48b5))
- **style:** fix checkbox-checked css in dark mode ([d3f08e3](https://github.com/anncwb/vue-vben-admin/commit/d3f08e37c5b6e46f33d525e9ef4b4c235d77a192))
- **table:** `value` show problem in editable cell ([61ce25b](https://github.com/anncwb/vue-vben-admin/commit/61ce25be1b40d7a0e26205ca6a6757c6c43fc21e)), closes [#922](https://github.com/anncwb/vue-vben-admin/issues/922)
- **table:** component shown in `fullscreen` mode ([a07ab6d](https://github.com/anncwb/vue-vben-admin/commit/a07ab6d7aa1060f856649a9bdbec9dfa50b14f26))
- **table:** editable cell display with validation ([202aa42](https://github.com/anncwb/vue-vben-admin/commit/202aa42b8d5a94e84ad386bcf7feab96926c70dd)), closes [#953](https://github.com/anncwb/vue-vben-admin/issues/953)
- **table:** fix `dataPicker` show in `fullscreen` mode ([a5a9b3f](https://github.com/anncwb/vue-vben-admin/commit/a5a9b3fb34c64b6ea9c9ab3d58045f6e5963952b))
- **table:** fix `getSelectRows` for treeTable ([f2b8bb4](https://github.com/anncwb/vue-vben-admin/commit/f2b8bb43a0b9172b9ef9ced8e83bf91143a091d9)), closes [#1003](https://github.com/anncwb/vue-vben-admin/issues/1003)
- **table:** fix `pagination` props working ([e327893](https://github.com/anncwb/vue-vben-admin/commit/e32789373eb5b1b531572b59692bf552dac365dc))
- **table:** fix editable cell not support `ellipsis` ([4bb506f](https://github.com/anncwb/vue-vben-admin/commit/4bb506fb1f6ac7d246f8792d29f337ec003ff426)), closes [#944](https://github.com/anncwb/vue-vben-admin/issues/944)
- **table:** fix expand style ([14fb21d](https://github.com/anncwb/vue-vben-admin/commit/14fb21d0b7b9ac69c7b3c463de6d709bd5713d14)), closes [#969](https://github.com/anncwb/vue-vben-admin/issues/969)
- **table:** fix tableSettings popup in fullscreen mode ([dce3fb0](https://github.com/anncwb/vue-vben-admin/commit/dce3fb0f20516aaf4817281f5d08109e53a73ecb))
- **tree:** fix `checkAll` effects `disabled` node ([ddd1893](https://github.com/anncwb/vue-vben-admin/commit/ddd1893b113e13786037522341abb2e75f8f9d5b))
- ensure PAGE_NOT_FOUND_ROUTE exist ([87583c8](https://github.com/anncwb/vue-vben-admin/commit/87583c8b54d335ddf1c416859ef62bbde189c809))
- expandIcon slot of BasicTable component is invalid ([#975](https://github.com/anncwb/vue-vben-admin/issues/975)) ([98c206d](https://github.com/anncwb/vue-vben-admin/commit/98c206d9c9661a18dde4ec7782cbe1eb6e62ebeb))
- fix homePage affix error ([c117802](https://github.com/anncwb/vue-vben-admin/commit/c1178027f0fab2791d02efcd7c52beff5fc7dc25))
- Fix vite profile hot update error reporting ([#968](https://github.com/anncwb/vue-vben-admin/issues/968)) ([956ed2e](https://github.com/anncwb/vue-vben-admin/commit/956ed2e3f770cc9cf822ce80f71b1e7f179792fb))
- fixed moment locale config ([27207a7](https://github.com/anncwb/vue-vben-admin/commit/27207a78caccb04372e0275c5cee526ec460de0e))
- infinite redirect in `BACK` mode ([4b46a84](https://github.com/anncwb/vue-vben-admin/commit/4b46a84c2b85e8da799426c54b3381ae93183db4))
- resolving `Vue Router warn` ([237e65e](https://github.com/anncwb/vue-vben-admin/commit/237e65eac909368c4b4857da6c8deb1dc18e7684))
- typo ([#980](https://github.com/anncwb/vue-vben-admin/issues/980)) ([7e6a89f](https://github.com/anncwb/vue-vben-admin/commit/7e6a89ffeb8c63467908d5807d3d7c4761620ee3))
- typo for utils/env ([#1004](https://github.com/anncwb/vue-vben-admin/issues/1004)) ([e8eefd1](https://github.com/anncwb/vue-vben-admin/commit/e8eefd1bca41c181ec6395bf1d087d2018e2b1f1))
- **table:** selection-change not triggered on row click ([6f845b5](https://github.com/anncwb/vue-vben-admin/commit/6f845b53bdc4c33fbca3e65f10f64c63166bed0e))
- **table:** treeTable editable error ([4ae39c5](https://github.com/anncwb/vue-vben-admin/commit/4ae39c53b49532fc6c31086a31e30429d2e236ed)), closes [#811](https://github.com/anncwb/vue-vben-admin/issues/811)
- **table-action:** fix `circle` button style ([db7254a](https://github.com/anncwb/vue-vben-admin/commit/db7254a5e0ac6d10a7ea37334ad523b150facb19))
- **table-action:** fixed icon `margin` without label ([dc51e6a](https://github.com/anncwb/vue-vben-admin/commit/dc51e6a8d4e4f2c97b387b37959944c9bb49d779))
- **table-action:** incorrect button color of `disabled` state ([0f28e80](https://github.com/anncwb/vue-vben-admin/commit/0f28e803d0b65537216cd9f40ad5cad63c20db9b)), closes [#891](https://github.com/anncwb/vue-vben-admin/issues/891)
- **table-action:** stopButtonPropagation not working ([9b8f165](https://github.com/anncwb/vue-vben-admin/commit/9b8f165a365758d001e6d86ae7afe4ae3316d485))
- **tree:** fixed `checkedKeys` with `search` mode ([f707541](https://github.com/anncwb/vue-vben-admin/commit/f707541dda78146bda89814ddccbb259d9f5d8a2))
- **upload:** ensure the value type is correct ([05329ce](https://github.com/anncwb/vue-vben-admin/commit/05329ce9501eb899a0bbb45320e5807c83372317))
- **useWatermark:** fix `func` call `createWatermark` call `clear` to resizeEvent removed ([#901](https://github.com/anncwb/vue-vben-admin/issues/901)) ([a1d956d](https://github.com/anncwb/vue-vben-admin/commit/a1d956d3697cd07e0ba8910768f2a73e55f18491))
- `menuSetting` can not set collapsed to false as default ([808291b](https://github.com/anncwb/vue-vben-admin/commit/808291b503d59e3026f5f0b5e7a38b9c69bcc451))
- ensure that safari is running properly, fix [#875](https://github.com/anncwb/vue-vben-admin/issues/875) ([dafcdd8](https://github.com/anncwb/vue-vben-admin/commit/dafcdd898caae57104f1155b0ec660ea333e7b19))
- **table:** scrollbar style ([d8c3820](https://github.com/anncwb/vue-vben-admin/commit/d8c38207c08510d805a8dc66ffbba210e0cf4215))
- **tailwindcss:** remove console warnings ([acacb32](https://github.com/anncwb/vue-vben-admin/commit/acacb32bb592345cd0a90b4bbeb60a9b6ab1ac3c))
- `hasPermission` not work in `ROLE` Mode ([76a5f87](https://github.com/anncwb/vue-vben-admin/commit/76a5f87c0ce871cca48b9e4c32331353a796e7d2))
- fix antdv console warning ([480cfb9](https://github.com/anncwb/vue-vben-admin/commit/480cfb914e78c06eb7784e33465ed91b7d4c3eee))
- fix defHttp baseUrl work ([d5f9919](https://github.com/anncwb/vue-vben-admin/commit/d5f9919b60fdd7d5c435129e8db519c0bbd37529))
- multi windows token sharing ([e5f3788](https://github.com/anncwb/vue-vben-admin/commit/e5f37885ffb32d04d244f0ef39ac660dda6b71e1)), closes [#761](https://github.com/anncwb/vue-vben-admin/issues/761)
- routes filter can't effective when permission mode set to ROUTE_MAPPING ([#836](https://github.com/anncwb/vue-vben-admin/issues/836)) ([3871204](https://github.com/anncwb/vue-vben-admin/commit/3871204d08d481b8984440cd60bbf2bacb58d063))
- support various vite modes of build, not just production ([#832](https://github.com/anncwb/vue-vben-admin/issues/832)) ([95c16a5](https://github.com/anncwb/vue-vben-admin/commit/95c16a5d26f9fd9a1d11894afe1146ca495eee93))
- user drop-down event key loss ([20d7a25](https://github.com/anncwb/vue-vben-admin/commit/20d7a25eb898a5c28351ff269b93bf104b8ac10e))
- user dropdown event response failure ([c73694a](https://github.com/anncwb/vue-vben-admin/commit/c73694ab8b0b6242c4d5e0f30bc7ebe3d69b4e33))
- **upload:** make sure to carry custom parameters, fix [#802](https://github.com/anncwb/vue-vben-admin/issues/802) ([c4b22a2](https://github.com/anncwb/vue-vben-admin/commit/c4b22a225d0088d87be0c0068f543366312521db))
- **utils:** The date function gets a non-date when the parameter is null ([#954](https://github.com/anncwb/vue-vben-admin/issues/954)) ([350c85a](https://github.com/anncwb/vue-vben-admin/commit/350c85accf5033cc5a21b71bc36d5b7a74eb2573))
### Features
- **use-loading:** add `setTip` method ([26d9476](https://github.com/anncwb/vue-vben-admin/commit/26d9476caff41cc355190604af42e0bd2ef0a353))
- Added support for tailwindcss night mode mechanism ([#998](https://github.com/anncwb/vue-vben-admin/issues/998)) ([189bc6f](https://github.com/anncwb/vue-vben-admin/commit/189bc6feb3f2860be8c531dd1ca996f3a2cff018))
- **api-select:** clear options before fetch ([9cf070d](https://github.com/anncwb/vue-vben-admin/commit/9cf070dd6305bb69a67ab6be85ef00bddc86fda0))
- **api-tree-select:** add `api` options to tree-select ([d81db89](https://github.com/anncwb/vue-vben-admin/commit/d81db890dfeb533d60f378ddb86f8ac50a31252b))
- **avatar-cropper:** add action tooltip ([6cbac4b](https://github.com/anncwb/vue-vben-admin/commit/6cbac4b7ece60a1a7c1fda931cfffce42dfe3e51))
- **avatar-cropper:** more props added ([b96ea07](https://github.com/anncwb/vue-vben-admin/commit/b96ea0753bfd769693a368cf1e3d8316688c0dcb))
- **axios:** add `withToken` option ([c99cf5e](https://github.com/anncwb/vue-vben-admin/commit/c99cf5e53f057cdc332ab6c0635adf9c2d27de29))
- **axios:** use `defHttp` like `axios` ([49f39de](https://github.com/anncwb/vue-vben-admin/commit/49f39de7b40e3ec8343bdeaf3eb00fd79d395746)), closes [#850](https://github.com/anncwb/vue-vben-admin/issues/850)
- **basic-table:** add `ApiTreeSelect` edit component ([52af1dd](https://github.com/anncwb/vue-vben-admin/commit/52af1dd0d494e66c0af20f886dcc2b983cbb096f))
- **basic-upload:** `value` support v-model ([16c5d32](https://github.com/anncwb/vue-vben-admin/commit/16c5d327f1209f7c7437acde2ab0fa031da6a641))
- **basic-upload:** add preview-delete event ([49e72a8](https://github.com/anncwb/vue-vben-admin/commit/49e72a8e76b78fe54e19de9e23d7c72a19427f01)), closes [#835](https://github.com/anncwb/vue-vben-admin/issues/835)
- **demo:** add `async-validator` demo ([8b4b767](https://github.com/anncwb/vue-vben-admin/commit/8b4b767f4ca78f7c6f7586d8ba662552c2b7bb51))
- **demo:** add basicTree with async data expand all ([5421211](https://github.com/anncwb/vue-vben-admin/commit/542121129eb5bf65f61e7b484835591756c80f04))
- **demo:** add route multi tabs show ([0e414ba](https://github.com/anncwb/vue-vben-admin/commit/0e414ba3c10b4e47a85feb1a38cae66c815719d8)), closes [#817](https://github.com/anncwb/vue-vben-admin/issues/817)
- **demo:** add search demo for apiSelect ([41e6d94](https://github.com/anncwb/vue-vben-admin/commit/41e6d94b3b64dc0d40b7ec57ecfaa4d966f202ae))
- **demo:** demo default expanded tree table ([5f1a6cd](https://github.com/anncwb/vue-vben-admin/commit/5f1a6cdc599d5840df2dfebdaad029aac093cd81))
- **demo:** multi-modal in one page usage ([7a7dab0](https://github.com/anncwb/vue-vben-admin/commit/7a7dab0c4b3602b7bd3e9381408e4168d7494c52))
- **menu:** the route is automatically mapped to the menu ([913c22c](https://github.com/anncwb/vue-vben-admin/commit/913c22c84fc9a4221fdfff6bae0e79a68fd09b17))
- **modal:** add `tooltip` for action buttons ([c3b9076](https://github.com/anncwb/vue-vben-admin/commit/c3b907656a5fad7a9b241562179f7a0f6fe0e6f0))
- **notice-list:** add `pagination` support ([c16be2c](https://github.com/anncwb/vue-vben-admin/commit/c16be2c499d90126dfa35d699da9294c21a4ab48)), closes [#894](https://github.com/anncwb/vue-vben-admin/issues/894)
- **preview:** add more features ([e23bd26](https://github.com/anncwb/vue-vben-admin/commit/e23bd2696da945291a9b652f1af39ad1936f376b))
- customized user home page ([0a3683a](https://github.com/anncwb/vue-vben-admin/commit/0a3683a186ab55d34a12a5a3c6d794dfa1094ad4))
- **param-menu:** feature: menu with params ([#845](https://github.com/anncwb/vue-vben-admin/issues/845)) ([48fcd76](https://github.com/anncwb/vue-vben-admin/commit/48fcd7684cabff66e8648b71527c6cb4ce7d03be))
- **route:** add `hidePathForChildren` in `meta` ([d52b0de](https://github.com/anncwb/vue-vben-admin/commit/d52b0de83e69f7505c28e6f59ec84bbe526ecd0d))
- **table:** add `headerTop` slot ([540423e](https://github.com/anncwb/vue-vben-admin/commit/540423ecf741a815d28d7a6baa1541ac884efe95)), closes [#881](https://github.com/anncwb/vue-vben-admin/issues/881)
- **table:** support asynchrony in beforeFetch and afterFetch ([#827](https://github.com/anncwb/vue-vben-admin/issues/827)) ([749ba5c](https://github.com/anncwb/vue-vben-admin/commit/749ba5c1daf459625518937c239787b756c0a780))
- **table-action:** support `tooltip` option ([5fab267](https://github.com/anncwb/vue-vben-admin/commit/5fab267a69600fdf5d7a7f9e4d9fff859d09dede)), closes [#848](https://github.com/anncwb/vue-vben-admin/issues/848)
- **tree:** add `insertNodesByKey` method ([5a20df4](https://github.com/anncwb/vue-vben-admin/commit/5a20df45ad36b523d48bf7fe11bdb10a6d03df64))
- add Tree LoadData demo ([9298b3c](https://github.com/anncwb/vue-vben-admin/commit/9298b3c988c10b81d83430ca31b9ce1d98a3fad9))
- routers support `ignoreRoute` option ([72ac240](https://github.com/anncwb/vue-vben-admin/commit/72ac240f2858cd74cb62b7647ca89d63bb71d247))
### Performance Improvements
- improve legacy compatibility ([e2664f6](https://github.com/anncwb/vue-vben-admin/commit/e2664f60029f03642f8b1a6afa9b1998705fce37))
- **menu:** Optimize the style of the bottom collapse button in the Mix menu layout ([#896](https://github.com/anncwb/vue-vben-admin/issues/896)) ([6f83070](https://github.com/anncwb/vue-vben-admin/commit/6f830703a2607c33e5d25d6d17d0e453fc2fac2e))
- image compression configuration optimization ([cf840e3](https://github.com/anncwb/vue-vben-admin/commit/cf840e3e73b9572de0ba7bf7b32d83f6a353a8ad))
- **icon:** remove Icon component global registration ([59d3e8c](https://github.com/anncwb/vue-vben-admin/commit/59d3e8c80f72f029f2b90432b31901ad54ed1ee4))
- **pagewrapper:** 优化 PageWrapper 的高度自适应表现使用 getViewportOffset 替代 useContentViewHeight ([#792](https://github.com/anncwb/vue-vben-admin/issues/792)) ([4d8e398](https://github.com/anncwb/vue-vben-admin/commit/4d8e39857ea59fff99e69832b4a8cabf3a424c24))
- **router:** reduce the number of guard files ([327d71b](https://github.com/anncwb/vue-vben-admin/commit/327d71b8fb4907ae971d040f6b84bbecb0a6d897))
- **scrollbar:** scrollbar update when slot changed ([e9e51b2](https://github.com/anncwb/vue-vben-admin/commit/e9e51b2fdc879a66d8df08504a0955c9c21e3e27))
### Reverts
- **axios:** remove baseUrl config ([61d4efd](https://github.com/anncwb/vue-vben-admin/commit/61d4efd55a8b4f09990b5f1888e23ead43958164))
## [2.6.1](https://github.com/anncwb/vue-vben-admin/compare/v2.6.0...v2.6.1) (2021-07-19)
### Bug Fixes
- **api-select:** fix `options-change` event data ([897bed9](https://github.com/anncwb/vue-vben-admin/commit/897bed97295a0b9101d33102340749689a4368de))
- **api-tree-select:** auto load data if necessary ([1b3058f](https://github.com/anncwb/vue-vben-admin/commit/1b3058f8253effe974feaf08a12250a111ab58c0))
- **api-tree-select:** fix `event` checked in form ([d9d0071](https://github.com/anncwb/vue-vben-admin/commit/d9d00714011fa7914c61f990ce1159351ee21a1a))
- **app-search:** exclude hidden items ([faf5c9f](https://github.com/anncwb/vue-vben-admin/commit/faf5c9fd7ea40c407419a5a5c473f9b0c32c2a53))
- **app-search:** exclude items by `hideChildrenInMenu` ([02d3dca](https://github.com/anncwb/vue-vben-admin/commit/02d3dca57efedc1322ae38e3f432cf1f6c2cf839))
- **basic-tree:** `checkedKeys` not worked with `search` ([b06a7ab](https://github.com/anncwb/vue-vben-admin/commit/b06a7ab77b99abee63dd55770ffd55b594ee42f9)), closes [#915](https://github.com/anncwb/vue-vben-admin/issues/915)
- **breadcrumb:** `redirect` not worked ([f5e31fe](https://github.com/anncwb/vue-vben-admin/commit/f5e31febbd18372a34166cac390b1d9b914fe80e))
- **code-editor:** `value` not support use as `v-model` ([8832a07](https://github.com/anncwb/vue-vben-admin/commit/8832a074dceb44f057c87289d3a99feef58c08fd)), closes [#933](https://github.com/anncwb/vue-vben-admin/issues/933)
- **countdown-input:** add `slots` support ([a764a95](https://github.com/anncwb/vue-vben-admin/commit/a764a95ae9a6cff831f75aa97b00724cadc48e92))
- **CountTo:** Fix displaying empty string when the value is 0 ([#864](https://github.com/anncwb/vue-vben-admin/issues/864)) ([82eb72b](https://github.com/anncwb/vue-vben-admin/commit/82eb72bbced931ba7f50069211f9511035ad09f4))
- **demo:** `setup` page route config ([d5d5c4b](https://github.com/anncwb/vue-vben-admin/commit/d5d5c4b4bfb3e3a5e54f9993966adc46a09a8b90))
- **demo:** add mock data `account detail` route ([993e19d](https://github.com/anncwb/vue-vben-admin/commit/993e19dcc319e2b4c68df2ab76174b7b4d7b0428)), closes [#858](https://github.com/anncwb/vue-vben-admin/issues/858)
- **demo:** fix display problem of editable table with `apiSelect` ([535bddd](https://github.com/anncwb/vue-vben-admin/commit/535bdddf91785e20295c18cf80c8a22cc2172681))
- **demo:** form pages support `keepAlive` ([9228282](https://github.com/anncwb/vue-vben-admin/commit/9228282ae27daaa246f42e441e27b1b05eb30464))
- **demo:** resolve `key not exist` warnings ([45a94e4](https://github.com/anncwb/vue-vben-admin/commit/45a94e41c1397b84d08373f84f766204d2488714))
- **form:** fix `suffix` slot style ([a9bbed1](https://github.com/anncwb/vue-vben-admin/commit/a9bbed19739376ab2bf67a14b04e872f14ca84cc))
- **formItem:** Fix labelcol type mismatch ([#903](https://github.com/anncwb/vue-vben-admin/issues/903)) ([03b17a8](https://github.com/anncwb/vue-vben-admin/commit/03b17a8f8bdb50322aa10e3b614bcc40b9e9dcc8))
- **markdown:** resolving markdown exceptions ([d95815b](https://github.com/anncwb/vue-vben-admin/commit/d95815b5031984e224140eb1b1d46e2dbf80abc1))
- **markdown:** set `value` error ([35e1347](https://github.com/anncwb/vue-vben-admin/commit/35e1347029e29a83a9648b6b398e6863cc40fca9))
- **menu:** display error when contains hidden items ([5ceeefd](https://github.com/anncwb/vue-vben-admin/commit/5ceeefd17d9ddc0e8844b900069b100f24d9c00e))
- **menu:** fix mix-menu incorrect jumping in `hover` mode ([cad021c](https://github.com/anncwb/vue-vben-admin/commit/cad021c34b71fa109640af75a0c2b72179e9e257))
- **mix-sider:** fix mix-sider hover logic ([0595a72](https://github.com/anncwb/vue-vben-admin/commit/0595a72da9c666af81a0916663e8e6a014e6fa69))
- **modal:** ensure that props are passed correctly,fix [#897](https://github.com/anncwb/vue-vben-admin/issues/897) ([ae7821e](https://github.com/anncwb/vue-vben-admin/commit/ae7821e29690bea8934ea724bfd0ae4e2cf30c77))
- **modal:** fixed `fullscreen` not worked ([5baaa58](https://github.com/anncwb/vue-vben-admin/commit/5baaa58581f22a915cda9fa39e4cb9f094254d3b)), closes [#918](https://github.com/anncwb/vue-vben-admin/issues/918)
- **model:** auto validate on value change ([f844017](https://github.com/anncwb/vue-vben-admin/commit/f8440175f35076073c9f53483cf6c0164d427ff4)), closes [#920](https://github.com/anncwb/vue-vben-admin/issues/920)
- **table:** fix index column style ([c7c0a7e](https://github.com/anncwb/vue-vben-admin/commit/c7c0a7e4c88a895000b1621981e4d4b2020c64b1))
- **table:** `value` show problem in editable cell ([61ce25b](https://github.com/anncwb/vue-vben-admin/commit/61ce25be1b40d7a0e26205ca6a6757c6c43fc21e)), closes [#922](https://github.com/anncwb/vue-vben-admin/issues/922)
- **table-action:** fixed icon `margin` without label ([dc51e6a](https://github.com/anncwb/vue-vben-admin/commit/dc51e6a8d4e4f2c97b387b37959944c9bb49d779))
- **tree:** fixed `checkedKeys` with `search` mode ([f707541](https://github.com/anncwb/vue-vben-admin/commit/f707541dda78146bda89814ddccbb259d9f5d8a2))
- fix homePage affix error ([c117802](https://github.com/anncwb/vue-vben-admin/commit/c1178027f0fab2791d02efcd7c52beff5fc7dc25))
- **table-action:** fix `circle` button style ([db7254a](https://github.com/anncwb/vue-vben-admin/commit/db7254a5e0ac6d10a7ea37334ad523b150facb19))
- `menuSetting` can not set collapsed to false as default ([808291b](https://github.com/anncwb/vue-vben-admin/commit/808291b503d59e3026f5f0b5e7a38b9c69bcc451))
- ensure PAGE_NOT_FOUND_ROUTE exist ([87583c8](https://github.com/anncwb/vue-vben-admin/commit/87583c8b54d335ddf1c416859ef62bbde189c809))
- ensure that safari is running properly, fix [#875](https://github.com/anncwb/vue-vben-admin/issues/875) ([dafcdd8](https://github.com/anncwb/vue-vben-admin/commit/dafcdd898caae57104f1155b0ec660ea333e7b19))
- infinite redirect in `BACK` mode ([4b46a84](https://github.com/anncwb/vue-vben-admin/commit/4b46a84c2b85e8da799426c54b3381ae93183db4))
- **multiple-tab:** ignore login page ([1e63379](https://github.com/anncwb/vue-vben-admin/commit/1e63379088e1d7c823f29f607ab49d62ca22cb25))
- resolving `Vue Router warn` ([237e65e](https://github.com/anncwb/vue-vben-admin/commit/237e65eac909368c4b4857da6c8deb1dc18e7684))
- **table:** fix tree node align ([1e61da6](https://github.com/anncwb/vue-vben-admin/commit/1e61da644f65a79ce10fde98ee017aba7d36be10)), closes [#829](https://github.com/anncwb/vue-vben-admin/issues/829)
- **table:** scrollbar style ([d8c3820](https://github.com/anncwb/vue-vben-admin/commit/d8c38207c08510d805a8dc66ffbba210e0cf4215))
- **table-action:** incorrect button color of `disabled` state ([0f28e80](https://github.com/anncwb/vue-vben-admin/commit/0f28e803d0b65537216cd9f40ad5cad63c20db9b)), closes [#891](https://github.com/anncwb/vue-vben-admin/issues/891)
- **upload:** ensure the value type is correct ([05329ce](https://github.com/anncwb/vue-vben-admin/commit/05329ce9501eb899a0bbb45320e5807c83372317))
- **useWatermark:** fix `func` call `createWatermark` call `clear` to resizeEvent removed ([#901](https://github.com/anncwb/vue-vben-admin/issues/901)) ([a1d956d](https://github.com/anncwb/vue-vben-admin/commit/a1d956d3697cd07e0ba8910768f2a73e55f18491))
### Features
- **api-tree-select:** add `api` options to tree-select ([d81db89](https://github.com/anncwb/vue-vben-admin/commit/d81db890dfeb533d60f378ddb86f8ac50a31252b))
- **basic-table:** add `ApiTreeSelect` edit component ([52af1dd](https://github.com/anncwb/vue-vben-admin/commit/52af1dd0d494e66c0af20f886dcc2b983cbb096f))
- **demo:** multi-modal in one page usage ([7a7dab0](https://github.com/anncwb/vue-vben-admin/commit/7a7dab0c4b3602b7bd3e9381408e4168d7494c52))
- customized user home page ([0a3683a](https://github.com/anncwb/vue-vben-admin/commit/0a3683a186ab55d34a12a5a3c6d794dfa1094ad4))
- **api-select:** clear options before fetch ([9cf070d](https://github.com/anncwb/vue-vben-admin/commit/9cf070dd6305bb69a67ab6be85ef00bddc86fda0))
- **demo:** add basicTree with async data expand all ([5421211](https://github.com/anncwb/vue-vben-admin/commit/542121129eb5bf65f61e7b484835591756c80f04))
- **demo:** add search demo for apiSelect ([41e6d94](https://github.com/anncwb/vue-vben-admin/commit/41e6d94b3b64dc0d40b7ec57ecfaa4d966f202ae))
- **demo:** demo default expanded tree table ([5f1a6cd](https://github.com/anncwb/vue-vben-admin/commit/5f1a6cdc599d5840df2dfebdaad029aac093cd81))
- **notice-list:** add `pagination` support ([c16be2c](https://github.com/anncwb/vue-vben-admin/commit/c16be2c499d90126dfa35d699da9294c21a4ab48)), closes [#894](https://github.com/anncwb/vue-vben-admin/issues/894)
- **table:** add `headerTop` slot ([540423e](https://github.com/anncwb/vue-vben-admin/commit/540423ecf741a815d28d7a6baa1541ac884efe95)), closes [#881](https://github.com/anncwb/vue-vben-admin/issues/881)
### Performance Improvements
- **menu:** Optimize the style of the bottom collapse button in the Mix menu layout ([#896](https://github.com/anncwb/vue-vben-admin/issues/896)) ([6f83070](https://github.com/anncwb/vue-vben-admin/commit/6f830703a2607c33e5d25d6d17d0e453fc2fac2e))
- image compression configuration optimization ([cf840e3](https://github.com/anncwb/vue-vben-admin/commit/cf840e3e73b9572de0ba7bf7b32d83f6a353a8ad))
# [2.6.0](https://github.com/anncwb/vue-vben-admin/compare/v2.5.2...v2.6.0) (2021-07-04) # [2.6.0](https://github.com/anncwb/vue-vben-admin/compare/v2.5.2...v2.6.0) (2021-07-04)
### Bug Fixes ### Bug Fixes
......
## 2.7.0(2021-08-03)
## (破坏性更新) Breaking changes
- 将项目`tailwindcss`还原回`windicss`,尝试了`tailwindcss`,问题可能还挺多,先切换回`windicss`提高开发效率,切换成本较低。
- 目前项目不兼容地方有
- `xl:!m-4` 之类的写法需要改为`!xl:m-4`,注意只有`!`这个不兼容,没用到则不用改
- 内存溢出问题可能还在(频率低,重启下即可,重启 vite 较快)
### ✨ Features
- **Preview** 添加新的属性及事件
- **Dark Theme** 新增对 tailwindcss 夜间模式的支持
- **其它** 为 useLoading 添加 setTip 方法
### 🐛 Bug Fixes ### 🐛 Bug Fixes
- **Table** - **ApiTreeSelect** 修复未能正确监听`params`变化的问题
- 修复滚动条样式问题 - **ImgRotateDragVerify** 修复组件`resume`方法无法调用的问题
- 修复树形表格的带有展开图标的单元格的内容对齐问题 - **TableAction** 修复 stopButtonPropagation 属性某些情况下不起作用的问题
- **PageWrapper** 修复`class`属性无效的问题
- **BasicTree** 修复`checkAll`方法会影响到`disabled`状态节点的问题
- **BasicTable**
- 修复可编辑单元格不支持`ellipsis`配置的问题
- 修复全屏模式下看不到子组件弹出层(popconfirm 以及 select、treeSelect 等编辑组件)的问题
- 修复启用`expandRowByClick`时,点击不可展开的行可能会导致样式错误的问题
- 修复`pagination`属性动态改变不生效的问题
- 修复`getSelectRows`不支持树形表格子级数据的问题
- **Dark Theme** 黑暗主题下的配色问题修正
- 修复`Tree`组件被选中节点的背景颜色
- 修复`Alert`组件的颜色配置
- 修复禁用状态下的`link`类型的按钮颜色问题
- 修复`Tree`已勾选的复选框的样式问题
- **其它** 修复 useScript 未能自动移除 script 节点的问题
## 2.6.1(2021-07-19)
### ✨ Features
- **NoticeList** 添加分页、超长自动省略、标题点击事件、标题删除线等功能
- **MixSider** 优化 Mix 菜单布局时 底部折叠按钮 的样式,与其它菜单布局时的风格保持一致
- **ApiTreeSelect** 扩展`antdv``TreeSelect`组件,支持远程数据源,用法类似`ApiSelect`
- **BasicTable**
- 新增`ApiTreeSelect`编辑组件
- 新增`headerTop`插槽 - 新增`headerTop`插槽
- **其它** 可以为不同的用户指定不同的后台首页:
-`getUserInfo`接口返回的用户信息中增加`homePath`字段(可选)即可为当前用户定制首页路径
### 🐛 Bug Fixes
- **BasicTable**
- 修复滚动条样式问题(移除了滚动样式补丁)
- 修复树形表格的带有展开图标的单元格的内容对齐问题
- 修复操作列的按钮在 disabled 状态下的颜色显示
- 修复可编辑单元格的值不能直接通过修改`dataSource`来更新显示的问题
- 修复使用`ApiSelect`编辑组件时的数据回显问题
- 修复在部分场景下编辑组件可能会报`onXXX`类型错误的问题
- **TableAction**
- 仅在 `action.tooltip`存在的情况下 才创建 Tooltip 组件
- 修复组件内的圆形按钮内容没有居中的问题
- **AppSearch** 修复可能会搜索隐藏菜单的问题 - **AppSearch** 修复可能会搜索隐藏菜单的问题
- **TableAction** 仅在 action.tooltip 存在的情况下 才包裹 Tooltip 组件
- **BasicUpload** 修复处理非`array`值时报错的问题 - **BasicUpload** 修复处理非`array`值时报错的问题
- **Form** 修复`FormItem``suffix`插槽样式问题 - **Form** 修复`FormItem``suffix`插槽样式问题
- **Menu** - **Menu**
- 修复左侧混合菜单的悬停触发逻辑 - 修复左侧混合菜单的悬停触发逻辑
- 修复顶栏菜单在显示包含需要隐藏的菜单项目时出错的问题 - 修复顶栏菜单在显示包含需要隐藏的菜单项目时出错的问题
- 修复悬停触发模式下左侧混合菜单会在没有子菜单且被激活时直接跳转路由 - 修复悬停触发模式下左侧混合菜单会在没有子菜单且被激活时直接跳转路由
- **Breadcrumb** 修复带有重定向的菜单点击无法跳转的问题
- **Markdown** 修复初始化异常以及不能正确地动态设置 value 的问题
- **Modal** 确保 props 正确被传递
- **MultipleTab** 修复可能会意外创建登录路由标签的问题
- **BasicTree** 修复搜索功能可能导致`checkedKeys`丢失的问题
- **CodeEditor** 修复 value 不支持 v-model 用法的问题
- **CountdownInput** 修复不支持`input`插槽的问题
- **ApiSelect** 修复`options-change`事件参数不是`select`所使用的标准`options`数据的问题
- **其它** - **其它**
- 修复菜单默认折叠的配置不起作用的问题 - 修复菜单默认折叠的配置不起作用的问题
- 修复`safari`浏览器报错导致网站打不开 - 修复`safari`浏览器报错导致网站打不开
- 修复在 window 上,拉取代码后 eslint 因 endOfLine 而保错问题 - 修复在 window 上,拉取代码后 eslint 因 endOfLine 而报错问题
- 修复因动态路由而产生的 `Vue Router warn`
### 🎫 Chores ### 🎫 Chores
......
<div align="center"> <a href="https://github.com/anncwb/vue-vben-admin"> <img alt="VbenAdmin Logo" width="200" height="200" src="https://anncwb.github.io/anncwb/images/logo.png"> </a> <br> <br> ## 更新 git 开源项目到本地
[![license](https://img.shields.io/github/license/anncwb/vue-vben-admin.svg)](LICENSE) ```
git checkout main
<h1>Vue vben admin</h1> git remote add ups https://github.com/anncwb/vue-vben-admin.git
</div> git fetch ups && git merge ups/main
解决冲突
```
**English** | [中文](./README.zh-CN.md) **English** | [中文](./README.zh-CN.md)
......
...@@ -34,7 +34,11 @@ export function wrapperEnv(envConf: Recordable): ViteEnv { ...@@ -34,7 +34,11 @@ export function wrapperEnv(envConf: Recordable): ViteEnv {
} catch (error) {} } catch (error) {}
} }
ret[envName] = realName; ret[envName] = realName;
if (typeof realName === 'string') {
process.env[envName] = realName; process.env[envName] = realName;
} else if (typeof realName === 'object') {
process.env[envName] = JSON.stringify(realName);
}
} }
return ret; return ret;
} }
......
...@@ -5,7 +5,7 @@ import vueJsx from '@vitejs/plugin-vue-jsx'; ...@@ -5,7 +5,7 @@ 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';
import { configMockPlugin } from './mock'; import { configMockPlugin } from './mock';
...@@ -33,6 +33,9 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { ...@@ -33,6 +33,9 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
vueJsx(), vueJsx(),
]; ];
// vite-plugin-windicss
vitePlugins.push(windiCSS());
// TODO // TODO
!isBuild && vitePlugins.push(configHmrPlugin()); !isBuild && vitePlugins.push(configHmrPlugin());
......
...@@ -33,14 +33,17 @@ export function configThemePlugin(isBuild: boolean): Plugin[] { ...@@ -33,14 +33,17 @@ export function configThemePlugin(isBuild: boolean): Plugin[] {
return s; return s;
case '.ant-steps-item-icon > .ant-steps-icon': case '.ant-steps-item-icon > .ant-steps-icon':
return s; return s;
case '.ant-select-item-option-selected:not(.ant-select-item-option-disabled)':
return s;
} }
return `[data-theme] ${s}`; return s.startsWith('[data-theme') ? s : `[data-theme] ${s}`;
}, },
colorVariables: [...getThemeColors(), ...colors], colorVariables: [...getThemeColors(), ...colors],
}), }),
antdDarkThemePlugin({ antdDarkThemePlugin({
preloadFiles: [ preloadFiles: [
path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/antd.less'), path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/antd.less'),
//path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/antd.dark.less'),
path.resolve(process.cwd(), 'src/design/index.less'), path.resolve(process.cwd(), 'src/design/index.less'),
], ],
filter: (id) => (isBuild ? !id.endsWith('antd.less') : true), filter: (id) => (isBuild ? !id.endsWith('antd.less') : true),
...@@ -48,8 +51,10 @@ export function configThemePlugin(isBuild: boolean): Plugin[] { ...@@ -48,8 +51,10 @@ export function configThemePlugin(isBuild: boolean): Plugin[] {
darkModifyVars: { darkModifyVars: {
...generateModifyVars(true), ...generateModifyVars(true),
'text-color': '#c9d1d9', 'text-color': '#c9d1d9',
'primary-1': 'rgb(255 255 255 / 8%)',
'text-color-base': '#c9d1d9', 'text-color-base': '#c9d1d9',
'component-background': '#151515', 'component-background': '#151515',
'heading-color': 'rgb(255 255 255 / 65%)',
// black: '#0e1117', // black: '#0e1117',
// #8b949e // #8b949e
'text-color-secondary': '#8b949e', 'text-color-secondary': '#8b949e',
...@@ -57,6 +62,20 @@ export function configThemePlugin(isBuild: boolean): Plugin[] { ...@@ -57,6 +62,20 @@ export function configThemePlugin(isBuild: boolean): Plugin[] {
// 'border-color-split': '#30363d', // 'border-color-split': '#30363d',
'item-active-bg': '#111b26', 'item-active-bg': '#111b26',
'app-content-background': 'rgb(255 255 255 / 4%)', 'app-content-background': 'rgb(255 255 255 / 4%)',
'tree-node-selected-bg': '#11263c',
'alert-success-border-color': '#274916',
'alert-success-bg-color': '#162312',
'alert-success-icon-color': '#49aa19',
'alert-info-border-color': '#153450',
'alert-info-bg-color': '#111b26',
'alert-info-icon-color': '#177ddc',
'alert-warning-border-color': '#594214',
'alert-warning-bg-color': '#2b2111',
'alert-warning-icon-color': '#d89614',
'alert-error-border-color': '#58181c',
'alert-error-bg-color': '#2a1215',
'alert-error-icon-color': '#a61d24',
}, },
}), }),
]; ];
......
...@@ -3,7 +3,7 @@ import { resultSuccess } from '../_util'; ...@@ -3,7 +3,7 @@ import { resultSuccess } from '../_util';
const demoList = (keyword) => { const demoList = (keyword) => {
const result = { const result = {
list: [], list: [] as any[],
}; };
for (let index = 0; index < 20; index++) { for (let index = 0; index < 20; index++) {
result.list.push({ result.list.push({
......
import { MockMethod } from 'vite-plugin-mock'; import { MockMethod } from 'vite-plugin-mock';
import { resultPageSuccess, resultSuccess } from '../_util'; import { resultError, resultPageSuccess, resultSuccess } from '../_util';
const accountList = (() => { const accountList = (() => {
const result: any[] = []; const result: any[] = [];
...@@ -28,6 +28,7 @@ const roleList = (() => { ...@@ -28,6 +28,7 @@ const roleList = (() => {
roleValue: '@first', roleValue: '@first',
createTime: '@datetime', createTime: '@datetime',
remark: '@cword(10,20)', remark: '@cword(10,20)',
menu: [['0', '1', '2'], ['0', '1'], ['0', '2'], ['2']][index],
'status|1': ['0', '1'], 'status|1': ['0', '1'],
}); });
} }
...@@ -185,4 +186,17 @@ export default [ ...@@ -185,4 +186,17 @@ export default [
return resultSuccess(menuList); return resultSuccess(menuList);
}, },
}, },
{
url: '/basic-api/system/accountExist',
timeout: 500,
method: 'post',
response: ({ body }) => {
const { account } = body || {};
if (account && account.indexOf('admin') !== -1) {
return resultError('该字段不能包含admin');
} else {
return resultSuccess(`${account} can use`);
}
},
},
] as MockMethod[]; ] as MockMethod[];
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess } from '../_util';
const demoTreeList = (keyword) => {
const result = {
list: [] as Recordable[],
};
for (let index = 0; index < 5; index++) {
const children: Recordable[] = [];
for (let j = 0; j < 3; j++) {
children.push({
title: `${keyword ?? ''}选项${index}-${j}`,
value: `${index}-${j}`,
key: `${index}-${j}`,
});
}
result.list.push({
title: `${keyword ?? ''}选项${index}`,
value: `${index}`,
key: `${index}`,
children,
});
}
return result;
};
export default [
{
url: '/basic-api/tree/getDemoOptions',
timeout: 1000,
method: 'get',
response: ({ query }) => {
const { keyword } = query;
console.log(keyword);
return resultSuccess(demoTreeList(keyword));
},
},
] as MockMethod[];
...@@ -5,13 +5,40 @@ import { createFakeUserList } from './user'; ...@@ -5,13 +5,40 @@ import { createFakeUserList } from './user';
// single // single
const dashboardRoute = { const dashboardRoute = {
path: '/dashboard', path: '/dashboard',
name: 'Welcome', name: 'Dashboard',
component: 'LAYOUT',
redirect: '/dashboard/analysis',
meta: {
title: 'routes.dashboard.dashboard',
hideChildrenInMenu: true,
icon: 'bx:bx-home',
},
children: [
{
path: 'analysis',
name: 'Analysis',
component: '/dashboard/analysis/index', component: '/dashboard/analysis/index',
meta: { meta: {
hideMenu: true,
hideBreadcrumb: true,
title: 'routes.dashboard.analysis', title: 'routes.dashboard.analysis',
affix: true, currentActiveMenu: '/dashboard',
icon: 'bx:bx-home', icon: 'bx:bx-home',
}, },
},
{
path: 'workbench',
name: 'Workbench',
component: '/dashboard/workbench/index',
meta: {
hideMenu: true,
hideBreadcrumb: true,
title: 'routes.dashboard.workbench',
currentActiveMenu: '/dashboard',
icon: 'bx:bx-home',
},
},
],
}; };
const backRoute = { const backRoute = {
...@@ -223,12 +250,21 @@ export default [ ...@@ -223,12 +250,21 @@ export default [
return resultError('Invalid user token!'); return resultError('Invalid user token!');
} }
const id = checkUser.userId; const id = checkUser.userId;
if (!id || id === '1') { let menu: Object[];
return resultSuccess([dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute]); switch (id) {
} case '1':
if (id === '2') { dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[0].path;
return resultSuccess([dashboardRoute, authRoute, levelRoute, linkRoute]); menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute];
break;
case '2':
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[1].path;
menu = [dashboardRoute, authRoute, levelRoute, linkRoute];
break;
default:
menu = [];
} }
return resultSuccess(menu);
}, },
}, },
] as MockMethod[]; ] as MockMethod[];
...@@ -11,6 +11,7 @@ export function createFakeUserList() { ...@@ -11,6 +11,7 @@ export function createFakeUserList() {
desc: 'manager', desc: 'manager',
password: '123456Aa', password: '123456Aa',
token: 'fakeToken1', token: 'fakeToken1',
homePath: '/dashboard/analysis',
roles: [ roles: [
{ {
roleName: 'Super Admin', roleName: 'Super Admin',
...@@ -26,6 +27,7 @@ export function createFakeUserList() { ...@@ -26,6 +27,7 @@ export function createFakeUserList() {
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=339449197&s=640', avatar: 'https://q1.qlogo.cn/g?b=qq&nk=339449197&s=640',
desc: 'tester', desc: 'tester',
token: 'fakeToken2', token: 'fakeToken2',
homePath: '/dashboard/workbench',
roles: [ roles: [
{ {
roleName: 'Tester', roleName: 'Tester',
......
{ {
"name": "vben-admin", "name": "vben-admin",
"version": "2.6.0", "version": "2.7.0",
"author": { "author": {
"name": "vben", "name": "vben",
"email": "anncwb@126.com", "email": "anncwb@126.com",
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
"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 --cache --max-warnings 0 \"{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 \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --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",
...@@ -35,111 +35,111 @@ ...@@ -35,111 +35,111 @@
}, },
"dependencies": { "dependencies": {
"@iconify/iconify": "^2.0.3", "@iconify/iconify": "^2.0.3",
"@logicflow/core": "^0.5.1", "@logicflow/core": "^0.6.6",
"@logicflow/extension": "^0.5.1", "@logicflow/extension": "^0.6.6",
"@vueuse/core": "^5.1.3", "@vueuse/core": "^5.2.0",
"@zxcvbn-ts/core": "^1.0.0-beta.0", "@zxcvbn-ts/core": "^1.0.0-beta.0",
"ant-design-vue": "2.2.1", "ant-design-vue": "2.2.2",
"axios": "^0.21.1", "axios": "^0.21.1",
"codemirror": "^5.62.0", "codemirror": "^5.62.2",
"cropperjs": "^1.5.12", "cropperjs": "^1.5.12",
"crypto-js": "^4.0.0", "crypto-js": "^4.1.1",
"echarts": "^5.1.2", "echarts": "^5.1.2",
"intro.js": "^4.1.0", "intro.js": "^4.1.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"pinia": "^2.0.0-beta.3", "pinia": "2.0.0-beta.5",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"sortablejs": "^1.14.0", "sortablejs": "^1.14.0",
"tinymce": "^5.8.2", "tinymce": "^5.8.2",
"vditor": "^3.8.6", "vditor": "^3.8.6",
"vue": "3.1.4", "vue": "3.1.5",
"vue-i18n": "9.1.6", "vue-i18n": "9.1.7",
"vue-json-pretty": "^2.0.2", "vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.10", "vue-router": "^4.0.10",
"vue-types": "^4.0.0", "vue-types": "^4.0.1",
"xlsx": "^0.17.0" "xlsx": "^0.17.0"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^12.1.4", "@commitlint/cli": "^13.1.0",
"@commitlint/config-conventional": "^12.1.4", "@commitlint/config-conventional": "^13.1.0",
"@iconify/json": "^1.1.371", "@iconify/json": "^1.1.382",
"@purge-icons/generated": "^0.7.0", "@purge-icons/generated": "^0.7.0",
"@types/codemirror": "^5.60.2", "@types/codemirror": "^5.60.2",
"@types/crypto-js": "^4.0.2", "@types/crypto-js": "^4.0.2",
"@types/fs-extra": "^9.0.12", "@types/fs-extra": "^9.0.12",
"@types/inquirer": "^7.3.3", "@types/inquirer": "^7.3.3",
"@types/intro.js": "^3.0.1", "@types/intro.js": "^3.0.2",
"@types/jest": "^26.0.24", "@types/jest": "^26.0.24",
"@types/lodash-es": "^4.17.4", "@types/lodash-es": "^4.17.4",
"@types/mockjs": "^1.0.3", "@types/mockjs": "^1.0.4",
"@types/node": "^16.0.0", "@types/node": "^16.4.10",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.0", "@types/qrcode": "^1.4.1",
"@types/qs": "^6.9.6", "@types/qs": "^6.9.7",
"@types/sortablejs": "^1.10.7", "@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^4.28.2", "@typescript-eslint/eslint-plugin": "^4.29.0",
"@typescript-eslint/parser": "^4.28.2", "@typescript-eslint/parser": "^4.29.0",
"@vitejs/plugin-legacy": "^1.4.3", "@vitejs/plugin-legacy": "^1.5.0",
"@vitejs/plugin-vue": "^1.2.4", "@vitejs/plugin-vue": "^1.3.0",
"@vitejs/plugin-vue-jsx": "^1.1.6", "@vitejs/plugin-vue-jsx": "^1.1.7",
"@vue/compiler-sfc": "3.1.4", "@vue/compiler-sfc": "3.1.5",
"@vue/test-utils": "^2.0.0-rc.10", "@vue/test-utils": "^2.0.0-rc.12",
"autoprefixer": "^10.2.6", "autoprefixer": "^10.3.1",
"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.30.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-define-config": "^1.0.9", "eslint-define-config": "^1.0.9",
"eslint-plugin-jest": "^24.3.6", "eslint-plugin-jest": "^24.4.0",
"eslint-plugin-prettier": "^3.4.0", "eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.13.0", "eslint-plugin-vue": "^7.15.0",
"esno": "^0.7.3", "esno": "^0.8.0",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"http-server": "^0.12.3", "http-server": "^0.12.3",
"husky": "^7.0.1", "husky": "^7.0.1",
"inquirer": "^8.1.1", "inquirer": "^8.1.2",
"is-ci": "^3.0.0", "is-ci": "^3.0.0",
"jest": "^27.0.6", "jest": "^27.0.6",
"less": "^4.1.1", "less": "^4.1.1",
"lint-staged": "^11.0.0", "lint-staged": "^11.1.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "^8.3.5", "postcss": "^8.3.6",
"prettier": "^2.3.2", "prettier": "^2.3.2",
"pretty-quick": "^3.1.1", "pretty-quick": "^3.1.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup-plugin-visualizer": "5.5.1", "rollup-plugin-visualizer": "5.5.2",
"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.4", "ts-jest": "^27.0.4",
"ts-jest": "^27.0.3", "ts-node": "^10.1.0",
"ts-node": "^10.0.0",
"typescript": "4.3.5", "typescript": "4.3.5",
"vite": "2.4.1", "vite": "2.4.4",
"vite-plugin-compression": "^0.3.0", "vite-plugin-compression": "^0.3.3",
"vite-plugin-html": "^2.0.7", "vite-plugin-html": "^2.0.7",
"vite-plugin-imagemin": "^0.4.0", "vite-plugin-imagemin": "^0.4.3",
"vite-plugin-mock": "^2.9.1", "vite-plugin-mock": "^2.9.4",
"vite-plugin-purge-icons": "^0.7.0", "vite-plugin-purge-icons": "^0.7.0",
"vite-plugin-pwa": "^0.8.1", "vite-plugin-pwa": "^0.9.3",
"vite-plugin-style-import": "^1.0.1", "vite-plugin-style-import": "^1.1.0",
"vite-plugin-svg-icons": "^1.0.1", "vite-plugin-svg-icons": "^1.0.3",
"vite-plugin-theme": "^0.8.1", "vite-plugin-theme": "^0.8.1",
"vue-eslint-parser": "^7.8.0", "vite-plugin-windicss": "^1.2.7",
"vue-tsc": "^0.2.0" "vue-eslint-parser": "^7.10.0",
"vue-tsc": "^0.2.2"
}, },
"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.52.8" "rollup": "^2.55.1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
......
module.exports = { module.exports = {
plugins: { plugins: {
tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
}; };
...@@ -14,6 +14,7 @@ import { defHttp } from '/@/utils/http/axios'; ...@@ -14,6 +14,7 @@ import { defHttp } from '/@/utils/http/axios';
enum Api { enum Api {
AccountList = '/system/getAccountList', AccountList = '/system/getAccountList',
IsAccountExist = '/system/accountExist',
DeptList = '/system/getDeptList', DeptList = '/system/getDeptList',
setRoleStatus = '/system/setRoleStatus', setRoleStatus = '/system/setRoleStatus',
MenuList = '/system/getMenuList', MenuList = '/system/getMenuList',
...@@ -38,3 +39,6 @@ export const getAllRoleList = (params?: RoleParams) => ...@@ -38,3 +39,6 @@ export const getAllRoleList = (params?: RoleParams) =>
export const setRoleStatus = (id: number, status: string) => export const setRoleStatus = (id: number, status: string) =>
defHttp.post({ url: Api.setRoleStatus, params: { id, status } }); defHttp.post({ url: Api.setRoleStatus, params: { id, status } });
export const isAccountExist = (account: string) =>
defHttp.post({ url: Api.IsAccountExist, params: { account } }, { errorMessageMode: 'none' });
import { defHttp } from '/@/utils/http/axios';
enum Api {
TREE_OPTIONS_LIST = '/tree/getDemoOptions',
}
/**
* @description: Get sample options value
*/
export const treeOptionsListApi = (params?: Recordable) =>
defHttp.get<Recordable[]>({ url: Api.TREE_OPTIONS_LIST, params });
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { PageEnum } from '/@/enums/pageEnum'; import { PageEnum } from '/@/enums/pageEnum';
import { useUserStore } from '/@/store/modules/user';
const props = { const props = {
/** /**
...@@ -39,6 +40,7 @@ ...@@ -39,6 +40,7 @@
setup(props) { setup(props) {
const { prefixCls } = useDesign('app-logo'); const { prefixCls } = useDesign('app-logo');
const { getCollapsedShowTitle } = useMenuSetting(); const { getCollapsedShowTitle } = useMenuSetting();
const userStore = useUserStore();
const { title } = useGlobSetting(); const { title } = useGlobSetting();
const go = useGo(); const go = useGo();
...@@ -56,7 +58,7 @@ ...@@ -56,7 +58,7 @@
]); ]);
function goHome() { function goHome() {
go(PageEnum.BASE_HOME); go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME);
} }
return { return {
......
...@@ -8,18 +8,20 @@ ...@@ -8,18 +8,20 @@
</Button> </Button>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, computed } from 'vue'; import { defineComponent, computed, unref } from 'vue';
import { Button } from 'ant-design-vue'; import { Button } from 'ant-design-vue';
import Icon from '/@/components/Icon/src/Icon.vue'; import Icon from '/@/components/Icon/src/Icon.vue';
import { buttonProps } from './props'; import { buttonProps } from './props';
import { useAttrs } from '/@/hooks/core/useAttrs';
export default defineComponent({ export default defineComponent({
name: 'AButton', name: 'AButton',
components: { Button, Icon }, components: { Button, Icon },
inheritAttrs: false, inheritAttrs: false,
props: buttonProps, props: buttonProps,
setup(props, { attrs }) { setup(props) {
// get component class // get component class
const attrs = useAttrs({ excludeDefaultKeys: false });
const getButtonClass = computed(() => { const getButtonClass = computed(() => {
const { color, disabled } = props; const { color, disabled } = props;
return [ return [
...@@ -27,12 +29,11 @@ ...@@ -27,12 +29,11 @@
[`ant-btn-${color}`]: !!color, [`ant-btn-${color}`]: !!color,
[`is-disabled`]: disabled, [`is-disabled`]: disabled,
}, },
attrs.class,
]; ];
}); });
// get inherit binding value // get inherit binding value
const getBindValue = computed(() => ({ ...attrs, ...props })); const getBindValue = computed(() => ({ ...unref(attrs), ...props }));
return { getBindValue, getButtonClass }; return { getBindValue, getButtonClass };
}, },
......
<script lang="ts"> <script lang="ts">
import { defineComponent, h, unref, computed } from 'vue'; import { computed, defineComponent, h, unref } from 'vue';
import BasicButton from './BasicButton.vue'; import BasicButton from './BasicButton.vue';
import { Popconfirm } from 'ant-design-vue'; import { Popconfirm } from 'ant-design-vue';
import { extendSlots } from '/@/utils/helper/tsxHelper'; import { extendSlots } from '/@/utils/helper/tsxHelper';
...@@ -29,19 +29,20 @@ ...@@ -29,19 +29,20 @@
// get inherit binding value // get inherit binding value
const getBindValues = computed(() => { const getBindValues = computed(() => {
const popValues = Object.assign( return Object.assign(
{ {
okText: t('common.okText'), okText: t('common.okText'),
cancelText: t('common.cancelText'), cancelText: t('common.cancelText'),
}, },
{ ...props, ...unref(attrs) } { ...props, ...unref(attrs) }
); );
return popValues;
}); });
return () => { return () => {
const bindValues = omit(unref(getBindValues), 'icon'); const bindValues = omit(unref(getBindValues), 'icon');
const Button = h(BasicButton, omit(bindValues, 'title'), extendSlots(slots)); const btnBind = omit(bindValues, 'title');
if (btnBind.disabled) btnBind.color = '';
const Button = h(BasicButton, btnBind, extendSlots(slots));
// If it is not enabled, it is a normal button // If it is not enabled, it is a normal button
if (!props.enable) { if (!props.enable) {
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
name: 'CodeEditor', name: 'CodeEditor',
components: { CodeMirrorEditor }, components: { CodeMirrorEditor },
props, props,
emits: ['change'], emits: ['change', 'update:value'],
setup(props, { emit }) { setup(props, { emit }) {
const getValue = computed(() => { const getValue = computed(() => {
const { value, mode } = props; const { value, mode } = props;
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
}); });
function handleValueChange(v) { function handleValueChange(v) {
emit('update:value', v);
emit('change', v); emit('change', v);
} }
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
<template #addonAfter> <template #addonAfter>
<CountButton :size="size" :count="count" :value="state" :beforeStartFunc="sendCodeApi" /> <CountButton :size="size" :count="count" :value="state" :beforeStartFunc="sendCodeApi" />
</template> </template>
<template #[item]="data" v-for="item in Object.keys($slots).filter((k) => k !== 'addonAfter')">
<slot :name="item" v-bind="data"></slot>
</template>
</a-input> </a-input>
</template> </template>
<script lang="ts"> <script lang="ts">
......
...@@ -130,7 +130,7 @@ ...@@ -130,7 +130,7 @@
}; };
export default defineComponent({ export default defineComponent({
name: 'CropperAvatar', name: 'CropperModal',
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip }, components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
props, props,
emits: ['uploadSuccess', 'register'], emits: ['uploadSuccess', 'register'],
......
...@@ -8,5 +8,6 @@ export { useForm } from './src/hooks/useForm'; ...@@ -8,5 +8,6 @@ export { useForm } from './src/hooks/useForm';
export { default as ApiSelect } from './src/components/ApiSelect.vue'; export { default as ApiSelect } from './src/components/ApiSelect.vue';
export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'; export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue';
export { BasicForm }; export { BasicForm };
...@@ -229,6 +229,10 @@ ...@@ -229,6 +229,10 @@
function setFormModel(key: string, value: any) { function setFormModel(key: string, value: any) {
formModel[key] = value; formModel[key] = value;
const { validateTrigger } = unref(getBindValue);
if (!validateTrigger || validateTrigger === 'change') {
validateFields([key]).catch((_) => {});
}
} }
function handleEnterPress(e: KeyboardEvent) { function handleEnterPress(e: KeyboardEvent) {
......
...@@ -22,6 +22,7 @@ import { ...@@ -22,6 +22,7 @@ import {
import RadioButtonGroup from './components/RadioButtonGroup.vue'; import RadioButtonGroup from './components/RadioButtonGroup.vue';
import ApiSelect from './components/ApiSelect.vue'; import ApiSelect from './components/ApiSelect.vue';
import ApiTreeSelect from './components/ApiTreeSelect.vue';
import { BasicUpload } from '/@/components/Upload'; import { BasicUpload } from '/@/components/Upload';
import { StrengthMeter } from '/@/components/StrengthMeter'; import { StrengthMeter } from '/@/components/StrengthMeter';
import { IconPicker } from '/@/components/Icon'; import { IconPicker } from '/@/components/Icon';
...@@ -40,6 +41,7 @@ componentMap.set('AutoComplete', AutoComplete); ...@@ -40,6 +41,7 @@ componentMap.set('AutoComplete', AutoComplete);
componentMap.set('Select', Select); componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect); componentMap.set('ApiSelect', ApiSelect);
componentMap.set('TreeSelect', TreeSelect); componentMap.set('TreeSelect', TreeSelect);
componentMap.set('ApiTreeSelect', ApiTreeSelect);
componentMap.set('Switch', Switch); componentMap.set('Switch', Switch);
componentMap.set('RadioButtonGroup', RadioButtonGroup); componentMap.set('RadioButtonGroup', RadioButtonGroup);
componentMap.set('RadioGroup', Radio.Group); componentMap.set('RadioGroup', Radio.Group);
......
...@@ -134,7 +134,7 @@ ...@@ -134,7 +134,7 @@
} }
function emitChange() { function emitChange() {
emit('options-change', unref(options)); emit('options-change', unref(getOptions));
} }
function handleChange(_, ...args) { function handleChange(_, ...args) {
......
<template>
<a-tree-select v-bind="getAttrs" @change="handleChange">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data"></slot>
</template>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
</a-tree-select>
</template>
<script lang="ts">
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
import { TreeSelect } from 'ant-design-vue';
import { isArray, isFunction } from '/@/utils/is';
import { get } from 'lodash-es';
import { propTypes } from '/@/utils/propTypes';
import { LoadingOutlined } from '@ant-design/icons-vue';
export default defineComponent({
name: 'ApiTreeSelect',
components: { ATreeSelect: TreeSelect, LoadingOutlined },
props: {
api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> },
params: { type: Object },
immediate: { type: Boolean, default: true },
resultField: propTypes.string.def(''),
},
emits: ['options-change', 'change'],
setup(props, { attrs, emit }) {
const treeData = ref<Recordable[]>([]);
const isFirstLoaded = ref<Boolean>(false);
const loading = ref(false);
const getAttrs = computed(() => {
return {
...(props.api ? { treeData: unref(treeData) } : {}),
...attrs,
};
});
function handleChange(...args) {
emit('change', ...args);
}
watch(
() => props.params,
() => {
isFirstLoaded.value && fetch();
},
{ deep: true }
);
watch(
() => props.immediate,
(v) => {
v && !isFirstLoaded.value && fetch();
}
);
onMounted(() => {
props.immediate && fetch();
});
async function fetch() {
const { api } = props;
if (!api || !isFunction(api)) return;
loading.value = true;
treeData.value = [];
let result;
try {
result = await api(props.params);
} catch (e) {
console.error(e);
}
loading.value = false;
if (!result) return;
if (!isArray(result)) {
result = get(result, props.resultField);
}
treeData.value = (result as Recordable[]) || [];
isFirstLoaded.value = true;
emit('options-change', treeData.value);
}
return { getAttrs, loading, handleChange };
},
});
</script>
<template> <template>
<a-col v-bind="actionColOpt" :style="{ textAlign: 'right' }" v-if="showActionButtonGroup"> <a-col v-bind="actionColOpt" v-if="showActionButtonGroup">
<div style="width: 100%; text-align: right"> <div style="width: 100%" :style="{ textAlign: actionColOpt.style.textAlign }">
<FormItem> <FormItem>
<slot name="resetBefore"></slot> <slot name="resetBefore"></slot>
<Button <Button
...@@ -92,6 +92,7 @@ ...@@ -92,6 +92,7 @@
? { span: actionSpan < 6 ? 24 : actionSpan } ? { span: actionSpan < 6 ? 24 : actionSpan }
: {}; : {};
const actionColOpt: Partial<ColEx> = { const actionColOpt: Partial<ColEx> = {
style: { textAlign: 'right' },
span: showAdvancedButton ? 6 : 4, span: showAdvancedButton ? 6 : 4,
...advancedSpanObj, ...advancedSpanObj,
...actionColOptions, ...actionColOptions,
......
...@@ -84,7 +84,7 @@ export function useFormEvents({ ...@@ -84,7 +84,7 @@ export function useFormEvents({
validKeys.push(key); validKeys.push(key);
} }
}); });
validateFields(validKeys); validateFields(validKeys).catch((_) => {});
} }
/** /**
* @description: Delete based on field name * @description: Delete based on field name
......
import type { NamePath } from 'ant-design-vue/lib/form/interface'; import type { NamePath } from 'ant-design-vue/lib/form/interface';
import type { ColProps } from 'ant-design-vue/lib/grid/Col'; import type { ColProps } from 'ant-design-vue/lib/grid/Col';
import type { VNodeChild } from 'vue'; import type { HTMLAttributes, VNodeChild } from 'vue';
export interface FormItem { export interface FormItem {
/** /**
...@@ -39,7 +39,7 @@ export interface FormItem { ...@@ -39,7 +39,7 @@ export interface FormItem {
* The layout of label. You can set span offset to something like {span: 3, offset: 12} or sm: {span: 3, offset: 12} same as with <Col> * The layout of label. You can set span offset to something like {span: 3, offset: 12} or sm: {span: 3, offset: 12} same as with <Col>
* @type Col * @type Col
*/ */
labelCol?: ColProps; labelCol?: ColProps & HTMLAttributes;
/** /**
* Whether provided or not, it will be generated by the validation rule. * Whether provided or not, it will be generated by the validation rule.
......
...@@ -91,6 +91,7 @@ export type ComponentType = ...@@ -91,6 +91,7 @@ export type ComponentType =
| 'Select' | 'Select'
| 'ApiSelect' | 'ApiSelect'
| 'TreeSelect' | 'TreeSelect'
| 'ApiTreeSelect'
| 'RadioButtonGroup' | 'RadioButtonGroup'
| 'RadioGroup' | 'RadioGroup'
| 'Checkbox' | 'Checkbox'
......
...@@ -12,10 +12,12 @@ interface Fn { ...@@ -12,10 +12,12 @@ interface Fn {
(): void; (): void;
} }
export function useLoading(props: Partial<LoadingProps>): [Fn, Fn]; export function useLoading(props: Partial<LoadingProps>): [Fn, Fn, (string) => void];
export function useLoading(opt: Partial<UseLoadingOptions>): [Fn, Fn]; export function useLoading(opt: Partial<UseLoadingOptions>): [Fn, Fn, (string) => void];
export function useLoading(opt: Partial<LoadingProps> | Partial<UseLoadingOptions>): [Fn, Fn] { export function useLoading(
opt: Partial<LoadingProps> | Partial<UseLoadingOptions>
): [Fn, Fn, (string) => void] {
let props: Partial<LoadingProps>; let props: Partial<LoadingProps>;
let target: HTMLElement | Ref<ElRef> = document.body; let target: HTMLElement | Ref<ElRef> = document.body;
...@@ -39,5 +41,9 @@ export function useLoading(opt: Partial<LoadingProps> | Partial<UseLoadingOption ...@@ -39,5 +41,9 @@ export function useLoading(opt: Partial<LoadingProps> | Partial<UseLoadingOption
instance.close(); instance.close();
}; };
return [open, close]; const setTip = (tip: string) => {
instance.setTip(tip);
};
return [open, close, setTip];
} }
...@@ -5,18 +5,19 @@ ...@@ -5,18 +5,19 @@
import { import {
defineComponent, defineComponent,
ref, ref,
onMounted,
unref, unref,
onUnmounted,
nextTick, nextTick,
computed, computed,
watch, watch,
onBeforeUnmount,
onDeactivated,
} from 'vue'; } from 'vue';
import Vditor from 'vditor'; import Vditor from 'vditor';
import 'vditor/dist/index.css'; import 'vditor/dist/index.css';
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';
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
type Lang = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' | undefined; type Lang = 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' | undefined;
...@@ -26,7 +27,7 @@ ...@@ -26,7 +27,7 @@
height: { type: Number, default: 360 }, height: { type: Number, default: 360 },
value: { type: String, default: '' }, value: { type: String, default: '' },
}, },
emits: ['change', 'get'], emits: ['change', 'get', 'update:value'],
setup(props, { attrs, emit }) { setup(props, { attrs, emit }) {
const wrapRef = ref<ElRef>(null); const wrapRef = ref<ElRef>(null);
const vditorRef = ref<Nullable<Vditor>>(null); const vditorRef = ref<Nullable<Vditor>>(null);
...@@ -36,17 +37,16 @@ ...@@ -36,17 +37,16 @@
const { getLocale } = useLocale(); const { getLocale } = useLocale();
const { getDarkMode } = useRootSetting(); const { getDarkMode } = useRootSetting();
const valueRef = ref('');
watch( watch(
[() => getDarkMode.value, () => initedRef.value], [() => getDarkMode.value, () => initedRef.value],
([val]) => { ([val, inited]) => {
const vditor = unref(vditorRef); if (!inited) {
if (!vditor) {
return; return;
} }
const theme = val === 'dark' ? 'dark' : undefined; const theme = val === 'dark' ? 'dark' : 'classic';
vditor.setTheme(theme as 'dark'); instance.getVditor()?.setTheme(theme);
}, },
{ {
immediate: true, immediate: true,
...@@ -54,6 +54,16 @@ ...@@ -54,6 +54,16 @@
} }
); );
watch(
() => props.value,
(v) => {
if (v !== valueRef.value) {
instance.getVditor()?.setValue(v);
}
valueRef.value = v;
}
);
const getCurrentLang = computed((): 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' => { const getCurrentLang = computed((): 'zh_CN' | 'en_US' | 'ja_JP' | 'ko_KR' => {
let lang: Lang; let lang: Lang;
switch (unref(getLocale)) { switch (unref(getLocale)) {
...@@ -72,54 +82,58 @@ ...@@ -72,54 +82,58 @@
return lang; return lang;
}); });
function init() { function init() {
const wrapEl = unref(wrapRef); const wrapEl = unref(wrapRef) as HTMLElement;
if (!wrapEl) return; if (!wrapEl) return;
const bindValue = { ...attrs, ...props }; const bindValue = { ...attrs, ...props };
vditorRef.value = new Vditor(wrapEl, { const insEditor = new Vditor(wrapEl, {
theme: 'classic', theme: getDarkMode.value === 'dark' ? 'dark' : 'classic',
lang: unref(getCurrentLang), lang: unref(getCurrentLang),
mode: 'sv', mode: 'sv',
preview: { preview: {
actions: [], actions: [],
}, },
input: (v) => { input: (v) => {
// emit('update:value', v); valueRef.value = v;
emit('update:value', v);
emit('change', v); emit('change', v);
}, },
after: () => {
nextTick(() => {
modalFn?.redoModalHeight?.();
insEditor.setValue(valueRef.value);
vditorRef.value = insEditor;
initedRef.value = true;
emit('get', instance);
});
},
blur: () => { blur: () => {
unref(vditorRef)?.setValue(props.value); //unref(vditorRef)?.setValue(props.value);
}, },
...bindValue, ...bindValue,
cache: { cache: {
enable: false, enable: false,
}, },
}); });
initedRef.value = true;
} }
const instance = { const instance = {
getVditor: (): Vditor => vditorRef.value!, getVditor: (): Vditor => vditorRef.value!,
}; };
onMounted(() => { function destroy() {
nextTick(() => {
init();
setTimeout(() => {
modalFn?.redoModalHeight?.();
}, 200);
});
emit('get', instance);
});
onUnmounted(() => {
const vditorInstance = unref(vditorRef); const vditorInstance = unref(vditorRef);
if (!vditorInstance) return; if (!vditorInstance) return;
try { try {
vditorInstance?.destroy?.(); vditorInstance?.destroy?.();
} catch (error) {} } catch (error) {}
}); vditorRef.value = null;
initedRef.value = false;
}
onMountedOrActivated(init);
onBeforeUnmount(destroy);
onDeactivated(destroy);
return { return {
wrapRef, wrapRef,
...instance, ...instance,
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
</template> </template>
<template #footer v-if="!$slots.footer"> <template #footer v-if="!$slots.footer">
<ModalFooter v-bind="getProps" @ok="handleOk" @cancel="handleCancel"> <ModalFooter v-bind="getBindValue" @ok="handleOk" @cancel="handleCancel">
<template #[item]="data" v-for="item in Object.keys($slots)"> <template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data"></slot> <slot :name="item" v-bind="data"></slot>
</template> </template>
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
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);
const modalWrapperRef = ref<ComponentRef>(null); const modalWrapperRef = ref<any>(null);
// modal Bottom and top height // modal Bottom and top height
const extHeightRef = ref(0); const extHeightRef = ref(0);
...@@ -133,7 +133,12 @@ ...@@ -133,7 +133,12 @@
}); });
const getBindValue = computed((): Recordable => { const getBindValue = computed((): Recordable => {
const attr = { ...attrs, ...unref(getProps) }; const attr = {
...attrs,
...unref(getMergeProps),
visible: unref(visibleRef),
wrapClassName: unref(getWrapClassName),
};
if (unref(fullScreenRef)) { if (unref(fullScreenRef)) {
return omit(attr, 'height'); return omit(attr, 'height');
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<PageHeader <PageHeader
:ghost="ghost" :ghost="ghost"
:title="title" :title="title"
v-bind="$attrs" v-bind="omit($attrs, 'class')"
ref="headerRef" ref="headerRef"
v-if="content || $slots.headerContent || title || getHeaderSlots.length" v-if="content || $slots.headerContent || title || getHeaderSlots.length"
> >
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
contentClass: propTypes.string, contentClass: propTypes.string,
fixedHeight: propTypes.bool, fixedHeight: propTypes.bool,
}, },
setup(props, { slots }) { setup(props, { slots, attrs }) {
const wrapperRef = ref(null); const wrapperRef = ref(null);
const headerRef = ref(null); const headerRef = ref(null);
const contentRef = ref(null); const contentRef = ref(null);
...@@ -86,6 +86,7 @@ ...@@ -86,6 +86,7 @@
{ {
[`${prefixCls}--dense`]: props.dense, [`${prefixCls}--dense`]: props.dense,
}, },
attrs.class ?? {},
]; ];
}); });
......
...@@ -38,13 +38,33 @@ ...@@ -38,13 +38,33 @@
type: Number as PropType<number>, type: Number as PropType<number>,
default: 0, default: 0,
}, },
scaleStep: {
type: Number as PropType<number>,
},
defaultWidth: {
type: Number as PropType<number>,
},
maskClosable: {
type: Boolean as PropType<boolean>,
},
rememberState: {
type: Boolean as PropType<boolean>,
},
}; };
const prefixCls = 'img-preview'; const prefixCls = 'img-preview';
export default defineComponent({ export default defineComponent({
name: 'ImagePreview', name: 'ImagePreview',
props, props,
setup(props: Props) { emits: ['img-load', 'img-error'],
setup(props: Props, { expose, emit }) {
interface stateInfo {
scale: number;
rotate: number;
top: number;
left: number;
}
const stateMap = new Map<string, stateInfo>();
const imgState = reactive<ImgState>({ const imgState = reactive<ImgState>({
currentUrl: '', currentUrl: '',
imgScale: 1, imgScale: 1,
...@@ -96,6 +116,14 @@ ...@@ -96,6 +116,14 @@
}; };
} }
const getScaleStep = computed(() => {
if (props.scaleStep > 0 && props.scaleStep < 100) {
return props.scaleStep / 100;
} else {
return imgState.imgScale / 10;
}
});
// 监听鼠标滚轮 // 监听鼠标滚轮
function scrollFunc(e: any) { function scrollFunc(e: any) {
e = e || window.event; e = e || window.event;
...@@ -104,11 +132,11 @@ ...@@ -104,11 +132,11 @@
e.preventDefault(); e.preventDefault();
if (e.delta > 0) { if (e.delta > 0) {
// 滑轮向上滚动 // 滑轮向上滚动
scaleFunc(0.015); scaleFunc(getScaleStep.value);
} }
if (e.delta < 0) { if (e.delta < 0) {
// 滑轮向下滚动 // 滑轮向下滚动
scaleFunc(-0.015); scaleFunc(-getScaleStep.value);
} }
} }
// 缩放函数 // 缩放函数
...@@ -134,11 +162,54 @@ ...@@ -134,11 +162,54 @@
imgState.status = StatueEnum.LOADING; imgState.status = StatueEnum.LOADING;
const img = new Image(); const img = new Image();
img.src = url; img.src = url;
img.onload = () => { img.onload = (e: Event) => {
if (imgState.currentUrl !== url) {
const ele: HTMLElement[] = e.composedPath();
if (props.rememberState) {
// 保存当前图片的缩放信息
stateMap.set(imgState.currentUrl, {
scale: imgState.imgScale,
top: imgState.imgTop,
left: imgState.imgLeft,
rotate: imgState.imgRotate,
});
// 如果之前已存储缩放信息,就应用
const stateInfo = stateMap.get(url);
if (stateInfo) {
imgState.imgScale = stateInfo.scale;
imgState.imgTop = stateInfo.top;
imgState.imgRotate = stateInfo.rotate;
imgState.imgLeft = stateInfo.left;
} else {
initState();
if (props.defaultWidth) {
imgState.imgScale = props.defaultWidth / ele[0].naturalWidth;
}
}
} else {
if (props.defaultWidth) {
imgState.imgScale = props.defaultWidth / ele[0].naturalWidth;
}
}
ele &&
emit('img-load', {
index: imgState.currentIndex,
dom: ele[0] as HTMLImageElement,
url,
});
}
imgState.currentUrl = url; imgState.currentUrl = url;
imgState.status = StatueEnum.DONE; imgState.status = StatueEnum.DONE;
}; };
img.onerror = () => { img.onerror = (e: Event) => {
const ele: EventTarget[] = e.composedPath();
ele &&
emit('img-error', {
index: imgState.currentIndex,
dom: ele[0] as HTMLImageElement,
url,
});
imgState.status = StatueEnum.FAIL; imgState.status = StatueEnum.FAIL;
}; };
} }
...@@ -146,6 +217,10 @@ ...@@ -146,6 +217,10 @@
// 关闭 // 关闭
function handleClose(e: MouseEvent) { function handleClose(e: MouseEvent) {
e && e.stopPropagation(); e && e.stopPropagation();
close();
}
function close() {
imgState.show = false; imgState.show = false;
// 移除火狐浏览器下的鼠标滚动事件 // 移除火狐浏览器下的鼠标滚动事件
document.body.removeEventListener('DOMMouseScroll', scrollFunc); document.body.removeEventListener('DOMMouseScroll', scrollFunc);
...@@ -158,6 +233,19 @@ ...@@ -158,6 +233,19 @@
initState(); initState();
} }
expose({
resume,
close,
prev: handleChange.bind(null, 'left'),
next: handleChange.bind(null, 'right'),
setScale: (scale: number) => {
if (scale > 0 && scale <= 10) imgState.imgScale = scale;
},
setRotate: (rotate: number) => {
imgState.imgRotate = rotate;
},
} as PreviewActions);
// 上一页下一页 // 上一页下一页
function handleChange(direction: 'left' | 'right') { function handleChange(direction: 'left' | 'right') {
const { currentIndex } = imgState; const { currentIndex } = imgState;
...@@ -205,6 +293,7 @@ ...@@ -205,6 +293,7 @@
transform: `scale(${imgScale}) rotate(${imgRotate}deg)`, transform: `scale(${imgScale}) rotate(${imgRotate}deg)`,
marginTop: `${imgTop}px`, marginTop: `${imgTop}px`,
marginLeft: `${imgLeft}px`, marginLeft: `${imgLeft}px`,
maxWidth: props.defaultWidth ? 'unset' : '100%',
}; };
}); });
...@@ -222,6 +311,16 @@ ...@@ -222,6 +311,16 @@
} }
}); });
const handleMaskClick = (e: MouseEvent) => {
if (
props.maskClosable &&
e.target &&
(e.target as HTMLDivElement).classList.contains(`${prefixCls}-content`)
) {
handleClose(e);
}
};
const renderClose = () => { const renderClose = () => {
return ( return (
<div class={`${prefixCls}__close`} onClick={handleClose}> <div class={`${prefixCls}__close`} onClick={handleClose}>
...@@ -246,10 +345,16 @@ ...@@ -246,10 +345,16 @@
const renderController = () => { const renderController = () => {
return ( return (
<div class={`${prefixCls}__controller`}> <div class={`${prefixCls}__controller`}>
<div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(-0.15)}> <div
class={`${prefixCls}__controller-item`}
onClick={() => scaleFunc(-getScaleStep.value)}
>
<img src={unScaleSvg} /> <img src={unScaleSvg} />
</div> </div>
<div class={`${prefixCls}__controller-item`} onClick={() => scaleFunc(0.15)}> <div
class={`${prefixCls}__controller-item`}
onClick={() => scaleFunc(getScaleStep.value)}
>
<img src={scaleSvg} /> <img src={scaleSvg} />
</div> </div>
<div class={`${prefixCls}__controller-item`} onClick={resume}> <div class={`${prefixCls}__controller-item`} onClick={resume}>
...@@ -279,7 +384,12 @@ ...@@ -279,7 +384,12 @@
return () => { return () => {
return ( return (
imgState.show && ( imgState.show && (
<div class={prefixCls} ref={wrapElRef} onMouseup={handleMouseUp}> <div
class={prefixCls}
ref={wrapElRef}
onMouseup={handleMouseUp}
onClick={handleMaskClick}
>
<div class={`${prefixCls}-content`}> <div class={`${prefixCls}-content`}>
{/*<Spin*/} {/*<Spin*/}
{/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/} {/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/}
......
...@@ -6,15 +6,12 @@ import { createVNode, render } from 'vue'; ...@@ -6,15 +6,12 @@ import { createVNode, render } from 'vue';
let instance: ReturnType<typeof createVNode> | null = null; let instance: ReturnType<typeof createVNode> | null = null;
export function createImgPreview(options: Options) { export function createImgPreview(options: Options) {
if (!isClient) return; if (!isClient) return;
const { imageList, show = true, index = 0 } = options;
const propsData: Partial<Props> = {}; const propsData: Partial<Props> = {};
const container = document.createElement('div'); const container = document.createElement('div');
propsData.imageList = imageList; Object.assign(propsData, { show: true, index: 0, scaleStep: 100 }, options);
propsData.show = show;
propsData.index = index;
instance = createVNode(ImgPreview, propsData); instance = createVNode(ImgPreview, propsData);
render(instance, container); render(instance, container);
document.body.appendChild(container); document.body.appendChild(container);
return instance.component?.exposed;
} }
...@@ -2,6 +2,12 @@ export interface Options { ...@@ -2,6 +2,12 @@ export interface Options {
show?: boolean; show?: boolean;
imageList: string[]; imageList: string[];
index?: number; index?: number;
scaleStep?: number;
defaultWidth?: number;
maskClosable?: boolean;
rememberState?: boolean;
onImgLoad?: ({ index: number, url: string, dom: HTMLImageElement }) => void;
onImgError?: ({ index: number, url: string, dom: HTMLImageElement }) => void;
} }
export interface Props { export interface Props {
...@@ -9,6 +15,19 @@ export interface Props { ...@@ -9,6 +15,19 @@ export interface Props {
instance: Props; instance: Props;
imageList: string[]; imageList: string[];
index: number; index: number;
scaleStep: number;
defaultWidth: number;
maskClosable: boolean;
rememberState: boolean;
}
export interface PreviewActions {
resume: () => void;
close: () => void;
prev: () => void;
next: () => void;
setScale: (scale: number) => void;
setRotate: (rotate: number) => void;
} }
export interface ImageProps { export interface ImageProps {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, watchEffect, PropType, ref, unref } from 'vue'; import { defineComponent, watch, PropType, ref, unref } from 'vue';
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';
...@@ -93,11 +93,16 @@ ...@@ -93,11 +93,16 @@
}); });
} }
watchEffect(() => { // 监听参数变化重新生成二维码
setTimeout(() => { watch(
props,
() => {
createQrcode(); createQrcode();
}, 30); },
}); {
deep: true,
}
);
return { wrapRef, download }; return { wrapRef, download };
}, },
......
import { toCanvas } from 'qrcode'; import { toCanvas } from 'qrcode';
import type { QRCodeRenderersOptions } from 'qrcode'; import type { QRCodeRenderersOptions } from 'qrcode';
import { RenderQrCodeParams, ContentType } from './typing'; import { RenderQrCodeParams, ContentType } from './typing';
export const renderQrCode = ({ canvas, content, width = 0, options = {} }: RenderQrCodeParams) => { import { cloneDeep } from 'lodash-es';
export const renderQrCode = ({
canvas,
content,
width = 0,
options: params = {},
}: RenderQrCodeParams) => {
const options = cloneDeep(params);
// 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率 // 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(content); options.errorCorrectionLevel = options.errorCorrectionLevel || getErrorCorrectionLevel(content);
......
...@@ -129,6 +129,7 @@ ...@@ -129,6 +129,7 @@
getDataSource, getDataSource,
setTableData, setTableData,
updateTableDataRecord, updateTableDataRecord,
findTableDataRecord,
fetch, fetch,
getRowKey, getRowKey,
reload, reload,
...@@ -211,7 +212,7 @@ ...@@ -211,7 +212,7 @@
// ...(dataSource.length === 0 ? { getPopupContainer: () => document.body } : {}), // ...(dataSource.length === 0 ? { getPopupContainer: () => document.body } : {}),
...attrs, ...attrs,
customRow, customRow,
expandIcon: expandIcon(), expandIcon: slots.expandIcon ? null : expandIcon(),
...unref(getProps), ...unref(getProps),
...unref(getHeaderProps), ...unref(getHeaderProps),
scroll: unref(getScrollRef), scroll: unref(getScrollRef),
...@@ -266,6 +267,7 @@ ...@@ -266,6 +267,7 @@
setPagination, setPagination,
setTableData, setTableData,
updateTableDataRecord, updateTableDataRecord,
findTableDataRecord,
redoHeight, redoHeight,
setSelectedRowKeys, setSelectedRowKeys,
setColumns, setColumns,
......
...@@ -9,7 +9,7 @@ import { ...@@ -9,7 +9,7 @@ import {
TimePicker, TimePicker,
} from 'ant-design-vue'; } from 'ant-design-vue';
import type { ComponentType } from './types/componentType'; import type { ComponentType } from './types/componentType';
import { ApiSelect } from '/@/components/Form'; import { ApiSelect, ApiTreeSelect } from '/@/components/Form';
const componentMap = new Map<ComponentType, Component>(); const componentMap = new Map<ComponentType, Component>();
...@@ -17,6 +17,7 @@ componentMap.set('Input', Input); ...@@ -17,6 +17,7 @@ componentMap.set('Input', Input);
componentMap.set('InputNumber', InputNumber); componentMap.set('InputNumber', InputNumber);
componentMap.set('Select', Select); componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect); componentMap.set('ApiSelect', ApiSelect);
componentMap.set('ApiTreeSelect', ApiTreeSelect);
componentMap.set('Switch', Switch); componentMap.set('Switch', Switch);
componentMap.set('Checkbox', Checkbox); componentMap.set('Checkbox', Checkbox);
componentMap.set('DatePicker', DatePicker); componentMap.set('DatePicker', DatePicker);
......
...@@ -3,7 +3,7 @@ import { BasicArrow } from '/@/components/Basic'; ...@@ -3,7 +3,7 @@ import { BasicArrow } from '/@/components/Basic';
export default () => { export default () => {
return (props: Recordable) => { return (props: Recordable) => {
if (!props.expandable) { if (!props.expandable) {
if (props.expanded) { if (props.needIndentSpaced) {
return <span class="ant-table-row-expand-icon ant-table-row-spaced" />; return <span class="ant-table-row-expand-icon ant-table-row-spaced" />;
} else { } else {
return <span />; return <span />;
......
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`"> <template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
<Tooltip v-if="action.tooltip" v-bind="getTooltip(action.tooltip)"> <Tooltip v-if="action.tooltip" v-bind="getTooltip(action.tooltip)">
<PopConfirmButton v-bind="action"> <PopConfirmButton v-bind="action">
<Icon :icon="action.icon" class="mr-1" v-if="action.icon" /> <Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" />
{{ action.label }} <template v-if="action.label">{{ action.label }}</template>
</PopConfirmButton> </PopConfirmButton>
</Tooltip> </Tooltip>
<PopConfirmButton v-else v-bind="action"> <PopConfirmButton v-else v-bind="action">
<Icon :icon="action.icon" class="mr-1" v-if="action.icon" /> <Icon :icon="action.icon" :class="{ 'mr-1': !!action.label }" v-if="action.icon" />
{{ action.label }} <template v-if="action.label">{{ action.label }}</template>
</PopConfirmButton> </PopConfirmButton>
<Divider <Divider
type="vertical" type="vertical"
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType, computed, toRaw } from 'vue'; import { defineComponent, PropType, computed, toRaw, unref } from 'vue';
import { MoreOutlined } from '@ant-design/icons-vue'; import { MoreOutlined } from '@ant-design/icons-vue';
import { Divider, Tooltip, TooltipProps } from 'ant-design-vue'; import { Divider, Tooltip, TooltipProps } from 'ant-design-vue';
import Icon from '/@/components/Icon/index'; import Icon from '/@/components/Icon/index';
...@@ -95,6 +95,7 @@ ...@@ -95,6 +95,7 @@
.map((action) => { .map((action) => {
const { popConfirm } = action; const { popConfirm } = action;
return { return {
getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body,
type: 'link', type: 'link',
size: 'small', size: 'small',
...action, ...action,
...@@ -131,19 +132,20 @@ ...@@ -131,19 +132,20 @@
}); });
function getTooltip(data: string | TooltipProps): TooltipProps { function getTooltip(data: string | TooltipProps): TooltipProps {
if (isString(data)) { return {
return { title: data, placement: 'bottom' }; getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body,
} else { placement: 'bottom',
return Object.assign({ placement: 'bottom' }, data); ...(isString(data) ? { title: data } : data),
} };
} }
function onCellClick(e: MouseEvent) { function onCellClick(e: MouseEvent) {
if (!props.stopButtonPropagation) return; if (!props.stopButtonPropagation) return;
const target = e.target as HTMLElement; const path = e.composedPath() as HTMLElement[];
if (target.tagName === 'BUTTON') { const isInButton = path.find((ele) => {
e.stopPropagation(); return ele.tagName?.toUpperCase() === 'BUTTON';
} });
isInButton && e.stopPropagation();
} }
return { prefixCls, getActions, getDropdownList, getAlign, onCellClick, getTooltip }; return { prefixCls, getActions, getDropdownList, getAlign, onCellClick, getTooltip };
...@@ -182,6 +184,12 @@ ...@@ -182,6 +184,12 @@
} }
} }
button.ant-btn-circle {
span {
margin: auto !important;
}
}
.ant-divider, .ant-divider,
.ant-divider-vertical { .ant-divider-vertical {
margin: 0 2px; margin: 0 2px;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div v-if="$slots.headerTop" style="margin: 5px"> <div v-if="$slots.headerTop" style="margin: 5px">
<slot name="headerTop"></slot> <slot name="headerTop"></slot>
</div> </div>
<div style="width: 100%; display: flex"> <div class="flex items-center">
<slot name="tableTitle" v-if="$slots.tableTitle"></slot> <slot name="tableTitle" v-if="$slots.tableTitle"></slot>
<TableTitle <TableTitle
:helpMessage="titleHelpMessage" :helpMessage="titleHelpMessage"
......
...@@ -10,10 +10,17 @@ export interface ComponentProps { ...@@ -10,10 +10,17 @@ export interface ComponentProps {
rule: boolean; rule: boolean;
popoverVisible: boolean; popoverVisible: boolean;
ruleMessage: string; ruleMessage: string;
getPopupContainer?: Fn;
} }
export const CellComponent: FunctionalComponent = ( export const CellComponent: FunctionalComponent = (
{ component = 'Input', rule = true, ruleMessage, popoverVisible }: ComponentProps, {
component = 'Input',
rule = true,
ruleMessage,
popoverVisible,
getPopupContainer,
}: ComponentProps,
{ attrs } { attrs }
) => { ) => {
const Comp = componentMap.get(component) as typeof defineComponent; const Comp = componentMap.get(component) as typeof defineComponent;
...@@ -24,7 +31,11 @@ export const CellComponent: FunctionalComponent = ( ...@@ -24,7 +31,11 @@ export const CellComponent: FunctionalComponent = (
} }
return h( return h(
Popover, Popover,
{ overlayClassName: 'edit-cell-rule-popover', visible: !!popoverVisible }, {
overlayClassName: 'edit-cell-rule-popover',
visible: !!popoverVisible,
...(getPopupContainer ? { getPopupContainer } : {}),
},
{ {
default: () => DefaultComp, default: () => DefaultComp,
content: () => ruleMessage, content: () => ruleMessage,
......
<template> <template>
<div :class="prefixCls"> <div :class="prefixCls">
<div v-show="!isEdit" :class="`${prefixCls}__normal`" @click="handleEdit"> <div
{{ getValues || '&nbsp;' }} v-show="!isEdit"
:class="{ [`${prefixCls}__normal`]: true, 'ellipsis-cell': column.ellipsis }"
@click="handleEdit"
>
<div class="cell-content" :title="column.ellipsis ? getValues || '' : ''">{{
getValues || '&nbsp;'
}}</div>
<FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" /> <FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" />
</div> </div>
...@@ -45,6 +51,7 @@ ...@@ -45,6 +51,7 @@
import { isString, isBoolean, isFunction, isNumber, isArray } from '/@/utils/is'; import { isString, isBoolean, isFunction, isNumber, isArray } from '/@/utils/is';
import { createPlaceholderMessage } from './helper'; import { createPlaceholderMessage } from './helper';
import { set, omit } from 'lodash-es'; import { set, omit } from 'lodash-es';
import { treeToList } from '/@/utils/helper/treeHelper';
export default defineComponent({ export default defineComponent({
name: 'EditableCell', name: 'EditableCell',
...@@ -106,6 +113,8 @@ ...@@ -106,6 +113,8 @@
const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val; const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
return { return {
getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body,
getCalendarContainer: () => unref(table?.wrapRef.value) ?? document.body,
placeholder: createPlaceholderMessage(unref(getComponent)), placeholder: createPlaceholderMessage(unref(getComponent)),
...apiSelectProps, ...apiSelectProps,
...omit(compProps, 'onChange'), ...omit(compProps, 'onChange'),
...@@ -154,6 +163,7 @@ ...@@ -154,6 +163,7 @@
watchEffect(() => { watchEffect(() => {
defaultValueRef.value = props.value; defaultValueRef.value = props.value;
currentValueRef.value = props.value;
}); });
watchEffect(() => { watchEffect(() => {
...@@ -275,10 +285,24 @@ ...@@ -275,10 +285,24 @@
} }
} }
// only ApiSelect // only ApiSelect or TreeSelect
function handleOptionsChange(options: LabelValueOptions) { function handleOptionsChange(options: LabelValueOptions) {
const { replaceFields } = props.column?.editComponentProps ?? {};
const component = unref(getComponent);
if (component === 'ApiTreeSelect') {
const { title = 'title', value = 'value', children = 'children' } = replaceFields || {};
let listOptions: Recordable[] = treeToList(options, { children });
listOptions = listOptions.map((item) => {
return {
label: item[title],
value: item[value],
};
});
optionsRef.value = listOptions as LabelValueOptions;
} else {
optionsRef.value = options; optionsRef.value = options;
} }
}
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) { function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
if (props.record) { if (props.record) {
...@@ -404,6 +428,16 @@ ...@@ -404,6 +428,16 @@
} }
} }
.ellipsis-cell {
.cell-content {
overflow-wrap: break-word;
word-break: break-word;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
&__normal { &__normal {
&-icon { &-icon {
position: absolute; position: absolute;
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
trigger="click" trigger="click"
@visibleChange="handleVisibleChange" @visibleChange="handleVisibleChange"
:overlayClassName="`${prefixCls}__cloumn-list`" :overlayClassName="`${prefixCls}__cloumn-list`"
:getPopupContainer="getPopupContainer"
> >
<template #title> <template #title>
<div :class="`${prefixCls}__popover-title`"> <div :class="`${prefixCls}__popover-title`">
...@@ -47,7 +48,11 @@ ...@@ -47,7 +48,11 @@
{{ item.label }} {{ item.label }}
</Checkbox> </Checkbox>
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4"> <Tooltip
placement="bottomLeft"
:mouseLeaveDelay="0.4"
:getPopupContainer="getPopupContainer"
>
<template #title> <template #title>
{{ t('component.table.settingFixedLeft') }} {{ t('component.table.settingFixedLeft') }}
</template> </template>
...@@ -64,7 +69,11 @@ ...@@ -64,7 +69,11 @@
/> />
</Tooltip> </Tooltip>
<Divider type="vertical" /> <Divider type="vertical" />
<Tooltip placement="bottomLeft" :mouseLeaveDelay="0.4"> <Tooltip
placement="bottomLeft"
:mouseLeaveDelay="0.4"
:getPopupContainer="getPopupContainer"
>
<template #title> <template #title>
{{ t('component.table.settingFixedRight') }} {{ t('component.table.settingFixedRight') }}
</template> </template>
...@@ -109,8 +118,8 @@ ...@@ -109,8 +118,8 @@
import { useTableContext } from '../../hooks/useTableContext'; import { useTableContext } from '../../hooks/useTableContext';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { useSortable } from '/@/hooks/web/useSortable'; import { useSortable } from '/@/hooks/web/useSortable';
import { isNullAndUnDef } from '/@/utils/is'; import { isFunction, isNullAndUnDef } from '/@/utils/is';
import { getPopupContainer } from '/@/utils'; import { getPopupContainer as getParentContainer } from '/@/utils';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
interface State { interface State {
...@@ -140,7 +149,7 @@ ...@@ -140,7 +149,7 @@
}, },
emits: ['columns-change'], emits: ['columns-change'],
setup(_, { emit }) { setup(_, { emit, attrs }) {
const { t } = useI18n(); const { t } = useI18n();
const table = useTableContext(); const table = useTableContext();
...@@ -350,6 +359,12 @@ ...@@ -350,6 +359,12 @@
emit('columns-change', data); emit('columns-change', data);
} }
function getPopupContainer() {
return isFunction(attrs.getPopupContainer)
? attrs.getPopupContainer()
: getParentContainer();
}
return { return {
t, t,
...toRefs(state), ...toRefs(state),
......
<template> <template>
<div class="table-settings"> <div class="table-settings">
<RedoSetting v-if="getSetting.redo" /> <RedoSetting v-if="getSetting.redo" :getPopupContainer="getTableContainer" />
<SizeSetting v-if="getSetting.size" /> <SizeSetting v-if="getSetting.size" :getPopupContainer="getTableContainer" />
<ColumnSetting v-if="getSetting.setting" @columns-change="handleColumnChange" /> <ColumnSetting
<FullScreenSetting v-if="getSetting.fullScreen" /> v-if="getSetting.setting"
@columns-change="handleColumnChange"
:getPopupContainer="getTableContainer"
/>
<FullScreenSetting v-if="getSetting.fullScreen" :getPopupContainer="getTableContainer" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import type { TableSetting, ColumnChangeParam } from '../../types/table'; import type { TableSetting, ColumnChangeParam } from '../../types/table';
import { defineComponent, computed } from 'vue'; import { defineComponent, computed, unref } from 'vue';
import ColumnSetting from './ColumnSetting.vue'; import ColumnSetting from './ColumnSetting.vue';
import SizeSetting from './SizeSetting.vue'; import SizeSetting from './SizeSetting.vue';
import RedoSetting from './RedoSetting.vue'; import RedoSetting from './RedoSetting.vue';
import FullScreenSetting from './FullScreenSetting.vue'; import FullScreenSetting from './FullScreenSetting.vue';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { useTableContext } from '../../hooks/useTableContext';
export default defineComponent({ export default defineComponent({
name: 'TableSetting', name: 'TableSetting',
...@@ -33,6 +38,7 @@ ...@@ -33,6 +38,7 @@
emits: ['columns-change'], emits: ['columns-change'],
setup(props, { emit }) { setup(props, { emit }) {
const { t } = useI18n(); const { t } = useI18n();
const table = useTableContext();
const getSetting = computed((): TableSetting => { const getSetting = computed((): TableSetting => {
return { return {
...@@ -48,7 +54,11 @@ ...@@ -48,7 +54,11 @@
emit('columns-change', data); emit('columns-change', data);
} }
return { getSetting, t, handleColumnChange }; function getTableContainer() {
return table ? unref(table.wrapRef) : document.body;
}
return { getSetting, t, handleColumnChange, getTableContainer };
}, },
}); });
</script> </script>
......
...@@ -149,18 +149,8 @@ export function useDataSource( ...@@ -149,18 +149,8 @@ export function useDataSource(
rowKey: string | number, rowKey: string | number,
record: Recordable record: Recordable
): Recordable | undefined { ): Recordable | undefined {
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return; const row = findTableDataRecord(rowKey);
const rowKeyName = unref(getRowKey);
if (!rowKeyName) {
return;
}
const row = dataSourceRef.value.find((r) => {
if (typeof rowKeyName === 'function') {
return (rowKeyName(r) as string) === rowKey;
} else {
return Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey;
}
});
if (row) { if (row) {
for (const field in row) { for (const field in row) {
if (Reflect.has(record, field)) row[field] = record[field]; if (Reflect.has(record, field)) row[field] = record[field];
...@@ -169,6 +159,43 @@ export function useDataSource( ...@@ -169,6 +159,43 @@ export function useDataSource(
} }
} }
function findTableDataRecord(rowKey: string | number) {
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
const rowKeyName = unref(getRowKey);
if (!rowKeyName) return;
const { childrenColumnName = 'children' } = unref(propsRef);
const findRow = (array: any[]) => {
let ret;
array.some(function iter(r) {
if (typeof rowKeyName === 'function') {
if ((rowKeyName(r) as string) === rowKey) {
ret = r;
return true;
}
} else {
if (Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey) {
ret = r;
return true;
}
}
return r[childrenColumnName] && r[childrenColumnName].some(iter);
});
return ret;
};
// const row = dataSourceRef.value.find(r => {
// if (typeof rowKeyName === 'function') {
// return (rowKeyName(r) as string) === rowKey
// } else {
// return Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey
// }
// })
return findRow(dataSourceRef.value);
}
async function fetch(opt?: FetchParams) { async function fetch(opt?: FetchParams) {
const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } = const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } =
unref(propsRef); unref(propsRef);
...@@ -280,6 +307,7 @@ export function useDataSource( ...@@ -280,6 +307,7 @@ export function useDataSource(
reload, reload,
updateTableData, updateTableData,
updateTableDataRecord, updateTableDataRecord,
findTableDataRecord,
handleTableChange, handleTableChange,
}; };
} }
import type { PaginationProps } from '../types/pagination'; import type { PaginationProps } from '../types/pagination';
import type { BasicTableProps } from '../types/table'; import type { BasicTableProps } from '../types/table';
import { computed, unref, ref, ComputedRef } from 'vue'; import { computed, unref, ref, ComputedRef, watchEffect } from 'vue';
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'; import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
import { isBoolean } from '/@/utils/is'; import { isBoolean } from '/@/utils/is';
import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../const'; import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../const';
...@@ -27,6 +27,16 @@ export function usePagination(refProps: ComputedRef<BasicTableProps>) { ...@@ -27,6 +27,16 @@ export function usePagination(refProps: ComputedRef<BasicTableProps>) {
const configRef = ref<PaginationProps>({}); const configRef = ref<PaginationProps>({});
const show = ref(true); const show = ref(true);
watchEffect(() => {
const { pagination } = unref(refProps);
if (!isBoolean(pagination) && pagination) {
configRef.value = {
...unref(configRef),
...(pagination ?? {}),
};
}
});
const getPaginationInfo = computed((): PaginationProps | boolean => { const getPaginationInfo = computed((): PaginationProps | boolean => {
const { pagination } = unref(refProps); const { pagination } = unref(refProps);
......
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import type { BasicTableProps, TableRowSelection } from '../types/table'; import type { BasicTableProps, TableRowSelection } from '../types/table';
import { computed, ref, unref, ComputedRef, Ref, toRaw, watch, nextTick } from 'vue'; import { computed, ComputedRef, nextTick, Ref, ref, toRaw, unref, watch } from 'vue';
import { ROW_KEY } from '../const'; import { ROW_KEY } from '../const';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
import { findNodeAll } from '/@/utils/helper/treeHelper';
export function useRowSelection( export function useRowSelection(
propsRef: ComputedRef<BasicTableProps>, propsRef: ComputedRef<BasicTableProps>,
...@@ -21,11 +22,12 @@ export function useRowSelection( ...@@ -21,11 +22,12 @@ export function useRowSelection(
return { return {
selectedRowKeys: unref(selectedRowKeysRef), selectedRowKeys: unref(selectedRowKeysRef),
hideDefaultSelections: false, hideDefaultSelections: false,
onChange: (selectedRowKeys: string[], selectedRows: Recordable[]) => { onChange: (selectedRowKeys: string[]) => {
selectedRowKeysRef.value = selectedRowKeys; setSelectedRowKeys(selectedRowKeys);
selectedRowRef.value = selectedRows; // selectedRowKeysRef.value = selectedRowKeys;
// selectedRowRef.value = selectedRows;
}, },
...omit(rowSelection === undefined ? {} : rowSelection, ['onChange']), ...omit(rowSelection, ['onChange']),
}; };
}); });
...@@ -64,11 +66,13 @@ export function useRowSelection( ...@@ -64,11 +66,13 @@ export function useRowSelection(
function setSelectedRowKeys(rowKeys: string[]) { function setSelectedRowKeys(rowKeys: string[]) {
selectedRowKeysRef.value = rowKeys; selectedRowKeysRef.value = rowKeys;
selectedRowRef.value = findNodeAll(
const rows = toRaw(unref(tableData)).filter((item) => toRaw(unref(tableData)),
rowKeys.includes(item[unref(getRowKey) as string]) (item) => rowKeys.includes(item[unref(getRowKey) as string]),
{
children: propsRef.value.childrenColumnName ?? 'children',
}
); );
selectedRowRef.value = rows;
} }
function setSelectedRows(rows: Recordable[]) { function setSelectedRows(rows: Recordable[]) {
......
...@@ -122,6 +122,9 @@ export function useTable(tableProps?: Props): [ ...@@ -122,6 +122,9 @@ export function useTable(tableProps?: Props): [
updateTableDataRecord: (rowKey: string | number, record: Recordable) => { updateTableDataRecord: (rowKey: string | number, record: Recordable) => {
return getTableInstance().updateTableDataRecord(rowKey, record); return getTableInstance().updateTableDataRecord(rowKey, record);
}, },
findTableDataRecord: (rowKey: string | number) => {
return getTableInstance().findTableDataRecord(rowKey);
},
getRowSelection: () => { getRowSelection: () => {
return toRaw(getTableInstance().getRowSelection()); return toRaw(getTableInstance().getRowSelection());
}, },
......
...@@ -3,6 +3,7 @@ export type ComponentType = ...@@ -3,6 +3,7 @@ export type ComponentType =
| 'InputNumber' | 'InputNumber'
| 'Select' | 'Select'
| 'ApiSelect' | 'ApiSelect'
| 'ApiTreeSelect'
| 'Checkbox' | 'Checkbox'
| 'Switch' | 'Switch'
| 'DatePicker' | 'DatePicker'
......
...@@ -95,6 +95,7 @@ export interface TableActionType { ...@@ -95,6 +95,7 @@ export interface TableActionType {
setPagination: (info: Partial<PaginationProps>) => void; setPagination: (info: Partial<PaginationProps>) => void;
setTableData: <T = Recordable>(values: T[]) => void; setTableData: <T = Recordable>(values: T[]) => void;
updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void; updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void;
findTableDataRecord: (rowKey: string | number) => Recordable | void;
getColumns: (opt?: GetColumnsParams) => BasicColumn[]; getColumns: (opt?: GetColumnsParams) => BasicColumn[];
setColumns: (columns: BasicColumn[] | string[]) => void; setColumns: (columns: BasicColumn[] | string[]) => void;
getDataSource: <T = Recordable>() => T[]; getDataSource: <T = Recordable>() => T[];
......
...@@ -278,8 +278,9 @@ ...@@ -278,8 +278,9 @@
if (!editor) { if (!editor) {
return; return;
} }
editor.execCommand('mceInsertContent', false, getUploadingImgName(name));
const content = editor?.getContent() ?? ''; const content = editor?.getContent() ?? '';
setValue(editor, `${content}\n${getUploadingImgName(name)}`); setValue(editor, content);
} }
function handleDone(name: string, url: string) { function handleDone(name: string, url: string) {
......
...@@ -18,8 +18,8 @@ ...@@ -18,8 +18,8 @@
import TreeHeader from './TreeHeader.vue'; import TreeHeader from './TreeHeader.vue';
import { ScrollContainer } from '/@/components/Container'; import { ScrollContainer } from '/@/components/Container';
import { omit, get } from 'lodash-es'; import { omit, get, difference } from 'lodash-es';
import { isBoolean, isFunction } from '/@/utils/is'; import { isArray, isBoolean, isFunction } from '/@/utils/is';
import { extendSlots, getSlot } from '/@/utils/helper/tsxHelper'; import { extendSlots, getSlot } from '/@/utils/helper/tsxHelper';
import { filter } from '/@/utils/helper/treeHelper'; import { filter } from '/@/utils/helper/treeHelper';
...@@ -90,8 +90,19 @@ ...@@ -90,8 +90,19 @@
emit('update:selectedKeys', v); emit('update:selectedKeys', v);
}, },
onCheck: (v: CheckKeys, e: CheckEvent) => { onCheck: (v: CheckKeys, e: CheckEvent) => {
let currentValue = toRaw(state.checkedKeys) as Keys;
if (isArray(currentValue) && searchState.startSearch) {
const { key } = unref(getReplaceFields);
currentValue = difference(currentValue, getChildrenKeys(e.node.$attrs.node[key]));
if (e.checked) {
currentValue.push(e.node.$attrs.node[key]);
}
state.checkedKeys = currentValue;
} else {
state.checkedKeys = v; state.checkedKeys = v;
const rawVal = toRaw(v); }
const rawVal = toRaw(state.checkedKeys);
emit('update:value', rawVal); emit('update:value', rawVal);
emit('check', rawVal, e); emit('check', rawVal, e);
}, },
...@@ -115,6 +126,8 @@ ...@@ -115,6 +126,8 @@
filterByLevel, filterByLevel,
updateNodeByKey, updateNodeByKey,
getAllKeys, getAllKeys,
getChildrenKeys,
getEnabledKeys,
} = useTree(treeDataRef, getReplaceFields); } = useTree(treeDataRef, getReplaceFields);
function getIcon(params: Recordable, icon?: string) { function getIcon(params: Recordable, icon?: string) {
...@@ -168,7 +181,7 @@ ...@@ -168,7 +181,7 @@
} }
function checkAll(checkAll: boolean) { function checkAll(checkAll: boolean) {
state.checkedKeys = checkAll ? getAllKeys() : ([] as Keys); state.checkedKeys = checkAll ? getEnabledKeys() : ([] as Keys);
} }
function expandAll(expandAll: boolean) { function expandAll(expandAll: boolean) {
......
...@@ -26,6 +26,45 @@ export function useTree( ...@@ -26,6 +26,45 @@ export function useTree(
} }
return keys as Keys; return keys as Keys;
} }
// get keys that can be checked and selected
function getEnabledKeys(list?: TreeDataItem[]) {
const keys: string[] = [];
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getReplaceFields);
if (!childrenField || !keyField) return keys;
for (let index = 0; index < treeData.length; index++) {
const node = treeData[index];
node.disabled !== true && node.selectable !== false && keys.push(node[keyField]!);
const children = node[childrenField];
if (children && children.length) {
keys.push(...(getEnabledKeys(children) as string[]));
}
}
return keys as Keys;
}
function getChildrenKeys(nodeKey: string | number, list?: TreeDataItem[]): Keys {
const keys: Keys = [];
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getReplaceFields);
if (!childrenField || !keyField) return keys;
for (let index = 0; index < treeData.length; index++) {
const node = treeData[index];
const children = node[childrenField];
if (nodeKey === node[keyField]) {
keys.push(node[keyField]!);
if (children && children.length) {
keys.push(...(getAllKeys(children) as string[]));
}
} else {
if (children && children.length) {
keys.push(...getChildrenKeys(nodeKey, children));
}
}
}
return keys as Keys;
}
// Update node // Update node
function updateNodeByKey(key: string, node: TreeDataItem, list?: TreeDataItem[]) { function updateNodeByKey(key: string, node: TreeDataItem, list?: TreeDataItem[]) {
...@@ -146,5 +185,7 @@ export function useTree( ...@@ -146,5 +185,7 @@ export function useTree(
filterByLevel, filterByLevel,
updateNodeByKey, updateNodeByKey,
getAllKeys, getAllKeys,
getChildrenKeys,
getEnabledKeys,
}; };
} }
<script lang="tsx"> <script lang="tsx">
import type { MoveData, DragVerifyActionType } from './typing'; import type { MoveData, DragVerifyActionType } from './typing';
import { defineComponent, computed, unref, reactive, watch, ref, getCurrentInstance } from 'vue'; import { defineComponent, computed, unref, reactive, watch, ref } from 'vue';
import { useTimeoutFn } from '/@/hooks/core/useTimeout'; import { useTimeoutFn } from '/@/hooks/core/useTimeout';
import BasicDragVerify from './DragVerify.vue'; import BasicDragVerify from './DragVerify.vue';
import { hackCss } from '/@/utils/domUtils'; import { hackCss } from '/@/utils/domUtils';
...@@ -8,11 +8,11 @@ ...@@ -8,11 +8,11 @@
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({ export default defineComponent({
name: 'ImgRotateDargVerify', name: 'ImgRotateDragVerify',
inheritAttrs: false, inheritAttrs: false,
props: rotateProps, props: rotateProps,
emits: ['success', 'change', 'update:value'], emits: ['success', 'change', 'update:value'],
setup(props, { emit, attrs }) { setup(props, { emit, attrs, expose }) {
const basicRef = ref<Nullable<DragVerifyActionType>>(null); const basicRef = ref<Nullable<DragVerifyActionType>>(null);
const state = reactive({ const state = reactive({
showTip: false, showTip: false,
...@@ -112,10 +112,8 @@ ...@@ -112,10 +112,8 @@
handleImgOnLoad(); handleImgOnLoad();
} }
const instance = getCurrentInstance() as any; expose({ resume });
if (instance) {
instance.resume = resume;
}
// handleImgOnLoad(); // handleImgOnLoad();
return () => { return () => {
const { src } = props; const { src } = props;
...@@ -138,6 +136,7 @@ ...@@ -138,6 +136,7 @@
onClick={() => { onClick={() => {
resume(); resume();
}} }}
alt="verify"
/> />
{state.showTip && ( {state.showTip && (
<span class={[`ir-dv-img__tip`, state.isPassing ? 'success' : 'error']}> <span class={[`ir-dv-img__tip`, state.isPassing ? 'success' : 'error']}>
......
...@@ -50,15 +50,15 @@ ...@@ -50,15 +50,15 @@
border-color: @button-cancel-hover-border-color; border-color: @button-cancel-hover-border-color;
} }
&[disabled], //&[disabled],
&[disabled]:hover { //&[disabled]:hover {
color: fade(@button-cancel-color, 40%); // color: fade(@button-cancel-color, 40%);
background: fade(@button-cancel-bg-color, 40%); // background: fade(@button-cancel-bg-color, 40%);
border-color: fade(@button-cancel-border-color, 40%); // border-color: fade(@button-cancel-border-color, 40%);
} //}
} }
&.ant-btn-link.is-disabled { [data-theme='light'] &.ant-btn-link.is-disabled {
color: rgba(0, 0, 0, 0.25); color: rgba(0, 0, 0, 0.25);
text-shadow: none; text-shadow: none;
cursor: not-allowed !important; cursor: not-allowed !important;
...@@ -67,6 +67,15 @@ ...@@ -67,6 +67,15 @@
box-shadow: none; box-shadow: none;
} }
[data-theme='dark'] &.ant-btn-link.is-disabled {
color: rgba(255, 255, 255, 0.25) !important;
text-shadow: none;
cursor: not-allowed !important;
background-color: transparent !important;
border-color: transparent !important;
box-shadow: none;
}
// color: @white; // color: @white;
&-success.ant-btn-link:not([disabled='disabled']) { &-success.ant-btn-link:not([disabled='disabled']) {
......
@import './pagination.less'; @import './pagination.less';
@import './input.less'; @import './input.less';
@import './btn.less'; @import './btn.less';
@import './table.less'; // @import './table.less';
// TODO beta.11 fix // TODO beta.11 fix
.ant-col { .ant-col {
...@@ -57,3 +57,9 @@ span.anticon:not(.app-iconify) { ...@@ -57,3 +57,9 @@ span.anticon:not(.app-iconify) {
.modal-icon-info { .modal-icon-info {
color: @primary-color !important; color: @primary-color !important;
} }
.ant-checkbox-checked .ant-checkbox-inner::after,
.ant-tree-checkbox-checked .ant-tree-checkbox-inner::after {
border-top: 0 !important;
border-left: 0 !important;
}
/*! @import */
@tailwind base;
@tailwind components;
@tailwind utilities;
...@@ -6,9 +6,24 @@ html[data-theme='light'] { ...@@ -6,9 +6,24 @@ html[data-theme='light'] {
.text-secondary { .text-secondary {
color: rgba(0, 0, 0, 0.45); color: rgba(0, 0, 0, 0.45);
} }
.ant-alert-success {
background-color: #f6ffed;
border: 1px solid #b7eb8f;
}
.ant-alert-error {
background-color: #fff2f0;
border: 1px solid #ffccc7;
}
.ant-alert-warning {
background-color: #fffbe6;
border: 1px solid #ffe58f;
}
} }
html[data-theme='dark'] { [data-theme='dark'] {
.text-secondary { .text-secondary {
color: #8b949e; color: #8b949e;
} }
...@@ -23,14 +38,11 @@ html[data-theme='dark'] { ...@@ -23,14 +38,11 @@ html[data-theme='dark'] {
0 1px 0 0 #434343 inset; 0 1px 0 0 #434343 inset;
} }
.ant-alert-message, .ant-calendar-selected-day .ant-calendar-date {
.ant-alert-with-description .ant-alert-message, color: rgba(0, 0, 0, 0.8);
.ant-alert-description {
color: rgba(0, 0, 0, 0.85);
} }
.ant-checkbox-checked .ant-checkbox-inner::after { .ant-select-tree li .ant-select-tree-node-content-wrapper.ant-select-tree-node-selected {
border-top: 0; color: rgba(0, 0, 0, 0.9);
border-left: 0;
} }
} }
...@@ -3,6 +3,7 @@ import type { Ref } from 'vue'; ...@@ -3,6 +3,7 @@ import type { Ref } from 'vue';
interface Params { interface Params {
excludeListeners?: boolean; excludeListeners?: boolean;
excludeKeys?: string[]; excludeKeys?: string[];
excludeDefaultKeys?: boolean;
} }
const DEFAULT_EXCLUDE_KEYS = ['class', 'style']; const DEFAULT_EXCLUDE_KEYS = ['class', 'style'];
...@@ -16,9 +17,9 @@ export function useAttrs(params: Params = {}): Ref<Recordable> | {} { ...@@ -16,9 +17,9 @@ export function useAttrs(params: Params = {}): Ref<Recordable> | {} {
const instance = getCurrentInstance(); const instance = getCurrentInstance();
if (!instance) return {}; if (!instance) return {};
const { excludeListeners = false, excludeKeys = [] } = params; const { excludeListeners = false, excludeKeys = [], excludeDefaultKeys = true } = params;
const attrs = shallowRef({}); const attrs = shallowRef({});
const allExcludeKeys = excludeKeys.concat(DEFAULT_EXCLUDE_KEYS); const allExcludeKeys = excludeKeys.concat(excludeDefaultKeys ? DEFAULT_EXCLUDE_KEYS : []);
// Since attrs are not reactive, make it reactive instead of doing in `onUpdated` hook for better performance // Since attrs are not reactive, make it reactive instead of doing in `onUpdated` hook for better performance
instance.attrs = reactive(instance.attrs); instance.attrs = reactive(instance.attrs);
......
import { onMounted, ref } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
interface ScriptOptions { interface ScriptOptions {
src: string; src: string;
...@@ -8,10 +8,11 @@ export function useScript(opts: ScriptOptions) { ...@@ -8,10 +8,11 @@ export function useScript(opts: ScriptOptions) {
const isLoading = ref(false); const isLoading = ref(false);
const error = ref(false); const error = ref(false);
const success = ref(false); const success = ref(false);
let script: HTMLScriptElement;
const promise = new Promise((resolve, reject) => { const promise = new Promise((resolve, reject) => {
onMounted(() => { onMounted(() => {
const script = document.createElement('script'); script = document.createElement('script');
script.type = 'text/javascript'; script.type = 'text/javascript';
script.onload = function () { script.onload = function () {
isLoading.value = false; isLoading.value = false;
...@@ -32,6 +33,10 @@ export function useScript(opts: ScriptOptions) { ...@@ -32,6 +33,10 @@ export function useScript(opts: ScriptOptions) {
}); });
}); });
onUnmounted(() => {
script && script.remove();
});
return { return {
isLoading, isLoading,
error, error,
......
import { getCurrentInstance, onBeforeUnmount, ref, Ref, unref } from 'vue'; import { getCurrentInstance, onBeforeUnmount, ref, Ref, shallowRef, unref } from 'vue';
import { useRafThrottle } from '/@/utils/domUtils';
import { addResizeListener, removeResizeListener } from '/@/utils/event';
import { isDef } from '/@/utils/is';
const domSymbol = Symbol('watermark-dom'); const domSymbol = Symbol('watermark-dom');
export function useWatermark( export function useWatermark(
appendEl: Ref<HTMLElement | null> = ref(document.body) as Ref<HTMLElement> appendEl: Ref<HTMLElement | null> = ref(document.body) as Ref<HTMLElement>
) { ) {
let func: Fn = () => {}; const func = useRafThrottle(function () {
const el = unref(appendEl);
if (!el) return;
const { clientHeight: height, clientWidth: width } = el;
updateWatermark({ height, width });
});
const id = domSymbol.toString(); const id = domSymbol.toString();
const watermarkEl = shallowRef<HTMLElement>();
const clear = () => { const clear = () => {
const domId = document.getElementById(id); const domId = unref(watermarkEl);
if (domId) { watermarkEl.value = undefined;
const el = unref(appendEl); const el = unref(appendEl);
el && el.removeChild(domId); if (!el) return;
} domId && el.removeChild(domId);
window.removeEventListener('resize', func); removeResizeListener(el, func);
}; };
const createWatermark = (str: string) => {
clear();
function createBase64(str: string) {
const can = document.createElement('canvas'); const can = document.createElement('canvas');
can.width = 300; const width = 300;
can.height = 240; const height = 240;
Object.assign(can, { width, height });
const cans = can.getContext('2d'); const cans = can.getContext('2d');
if (cans) { if (cans) {
...@@ -29,30 +39,55 @@ export function useWatermark( ...@@ -29,30 +39,55 @@ export function useWatermark(
cans.fillStyle = 'rgba(0, 0, 0, 0.15)'; cans.fillStyle = 'rgba(0, 0, 0, 0.15)';
cans.textAlign = 'left'; cans.textAlign = 'left';
cans.textBaseline = 'middle'; cans.textBaseline = 'middle';
cans.fillText(str, can.width / 20, can.height); cans.fillText(str, width / 20, height);
}
return can.toDataURL('image/png');
} }
function updateWatermark(
options: {
width?: number;
height?: number;
str?: string;
} = {}
) {
const el = unref(watermarkEl);
if (!el) return;
if (isDef(options.width)) {
el.style.width = `${options.width}px`;
}
if (isDef(options.height)) {
el.style.height = `${options.height}px`;
}
if (isDef(options.str)) {
el.style.background = `url(${createBase64(options.str)}) left top repeat`;
}
}
const createWatermark = (str: string) => {
if (unref(watermarkEl)) {
updateWatermark({ str });
return id;
}
const div = document.createElement('div'); const div = document.createElement('div');
watermarkEl.value = div;
div.id = id; div.id = id;
div.style.pointerEvents = 'none'; div.style.pointerEvents = 'none';
div.style.top = '0px'; div.style.top = '0px';
div.style.left = '0px'; div.style.left = '0px';
div.style.position = 'absolute'; div.style.position = 'absolute';
div.style.zIndex = '100000'; div.style.zIndex = '100000';
div.style.width = document.documentElement.clientWidth + 'px';
div.style.height = document.documentElement.clientHeight + 'px';
div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat';
const el = unref(appendEl); const el = unref(appendEl);
el && el.appendChild(div); if (!el) return id;
const { clientHeight: height, clientWidth: width } = el;
updateWatermark({ str, width, height });
el.appendChild(div);
return id; return id;
}; };
function setWatermark(str: string) { function setWatermark(str: string) {
createWatermark(str); createWatermark(str);
func = () => { addResizeListener(document.documentElement, func);
createWatermark(str);
};
window.addEventListener('resize', func);
const instance = getCurrentInstance(); const instance = getCurrentInstance();
if (instance) { if (instance) {
onBeforeUnmount(() => { onBeforeUnmount(() => {
......
<template> <template>
<div :class="[prefixCls, `${prefixCls}--${theme}`]"> <div :class="[prefixCls, `${prefixCls}--${theme}`]">
<a-breadcrumb :routes="routes"> <a-breadcrumb :routes="routes">
<template #itemRender="{ route, routes, paths }"> <template #itemRender="{ route, routes: routesMatched, paths }">
<Icon :icon="getIcon(route)" v-if="getShowBreadCrumbIcon && getIcon(route)" /> <Icon :icon="getIcon(route)" v-if="getShowBreadCrumbIcon && getIcon(route)" />
<span v-if="!hasRedirect(routes, route)"> <span v-if="!hasRedirect(routesMatched, route)">
{{ t(route.name || route.meta.title) }} {{ t(route.name || route.meta.title) }}
</span> </span>
<router-link v-else to="" @click="handleClick(route, paths, $event)"> <router-link v-else to="" @click="handleClick(route, paths, $event)">
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import type { RouteLocationMatched } from 'vue-router'; import type { RouteLocationMatched } from 'vue-router';
import { useRouter } from 'vue-router';
import type { Menu } from '/@/router/types'; import type { Menu } from '/@/router/types';
import { defineComponent, ref, watchEffect } from 'vue'; import { defineComponent, ref, watchEffect } from 'vue';
...@@ -26,7 +27,6 @@ ...@@ -26,7 +27,6 @@
import { useRootSetting } from '/@/hooks/setting/useRootSetting'; import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { useGo } from '/@/hooks/web/usePage'; import { useGo } from '/@/hooks/web/usePage';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { useRouter } from 'vue-router';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
import { isString } from '/@/utils/is'; import { isString } from '/@/utils/is';
...@@ -96,7 +96,7 @@ ...@@ -96,7 +96,7 @@
} }
function filterItem(list: RouteLocationMatched[]) { function filterItem(list: RouteLocationMatched[]) {
let resultList = filter(list, (item) => { return filter(list, (item) => {
const { meta, name } = item; const { meta, name } = item;
if (!meta) { if (!meta) {
return !!name; return !!name;
...@@ -107,8 +107,6 @@ ...@@ -107,8 +107,6 @@
} }
return true; return true;
}).filter((item) => !item.meta?.hideBreadcrumb || !item.meta?.hideMenu); }).filter((item) => !item.meta?.hideBreadcrumb || !item.meta?.hideMenu);
return resultList;
} }
function handleClick(route: RouteLocationMatched, paths: string[], e: Event) { function handleClick(route: RouteLocationMatched, paths: string[], e: Event) {
...@@ -140,10 +138,7 @@ ...@@ -140,10 +138,7 @@
} }
function hasRedirect(routes: RouteLocationMatched[], route: RouteLocationMatched) { function hasRedirect(routes: RouteLocationMatched[], route: RouteLocationMatched) {
if (routes.indexOf(route) === routes.length - 1) { return routes.indexOf(route) !== routes.length - 1;
return false;
}
return true;
} }
function getIcon(route) { function getIcon(route) {
......
<template> <template>
<a-list :class="prefixCls"> <a-list :class="prefixCls" bordered :pagination="getPagination">
<template v-for="item in list" :key="item.id"> <template v-for="item in getData" :key="item.id">
<a-list-item class="list-item"> <a-list-item class="list-item">
<a-list-item-meta> <a-list-item-meta>
<template #title> <template #title>
<div class="title"> <div class="title">
{{ item.title }} <a-typography-paragraph
@click="handleTitleClick(item)"
style="width: 100%; margin-bottom: 0 !important"
:style="{ cursor: isTitleClickable ? 'pointer' : '' }"
:delete="!!item.titleDelete"
:ellipsis="
$props.titleRows > 0 ? { rows: $props.titleRows, tooltip: item.title } : false
"
:content="item.title"
/>
<div class="extra" v-if="item.extra"> <div class="extra" v-if="item.extra">
<a-tag class="tag" :color="item.color"> <a-tag class="tag" :color="item.color">
{{ item.extra }} {{ item.extra }}
...@@ -21,8 +30,16 @@ ...@@ -21,8 +30,16 @@
<template #description> <template #description>
<div> <div>
<div class="description"> <div class="description" v-if="item.description">
{{ item.description }} <a-typography-paragraph
style="width: 100%; margin-bottom: 0 !important"
:ellipsis="
$props.descRows > 0
? { rows: $props.descRows, tooltip: item.description }
: false
"
:content="item.description"
/>
</div> </div>
<div class="datetime"> <div class="datetime">
{{ item.datetime }} {{ item.datetime }}
...@@ -35,16 +52,18 @@ ...@@ -35,16 +52,18 @@
</a-list> </a-list>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType } from 'vue'; import { computed, defineComponent, PropType, ref, watch, unref } from 'vue';
import { ListItem } from './data'; import { ListItem } from './data';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { List, Avatar, Tag } from 'ant-design-vue'; import { List, Avatar, Tag, Typography } from 'ant-design-vue';
import { isNumber } from '/@/utils/is';
export default defineComponent({ export default defineComponent({
components: { components: {
[Avatar.name]: Avatar, [Avatar.name]: Avatar,
[List.name]: List, [List.name]: List,
[List.Item.name]: List.Item, [List.Item.name]: List.Item,
AListItemMeta: List.Item.Meta, AListItemMeta: List.Item.Meta,
ATypographyParagraph: Typography.Paragraph,
[Tag.name]: Tag, [Tag.name]: Tag,
}, },
props: { props: {
...@@ -52,10 +71,67 @@ ...@@ -52,10 +71,67 @@
type: Array as PropType<ListItem[]>, type: Array as PropType<ListItem[]>,
default: () => [], default: () => [],
}, },
pageSize: {
type: [Boolean, Number] as PropType<Boolean | Number>,
default: 5,
}, },
setup() { currentPage: {
type: Number,
default: 1,
},
titleRows: {
type: Number,
default: 1,
},
descRows: {
type: Number,
default: 2,
},
onTitleClick: {
type: Function as PropType<(Recordable) => void>,
},
},
emits: ['update:currentPage'],
setup(props, { emit }) {
const { prefixCls } = useDesign('header-notify-list'); const { prefixCls } = useDesign('header-notify-list');
return { prefixCls }; const current = ref(props.currentPage || 1);
const getData = computed(() => {
const { pageSize, list } = props;
console.log('refreshData', list);
if (pageSize === false) return [];
let size = isNumber(pageSize) ? pageSize : 5;
return list.slice(size * (unref(current) - 1), size * unref(current));
});
watch(
() => props.currentPage,
(v) => {
current.value = v;
}
);
const isTitleClickable = computed(() => !!props.onTitleClick);
const getPagination = computed(() => {
const { list, pageSize } = props;
if (pageSize > 0 && list && list.length > pageSize) {
return {
total: list.length,
pageSize,
//size: 'small',
current: unref(current),
onChange(page) {
current.value = page;
emit('update:currentPage', page);
},
};
} else {
return false;
}
});
function handleTitleClick(item: ListItem) {
props.onTitleClick && props.onTitleClick(item);
}
return { prefixCls, getPagination, getData, handleTitleClick, isTitleClickable };
}, },
}); });
</script> </script>
...@@ -67,6 +143,10 @@ ...@@ -67,6 +143,10 @@
display: none; display: none;
} }
::v-deep(.ant-pagination-disabled) {
display: inline-block !important;
}
&-item { &-item {
padding: 6px; padding: 6px;
overflow: hidden; overflow: hidden;
......
export interface ListItem { export interface ListItem {
id: string; id: string;
avatar: string; avatar: string;
// 通知的标题内容
title: string; title: string;
// 是否在标题上显示删除线
titleDelete?: boolean;
datetime: string; datetime: string;
type: string; type: string;
read?: boolean; read?: boolean;
...@@ -56,6 +59,55 @@ export const tabListData: TabItem[] = [ ...@@ -56,6 +59,55 @@ export const tabListData: TabItem[] = [
datetime: '2017-08-07', datetime: '2017-08-07',
type: '1', type: '1',
}, },
{
id: '000000005',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title:
'标题可以设置自动显示省略号,本例中标题行数已设为1行,如果内容超过1行将自动截断并支持tooltip显示完整标题。',
description: '',
datetime: '2017-08-07',
type: '1',
},
{
id: '000000006',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
description: '',
datetime: '2017-08-07',
type: '1',
},
{
id: '000000007',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
description: '',
datetime: '2017-08-07',
type: '1',
},
{
id: '000000008',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
description: '',
datetime: '2017-08-07',
type: '1',
},
{
id: '000000009',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
description: '',
datetime: '2017-08-07',
type: '1',
},
{
id: '000000010',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
description: '',
datetime: '2017-08-07',
type: '1',
},
], ],
}, },
{ {
...@@ -84,7 +136,8 @@ export const tabListData: TabItem[] = [ ...@@ -84,7 +136,8 @@ export const tabListData: TabItem[] = [
id: '000000008', id: '000000008',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '标题', title: '标题',
description: '这种模板用于提醒谁与你发生了互动', description:
'请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容',
datetime: '2017-08-07', datetime: '2017-08-07',
type: '2', type: '2',
clickClose: true, clickClose: true,
......
...@@ -6,13 +6,15 @@ ...@@ -6,13 +6,15 @@
</Badge> </Badge>
<template #content> <template #content>
<Tabs> <Tabs>
<template v-for="item in tabListData" :key="item.key"> <template v-for="item in listData" :key="item.key">
<TabPane> <TabPane>
<template #tab> <template #tab>
{{ item.name }} {{ item.name }}
<span v-if="item.list.length !== 0">({{ item.list.length }})</span> <span v-if="item.list.length !== 0">({{ item.list.length }})</span>
</template> </template>
<NoticeList :list="item.list" /> <!-- 绑定title-click事件的通知列表中标题是“可点击”的-->
<NoticeList :list="item.list" v-if="item.key === '1'" @title-click="onNoticeClick" />
<NoticeList :list="item.list" v-else />
</TabPane> </TabPane>
</template> </template>
</Tabs> </Tabs>
...@@ -21,28 +23,40 @@ ...@@ -21,28 +23,40 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, ref } from 'vue';
import { Popover, Tabs, Badge } from 'ant-design-vue'; import { Popover, Tabs, Badge } from 'ant-design-vue';
import { BellOutlined } from '@ant-design/icons-vue'; import { BellOutlined } from '@ant-design/icons-vue';
import { tabListData } from './data'; import { tabListData, ListItem } from './data';
import NoticeList from './NoticeList.vue'; import NoticeList from './NoticeList.vue';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { useMessage } from '/@/hooks/web/useMessage';
export default defineComponent({ export default defineComponent({
components: { Popover, BellOutlined, Tabs, TabPane: Tabs.TabPane, Badge, NoticeList }, components: { Popover, BellOutlined, Tabs, TabPane: Tabs.TabPane, Badge, NoticeList },
setup() { setup() {
const { prefixCls } = useDesign('header-notify'); const { prefixCls } = useDesign('header-notify');
const { createMessage } = useMessage();
const listData = ref(tabListData);
const count = computed(() => {
let count = 0; let count = 0;
for (let i = 0; i < tabListData.length; i++) { for (let i = 0; i < tabListData.length; i++) {
count += tabListData[i].list.length; count += tabListData[i].list.length;
} }
return count;
});
function onNoticeClick(record: ListItem) {
createMessage.success('你点击了通知,ID=' + record.id);
// 可以直接将其标记为已读(为标题添加删除线),此处演示的代码会切换删除线状态
record.titleDelete = !record.titleDelete;
}
return { return {
prefixCls, prefixCls,
tabListData, listData,
count, count,
onNoticeClick,
numberStyle: {}, numberStyle: {},
}; };
}, },
......
...@@ -181,6 +181,12 @@ ...@@ -181,6 +181,12 @@
font-size: 16px !important; font-size: 16px !important;
} }
.ant-badge {
span {
color: @white;
}
}
&:hover { &:hover {
background-color: @header-dark-bg-hover-color; background-color: @header-dark-bg-hover-color;
} }
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
> >
<AppLogo :showTitle="false" :class="`${prefixCls}-logo`" /> <AppLogo :showTitle="false" :class="`${prefixCls}-logo`" />
<Trigger :class="`${prefixCls}-trigger`" /> <LayoutTrigger :class="`${prefixCls}-trigger`" />
<ScrollContainer> <ScrollContainer>
<ul :class="`${prefixCls}-module`"> <ul :class="`${prefixCls}-module`">
...@@ -86,7 +86,6 @@ ...@@ -86,7 +86,6 @@
import { SimpleMenu, SimpleMenuTag } from '/@/components/SimpleMenu'; import { SimpleMenu, SimpleMenuTag } from '/@/components/SimpleMenu';
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon';
import { AppLogo } from '/@/components/Application'; import { AppLogo } from '/@/components/Application';
import Trigger from '../trigger/HeaderTrigger.vue';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting'; import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useDragLine } from './useLayoutSider'; import { useDragLine } from './useLayoutSider';
import { useGlobSetting } from '/@/hooks/setting'; import { useGlobSetting } from '/@/hooks/setting';
...@@ -97,6 +96,7 @@ ...@@ -97,6 +96,7 @@
import clickOutside from '/@/directives/clickOutside'; import clickOutside from '/@/directives/clickOutside';
import { getChildrenMenus, getCurrentParentPath, getShallowMenus } from '/@/router/menus'; import { getChildrenMenus, getCurrentParentPath, getShallowMenus } from '/@/router/menus';
import { listenerRouteChange } from '/@/logics/mitt/routeChange'; import { listenerRouteChange } from '/@/logics/mitt/routeChange';
import LayoutTrigger from '../trigger/index.vue';
export default defineComponent({ export default defineComponent({
name: 'LayoutMixSider', name: 'LayoutMixSider',
...@@ -105,7 +105,7 @@ ...@@ -105,7 +105,7 @@
AppLogo, AppLogo,
SimpleMenu, SimpleMenu,
Icon, Icon,
Trigger, LayoutTrigger,
SimpleMenuTag, SimpleMenuTag,
}, },
directives: { directives: {
...@@ -486,17 +486,19 @@ ...@@ -486,17 +486,19 @@
bottom: 0; bottom: 0;
left: 0; left: 0;
width: 100%; width: 100%;
padding: 6px; font-size: 14px;
padding-left: 12px;
font-size: 18px;
color: rgba(255, 255, 255, 0.65); color: rgba(255, 255, 255, 0.65);
text-align: center;
cursor: pointer; cursor: pointer;
background-color: @sider-dark-bg-color; background-color: @trigger-dark-bg-color;
height: 36px;
line-height: 36px;
} }
&.light &-trigger { &.light &-trigger {
color: rgba(0, 0, 0, 0.65); color: rgba(0, 0, 0, 0.65);
background-color: #fff; background-color: #fff;
border-top: 1px solid #eee;
} }
&-menu-list { &-menu-list {
......
import { genMessage } from '../helper'; import { genMessage } from '../helper';
import antdLocale from 'ant-design-vue/es/locale/en_US'; import antdLocale from 'ant-design-vue/es/locale/en_US';
import momentLocale from 'moment/dist/locale/eu'; // import momentLocale from 'moment/dist/locale/en-us';
const modules = import.meta.globEager('./en/**/*.ts'); const modules = import.meta.globEager('./en/**/*.ts');
export default { export default {
...@@ -8,6 +8,6 @@ export default { ...@@ -8,6 +8,6 @@ export default {
...genMessage(modules, 'en'), ...genMessage(modules, 'en'),
antdLocale, antdLocale,
}, },
momentLocale, momentLocale: null,
momentLocaleName: 'eu', momentLocaleName: 'en',
}; };
import { darkCssIsReady, loadDarkThemeCss } from 'vite-plugin-theme/es/client'; import { darkCssIsReady, loadDarkThemeCss } from 'vite-plugin-theme/es/client';
import { addClass, hasClass, removeClass } from '/@/utils/domUtils';
export async function updateDarkTheme(mode: string | null = 'light') { export async function updateDarkTheme(mode: string | null = 'light') {
const htmlRoot = document.getElementById('htmlRoot'); const htmlRoot = document.getElementById('htmlRoot');
if (!htmlRoot) {
return;
}
const hasDarkClass = hasClass(htmlRoot, 'dark');
if (mode === 'dark') { if (mode === 'dark') {
if (import.meta.env.PROD && !darkCssIsReady) { if (import.meta.env.PROD && !darkCssIsReady) {
await loadDarkThemeCss(); await loadDarkThemeCss();
} }
htmlRoot?.setAttribute('data-theme', 'dark'); htmlRoot.setAttribute('data-theme', 'dark');
if (!hasDarkClass) {
addClass(htmlRoot, 'dark');
}
} else { } else {
htmlRoot?.setAttribute('data-theme', 'light'); htmlRoot.setAttribute('data-theme', 'light');
if (hasDarkClass) {
removeClass(htmlRoot, 'dark');
}
} }
} }
import '/@/design/index.less'; import '/@/design/index.less';
import '/@/design/tailwind.css';
// Register windi
import 'virtual:windi.css';
// Register icon sprite // Register icon sprite
import 'virtual:svg-icons-register'; import 'virtual:svg-icons-register';
import App from './App.vue'; import App from './App.vue';
......
...@@ -2,6 +2,8 @@ export const REDIRECT_NAME = 'Redirect'; ...@@ -2,6 +2,8 @@ export const REDIRECT_NAME = 'Redirect';
export const PARENT_LAYOUT_NAME = 'ParentLayout'; export const PARENT_LAYOUT_NAME = 'ParentLayout';
export const PAGE_NOT_FOUND_NAME = 'PageNotFound';
export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exception.vue'); export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exception.vue');
/** /**
......
...@@ -7,17 +7,25 @@ import { useUserStoreWithOut } from '/@/store/modules/user'; ...@@ -7,17 +7,25 @@ import { useUserStoreWithOut } from '/@/store/modules/user';
import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic'; import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic';
import { RootRoute } from '/@/router/routes';
const LOGIN_PATH = PageEnum.BASE_LOGIN; const LOGIN_PATH = PageEnum.BASE_LOGIN;
const ROOT_PATH = RootRoute.path;
const whitePathList: PageEnum[] = [LOGIN_PATH]; const whitePathList: PageEnum[] = [LOGIN_PATH];
export function createPermissionGuard(router: Router) { export function createPermissionGuard(router: Router) {
const userStore = useUserStoreWithOut(); const userStore = useUserStoreWithOut();
const permissionStore = usePermissionStoreWithOut(); const permissionStore = usePermissionStoreWithOut();
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
// Jump to the 404 page after processing the login if (
if (from.path === LOGIN_PATH && to.name === PAGE_NOT_FOUND_ROUTE.name) { from.path === ROOT_PATH &&
next(PageEnum.BASE_HOME); to.path === PageEnum.BASE_HOME &&
userStore.getUserInfo.homePath &&
userStore.getUserInfo.homePath !== PageEnum.BASE_HOME
) {
next(userStore.getUserInfo.homePath);
return; return;
} }
...@@ -52,6 +60,17 @@ export function createPermissionGuard(router: Router) { ...@@ -52,6 +60,17 @@ export function createPermissionGuard(router: Router) {
return; return;
} }
// Jump to the 404 page after processing the login
if (
from.path === LOGIN_PATH &&
to.name === PAGE_NOT_FOUND_ROUTE.name &&
to.fullPath !== (userStore.getUserInfo.homePath || PageEnum.BASE_HOME)
) {
next(userStore.getUserInfo.homePath || PageEnum.BASE_HOME);
console.log({ from, to });
return;
}
if (permissionStore.getIsDynamicAddedRoute) { if (permissionStore.getIsDynamicAddedRoute) {
next(); next();
return; return;
...@@ -63,10 +82,19 @@ export function createPermissionGuard(router: Router) { ...@@ -63,10 +82,19 @@ export function createPermissionGuard(router: Router) {
router.addRoute(route as unknown as RouteRecordRaw); router.addRoute(route as unknown as RouteRecordRaw);
}); });
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw);
permissionStore.setDynamicAddedRoute(true);
if (to.name === PAGE_NOT_FOUND_ROUTE.name) {
// 动态添加路由后,此处应当重定向到fullPath,否则会加载404页面内容
// fix: 添加query以免丢失
next({ path: to.fullPath, replace: true, query: to.query });
} else {
const redirectPath = (from.query.redirect || to.path) as string; const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath); const redirect = decodeURIComponent(redirectPath);
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }; const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
permissionStore.setDynamicAddedRoute(true);
next(nextData); next(nextData);
}
}); });
} }
...@@ -62,6 +62,7 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi ...@@ -62,6 +62,7 @@ export function transformRouteToMenu(routeModList: AppRouteModule[], routerMappi
name: title, name: title,
hideMenu, hideMenu,
path: node.path, path: node.path,
...(node.redirect ? { redirect: node.redirect } : {}),
}; };
}, },
}); });
......
import type { AppRouteRecordRaw } from '/@/router/types'; import type { AppRouteRecordRaw } from '/@/router/types';
import { t } from '/@/hooks/web/useI18n'; import { t } from '/@/hooks/web/useI18n';
import { REDIRECT_NAME, LAYOUT, EXCEPTION_COMPONENT } from '/@/router/constant'; import {
REDIRECT_NAME,
LAYOUT,
EXCEPTION_COMPONENT,
PAGE_NOT_FOUND_NAME,
} from '/@/router/constant';
// 404 on a page // 404 on a page
export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = { export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = {
path: '/:path(.*)*', path: '/:path(.*)*',
name: 'ErrorPage', name: PAGE_NOT_FOUND_NAME,
component: LAYOUT, component: LAYOUT,
meta: { meta: {
title: 'ErrorPage', title: 'ErrorPage',
...@@ -15,11 +20,12 @@ export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = { ...@@ -15,11 +20,12 @@ export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = {
children: [ children: [
{ {
path: '/:path(.*)*', path: '/:path(.*)*',
name: 'ErrorPage', name: PAGE_NOT_FOUND_NAME,
component: EXCEPTION_COMPONENT, component: EXCEPTION_COMPONENT,
meta: { meta: {
title: 'ErrorPage', title: 'ErrorPage',
hideBreadcrumb: true, hideBreadcrumb: true,
hideMenu: true,
}, },
}, },
], ],
...@@ -51,9 +57,11 @@ export const ERROR_LOG_ROUTE: AppRouteRecordRaw = { ...@@ -51,9 +57,11 @@ export const ERROR_LOG_ROUTE: AppRouteRecordRaw = {
path: '/error-log', path: '/error-log',
name: 'ErrorLog', name: 'ErrorLog',
component: LAYOUT, component: LAYOUT,
redirect: '/error-log/list',
meta: { meta: {
title: 'ErrorLog', title: 'ErrorLog',
hideBreadcrumb: true, hideBreadcrumb: true,
hideChildrenInMenu: true,
}, },
children: [ children: [
{ {
...@@ -63,6 +71,7 @@ export const ERROR_LOG_ROUTE: AppRouteRecordRaw = { ...@@ -63,6 +71,7 @@ export const ERROR_LOG_ROUTE: AppRouteRecordRaw = {
meta: { meta: {
title: t('routes.basic.errorLogList'), title: t('routes.basic.errorLogList'),
hideBreadcrumb: true, hideBreadcrumb: true,
currentActiveMenu: '/error-log',
}, },
}, },
], ],
......
...@@ -37,4 +37,10 @@ export const LoginRoute: AppRouteRecordRaw = { ...@@ -37,4 +37,10 @@ export const LoginRoute: AppRouteRecordRaw = {
}; };
// Basic routing without permission // Basic routing without permission
export const basicRoutes = [LoginRoute, RootRoute, ...mainOutRoutes, REDIRECT_ROUTE]; export const basicRoutes = [
LoginRoute,
RootRoute,
...mainOutRoutes,
REDIRECT_ROUTE,
PAGE_NOT_FOUND_ROUTE,
];
...@@ -22,7 +22,7 @@ const dashboard: AppRouteModule = { ...@@ -22,7 +22,7 @@ const dashboard: AppRouteModule = {
meta: { meta: {
title: t('routes.dashboard.about'), title: t('routes.dashboard.about'),
icon: 'simple-icons:about-dot-me', icon: 'simple-icons:about-dot-me',
// hideMenu: true, hideMenu: true,
}, },
}, },
], ],
......
...@@ -19,7 +19,7 @@ const dashboard: AppRouteModule = { ...@@ -19,7 +19,7 @@ const dashboard: AppRouteModule = {
name: 'Analysis', name: 'Analysis',
component: () => import('/@/views/dashboard/analysis/index.vue'), component: () => import('/@/views/dashboard/analysis/index.vue'),
meta: { meta: {
affix: true, // affix: true,
title: t('routes.dashboard.analysis'), title: t('routes.dashboard.analysis'),
}, },
}, },
......
...@@ -181,7 +181,7 @@ const feat: AppRouteModule = { ...@@ -181,7 +181,7 @@ const feat: AppRouteModule = {
}, },
}, },
{ {
path: 'error-log', path: '/error-log',
name: 'ErrorLog', name: 'ErrorLog',
component: () => import('/@/views/sys/error-log/index.vue'), component: () => import('/@/views/sys/error-log/index.vue'),
meta: { meta: {
......
...@@ -13,6 +13,7 @@ import { getRawRoute } from '/@/utils'; ...@@ -13,6 +13,7 @@ import { getRawRoute } from '/@/utils';
import { MULTIPLE_TABS_KEY } from '/@/enums/cacheEnum'; import { MULTIPLE_TABS_KEY } from '/@/enums/cacheEnum';
import projectSetting from '/@/settings/projectSetting'; import projectSetting from '/@/settings/projectSetting';
import { useUserStore } from '/@/store/modules/user';
export interface MultipleTabState { export interface MultipleTabState {
cacheTabList: Set<string>; cacheTabList: Set<string>;
...@@ -113,6 +114,7 @@ export const useMultipleTabStore = defineStore({ ...@@ -113,6 +114,7 @@ export const useMultipleTabStore = defineStore({
// 404 The page does not need to add a tab // 404 The page does not need to add a tab
if ( if (
path === PageEnum.ERROR_PAGE || path === PageEnum.ERROR_PAGE ||
path === PageEnum.BASE_LOGIN ||
!name || !name ||
[REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string) [REDIRECT_ROUTE.name, PAGE_NOT_FOUND_ROUTE.name].includes(name as string)
) { ) {
...@@ -181,7 +183,8 @@ export const useMultipleTabStore = defineStore({ ...@@ -181,7 +183,8 @@ export const useMultipleTabStore = defineStore({
if (index === 0) { if (index === 0) {
// There is only one tab, then jump to the homepage, otherwise jump to the right tab // There is only one tab, then jump to the homepage, otherwise jump to the right tab
if (this.tabList.length === 1) { if (this.tabList.length === 1) {
toTarget = PageEnum.BASE_HOME; const userStore = useUserStore();
toTarget = userStore.getUserInfo.homePath || PageEnum.BASE_HOME;
} else { } else {
// Jump to the right tab // Jump to the right tab
const page = this.tabList[index + 1]; const page = this.tabList[index + 1];
......
...@@ -22,6 +22,7 @@ import { getMenuList } from '/@/api/sys/menu'; ...@@ -22,6 +22,7 @@ import { getMenuList } from '/@/api/sys/menu';
import { getPermCode } from '/@/api/sys/user'; import { getPermCode } from '/@/api/sys/user';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { PageEnum } from '/@/enums/pageEnum';
interface PermissionState { interface PermissionState {
// Permission code list // Permission code list
...@@ -117,6 +118,36 @@ export const usePermissionStore = defineStore({ ...@@ -117,6 +118,36 @@ export const usePermissionStore = defineStore({
return !ignoreRoute; return !ignoreRoute;
}; };
/**
* @description 根据设置的首页path,修正routes中的affix标记(固定首页)
* */
const patchHomeAffix = (routes: AppRouteRecordRaw[]) => {
if (!routes || routes.length === 0) return;
let homePath: string = userStore.getUserInfo.homePath || PageEnum.BASE_HOME;
function patcher(routes: AppRouteRecordRaw[], parentPath = '') {
if (parentPath) parentPath = parentPath + '/';
routes.forEach((route: AppRouteRecordRaw) => {
const { path, children, redirect } = route;
const currentPath = path.startsWith('/') ? path : parentPath + path;
if (currentPath === homePath) {
if (redirect) {
homePath = route.redirect! as string;
} else {
route.meta = Object.assign({}, route.meta, { affix: true });
throw new Error('end');
}
}
children && children.length > 0 && patcher(children, currentPath);
});
}
try {
patcher(routes);
} catch (e) {
// 已处理完毕跳出循环
}
return;
};
switch (permissionMode) { switch (permissionMode) {
case PermissionModeEnum.ROLE: case PermissionModeEnum.ROLE:
routes = filter(asyncRoutes, routeFilter); routes = filter(asyncRoutes, routeFilter);
...@@ -176,6 +207,7 @@ export const usePermissionStore = defineStore({ ...@@ -176,6 +207,7 @@ export const usePermissionStore = defineStore({
} }
routes.push(ERROR_LOG_ROUTE); routes.push(ERROR_LOG_ROUTE);
patchHomeAffix(routes);
return routes; return routes;
}, },
}, },
......
...@@ -11,6 +11,9 @@ import { doLogout, getUserInfo, loginApi } from '/@/api/sys/user'; ...@@ -11,6 +11,9 @@ import { doLogout, getUserInfo, loginApi } from '/@/api/sys/user';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { router } from '/@/router'; import { router } from '/@/router';
import { usePermissionStore } from '/@/store/modules/permission';
import { RouteRecordRaw } from 'vue-router';
import { PAGE_NOT_FOUND_ROUTE } from '/@/router/routes/basic';
interface UserState { interface UserState {
userInfo: Nullable<UserInfo>; userInfo: Nullable<UserInfo>;
...@@ -87,14 +90,26 @@ export const useUserStore = defineStore({ ...@@ -87,14 +90,26 @@ export const useUserStore = defineStore({
const userInfo = await this.getUserInfoAction(); const userInfo = await this.getUserInfoAction();
const sessionTimeout = this.sessionTimeout; const sessionTimeout = this.sessionTimeout;
sessionTimeout && this.setSessionTimeout(false); if (sessionTimeout) {
!sessionTimeout && goHome && (await router.replace(PageEnum.BASE_HOME)); this.setSessionTimeout(false);
} else if (goHome) {
const permissionStore = usePermissionStore();
if (!permissionStore.isDynamicAddedRoute) {
const routes = await permissionStore.buildRoutesAction();
routes.forEach((route) => {
router.addRoute(route as unknown as RouteRecordRaw);
});
router.addRoute(PAGE_NOT_FOUND_ROUTE as unknown as RouteRecordRaw);
permissionStore.setDynamicAddedRoute(true);
}
await router.replace(userInfo.homePath || PageEnum.BASE_HOME);
}
return userInfo; return userInfo;
} catch (error) { } catch (error) {
return Promise.reject(error); return Promise.reject(error);
} }
}, },
async getUserInfoAction() { async getUserInfoAction(): Promise<UserInfo> {
const userInfo = await getUserInfo(); const userInfo = await getUserInfo();
const { roles } = userInfo; const { roles } = userInfo;
const roleList = roles.map((item) => item.value) as RoleEnum[]; const roleList = roles.map((item) => item.value) as RoleEnum[];
......
...@@ -7,13 +7,13 @@ const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm'; ...@@ -7,13 +7,13 @@ const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
const DATE_FORMAT = 'YYYY-MM-DD '; const DATE_FORMAT = 'YYYY-MM-DD ';
export function formatToDateTime( export function formatToDateTime(
date: moment.MomentInput = null, date: moment.MomentInput = undefined,
format = DATE_TIME_FORMAT format = DATE_TIME_FORMAT
): string { ): string {
return moment(date).format(format); return moment(date).format(format);
} }
export function formatToDate(date: moment.MomentInput = null, format = DATE_FORMAT): string { export function formatToDate(date: moment.MomentInput = undefined, format = DATE_FORMAT): string {
return moment(date).format(format); return moment(date).format(format);
} }
......
import type { FunctionArgs } from '@vueuse/core';
import { upperFirst } from 'lodash-es'; import { upperFirst } from 'lodash-es';
export interface ViewportOffsetResult { export interface ViewportOffsetResult {
...@@ -163,3 +164,17 @@ export function once(el: HTMLElement, event: string, fn: EventListener): void { ...@@ -163,3 +164,17 @@ export function once(el: HTMLElement, event: string, fn: EventListener): void {
}; };
on(el, event, listener); on(el, event, listener);
} }
export function useRafThrottle<T extends FunctionArgs>(fn: T): T {
let locked = false;
// @ts-ignore
return function (...args: any[]) {
if (locked) return;
locked = true;
window.requestAnimationFrame(() => {
// @ts-ignore
fn.apply(this, args);
locked = false;
});
};
}
...@@ -46,7 +46,7 @@ export function getAppEnvConfig() { ...@@ -46,7 +46,7 @@ export function getAppEnvConfig() {
} }
/** /**
* @description: Development model * @description: Development mode
*/ */
export const devMode = 'development'; export const devMode = 'development';
......
...@@ -23,6 +23,7 @@ import { ...@@ -23,6 +23,7 @@ import {
VisualMapComponent, VisualMapComponent,
TimelineComponent, TimelineComponent,
CalendarComponent, CalendarComponent,
GraphicComponent,
} from 'echarts/components'; } from 'echarts/components';
import { SVGRenderer } from 'echarts/renderers'; import { SVGRenderer } from 'echarts/renderers';
...@@ -48,6 +49,7 @@ echarts.use([ ...@@ -48,6 +49,7 @@ echarts.use([
VisualMapComponent, VisualMapComponent,
TimelineComponent, TimelineComponent,
CalendarComponent, CalendarComponent,
GraphicComponent,
]); ]);
export default echarts; export default echarts;
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
size="small" size="small"
:loading="$attrs.loading" :loading="$attrs.loading"
:title="item.title" :title="item.title"
class="md:w-1/4 w-full md:!mt-0 !mt-4" class="md:w-1/4 w-full !md:mt-0 !mt-4"
:class="[index + 1 < 4 && 'md:!mr-4']" :class="[index + 1 < 4 && '!md:mr-4']"
:canExpan="false" :canExpan="false"
> >
<template #extra> <template #extra>
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<div class="md:flex enter-y"> <div class="md:flex enter-y">
<VisitRadar class="md:w-1/3 w-full" :loading="loading" /> <VisitRadar class="md:w-1/3 w-full" :loading="loading" />
<VisitSource class="md:w-1/3 md:!mx-4 md:!my-0 !my-4 w-full" :loading="loading" /> <VisitSource class="md:w-1/3 !md:mx-4 !md:my-0 !my-4 w-full" :loading="loading" />
<SalesProductPie class="md:w-1/3 w-full" :loading="loading" /> <SalesProductPie class="md:w-1/3 w-full" :loading="loading" />
</div> </div>
</div> </div>
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
</template> </template>
<template v-for="item in items" :key="item"> <template v-for="item in items" :key="item">
<CardGrid class="md:!w-1/3 !w-full"> <CardGrid class="!md:w-1/3 !w-full">
<span class="flex"> <span class="flex">
<Icon :icon="item.icon" :color="item.color" size="30" /> <Icon :icon="item.icon" :color="item.color" size="30" />
<span class="text-lg ml-4">{{ item.title }}</span> <span class="text-lg ml-4">{{ item.title }}</span>
......
...@@ -10,11 +10,7 @@ ...@@ -10,11 +10,7 @@
<QuickNav :loading="loading" class="enter-y" /> <QuickNav :loading="loading" class="enter-y" />
<Card class="!my-4 enter-y" :loading="loading"> <Card class="!my-4 enter-y" :loading="loading">
<img <img class="xl:h-50 h-30 mx-auto" src="../../../assets/svg/illustration.svg" />
style="height: 216px"
class="h-20 mx-auto"
src="../../../assets/svg/illustration.svg"
/>
</Card> </Card>
<SaleRadar :loading="loading" class="enter-y" /> <SaleRadar :loading="loading" class="enter-y" />
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
@visible-change="handleShow" @visible-change="handleShow"
> >
<template #insertFooter> <template #insertFooter>
<a-button type="danger" @click="setLines" :disabled="loading">点我更新内容</a-button> <a-button type="primary" danger @click="setLines" :disabled="loading">点我更新内容</a-button>
</template> </template>
<template v-if="loading"> <template v-if="loading">
<div class="empty-tips"> 加载中,稍等3秒…… </div> <div class="empty-tips"> 加载中,稍等3秒…… </div>
......
<template> <template>
<BasicModal <BasicModal
v-bind="$attrs"
@register="register" @register="register"
title="Modal Title" title="Modal Title"
:helpMessage="['提示1', '提示2']" :helpMessage="['提示1', '提示2']"
:okButtonProps="{ disabled: true }"
> >
<a-button type="primary" @click="closeModal" class="mr-2"> 从内部关闭弹窗 </a-button> <a-button type="primary" @click="closeModal" class="mr-2"> 从内部关闭弹窗 </a-button>
<a-button type="primary" @click="setModalProps"> 从内部修改title </a-button> <a-button type="primary" @click="setModalProps"> 从内部修改title </a-button>
</BasicModal> </BasicModal>
</template> </template>
......
<template> <template>
<BasicModal v-bind="$attrs" @register="register" title="Modal Title"> <BasicModal
v-bind="$attrs"
@register="register"
title="Modal Title"
@visible-change="handleVisibleChange"
>
<BasicForm @register="registerForm" :model="model" /> <BasicForm @register="registerForm" :model="model" />
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from 'vue'; import { defineComponent, ref, nextTick } 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';
const schemas: FormSchema[] = [ const schemas: FormSchema[] = [
...@@ -13,7 +18,7 @@ ...@@ -13,7 +18,7 @@
component: 'Input', component: 'Input',
label: '字段1', label: '字段1',
colProps: { colProps: {
span: 12, span: 24,
}, },
defaultValue: '111', defaultValue: '111',
}, },
...@@ -22,13 +27,16 @@ ...@@ -22,13 +27,16 @@
component: 'Input', component: 'Input',
label: '字段2', label: '字段2',
colProps: { colProps: {
span: 12, span: 24,
}, },
}, },
]; ];
export default defineComponent({ export default defineComponent({
components: { BasicModal, BasicForm }, components: { BasicModal, BasicForm },
setup() { props: {
userData: { type: Object },
},
setup(props) {
const modelRef = ref({}); const modelRef = ref({});
const [ const [
registerForm, registerForm,
...@@ -46,20 +54,30 @@ ...@@ -46,20 +54,30 @@
}); });
const [register] = useModalInner((data) => { const [register] = useModalInner((data) => {
// 方式1 data && onDataReceive(data);
});
function onDataReceive(data) {
console.log('Data Received', data);
// 方式1;
// setFieldsValue({ // setFieldsValue({
// field2: data.data, // field2: data.data,
// field1: data.info, // field1: data.info,
// }); // });
// 方式2 // // 方式2
modelRef.value = { field2: data.data, field1: data.info }; modelRef.value = { field2: data.data, field1: data.info };
// setProps({ // setProps({
// model:{ field2: data.data, field1: data.info } // model:{ field2: data.data, field1: data.info }
// }) // })
}); }
return { register, schemas, registerForm, model: modelRef };
function handleVisibleChange(v) {
v && props.userData && nextTick(() => onDataReceive(props.userData));
}
return { register, schemas, registerForm, model: modelRef, handleVisibleChange };
}, },
}); });
</script> </script>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment