mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-11-03 13:44:47 +08:00
Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee4fb9129e | ||
|
|
ecebb98ab6 | ||
|
|
4e3d752fb6 | ||
|
|
799a5140e3 | ||
|
|
c19cb6004b | ||
|
|
0b8412c109 | ||
|
|
5f3210f69e | ||
|
|
1aba27590f | ||
|
|
a1b8b27a1c | ||
|
|
6d141bfd2f | ||
|
|
86ec72f3c0 | ||
|
|
43ddf7aba8 | ||
|
|
a71bf0befb | ||
|
|
47f951312e | ||
|
|
b2d06d2b3b | ||
|
|
7957dc2c18 | ||
|
|
fd9c19dd00 | ||
|
|
b77586da07 | ||
|
|
09cbc7ddc3 | ||
|
|
f5617d5eb2 | ||
|
|
d4de5fc6f6 | ||
|
|
4bc888b273 | ||
|
|
723cb46eaf | ||
|
|
42f5a36145 | ||
|
|
fa4b951027 | ||
|
|
3705a0c11b | ||
|
|
4ae0d9bc04 | ||
|
|
2024c4e5fe | ||
|
|
dc0ad8523c | ||
|
|
9d0c3f305d | ||
|
|
d49b23e8f9 | ||
|
|
6176d9508b | ||
|
|
afc15ea8df | ||
|
|
7713e7fb9e | ||
|
|
c47a281758 | ||
|
|
b5750dcba1 | ||
|
|
bccd27ad30 | ||
|
|
b1e0968a9b | ||
|
|
ca97465e9e | ||
|
|
89a20c6e46 | ||
|
|
d305e6fb1f | ||
|
|
12f0f096ca | ||
|
|
2ddfcf16e4 | ||
|
|
6bb921acbd | ||
|
|
b953149e5a | ||
|
|
8f528028b1 | ||
|
|
4be2f04b4d | ||
|
|
638a95a4af | ||
|
|
40b7e12eaa | ||
|
|
6ebcb0a259 | ||
|
|
1c7af560fc | ||
|
|
02608a410a | ||
|
|
bff68c3e7b | ||
|
|
990caef48d | ||
|
|
30eddc1373 | ||
|
|
fe8e84ec9f | ||
|
|
f47fe9f19d | ||
|
|
b4456dc71a | ||
|
|
8fc9a4eca6 | ||
|
|
49d88b367d | ||
|
|
3ee8c51f90 | ||
|
|
fad1db8c57 | ||
|
|
2d0cc4cfa2 | ||
|
|
a3a4d2db08 | ||
|
|
f43673379b | ||
|
|
094599496b | ||
|
|
c73f9acbac | ||
|
|
7892c9c600 | ||
|
|
c7f1ae9fb7 | ||
|
|
b455b2da89 | ||
|
|
e323411d1c | ||
|
|
5efba00330 | ||
|
|
d6ab5ad598 | ||
|
|
3e93618015 | ||
|
|
151592c660 | ||
|
|
eeea7bf718 | ||
|
|
4a032426a8 | ||
|
|
bfa07107c9 | ||
|
|
1f9fe0ef29 | ||
|
|
d0ed3be827 |
3
.env
3
.env
@@ -1,2 +1,5 @@
|
||||
# 平台本地运行端口号
|
||||
VITE_PORT = 8848
|
||||
|
||||
# 是否隐藏首页 隐藏 true 不隐藏 false (勿删除,VITE_HIDE_HOME只需在.env文件配置)
|
||||
VITE_HIDE_HOME = false
|
||||
|
||||
@@ -13,4 +13,4 @@ VITE_CDN = true
|
||||
# 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件)
|
||||
# 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
||||
# 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
||||
VITE_COMPRESSION = "both-clear"
|
||||
VITE_COMPRESSION = "none"
|
||||
|
||||
2
.github/workflows/gitee.yml
vendored
2
.github/workflows/gitee.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
- name: Deploy 🔧
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm install --no-frozen-lockfile
|
||||
sed -i "s#VITE_PUBLIC_PATH = /#VITE_PUBLIC_PATH = /vue-pure-admin/#g" $(pwd)/.env.production
|
||||
pnpm build
|
||||
cd dist
|
||||
|
||||
2
.github/workflows/linter.yml
vendored
2
.github/workflows/linter.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
pnpm install
|
||||
pnpm install --no-frozen-lockfile
|
||||
pnpm lint
|
||||
pnpm typecheck
|
||||
env:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/dist/*
|
||||
/public/*
|
||||
public/*
|
||||
public/*
|
||||
src/style/reset.scss
|
||||
@@ -1,3 +1,87 @@
|
||||
# 4.2.0 (2023-05-15)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Added segment controller component and adapted to dark mode
|
||||
- Static routing supports configuration array format
|
||||
- Functional bullet box component adds full screen and exit full screen operation buttons
|
||||
- New component - Waterfall `demo`
|
||||
- Add `Exclusive` type mutual exclusion syntactic sugar
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Standardize the way of writing routes in `template` template, no longer use `$route` and `$router`, this way of writing `vue-tsc` fails to compile
|
||||
|
||||
# 4.1.0 (2023-05-12)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Add a `demo` example combined with `Form` for the functional pop-up box component
|
||||
- wrapper `el-col` component of `element-plus`
|
||||
- Add `beforeCancel` and `beforeSure` callbacks to the functional popup component, which can suspend the closing of the popup
|
||||
- Improve `System Management-Department Management` page
|
||||
- Optimize `PureTableBar` component, add drag and drop function for column display
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fix the problem that the page cache still exists when you click the tab to reload after turning on `keepAlive`
|
||||
- Fix the problem that the left menu will flicker after refreshing the tab in the mixed mode menu
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Optimize home page layout
|
||||
- Dependency update to `vue3.3+` and remove `unplugin-vue-define-options` plugin
|
||||
|
||||
# 4.0.0 (2023-05-09)
|
||||
|
||||
[View 4.0.0 version optimization details](https://github.com/pure-admin/vue-pure-admin/issues/428#issuecomment-1422191158)
|
||||
|
||||
### ✔️ refactor
|
||||
|
||||
- Use `css` pseudo-class `before` to refactor the activation background of the menu, similar to [ant.design-menu](https://ant.design/components/menu-cn#components-menu-demo-inline-collapsed)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Optimize the extra icon on the right side of the menu name to support more icon rendering modes
|
||||
- Configurable home menu display and hide
|
||||
- Promote the namespace of the local reactive store into the global configuration
|
||||
- Added functional popup components and `demo` examples, making it easier to use
|
||||
- `PureTableBar` component adds column display function
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed the `echarts` chart on the home page not adapting to the container when the menu is collapsed or expanded
|
||||
- Fixed the problem that when there is only one submenu, the search function cannot find the submenu
|
||||
- Fix the problem that the global configuration `Theme` is empty cache for `light` and re-login theme configuration does not take effect
|
||||
- Fixed the problem that the search boxes were not automatically gathered after the menu search function pop-up box was opened
|
||||
- Fixed the problem of toolbar button text display after pressing `ESC` to exit full screen
|
||||
- Fix the problem of `tooltip` click penetration in the notification bar of the mobile terminal
|
||||
- Fixed the problem that the text is not displayed when switching to `horizontal` navigation mode after the left menu is collapsed
|
||||
- Fixed the problem that the status cannot be reset when closing other tabs when navigating `tab`
|
||||
- Fix the page hot update error caused by uninitialized environment variables in the `getHistoryMode` function
|
||||
- Fixed too many `tabs` in the navigation, which caused the tabs on the left to be closed and could not be displayed normally
|
||||
- Fixed the problem of full-screen error reporting when clicking on the content area
|
||||
- Fixed the problem that the left navigation bar is always loading after opening `showLink:false` page under hybrid navigation and refreshing
|
||||
- Fixed the left navigation memory overflow problem caused by calling `initRouter` function in mixed mode navigation
|
||||
- Fixed the problem that the cached page was not destroyed when closing the left, right, other, and all tab operations
|
||||
- Fix the problem that the route passes parameters through `query` or `params`, and the cache invalidation problem occurs when the tab page is closed after the cache is enabled
|
||||
- Fixed the problem that the breadcrumbs could not find the parent path in the `params` route parameter passing mode
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Optimize `buttons` named slot of `RePureTableBar` component
|
||||
- Optimize navigation style and menu collapse animation
|
||||
- Optimize the extra icon on the right side of the menu name to support more icon rendering modes
|
||||
- Optimize `logo` image and text layout and unified configuration
|
||||
- After the routing information `showLink` is set to `false`, the current routing information will not be added to the tab
|
||||
- Export `addPathMatch` function
|
||||
- All `getters` in `pinia` are changed to the official recommended way of writing, and `this` is changed to `state` to automatically deduce the type
|
||||
- Adapt to the `api` of the latest version of `pure-table`
|
||||
- Ignore `deprecation` warnings for `sourcemap-codec` and `stable` dependencies
|
||||
- Remove `"incremental": true` from `tsconfig.json` file
|
||||
- Update `stylelint` and related configurations to the latest, strengthen style validation
|
||||
- Breadcrumbs are removed from the homepage, and are displayed according to the selected menu. The homepage is no longer fixed in the breadcrumbs, and the routing monitoring of the breadcrumbs page is optimized
|
||||
|
||||
# 3.9.7 (2022-12-26)
|
||||
|
||||
### 🍏 Perf
|
||||
@@ -18,7 +102,7 @@
|
||||
### 🍏 Perf
|
||||
|
||||
- Update [@pureadmin/theme](https://github.com/pure-admin/pure-admin-theme) to the latest version, bringing more friendly type hints
|
||||
- Optimize [PureTableBar](https://github.com/xiaoxian521/vue-pure-admin/tree/main/src/components/RePureTableBar) component
|
||||
- Optimize [PureTableBar](https://github.com/pure-admin/vue-pure-admin/tree/main/src/components/RePureTableBar) component
|
||||
- Optimize the business code of the system management page to bring better code reference
|
||||
|
||||
# 3.9.5 (2022-12-13)
|
||||
@@ -46,7 +130,7 @@
|
||||
### ✔️ refactor
|
||||
|
||||
- Completely removed `vxe-table`, after removal, the overall package size of the full version is reduced by `1.82MB`, and the initial startup time is basically the same as the lite version 🐮
|
||||
[Click here to see Why Removed? How to integrate it yourself?](https://xiaoxian521.github.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-4-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-vxe-table-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
[Click here to see Why Removed? How to integrate it yourself?](https://pure-admin.github.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-4-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-vxe-table-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -73,7 +157,7 @@
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Global coverage of `el-dialog`, `el-drawer`, `el-message-box`, `el-notification` components of `element-plus`The style of the close icon in the upper right corner makes it more vivid [specific modification Code record](https://github.com/xiaoxian521/vue-pure-admin/commit/c80818d792276666aaea4b18413a0f08777f2ed1)
|
||||
- Global coverage of `el-dialog`, `el-drawer`, `el-message-box`, `el-notification` components of `element-plus`The style of the close icon in the upper right corner makes it more vivid [specific modification Code record](https://github.com/pure-admin/vue-pure-admin/commit/c80818d792276666aaea4b18413a0f08777f2ed1)
|
||||
- The packaging output information is compatible with different packaging output paths
|
||||
- Optimize some animations
|
||||
|
||||
@@ -121,7 +205,7 @@
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Great optimization, remove `@pureadmin/components` and use compatible writing, the package size of the platform is reduced by `0.4` MB before compression is not enabled, and the resource of `2.3` MB is reduced for the first screen request, which is for the [lite version ](https://github.com/xiaoxian521/pure-admin-thin) is a very big optimization, the streamlined version has synchronized code
|
||||
- Great optimization, remove `@pureadmin/components` and use compatible writing, the package size of the platform is reduced by `0.4` MB before compression is not enabled, and the resource of `2.3` MB is reduced for the first screen request, which is for the [lite version ](https://github.com/pure-admin/pure-admin-thin) is a very big optimization, the streamlined version has synchronized code
|
||||
|
||||
# 3.8.0 (2022-11-26)
|
||||
|
||||
@@ -159,7 +243,7 @@
|
||||
### 🎫 Feat
|
||||
|
||||
- Add front-end single sign-on, test address https://yiming_chang.gitee.io/vue-pure-admin/#/pure-table/index?username=sso&roles=admin&accessToken=eyJhbGciOiJIUzUxMiJ9.admin
|
||||
- Add more examples for [@pureadmin/table](https://github.com/xiaoxian521/pure-admin-table) and `element-plus` [table](https://element-plus.org /zh-CN/component/table.html) example remains the same
|
||||
- Add more examples for [@pureadmin/table](https://github.com/pure-admin/pure-admin-table) and `element-plus` [table](https://element-plus.org /zh-CN/component/table.html) example remains the same
|
||||
- Rich watermark function page (supports customizing various colors, shadows, text, additional attributes, setting undeletable watermarks and setting watermarks for specified elements)
|
||||
- Optimize the menu, add `MenuArrowIconNoTransition` global configuration, configure it in `public/serverConfig.json`, for the left menu mode, the menu expansion can be set `MenuArrowIconNoTransition: true` to solve
|
||||
- Replacement form designer component demo
|
||||
@@ -259,7 +343,7 @@
|
||||
|
||||
### ✔️ refactor
|
||||
|
||||
- Replace `unocss` with `tailwindcss`, add `tailwindcss` [documentation](https://xiaoxian521.github.io/pure-admin-doc/pages/39156f/)
|
||||
- Replace `unocss` with `tailwindcss`, add `tailwindcss` [documentation](https://pure-admin.github.io/pure-admin-doc/pages/39156f/)
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
@@ -295,8 +379,8 @@
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Secondary encapsulation of `Table` of `element-plus` into [@pureadmin/table](https://github.com/xiaoxian521/pure-admin-table), providing flexible configuration items and integrating into the platform
|
||||
- Secondary encapsulation of `Descriptions` of `element-plus` into [@pureadmin/descriptions](https://github.com/xiaoxian521/pure-admin-descriptions), providing flexible configuration items and integrating into the platform
|
||||
- Secondary encapsulation of `Table` of `element-plus` into [@pureadmin/table](https://github.com/pure-admin/pure-admin-table), providing flexible configuration items and integrating into the platform
|
||||
- Secondary encapsulation of `Descriptions` of `element-plus` into [@pureadmin/descriptions](https://github.com/pure-admin/pure-admin-descriptions), providing flexible configuration items and integrating into the platform
|
||||
- Centralize most of the tools and hooks of the platform to [@pureadmin/utils](https://pure-admin-utils.netlify.app), and delete the code concentrated in this library to reduce the size of the platform
|
||||
- Add [unplugin-vue-define-options](https://www.npmjs.com/package/unplugin-vue-define-options) plugin, the page can directly write `defineOptions({name: custom name})`
|
||||
- Add project files, language analysis tool [cloc](https://www.npmjs.com/package/cloc)
|
||||
@@ -341,7 +425,7 @@
|
||||
- Integrate `Swiper` plugin
|
||||
- Routing supports passing `component`, representing the component path
|
||||
- Added pre-release packaging mode
|
||||
- Add [hooks] to close a tag (https://github.com/xiaoxian521/vue-pure-admin/commit/5e8723a031923e79f507e5a17151d3bd88a51523)
|
||||
- Add [hooks] to close a tag (https://github.com/pure-admin/vue-pure-admin/commit/5e8723a031923e79f507e5a17151d3bd88a51523)
|
||||
|
||||
### ✔️ refactor
|
||||
|
||||
@@ -353,7 +437,7 @@
|
||||
- Optimized the style of the `split-pane` component for the platform
|
||||
- Optimize internationalization, no longer pass the `i18n` field in the route, the platform automatically reads the files in the `locales` folder of the root directory for internationalization matching
|
||||
- Optimized icon selector
|
||||
- Optimize `layout` to display user information [commit](https://github.com/xiaoxian521/vue-pure-admin/commit/56f9dc85e7fbe0637605c43577c794de9f8968aa)
|
||||
- Optimize `layout` to display user information [commit](https://github.com/pure-admin/vue-pure-admin/commit/56f9dc85e7fbe0637605c43577c794de9f8968aa)
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
|
||||
104
CHANGELOG.md
104
CHANGELOG.md
@@ -1,3 +1,87 @@
|
||||
# 4.2.0 (2023-05-15)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Added segment controller component and adapted to dark mode
|
||||
- Static routing supports configuration array format
|
||||
- Functional bullet box component adds full screen and exit full screen operation buttons
|
||||
- New component - Waterfall `demo`
|
||||
- Add `Exclusive` type mutual exclusion syntactic sugar
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Standardize the way of writing routes in `template` template, no longer use `$route` and `$router`, this way of writing `vue-tsc` fails to compile
|
||||
|
||||
# 4.1.0 (2023-05-12)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Add a `demo` example combined with `Form` for the functional pop-up box component
|
||||
- wrapper `el-col` component of `element-plus`
|
||||
- Add `beforeCancel` and `beforeSure` callbacks to the functional popup component, which can suspend the closing of the popup
|
||||
- Improve `System Management-Department Management` page
|
||||
- Optimize `PureTableBar` component, add drag and drop function for column display
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fix the problem that the page cache still exists when you click the tab to reload after turning on `keepAlive`
|
||||
- Fix the problem that the left menu will flicker after refreshing the tab in the mixed mode menu
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Optimize home page layout
|
||||
- Dependency update to `vue3.3+` and remove `unplugin-vue-define-options` plugin
|
||||
|
||||
# 4.0.0 (2023-05-09)
|
||||
|
||||
[View 4.0.0 version optimization details](https://github.com/pure-admin/vue-pure-admin/issues/428#issuecomment-1422191158)
|
||||
|
||||
### ✔️ refactor
|
||||
|
||||
- Use `css` pseudo-class `before` to refactor the activation background of the menu, similar to [ant.design-menu](https://ant.design/components/menu-cn#components-menu-demo-inline-collapsed)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Optimize the extra icon on the right side of the menu name to support more icon rendering modes
|
||||
- Configurable home menu display and hide
|
||||
- Promote the namespace of the local reactive store into the global configuration
|
||||
- Added functional popup components and `demo` examples, making it easier to use
|
||||
- `PureTableBar` component adds column display function
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- Fixed the `echarts` chart on the home page not adapting to the container when the menu is collapsed or expanded
|
||||
- Fixed the problem that when there is only one submenu, the search function cannot find the submenu
|
||||
- Fix the problem that the global configuration `Theme` is empty cache for `light` and re-login theme configuration does not take effect
|
||||
- Fixed the problem that the search boxes were not automatically gathered after the menu search function pop-up box was opened
|
||||
- Fixed the problem of toolbar button text display after pressing `ESC` to exit full screen
|
||||
- Fix the problem of `tooltip` click penetration in the notification bar of the mobile terminal
|
||||
- Fixed the problem that the text is not displayed when switching to `horizontal` navigation mode after the left menu is collapsed
|
||||
- Fixed the problem that the status cannot be reset when closing other tabs when navigating `tab`
|
||||
- Fix the page hot update error caused by uninitialized environment variables in the `getHistoryMode` function
|
||||
- Fixed too many `tabs` in the navigation, which caused the tabs on the left to be closed and could not be displayed normally
|
||||
- Fixed the problem of full-screen error reporting when clicking on the content area
|
||||
- Fixed the problem that the left navigation bar is always loading after opening `showLink:false` page under hybrid navigation and refreshing
|
||||
- Fixed the left navigation memory overflow problem caused by calling `initRouter` function in mixed mode navigation
|
||||
- Fixed the problem that the cached page was not destroyed when closing the left, right, other, and all tab operations
|
||||
- Fix the problem that the route passes parameters through `query` or `params`, and the cache invalidation problem occurs when the tab page is closed after the cache is enabled
|
||||
- Fixed the problem that the breadcrumbs could not find the parent path in the `params` route parameter passing mode
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Optimize `buttons` named slot of `RePureTableBar` component
|
||||
- Optimize navigation style and menu collapse animation
|
||||
- Optimize the extra icon on the right side of the menu name to support more icon rendering modes
|
||||
- Optimize `logo` image and text layout and unified configuration
|
||||
- After the routing information `showLink` is set to `false`, the current routing information will not be added to the tab
|
||||
- Export `addPathMatch` function
|
||||
- All `getters` in `pinia` are changed to the official recommended way of writing, and `this` is changed to `state` to automatically deduce the type
|
||||
- Adapt to the `api` of the latest version of `pure-table`
|
||||
- Ignore `deprecation` warnings for `sourcemap-codec` and `stable` dependencies
|
||||
- Remove `"incremental": true` from `tsconfig.json` file
|
||||
- Update `stylelint` and related configurations to the latest, strengthen style validation
|
||||
- Breadcrumbs are removed from the homepage, and are displayed according to the selected menu. The homepage is no longer fixed in the breadcrumbs, and the routing monitoring of the breadcrumbs page is optimized
|
||||
|
||||
# 3.9.7 (2022-12-26)
|
||||
|
||||
### 🍏 Perf
|
||||
@@ -18,7 +102,7 @@
|
||||
### 🍏 Perf
|
||||
|
||||
- Update [@pureadmin/theme](https://github.com/pure-admin/pure-admin-theme) to the latest version, bringing more friendly type hints
|
||||
- Optimize [PureTableBar](https://github.com/xiaoxian521/vue-pure-admin/tree/main/src/components/RePureTableBar) component
|
||||
- Optimize [PureTableBar](https://github.com/pure-admin/vue-pure-admin/tree/main/src/components/RePureTableBar) component
|
||||
- Optimize the business code of the system management page to bring better code reference
|
||||
|
||||
# 3.9.5 (2022-12-13)
|
||||
@@ -46,7 +130,7 @@
|
||||
### ✔️ refactor
|
||||
|
||||
- Completely removed `vxe-table`, after removal, the overall package size of the full version is reduced by `1.82MB`, and the initial startup time is basically the same as the lite version 🐮
|
||||
[Click here to see Why Removed? How to integrate it yourself?](https://xiaoxian521.github.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-4-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-vxe-table-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
[Click here to see Why Removed? How to integrate it yourself?](https://pure-admin.github.io/pure-admin-doc/pages/FAQ/#%E5%B9%B3%E5%8F%B0%E5%9C%A8-v3-9-4-%E7%89%88%E6%9C%AC%E5%AE%8C%E5%85%A8%E7%A7%BB%E9%99%A4%E4%BA%86-vxe-table-%E4%B8%BA%E4%BB%80%E4%B9%88%E7%A7%BB%E9%99%A4-%E5%A6%82%E4%BD%95%E8%87%AA%E8%A1%8C%E9%9B%86%E6%88%90)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
@@ -73,7 +157,7 @@
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Global coverage of `el-dialog`, `el-drawer`, `el-message-box`, `el-notification` components of `element-plus`The style of the close icon in the upper right corner makes it more vivid [specific modification Code record](https://github.com/xiaoxian521/vue-pure-admin/commit/c80818d792276666aaea4b18413a0f08777f2ed1)
|
||||
- Global coverage of `el-dialog`, `el-drawer`, `el-message-box`, `el-notification` components of `element-plus`The style of the close icon in the upper right corner makes it more vivid [specific modification Code record](https://github.com/pure-admin/vue-pure-admin/commit/c80818d792276666aaea4b18413a0f08777f2ed1)
|
||||
- The packaging output information is compatible with different packaging output paths
|
||||
- Optimize some animations
|
||||
|
||||
@@ -121,7 +205,7 @@
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- Great optimization, remove `@pureadmin/components` and use compatible writing, the package size of the platform is reduced by `0.4` MB before compression is not enabled, and the resource of `2.3` MB is reduced for the first screen request, which is for the [lite version ](https://github.com/xiaoxian521/pure-admin-thin) is a very big optimization, the streamlined version has synchronized code
|
||||
- Great optimization, remove `@pureadmin/components` and use compatible writing, the package size of the platform is reduced by `0.4` MB before compression is not enabled, and the resource of `2.3` MB is reduced for the first screen request, which is for the [lite version ](https://github.com/pure-admin/pure-admin-thin) is a very big optimization, the streamlined version has synchronized code
|
||||
|
||||
# 3.8.0 (2022-11-26)
|
||||
|
||||
@@ -159,7 +243,7 @@
|
||||
### 🎫 Feat
|
||||
|
||||
- Add front-end single sign-on, test address https://yiming_chang.gitee.io/vue-pure-admin/#/pure-table/index?username=sso&roles=admin&accessToken=eyJhbGciOiJIUzUxMiJ9.admin
|
||||
- Add more examples for [@pureadmin/table](https://github.com/xiaoxian521/pure-admin-table) and `element-plus` [table](https://element-plus.org /zh-CN/component/table.html) example remains the same
|
||||
- Add more examples for [@pureadmin/table](https://github.com/pure-admin/pure-admin-table) and `element-plus` [table](https://element-plus.org /zh-CN/component/table.html) example remains the same
|
||||
- Rich watermark function page (supports customizing various colors, shadows, text, additional attributes, setting undeletable watermarks and setting watermarks for specified elements)
|
||||
- Optimize the menu, add `MenuArrowIconNoTransition` global configuration, configure it in `public/serverConfig.json`, for the left menu mode, the menu expansion can be set `MenuArrowIconNoTransition: true` to solve
|
||||
- Replacement form designer component demo
|
||||
@@ -259,7 +343,7 @@
|
||||
|
||||
### ✔️ refactor
|
||||
|
||||
- Replace `unocss` with `tailwindcss`, add `tailwindcss` [documentation](https://xiaoxian521.github.io/pure-admin-doc/pages/39156f/)
|
||||
- Replace `unocss` with `tailwindcss`, add `tailwindcss` [documentation](https://pure-admin.github.io/pure-admin-doc/pages/39156f/)
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
@@ -295,8 +379,8 @@
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- Secondary encapsulation of `Table` of `element-plus` into [@pureadmin/table](https://github.com/xiaoxian521/pure-admin-table), providing flexible configuration items and integrating into the platform
|
||||
- Secondary encapsulation of `Descriptions` of `element-plus` into [@pureadmin/descriptions](https://github.com/xiaoxian521/pure-admin-descriptions), providing flexible configuration items and integrating into the platform
|
||||
- Secondary encapsulation of `Table` of `element-plus` into [@pureadmin/table](https://github.com/pure-admin/pure-admin-table), providing flexible configuration items and integrating into the platform
|
||||
- Secondary encapsulation of `Descriptions` of `element-plus` into [@pureadmin/descriptions](https://github.com/pure-admin/pure-admin-descriptions), providing flexible configuration items and integrating into the platform
|
||||
- Centralize most of the tools and hooks of the platform to [@pureadmin/utils](https://pure-admin-utils.netlify.app), and delete the code concentrated in this library to reduce the size of the platform
|
||||
- Add [unplugin-vue-define-options](https://www.npmjs.com/package/unplugin-vue-define-options) plugin, the page can directly write `defineOptions({name: custom name})`
|
||||
- Add project files, language analysis tool [cloc](https://www.npmjs.com/package/cloc)
|
||||
@@ -341,7 +425,7 @@
|
||||
- Integrate `Swiper` plugin
|
||||
- Routing supports passing `component`, representing the component path
|
||||
- Added pre-release packaging mode
|
||||
- Add [hooks] to close a tag (https://github.com/xiaoxian521/vue-pure-admin/commit/5e8723a031923e79f507e5a17151d3bd88a51523)
|
||||
- Add [hooks] to close a tag (https://github.com/pure-admin/vue-pure-admin/commit/5e8723a031923e79f507e5a17151d3bd88a51523)
|
||||
|
||||
### ✔️ refactor
|
||||
|
||||
@@ -353,7 +437,7 @@
|
||||
- Optimized the style of the `split-pane` component for the platform
|
||||
- Optimize internationalization, no longer pass the `i18n` field in the route, the platform automatically reads the files in the `locales` folder of the root directory for internationalization matching
|
||||
- Optimized icon selector
|
||||
- Optimize `layout` to display user information [commit](https://github.com/xiaoxian521/vue-pure-admin/commit/56f9dc85e7fbe0637605c43577c794de9f8968aa)
|
||||
- Optimize `layout` to display user information [commit](https://github.com/pure-admin/vue-pure-admin/commit/56f9dc85e7fbe0637605c43577c794de9f8968aa)
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
|
||||
@@ -1,3 +1,87 @@
|
||||
# 4.2.0 (2023-05-15)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- 新增分段控制器组件并适配暗黑模式
|
||||
- 静态路由支持配置数组格式
|
||||
- 函数式弹框组件添加全屏、退出全屏操作按钮
|
||||
- 新增组件-瀑布流 `demo`
|
||||
- 添加 `Exclusive` 类型互斥语法糖
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- 规范 `template` 模版中路由写法,不再使用 `$route` 和 `$router`,此写法 `vue-tsc` 编译不通过
|
||||
|
||||
# 4.1.0 (2023-05-12)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- 函数式弹框组件添加结合 `Form` 的 `demo` 示例
|
||||
- 封装 `element-plus` 的 `el-col` 组件
|
||||
- 函数式弹框组件添加 `beforeCancel` 和 `beforeSure` 回调,可暂停弹框的关闭
|
||||
- 完善 `系统管理-部门管理` 页面
|
||||
- 优化 `PureTableBar` 组件,列展示添加拖拽功能
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- 修复开启 `keepAlive` 后点击标签页的重新加载,页面缓存还存在的问题
|
||||
- 修复混合模式菜单下刷新页签后左侧菜单会闪烁一下的问题
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- 优化首页布局
|
||||
- 依赖更新到 `vue3.3+` 以及删除 `unplugin-vue-define-options` 插件
|
||||
|
||||
# 4.0.0 (2023-05-09)
|
||||
|
||||
[查看 4.0.0 版本优化细节](https://github.com/pure-admin/vue-pure-admin/issues/428#issuecomment-1422191158)
|
||||
|
||||
### ✔️ refactor
|
||||
|
||||
- 采用 `css` 伪类 `before` 写法重构菜单的激活背景,类似于 [ant.design-menu](https://ant.design/components/menu-cn#components-menu-demo-inline-collapsed)
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- 优化菜单名称右侧的额外图标,使其支持更多图标渲染模式
|
||||
- 可配置首页菜单显示与隐藏
|
||||
- 将本地响应式存储的命名空间提升到全局配置中
|
||||
- 新增函数式弹框组件以及 `demo` 示例,使用更便捷
|
||||
- `PureTableBar` 组件添加列展示功能
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
- 修复当菜单折叠或展开时首页 `echarts` 图表未自适应容器
|
||||
- 修复当只有一个子菜单时,搜索功能搜索不到该子菜单问题
|
||||
- 修复全局配置 `Theme` 为 `light` 清空缓存重新登录主题配置不生效的问题
|
||||
- 修复菜单搜索功能弹框打开后搜索框未自动聚集的问题
|
||||
- 修复按 `ESC` 退出全屏后,工具栏按钮文案展示问题
|
||||
- 修复移动端通知栏 `tooltip` 点击穿透问题
|
||||
- 修复当左侧菜单收起后,切换到 `horizontal` 导航模式时文字不展示的问题
|
||||
- 修复导航 `tab` 关闭其他标签页无法重置状态问题
|
||||
- 修复 `getHistoryMode` 函数中环境变量未初始化带来的页面热更新报错
|
||||
- 修复导航 `tab` 过多导致关闭左侧标签页无法正常显示
|
||||
- 修复点击内容区全屏报错问题
|
||||
- 修复混合导航下打开 `showLink:false` 页面并刷新后,左侧导航栏一直处于加载状态的问题
|
||||
- 修复混合模式导航下调用 `initRouter` 函数导致左侧导航内存溢出问题
|
||||
- 修复关闭左侧、右侧、其他、全部标签页操作时缓存页面并没有销毁问题
|
||||
- 修复路由通过 `query` 或 `params` 传参,开启缓存后关闭标签页缓存失效问题
|
||||
- 修复 `params` 路由传参模式下,面包屑无法找到父级路径问题
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- 优化 `RePureTableBar` 组件的 `buttons` 具名插槽
|
||||
- 优化导航样式以及菜单折叠动画
|
||||
- 优化菜单名称右侧的额外图标,使其支持更多图标渲染模式
|
||||
- 优化 `logo` 图和文字布局以及统一配置
|
||||
- 路由信息 `showLink` 设置成 `false` 后,当前路由信息不添加到标签页
|
||||
- 导出 `addPathMatch` 函数
|
||||
- `pinia` 中所有 `getters` 改为官方推荐写法,`this` 改成 `state` 可自动推导类型
|
||||
- 适配最新版 `pure-table` 的 `api`
|
||||
- 忽略 `sourcemap-codec` 和 `stable` 依赖包的 `deprecation` 警告
|
||||
- 从 `tsconfig.json` 文件中移除 `"incremental": true`
|
||||
- 更新 `stylelint` 以及相关配置至最新,强化样式校验
|
||||
- 面包屑去首页化,根据选择的菜单对应显示,首页不在固定到面包屑里,并优化面包屑页面的路由监听
|
||||
|
||||
# 3.9.7 (2022-12-26)
|
||||
|
||||
### 🍏 Perf
|
||||
@@ -18,7 +102,7 @@
|
||||
### 🍏 Perf
|
||||
|
||||
- 更新 [@pureadmin/theme](https://github.com/pure-admin/pure-admin-theme) 至最新版,带来更友好的类型提示
|
||||
- 优化 [PureTableBar](https://github.com/xiaoxian521/vue-pure-admin/tree/main/src/components/RePureTableBar) 组件
|
||||
- 优化 [PureTableBar](https://github.com/pure-admin/vue-pure-admin/tree/main/src/components/RePureTableBar) 组件
|
||||
- 优化系统管理页面业务代码,带来更好的代码参考
|
||||
|
||||
# 3.9.5 (2022-12-13)
|
||||
@@ -73,7 +157,7 @@
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- 全局覆盖 `element-plus` 的 `el-dialog`、`el-drawer`、`el-message-box`、`el-notification` 组件右上角关闭图标的样式,使其表现更鲜明 [具体代码修改记录](https://github.com/xiaoxian521/vue-pure-admin/commit/c80818d792276666aaea4b18413a0f08777f2ed1)
|
||||
- 全局覆盖 `element-plus` 的 `el-dialog`、`el-drawer`、`el-message-box`、`el-notification` 组件右上角关闭图标的样式,使其表现更鲜明 [具体代码修改记录](https://github.com/pure-admin/vue-pure-admin/commit/c80818d792276666aaea4b18413a0f08777f2ed1)
|
||||
- 打包输出信息兼容不同打包输出路径
|
||||
- 优化一些动画
|
||||
|
||||
@@ -121,7 +205,7 @@
|
||||
|
||||
### 🍏 Perf
|
||||
|
||||
- 大优化,移除 `@pureadmin/components` 并采用兼容写法,平台打包大小在未启用压缩前对比优化前减少 `0.4` MB , 首屏请求减少 `2.3` MB 的资源,这对于 [精简版](https://github.com/xiaoxian521/pure-admin-thin) 来说是非常大的优化,精简版已经同步代码
|
||||
- 大优化,移除 `@pureadmin/components` 并采用兼容写法,平台打包大小在未启用压缩前对比优化前减少 `0.4` MB , 首屏请求减少 `2.3` MB 的资源,这对于 [精简版](https://github.com/pure-admin/pure-admin-thin) 来说是非常大的优化,精简版已经同步代码
|
||||
|
||||
# 3.8.0 (2022-11-26)
|
||||
|
||||
@@ -159,7 +243,7 @@
|
||||
### 🎫 Feat
|
||||
|
||||
- 添加前端单点登录,测试地址 https://yiming_chang.gitee.io/vue-pure-admin/#/pure-table/index?username=sso&roles=admin&accessToken=eyJhbGciOiJIUzUxMiJ9.admin
|
||||
- 为 [@pureadmin/table](https://github.com/xiaoxian521/pure-admin-table) 添加更多的示例和 `element-plus` 的 [table](https://element-plus.org/zh-CN/component/table.html) 示例保持一致
|
||||
- 为 [@pureadmin/table](https://github.com/pure-admin/pure-admin-table) 添加更多的示例和 `element-plus` 的 [table](https://element-plus.org/zh-CN/component/table.html) 示例保持一致
|
||||
- 丰富水印功能页面(支持自定义各种颜色、阴影、文字、额外属性、设置不可删除水印以及给指定元素设置水印)
|
||||
- 优化菜单,添加 `MenuArrowIconNoTransition` 全局配置,在 `public/serverConfig.json` 中配置即可,对于出现左侧菜单模式,菜单展开卡顿的可设置 `MenuArrowIconNoTransition: true` 即可解决
|
||||
- 更换表单设计器组件演示
|
||||
@@ -295,8 +379,8 @@
|
||||
|
||||
### 🎫 Feat
|
||||
|
||||
- 将 `element-plus` 的 `Table` 二次封装到[@pureadmin/table](https://github.com/xiaoxian521/pure-admin-table),提供灵活的配置项并集成到平台里
|
||||
- 将 `element-plus` 的 `Descriptions` 二次封装到[@pureadmin/descriptions](https://github.com/xiaoxian521/pure-admin-descriptions),提供灵活的配置项并集成到平台里
|
||||
- 将 `element-plus` 的 `Table` 二次封装到[@pureadmin/table](https://github.com/pure-admin/pure-admin-table),提供灵活的配置项并集成到平台里
|
||||
- 将 `element-plus` 的 `Descriptions` 二次封装到[@pureadmin/descriptions](https://github.com/pure-admin/pure-admin-descriptions),提供灵活的配置项并集成到平台里
|
||||
- 将平台的大部分工具以及 `hooks` 都集中到[@pureadmin/utils](https://pure-admin-utils.netlify.app),并删除集中到这个库里的代码,减少平台体积
|
||||
- 添加[unplugin-vue-define-options](https://www.npmjs.com/package/unplugin-vue-define-options)插件,页面可直接写 `defineOptions({name: 自定义名称})`
|
||||
- 添加项目文件、语言分析工具 [cloc](https://www.npmjs.com/package/cloc)
|
||||
@@ -341,7 +425,7 @@
|
||||
- 集成`Swiper`插件
|
||||
- 路由支持传`component`,代表组件路径
|
||||
- 添加预发布打包模式
|
||||
- 添加关闭某个标签的[hooks](https://github.com/xiaoxian521/vue-pure-admin/commit/5e8723a031923e79f507e5a17151d3bd88a51523)
|
||||
- 添加关闭某个标签的[hooks](https://github.com/pure-admin/vue-pure-admin/commit/5e8723a031923e79f507e5a17151d3bd88a51523)
|
||||
|
||||
### ✔️ refactor
|
||||
|
||||
@@ -353,7 +437,7 @@
|
||||
- 优化平台的`split-pane`组件样式
|
||||
- 优化国际化,路由不再传`i18n`字段,平台自动读取根目录`locales`文件夹下文件进行国际化匹配
|
||||
- 优化图标选择器
|
||||
- 优化`layout`显示用户信息[commit](https://github.com/xiaoxian521/vue-pure-admin/commit/56f9dc85e7fbe0637605c43577c794de9f8968aa)
|
||||
- 优化`layout`显示用户信息[commit](https://github.com/pure-admin/vue-pure-admin/commit/56f9dc85e7fbe0637605c43577c794de9f8968aa)
|
||||
|
||||
### 🐞 Bug fixes
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 啝裳
|
||||
Copyright (c) 2020-present, pure-admin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<h1>vue-pure-admin</h1>
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
**English** | [中文](./README.md)
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
|
||||
## Thin version (offering non-internationalized and internationalized versions)
|
||||
|
||||
The simplified version is based on the shelf extracted from [vue-pure-admin](https://github.com/xiaoxian521/vue-pure-admin), which contains main functions and is more suitable for actual project development. The packaged size is introduced globally [element-plus](https://element-plus.org) is still below `2.3MB`, and the full version of the code will be permanently synchronized. After enabling `brotli` compression and `cdn` to replace the local library mode, the package size is less than `350kb`
|
||||
The simplified version is based on the shelf extracted from [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin), which contains main functions and is more suitable for actual project development. The packaged size is introduced globally [element-plus](https://element-plus.org) is still below `2.3MB`, and the full version of the code will be permanently synchronized. After enabling `brotli` compression and `cdn` to replace the local library mode, the package size is less than `350kb`
|
||||
|
||||
- [Click me to view the non-internationalized version](https://github.com/xiaoxian521/pure-admin-thin)
|
||||
- [Click me to view Internationalization version](https://github.com/xiaoxian521/pure-admin-thin/tree/i18n)
|
||||
- [Click me to view the non-internationalized version](https://github.com/pure-admin/pure-admin-thin)
|
||||
- [Click me to view Internationalization version](https://github.com/pure-admin/pure-admin-thin/tree/i18n)
|
||||
|
||||
## Supporting Video
|
||||
|
||||
@@ -25,20 +25,20 @@ The simplified version is based on the shelf extracted from [vue-pure-admin](htt
|
||||
## Docs (support `PWA` fast, offline access)
|
||||
|
||||
- [Click me to view the domestic documentation site](https://yiming_chang.gitee.io/pure-admin-doc)
|
||||
- [Click me to view foreign document site](https://xiaoxian521.github.io/pure-admin-doc)
|
||||
- [Click me to view foreign document site](https://pure-admin.github.io/pure-admin-doc)
|
||||
|
||||
## Tauri
|
||||
|
||||
- [Click Watch Tauri](https://github.com/xiaoxian521/tauri-pure-admin)
|
||||
- [Click Watch Tauri](https://github.com/pure-admin/tauri-pure-admin)
|
||||
|
||||
## Electron
|
||||
|
||||
- [Click Watch Electron](https://github.com/xiaoxian521/electron-pure-admin)
|
||||
- [Click Watch Electron](https://github.com/pure-admin/electron-pure-admin)
|
||||
|
||||
## Preview
|
||||
|
||||
- [Click me to view the domestic preview station](https://yiming_chang.gitee.io/vue-pure-admin)
|
||||
- [Click me to view foreign preview site](https://xiaoxian521.github.io/vue-pure-admin)
|
||||
- [Click me to view foreign preview site](https://pure-admin.github.io/vue-pure-admin)
|
||||
|
||||
- PC
|
||||
<p align="center">
|
||||
@@ -59,14 +59,14 @@ The simplified version is based on the shelf extracted from [vue-pure-admin](htt
|
||||
|
||||
Open the project in Gitpod (free online dev environment for GitHub) and start coding immediately.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/xiaoxian521/vue-pure-admin)
|
||||
[](https://gitpod.io/#https://github.com/pure-admin/vue-pure-admin)
|
||||
|
||||
## Install and use
|
||||
|
||||
- Get the project code
|
||||
|
||||
```bash
|
||||
git clone https://github.com/xiaoxian521/vue-pure-admin.git
|
||||
git clone https://github.com/pure-admin/vue-pure-admin.git
|
||||
or
|
||||
git clone https://gitee.com/yiming_chang/vue-pure-admin.git
|
||||
```
|
||||
@@ -98,7 +98,7 @@ pnpm build
|
||||
|
||||
## How to contribute
|
||||
|
||||
You are very welcome to join Or submit a Pull Request
|
||||
You are very welcome to join Or submit a Pull Request
|
||||
|
||||
**Pull Request:**
|
||||
|
||||
@@ -150,7 +150,7 @@ If you think this project is helpful to you, you can help the author buy a glass
|
||||
|
||||
In principle, no fees and copyrights are charged, and you can use it with confidence, but if you need secondary open source, please contact the author for permission!
|
||||
|
||||
[MIT © xiaoxian521-2020](./LICENSE)
|
||||
[MIT © 2020-present, pure-admin](./LICENSE)
|
||||
|
||||
## Backers
|
||||
|
||||
@@ -166,16 +166,16 @@ Thank you very much for your support, I believe the project will get better and
|
||||
|
||||
Thanks to all the people who contribute :heart:
|
||||
|
||||
<a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a>
|
||||
<a href="https://github.com/pure-admin/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=pure-admin/vue-pure-admin" /></a>
|
||||
|
||||
## `Star`
|
||||
|
||||
Many thanks to the kind individuals who leave a star. Your support is much appreciated :heart:
|
||||
|
||||
[](https://github.com/xiaoxian521/vue-pure-admin/stargazers)
|
||||
[](https://github.com/pure-admin/vue-pure-admin/stargazers)
|
||||
|
||||
## `Fork`
|
||||
|
||||
It's so cool that you study hard :heart:
|
||||
|
||||
[](https://github.com/xiaoxian521/vue-pure-admin/network/members)
|
||||
[](https://github.com/pure-admin/vue-pure-admin/network/members)
|
||||
|
||||
34
README.md
34
README.md
@@ -1,8 +1,8 @@
|
||||
<h1>vue-pure-admin</h1>
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
**中文** | [English](./README.en-US.md)
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
|
||||
## 精简版(实际项目开发请用精简版,提供 `非国际化` 、`国际化` 两个版本选择)
|
||||
|
||||
精简版是基于 [vue-pure-admin](https://github.com/xiaoxian521/vue-pure-admin) 提炼出的架子,包含主体功能,更适合实际项目开发,打包后的大小在全局引入 [element-plus](https://element-plus.org) 的情况下仍然低于 `2.3MB`,并且会永久同步完整版的代码。开启 `brotli` 压缩和 `cdn` 替换本地库模式后,打包大小低于 `350kb`
|
||||
精简版是基于 [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin) 提炼出的架子,包含主体功能,更适合实际项目开发,打包后的大小在全局引入 [element-plus](https://element-plus.org) 的情况下仍然低于 `2.3MB`,并且会永久同步完整版的代码。开启 `brotli` 压缩和 `cdn` 替换本地库模式后,打包大小低于 `350kb`
|
||||
|
||||
- [点我查看非国际化精简版](https://github.com/xiaoxian521/pure-admin-thin)
|
||||
- [点我查看国际化精简版](https://github.com/xiaoxian521/pure-admin-thin/tree/i18n)
|
||||
- [点我查看非国际化精简版](https://github.com/pure-admin/pure-admin-thin)
|
||||
- [点我查看国际化精简版](https://github.com/pure-admin/pure-admin-thin/tree/i18n)
|
||||
|
||||
## 配套视频
|
||||
|
||||
@@ -25,20 +25,20 @@
|
||||
## 配套保姆级文档(支持 `PWA` 快速、离线访问)
|
||||
|
||||
- [点我查看国内文档站](https://yiming_chang.gitee.io/pure-admin-doc)
|
||||
- [点我查看国外文档站](https://xiaoxian521.github.io/pure-admin-doc)
|
||||
- [点我查看国外文档站](https://pure-admin.github.io/pure-admin-doc)
|
||||
|
||||
## `Tauri` 版
|
||||
|
||||
- [点我查看 Tauri 版](https://github.com/xiaoxian521/tauri-pure-admin)
|
||||
- [点我查看 Tauri 版](https://github.com/pure-admin/tauri-pure-admin)
|
||||
|
||||
## `Electron` 版
|
||||
|
||||
- [点我查看 Electron 版](https://github.com/xiaoxian521/electron-pure-admin)
|
||||
- [点我查看 Electron 版](https://github.com/pure-admin/electron-pure-admin)
|
||||
|
||||
## 预览
|
||||
|
||||
- [点我查看国内预览站](https://yiming_chang.gitee.io/vue-pure-admin)
|
||||
- [点我查看国外预览站](https://xiaoxian521.github.io/vue-pure-admin)
|
||||
- [点我查看国外预览站](https://pure-admin.github.io/vue-pure-admin)
|
||||
|
||||
- PC 端
|
||||
<p align="center">
|
||||
@@ -59,14 +59,14 @@
|
||||
|
||||
在 Gitpod(适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/xiaoxian521/vue-pure-admin)
|
||||
[](https://gitpod.io/#https://github.com/pure-admin/vue-pure-admin)
|
||||
|
||||
## 安装使用
|
||||
|
||||
- 获取项目代码
|
||||
|
||||
```bash
|
||||
git clone https://github.com/xiaoxian521/vue-pure-admin.git
|
||||
git clone https://github.com/pure-admin/vue-pure-admin.git
|
||||
or
|
||||
git clone https://gitee.com/yiming_chang/vue-pure-admin.git
|
||||
```
|
||||
@@ -98,7 +98,7 @@ pnpm build
|
||||
|
||||
## 如何贡献
|
||||
|
||||
非常欢迎您的加入 或者提交一个 `Pull Request`
|
||||
非常欢迎您的加入 或者提交一个 `Pull Request`
|
||||
|
||||
**Pull Request:**
|
||||
|
||||
@@ -154,7 +154,7 @@ pnpm build
|
||||
|
||||
原则上不收取任何费用及版权,可以放心使用,不过如需二次开源(比如用此平台二次开发并开源)请联系作者获取许可!
|
||||
|
||||
[MIT © xiaoxian521-2020](./LICENSE)
|
||||
[MIT © 2020-present, pure-admin](./LICENSE)
|
||||
|
||||
## 支持者
|
||||
|
||||
@@ -170,16 +170,16 @@ pnpm build
|
||||
|
||||
感谢所有做出贡献的人 :heart:
|
||||
|
||||
<a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a>
|
||||
<a href="https://github.com/pure-admin/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=pure-admin/vue-pure-admin" /></a>
|
||||
|
||||
## `Star`
|
||||
|
||||
非常感谢留下星星的好心人,感谢您的支持 :heart:
|
||||
|
||||
[](https://github.com/xiaoxian521/vue-pure-admin/stargazers)
|
||||
[](https://github.com/pure-admin/vue-pure-admin/stargazers)
|
||||
|
||||
## `Fork`
|
||||
|
||||
瞧,那些 `小哥哥` 、`小姐姐` 认真 `学习` 的样子真滴是 `哎呦不错哦` :heart:
|
||||
|
||||
[](https://github.com/xiaoxian521/vue-pure-admin/network/members)
|
||||
[](https://github.com/pure-admin/vue-pure-admin/network/members)
|
||||
|
||||
@@ -6,6 +6,7 @@ const warpperEnv = (envConf: Recordable): ViteEnv => {
|
||||
VITE_PUBLIC_PATH: "",
|
||||
VITE_ROUTER_HISTORY: "",
|
||||
VITE_CDN: false,
|
||||
VITE_HIDE_HOME: "false",
|
||||
VITE_COMPRESSION: "none"
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Plugin } from "vite";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import utils from "@pureadmin/utils";
|
||||
import duration from "dayjs/plugin/duration";
|
||||
import { green, blue, bold } from "picocolors";
|
||||
import { getPackageSize } from "@pureadmin/utils";
|
||||
dayjs.extend(duration);
|
||||
|
||||
export function viteBuildInfo(): Plugin {
|
||||
@@ -22,7 +22,7 @@ export function viteBuildInfo(): Plugin {
|
||||
green(
|
||||
`👏欢迎使用${blue(
|
||||
"[vue-pure-admin]"
|
||||
)},如果您感觉不错,记得点击后面链接给个star哦💖 https://github.com/xiaoxian521/vue-pure-admin`
|
||||
)},如果您感觉不错,记得点击后面链接给个star哦💖 https://github.com/pure-admin/vue-pure-admin`
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -33,7 +33,7 @@ export function viteBuildInfo(): Plugin {
|
||||
closeBundle() {
|
||||
if (config.command === "build") {
|
||||
endTime = dayjs(new Date());
|
||||
getPackageSize({
|
||||
utils.getPackageSize({
|
||||
folder: outDir,
|
||||
callback: (size: string) => {
|
||||
console.log(
|
||||
|
||||
@@ -12,7 +12,6 @@ const include = [
|
||||
"axios",
|
||||
"pinia",
|
||||
"swiper",
|
||||
"echarts",
|
||||
"intro.js",
|
||||
"vue-i18n",
|
||||
"js-cookie",
|
||||
@@ -37,12 +36,9 @@ const include = [
|
||||
"element-resize-detector",
|
||||
"@amap/amap-jsapi-loader",
|
||||
"el-table-infinite-scroll",
|
||||
"vue-waterfall-plugin-next",
|
||||
"@wangeditor/editor-for-vue",
|
||||
"xgplayer/dist/simple_player",
|
||||
"xgplayer/es/controls/volume",
|
||||
"vuedraggable/src/vuedraggable",
|
||||
"xgplayer/es/controls/screenShot",
|
||||
"xgplayer/es/controls/playbackRate"
|
||||
"vuedraggable/src/vuedraggable"
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@ import { visualizer } from "rollup-plugin-visualizer";
|
||||
import removeConsole from "vite-plugin-remove-console";
|
||||
import themePreprocessorPlugin from "@pureadmin/theme";
|
||||
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
|
||||
import DefineOptions from "unplugin-vue-define-options/vite";
|
||||
import { genScssMultipleScopeVars } from "../src/layout/theme";
|
||||
|
||||
export function getPluginsList(
|
||||
@@ -31,7 +30,6 @@ export function getPluginsList(
|
||||
vueJsx(),
|
||||
VITE_CDN ? cdn : null,
|
||||
configCompressPlugin(VITE_COMPRESSION),
|
||||
DefineOptions(),
|
||||
// 线上环境删除console
|
||||
removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }),
|
||||
viteBuildInfo(),
|
||||
|
||||
38
index.html
38
index.html
@@ -21,54 +21,54 @@
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.loader,
|
||||
.loader:before,
|
||||
.loader:after {
|
||||
border-radius: 50%;
|
||||
.loader::before,
|
||||
.loader::after {
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
border-radius: 50%;
|
||||
animation: load-animation 1.8s infinite ease-in-out;
|
||||
animation-fill-mode: both;
|
||||
animation: loadAnimation 1.8s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.loader {
|
||||
color: #406eeb;
|
||||
font-size: 10px;
|
||||
margin: 80px auto;
|
||||
position: relative;
|
||||
top: 0;
|
||||
margin: 80px auto;
|
||||
font-size: 10px;
|
||||
color: #406eeb;
|
||||
text-indent: -9999em;
|
||||
transform: translateZ(0);
|
||||
animation-delay: -0.16s;
|
||||
top: 0;
|
||||
transform: translate(-50%, 0);
|
||||
animation-delay: -0.16s;
|
||||
}
|
||||
|
||||
.loader:before,
|
||||
.loader:after {
|
||||
content: "";
|
||||
.loader::before,
|
||||
.loader::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.loader:before {
|
||||
.loader::before {
|
||||
left: -3.5em;
|
||||
animation-delay: -0.32s;
|
||||
}
|
||||
|
||||
.loader:after {
|
||||
.loader::after {
|
||||
left: 3.5em;
|
||||
}
|
||||
|
||||
@keyframes loadAnimation {
|
||||
@keyframes load-animation {
|
||||
0%,
|
||||
80%,
|
||||
100% {
|
||||
|
||||
@@ -36,8 +36,11 @@ menus:
|
||||
hsfourZeroOne: "403"
|
||||
hsFive: "500"
|
||||
hscomponents: Components
|
||||
hsdialog: Dialog Components
|
||||
hsmessage: Message Tips Components
|
||||
hsvideo: Video Components
|
||||
hssegmented: Segmented Components
|
||||
hswaterfall: Waterfall Components
|
||||
hsmap: Map Components
|
||||
hsdraggable: Draggable Components
|
||||
hssplitPane: Split Pane
|
||||
|
||||
@@ -36,8 +36,11 @@ menus:
|
||||
hsfourZeroOne: "403"
|
||||
hsFive: "500"
|
||||
hscomponents: 组件
|
||||
hsdialog: 函数式弹框组件
|
||||
hsmessage: 消息提示组件
|
||||
hsvideo: 视频组件
|
||||
hssegmented: 分段控制器组件
|
||||
hswaterfall: 瀑布流组件
|
||||
hsmap: 地图组件
|
||||
hsdraggable: 拖拽组件
|
||||
hssplitPane: 切割面板
|
||||
|
||||
@@ -159,7 +159,7 @@ const frameRouter = {
|
||||
const tabsRouter = {
|
||||
path: "/tabs",
|
||||
meta: {
|
||||
icon: "IF-team-icontabs",
|
||||
icon: "IF-pure-iconfont-tabs",
|
||||
title: "menus.hstabs",
|
||||
rank: tabs
|
||||
},
|
||||
|
||||
177
mock/system.ts
177
mock/system.ts
@@ -1,6 +1,7 @@
|
||||
import { MockMethod } from "vite-plugin-mock";
|
||||
|
||||
export default [
|
||||
// 角色
|
||||
{
|
||||
url: "/role",
|
||||
method: "post",
|
||||
@@ -10,7 +11,7 @@ export default [
|
||||
data: {
|
||||
list: [
|
||||
{
|
||||
createTime: 1609837428000,
|
||||
createTime: 1605456000000,
|
||||
updateTime: 1645477701000,
|
||||
creator: "admin",
|
||||
updater: "",
|
||||
@@ -27,7 +28,7 @@ export default [
|
||||
dataScopeDeptIds: null
|
||||
},
|
||||
{
|
||||
createTime: 1609837428000,
|
||||
createTime: 1605456000000,
|
||||
updateTime: 1645477700000,
|
||||
creator: "admin",
|
||||
updater: "",
|
||||
@@ -44,7 +45,7 @@ export default [
|
||||
dataScopeDeptIds: null
|
||||
},
|
||||
{
|
||||
createTime: 1609912175000,
|
||||
createTime: 1605456000000,
|
||||
updateTime: 1647698441000,
|
||||
creator: "",
|
||||
updater: "1",
|
||||
@@ -66,6 +67,7 @@ export default [
|
||||
};
|
||||
}
|
||||
},
|
||||
// 部门
|
||||
{
|
||||
url: "/dept",
|
||||
method: "post",
|
||||
@@ -75,138 +77,139 @@ export default [
|
||||
data: [
|
||||
{
|
||||
name: "杭州总公司",
|
||||
type: 1, // 1 公司 2 分公司 3 部门
|
||||
parentId: 0,
|
||||
sort: 0,
|
||||
leaderUserId: 1,
|
||||
phone: "15888888888",
|
||||
email: "ry@qq.com",
|
||||
status: 0,
|
||||
id: 100,
|
||||
createTime: 1609837427000,
|
||||
remark: "备注、备注、备注、备注、备注、备注、备注"
|
||||
sort: 0,
|
||||
phone: "15888888888",
|
||||
principal: "@cname()",
|
||||
email: "@email",
|
||||
status: 1,
|
||||
type: 1, // 1 公司 2 分公司 3 部门
|
||||
createTime: 1605456000000,
|
||||
remark: "@cparagraph(1, 3)"
|
||||
},
|
||||
{
|
||||
name: "郑州分公司",
|
||||
type: 2,
|
||||
parentId: 100,
|
||||
sort: 1,
|
||||
leaderUserId: 104,
|
||||
phone: "15888888888",
|
||||
email: "ry@qq.com",
|
||||
status: 0,
|
||||
id: 101,
|
||||
createTime: 1609837427000,
|
||||
remark: "备注、备注、备注、备注、备注、备注、备注"
|
||||
sort: 1,
|
||||
phone: "15888888888",
|
||||
principal: "@cname()",
|
||||
email: "@email",
|
||||
status: 1,
|
||||
type: 2,
|
||||
createTime: 1605456000000,
|
||||
remark: "@cparagraph(1, 3)"
|
||||
},
|
||||
{
|
||||
name: "研发部门",
|
||||
type: 3,
|
||||
parentId: 101,
|
||||
sort: 1,
|
||||
leaderUserId: 104,
|
||||
phone: "15888888888",
|
||||
email: "ry@qq.com",
|
||||
status: 0,
|
||||
id: 103,
|
||||
createTime: 1609837427000,
|
||||
remark: "备注、备注、备注、备注、备注、备注、备注"
|
||||
sort: 1,
|
||||
phone: "15888888888",
|
||||
principal: "@cname()",
|
||||
email: "@email",
|
||||
status: 1,
|
||||
type: 3,
|
||||
createTime: 1605456000000,
|
||||
remark: "@cparagraph(1, 3)"
|
||||
},
|
||||
{
|
||||
name: "市场部门",
|
||||
type: 3,
|
||||
parentId: 102,
|
||||
sort: 1,
|
||||
leaderUserId: null,
|
||||
phone: "15888888888",
|
||||
email: "ry@qq.com",
|
||||
status: 0,
|
||||
id: 108,
|
||||
createTime: 1609837427000,
|
||||
remark: "备注、备注、备注、备注、备注、备注、备注"
|
||||
sort: 1,
|
||||
phone: "15888888888",
|
||||
principal: "@cname()",
|
||||
email: "@email",
|
||||
status: 1,
|
||||
type: 3,
|
||||
createTime: 1605456000000,
|
||||
remark: "@cparagraph(1, 3)"
|
||||
},
|
||||
{
|
||||
name: "深圳分公司",
|
||||
type: 2,
|
||||
parentId: 100,
|
||||
sort: 2,
|
||||
leaderUserId: null,
|
||||
phone: "15888888888",
|
||||
email: "ry@qq.com",
|
||||
status: 0,
|
||||
id: 102,
|
||||
createTime: 1609837427000,
|
||||
remark: "备注、备注、备注、备注、备注、备注、备注"
|
||||
sort: 2,
|
||||
phone: "15888888888",
|
||||
principal: "@cname()",
|
||||
email: "@email",
|
||||
status: 1,
|
||||
type: 2,
|
||||
createTime: 1605456000000,
|
||||
remark: "@cparagraph(1, 3)"
|
||||
},
|
||||
{
|
||||
name: "市场部门",
|
||||
type: 3,
|
||||
parentId: 101,
|
||||
sort: 2,
|
||||
leaderUserId: null,
|
||||
phone: "15888888888",
|
||||
email: "ry@qq.com",
|
||||
status: 1,
|
||||
id: 104,
|
||||
createTime: 1609837427000,
|
||||
remark: "备注、备注、备注、备注、备注、备注、备注"
|
||||
sort: 2,
|
||||
phone: "15888888888",
|
||||
principal: "@cname()",
|
||||
email: "@email",
|
||||
status: 1,
|
||||
type: 3,
|
||||
createTime: 1605456000000,
|
||||
remark: "@cparagraph(1, 3)"
|
||||
},
|
||||
{
|
||||
name: "财务部门",
|
||||
type: 3,
|
||||
parentId: 102,
|
||||
sort: 2,
|
||||
leaderUserId: null,
|
||||
phone: "15888888888",
|
||||
email: "ry@qq.com",
|
||||
status: 0,
|
||||
id: 109,
|
||||
createTime: 1609837427000,
|
||||
remark: "备注、备注、备注、备注、备注、备注、备注"
|
||||
sort: 2,
|
||||
phone: "15888888888",
|
||||
principal: "@cname()",
|
||||
email: "@email",
|
||||
status: 1,
|
||||
type: 3,
|
||||
createTime: 1605456000000,
|
||||
remark: "@cparagraph(1, 3)"
|
||||
},
|
||||
{
|
||||
name: "测试部门",
|
||||
type: 3,
|
||||
parentId: 101,
|
||||
sort: 3,
|
||||
leaderUserId: null,
|
||||
phone: "15888888888",
|
||||
email: "ry@qq.com",
|
||||
status: 0,
|
||||
id: 105,
|
||||
createTime: 1609837427000,
|
||||
remark: "备注、备注、备注、备注、备注、备注、备注"
|
||||
sort: 3,
|
||||
phone: "15888888888",
|
||||
principal: "@cname()",
|
||||
email: "@email",
|
||||
status: 0,
|
||||
type: 3,
|
||||
createTime: 1605456000000,
|
||||
remark: "@cparagraph(1, 3)"
|
||||
},
|
||||
{
|
||||
name: "财务部门",
|
||||
type: 3,
|
||||
parentId: 101,
|
||||
sort: 4,
|
||||
leaderUserId: 103,
|
||||
phone: "15888888888",
|
||||
email: "ry@qq.com",
|
||||
status: 1,
|
||||
id: 106,
|
||||
createTime: 1609837427000,
|
||||
remark: "备注、备注、备注、备注、备注、备注、备注"
|
||||
sort: 4,
|
||||
phone: "15888888888",
|
||||
principal: "@cname()",
|
||||
email: "@email",
|
||||
status: 1,
|
||||
type: 3,
|
||||
createTime: 1605456000000,
|
||||
remark: "@cparagraph(1, 3)"
|
||||
},
|
||||
{
|
||||
name: "运维部门",
|
||||
type: 3,
|
||||
parentId: 101,
|
||||
sort: 5,
|
||||
leaderUserId: null,
|
||||
phone: "15888888888",
|
||||
email: "ry@qq.com",
|
||||
status: 0,
|
||||
id: 107,
|
||||
createTime: 1609837427000,
|
||||
remark: "备注、备注、备注、备注、备注、备注、备注"
|
||||
sort: 5,
|
||||
phone: "15888888888",
|
||||
principal: "@cname()",
|
||||
email: "@email",
|
||||
status: 0,
|
||||
type: 3,
|
||||
createTime: 1605456000000,
|
||||
remark: "@cparagraph(1, 3)"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
},
|
||||
// 用户
|
||||
{
|
||||
url: "/user",
|
||||
method: "post",
|
||||
@@ -225,7 +228,7 @@ export default [
|
||||
sex: 0,
|
||||
id: 1,
|
||||
status: 0,
|
||||
createTime: 1609837427000,
|
||||
createTime: 1605456000000,
|
||||
dept: {
|
||||
id: 103,
|
||||
name: "研发部门"
|
||||
@@ -241,7 +244,7 @@ export default [
|
||||
sex: 0,
|
||||
id: 100,
|
||||
status: 1,
|
||||
createTime: 1609981637000,
|
||||
createTime: 1605456000000,
|
||||
dept: {
|
||||
id: 104,
|
||||
name: "市场部门"
|
||||
@@ -257,7 +260,7 @@ export default [
|
||||
sex: 1,
|
||||
id: 103,
|
||||
status: 1,
|
||||
createTime: 1610553035000,
|
||||
createTime: 1605456000000,
|
||||
dept: {
|
||||
id: 106,
|
||||
name: "财务部门"
|
||||
@@ -273,7 +276,7 @@ export default [
|
||||
sex: 0,
|
||||
id: 104,
|
||||
status: 0,
|
||||
createTime: 1611166433000,
|
||||
createTime: 1605456000000,
|
||||
dept: {
|
||||
id: 107,
|
||||
name: "运维部门"
|
||||
|
||||
174
package.json
174
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vue-pure-admin",
|
||||
"version": "3.9.7",
|
||||
"version": "4.2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
|
||||
@@ -15,8 +15,8 @@
|
||||
"cloc": "NODE_OPTIONS=--max-old-space-size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML",
|
||||
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
|
||||
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
|
||||
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
|
||||
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"",
|
||||
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
|
||||
"lint:pretty": "pretty-quick --staged",
|
||||
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
|
||||
@@ -30,124 +30,134 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@howdyjs/mouse-menu": "^2.0.5",
|
||||
"@logicflow/core": "^1.1.30",
|
||||
"@logicflow/extension": "^1.1.30",
|
||||
"@pureadmin/descriptions": "^1.1.0",
|
||||
"@pureadmin/table": "^1.9.0",
|
||||
"@pureadmin/utils": "^1.8.5",
|
||||
"@vueuse/core": "^9.6.0",
|
||||
"@howdyjs/mouse-menu": "^2.0.7",
|
||||
"@logicflow/core": "^1.2.6",
|
||||
"@logicflow/extension": "^1.2.6",
|
||||
"@pureadmin/descriptions": "^1.1.1",
|
||||
"@pureadmin/table": "^2.1.0",
|
||||
"@pureadmin/utils": "^1.8.9",
|
||||
"@vueuse/core": "^10.1.2",
|
||||
"@vueuse/motion": "2.0.0-beta.12",
|
||||
"@wangeditor/editor": "^5.1.21",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.2.0",
|
||||
"axios": "^1.4.0",
|
||||
"china-area-data": "^5.0.1",
|
||||
"cropperjs": "^1.5.12",
|
||||
"dayjs": "^1.11.6",
|
||||
"echarts": "^5.4.0",
|
||||
"cropperjs": "^1.5.13",
|
||||
"dayjs": "^1.11.7",
|
||||
"echarts": "^5.4.2",
|
||||
"el-table-infinite-scroll": "^3.0.1",
|
||||
"element-plus": "^2.2.27",
|
||||
"element-plus": "^2.3.4",
|
||||
"element-resize-detector": "^1.2.4",
|
||||
"intro.js": "^6.0.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
"intro.js": "^7.0.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsbarcode": "^3.11.5",
|
||||
"md-editor-v3": "^2.5.0",
|
||||
"md-editor-v3": "2.7.2",
|
||||
"mitt": "^3.0.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^2.0.28",
|
||||
"qrcode": "^1.5.1",
|
||||
"qs": "^6.11.0",
|
||||
"responsive-storage": "^2.1.0",
|
||||
"pinia": "^2.0.36",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.1",
|
||||
"responsive-storage": "^2.2.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"swiper": "^8.4.5",
|
||||
"typeit": "^8.7.0",
|
||||
"swiper": "^9.3.1",
|
||||
"typeit": "^8.7.1",
|
||||
"v-contextmenu": "3.0.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue": "^3.3.2",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-json-pretty": "^2.2.3",
|
||||
"vue-pdf-embed": "^1.1.5",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-types": "^4.2.1",
|
||||
"vue-json-pretty": "^2.2.4",
|
||||
"vue-pdf-embed": "^1.1.6",
|
||||
"vue-router": "^4.2.0",
|
||||
"vue-types": "^5.0.2",
|
||||
"vue-virtual-scroller": "2.0.0-beta.7",
|
||||
"vue3-danmaku": "^1.2.0",
|
||||
"vue-waterfall-plugin-next": "^2.2.1",
|
||||
"vue3-danmaku": "1.4.0-beta.1",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"xgplayer": "^2.32.1",
|
||||
"xgplayer": "^3.0.2",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "13.1.0",
|
||||
"@commitlint/config-conventional": "13.1.0",
|
||||
"@iconify-icons/ep": "^1.2.7",
|
||||
"@iconify-icons/ri": "^1.2.3",
|
||||
"@iconify/vue": "^4.0.0",
|
||||
"@intlify/unplugin-vue-i18n": "^0.8.1",
|
||||
"@commitlint/cli": "^17.6.3",
|
||||
"@commitlint/config-conventional": "^17.6.3",
|
||||
"@iconify-icons/ep": "^1.2.11",
|
||||
"@iconify-icons/ri": "^1.2.7",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@intlify/unplugin-vue-i18n": "^0.10.0",
|
||||
"@pureadmin/theme": "^3.0.0",
|
||||
"@types/element-resize-detector": "1.1.3",
|
||||
"@types/intro.js": "^5.1.0",
|
||||
"@types/js-cookie": "^3.0.1",
|
||||
"@types/intro.js": "^5.1.1",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"@types/mockjs": "^1.0.7",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/node": "^18.15.12",
|
||||
"@types/nprogress": "0.2.0",
|
||||
"@types/qrcode": "^1.4.2",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/sortablejs": "^1.15.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.43.0",
|
||||
"@typescript-eslint/parser": "^5.43.0",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.0",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.2",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cloc": "^2.10.0",
|
||||
"cssnano": "^5.1.14",
|
||||
"eslint": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^9.7.0",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "11.1.2",
|
||||
"@types/sortablejs": "^1.15.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
||||
"@typescript-eslint/parser": "^5.59.5",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"@vue/eslint-config-prettier": "^7.1.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cloc": "^2.11.0",
|
||||
"cssnano": "^6.0.1",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.12.0",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^13.2.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss": "^8.4.18",
|
||||
"postcss": "^8.4.23",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-import": "^15.0.0",
|
||||
"postcss-scss": "^4.0.5",
|
||||
"prettier": "^2.5.1",
|
||||
"postcss-import": "^15.1.0",
|
||||
"postcss-scss": "^4.0.6",
|
||||
"prettier": "^2.8.7",
|
||||
"pretty-quick": "3.1.1",
|
||||
"rimraf": "3.0.2",
|
||||
"rollup-plugin-visualizer": "^5.8.3",
|
||||
"sass": "^1.56.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"stylelint": "^14.3.0",
|
||||
"stylelint-config-html": "^1.0.0",
|
||||
"stylelint-config-prettier": "^9.0.3",
|
||||
"stylelint-config-recommended": "^9.0.0",
|
||||
"stylelint-config-standard": "^29.0.0",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"rollup-plugin-visualizer": "^5.9.0",
|
||||
"sass": "^1.62.1",
|
||||
"sass-loader": "^13.2.2",
|
||||
"stylelint": "^15.6.1",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-recess-order": "^4.0.0",
|
||||
"stylelint-config-recommended": "^12.0.0",
|
||||
"stylelint-config-recommended-scss": "^11.0.0",
|
||||
"stylelint-config-recommended-vue": "^1.4.0",
|
||||
"stylelint-config-standard": "^33.0.0",
|
||||
"stylelint-config-standard-scss": "^9.0.0",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"stylelint-prettier": "^3.0.0",
|
||||
"stylelint-scss": "^5.0.0",
|
||||
"svgo": "^3.0.2",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"terser": "^5.15.1",
|
||||
"typescript": "^4.9.3",
|
||||
"unplugin-vue-define-options": "^1.0.0",
|
||||
"vite": "^4.0.3",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"terser": "^5.17.3",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.3.5",
|
||||
"vite-plugin-cdn-import": "^0.3.5",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-remove-console": "^1.3.0",
|
||||
"vite-svg-loader": "^3.6.0",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
"vue-tsc": "^1.0.9"
|
||||
"vite-plugin-remove-console": "^2.1.1",
|
||||
"vite-svg-loader": "^4.0.0",
|
||||
"vue-eslint-parser": "^9.3.0",
|
||||
"vue-tsc": "^1.6.5"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"ignoreMissing": [
|
||||
"rollup",
|
||||
"webpack"
|
||||
"webpack",
|
||||
"core-js"
|
||||
]
|
||||
},
|
||||
"allowedDeprecatedVersions": {
|
||||
"sourcemap-codec": "*",
|
||||
"stable": "*"
|
||||
}
|
||||
},
|
||||
"repository": "git@github.com:xiaoxian521/vue-pure-admin.git",
|
||||
"repository": "git@github.com:pure-admin/vue-pure-admin.git",
|
||||
"author": "xiaoxian521",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
7087
pnpm-lock.yaml
generated
7087
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
"postcss-import": {},
|
||||
"tailwindcss/nesting": {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {})
|
||||
|
||||
1
public/logo.svg
Normal file
1
public/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109z"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665z"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.114 323.114 0 0 1-107.769-242.852z"/></svg>
|
||||
|
After Width: | Height: | Size: 712 B |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"Version": "3.9.7",
|
||||
"Version": "4.2.0",
|
||||
"Title": "PureAdmin",
|
||||
"FixedHeader": true,
|
||||
"HiddenSideBar": false,
|
||||
@@ -19,6 +19,7 @@
|
||||
"MenuArrowIconNoTransition": true,
|
||||
"CachingAsyncRoutes": false,
|
||||
"TooltipEffect": "light",
|
||||
"ResponsiveStorageNameSpace": "responsive-",
|
||||
"MapConfigure": {
|
||||
"amapKey": "97b3248d1553172e81f168cf94ea667e",
|
||||
"options": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<el-config-provider :locale="currentLocale">
|
||||
<router-view />
|
||||
<ReDialog />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
@@ -9,10 +10,12 @@ import { defineComponent } from "vue";
|
||||
import { ElConfigProvider } from "element-plus";
|
||||
import zhCn from "element-plus/lib/locale/lang/zh-cn";
|
||||
import en from "element-plus/lib/locale/lang/en";
|
||||
import { ReDialog } from "@/components/ReDialog";
|
||||
export default defineComponent({
|
||||
name: "app",
|
||||
components: {
|
||||
[ElConfigProvider.name]: ElConfigProvider
|
||||
[ElConfigProvider.name]: ElConfigProvider,
|
||||
ReDialog
|
||||
},
|
||||
computed: {
|
||||
currentLocale() {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 2208059 */
|
||||
src: url("iconfont.woff2?t=1638023560828") format("woff2"),
|
||||
url("iconfont.woff?t=1638023560828") format("woff"),
|
||||
url("iconfont.ttf?t=1638023560828") format("truetype");
|
||||
src: url("iconfont.woff2?t=1671895108120") format("woff2"),
|
||||
url("iconfont.woff?t=1671895108120") format("woff"),
|
||||
url("iconfont.ttf?t=1671895108120") format("truetype");
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,26 +13,14 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.team-icontabs::before {
|
||||
.pure-iconfont-tabs:before {
|
||||
content: "\e63e";
|
||||
}
|
||||
|
||||
.team-iconlogo::before {
|
||||
.pure-iconfont-logo:before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.team-iconxinpin::before {
|
||||
content: "\e614";
|
||||
}
|
||||
|
||||
.team-iconxinpinrenqiwang::before {
|
||||
.pure-iconfont-new:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.team-iconexit-fullscreen::before {
|
||||
content: "\e62a";
|
||||
}
|
||||
|
||||
.team-iconfullscreen::before {
|
||||
content: "\e62b";
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,50 +2,29 @@
|
||||
"id": "2208059",
|
||||
"name": "pure-admin",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "team-icon",
|
||||
"description": "pure-admin",
|
||||
"css_prefix_text": "pure-iconfont-",
|
||||
"description": "pure-admin-iconfont",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "20594647",
|
||||
"name": "标签页",
|
||||
"name": "Tabs",
|
||||
"font_class": "tabs",
|
||||
"unicode": "e63e",
|
||||
"unicode_decimal": 58942
|
||||
},
|
||||
{
|
||||
"icon_id": "22129506",
|
||||
"name": "水能",
|
||||
"name": "PureLogo",
|
||||
"font_class": "logo",
|
||||
"unicode": "e620",
|
||||
"unicode_decimal": 58912
|
||||
},
|
||||
{
|
||||
"icon_id": "7795613",
|
||||
"name": "新品",
|
||||
"font_class": "xinpin",
|
||||
"unicode": "e614",
|
||||
"unicode_decimal": 58900
|
||||
},
|
||||
{
|
||||
"icon_id": "7795615",
|
||||
"name": "新品人气王",
|
||||
"font_class": "xinpinrenqiwang",
|
||||
"name": "New",
|
||||
"font_class": "new",
|
||||
"unicode": "e615",
|
||||
"unicode_decimal": 58901
|
||||
},
|
||||
{
|
||||
"icon_id": "5698509",
|
||||
"name": "全屏缩小",
|
||||
"font_class": "exit-fullscreen",
|
||||
"unicode": "e62a",
|
||||
"unicode_decimal": 58922
|
||||
},
|
||||
{
|
||||
"icon_id": "5698510",
|
||||
"name": "全屏显示",
|
||||
"font_class": "fullscreen",
|
||||
"unicode": "e62b",
|
||||
"unicode_decimal": 58923
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
29
src/components/ReCol/index.ts
Normal file
29
src/components/ReCol/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { ElCol } from "element-plus";
|
||||
import { h, defineComponent } from "vue";
|
||||
|
||||
// 封装element-plus的el-col组件
|
||||
export default defineComponent({
|
||||
name: "ReCol",
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 24
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const attrs = this.$attrs;
|
||||
const val = this.value;
|
||||
return h(
|
||||
ElCol,
|
||||
{
|
||||
xs: val,
|
||||
sm: val,
|
||||
md: val,
|
||||
lg: val,
|
||||
xl: val,
|
||||
...attrs
|
||||
},
|
||||
{ default: () => this.$slots.default() }
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PropType } from "vue";
|
||||
import { propTypes } from "@/utils/propTypes";
|
||||
import propTypes from "@/utils/propTypes";
|
||||
export const countToProps = {
|
||||
startVal: propTypes.number.def(0),
|
||||
endVal: propTypes.number.def(2020),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PropType } from "vue";
|
||||
import { propTypes } from "@/utils/propTypes";
|
||||
import propTypes from "@/utils/propTypes";
|
||||
export const reboundProps = {
|
||||
delay: propTypes.number.def(1),
|
||||
blur: propTypes.number.def(2),
|
||||
|
||||
39
src/components/ReDialog/index.ts
Normal file
39
src/components/ReDialog/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { ref } from "vue";
|
||||
import reDialog from "./index.vue";
|
||||
import { useTimeoutFn } from "@vueuse/core";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
import type {
|
||||
EventType,
|
||||
ArgsType,
|
||||
DialogProps,
|
||||
ButtonProps,
|
||||
DialogOptions
|
||||
} from "./type";
|
||||
|
||||
const dialogStore = ref<Array<DialogOptions>>([]);
|
||||
|
||||
const addDialog = (options: DialogOptions) => {
|
||||
const open = () =>
|
||||
dialogStore.value.push(Object.assign(options, { visible: true }));
|
||||
if (options?.openDelay) {
|
||||
useTimeoutFn(() => {
|
||||
open();
|
||||
}, options.openDelay);
|
||||
} else {
|
||||
open();
|
||||
}
|
||||
};
|
||||
|
||||
const closeDialog = (options: DialogOptions, index: number, args?: any) => {
|
||||
dialogStore.value.splice(index, 1);
|
||||
options.closeCallBack && options.closeCallBack({ options, index, args });
|
||||
};
|
||||
|
||||
const closeAllDialog = () => {
|
||||
dialogStore.value = [];
|
||||
};
|
||||
|
||||
const ReDialog = withInstall(reDialog);
|
||||
|
||||
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };
|
||||
export { ReDialog, dialogStore, addDialog, closeDialog, closeAllDialog };
|
||||
157
src/components/ReDialog/index.vue
Normal file
157
src/components/ReDialog/index.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
closeDialog,
|
||||
dialogStore,
|
||||
type EventType,
|
||||
type ButtonProps,
|
||||
type DialogOptions
|
||||
} from "./index";
|
||||
import { ref, computed } from "vue";
|
||||
import { isFunction } from "@pureadmin/utils";
|
||||
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
|
||||
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
|
||||
|
||||
const fullscreen = ref(false);
|
||||
|
||||
const footerButtons = computed(() => {
|
||||
return (options: DialogOptions) => {
|
||||
return options?.footerButtons?.length > 0
|
||||
? options.footerButtons
|
||||
: ([
|
||||
{
|
||||
label: "取消",
|
||||
text: true,
|
||||
bg: true,
|
||||
btnClick: ({ dialog: { options, index } }) => {
|
||||
const done = () =>
|
||||
closeDialog(options, index, { command: "cancel" });
|
||||
if (options?.beforeCancel && isFunction(options?.beforeCancel)) {
|
||||
options.beforeCancel(done, { options, index });
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "确定",
|
||||
type: "primary",
|
||||
text: true,
|
||||
bg: true,
|
||||
btnClick: ({ dialog: { options, index } }) => {
|
||||
const done = () =>
|
||||
closeDialog(options, index, { command: "sure" });
|
||||
if (options?.beforeSure && isFunction(options?.beforeSure)) {
|
||||
options.beforeSure(done, { options, index });
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
] as Array<ButtonProps>);
|
||||
};
|
||||
});
|
||||
|
||||
const fullscreenClass = computed(() => {
|
||||
return [
|
||||
"el-icon",
|
||||
"el-dialog__close",
|
||||
"-translate-x-2",
|
||||
"cursor-pointer",
|
||||
"hover:!text-[red]"
|
||||
];
|
||||
});
|
||||
|
||||
function eventsCallBack(
|
||||
event: EventType,
|
||||
options: DialogOptions,
|
||||
index: number
|
||||
) {
|
||||
fullscreen.value = options?.fullscreen ?? false;
|
||||
if (options?.[event] && isFunction(options?.[event])) {
|
||||
return options?.[event]({ options, index });
|
||||
}
|
||||
}
|
||||
|
||||
function handleClose(
|
||||
options: DialogOptions,
|
||||
index: number,
|
||||
args = { command: "close" }
|
||||
) {
|
||||
closeDialog(options, index, args);
|
||||
eventsCallBack("close", options, index);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
class="pure-dialog"
|
||||
v-for="(options, index) in dialogStore"
|
||||
:key="index"
|
||||
v-bind="options"
|
||||
v-model="options.visible"
|
||||
:fullscreen="fullscreen ? true : options?.fullscreen ? true : false"
|
||||
@close="handleClose(options, index)"
|
||||
@opened="eventsCallBack('open', options, index)"
|
||||
@openAutoFocus="eventsCallBack('openAutoFocus', options, index)"
|
||||
@closeAutoFocus="eventsCallBack('closeAutoFocus', options, index)"
|
||||
>
|
||||
<!-- header -->
|
||||
<template
|
||||
v-if="options?.fullscreenIcon || options?.headerRenderer"
|
||||
#header="{ close, titleId, titleClass }"
|
||||
>
|
||||
<div
|
||||
v-if="options?.fullscreenIcon"
|
||||
class="flex items-center justify-between"
|
||||
>
|
||||
<span :id="titleId" :class="titleClass">{{ options?.title }}</span>
|
||||
<i
|
||||
v-if="!options?.fullscreen"
|
||||
:class="fullscreenClass"
|
||||
@click="fullscreen = !fullscreen"
|
||||
>
|
||||
<IconifyIconOffline
|
||||
class="pure-dialog-svg"
|
||||
:icon="
|
||||
options?.fullscreen
|
||||
? ExitFullscreen
|
||||
: fullscreen
|
||||
? ExitFullscreen
|
||||
: Fullscreen
|
||||
"
|
||||
/>
|
||||
</i>
|
||||
</div>
|
||||
<component
|
||||
v-else
|
||||
:is="options?.headerRenderer({ close, titleId, titleClass })"
|
||||
/>
|
||||
</template>
|
||||
<component
|
||||
v-bind="options?.props"
|
||||
:is="options.contentRenderer({ options, index })"
|
||||
@close="args => handleClose(options, index, args)"
|
||||
/>
|
||||
<!-- footer -->
|
||||
<template v-if="!options?.hideFooter" #footer>
|
||||
<template v-if="options?.footerRenderer">
|
||||
<component :is="options?.footerRenderer({ options, index })" />
|
||||
</template>
|
||||
<span v-else>
|
||||
<el-button
|
||||
v-for="(btn, key) in footerButtons(options)"
|
||||
:key="key"
|
||||
v-bind="btn"
|
||||
@click="
|
||||
btn.btnClick({
|
||||
dialog: { options, index },
|
||||
button: { btn, index: key }
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ btn?.label }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
218
src/components/ReDialog/type.ts
Normal file
218
src/components/ReDialog/type.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import type { CSSProperties, VNode, Component } from "vue";
|
||||
|
||||
type DoneFn = (cancel?: boolean) => void;
|
||||
type EventType = "open" | "close" | "openAutoFocus" | "closeAutoFocus";
|
||||
type ArgsType = {
|
||||
/** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页 */
|
||||
command: "cancel" | "sure" | "close";
|
||||
};
|
||||
|
||||
/** https://element-plus.org/zh-CN/component/dialog.html#attributes */
|
||||
type DialogProps = {
|
||||
/** `Dialog` 的显示与隐藏 */
|
||||
visible?: boolean;
|
||||
/** `Dialog` 的标题 */
|
||||
title?: string;
|
||||
/** `Dialog` 的宽度,默认 `50%` */
|
||||
width?: string | number;
|
||||
/** 是否为全屏 `Dialog`(会一直处于全屏状态,除非弹框关闭),默认 `false`,`fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */
|
||||
fullscreen?: boolean;
|
||||
/** 是否显示全屏操作图标,默认 `false`,`fullscreen` 和 `fullscreenIcon` 都传时只有 `fullscreen` 会生效 */
|
||||
fullscreenIcon?: boolean;
|
||||
/** `Dialog CSS` 中的 `margin-top` 值,默认 `15vh` */
|
||||
top?: string;
|
||||
/** 是否需要遮罩层,默认 `true` */
|
||||
modal?: boolean;
|
||||
/** `Dialog` 自身是否插入至 `body` 元素上。嵌套的 `Dialog` 必须指定该属性并赋值为 `true`,默认 `false` */
|
||||
appendToBody?: boolean;
|
||||
/** 是否在 `Dialog` 出现时将 `body` 滚动锁定,默认 `true` */
|
||||
lockScroll?: boolean;
|
||||
/** `Dialog` 的自定义类名 */
|
||||
class?: string;
|
||||
/** `Dialog` 的自定义样式 */
|
||||
style?: CSSProperties;
|
||||
/** `Dialog` 打开的延时时间,单位毫秒,默认 `0` */
|
||||
openDelay?: number;
|
||||
/** `Dialog` 关闭的延时时间,单位毫秒,默认 `0` */
|
||||
closeDelay?: number;
|
||||
/** 是否可以通过点击 `modal` 关闭 `Dialog`,默认 `true` */
|
||||
closeOnClickModal?: boolean;
|
||||
/** 是否可以通过按下 `ESC` 关闭 `Dialog`,默认 `true` */
|
||||
closeOnPressEscape?: boolean;
|
||||
/** 是否显示关闭按钮,默认 `true` */
|
||||
showClose?: boolean;
|
||||
/** 关闭前的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
|
||||
beforeClose?: (done: DoneFn) => void;
|
||||
/** 为 `Dialog` 启用可拖拽功能,默认 `false` */
|
||||
draggable?: boolean;
|
||||
/** 是否让 `Dialog` 的 `header` 和 `footer` 部分居中排列,默认 `false` */
|
||||
center?: boolean;
|
||||
/** 是否水平垂直对齐对话框,默认 `false` */
|
||||
alignCenter?: boolean;
|
||||
/** 当关闭 `Dialog` 时,销毁其中的元素,默认 `false` */
|
||||
destroyOnClose?: boolean;
|
||||
};
|
||||
|
||||
type BtnClickDialog = {
|
||||
options?: DialogOptions;
|
||||
index?: number;
|
||||
};
|
||||
type BtnClickButton = {
|
||||
btn?: ButtonProps;
|
||||
index?: number;
|
||||
};
|
||||
/** https://element-plus.org/zh-CN/component/button.html#button-attributes */
|
||||
type ButtonProps = {
|
||||
/** 按钮文字 */
|
||||
label: string;
|
||||
/** 按钮尺寸 */
|
||||
size?: "large" | "default" | "small";
|
||||
/** 按钮类型 */
|
||||
type?: "primary" | "success" | "warning" | "danger" | "info";
|
||||
/** 是否为朴素按钮,默认 `false` */
|
||||
plain?: boolean;
|
||||
/** 是否为文字按钮,默认 `false` */
|
||||
text?: boolean;
|
||||
/** 是否显示文字按钮背景颜色,默认 `false` */
|
||||
bg?: boolean;
|
||||
/** 是否为链接按钮,默认 `false` */
|
||||
link?: boolean;
|
||||
/** 是否为圆角按钮,默认 `false` */
|
||||
round?: boolean;
|
||||
/** 是否为圆形按钮,默认 `false` */
|
||||
circle?: boolean;
|
||||
/** 是否为加载中状态,默认 `false` */
|
||||
loading?: boolean;
|
||||
/** 自定义加载中状态图标组件 */
|
||||
loadingIcon?: string | Component;
|
||||
/** 按钮是否为禁用状态,默认 `false` */
|
||||
disabled?: boolean;
|
||||
/** 图标组件 */
|
||||
icon?: string | Component;
|
||||
/** 是否开启原生 `autofocus` 属性,默认 `false` */
|
||||
autofocus?: boolean;
|
||||
/** 原生 `type` 属性,默认 `button` */
|
||||
nativeType?: "button" | "submit" | "reset";
|
||||
/** 自动在两个中文字符之间插入空格 */
|
||||
autoInsertSpace?: boolean;
|
||||
/** 自定义按钮颜色, 并自动计算 `hover` 和 `active` 触发后的颜色 */
|
||||
color?: string;
|
||||
/** `dark` 模式, 意味着自动设置 `color` 为 `dark` 模式的颜色,默认 `false` */
|
||||
dark?: boolean;
|
||||
/** 自定义元素标签 */
|
||||
tag?: string | Component;
|
||||
/** 点击按钮后触发的回调 */
|
||||
btnClick?: ({
|
||||
dialog,
|
||||
button
|
||||
}: {
|
||||
/** 当前 `Dialog` 信息 */
|
||||
dialog: BtnClickDialog;
|
||||
/** 当前 `button` 信息 */
|
||||
button: BtnClickButton;
|
||||
}) => void;
|
||||
};
|
||||
|
||||
interface DialogOptions extends DialogProps {
|
||||
/** 内容区组件的 `props`,可通过 `defineProps` 接收 */
|
||||
props?: any;
|
||||
/** 是否隐藏 `Dialog` 按钮操作区的内容 */
|
||||
hideFooter?: boolean;
|
||||
/**
|
||||
* @description 自定义对话框标题的内容渲染器
|
||||
* @see {@link https://element-plus.org/zh-CN/component/dialog.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A4%B4%E9%83%A8}
|
||||
*/
|
||||
headerRenderer?: ({
|
||||
close,
|
||||
titleId,
|
||||
titleClass
|
||||
}: {
|
||||
close: Function;
|
||||
titleId: string;
|
||||
titleClass: string;
|
||||
}) => VNode | Component;
|
||||
/** 自定义内容渲染器 */
|
||||
contentRenderer?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => VNode | Component;
|
||||
/** 自定义按钮操作区的内容渲染器,会覆盖`footerButtons`以及默认的 `取消` 和 `确定` 按钮 */
|
||||
footerRenderer?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => VNode | Component;
|
||||
/** 自定义底部按钮操作 */
|
||||
footerButtons?: Array<ButtonProps>;
|
||||
/** `Dialog` 打开后的回调 */
|
||||
open?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
/** `Dialog` 关闭后的回调(只有点击右上角关闭按钮或者空白页关闭页面时才会触发) */
|
||||
close?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
/** `Dialog` 关闭后的回调。 `args` 返回的 `command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页 */
|
||||
closeCallBack?: ({
|
||||
options,
|
||||
index,
|
||||
args
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
args: any;
|
||||
}) => void;
|
||||
/** 输入焦点聚焦在 `Dialog` 内容时的回调 */
|
||||
openAutoFocus?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
/** 输入焦点从 `Dialog` 内容失焦时的回调 */
|
||||
closeAutoFocus?: ({
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}) => void;
|
||||
/** 点击底部取消按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
|
||||
beforeCancel?: (
|
||||
done: Function,
|
||||
{
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}
|
||||
) => void;
|
||||
/** 点击底部确定按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
|
||||
beforeSure?: (
|
||||
done: Function,
|
||||
{
|
||||
options,
|
||||
index
|
||||
}: {
|
||||
options: DialogOptions;
|
||||
index: number;
|
||||
}
|
||||
) => void;
|
||||
}
|
||||
|
||||
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineComponent, ref } from "vue";
|
||||
import { propTypes } from "@/utils/propTypes";
|
||||
import propTypes from "@/utils/propTypes";
|
||||
import "./filpper.css";
|
||||
|
||||
const props = {
|
||||
|
||||
@@ -123,9 +123,9 @@ onUnmounted(() => {
|
||||
|
||||
.flip-clock em {
|
||||
display: inline-block;
|
||||
line-height: 102px;
|
||||
font-size: 66px;
|
||||
font-style: normal;
|
||||
line-height: 102px;
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -132,10 +132,10 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@import "./assets/iconfont/iconfont.css";
|
||||
@import url("./assets/iconfont/iconfont.css");
|
||||
|
||||
.control-container {
|
||||
background: hsla(0, 0%, 100%, 0.8);
|
||||
background: hsl(0deg 0% 100% / 80%);
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 20%);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -200,8 +200,8 @@ watch(
|
||||
|
||||
.icon-item {
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
transition: all 0.4s;
|
||||
transform: scaleX(1.05);
|
||||
}
|
||||
@@ -224,15 +224,15 @@ watch(
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
height: 30px;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__header),
|
||||
:deep(.el-tabs__nav-wrap) {
|
||||
margin: 0;
|
||||
position: static;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import pureTableBar from "./src/bar";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
|
||||
/** 配合 `@pureadmin/table` 实现快速便捷的表格操作 https://github.com/xiaoxian521/pure-admin-table */
|
||||
/** 配合 `@pureadmin/table` 实现快速便捷的表格操作 https://github.com/pure-admin/pure-admin-table */
|
||||
export const PureTableBar = withInstall(pureTableBar);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { delay } from "@pureadmin/utils";
|
||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
||||
import { defineComponent, ref, computed, type PropType } from "vue";
|
||||
import { delay, getKeyList, cloneDeep } from "@pureadmin/utils";
|
||||
import { defineComponent, ref, computed, type PropType, nextTick } from "vue";
|
||||
|
||||
import Sortable from "sortablejs";
|
||||
import DragIcon from "./svg/drag.svg?component";
|
||||
import ExpandIcon from "./svg/expand.svg?component";
|
||||
import RefreshIcon from "./svg/refresh.svg?component";
|
||||
import SettingIcon from "./svg/settings.svg?component";
|
||||
@@ -15,6 +18,11 @@ const props = {
|
||||
/** 对于树形表格,如果想启用展开和折叠功能,传入当前表格的ref即可 */
|
||||
tableRef: {
|
||||
type: Object as PropType<any>
|
||||
},
|
||||
/** 需要展示的列 */
|
||||
columns: {
|
||||
type: Array as PropType<TableColumnList>,
|
||||
default: () => []
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,10 +32,14 @@ export default defineComponent({
|
||||
emits: ["refresh"],
|
||||
setup(props, { emit, slots, attrs }) {
|
||||
const buttonRef = ref();
|
||||
const checkList = ref([]);
|
||||
const size = ref("default");
|
||||
const isExpandAll = ref(true);
|
||||
const loading = ref(false);
|
||||
const checkAll = ref(true);
|
||||
const isIndeterminate = ref(false);
|
||||
let checkColumnList = getKeyList(cloneDeep(props?.columns), "label");
|
||||
const checkedColumns = ref(checkColumnList);
|
||||
const dynamicColumns = ref(cloneDeep(props?.columns));
|
||||
|
||||
const getDropdownItemStyle = computed(() => {
|
||||
return s => {
|
||||
@@ -50,6 +62,19 @@ export default defineComponent({
|
||||
];
|
||||
});
|
||||
|
||||
const topClass = computed(() => {
|
||||
return [
|
||||
"flex",
|
||||
"justify-between",
|
||||
"pt-[3px]",
|
||||
"px-[11px]",
|
||||
"border-b-[1px]",
|
||||
"border-solid",
|
||||
"border-[#dcdfe6]",
|
||||
"dark:border-[#303030]"
|
||||
];
|
||||
});
|
||||
|
||||
function onReFresh() {
|
||||
loading.value = true;
|
||||
emit("refresh");
|
||||
@@ -70,6 +95,34 @@ export default defineComponent({
|
||||
});
|
||||
}
|
||||
|
||||
function handleCheckAllChange(val: boolean) {
|
||||
checkedColumns.value = val ? checkColumnList : [];
|
||||
isIndeterminate.value = false;
|
||||
dynamicColumns.value.map(column =>
|
||||
val ? (column.hide = false) : (column.hide = true)
|
||||
);
|
||||
}
|
||||
|
||||
function handleCheckedColumnsChange(value: string[]) {
|
||||
const checkedCount = value.length;
|
||||
checkAll.value = checkedCount === checkColumnList.length;
|
||||
isIndeterminate.value =
|
||||
checkedCount > 0 && checkedCount < checkColumnList.length;
|
||||
}
|
||||
|
||||
function handleCheckColumnListChange(val: boolean, label: string) {
|
||||
dynamicColumns.value.filter(item => item.label === label)[0].hide = !val;
|
||||
}
|
||||
|
||||
async function onReset() {
|
||||
checkAll.value = true;
|
||||
isIndeterminate.value = false;
|
||||
dynamicColumns.value = cloneDeep(props?.columns);
|
||||
checkColumnList = [];
|
||||
checkColumnList = await getKeyList(cloneDeep(props?.columns), "label");
|
||||
checkedColumns.value = checkColumnList;
|
||||
}
|
||||
|
||||
const dropdown = {
|
||||
dropdown: () => (
|
||||
<el-dropdown-menu class="translation">
|
||||
@@ -95,6 +148,47 @@ export default defineComponent({
|
||||
)
|
||||
};
|
||||
|
||||
/** 列展示拖拽排序 */
|
||||
const rowDrop = (event: { preventDefault: () => void }) => {
|
||||
event.preventDefault();
|
||||
nextTick(() => {
|
||||
const wrapper: HTMLElement = document.querySelector(
|
||||
".el-checkbox-group>div"
|
||||
);
|
||||
Sortable.create(wrapper, {
|
||||
animation: 300,
|
||||
handle: ".drag-btn",
|
||||
onEnd: ({ newIndex, oldIndex, item }) => {
|
||||
const targetThElem = item;
|
||||
const wrapperElem = targetThElem.parentNode as HTMLElement;
|
||||
const oldColumn = dynamicColumns.value[oldIndex];
|
||||
const newColumn = dynamicColumns.value[newIndex];
|
||||
if (oldColumn?.fixed || newColumn?.fixed) {
|
||||
// 当前列存在fixed属性 则不可拖拽
|
||||
const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
|
||||
if (newIndex > oldIndex) {
|
||||
wrapperElem.insertBefore(targetThElem, oldThElem);
|
||||
} else {
|
||||
wrapperElem.insertBefore(
|
||||
targetThElem,
|
||||
oldThElem ? oldThElem.nextElementSibling : oldThElem
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
|
||||
dynamicColumns.value.splice(newIndex, 0, currentRow);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const isFixedColumn = (label: string) => {
|
||||
return dynamicColumns.value.filter(item => item.label === label)[0].fixed
|
||||
? true
|
||||
: false;
|
||||
};
|
||||
|
||||
const reference = {
|
||||
reference: () => (
|
||||
<SettingIcon
|
||||
@@ -110,7 +204,9 @@ export default defineComponent({
|
||||
<div class="flex justify-between w-full h-[60px] p-4">
|
||||
<p class="font-bold truncate">{props.title}</p>
|
||||
<div class="flex items-center justify-around">
|
||||
<div class="flex mr-4">{slots?.buttons()}</div>
|
||||
{slots?.buttons ? (
|
||||
<div class="flex mr-4">{slots.buttons()}</div>
|
||||
) : null}
|
||||
{props.tableRef?.size ? (
|
||||
<>
|
||||
<el-tooltip
|
||||
@@ -140,7 +236,6 @@ export default defineComponent({
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-divider direction="vertical" />
|
||||
|
||||
<el-tooltip effect="dark" content="密度" placement="top">
|
||||
<el-dropdown v-slots={dropdown} trigger="click">
|
||||
<CollapseIcon class={["w-[16px]", iconClass.value]} />
|
||||
@@ -148,11 +243,69 @@ export default defineComponent({
|
||||
</el-tooltip>
|
||||
<el-divider direction="vertical" />
|
||||
|
||||
<el-popover v-slots={reference} width="200" trigger="click">
|
||||
<el-checkbox-group v-model={checkList.value}>
|
||||
<el-checkbox label="序号列" />
|
||||
<el-checkbox label="勾选列" />
|
||||
</el-checkbox-group>
|
||||
<el-popover
|
||||
v-slots={reference}
|
||||
popper-style={{ padding: 0 }}
|
||||
width="160"
|
||||
trigger="click"
|
||||
>
|
||||
<div class={[topClass.value]}>
|
||||
<el-checkbox
|
||||
class="!-mr-1"
|
||||
label="列展示"
|
||||
v-model={checkAll.value}
|
||||
indeterminate={isIndeterminate.value}
|
||||
onChange={value => handleCheckAllChange(value)}
|
||||
/>
|
||||
<el-button type="primary" link onClick={() => onReset()}>
|
||||
重置
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="pt-[6px] pl-[11px]">
|
||||
<el-checkbox-group
|
||||
v-model={checkedColumns.value}
|
||||
onChange={value => handleCheckedColumnsChange(value)}
|
||||
>
|
||||
<el-space
|
||||
direction="vertical"
|
||||
alignment="flex-start"
|
||||
size={0}
|
||||
>
|
||||
{checkColumnList.map(item => {
|
||||
return (
|
||||
<div class="flex items-center">
|
||||
<DragIcon
|
||||
class={[
|
||||
"drag-btn w-[16px] mr-2",
|
||||
isFixedColumn(item)
|
||||
? "!cursor-no-drop"
|
||||
: "!cursor-grab"
|
||||
]}
|
||||
onMouseenter={(event: {
|
||||
preventDefault: () => void;
|
||||
}) => rowDrop(event)}
|
||||
/>
|
||||
<el-checkbox
|
||||
key={item}
|
||||
label={item}
|
||||
onChange={value =>
|
||||
handleCheckColumnListChange(value, item)
|
||||
}
|
||||
>
|
||||
<span
|
||||
title={item}
|
||||
class="inline-block w-[120px] truncate hover:text-text_color_primary"
|
||||
>
|
||||
{item}
|
||||
</span>
|
||||
</el-checkbox>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</el-space>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
|
||||
@@ -175,7 +328,10 @@ export default defineComponent({
|
||||
content="列设置"
|
||||
/>
|
||||
</div>
|
||||
{slots.default({ size: size.value, checkList: checkList.value })}
|
||||
{slots.default({
|
||||
size: size.value,
|
||||
dynamicColumns: dynamicColumns.value
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
1
src/components/RePureTableBar/src/svg/drag.svg
Normal file
1
src/components/RePureTableBar/src/svg/drag.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="32" height="32" fill="currentColor" aria-hidden="true" data-icon="holder" viewBox="64 64 896 896"><path d="M300 276.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zm0 284a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 228a56 56 0 1 0 112 0 56 56 0 0 0-112 0zm0 284a56 56 0 1 0 112 0 56 56 0 0 0-112 0zM300 844.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 796a56 56 0 1 0 112 0 56 56 0 0 0-112 0z"/></svg>
|
||||
|
After Width: | Height: | Size: 398 B |
@@ -1,6 +1,7 @@
|
||||
.qrcode {
|
||||
&--disabled {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
background: rgb(255 255 255 / 95%);
|
||||
|
||||
& > div {
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
defineComponent
|
||||
} from "vue";
|
||||
import "./index.scss";
|
||||
import { propTypes } from "@/utils/propTypes";
|
||||
import propTypes from "@/utils/propTypes";
|
||||
import { isString, cloneDeep } from "@pureadmin/utils";
|
||||
import QRCode, { QRCodeRenderersOptions } from "qrcode";
|
||||
import RefreshRight from "@iconify-icons/ep/refresh-right";
|
||||
|
||||
8
src/components/ReSegmented/index.ts
Normal file
8
src/components/ReSegmented/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import reSegmented from "./src/index";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
|
||||
/** 分段控制器组件 */
|
||||
export const ReSegmented = withInstall(reSegmented);
|
||||
|
||||
export default ReSegmented;
|
||||
export type { OptionsType } from "./src/type";
|
||||
78
src/components/ReSegmented/src/index.css
Normal file
78
src/components/ReSegmented/src/index.css
Normal file
@@ -0,0 +1,78 @@
|
||||
.pure-segmented {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
padding: 2px;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
background-color: rgb(0 0 0 / 4%);
|
||||
border-radius: 2px;
|
||||
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.pure-segmented-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pure-segmented-item-selected {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
padding: 4px 0;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 8px -2px rgb(0 0 0 / 5%), 0 1px 4px -1px rgb(0 0 0 / 7%),
|
||||
0 0 1px rgb(0 0 0 / 7%);
|
||||
transition: transform 0.5s cubic-bezier(0.645, 0.045, 0.355, 1),
|
||||
width 0.5s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
will-change: transform, width;
|
||||
}
|
||||
|
||||
.pure-segmented-item {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
}
|
||||
|
||||
.pure-segmented-item > div {
|
||||
min-height: 28px;
|
||||
line-height: 28px;
|
||||
padding: 0 11px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.pure-segmented-item > input {
|
||||
position: absolute;
|
||||
inset-block-start: 0;
|
||||
inset-inline-start: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pure-segmented-item-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pure-segmented-item-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.pure-segmented-item-disabled {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
150
src/components/ReSegmented/src/index.tsx
Normal file
150
src/components/ReSegmented/src/index.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import "./index.css";
|
||||
import {
|
||||
h,
|
||||
ref,
|
||||
watch,
|
||||
nextTick,
|
||||
defineComponent,
|
||||
getCurrentInstance
|
||||
} from "vue";
|
||||
import type { OptionsType } from "./type";
|
||||
import { isFunction, useDark } from "@pureadmin/utils";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
|
||||
const props = {
|
||||
options: {
|
||||
type: Array<OptionsType>,
|
||||
default: () => []
|
||||
},
|
||||
/** 默认选中,按照第一个索引为 `0` 的模式 */
|
||||
defaultValue: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: "ReSegmented",
|
||||
props,
|
||||
emits: ["change"],
|
||||
setup(props, { emit }) {
|
||||
const width = ref(0);
|
||||
const translateX = ref(0);
|
||||
const { isDark } = useDark();
|
||||
const initStatus = ref(false);
|
||||
const curMouseActive = ref(-1);
|
||||
const segmentedItembg = ref("");
|
||||
const instance = getCurrentInstance()!;
|
||||
const curIndex = ref(props.defaultValue);
|
||||
|
||||
function handleChange({ option, index }, event: Event) {
|
||||
if (option.disabled) return;
|
||||
event.preventDefault();
|
||||
curIndex.value = index;
|
||||
segmentedItembg.value = "";
|
||||
emit("change", { index, option });
|
||||
}
|
||||
|
||||
function handleMouseenter({ option, index }, event: Event) {
|
||||
event.preventDefault();
|
||||
curMouseActive.value = index;
|
||||
if (option.disabled || curIndex.value === index) {
|
||||
segmentedItembg.value = "";
|
||||
} else {
|
||||
segmentedItembg.value = isDark.value
|
||||
? "#1f1f1f"
|
||||
: "rgba(0, 0, 0, 0.06)";
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseleave(_, event: Event) {
|
||||
event.preventDefault();
|
||||
curMouseActive.value = -1;
|
||||
}
|
||||
|
||||
function handleInit(index = curIndex.value) {
|
||||
nextTick(() => {
|
||||
const curLabelRef = instance?.proxy?.$refs[`labelRef${index}`] as ElRef;
|
||||
width.value = curLabelRef.clientWidth;
|
||||
translateX.value = curLabelRef.offsetLeft;
|
||||
initStatus.value = true;
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => curIndex.value,
|
||||
index => {
|
||||
nextTick(() => {
|
||||
handleInit(index);
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
const rendLabel = () => {
|
||||
return props.options.map((option, index) => {
|
||||
return (
|
||||
<label
|
||||
ref={`labelRef${index}`}
|
||||
class={[
|
||||
"pure-segmented-item",
|
||||
option?.disabled && "pure-segmented-item-disabled"
|
||||
]}
|
||||
style={{
|
||||
background:
|
||||
curMouseActive.value === index ? segmentedItembg.value : "",
|
||||
color:
|
||||
!option.disabled &&
|
||||
(curIndex.value === index || curMouseActive.value === index)
|
||||
? isDark.value
|
||||
? "rgba(255, 255, 255, 0.85)"
|
||||
: "rgba(0,0,0,.88)"
|
||||
: ""
|
||||
}}
|
||||
onMouseenter={event => handleMouseenter({ option, index }, event)}
|
||||
onMouseleave={event => handleMouseleave({ option, index }, event)}
|
||||
onClick={event => handleChange({ option, index }, event)}
|
||||
>
|
||||
<input type="radio" name="segmented" />
|
||||
<div class="pure-segmented-item-label">
|
||||
{option.icon && !isFunction(option.label) ? (
|
||||
<span
|
||||
class="pure-segmented-item-icon"
|
||||
style={{ marginRight: option.label ? "6px" : 0 }}
|
||||
>
|
||||
{h(useRenderIcon(option.icon))}
|
||||
</span>
|
||||
) : null}
|
||||
{option.label ? (
|
||||
isFunction(option.label) ? (
|
||||
h(option.label)
|
||||
) : (
|
||||
<span>{option.label}</span>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return () => (
|
||||
<div class="pure-segmented">
|
||||
<div class="pure-segmented-group">
|
||||
<div
|
||||
class="pure-segmented-item-selected"
|
||||
style={{
|
||||
width: `${width.value}px`,
|
||||
transform: `translateX(${translateX.value}px)`,
|
||||
display: initStatus.value ? "block" : "none"
|
||||
}}
|
||||
></div>
|
||||
{rendLabel()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
15
src/components/ReSegmented/src/type.ts
Normal file
15
src/components/ReSegmented/src/type.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { VNode, Component } from "vue";
|
||||
|
||||
export interface OptionsType {
|
||||
/** 文字 */
|
||||
label?: string | (() => VNode | Component);
|
||||
/**
|
||||
* @description 图标,采用平台内置的 `useRenderIcon` 函数渲染
|
||||
* @see {@link 用法参考 https://yiming_chang.gitee.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
|
||||
*/
|
||||
icon?: string | Component;
|
||||
/** 值 */
|
||||
value?: string | number;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
}
|
||||
@@ -8,42 +8,48 @@ $--element-tree-line-width: 1px !default;
|
||||
}
|
||||
|
||||
.element-tree-node-label-wrapper {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.element-tree-node-label {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.element-tree-node-line-ver {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
height: 100%;
|
||||
border-left: $--element-tree-line-width $--element-tree-line-style
|
||||
$--element-tree-line-color;
|
||||
|
||||
&.last-node-line {
|
||||
border-left: $--element-tree-line-width $--element-tree-line-style
|
||||
transparent;
|
||||
}
|
||||
|
||||
&.last-node-isLeaf-line {
|
||||
height: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.element-tree-node-line-hor {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
display: block;
|
||||
height: 0;
|
||||
border-bottom: $--element-tree-line-width $--element-tree-line-style
|
||||
$--element-tree-line-color;
|
||||
}
|
||||
|
||||
.element-tree-node-label-line {
|
||||
flex: 1;
|
||||
border-top: $--element-tree-line-width $--element-tree-line-style
|
||||
$--element-tree-line-color;
|
||||
align-self: center;
|
||||
margin: 0 10px;
|
||||
border-top: $--element-tree-line-width $--element-tree-line-style
|
||||
$--element-tree-line-color;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import TypeIt from "typeit";
|
||||
export default defineComponent({
|
||||
name: "TypeIt",
|
||||
props: {
|
||||
/** 打字速度,以每一步之间的毫秒数为单位 */
|
||||
/** 打字速度,以每一步之间的毫秒数为单位,默认`200` */
|
||||
speed: {
|
||||
type: Number,
|
||||
default: 200
|
||||
|
||||
@@ -49,4 +49,7 @@ export const getServerConfig = async (app: App): Promise<undefined> => {
|
||||
});
|
||||
};
|
||||
|
||||
export { getConfig, setConfig };
|
||||
/** 本地响应式存储的命名空间 */
|
||||
const responsiveStorageNameSpace = () => getConfig().ResponsiveStorageNameSpace;
|
||||
|
||||
export { getConfig, setConfig, responsiveStorageNameSpace };
|
||||
|
||||
@@ -130,16 +130,16 @@ const transitionMain = defineComponent({
|
||||
|
||||
<style scoped>
|
||||
.app-main {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.app-main-nofixed-header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
|
||||
@@ -121,28 +121,28 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
|
||||
overflow: hidden;
|
||||
|
||||
.hamburger-container {
|
||||
line-height: 48px;
|
||||
height: 100%;
|
||||
float: left;
|
||||
height: 100%;
|
||||
line-height: 48px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vertical-header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 280px;
|
||||
height: 48px;
|
||||
align-items: center;
|
||||
color: #000000d9;
|
||||
justify-content: flex-end;
|
||||
|
||||
.el-dropdown-link {
|
||||
height: 48px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
cursor: pointer;
|
||||
height: 48px;
|
||||
padding: 10px;
|
||||
color: #000000d9;
|
||||
cursor: pointer;
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
@@ -182,9 +182,9 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
|
||||
max-width: 120px;
|
||||
|
||||
::v-deep(.el-dropdown-menu__item) {
|
||||
min-width: 100%;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -46,8 +46,8 @@ notices.value.map(v => (noticesNum.value += v.list.length));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 48px;
|
||||
width: 60px;
|
||||
height: 48px;
|
||||
cursor: pointer;
|
||||
|
||||
.header-notice-icon {
|
||||
@@ -59,7 +59,7 @@ notices.value.map(v => (noticesNum.value += v.list.length));
|
||||
width: 330px;
|
||||
|
||||
.noticeList-container {
|
||||
padding: 15px 24px 0 24px;
|
||||
padding: 15px 24px 0;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__header) {
|
||||
@@ -71,7 +71,7 @@ notices.value.map(v => (noticesNum.value += v.list.length));
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav-wrap) {
|
||||
padding: 0 36px 0 36px;
|
||||
padding: 0 36px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { ListItem } from "./data";
|
||||
import { ref, PropType, nextTick } from "vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { deviceDetection } from "@pureadmin/utils";
|
||||
|
||||
const props = defineProps({
|
||||
noticeItem: {
|
||||
@@ -15,6 +16,7 @@ const titleTooltip = ref(false);
|
||||
const descriptionRef = ref(null);
|
||||
const descriptionTooltip = ref(false);
|
||||
const { tooltipEffect } = useNav();
|
||||
const isMobile = deviceDetection();
|
||||
|
||||
function hoverTitle() {
|
||||
nextTick(() => {
|
||||
@@ -63,6 +65,7 @@ function hoverDescription(event, description) {
|
||||
:disabled="!titleTooltip"
|
||||
:content="props.noticeItem.title"
|
||||
placement="top-start"
|
||||
:enterable="!isMobile"
|
||||
>
|
||||
<div
|
||||
ref="titleRef"
|
||||
@@ -115,6 +118,7 @@ function hoverDescription(event, description) {
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
padding: 12px 0;
|
||||
|
||||
// border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.notice-container-avatar {
|
||||
@@ -124,15 +128,15 @@ function hoverDescription(event, description) {
|
||||
|
||||
.notice-container-text {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
|
||||
.notice-text-title {
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 1.5715;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -140,8 +144,8 @@ function hoverDescription(event, description) {
|
||||
flex: 1;
|
||||
width: 200px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.notice-title-extra {
|
||||
@@ -159,8 +163,8 @@ function hoverDescription(event, description) {
|
||||
|
||||
.notice-text-description {
|
||||
display: -webkit-box;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
@@ -60,9 +60,9 @@ emitter.on("openPanel", () => {
|
||||
|
||||
<style>
|
||||
.showright-panel {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: calc(100% - 15px);
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -71,23 +71,23 @@ emitter.on("openPanel", () => {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
background: rgb(0 0 0 / 20%);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
width: 100%;
|
||||
max-width: 315px;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.05);
|
||||
z-index: 40000;
|
||||
width: 100%;
|
||||
max-width: 315px;
|
||||
height: 100vh;
|
||||
box-shadow: 0 0 15px 0 rgb(0 0 0 / 5%);
|
||||
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||
transform: translate(100%);
|
||||
z-index: 40000;
|
||||
}
|
||||
|
||||
.show {
|
||||
@@ -95,9 +95,9 @@ emitter.on("openPanel", () => {
|
||||
|
||||
.right-panel-background {
|
||||
z-index: 20000;
|
||||
opacity: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
@@ -106,20 +106,20 @@ emitter.on("openPanel", () => {
|
||||
}
|
||||
|
||||
.handle-button {
|
||||
position: absolute;
|
||||
top: 45%;
|
||||
left: -48px;
|
||||
z-index: 0;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: absolute;
|
||||
left: -48px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
border-radius: 6px 0 0 6px !important;
|
||||
z-index: 0;
|
||||
line-height: 48px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
line-height: 48px;
|
||||
top: 45%;
|
||||
background: rgb(24, 144, 255);
|
||||
background: rgb(24 144 255);
|
||||
border-radius: 6px 0 0 6px !important;
|
||||
|
||||
i {
|
||||
font-size: 24px;
|
||||
@@ -128,24 +128,24 @@ emitter.on("openPanel", () => {
|
||||
}
|
||||
|
||||
.right-panel-items {
|
||||
margin-top: 60px;
|
||||
height: calc(100vh - 60px);
|
||||
margin-top: 60px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.project-configuration {
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
position: fixed;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
top: 15px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-divider--horizontal) {
|
||||
width: 90%;
|
||||
margin: 20px auto 0 auto;
|
||||
margin: 20px auto 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useFullscreen } from "@vueuse/core";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="screen-full w-[36px] h-[48px] flex-ac cursor-pointer navbar-bg-hover"
|
||||
@click="toggle"
|
||||
>
|
||||
<FontIcon
|
||||
:title="
|
||||
isFullscreen ? t('buttons.hsexitfullscreen') : t('buttons.hsfullscreen')
|
||||
"
|
||||
:icon="isFullscreen ? 'team-iconexit-fullscreen' : 'team-iconfullscreen'"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -3,11 +3,10 @@ import { useRouter } from "vue-router";
|
||||
import { cloneDeep } from "@pureadmin/utils";
|
||||
import SearchResult from "./SearchResult.vue";
|
||||
import SearchFooter from "./SearchFooter.vue";
|
||||
import { deleteChildren } from "@/utils/tree";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { ref, computed, shallowRef } from "vue";
|
||||
import { useDebounceFn, onKeyStroke } from "@vueuse/core";
|
||||
import { ref, watch, computed, nextTick, shallowRef } from "vue";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import Search from "@iconify-icons/ep/search";
|
||||
|
||||
@@ -33,7 +32,7 @@ const handleSearch = useDebounceFn(search, 300);
|
||||
|
||||
/** 菜单树形结构 */
|
||||
const menusData = computed(() => {
|
||||
return deleteChildren(cloneDeep(usePermissionStoreHook().wholeMenus));
|
||||
return cloneDeep(usePermissionStoreHook().wholeMenus);
|
||||
});
|
||||
|
||||
const show = computed({
|
||||
@@ -45,14 +44,6 @@ const show = computed({
|
||||
}
|
||||
});
|
||||
|
||||
watch(show, async val => {
|
||||
if (val) {
|
||||
/** 自动聚焦 */
|
||||
await nextTick();
|
||||
inputRef.value?.focus();
|
||||
}
|
||||
});
|
||||
|
||||
/** 将菜单树形结构扁平化为一维数组,用于菜单查询 */
|
||||
function flatTree(arr) {
|
||||
const res = [];
|
||||
@@ -136,9 +127,11 @@ onKeyStroke("ArrowDown", handleDown);
|
||||
<template>
|
||||
<el-dialog
|
||||
top="5vh"
|
||||
:width="device === 'mobile' ? '80vw' : '50vw'"
|
||||
v-model="show"
|
||||
:width="device === 'mobile' ? '80vw' : '50vw'"
|
||||
:before-close="handleClose"
|
||||
@opened="inputRef.focus()"
|
||||
@closed="inputRef.blur()"
|
||||
>
|
||||
<el-input
|
||||
ref="inputRef"
|
||||
|
||||
@@ -84,11 +84,11 @@ function handleTo() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 56px;
|
||||
margin-top: 8px;
|
||||
padding: 14px;
|
||||
border-radius: 4px;
|
||||
margin-top: 8px;
|
||||
cursor: pointer;
|
||||
border: 0.1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s;
|
||||
|
||||
&-title {
|
||||
|
||||
@@ -418,35 +418,35 @@ onBeforeMount(() => {
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.pure-datatheme {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
padding-top: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pure-theme {
|
||||
margin-top: 25px;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
margin-top: 25px;
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
width: 18%;
|
||||
height: 45px;
|
||||
background: #f0f2f5;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
background: #f0f2f5;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 2.5px 0 rgb(0 0 0 / 18%);
|
||||
|
||||
@@ -459,13 +459,13 @@ onBeforeMount(() => {
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 70%;
|
||||
height: 30%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 70%;
|
||||
height: 30%;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 1px #888;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -491,13 +491,13 @@ onBeforeMount(() => {
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 30%;
|
||||
height: 70%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 30%;
|
||||
height: 70%;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 1px #888;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -505,11 +505,11 @@ onBeforeMount(() => {
|
||||
}
|
||||
|
||||
.theme-color {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
@@ -519,8 +519,8 @@ onBeforeMount(() => {
|
||||
margin-right: 8px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
|
||||
&:nth-child(2) {
|
||||
border: 1px solid #ddd;
|
||||
|
||||
@@ -12,12 +12,6 @@ const router = useRouter();
|
||||
const routes: any = router.options.routes;
|
||||
const multiTags: any = useMultiTagsStoreHook().multiTags;
|
||||
|
||||
const isDashboard = (route: RouteLocationMatched): boolean | string => {
|
||||
const name = route && (route.name as string);
|
||||
if (!name) return false;
|
||||
return name.trim().toLocaleLowerCase() === "Welcome".toLocaleLowerCase();
|
||||
};
|
||||
|
||||
const getBreadcrumb = (): void => {
|
||||
// 当前路由信息
|
||||
let currentRoute;
|
||||
@@ -35,28 +29,24 @@ const getBreadcrumb = (): void => {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
currentRoute = findRouteByPath(router.currentRoute.value.path, multiTags);
|
||||
currentRoute = findRouteByPath(router.currentRoute.value.path, routes);
|
||||
}
|
||||
|
||||
// 当前路由的父级路径组成的数组
|
||||
const parentRoutes = getParentPaths(router.currentRoute.value.path, routes);
|
||||
const parentRoutes = getParentPaths(
|
||||
router.currentRoute.value.name as string,
|
||||
routes,
|
||||
"name"
|
||||
);
|
||||
// 存放组成面包屑的数组
|
||||
let matched = [];
|
||||
const matched = [];
|
||||
|
||||
// 获取每个父级路径对应的路由信息
|
||||
parentRoutes.forEach(path => {
|
||||
if (path !== "/") matched.push(findRouteByPath(path, routes));
|
||||
});
|
||||
|
||||
if (currentRoute?.path !== "/welcome") matched.push(currentRoute);
|
||||
|
||||
if (!isDashboard(matched[0])) {
|
||||
matched = [
|
||||
{
|
||||
path: "/welcome",
|
||||
parentPath: "/",
|
||||
meta: { title: "menus.hshome" }
|
||||
} as unknown as RouteLocationMatched
|
||||
].concat(matched);
|
||||
}
|
||||
matched.push(currentRoute);
|
||||
|
||||
matched.forEach((item, index) => {
|
||||
if (currentRoute?.query || currentRoute?.params) return;
|
||||
@@ -91,6 +81,9 @@ watch(
|
||||
() => route.path,
|
||||
() => {
|
||||
getBreadcrumb();
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
20
src/layout/components/sidebar/extraIcon.vue
Normal file
20
src/layout/components/sidebar/extraIcon.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { toRaw } from "vue";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
|
||||
const props = defineProps({
|
||||
extraIcon: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.extraIcon" class="flex justify-center items-center">
|
||||
<component
|
||||
:is="useRenderIcon(toRaw(props.extraIcon))"
|
||||
class="w-[30px] h-[30px]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -19,7 +19,7 @@ const {
|
||||
title,
|
||||
routers,
|
||||
logout,
|
||||
backHome,
|
||||
backTopMenu,
|
||||
onPanel,
|
||||
menuSelect,
|
||||
username,
|
||||
@@ -45,9 +45,9 @@ watch(
|
||||
v-loading="usePermissionStoreHook().wholeMenus.length === 0"
|
||||
class="horizontal-header"
|
||||
>
|
||||
<div class="horizontal-header-left" @click="backHome">
|
||||
<FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
|
||||
<h4>{{ title }}</h4>
|
||||
<div class="horizontal-header-left" @click="backTopMenu">
|
||||
<img src="/logo.svg" alt="logo" />
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
<el-menu
|
||||
router
|
||||
@@ -156,9 +156,9 @@ watch(
|
||||
max-width: 120px;
|
||||
|
||||
::v-deep(.el-dropdown-menu__item) {
|
||||
min-width: 100%;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,7 +24,7 @@ const iconClass = computed(() => {
|
||||
"align-middle",
|
||||
"text-primary",
|
||||
"cursor-pointer",
|
||||
"duration-[360ms]",
|
||||
"duration-[100ms]",
|
||||
"hover:text-primary",
|
||||
"dark:hover:!text-white"
|
||||
];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { getTopMenu } from "@/router/utils";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
|
||||
const props = defineProps({
|
||||
@@ -6,6 +7,7 @@ const props = defineProps({
|
||||
});
|
||||
|
||||
const { title } = useNav();
|
||||
const topPath = getTopMenu().path;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -16,9 +18,9 @@ const { title } = useNav();
|
||||
key="props.collapse"
|
||||
:title="title"
|
||||
class="sidebar-logo-link"
|
||||
to="/"
|
||||
:to="topPath"
|
||||
>
|
||||
<FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
|
||||
<img src="/logo.svg" alt="logo" />
|
||||
<span class="sidebar-title">{{ title }}</span>
|
||||
</router-link>
|
||||
<router-link
|
||||
@@ -26,9 +28,9 @@ const { title } = useNav();
|
||||
key="expand"
|
||||
:title="title"
|
||||
class="sidebar-logo-link"
|
||||
to="/"
|
||||
:to="topPath"
|
||||
>
|
||||
<FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
|
||||
<img src="/logo.svg" alt="logo" />
|
||||
<span class="sidebar-title">{{ title }}</span>
|
||||
</router-link>
|
||||
</transition>
|
||||
@@ -40,34 +42,30 @@ const { title } = useNav();
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
.sidebar-logo-link {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin-top: 5px;
|
||||
|
||||
img {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
display: block;
|
||||
width: 160px;
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
margin: 2px 0 0 12px;
|
||||
overflow: hidden;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 32px;
|
||||
color: $subMenuActiveText;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
color: #1890ff;
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
margin-top: 10px;
|
||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.collapses {
|
||||
.sidebar-logo {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import extraIcon from "./extraIcon.vue";
|
||||
import Search from "../search/index.vue";
|
||||
import Notice from "../notice/index.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
@@ -26,6 +27,7 @@ const {
|
||||
menuSelect,
|
||||
resolvePath,
|
||||
username,
|
||||
getDivStyle,
|
||||
avatarsStyle,
|
||||
getDropdownItemStyle,
|
||||
getDropdownItemClass
|
||||
@@ -85,15 +87,12 @@ watch(
|
||||
:is="useRenderIcon(route.meta && toRaw(route.meta.icon))"
|
||||
/>
|
||||
</div>
|
||||
<span class="select-none">{{ transformI18n(route.meta.title) }}</span>
|
||||
<FontIcon
|
||||
v-if="route.meta.extraIcon"
|
||||
width="30px"
|
||||
height="30px"
|
||||
style="position: absolute; right: 10px"
|
||||
:icon="route.meta.extraIcon.name"
|
||||
:svg="route.meta.extraIcon.svg ? true : false"
|
||||
/>
|
||||
<div :style="getDivStyle">
|
||||
<span class="select-none">
|
||||
{{ transformI18n(route.meta.title) }}
|
||||
</span>
|
||||
<extraIcon :extraIcon="route.meta.extraIcon" />
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
@@ -189,9 +188,9 @@ watch(
|
||||
max-width: 120px;
|
||||
|
||||
::v-deep(.el-dropdown-menu__item) {
|
||||
min-width: 100%;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import path from "path";
|
||||
import { getConfig } from "@/config";
|
||||
import { childrenType } from "../../types";
|
||||
import { menuType } from "../../types";
|
||||
import extraIcon from "./extraIcon.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import { ref, toRaw, PropType, nextTick, computed, CSSProperties } from "vue";
|
||||
|
||||
import ArrowUp from "@iconify-icons/ep/arrow-up";
|
||||
import EpArrowDown from "@iconify-icons/ep/arrow-down";
|
||||
import ArrowLeft from "@iconify-icons/ep/arrow-left";
|
||||
import ArrowRight from "@iconify-icons/ep/arrow-right";
|
||||
import ArrowUp from "@iconify-icons/ep/arrow-up-bold";
|
||||
import EpArrowDown from "@iconify-icons/ep/arrow-down-bold";
|
||||
import ArrowLeft from "@iconify-icons/ep/arrow-left-bold";
|
||||
import ArrowRight from "@iconify-icons/ep/arrow-right-bold";
|
||||
|
||||
const { layout, isCollapse, tooltipEffect } = useNav();
|
||||
const { layout, isCollapse, tooltipEffect, getDivStyle } = useNav();
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object as PropType<childrenType>
|
||||
type: Object as PropType<menuType>
|
||||
},
|
||||
isNest: {
|
||||
type: Boolean,
|
||||
@@ -28,17 +29,11 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
const getExtraIconStyle = computed((): CSSProperties => {
|
||||
if (!isCollapse.value) {
|
||||
return {
|
||||
position: "absolute",
|
||||
right: "10px"
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
position: "static"
|
||||
};
|
||||
}
|
||||
const getSpanStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
width: "100%",
|
||||
textAlign: "center"
|
||||
};
|
||||
});
|
||||
|
||||
const getNoDropdownStyle = computed((): CSSProperties => {
|
||||
@@ -48,16 +43,6 @@ const getNoDropdownStyle = computed((): CSSProperties => {
|
||||
};
|
||||
});
|
||||
|
||||
const getDivStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
width: !isCollapse.value ? "" : "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
overflow: "hidden"
|
||||
};
|
||||
});
|
||||
|
||||
const getMenuTextStyle = computed(() => {
|
||||
return {
|
||||
overflow: "hidden",
|
||||
@@ -66,19 +51,54 @@ const getMenuTextStyle = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const getSubTextStyle = computed((): CSSProperties => {
|
||||
const getsubMenuIconStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
width: !isCollapse.value ? "210px" : "",
|
||||
display: "inline-block",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
margin:
|
||||
layout.value === "horizontal"
|
||||
? "0 5px 0 0"
|
||||
: isCollapse.value
|
||||
? "0 auto"
|
||||
: "0 5px 0 0"
|
||||
};
|
||||
});
|
||||
|
||||
const getSpanStyle = computed(() => {
|
||||
return {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
const getSubTextStyle = computed((): CSSProperties => {
|
||||
if (!isCollapse.value) {
|
||||
return {
|
||||
width: "210px",
|
||||
display: "inline-block",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
width: ""
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const getSubMenuDivStyle = computed((): any => {
|
||||
return item => {
|
||||
return !isCollapse.value
|
||||
? {
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
overflow: "hidden"
|
||||
}
|
||||
: {
|
||||
width: "100%",
|
||||
textAlign:
|
||||
item?.parentId === null
|
||||
? "center"
|
||||
: layout.value === "mix" && item?.pathList?.length === 2
|
||||
? "center"
|
||||
: ""
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
@@ -92,7 +112,7 @@ const expandCloseIcon = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const onlyOneChild: childrenType = ref(null);
|
||||
const onlyOneChild: menuType = ref(null);
|
||||
// 存放菜单是否存在showTooltip属性标识
|
||||
const hoverMenuMap = new WeakMap();
|
||||
// 存储菜单文本dom元素
|
||||
@@ -115,10 +135,21 @@ function hoverMenu(key) {
|
||||
});
|
||||
}
|
||||
|
||||
function hasOneShowingChild(
|
||||
children: childrenType[] = [],
|
||||
parent: childrenType
|
||||
) {
|
||||
// 左侧菜单折叠后,当菜单没有图标时只显示第一个文字并加上省略号
|
||||
function overflowSlice(text, item?: any) {
|
||||
const newText =
|
||||
(text?.length > 1 ? text.toString().slice(0, 1) : text) + "...";
|
||||
if (item && !(isCollapse.value && item?.parentId === null)) {
|
||||
return layout.value === "mix" &&
|
||||
item?.pathList?.length === 2 &&
|
||||
isCollapse.value
|
||||
? newText
|
||||
: text;
|
||||
}
|
||||
return newText;
|
||||
}
|
||||
|
||||
function hasOneShowingChild(children: menuType[] = [], parent: menuType) {
|
||||
const showingChildren = children.filter((item: any) => {
|
||||
onlyOneChild.value = item;
|
||||
return true;
|
||||
@@ -151,84 +182,78 @@ function resolvePath(routePath) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template
|
||||
<el-menu-item
|
||||
v-if="
|
||||
hasOneShowingChild(props.item.children, props.item) &&
|
||||
(!onlyOneChild.children || onlyOneChild.noShowingChildren)
|
||||
"
|
||||
:index="resolvePath(onlyOneChild.path)"
|
||||
:class="{ 'submenu-title-noDropdown': !isNest }"
|
||||
:style="getNoDropdownStyle"
|
||||
>
|
||||
<el-menu-item
|
||||
:index="resolvePath(onlyOneChild.path)"
|
||||
:class="{ 'submenu-title-noDropdown': !isNest }"
|
||||
:style="getNoDropdownStyle"
|
||||
<div
|
||||
v-if="toRaw(props.item.meta.icon)"
|
||||
class="sub-menu-icon"
|
||||
:style="getsubMenuIconStyle"
|
||||
>
|
||||
<div class="sub-menu-icon" v-if="toRaw(props.item.meta.icon)">
|
||||
<component
|
||||
:is="
|
||||
useRenderIcon(
|
||||
toRaw(onlyOneChild.meta.icon) ||
|
||||
(props.item.meta && toRaw(props.item.meta.icon))
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
isCollapse &&
|
||||
layout === 'vertical' &&
|
||||
props.item?.pathList?.length === 1
|
||||
<component
|
||||
:is="
|
||||
useRenderIcon(
|
||||
toRaw(onlyOneChild.meta.icon) ||
|
||||
(props.item.meta && toRaw(props.item.meta.icon))
|
||||
)
|
||||
"
|
||||
:style="getDivStyle"
|
||||
>
|
||||
<span :style="getMenuTextStyle">
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
v-if="
|
||||
!props.item?.meta.icon &&
|
||||
isCollapse &&
|
||||
layout === 'vertical' &&
|
||||
props.item?.pathList?.length === 1
|
||||
"
|
||||
:style="getSpanStyle"
|
||||
>
|
||||
{{ overflowSlice(transformI18n(onlyOneChild.meta.title)) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="
|
||||
!onlyOneChild.meta.icon &&
|
||||
isCollapse &&
|
||||
layout === 'mix' &&
|
||||
props.item?.pathList?.length === 2
|
||||
"
|
||||
:style="getSpanStyle"
|
||||
>
|
||||
{{ overflowSlice(transformI18n(onlyOneChild.meta.title)) }}
|
||||
</span>
|
||||
<template #title>
|
||||
<div :style="getDivStyle">
|
||||
<span v-if="layout === 'horizontal'">
|
||||
{{ transformI18n(onlyOneChild.meta.title) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
isCollapse && layout === 'mix' && props.item?.pathList?.length === 2
|
||||
"
|
||||
:style="getDivStyle"
|
||||
>
|
||||
<span :style="getMenuTextStyle">
|
||||
{{ transformI18n(onlyOneChild.meta.title) }}
|
||||
</span>
|
||||
</div>
|
||||
<template #title>
|
||||
<div :style="getDivStyle">
|
||||
<span v-if="layout === 'horizontal'">
|
||||
<el-tooltip
|
||||
v-else
|
||||
placement="top"
|
||||
:effect="tooltipEffect"
|
||||
:offset="-10"
|
||||
:disabled="!onlyOneChild.showTooltip"
|
||||
>
|
||||
<template #content>
|
||||
{{ transformI18n(onlyOneChild.meta.title) }}
|
||||
</template>
|
||||
<span
|
||||
ref="menuTextRef"
|
||||
:style="getMenuTextStyle"
|
||||
@mouseover="hoverMenu(onlyOneChild)"
|
||||
>
|
||||
{{ transformI18n(onlyOneChild.meta.title) }}
|
||||
</span>
|
||||
<el-tooltip
|
||||
v-else
|
||||
placement="top"
|
||||
:effect="tooltipEffect"
|
||||
:offset="-10"
|
||||
:disabled="!onlyOneChild.showTooltip"
|
||||
>
|
||||
<template #content>
|
||||
{{ transformI18n(onlyOneChild.meta.title) }}
|
||||
</template>
|
||||
<span
|
||||
ref="menuTextRef"
|
||||
:style="getMenuTextStyle"
|
||||
@mouseover="hoverMenu(onlyOneChild)"
|
||||
>
|
||||
{{ transformI18n(onlyOneChild.meta.title) }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<FontIcon
|
||||
v-if="onlyOneChild.meta.extraIcon"
|
||||
width="30px"
|
||||
height="30px"
|
||||
:style="getExtraIconStyle"
|
||||
:icon="onlyOneChild.meta.extraIcon.name"
|
||||
:svg="onlyOneChild.meta.extraIcon.svg ? true : false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-tooltip>
|
||||
<extraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
|
||||
<el-sub-menu
|
||||
v-else
|
||||
@@ -237,7 +262,11 @@ function resolvePath(routePath) {
|
||||
:index="resolvePath(props.item.path)"
|
||||
>
|
||||
<template #title>
|
||||
<div v-if="toRaw(props.item.meta.icon)" class="sub-menu-icon">
|
||||
<div
|
||||
v-if="toRaw(props.item.meta.icon)"
|
||||
:style="getsubMenuIconStyle"
|
||||
class="sub-menu-icon"
|
||||
>
|
||||
<component
|
||||
:is="useRenderIcon(props.item.meta && toRaw(props.item.meta.icon))"
|
||||
/>
|
||||
@@ -245,34 +274,38 @@ function resolvePath(routePath) {
|
||||
<span v-if="layout === 'horizontal'">
|
||||
{{ transformI18n(props.item.meta.title) }}
|
||||
</span>
|
||||
<el-tooltip
|
||||
v-else
|
||||
placement="top"
|
||||
:effect="tooltipEffect"
|
||||
:offset="-10"
|
||||
:disabled="!props.item.showTooltip"
|
||||
<div
|
||||
:style="getSubMenuDivStyle(props.item)"
|
||||
v-if="
|
||||
!(
|
||||
isCollapse &&
|
||||
toRaw(props.item.meta.icon) &&
|
||||
props.item.parentId === null
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #content>
|
||||
{{ transformI18n(props.item.meta.title) }}
|
||||
</template>
|
||||
<div
|
||||
ref="menuTextRef"
|
||||
:style="getSubTextStyle"
|
||||
@mouseover="hoverMenu(props.item)"
|
||||
<el-tooltip
|
||||
v-if="layout !== 'horizontal'"
|
||||
placement="top"
|
||||
:effect="tooltipEffect"
|
||||
:offset="-10"
|
||||
:disabled="!props.item.showTooltip"
|
||||
>
|
||||
<span :style="getSpanStyle">
|
||||
<template #content>
|
||||
{{ transformI18n(props.item.meta.title) }}
|
||||
</template>
|
||||
<span
|
||||
ref="menuTextRef"
|
||||
:style="getSubTextStyle"
|
||||
@mouseover="hoverMenu(props.item)"
|
||||
>
|
||||
{{
|
||||
overflowSlice(transformI18n(props.item.meta.title), props.item)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<FontIcon
|
||||
v-if="props.item.meta.extraIcon"
|
||||
width="30px"
|
||||
height="30px"
|
||||
style="position: absolute; right: 10px"
|
||||
:icon="props.item.meta.extraIcon.name"
|
||||
:svg="props.item.meta.extraIcon.svg ? true : false"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<extraIcon v-if="!isCollapse" :extraIcon="props.item.meta.extraIcon" />
|
||||
</div>
|
||||
</template>
|
||||
<sidebar-item
|
||||
v-for="child in props.item.children"
|
||||
|
||||
@@ -6,14 +6,16 @@ import SidebarItem from "./sidebarItem.vue";
|
||||
import leftCollapse from "./leftCollapse.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { responsiveStorageNameSpace } from "@/config";
|
||||
import { ref, computed, watch, onBeforeMount } from "vue";
|
||||
import { findRouteByPath, getParentPaths } from "@/router/utils";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
|
||||
const route = useRoute();
|
||||
const showLogo = ref(
|
||||
storageLocal().getItem<StorageConfigs>("responsive-configure")?.showLogo ??
|
||||
true
|
||||
storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}configure`
|
||||
)?.showLogo ?? true
|
||||
);
|
||||
|
||||
const { routers, device, pureApp, isCollapse, menuSelect, toggleSideBar } =
|
||||
@@ -27,7 +29,12 @@ const menuData = computed(() => {
|
||||
: usePermissionStoreHook().wholeMenus;
|
||||
});
|
||||
|
||||
const loading = computed(() =>
|
||||
pureApp.layout === "mix" ? false : menuData.value.length === 0 ? true : false
|
||||
);
|
||||
|
||||
function getSubMenuData(path: string) {
|
||||
subMenuData.value = [];
|
||||
// path的上级路由组成的数组
|
||||
const parentPathArr = getParentPaths(
|
||||
path,
|
||||
@@ -53,6 +60,7 @@ onBeforeMount(() => {
|
||||
watch(
|
||||
() => [route.path, usePermissionStoreHook().wholeMenus],
|
||||
() => {
|
||||
if (route.path.includes("/redirect")) return;
|
||||
getSubMenuData(route.path);
|
||||
menuSelect(route.path, routers);
|
||||
}
|
||||
@@ -61,7 +69,7 @@ watch(
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-loading="menuData.length === 0"
|
||||
v-loading="loading"
|
||||
:class="['sidebar-container', showLogo ? 'has-logo' : '']"
|
||||
>
|
||||
<Logo v-if="showLogo" :collapse="isCollapse" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@keyframes scheduleInWidth {
|
||||
@keyframes schedule-in-width {
|
||||
from {
|
||||
width: 0;
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scheduleOutWidth {
|
||||
@keyframes schedule-out-width {
|
||||
from {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -39,41 +39,41 @@
|
||||
}
|
||||
|
||||
.tags-view {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-primary);
|
||||
background: #fff;
|
||||
position: relative;
|
||||
box-shadow: 0 0 1px #888;
|
||||
|
||||
.scroll-item {
|
||||
border-radius: 3px 3px 0 0;
|
||||
padding: 0 6px;
|
||||
box-shadow: 0 0 1px #888;
|
||||
position: relative;
|
||||
margin-right: 4px;
|
||||
height: 28px;
|
||||
display: inline-block;
|
||||
height: 28px;
|
||||
padding: 0 6px;
|
||||
margin-right: 4px;
|
||||
line-height: 28px;
|
||||
transition: all 0.4s;
|
||||
cursor: pointer;
|
||||
border-radius: 3px 3px 0 0;
|
||||
box-shadow: 0 0 1px #888;
|
||||
transition: all 0.4s;
|
||||
|
||||
.el-icon-close {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
font-size: 10px;
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: font-size 0.2s;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
&:hover {
|
||||
border-radius: 50%;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
background: #b4bccc;
|
||||
font-size: 13px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,24 +91,24 @@
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--el-text-color-primary);
|
||||
padding: 0 4px;
|
||||
color: var(--el-text-color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 5px 0;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
padding: 5px 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
.tab {
|
||||
position: relative;
|
||||
float: left;
|
||||
list-style: none;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
list-style: none;
|
||||
transition: transform 0.5s ease-in-out;
|
||||
|
||||
.scroll-item {
|
||||
@@ -123,29 +123,28 @@
|
||||
|
||||
/* 右键菜单 */
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
color: var(--el-text-color-primary);
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
color: var(--el-text-color-primary);
|
||||
white-space: nowrap;
|
||||
list-style-type: none;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
outline: 0;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
|
||||
|
||||
li {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 7px 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 7px 12px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
// background: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
@@ -159,11 +158,11 @@
|
||||
|
||||
.el-dropdown-menu {
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
display: block;
|
||||
@@ -184,6 +183,7 @@
|
||||
:deep(.el-dropdown-menu__item--divided) {
|
||||
margin: 1px 0;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item--divided::before {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -193,7 +193,6 @@
|
||||
}
|
||||
|
||||
.scroll-item.is-active {
|
||||
// background-color: var(--el-color-primary-light-9);
|
||||
position: relative;
|
||||
color: #fff;
|
||||
|
||||
@@ -213,16 +212,16 @@
|
||||
.arrow-left,
|
||||
.arrow-right,
|
||||
.arrow-down {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 38px;
|
||||
color: var(--el-text-color-primary);
|
||||
position: relative;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: translate(-50%, 50%);
|
||||
}
|
||||
}
|
||||
@@ -236,8 +235,8 @@
|
||||
}
|
||||
|
||||
.arrow-right {
|
||||
box-shadow: -5px 0 5px -6px #ccc;
|
||||
border-right: 0.5px solid #ccc;
|
||||
box-shadow: -5px 0 5px -6px #ccc;
|
||||
|
||||
&:hover {
|
||||
cursor: e-resize;
|
||||
@@ -255,8 +254,8 @@
|
||||
|
||||
/* 卡片模式下鼠标移出隐藏蓝色边框 */
|
||||
.card-out {
|
||||
border: none;
|
||||
color: #666;
|
||||
border: none;
|
||||
|
||||
a {
|
||||
color: #666;
|
||||
@@ -265,32 +264,32 @@
|
||||
|
||||
/* 灵动模式 */
|
||||
.schedule-active {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: var(--el-color-primary);
|
||||
}
|
||||
|
||||
/* 灵动模式下鼠标移入显示蓝色进度条 */
|
||||
.schedule-in {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: var(--el-color-primary);
|
||||
animation: scheduleInWidth 400ms ease-in;
|
||||
animation: schedule-in-width 200ms ease-in;
|
||||
}
|
||||
|
||||
/* 灵动模式下鼠标移出隐藏蓝色进度条 */
|
||||
.schedule-out {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: var(--el-color-primary);
|
||||
animation: scheduleOutWidth 400ms ease-in;
|
||||
animation: schedule-out-width 200ms ease-in;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ import { RouteConfigs } from "../../types";
|
||||
import { useTags } from "../../hooks/useTag";
|
||||
import { routerArrays } from "@/layout/types";
|
||||
import { isEqual, isAllEmpty } from "@pureadmin/utils";
|
||||
import { handleAliveRoute, getTopMenu } from "@/router/utils";
|
||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||
import { ref, watch, unref, nextTick, onBeforeMount } from "vue";
|
||||
import { handleAliveRoute, delAliveRoutes } from "@/router/utils";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { ref, watch, unref, toRaw, nextTick, onBeforeMount } from "vue";
|
||||
import { useResizeObserver, useDebounceFn, useFullscreen } from "@vueuse/core";
|
||||
|
||||
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
|
||||
@@ -50,6 +50,8 @@ const tabDom = ref();
|
||||
const containerDom = ref();
|
||||
const scrollbarDom = ref();
|
||||
const isShowArrow = ref(false);
|
||||
const topPath = getTopMenu().path;
|
||||
const { VITE_HIDE_HOME } = import.meta.env;
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
|
||||
const dynamicTagView = () => {
|
||||
@@ -65,7 +67,7 @@ const dynamicTagView = () => {
|
||||
moveToView(index);
|
||||
};
|
||||
|
||||
const moveToView = (index: number): void => {
|
||||
const moveToView = async (index: number): Promise<void> => {
|
||||
const tabNavPadding = 10;
|
||||
if (!instance.refs["dynamic" + index]) return;
|
||||
const tabItemEl = instance.refs["dynamic" + index][0];
|
||||
@@ -75,8 +77,13 @@ const moveToView = (index: number): void => {
|
||||
const scrollbarDomWidth = scrollbarDom.value
|
||||
? scrollbarDom.value?.offsetWidth
|
||||
: 0;
|
||||
|
||||
// 获取视图更新后dom
|
||||
await nextTick();
|
||||
|
||||
// 已有标签页总长度(包含溢出部分)
|
||||
const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
|
||||
|
||||
scrollbarDomWidth <= tabDomWidth
|
||||
? (isShowArrow.value = true)
|
||||
: (isShowArrow.value = false);
|
||||
@@ -160,13 +167,12 @@ function onFresh() {
|
||||
const { fullPath, query } = unref(route);
|
||||
router.replace({
|
||||
path: "/redirect" + fullPath,
|
||||
query: query
|
||||
query
|
||||
});
|
||||
handleAliveRoute(route as toRouteType, "refresh");
|
||||
}
|
||||
|
||||
function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||
// 存放被删除的缓存路由
|
||||
let delAliveRouteList = [];
|
||||
const valueIndex: number = multiTags.value.findIndex((item: any) => {
|
||||
if (item.query) {
|
||||
if (item.path === obj.path) {
|
||||
@@ -187,13 +193,17 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||
other?: boolean
|
||||
): void => {
|
||||
if (other) {
|
||||
useMultiTagsStoreHook().handleTags("equal", [routerArrays[0], obj]);
|
||||
useMultiTagsStoreHook().handleTags("equal", [
|
||||
VITE_HIDE_HOME === "false" ? routerArrays[0] : toRaw(getTopMenu()),
|
||||
obj
|
||||
]);
|
||||
} else {
|
||||
delAliveRouteList = useMultiTagsStoreHook().handleTags("splice", "", {
|
||||
useMultiTagsStoreHook().handleTags("splice", "", {
|
||||
startIndex,
|
||||
length
|
||||
}) as any;
|
||||
}
|
||||
dynamicTagView();
|
||||
};
|
||||
|
||||
if (tag === "other") {
|
||||
@@ -208,10 +218,6 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||
}
|
||||
const newRoute = useMultiTagsStoreHook().handleTags("slice");
|
||||
if (current === route.path) {
|
||||
// 删除缓存路由
|
||||
tag
|
||||
? delAliveRoutes(delAliveRouteList)
|
||||
: handleAliveRoute(route.matched, "delete");
|
||||
// 如果删除当前激活tag就自动切换到最后一个tag
|
||||
if (tag === "left") return;
|
||||
if (newRoute[0]?.query) {
|
||||
@@ -222,8 +228,6 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||
router.push({ path: newRoute[0].path });
|
||||
}
|
||||
} else {
|
||||
// 删除缓存路由
|
||||
tag ? delAliveRoutes(delAliveRouteList) : delAliveRoutes([obj]);
|
||||
if (!multiTags.value.length) return;
|
||||
if (multiTags.value.some(item => item.path === route.path)) return;
|
||||
if (newRoute[0]?.query) {
|
||||
@@ -238,6 +242,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||
|
||||
function deleteMenu(item, tag?: string) {
|
||||
deleteDynamicTag(item, item.path, tag);
|
||||
handleAliveRoute(route as toRouteType);
|
||||
}
|
||||
|
||||
function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
||||
@@ -284,7 +289,8 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
||||
startIndex: 1,
|
||||
length: multiTags.value.length
|
||||
});
|
||||
router.push("/welcome");
|
||||
router.push(topPath);
|
||||
handleAliveRoute(route as toRouteType);
|
||||
break;
|
||||
case 6:
|
||||
// 整体页面全屏
|
||||
@@ -340,7 +346,7 @@ function disabledMenus(value: boolean) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是首页,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
|
||||
/** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是顶级菜单,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
|
||||
function showMenuModel(
|
||||
currentPath: string,
|
||||
query: object = {},
|
||||
@@ -362,11 +368,11 @@ function showMenuModel(
|
||||
}
|
||||
|
||||
/**
|
||||
* currentIndex为1时,左侧的菜单是首页,则不显示关闭左侧标签页
|
||||
* currentIndex为1时,左侧的菜单顶级菜单,则不显示关闭左侧标签页
|
||||
* 如果currentIndex等于routeLength-1,右侧没有菜单,则不显示关闭右侧标签页
|
||||
*/
|
||||
if (currentIndex === 1 && routeLength !== 2) {
|
||||
// 左侧的菜单是首页,右侧存在别的菜单
|
||||
// 左侧的菜单是顶级菜单,右侧存在别的菜单
|
||||
tagsViews[2].show = false;
|
||||
Array.of(1, 3, 4, 5).forEach(v => {
|
||||
tagsViews[v].disabled = false;
|
||||
@@ -374,7 +380,7 @@ function showMenuModel(
|
||||
tagsViews[2].disabled = true;
|
||||
} else if (currentIndex === 1 && routeLength === 2) {
|
||||
disabledMenus(false);
|
||||
// 左侧的菜单是首页,右侧不存在别的菜单
|
||||
// 左侧的菜单是顶级菜单,右侧不存在别的菜单
|
||||
Array.of(2, 3, 4).forEach(v => {
|
||||
tagsViews[v].show = false;
|
||||
tagsViews[v].disabled = true;
|
||||
@@ -386,8 +392,8 @@ function showMenuModel(
|
||||
tagsViews[v].disabled = false;
|
||||
});
|
||||
tagsViews[3].disabled = true;
|
||||
} else if (currentIndex === 0 || currentPath === "/redirect/welcome") {
|
||||
// 当前路由为首页
|
||||
} else if (currentIndex === 0 || currentPath === `/redirect${topPath}`) {
|
||||
// 当前路由为顶级菜单
|
||||
disabledMenus(true);
|
||||
} else {
|
||||
disabledMenus(false);
|
||||
@@ -396,8 +402,8 @@ function showMenuModel(
|
||||
|
||||
function openMenu(tag, e) {
|
||||
closeMenu();
|
||||
if (tag.path === "/welcome") {
|
||||
// 右键菜单为首页,只显示刷新
|
||||
if (tag.path === topPath) {
|
||||
// 右键菜单为顶级菜单,只显示刷新
|
||||
showMenus(false);
|
||||
tagsViews[0].show = true;
|
||||
} else if (route.path !== tag.path && route.name !== tag.name) {
|
||||
@@ -490,6 +496,11 @@ watch([route], () => {
|
||||
dynamicTagView();
|
||||
});
|
||||
|
||||
watch(isFullscreen, () => {
|
||||
tagsViews[6].icon = Fullscreen;
|
||||
tagsViews[6].text = $t("buttons.hswholeFullScreen");
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
useResizeObserver(
|
||||
scrollbarDom,
|
||||
@@ -514,7 +525,7 @@ onMounted(() => {
|
||||
:class="[
|
||||
'scroll-item is-closable',
|
||||
linkIsActive(item),
|
||||
$route.path === item.path && showModel === 'card'
|
||||
route.path === item.path && showModel === 'card'
|
||||
? 'card-active'
|
||||
: ''
|
||||
]"
|
||||
@@ -598,5 +609,5 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./index.scss";
|
||||
@import url("./index.scss");
|
||||
</style>
|
||||
|
||||
@@ -56,15 +56,15 @@ onMounted(() => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.frame {
|
||||
height: calc(100vh - 88px);
|
||||
z-index: 998;
|
||||
height: calc(100vh - 88px);
|
||||
|
||||
.frame-iframe {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export function useDataThemeChange() {
|
||||
const body = document.documentElement as HTMLElement;
|
||||
|
||||
/** 设置导航主题色 */
|
||||
function setLayoutThemeColor(theme = "default") {
|
||||
function setLayoutThemeColor(theme = getConfig().Theme ?? "default") {
|
||||
layoutTheme.value.theme = theme;
|
||||
toggleTheme({
|
||||
scopeName: `layout-theme-${theme}`
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { computed } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { getConfig } from "@/config";
|
||||
import { useRouter } from "vue-router";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import { routeMetaType } from "../types";
|
||||
import { getTopMenu } from "@/router/utils";
|
||||
import { useGlobal } from "@pureadmin/utils";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { router, remainingPaths } from "@/router";
|
||||
import { computed, type CSSProperties } from "vue";
|
||||
import { useAppStoreHook } from "@/store/modules/app";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
||||
@@ -21,6 +22,16 @@ export function useNav() {
|
||||
/** 平台`layout`中所有`el-tooltip`的`effect`配置,默认`light` */
|
||||
const tooltipEffect = getConfig()?.TooltipEffect ?? "light";
|
||||
|
||||
const getDivStyle = computed((): CSSProperties => {
|
||||
return {
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
overflow: "hidden"
|
||||
};
|
||||
});
|
||||
|
||||
/** 用户名 */
|
||||
const username = computed(() => {
|
||||
return useUserStoreHook()?.username;
|
||||
@@ -75,8 +86,8 @@ export function useNav() {
|
||||
useUserStoreHook().logOut();
|
||||
}
|
||||
|
||||
function backHome() {
|
||||
router.push("/welcome");
|
||||
function backTopMenu() {
|
||||
router.push(getTopMenu().path);
|
||||
}
|
||||
|
||||
function onPanel() {
|
||||
@@ -144,8 +155,9 @@ export function useNav() {
|
||||
logout,
|
||||
routers,
|
||||
$storage,
|
||||
backHome,
|
||||
backTopMenu,
|
||||
onPanel,
|
||||
getDivStyle,
|
||||
changeTitle,
|
||||
toggleSideBar,
|
||||
menuSelect,
|
||||
|
||||
@@ -12,10 +12,16 @@ import { tagsViewsType } from "../types";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { transformI18n, $t } from "@/plugins/i18n";
|
||||
import { isEqual, isBoolean } from "@pureadmin/utils";
|
||||
import { responsiveStorageNameSpace } from "@/config";
|
||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { storageLocal, toggleClass, hasClass } from "@pureadmin/utils";
|
||||
import {
|
||||
isEqual,
|
||||
isBoolean,
|
||||
storageLocal,
|
||||
toggleClass,
|
||||
hasClass
|
||||
} from "@pureadmin/utils";
|
||||
|
||||
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
|
||||
import CloseAllTags from "@iconify-icons/ri/subtract-line";
|
||||
@@ -41,13 +47,16 @@ export function useTags() {
|
||||
|
||||
/** 显示模式,默认灵动模式 */
|
||||
const showModel = ref(
|
||||
storageLocal().getItem<StorageConfigs>("responsive-configure")?.showModel ||
|
||||
"smart"
|
||||
storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}configure`
|
||||
)?.showModel || "smart"
|
||||
);
|
||||
/** 是否隐藏标签页,默认显示 */
|
||||
const showTags =
|
||||
ref(
|
||||
storageLocal().getItem<StorageConfigs>("responsive-configure").hideTabs
|
||||
storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}configure`
|
||||
).hideTabs
|
||||
) ?? ref("false");
|
||||
const multiTags: any = computed(() => {
|
||||
return useMultiTagsStoreHook().multiTags;
|
||||
@@ -196,10 +205,13 @@ export function useTags() {
|
||||
onMounted(() => {
|
||||
if (!showModel.value) {
|
||||
const configure = storageLocal().getItem<StorageConfigs>(
|
||||
"responsive-configure"
|
||||
`${responsiveStorageNameSpace()}configure`
|
||||
);
|
||||
configure.showModel = "card";
|
||||
storageLocal().setItem("responsive-configure", configure);
|
||||
storageLocal().setItem(
|
||||
`${responsiveStorageNameSpace()}configure`,
|
||||
configure
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -179,20 +179,16 @@ const layoutHeader = defineComponent({
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@mixin clearfix {
|
||||
.app-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
@@ -201,13 +197,13 @@ const layoutHeader = defineComponent({
|
||||
}
|
||||
|
||||
.app-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.re-screen {
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import type { IconifyIcon } from "@iconify/vue";
|
||||
const { VITE_HIDE_HOME } = import.meta.env;
|
||||
|
||||
export const routerArrays: Array<RouteConfigs> = [
|
||||
{
|
||||
path: "/welcome",
|
||||
parentPath: "/",
|
||||
meta: {
|
||||
title: "menus.hshome",
|
||||
icon: "homeFilled"
|
||||
}
|
||||
}
|
||||
];
|
||||
export const routerArrays: Array<RouteConfigs> =
|
||||
VITE_HIDE_HOME === "false"
|
||||
? [
|
||||
{
|
||||
path: "/welcome",
|
||||
parentPath: "/",
|
||||
meta: {
|
||||
title: "menus.hshome",
|
||||
icon: "homeFilled"
|
||||
}
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
export type routeMetaType = {
|
||||
title?: string;
|
||||
@@ -58,23 +62,23 @@ export interface setType {
|
||||
hideTabs: boolean;
|
||||
}
|
||||
|
||||
export type childrenType = {
|
||||
export type menuType = {
|
||||
id?: number;
|
||||
path?: string;
|
||||
noShowingChildren?: boolean;
|
||||
children?: childrenType[];
|
||||
children?: menuType[];
|
||||
value: unknown;
|
||||
meta?: {
|
||||
icon?: string;
|
||||
title?: string;
|
||||
rank?: number;
|
||||
showParent?: boolean;
|
||||
extraIcon?: {
|
||||
svg?: boolean;
|
||||
name?: string;
|
||||
};
|
||||
extraIcon?: string;
|
||||
};
|
||||
showTooltip?: boolean;
|
||||
parentId?: number;
|
||||
pathList?: number[];
|
||||
redirect?: string;
|
||||
};
|
||||
|
||||
export type themeColorsType = {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { App, WritableComputedRef } from "vue";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { type I18n, createI18n } from "vue-i18n";
|
||||
import { responsiveStorageNameSpace } from "@/config";
|
||||
|
||||
// element-plus国际化
|
||||
import enLocale from "element-plus/lib/locale/lang/en";
|
||||
@@ -63,7 +64,9 @@ export const $t = (key: string) => key;
|
||||
export const i18n: I18n = createI18n({
|
||||
legacy: false,
|
||||
locale:
|
||||
storageLocal().getItem<StorageConfigs>("responsive-locale")?.locale ?? "zh",
|
||||
storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}locale`
|
||||
)?.locale ?? "zh",
|
||||
fallbackLocale: "en",
|
||||
messages: localesConfigs
|
||||
});
|
||||
|
||||
@@ -13,13 +13,13 @@ const home = 0, // 平台规定只有 home 路由的 rank 才能为 0 ,所以
|
||||
list = 10,
|
||||
permission = 11,
|
||||
system = 12,
|
||||
menuoverflow = 13,
|
||||
tabs = 14,
|
||||
formdesign = 15,
|
||||
flowchart = 16,
|
||||
ppt = 17,
|
||||
editor = 18,
|
||||
guide = 19,
|
||||
tabs = 13,
|
||||
formdesign = 14,
|
||||
flowchart = 15,
|
||||
ppt = 16,
|
||||
editor = 17,
|
||||
guide = 18,
|
||||
menuoverflow = 19,
|
||||
about = 20;
|
||||
|
||||
export {
|
||||
@@ -36,12 +36,12 @@ export {
|
||||
list,
|
||||
permission,
|
||||
system,
|
||||
menuoverflow,
|
||||
tabs,
|
||||
formdesign,
|
||||
flowchart,
|
||||
ppt,
|
||||
editor,
|
||||
guide,
|
||||
menuoverflow,
|
||||
about
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "vue-router";
|
||||
import {
|
||||
ascending,
|
||||
getTopMenu,
|
||||
initRouter,
|
||||
isOneOfArray,
|
||||
getHistoryMode,
|
||||
@@ -46,13 +47,13 @@ Object.keys(modules).forEach(key => {
|
||||
|
||||
/** 导出处理后的静态路由(三级及以上的路由全部拍成二级) */
|
||||
export const constantRoutes: Array<RouteRecordRaw> = formatTwoStageRoutes(
|
||||
formatFlatteningRoutes(buildHierarchyTree(ascending(routes)))
|
||||
formatFlatteningRoutes(buildHierarchyTree(ascending(routes.flat(Infinity))))
|
||||
);
|
||||
|
||||
/** 用于渲染菜单,保持原始层级 */
|
||||
export const constantMenus: Array<RouteComponent> = ascending(routes).concat(
|
||||
...remainingRouter
|
||||
);
|
||||
export const constantMenus: Array<RouteComponent> = ascending(
|
||||
routes.flat(Infinity)
|
||||
).concat(...remainingRouter);
|
||||
|
||||
/** 不参与菜单的路由 */
|
||||
export const remainingPaths = Object.keys(remainingRouter).map(v => {
|
||||
@@ -61,7 +62,7 @@ export const remainingPaths = Object.keys(remainingRouter).map(v => {
|
||||
|
||||
/** 创建路由实例 */
|
||||
export const router: Router = createRouter({
|
||||
history: getHistoryMode(),
|
||||
history: getHistoryMode(import.meta.env.VITE_ROUTER_HISTORY),
|
||||
routes: constantRoutes.concat(...(remainingRouter as any)),
|
||||
strict: true,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
@@ -86,7 +87,9 @@ export function resetRouter() {
|
||||
if (name && router.hasRoute(name) && meta?.backstage) {
|
||||
router.removeRoute(name);
|
||||
router.options.routes = formatTwoStageRoutes(
|
||||
formatFlatteningRoutes(buildHierarchyTree(ascending(routes)))
|
||||
formatFlatteningRoutes(
|
||||
buildHierarchyTree(ascending(routes.flat(Infinity)))
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -96,13 +99,14 @@ export function resetRouter() {
|
||||
/** 路由白名单 */
|
||||
const whiteList = ["/login"];
|
||||
|
||||
const { VITE_HIDE_HOME } = import.meta.env;
|
||||
|
||||
router.beforeEach((to: toRouteType, _from, next) => {
|
||||
if (to.meta?.keepAlive) {
|
||||
const newMatched = to.matched;
|
||||
handleAliveRoute(newMatched, "add");
|
||||
handleAliveRoute(to, "add");
|
||||
// 页面整体刷新和点击标签页刷新
|
||||
if (_from.name === undefined || _from.name === "Redirect") {
|
||||
handleAliveRoute(newMatched);
|
||||
handleAliveRoute(to);
|
||||
}
|
||||
}
|
||||
const userInfo = storageSession().getItem<DataInfo<number>>(sessionKey);
|
||||
@@ -126,6 +130,10 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
||||
if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) {
|
||||
next({ path: "/error/403" });
|
||||
}
|
||||
// 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面
|
||||
if (VITE_HIDE_HOME === "true" && to.fullPath === "/welcome") {
|
||||
next({ path: "/error/404" });
|
||||
}
|
||||
if (_from?.name) {
|
||||
// name为超链接
|
||||
if (externalLink) {
|
||||
@@ -139,7 +147,7 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
||||
if (
|
||||
usePermissionStoreHook().wholeMenus.length === 0 &&
|
||||
to.path !== "/login"
|
||||
)
|
||||
) {
|
||||
initRouter().then((router: Router) => {
|
||||
if (!useMultiTagsStoreHook().getMultiTagsCache) {
|
||||
const { path } = to;
|
||||
@@ -147,6 +155,7 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
||||
path,
|
||||
router.options.routes[0].children
|
||||
);
|
||||
getTopMenu(true);
|
||||
// query、params模式路由传参数的标签页不在此处处理
|
||||
if (route && route.meta?.title) {
|
||||
useMultiTagsStoreHook().handleTags("push", {
|
||||
@@ -158,6 +167,7 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
||||
}
|
||||
router.push(to.fullPath);
|
||||
});
|
||||
}
|
||||
toCorrectRoute();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -11,21 +11,44 @@ export default {
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/components/message",
|
||||
name: "Message",
|
||||
component: () => import("@/views/components/message/index.vue"),
|
||||
path: "/components/dialog",
|
||||
name: "Dialog",
|
||||
component: () => import("@/views/components/dialog/index.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsmessage"),
|
||||
extraIcon: {
|
||||
svg: true,
|
||||
name: "team-iconxinpinrenqiwang"
|
||||
},
|
||||
title: $t("menus.hsdialog"),
|
||||
extraIcon: "IF-pure-iconfont-new svg",
|
||||
transition: {
|
||||
enterTransition: "animate__fadeInLeft",
|
||||
leaveTransition: "animate__fadeOutRight"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/components/message",
|
||||
name: "Message",
|
||||
component: () => import("@/views/components/message/index.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hsmessage")
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/components/segmented",
|
||||
name: "Segmented",
|
||||
component: () => import("@/views/components/segmented/index.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hssegmented"),
|
||||
extraIcon: "IF-pure-iconfont-new svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/components/waterfall",
|
||||
name: "Waterfall",
|
||||
component: () => import("@/views/components/waterfall/index.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hswaterfall"),
|
||||
extraIcon: "IF-pure-iconfont-new svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/components/video",
|
||||
name: "Video",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { $t } from "@/plugins/i18n";
|
||||
import { home } from "@/router/enums";
|
||||
const { VITE_HIDE_HOME } = import.meta.env;
|
||||
const Layout = () => import("@/layout/index.vue");
|
||||
|
||||
export default {
|
||||
@@ -18,7 +19,8 @@ export default {
|
||||
name: "Welcome",
|
||||
component: () => import("@/views/welcome/index.vue"),
|
||||
meta: {
|
||||
title: $t("menus.hshome")
|
||||
title: $t("menus.hshome"),
|
||||
showLink: VITE_HIDE_HOME === "true" ? false : true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -53,10 +53,7 @@ export default {
|
||||
meta: {
|
||||
title: $t("menus.hsmenu1-2-2"),
|
||||
keepAlive: true,
|
||||
extraIcon: {
|
||||
svg: true,
|
||||
name: "team-iconxinpinrenqiwang"
|
||||
}
|
||||
extraIcon: "IF-pure-iconfont-new svg"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -16,8 +16,7 @@ export default [
|
||||
path: "/redirect",
|
||||
component: Layout,
|
||||
meta: {
|
||||
icon: "homeFilled",
|
||||
title: $t("menus.hshome"),
|
||||
title: $t("status.hsLoad"),
|
||||
showLink: false,
|
||||
rank: 102
|
||||
},
|
||||
|
||||
@@ -3,13 +3,11 @@ import {
|
||||
RouteRecordRaw,
|
||||
RouteComponent,
|
||||
createWebHistory,
|
||||
createWebHashHistory,
|
||||
RouteRecordNormalized
|
||||
createWebHashHistory
|
||||
} from "vue-router";
|
||||
import { router } from "./index";
|
||||
import { isProxy, toRaw } from "vue";
|
||||
import { useTimeoutFn } from "@vueuse/core";
|
||||
import { RouteConfigs } from "@/layout/types";
|
||||
import {
|
||||
isString,
|
||||
cloneDeep,
|
||||
@@ -19,8 +17,10 @@ import {
|
||||
isIncludeAllChildren
|
||||
} from "@pureadmin/utils";
|
||||
import { getConfig } from "@/config";
|
||||
import { menuType } from "@/layout/types";
|
||||
import { buildHierarchyTree } from "@/utils/tree";
|
||||
import { sessionKey, type DataInfo } from "@/utils/auth";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
const IFrame = () => import("@/layout/frameView.vue");
|
||||
// https://cn.vitejs.dev/guide/features.html#glob-import
|
||||
@@ -94,30 +94,20 @@ function filterNoPermissionTree(data: RouteComponent[]) {
|
||||
return filterChildrenTree(newTree);
|
||||
}
|
||||
|
||||
/** 批量删除缓存路由(keepalive) */
|
||||
function delAliveRoutes(delAliveRouteList: Array<RouteConfigs>) {
|
||||
delAliveRouteList.forEach(route => {
|
||||
usePermissionStoreHook().cacheOperate({
|
||||
mode: "delete",
|
||||
name: route?.name
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** 通过path获取父级路径 */
|
||||
function getParentPaths(path: string, routes: RouteRecordRaw[]) {
|
||||
/** 通过指定 `key` 获取父级路径集合,默认 `key` 为 `path` */
|
||||
function getParentPaths(value: string, routes: RouteRecordRaw[], key = "path") {
|
||||
// 深度遍历查找
|
||||
function dfs(routes: RouteRecordRaw[], path: string, parents: string[]) {
|
||||
function dfs(routes: RouteRecordRaw[], value: string, parents: string[]) {
|
||||
for (let i = 0; i < routes.length; i++) {
|
||||
const item = routes[i];
|
||||
// 找到path则返回父级path
|
||||
if (item.path === path) return parents;
|
||||
// 返回父级path
|
||||
if (item[key] === value) return parents;
|
||||
// children不存在或为空则不递归
|
||||
if (!item.children || !item.children.length) continue;
|
||||
// 往下查找时将当前path入栈
|
||||
parents.push(item.path);
|
||||
|
||||
if (dfs(item.children, path, parents).length) return parents;
|
||||
if (dfs(item.children, value, parents).length) return parents;
|
||||
// 深度遍历查找未找到时当前path 出栈
|
||||
parents.pop();
|
||||
}
|
||||
@@ -125,10 +115,10 @@ function getParentPaths(path: string, routes: RouteRecordRaw[]) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return dfs(routes, path, []);
|
||||
return dfs(routes, value, []);
|
||||
}
|
||||
|
||||
/** 查找对应path的路由信息 */
|
||||
/** 查找对应 `path` 的路由信息 */
|
||||
function findRouteByPath(path: string, routes: RouteRecordRaw[]) {
|
||||
let res = routes.find((item: { path: string }) => item.path == path);
|
||||
if (res) {
|
||||
@@ -241,7 +231,7 @@ function formatFlatteningRoutes(routesList: RouteRecordRaw[]) {
|
||||
|
||||
/**
|
||||
* 一维数组处理成多级嵌套数组(三级及以上的路由全部拍成二级,keep-alive 只支持到二级缓存)
|
||||
* https://github.com/xiaoxian521/vue-pure-admin/issues/67
|
||||
* https://github.com/pure-admin/vue-pure-admin/issues/67
|
||||
* @param routesList 处理后的一维路由菜单数组
|
||||
* @returns 返回将一维数组重新处理成规定路由的格式
|
||||
*/
|
||||
@@ -266,27 +256,35 @@ function formatTwoStageRoutes(routesList: RouteRecordRaw[]) {
|
||||
}
|
||||
|
||||
/** 处理缓存路由(添加、删除、刷新) */
|
||||
function handleAliveRoute(matched: RouteRecordNormalized[], mode?: string) {
|
||||
function handleAliveRoute({ name }: toRouteType, mode?: string) {
|
||||
switch (mode) {
|
||||
case "add":
|
||||
matched.forEach(v => {
|
||||
usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name });
|
||||
usePermissionStoreHook().cacheOperate({
|
||||
mode: "add",
|
||||
name
|
||||
});
|
||||
break;
|
||||
case "delete":
|
||||
usePermissionStoreHook().cacheOperate({
|
||||
mode: "delete",
|
||||
name: matched[matched.length - 1].name
|
||||
name
|
||||
});
|
||||
break;
|
||||
case "refresh":
|
||||
usePermissionStoreHook().cacheOperate({
|
||||
mode: "refresh",
|
||||
name
|
||||
});
|
||||
break;
|
||||
default:
|
||||
usePermissionStoreHook().cacheOperate({
|
||||
mode: "delete",
|
||||
name: matched[matched.length - 1].name
|
||||
name
|
||||
});
|
||||
useTimeoutFn(() => {
|
||||
matched.forEach(v => {
|
||||
usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name });
|
||||
usePermissionStoreHook().cacheOperate({
|
||||
mode: "add",
|
||||
name
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
@@ -322,8 +320,7 @@ function addAsyncRoutes(arrRoutes: Array<RouteRecordRaw>) {
|
||||
}
|
||||
|
||||
/** 获取路由历史模式 https://next.router.vuejs.org/zh/guide/essentials/history-mode.html */
|
||||
function getHistoryMode(): RouterHistory {
|
||||
const routerHistory = import.meta.env.VITE_ROUTER_HISTORY;
|
||||
function getHistoryMode(routerHistory): RouterHistory {
|
||||
// len为1 代表只有历史模式 为2 代表历史模式中存在base参数 https://next.router.vuejs.org/zh/api/#%E5%8F%82%E6%95%B0-1
|
||||
const historyMode = routerHistory.split(",");
|
||||
const leftMode = historyMode[0];
|
||||
@@ -362,16 +359,24 @@ function hasAuth(value: string | Array<string>): boolean {
|
||||
return isAuths ? true : false;
|
||||
}
|
||||
|
||||
/** 获取所有菜单中的第一个菜单(顶级菜单)*/
|
||||
function getTopMenu(tag = false): menuType {
|
||||
const topMenu = usePermissionStoreHook().wholeMenus[0]?.children[0];
|
||||
tag && useMultiTagsStoreHook().handleTags("push", topMenu);
|
||||
return topMenu;
|
||||
}
|
||||
|
||||
export {
|
||||
hasAuth,
|
||||
getAuths,
|
||||
ascending,
|
||||
filterTree,
|
||||
initRouter,
|
||||
getTopMenu,
|
||||
addPathMatch,
|
||||
isOneOfArray,
|
||||
getHistoryMode,
|
||||
addAsyncRoutes,
|
||||
delAliveRoutes,
|
||||
getParentPaths,
|
||||
findRouteByPath,
|
||||
handleAliveRoute,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { store } from "@/store";
|
||||
import { appType } from "./types";
|
||||
import { defineStore } from "pinia";
|
||||
import { getConfig } from "@/config";
|
||||
import { getConfig, responsiveStorageNameSpace } from "@/config";
|
||||
import { deviceDetection, storageLocal } from "@pureadmin/utils";
|
||||
|
||||
export const useAppStore = defineStore({
|
||||
@@ -9,31 +9,34 @@ export const useAppStore = defineStore({
|
||||
state: (): appType => ({
|
||||
sidebar: {
|
||||
opened:
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout")
|
||||
?.sidebarStatus ?? getConfig().SidebarStatus,
|
||||
storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}layout`
|
||||
)?.sidebarStatus ?? getConfig().SidebarStatus,
|
||||
withoutAnimation: false,
|
||||
isClickCollapse: false
|
||||
},
|
||||
// 这里的layout用于监听容器拖拉后恢复对应的导航模式
|
||||
layout:
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout")?.layout ??
|
||||
getConfig().Layout,
|
||||
storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}layout`
|
||||
)?.layout ?? getConfig().Layout,
|
||||
device: deviceDetection() ? "mobile" : "desktop",
|
||||
// 作用于 src/views/components/draggable/index.vue 页面,当离开页面并不会销毁 new Swap(),sortablejs 官网也没有提供任何销毁的 api
|
||||
sortSwap: false
|
||||
}),
|
||||
getters: {
|
||||
getSidebarStatus() {
|
||||
return this.sidebar.opened;
|
||||
getSidebarStatus(state) {
|
||||
return state.sidebar.opened;
|
||||
},
|
||||
getDevice() {
|
||||
return this.device;
|
||||
getDevice(state) {
|
||||
return state.device;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
TOGGLE_SIDEBAR(opened?: boolean, resize?: string) {
|
||||
const layout =
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout");
|
||||
const layout = storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}layout`
|
||||
);
|
||||
if (opened && resize) {
|
||||
this.sidebar.withoutAnimation = true;
|
||||
this.sidebar.opened = true;
|
||||
@@ -48,7 +51,7 @@ export const useAppStore = defineStore({
|
||||
this.sidebar.isClickCollapse = !this.sidebar.opened;
|
||||
layout.sidebarStatus = this.sidebar.opened;
|
||||
}
|
||||
storageLocal().setItem("responsive-layout", layout);
|
||||
storageLocal().setItem(`${responsiveStorageNameSpace()}layout`, layout);
|
||||
},
|
||||
async toggleSideBar(opened?: boolean, resize?: string) {
|
||||
await this.TOGGLE_SIDEBAR(opened, resize);
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
import { store } from "@/store";
|
||||
import { defineStore } from "pinia";
|
||||
import { getConfig } from "@/config";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { getConfig, responsiveStorageNameSpace } from "@/config";
|
||||
|
||||
export const useEpThemeStore = defineStore({
|
||||
id: "pure-epTheme",
|
||||
state: () => ({
|
||||
epThemeColor:
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout")
|
||||
?.epThemeColor ?? getConfig().EpThemeColor,
|
||||
storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}layout`
|
||||
)?.epThemeColor ?? getConfig().EpThemeColor,
|
||||
epTheme:
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout")?.theme ??
|
||||
getConfig().Theme
|
||||
storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}layout`
|
||||
)?.theme ?? getConfig().Theme
|
||||
}),
|
||||
getters: {
|
||||
getEpThemeColor() {
|
||||
return this.epThemeColor;
|
||||
getEpThemeColor(state) {
|
||||
return state.epThemeColor;
|
||||
},
|
||||
/** 用于mix导航模式下hamburger-svg的fill属性 */
|
||||
fill() {
|
||||
if (this.epTheme === "light") {
|
||||
fill(state) {
|
||||
if (state.epTheme === "light") {
|
||||
return "#409eff";
|
||||
} else if (this.epTheme === "yellow") {
|
||||
} else if (state.epTheme === "yellow") {
|
||||
return "#d25f00";
|
||||
} else {
|
||||
return "#fff";
|
||||
@@ -30,13 +32,14 @@ export const useEpThemeStore = defineStore({
|
||||
},
|
||||
actions: {
|
||||
setEpThemeColor(newColor: string): void {
|
||||
const layout =
|
||||
storageLocal().getItem<StorageConfigs>("responsive-layout");
|
||||
const layout = storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}layout`
|
||||
);
|
||||
this.epTheme = layout?.theme;
|
||||
this.epThemeColor = newColor;
|
||||
if (!layout) return;
|
||||
layout.epThemeColor = newColor;
|
||||
storageLocal().setItem("responsive-layout", layout);
|
||||
storageLocal().setItem(`${responsiveStorageNameSpace()}layout`, layout);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,39 +1,48 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { store } from "@/store";
|
||||
import { isEqual } from "@pureadmin/utils";
|
||||
import { routerArrays } from "@/layout/types";
|
||||
import { multiType, positionType } from "./types";
|
||||
import { isUrl, storageLocal } from "@pureadmin/utils";
|
||||
import { responsiveStorageNameSpace } from "@/config";
|
||||
import { isEqual, isBoolean, isUrl, storageLocal } from "@pureadmin/utils";
|
||||
|
||||
export const useMultiTagsStore = defineStore({
|
||||
id: "pure-multiTags",
|
||||
state: () => ({
|
||||
// 存储标签页信息(路由信息)
|
||||
multiTags: storageLocal().getItem<StorageConfigs>("responsive-configure")
|
||||
?.multiTagsCache
|
||||
? storageLocal().getItem<StorageConfigs>("responsive-tags")
|
||||
multiTags: storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}configure`
|
||||
)?.multiTagsCache
|
||||
? storageLocal().getItem<StorageConfigs>(
|
||||
`${responsiveStorageNameSpace()}tags`
|
||||
)
|
||||
: [...routerArrays],
|
||||
multiTagsCache: storageLocal().getItem<StorageConfigs>(
|
||||
"responsive-configure"
|
||||
`${responsiveStorageNameSpace()}configure`
|
||||
)?.multiTagsCache
|
||||
}),
|
||||
getters: {
|
||||
getMultiTagsCache() {
|
||||
return this.multiTagsCache;
|
||||
getMultiTagsCache(state) {
|
||||
return state.multiTagsCache;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
multiTagsCacheChange(multiTagsCache: boolean) {
|
||||
this.multiTagsCache = multiTagsCache;
|
||||
if (multiTagsCache) {
|
||||
storageLocal().setItem("responsive-tags", this.multiTags);
|
||||
storageLocal().setItem(
|
||||
`${responsiveStorageNameSpace()}tags`,
|
||||
this.multiTags
|
||||
);
|
||||
} else {
|
||||
storageLocal().removeItem("responsive-tags");
|
||||
storageLocal().removeItem(`${responsiveStorageNameSpace()}tags`);
|
||||
}
|
||||
},
|
||||
tagsCache(multiTags) {
|
||||
this.getMultiTagsCache &&
|
||||
storageLocal().setItem("responsive-tags", multiTags);
|
||||
storageLocal().setItem(
|
||||
`${responsiveStorageNameSpace()}tags`,
|
||||
multiTags
|
||||
);
|
||||
},
|
||||
handleTags<T>(
|
||||
mode: string,
|
||||
@@ -54,6 +63,9 @@ export const useMultiTagsStore = defineStore({
|
||||
if (isUrl(tagVal?.name)) return;
|
||||
// 如果title为空拒绝添加空信息到标签页
|
||||
if (tagVal?.meta?.title.length === 0) return;
|
||||
// showLink:false 不添加到标签页
|
||||
if (isBoolean(tagVal?.meta?.showLink) && !tagVal?.meta?.showLink)
|
||||
return;
|
||||
const tagPath = tagVal.path;
|
||||
// 判断tag是否已存在
|
||||
const tagHasExits = this.multiTags.some(tag => {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { defineStore } from "pinia";
|
||||
import { store } from "@/store";
|
||||
import { cacheType } from "./types";
|
||||
import { constantMenus } from "@/router";
|
||||
import { getKeyList } from "@pureadmin/utils";
|
||||
import { useMultiTagsStoreHook } from "./multiTags";
|
||||
import { ascending, filterTree, filterNoPermissionTree } from "@/router/utils";
|
||||
|
||||
export const usePermissionStore = defineStore({
|
||||
@@ -22,17 +24,32 @@ export const usePermissionStore = defineStore({
|
||||
);
|
||||
},
|
||||
cacheOperate({ mode, name }: cacheType) {
|
||||
const delIndex = this.cachePageList.findIndex(v => v === name);
|
||||
switch (mode) {
|
||||
case "refresh":
|
||||
this.cachePageList = this.cachePageList.filter(v => v !== name);
|
||||
break;
|
||||
case "add":
|
||||
this.cachePageList.push(name);
|
||||
this.cachePageList = [...new Set(this.cachePageList)];
|
||||
break;
|
||||
case "delete":
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const delIndex = this.cachePageList.findIndex(v => v === name);
|
||||
delIndex !== -1 && this.cachePageList.splice(delIndex, 1);
|
||||
break;
|
||||
}
|
||||
/** 监听缓存页面是否存在于标签页,不存在则删除 */
|
||||
(() => {
|
||||
let cacheLength = this.cachePageList.length;
|
||||
const nameList = getKeyList(useMultiTagsStoreHook().multiTags, "name");
|
||||
while (cacheLength > 0) {
|
||||
nameList.findIndex(v => v === this.cachePageList[cacheLength - 1]) ===
|
||||
-1 &&
|
||||
this.cachePageList.splice(
|
||||
this.cachePageList.indexOf(this.cachePageList[cacheLength - 1]),
|
||||
1
|
||||
);
|
||||
cacheLength--;
|
||||
}
|
||||
})();
|
||||
},
|
||||
/** 清空缓存页面 */
|
||||
clearAllCachePage() {
|
||||
|
||||
@@ -11,20 +11,19 @@ export const useSettingStore = defineStore({
|
||||
hiddenSideBar: getConfig().HiddenSideBar
|
||||
}),
|
||||
getters: {
|
||||
getTitle() {
|
||||
return this.title;
|
||||
getTitle(state) {
|
||||
return state.title;
|
||||
},
|
||||
getFixedHeader() {
|
||||
return this.fixedHeader;
|
||||
getFixedHeader(state) {
|
||||
return state.fixedHeader;
|
||||
},
|
||||
getHiddenSideBar() {
|
||||
return this.HiddenSideBar;
|
||||
getHiddenSideBar(state) {
|
||||
return state.hiddenSideBar;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
CHANGE_SETTING({ key, value }) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (this.hasOwnProperty(key)) {
|
||||
if (Reflect.has(this, key)) {
|
||||
this[key] = value;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -32,8 +32,8 @@ html.dark {
|
||||
.tags-view {
|
||||
.arrow-left,
|
||||
.arrow-right {
|
||||
box-shadow: none;
|
||||
border-right: 1px solid $border-style;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.arrow-right {
|
||||
@@ -46,6 +46,7 @@ html.dark {
|
||||
.el-divider__text {
|
||||
--el-bg-color: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.el-divider--horizontal {
|
||||
border-top: none;
|
||||
}
|
||||
@@ -63,10 +64,10 @@ html.dark {
|
||||
}
|
||||
|
||||
.form-edit-widget-label a {
|
||||
color: $color-white;
|
||||
background: var(--el-color-primary);
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: $color-white;
|
||||
}
|
||||
|
||||
.el-aside {
|
||||
@@ -84,14 +85,18 @@ html.dark {
|
||||
.el-table__cell {
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
.el-card {
|
||||
--el-card-bg-color: var(--el-bg-color);
|
||||
|
||||
// border: none !important;
|
||||
}
|
||||
|
||||
.el-backtop {
|
||||
--el-backtop-bg-color: var(--el-color-primary-light-9);
|
||||
--el-backtop-hover-bg-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item:not(.is-disabled):hover {
|
||||
background: transparent;
|
||||
}
|
||||
@@ -103,18 +108,22 @@ html.dark {
|
||||
&.el-message-box__close,
|
||||
&.el-notification__closeBtn {
|
||||
&:hover {
|
||||
color: rgba(255, 255, 255, 0.85) !important;
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
color: rgb(255 255 255 / 85%) !important;
|
||||
background-color: rgb(255 255 255 / 12%);
|
||||
|
||||
.pure-dialog-svg {
|
||||
color: rgb(255 255 255 / 85%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,非暗黑模式在 src/style/element-plus.scss 文件进行了适配 */
|
||||
.pure-message {
|
||||
background-color: rgb(36 37 37) !important;
|
||||
background-image: initial !important;
|
||||
background-color: rgb(36, 37, 37) !important;
|
||||
box-shadow: rgb(13 13 13 / 12%) 0px 3px 6px -4px,
|
||||
rgb(13 13 13 / 8%) 0px 6px 16px 0px, rgb(13 13 13 / 5%) 0px 9px 28px 8px !important;
|
||||
box-shadow: rgb(13 13 13 / 12%) 0 3px 6px -4px,
|
||||
rgb(13 13 13 / 8%) 0 6px 16px 0, rgb(13 13 13 / 5%) 0 9px 28px 8px !important;
|
||||
|
||||
& .el-message__content {
|
||||
color: $color-white !important;
|
||||
@@ -124,9 +133,23 @@ html.dark {
|
||||
|
||||
& .el-message__closeBtn {
|
||||
&:hover {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
color: rgb(255 255 255 / 85%);
|
||||
background-color: rgb(255 255 255 / 12%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ReSegmented 组件 */
|
||||
.pure-segmented {
|
||||
color: rgb(255 255 255 / 65%);
|
||||
background-color: #000;
|
||||
|
||||
.pure-segmented-item-selected {
|
||||
background-color: #1f1f1f;
|
||||
}
|
||||
|
||||
.pure-segmented-item-disabled {
|
||||
color: rgb(255 255 255 / 25%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
.pure-dialog {
|
||||
.pure-dialog-svg {
|
||||
color: var(--el-color-info);
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
top: 20px;
|
||||
right: 14px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 全局覆盖element-plus的el-dialog、el-drawer、el-message-box、el-notification组件右上角关闭图标的样式,表现更鲜明 */
|
||||
.el-dialog__headerbtn,
|
||||
.el-message-box__headerbtn {
|
||||
@@ -78,6 +91,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
&.el-dialog__close,
|
||||
&.el-drawer__close,
|
||||
@@ -85,22 +99,27 @@
|
||||
&.el-notification__closeBtn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.88) !important;
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
color: rgb(0 0 0 / 88%) !important;
|
||||
text-decoration: none;
|
||||
background-color: rgb(0 0 0 / 6%);
|
||||
|
||||
.pure-dialog-svg {
|
||||
color: rgb(0 0 0 / 88%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,暗黑模式在 src/style/dark.scss 文件进行了适配 */
|
||||
.pure-message {
|
||||
border-width: 0 !important;
|
||||
background: #fff !important;
|
||||
padding: 10px 13px !important;
|
||||
background: #fff !important;
|
||||
border-width: 0 !important;
|
||||
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014,
|
||||
0 9px 28px 8px #0000000d !important;
|
||||
|
||||
@@ -119,13 +138,13 @@
|
||||
}
|
||||
|
||||
& .el-message__closeBtn {
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
right: 9px !important;
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.06);
|
||||
background-color: rgb(0 0 0 / 6%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
@import "./mixin.scss";
|
||||
@import "./transition.scss";
|
||||
@import "./element-plus.scss";
|
||||
@import "./sidebar.scss";
|
||||
@import "./dark.scss";
|
||||
@import "./transition";
|
||||
@import "./element-plus";
|
||||
@import "./sidebar";
|
||||
@import "./dark";
|
||||
|
||||
/* 自定义全局 CssVar */
|
||||
:root {
|
||||
--pure-transition-duration: 0.016s;
|
||||
/* 左侧菜单展开、收起动画时长 */
|
||||
--pure-transition-duration: 0.3s;
|
||||
}
|
||||
|
||||
/* 灰色模式 */
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
@mixin clearfix {
|
||||
&::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin relative {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@mixin scrollBar {
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background: #d3dce6;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #99a9bf;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
border-width: 0;
|
||||
border-style: solid;
|
||||
border-color: currentColor;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
#app {
|
||||
@@ -13,25 +13,24 @@
|
||||
}
|
||||
|
||||
html {
|
||||
line-height: 1.5;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-moz-tab-size: 4;
|
||||
tab-size: 4;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.5;
|
||||
tab-size: 4;
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
line-height: inherit;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
|
||||
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
line-height: inherit;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizelegibility;
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
|
||||
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
}
|
||||
|
||||
hr {
|
||||
@@ -69,9 +68,9 @@ small {
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
@@ -85,8 +84,8 @@ sup {
|
||||
|
||||
table {
|
||||
text-indent: 0;
|
||||
border-color: inherit;
|
||||
border-collapse: collapse;
|
||||
border-color: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
@@ -94,12 +93,12 @@ input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: 100%;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
@@ -160,8 +159,8 @@ pre {
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
@@ -171,9 +170,9 @@ legend {
|
||||
ol,
|
||||
ul,
|
||||
menu {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
@@ -182,8 +181,8 @@ textarea {
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
opacity: 1;
|
||||
color: #9ca3af;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
button,
|
||||
@@ -238,9 +237,9 @@ a:active {
|
||||
a,
|
||||
a:focus,
|
||||
a:hover {
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div:focus {
|
||||
@@ -249,11 +248,11 @@ div:focus {
|
||||
|
||||
.clearfix {
|
||||
&::after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
clear: both;
|
||||
font-size: 0;
|
||||
visibility: hidden;
|
||||
content: " ";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,47 +2,50 @@
|
||||
@mixin merge-style($sideBarWidth) {
|
||||
$menuActiveText: #7a80b4;
|
||||
|
||||
@media screen and (min-width: 150px) and (max-width: 420px) {
|
||||
@media screen and (width >= 150px) and (width <= 420px) {
|
||||
.app-main-nofixed-header {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 420px) {
|
||||
|
||||
@media screen and (width >= 420px) {
|
||||
.app-main-nofixed-header {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-menu-icon {
|
||||
vertical-align: middle;
|
||||
font-size: 18px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 5px;
|
||||
font-size: 18px;
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.set-icon {
|
||||
height: 48px;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 48px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
min-height: 100%;
|
||||
/* main-content 属性动画 */
|
||||
transition: margin-left var(--pure-transition-duration);
|
||||
margin-left: $sideBarWidth;
|
||||
position: relative;
|
||||
background: #f0f2f5;
|
||||
|
||||
/* main-content 属性动画 */
|
||||
transition: margin-left var(--pure-transition-duration);
|
||||
|
||||
.el-scrollbar__wrap {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +55,7 @@
|
||||
right: 0;
|
||||
z-index: 998;
|
||||
width: calc(100% - 210px);
|
||||
|
||||
/* fixed-header 属性左上角动画 */
|
||||
transition: width var(--pure-transition-duration);
|
||||
}
|
||||
@@ -69,20 +73,21 @@
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
/* 展开动画 */
|
||||
transition: width var(--pure-transition-duration);
|
||||
width: $sideBarWidth !important;
|
||||
background: $menuBg;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
font-size: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
width: $sideBarWidth !important;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-size: 0;
|
||||
background: $menuBg;
|
||||
box-shadow: 0 0 1px #888;
|
||||
|
||||
/* 展开动画 */
|
||||
transition: width var(--pure-transition-duration);
|
||||
|
||||
.scrollbar-wrapper {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
@@ -100,6 +105,7 @@
|
||||
/* logo: 48px、leftCollapse: 40px、leftCollapse-shadow: 4px */
|
||||
height: calc(100% - 92px);
|
||||
}
|
||||
|
||||
.el-scrollbar.mobile {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -112,15 +118,15 @@
|
||||
a {
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
padding-left: 10px;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border: none;
|
||||
height: 100%;
|
||||
background-color: transparent !important;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
@@ -157,163 +163,72 @@
|
||||
}
|
||||
|
||||
.is-active {
|
||||
transition: color 0.3s;
|
||||
color: $subMenuActiveText !important;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active.nest-menu > * {
|
||||
z-index: 1;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active.nest-menu::before {
|
||||
position: absolute;
|
||||
inset: 0 8px;
|
||||
margin: 4px 0;
|
||||
clear: both;
|
||||
content: "";
|
||||
background: var(--el-color-primary) !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.el-menu .el-menu--inline .el-sub-menu__title,
|
||||
& .el-sub-menu .el-menu-item {
|
||||
font-size: 12px;
|
||||
min-width: $sideBarWidth !important;
|
||||
font-size: 12px;
|
||||
background-color: $subMenuBg !important;
|
||||
}
|
||||
|
||||
/* 有子集的激活菜单左侧小竖条 */
|
||||
.el-menu--collapse
|
||||
.is-active.outer-most.el-sub-menu
|
||||
> .el-sub-menu__title::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
clear: both;
|
||||
content: "";
|
||||
background-color: $menuActiveBefore;
|
||||
transition: all var(--pure-transition-duration) ease-in-out;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.el-menu--collapse .outer-most.el-sub-menu > .el-sub-menu__title::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
display: block;
|
||||
width: 3px;
|
||||
height: 0;
|
||||
content: "";
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
/* 无子集的激活菜单背景 */
|
||||
.is-active.submenu-title-noDropdown.outer-most {
|
||||
background: $subMenuActiveBg !important;
|
||||
.is-active.submenu-title-noDropdown.outer-most > * {
|
||||
z-index: 1;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 有子集的激活菜单背景 */
|
||||
.is-active.nest-menu {
|
||||
background: $subMenuActiveBg !important;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-header {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
background: $menuBg;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
align-items: center;
|
||||
|
||||
.horizontal-header-left {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
min-width: 200px;
|
||||
align-items: center;
|
||||
padding-left: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.125s ease;
|
||||
|
||||
i {
|
||||
font-size: 30px;
|
||||
color: #1890ff;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: $subMenuActiveText;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-header-menu {
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.horizontal-header-right {
|
||||
display: flex;
|
||||
min-width: 340px;
|
||||
align-items: center;
|
||||
color: $subMenuActiveText;
|
||||
justify-content: flex-end;
|
||||
|
||||
/* 搜索 */
|
||||
.search-container,
|
||||
/* 告警 */
|
||||
.dropdown-badge,
|
||||
/* 国际化 */
|
||||
.globalization,
|
||||
/* 用户名 */
|
||||
.el-dropdown-link,
|
||||
/* 设置 */
|
||||
.set-icon {
|
||||
&:hover {
|
||||
background: $menuHover;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-badge {
|
||||
height: 48px;
|
||||
color: $subMenuActiveText;
|
||||
}
|
||||
|
||||
.globalization {
|
||||
width: 40px;
|
||||
height: 48px;
|
||||
padding: 11px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
color: $subMenuActiveText;
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
height: 48px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
color: $subMenuActiveText;
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border: none;
|
||||
height: 100%;
|
||||
width: 100% !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
color: $menuText;
|
||||
|
||||
&:hover {
|
||||
color: $menuTitleHover !important;
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-title-noDropdown,
|
||||
.el-sub-menu__title {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
background: $menuBg;
|
||||
|
||||
svg {
|
||||
position: static !important;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active > .el-sub-menu__title,
|
||||
.is-active.submenu-title-noDropdown {
|
||||
color: $subMenuActiveText !important;
|
||||
|
||||
i {
|
||||
color: $subMenuActiveText !important;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active {
|
||||
transition: color 0.3s;
|
||||
color: $subMenuActiveText !important;
|
||||
.is-active.submenu-title-noDropdown.outer-most::before {
|
||||
position: absolute;
|
||||
inset: 0 8px;
|
||||
margin: 4px 0;
|
||||
clear: both;
|
||||
content: "";
|
||||
background: var(--el-color-primary) !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,8 +261,8 @@
|
||||
|
||||
/* 子菜单中还有子菜单 */
|
||||
.el-menu .el-sub-menu__title {
|
||||
font-size: 12px;
|
||||
min-width: $sideBarWidth !important;
|
||||
font-size: 12px;
|
||||
background-color: $subMenuBg !important;
|
||||
}
|
||||
|
||||
@@ -364,29 +279,33 @@
|
||||
}
|
||||
|
||||
.is-active {
|
||||
transition: color 0.3s;
|
||||
color: $subMenuActiveText !important;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active.nest-menu {
|
||||
background: $subMenuActiveBg !important;
|
||||
.el-menu-item.is-active.nest-menu > * {
|
||||
z-index: 1;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active.nest-menu::before {
|
||||
position: absolute;
|
||||
inset: 0 8px;
|
||||
clear: both;
|
||||
content: "";
|
||||
background: var(--el-color-primary) !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu {
|
||||
// i {
|
||||
// width: 20px;
|
||||
// text-align: center;
|
||||
// font-size: 16px;
|
||||
// }
|
||||
.iconfont {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
// i.fa {
|
||||
// margin-right: 5px;
|
||||
// font-size: 16px;
|
||||
// }
|
||||
.el-menu-tooltip__trigger {
|
||||
width: 54px;
|
||||
padding: 18px !important;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -423,8 +342,8 @@
|
||||
|
||||
/* 子菜单中还有子菜单 */
|
||||
.el-menu .el-sub-menu__title {
|
||||
font-size: 12px;
|
||||
min-width: $sideBarWidth !important;
|
||||
font-size: 12px;
|
||||
background-color: $subMenuBg !important;
|
||||
|
||||
&:hover {
|
||||
@@ -448,14 +367,167 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 有子集的激活菜单背景 */
|
||||
.is-active.nest-menu {
|
||||
background: $subMenuActiveBg !important;
|
||||
.el-menu-item.is-active {
|
||||
color: $subMenuActiveText !important;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active {
|
||||
transition: color 0.3s;
|
||||
.el-menu-item.is-active.nest-menu > * {
|
||||
z-index: 1;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.el-menu-item.is-active.nest-menu::before {
|
||||
position: absolute;
|
||||
inset: 0 5px;
|
||||
clear: both;
|
||||
content: "";
|
||||
background: var(--el-color-primary) !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
background: $menuBg;
|
||||
|
||||
.horizontal-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
min-width: 200px;
|
||||
height: 100%;
|
||||
padding-left: 10px;
|
||||
cursor: pointer;
|
||||
transition: all var(--pure-transition-duration) ease;
|
||||
|
||||
img {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
margin: 2px 0 0 12px;
|
||||
overflow: hidden;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 32px;
|
||||
color: $subMenuActiveText;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal-header-menu {
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.horizontal-header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
min-width: 340px;
|
||||
color: $subMenuActiveText;
|
||||
|
||||
/* 搜索 */
|
||||
.search-container,
|
||||
/* 告警 */
|
||||
.dropdown-badge,
|
||||
/* 国际化 */
|
||||
.globalization,
|
||||
/* 用户名 */
|
||||
.el-dropdown-link,
|
||||
/* 设置 */
|
||||
.set-icon {
|
||||
&:hover {
|
||||
background: $menuHover;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-badge {
|
||||
height: 48px;
|
||||
color: $subMenuActiveText;
|
||||
}
|
||||
|
||||
.globalization {
|
||||
width: 40px;
|
||||
height: 48px;
|
||||
padding: 11px;
|
||||
color: $subMenuActiveText;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
height: 48px;
|
||||
padding: 10px;
|
||||
color: $subMenuActiveText;
|
||||
cursor: pointer;
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
width: 100% !important;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-sub-menu__title {
|
||||
padding-right: var(--el-menu-base-level-padding);
|
||||
color: $menuText;
|
||||
|
||||
&:hover {
|
||||
color: $menuTitleHover !important;
|
||||
}
|
||||
}
|
||||
|
||||
.submenu-title-noDropdown,
|
||||
.el-sub-menu__title {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
background: $menuBg;
|
||||
|
||||
svg {
|
||||
position: static !important;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active > .el-sub-menu__title,
|
||||
.is-active.submenu-title-noDropdown {
|
||||
color: $subMenuActiveText !important;
|
||||
|
||||
i {
|
||||
color: $subMenuActiveText !important;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active {
|
||||
color: $subMenuActiveText !important;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,35 +535,6 @@
|
||||
min-width: $sideBarWidth !important;
|
||||
}
|
||||
|
||||
/* 有子菜单 */
|
||||
.el-menu--collapse
|
||||
.is-active.outer-most.el-sub-menu
|
||||
> .el-sub-menu__title::before,
|
||||
/* 无子菜单 */
|
||||
.el-menu--collapse .is-active.submenu-title-noDropdown.outer-most::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 2px;
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
background-color: $menuActiveBefore;
|
||||
content: "";
|
||||
clear: both;
|
||||
transition: all 0.125s ease-in-out;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.el-menu--collapse .outer-most.el-sub-menu > .el-sub-menu__title::before,
|
||||
.el-menu--collapse .submenu-title-noDropdown.outer-most::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 0;
|
||||
width: 3px;
|
||||
transform: translateY(-50%);
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
/* 手机端 */
|
||||
.mobile {
|
||||
.fixed-header {
|
||||
@@ -504,8 +547,8 @@
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
transition: transform var(--pure-transition-duration);
|
||||
width: $sideBarWidth;
|
||||
transition: transform var(--pure-transition-duration);
|
||||
}
|
||||
|
||||
&.hideSidebar {
|
||||
@@ -520,6 +563,7 @@
|
||||
|
||||
body[layout="vertical"] {
|
||||
$sideBarWidth: 210px;
|
||||
|
||||
@include merge-style($sideBarWidth);
|
||||
|
||||
.el-menu--collapse {
|
||||
@@ -537,8 +581,8 @@ body[layout="vertical"] {
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
transition: width 0.125s;
|
||||
width: 54px !important;
|
||||
transition: width var(--pure-transition-duration);
|
||||
|
||||
.is-active.submenu-title-noDropdown.outer-most {
|
||||
background: transparent !important;
|
||||
@@ -554,11 +598,10 @@ body[layout="vertical"] {
|
||||
.el-sub-menu {
|
||||
& > .el-sub-menu__title {
|
||||
& > span {
|
||||
height: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -568,7 +611,7 @@ body[layout="vertical"] {
|
||||
}
|
||||
|
||||
.el-sub-menu__title {
|
||||
padding: 0 18px !important;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,16 +638,22 @@ body[layout="vertical"] {
|
||||
|
||||
body[layout="horizontal"] {
|
||||
$sideBarWidth: 0;
|
||||
|
||||
@include merge-style($sideBarWidth);
|
||||
|
||||
.fixed-header,
|
||||
.main-container {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.fixed-header {
|
||||
width: 100%;
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
body[layout="mix"] {
|
||||
$sideBarWidth: 210px;
|
||||
|
||||
@include merge-style($sideBarWidth);
|
||||
|
||||
.el-menu--collapse {
|
||||
@@ -622,8 +671,8 @@ body[layout="mix"] {
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
transition: width 0.125s;
|
||||
width: 54px !important;
|
||||
transition: width var(--pure-transition-duration);
|
||||
|
||||
.is-active.submenu-title-noDropdown.outer-most {
|
||||
background: transparent !important;
|
||||
@@ -638,12 +687,13 @@ body[layout="mix"] {
|
||||
.el-menu--collapse {
|
||||
.el-sub-menu {
|
||||
& > .el-sub-menu__title {
|
||||
padding: 0;
|
||||
|
||||
& > span {
|
||||
height: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
}
|
||||
|
||||
.breadcrumb-leave-active {
|
||||
position: absolute;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
@@ -40,10 +41,6 @@
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.breadcrumb-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 重置el-menu的展开收起动画时长
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user