Browse Source

chore: init

master
AaronWu 1 year ago
commit
ac8acf8e9f
  1. 14
      .env
  2. 14
      .env.development
  3. 14
      .env.production
  4. 24
      .gitignore
  5. 100
      README.md
  6. 55
      components.d.ts
  7. 13
      index.html
  8. 10
      middlewares/cache.ts
  9. 11610
      package-lock.json
  10. 48
      package.json
  11. 10
      prettier.config.cjs
  12. 1
      public/iconfont.js
  13. 38
      src/App.vue
  14. 39
      src/api/account/index.ts
  15. 50
      src/api/account/model.d.ts
  16. 23
      src/api/authorize/index.ts
  17. 20
      src/api/authorize/model.d.ts
  18. 109
      src/api/dictionary/index.ts
  19. 27
      src/api/dictionary/model.d.ts
  20. 30
      src/api/login/index.ts
  21. 54
      src/api/login/model.d.ts
  22. 65
      src/api/typings.d.ts
  23. BIN
      src/assets/404.gif
  24. 1
      src/assets/analysis.svg
  25. 39
      src/assets/icons/dark.svg
  26. 1
      src/assets/icons/delete.svg
  27. 1
      src/assets/icons/edit.svg
  28. 1
      src/assets/icons/file-type-code.svg
  29. 1
      src/assets/icons/file-type-dir.svg
  30. 1
      src/assets/icons/file-type-docx.svg
  31. 1
      src/assets/icons/file-type-excel.svg
  32. 1
      src/assets/icons/file-type-img.svg
  33. 1
      src/assets/icons/file-type-music.svg
  34. 1
      src/assets/icons/file-type-office.svg
  35. 1
      src/assets/icons/file-type-pdf.svg
  36. 1
      src/assets/icons/file-type-ppt.svg
  37. 1
      src/assets/icons/file-type-psd.svg
  38. 1
      src/assets/icons/file-type-txt.svg
  39. 1
      src/assets/icons/file-type-unknown.svg
  40. 1
      src/assets/icons/file-type-video.svg
  41. 1
      src/assets/icons/file-type-zip.svg
  42. 39
      src/assets/icons/light.svg
  43. 14
      src/assets/icons/locale.svg
  44. 68
      src/assets/icons/login.svg
  45. 48
      src/assets/icons/logo.svg
  46. 1
      src/assets/icons/moon.svg
  47. 39
      src/assets/icons/realDark.svg
  48. 1
      src/assets/icons/searchoutlined.svg
  49. 39
      src/assets/icons/sidemenu.svg
  50. 1
      src/assets/icons/sun.svg
  51. 39
      src/assets/icons/topmenu.svg
  52. 1
      src/assets/icons/users.svg
  53. BIN
      src/assets/images/no-preview.png
  54. BIN
      src/assets/images/tool.png
  55. 68
      src/assets/login.svg
  56. 3
      src/components/basic/ContextMenu/index.ts
  57. 209
      src/components/basic/ContextMenu/src/ContextMenu.vue
  58. 83
      src/components/basic/ContextMenu/src/createContextMenu.ts
  59. 40
      src/components/basic/ContextMenu/src/typing.ts
  60. 1
      src/components/basic/basic-arrow/index.ts
  61. 24
      src/components/basic/basic-arrow/index.vue
  62. 94
      src/components/basic/basic-help/index.vue
  63. 16
      src/components/basic/button/button.ts
  64. 48
      src/components/basic/button/button.vue
  65. 9
      src/components/basic/button/index.ts
  66. 54
      src/components/basic/check-box/index.vue
  67. 72
      src/components/basic/iconfont/icon-font.tsx
  68. 2
      src/components/basic/iconfont/index.ts
  69. 1311
      src/components/basic/icons-select/icons.json
  70. 71
      src/components/basic/icons-select/index.vue
  71. 1
      src/components/basic/locale-picker/index.ts
  72. 60
      src/components/basic/locale-picker/index.vue
  73. 3
      src/components/basic/split-panel/index.ts
  74. 102
      src/components/basic/split-panel/index.vue
  75. 3
      src/components/basic/svg-icon/index.ts
  76. 50
      src/components/basic/svg-icon/svg-icon.vue
  77. 1
      src/components/basic/title-i18n/index.ts
  78. 26
      src/components/basic/title-i18n/index.vue
  79. 6
      src/components/core/Container/index.ts
  80. 109
      src/components/core/Container/src/collapse/CollapseContainer.vue
  81. 40
      src/components/core/Container/src/collapse/CollapseHeader.vue
  82. 17
      src/components/core/Container/src/typing.ts
  83. 27
      src/components/core/Transition/index.ts
  84. 78
      src/components/core/Transition/src/CollapseTransition.vue
  85. 73
      src/components/core/Transition/src/CreateTransition.tsx
  86. 89
      src/components/core/Transition/src/ExpandTransition.ts
  87. 7
      src/components/core/Tree/index.ts
  88. 468
      src/components/core/Tree/src/BasicTree.vue
  89. 14
      src/components/core/Tree/src/TreeIcon.ts
  90. 172
      src/components/core/Tree/src/components/TreeHeader.vue
  91. 207
      src/components/core/Tree/src/hooks/useTree.ts
  92. 204
      src/components/core/Tree/src/types/tree.ts
  93. 46
      src/components/core/Tree/style/index.less
  94. 1
      src/components/core/Tree/style/index.ts
  95. 1
      src/components/core/draggable-modal/index.ts
  96. 354
      src/components/core/draggable-modal/index.vue
  97. 13
      src/components/core/dynamic-table/index.ts
  98. 190
      src/components/core/dynamic-table/src/components/editable-cell/index.vue
  99. 3
      src/components/core/dynamic-table/src/components/index.ts
  100. 130
      src/components/core/dynamic-table/src/components/table-action.vue

14
.env

@ -0,0 +1,14 @@
# 供docker构建时使用
ENV = 'production'
# 网站前缀
VITE_BASE_URL = /
# base api
VITE_BASE_API = '/api/admin/'
VITE_BASE_SOCKET_PATH = '/ws-api'
VITE_BASE_SOCKET_NSP = '/admin'
# mock api
VITE_MOCK_API = '/mock-api/'

14
.env.development

@ -0,0 +1,14 @@
# 只在开发模式中被载入
# 网站前缀
VITE_BASE_API_URL = http://192.168.3.118:10010/admin-api/
# base api
VITE_BASE_API = '/admin-api/'
VITE_BASE_SOCKET_PATH = '/ws-api'
VITE_BASE_SOCKET_NSP = '/admin'
# mock api
VITE_MOCK_API = '/mock-api/'
VITE_DROP_CONSOLE = false

14
.env.production

@ -0,0 +1,14 @@
# 只在生产模式中被载入
# 网站前缀
VITE_BASE_API_URL = http://192.168.3.118:10010/admin-api/
# base api
VITE_BASE_API = '/admin-api/'
VITE_BASE_SOCKET_PATH = '/ws-api'
VITE_BASE_SOCKET_NSP = '/admin'
# mock api
VITE_MOCK_API = '/mock-api/'
VITE_DROP_CONSOLE = true

24
.gitignore

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

100
README.md

@ -0,0 +1,100 @@
<!--
* @Author: AaronWu 2463371514@qq.com
* @Date: 2025-03-31 15:12:18
* @LastEditors: AaronWu 2463371514@qq.com
* @LastEditTime: 2025-03-31 15:12:42
* @FilePath: /IssueSupportManage/README.md
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
# IssueSupportManage
template
## Getting started
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
## Add your files
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
```
cd existing_repo
git remote add origin https://git.koalacloud.com/koalacloud/web/kaola-cloud-platform/koalacloud-platform-web.git
git branch -M main
git push -uf origin main
```
## Integrate with your tools
- [ ] [Set up project integrations](https://git.koalacloud.com/koalacloud/web/kaola-cloud-platform/koalacloud-platform-web/-/settings/integrations)
## Collaborate with your team
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
## Test and Deploy
Use the built-in continuous integration in GitLab.
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
***
# Editing this README
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
## Name
Choose a self-explaining name for your project.
## Description
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
## Badges
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
## Visuals
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
## Installation
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
## Usage
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
## Support
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
## Roadmap
If you have ideas for releases in the future, it is a good idea to list them in the README.
## Contributing
State if you are open to contributions and what your requirements are for accepting them.
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
## Authors and acknowledgment
Show your appreciation to those who have contributed to the project.
## License
For open source projects, say how it is licensed.
## Project status
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.

55
components.d.ts

@ -0,0 +1,55 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AButton: typeof import('ant-design-vue/es')['Button']
ACard: typeof import('ant-design-vue/es')['Card']
ACol: typeof import('ant-design-vue/es')['Col']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AInput: typeof import('ant-design-vue/es')['Input']
ALayout: typeof import('ant-design-vue/es')['Layout']
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
ApiSelect: typeof import('./src/components/core/schema-form/src/components/ApiSelect.vue')['default']
ARow: typeof import('ant-design-vue/es')['Row']
ASelect: typeof import('ant-design-vue/es')['Select']
ASpin: typeof import('ant-design-vue/es')['Spin']
ATypographyText: typeof import('ant-design-vue/es')['TypographyText']
BasicArrow: typeof import('./src/components/basic/basic-arrow/index.vue')['default']
BasicHelp: typeof import('./src/components/basic/basic-help/index.vue')['default']
BasicTree: typeof import('./src/components/core/Tree/src/BasicTree.vue')['default']
Button: typeof import('./src/components/basic/button/button.vue')['default']
CheckBox: typeof import('./src/components/basic/check-box/index.vue')['default']
CollapseContainer: typeof import('./src/components/core/Container/src/collapse/CollapseContainer.vue')['default']
CollapseHeader: typeof import('./src/components/core/Container/src/collapse/CollapseHeader.vue')['default']
CollapseTransition: typeof import('./src/components/core/Transition/src/CollapseTransition.vue')['default']
ColumnSetting: typeof import('./src/components/core/dynamic-table/src/components/table-settings/column-setting.vue')['default']
ContextMenu: typeof import('./src/components/basic/ContextMenu/src/ContextMenu.vue')['default']
DraggableModal: typeof import('./src/components/core/draggable-modal/index.vue')['default']
DynamicTable: typeof import('./src/components/core/dynamic-table/src/dynamic-table.vue')['default']
EditableCell: typeof import('./src/components/core/dynamic-table/src/components/editable-cell/index.vue')['default']
FormAction: typeof import('./src/components/core/schema-form/src/components/form-action.vue')['default']
IconsSelect: typeof import('./src/components/basic/icons-select/index.vue')['default']
IframePage: typeof import('./src/components/iframe-page/index.vue')['default']
LocalePicker: typeof import('./src/components/basic/locale-picker/index.vue')['default']
RefreshSetting: typeof import('./src/components/core/dynamic-table/src/components/table-settings/refresh-setting.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SchemaForm: typeof import('./src/components/core/schema-form/src/schema-form.vue')['default']
SchemaFormItem: typeof import('./src/components/core/schema-form/src/schema-form-item.vue')['default']
SizeSetting: typeof import('./src/components/core/dynamic-table/src/components/table-settings/size-setting.vue')['default']
SplitPanel: typeof import('./src/components/basic/split-panel/index.vue')['default']
SvgIcon: typeof import('./src/components/basic/svg-icon/svg-icon.vue')['default']
TableAction: typeof import('./src/components/basic/tableAction/index.vue')['default']
TableSettings: typeof import('./src/components/core/dynamic-table/src/components/table-settings/index.vue')['default']
TitleI18n: typeof import('./src/components/basic/title-i18n/index.vue')['default']
ToolBar: typeof import('./src/components/core/dynamic-table/src/components/tool-bar/index.vue')['default']
TreeHeader: typeof import('./src/components/core/Tree/src/components/TreeHeader.vue')['default']
}
}

13
index.html

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

10
middlewares/cache.ts

@ -0,0 +1,10 @@
// middlewares/cache.ts
import { Request, Response, NextFunction } from 'express'
export default function cacheMiddleware(req: Request, res: Response, next: NextFunction) {
if (process.env.NODE_ENV === 'production') { // 只在生产环境中启用缓存
const cacheTime = 60 * 60 * 24 // 设置缓存时间,这里为一天
res.setHeader('Cache-Control', `public, max-age=${cacheTime}`)
}
next()
}

11610
package-lock.json

File diff suppressed because it is too large

48
package.json

@ -0,0 +1,48 @@
{
"name": "my-vue-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"serve": "vite",
"build": "rimraf dist && cross-env NODE_ENV=production vite build",
"preview": "vite preview",
"clean:cache": "npx rimraf node_modules/.cache/ && npx rimraf node_modules/.vite",
"clean:lib": "npx rimraf node_modules",
"format": "prettier --write ."
},
"dependencies": {
"@vitejs/plugin-vue-jsx": "^3.0.1",
"@vueuse/core": "~9.6.0",
"ant-design-vue": "^3.2.20",
"axios": "^1.4.0",
"express": "^4.18.2",
"file-saver": "^2.0.5",
"pinia": "^2.1.3",
"qs": "^6.11.2",
"sortablejs": "^1.15.0",
"vite-plugin-windicss": "^1.9.0",
"vue": "^3.2.47",
"vue-i18n": "9.2.2",
"vue-router": "^4.2.1",
"windicss": "^3.5.6",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/node": "^20.2.3",
"@vitejs/plugin-vue": "^4.1.0",
"less": "^4.1.3",
"less-loader": "^11.1.0",
"lodash-es": "~4.17.21",
"nprogress": "~1.0.0-1",
"rimraf": "^5.0.1",
"rollup-plugin-terser": "^7.0.2",
"typescript": "^5.0.2",
"unplugin-vue-components": "~0.22.12",
"unplugin-vue-define-options": "~1.0.0",
"vite": "^4.3.2",
"vite-plugin-svg-icons": "~2.0.1",
"vite-tsconfig-paths": "^4.2.0",
"vue-tsc": "^1.4.2"
}
}

10
prettier.config.cjs

@ -0,0 +1,10 @@
module.exports = {
printWidth: 100,
semi: true,
vueIndentScriptAndStyle: true,
singleQuote: true,
trailingComma: 'all',
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
};

1
public/iconfont.js

File diff suppressed because one or more lines are too long

38
src/App.vue

@ -0,0 +1,38 @@
<template>
<ConfigProvider :locale="getAntdLocale">
<router-view #="{ Component }">
<component :is="Component" />
</router-view>
</ConfigProvider>
</template>
<script setup lang="ts">
import { watchEffect } from 'vue';
import { useRoute } from 'vue-router';
import { ConfigProvider } from 'ant-design-vue';
import { transformI18n } from './hooks/useI18n';
import { useLocale } from '@/locales/useLocale';
const route = useRoute();
const { getAntdLocale } = useLocale();
watchEffect(() => {
if (route.meta?.title) {
//
document.title = transformI18n(route.meta.title);
}
});
</script>
<style lang="less">
.ant-message-custom-content {
display: flex;
align-items: center;
}
.anticon svg{
display: block !important;
}
</style>

39
src/api/account/index.ts

@ -0,0 +1,39 @@
import type { BaseResponse } from '@/utils/request';
import { request } from '@/utils/request';
export function updateAccountInfo(data: any) {
return request<BaseResponse<any>>({
url: 'account/update',
method: 'post',
data,
});
}
export function updatePassword(data: any) {
return request({
url: 'account/password',
method: 'post',
data,
});
}
export function getInfo() {
return request<API.AdminUserInfo>({
url: 'account/info',
method: 'get',
});
}
export function permmenu() {
return request<API.PermMenu>({
url: 'account/permmenu',
method: 'get',
});
}
export function logout() {
return request({
url: 'account/logout',
method: 'post',
});
}

50
src/api/account/model.d.ts

@ -0,0 +1,50 @@
declare namespace API {
type Menu = {
createTime: Date;
updateTime: Date;
id: number;
parentId: number;
name: string;
router: string;
perms: string;
/** 当前菜单类型 0: 目录 | 1: 菜单 | 2: 权限 */
type: 0 | 1 | 2;
icon: string;
orderNum: number;
viewPath: string;
keepalive: boolean;
isShow: boolean;
/** 是否外链 */
isExt?: boolean;
/**
* 1: 新窗口打开
* 2: iframe
*/
openMode?: 1 | 2;
};
type PermMenu = {
menus: Menu[];
perms: string[];
};
type AdminUserInfo = {
createTime: Date;
updateTime: Date;
id: number;
departmentId: number;
name: string;
username: string;
password: string;
psalt: string;
nickName: string;
headImg: string;
loginIp: string;
email: string;
phone: string;
remark: string;
status: number;
roles: number[];
departmentName: string;
};
}

23
src/api/authorize/index.ts

@ -0,0 +1,23 @@
import type { BaseResponse } from '@/utils/request';
import { request } from '@/utils/request';
/**
* @description
* @param {SearchPageListParams} data
* @returns
*/
export function fetchAuthorizePageList(data: API.SearchPageListParams) {
return request<BaseResponse<API.SearchPageListResult>>(
{
url: 'grant/getTenantAppPage',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}

20
src/api/authorize/model.d.ts

@ -0,0 +1,20 @@
import { AnyRecord } from "dns";
declare namespace API {
type infoType = {
id:string
name:string
}
/** 授权信息类型 */
type AuthorizeInfoType = {
id?: string;
tenant:infoType;
application:infoType;
expirationTime:string;
echoMap:AnyRecord
};
}

109
src/api/dictionary/index.ts

@ -0,0 +1,109 @@
import type { BaseResponse } from '@/utils/request';
import { request } from '@/utils/request';
/**
* @description
* @param {SearchListParams} data
* @returns
*/
export function fetchDictionaryList(data: API.SearchListParams) {
return request<BaseResponse<API.SearchListResult>>(
{
url: 'system/dict/query',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
* @param {SearchPageListParams} data
* @returns
*/
export function fetchDictionaryPageList(data: API.SearchPageListParams) {
return request<BaseResponse<API.SearchPageListResult>>(
{
url: 'system/dict/page',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
* @param {DictionaryInfoType} data
* @returns
*/
export function createDictionary(data: API.DictionaryInfoType) {
return request<BaseResponse<API.SearchListResult>>(
{
url: 'system/dict/create',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
* @param {DictionaryInfoType} data
* @returns
*/
export function updateDictionary(data: API.DictionaryInfoType) {
return request<BaseResponse<API.SearchListResult>>(
{
url: 'system/dict/update',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
*/
export function findOneById(params: {
id:string
}) {
return request<API.SearchListResult>({
url: `system/dict/getById`,
method: 'get',
params
});
}
/**
* @description
*/
export function deleteDictionaryById(params:API.DeleteDictionaryParams) {
return request<API.SearchListResult>({
url: `system/dict/delById`,
method: 'post',
params
});
}
/**
* @description
*/
export function deleteBatchDictionaryById(data:API.DeleteBatchDictionaryParams) {
return request<API.SearchListResult>({
url: `system/dict/deleteBatch`,
method: 'delete',
data,
});
}

27
src/api/dictionary/model.d.ts

@ -0,0 +1,27 @@
declare namespace API {
/** 用户信息类型 */
type DictionaryInfoType = {
id?: string;
dictKey: string;
name: string;
status: boolean;
createTime?: string;
};
type DictionaryItemInfoType = DictionaryInfoType & {
sortValue: number | string;
};
type CreateDictionaryParams = {
dictKey: string;
name: string;
status: boolean;
createTime: string;
};
type DeleteDictionaryParams = {
id: string;
};
type DeleteBatchDictionaryParams = string[];
}

30
src/api/login/index.ts

@ -0,0 +1,30 @@
import type { BaseResponse } from '@/utils/request';
import { request } from '@/utils/request';
/**
* @description
* @param {LoginParams} data
* @returns
*/
export function login(data: API.LoginParams) {
return request<BaseResponse<API.LoginResult>>(
{
url: 'auth/login',
method: 'post',
data,
},
{
isGetDataDirectly: false,
},
);
}
/**
* @description
*/
export function getImageCaptcha(params?: API.CaptchaParams) {
return request<API.CaptchaResult>({
url: 'captcha/img',
method: 'get',
params,
});
}

54
src/api/login/model.d.ts

@ -0,0 +1,54 @@
declare namespace API {
type Menu = {
createTime: Date;
updateTime: Date;
id: number;
parentId: number;
name: string;
router: string;
perms: string;
/** 当前菜单类型 0: 目录 | 1: 菜单 | 2: 权限 */
type: 0 | 1 | 2;
icon: string;
orderNum: number;
viewPath: string;
keepalive: boolean;
isShow: boolean;
/** 是否外链 */
isExt?: boolean;
/**
* 1: 新窗口打开
* 2: iframe
*/
openMode?: 1 | 2;
};
/** 登录参数 */
type LoginParams = {
captchaId?: string;
password: string;
username: string;
verifyCode?: string;
};
/** 登录成功结果 */
type LoginResult = {
accessToken: string;
expiresTime:string;
refreshToken:string;
userId:string;
};
/** 获取验证码参数 */
type CaptchaParams = {
width?: number;
height?: number;
};
/** 获取验证码结果 */
type CaptchaResult = {
img: string;
id: string;
};
}

65
src/api/typings.d.ts

@ -0,0 +1,65 @@
// @ts-ignore
/* eslint-disable */
declare namespace API {
/** 查询列表参数 */
type SearchListParams = {
name?: string;
};
/** 查询分页列表参数 */
type SearchPageListParams = {
model: SearchListParams;
current: number;
size: number;
sort?: string;
order?: string;
};
/** 查询列表成功结果 */
type SearchListResult = any[]
/** 查询分页列表成功结果 */
type SearchPageListResult = {
// code: string;
// data: {
// list:TenantInfoType[]
// total:string
// };
// msg: string;
list:any[]
total:string
};
/** 全局通过表格查询返回结果 */
type TableListResult<T = any> = {
list: T;
pagination?: PaginationResult;
};
/** 全局通用表格分页返回数据结构 */
type PaginationResult = {
page: number;
size: number;
total: number;
};
/** 全局通用表格分页请求参数 */
type PageParams<T = any> = {
limit?: number;
page?: number;
} & {
[P in keyof T]?: T[P];
};
type ErrorResponse = {
/** 业务约定的错误码 */
errorCode: string;
/** 业务上的错误信息 */
errorMessage?: string;
/** 业务上的请求是否成功 */
success?: boolean;
};
}

BIN
src/assets/404.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

1
src/assets/analysis.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

39
src/assets/icons/dark.svg

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

1
src/assets/icons/delete.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1685352174209" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4435" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72z" fill="#f14a34" p-id="4436"></path><path d="M864 256H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z" p-id="4437" fill="#f14a34"></path></svg>

After

Width:  |  Height:  |  Size: 760 B

1
src/assets/icons/edit.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1685351688292" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3039" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M848.937379 537.578518a38.363045 38.363045 0 0 0-38.387037 38.387037c0 1.495495 0.111962 2.879028 0.215927 4.262561h-0.215927V947.040246H76.934078V213.423982h400.92861c21.224833 0 38.387037-17.170202 38.387037-38.387037 0-21.224833-17.162204-38.387037-38.387037-38.387037H76.934078a76.72609 76.72609 0 0 0-76.774074 76.774074V947.040246c0 42.441668 34.332406 76.774074 76.774074 76.774074h733.616264c42.441668 0 76.774074-34.332406 76.774074-76.774074V580.228116h-0.20793c0.103965-1.383533 0.20793-2.767066 0.20793-4.262561 0-21.216835-17.162204-38.387037-38.387037-38.387037z" p-id="3040"></path><path d="M1001.101994 107.107885L916.546547 22.552438c-29.965881-29.965881-78.805388-29.749954-108.763271 0.215927l-423.536975 423.536975a34.100485 34.100485 0 0 0-7.25355 10.876327L270.572688 708.184906c-5.965985 14.075247-1.80739 28.262456 7.25355 37.427361 9.06094 9.172902 23.352114 13.331498 37.531326 7.357515l251.115201-106.308101c4.054631-1.703425 7.677407-4.158596 10.876327-7.25355l423.536975-423.536975c29.965881-29.957883 30.181808-78.797391 0.215927-108.763271zM528.837474 579.164475l-146.510525 62.059043 62.059043-146.510525L741.669602 197.429384l84.451481 84.451481L528.837474 579.164475z m417.666957-417.674955l-66.105677 66.113674-84.451481-84.451481 66.105677-66.113674 0.215927-0.20793 84.451481 84.451482-0.215927 0.207929z" p-id="3041"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

1
src/assets/icons/file-type-code.svg

@ -0,0 +1 @@
<svg t="1619506354975" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2334" width="64" height="64"><path d="M137.90246 0.00041a48.573421 48.573421 0 0 0-35.589106 15.293433A53.964778 53.964778 0 0 0 87.0404 50.934149V968.345622a48.706541 48.706541 0 0 0 15.272954 35.640306 49.97118 49.97118 0 0 0 35.589106 15.293434h746.336982a48.639981 48.639981 0 0 0 35.589105-15.293434 50.37054 50.37054 0 0 0 15.272954-35.640306V288.717094L646.727857 0.00041H137.90246z" fill="#FF8976" p-id="2335"></path><path d="M935.101501 288.717094h-237.445025c-27.822069-0.6656-50.22718-23.075831-50.928619-50.93374V0.00041l288.373644 288.716684z" fill="#FFD0C8" p-id="2336"></path><path d="M415.257869 564.731064l-124.70267 49.26974 124.70267 49.27486v39.096305l-172.175291-68.756453v-39.096304l172.175291-68.756453v38.963185zM539.02358 451.686629h39.045104l-95.016922 254.801818h-38.978544l94.950362-254.801818z m67.860452 212.377515l124.702671-49.26974-124.702671-49.269741v-39.101424l172.175292 67.957733v39.895024l-172.175292 68.823012V664.064144z" fill="#FFFFFF" p-id="2337"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
src/assets/icons/file-type-dir.svg

@ -0,0 +1 @@
<svg t="1619506230415" class="icon" viewBox="0 0 1208 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1594" width="64" height="64"><path d="M132.51584 120.4736h879.4368c33.26976 0 60.2368 26.96704 60.2368 60.23168v409.6c0 33.26976-26.96704 60.2368-60.2368 60.2368H132.51584c-33.26464 0-60.23168-26.96704-60.23168-60.2368v-409.6c0-33.26464 26.96704-60.2368 60.23168-60.2368z" fill="#F9B552" p-id="1595"></path><path d="M469.8368 0c73.18528 0 132.51584 59.33056 132.51584 132.51584v84.3264h469.8368c73.18528 0 132.51584 59.33568 132.51584 132.52096v542.12096c0 73.18528-59.33056 132.51584-132.51584 132.51584H132.51584A132.51584 132.51584 0 0 1 0 891.48416V349.3632c0-4.03456 0.1792-8.06912 0.54272-12.04736A134.25664 134.25664 0 0 1 0 325.2736V132.51584C0 59.33056 59.33056 0 132.51584 0h337.32096z" fill="#FFCF5C" p-id="1596"></path></svg>

After

Width:  |  Height:  |  Size: 853 B

1
src/assets/icons/file-type-docx.svg

@ -0,0 +1 @@
<svg t="1619506254031" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1741" width="64" height="64"><path d="M594.944 0l335.12448 341.31968v563.2c0 65.9968-52.50048 119.48032-117.29408 119.48032H209.54624c-64.7936 0-117.2992-53.5296-117.2992-119.48032V119.48032C92.25216 53.48352 144.75776 0 209.55136 0H594.944z" fill="#5895FF" p-id="1742"></path><path d="M930.06848 341.31968h-211.9168c-64.74752 0-123.20768-59.48928-123.20768-125.4912V0l335.12448 341.31968z" fill="#FFFFFF" fill-opacity=".4" p-id="1743"></path><path d="M427.37664 725.31968V768H259.8144v-42.68032h167.56224zM594.944 640v42.68032H259.8144V640H594.944z m0-85.31968v42.63936H259.8144v-42.63936H594.944z m0-85.36064V512H259.8144v-42.68032H594.944z" fill="#FFFFFF" p-id="1744"></path></svg>

After

Width:  |  Height:  |  Size: 800 B

1
src/assets/icons/file-type-excel.svg

@ -0,0 +1 @@
<svg t="1619506332655" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2186" width="64" height="64"><path d="M594.944 0l335.12448 341.31968v563.2c0 65.9968-52.50048 119.48032-117.29408 119.48032H209.54624c-64.7936 0-117.2992-53.5296-117.2992-119.48032V119.48032C92.25216 53.48352 144.75776 0 209.55136 0H594.944z" fill="#1ABF74" p-id="2187"></path><path d="M930.06848 341.31968h-211.9168c-64.74752 0-123.20768-59.48928-123.20768-125.4912V0l335.12448 341.31968z" fill="#FFFFFF" fill-opacity=".4" p-id="2188"></path><path d="M594.61632 426.82368v0.2304h0.09216V768H566.8352v-0.0512h-83.968 0.04608H399.7696h0.0512-139.91936v-28.3904l0.0512-0.04608v-85.82656h-0.0512v-28.39552h0.0512v-85.82656h-0.09728v-28.39552l0.09216-0.04608V455.168h-0.09216v-28.3904h334.70976l0.04608 0.04608z m-222.62784 226.86208H287.88224v85.82656h84.10624v-85.82656z m83.08224 0h-55.2448v85.82656h55.19872v-85.82656h0.0512z m111.75936 0H482.90816v85.82656H566.784v-85.82656h0.04608z m-194.8416-114.2272H287.88224v85.83168h84.10624v-85.82656z m83.08224 0h-55.2448v85.83168h55.19872v-85.82656h0.0512z m111.75936 0H482.90816v85.83168H566.784v-85.82656h0.04608z m0-84.24448H287.88224v55.808H566.784V455.168l0.04608 0.0512z" fill="#FFFFFF" p-id="2189"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
src/assets/icons/file-type-img.svg

@ -0,0 +1 @@
<svg t="1619506370346" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2482" width="64" height="64"><path d="M137.90246 0.00041a48.573421 48.573421 0 0 0-35.589106 15.293433A53.964778 53.964778 0 0 0 87.0404 50.934149V968.345622a48.706541 48.706541 0 0 0 15.272954 35.640306 49.97118 49.97118 0 0 0 35.589106 15.293434h746.336982a48.639981 48.639981 0 0 0 35.589105-15.293434 50.37054 50.37054 0 0 0 15.272954-35.640306V288.717094L646.727857 0.00041H137.90246z" fill="#FF5562" p-id="2483"></path><path d="M935.101501 288.717094h-237.445025c-27.822069-0.6656-50.22718-23.075831-50.928619-50.93374V0.00041l288.373644 288.716684z" fill="#FFBBC0" p-id="2484"></path><path d="M772.290686 715.069564l-106.239957-164.766655a16.593913 16.593913 0 0 0-14.412794-7.649276 18.851832 18.851832 0 0 0-14.407675 7.649276l-56.837097 88.299485-127.221709-206.131117a16.527353 16.527353 0 0 0-14.407674-7.644157 18.851832 18.851832 0 0 0-14.412795 7.649277L249.656655 715.264123a15.447034 15.447034 0 0 0 0 16.957434 15.667194 15.667194 0 0 0 14.407675 8.509436h493.547322a18.851832 18.851832 0 0 0 15.272954-8.509436 16.977913 16.977913 0 0 0-0.7936-16.957434l0.19968-0.199679zM628.070584 403.149048a42.490863 42.490863 0 0 0 26.14271 39.306225 42.388463 42.388463 0 0 0 46.264301-9.169917 42.531823 42.531823 0 0 0 9.226236-46.310381 42.429423 42.429423 0 0 0-39.203824-26.24511c-23.408631 0-42.393583 18.979832-42.434543 42.419183z" fill="#FFFFFF" p-id="2485"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

1
src/assets/icons/file-type-music.svg

@ -0,0 +1 @@
<svg t="1619506298580" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2038" width="64" height="64"><path d="M594.944 0l335.12448 341.31968v563.2c0 65.9968-52.50048 119.48032-117.29408 119.48032H209.54624c-64.7936 0-117.2992-53.5296-117.2992-119.48032V119.48032C92.25216 53.48352 144.75776 0 209.55136 0H594.944z" fill="#FFC547" p-id="2039"></path><path d="M930.06848 341.31968h-211.9168c-64.74752 0-123.20768-59.48928-123.20768-125.4912V0l335.12448 341.31968z" fill="#FFFFFF" fill-opacity=".4" p-id="2040"></path><path d="M568.7808 446.464V675.84a27.2384 27.2384 0 0 1-6.79424 18.24768 45.66016 45.66016 0 0 1-17.31584 12.38016 112.64 112.64 0 0 1-20.80256 6.51776c-6.9376 1.43872-13.40416 2.18624-19.40992 2.18624-6.05184 0-12.52352-0.74752-19.40992-2.14016a112.45568 112.45568 0 0 1-20.80768-6.56384 45.70624 45.70624 0 0 1-17.31584-12.38016A27.22816 27.22816 0 0 1 440.08448 675.84c0-6.84032 2.3296-12.89216 6.84032-18.24768a45.70624 45.70624 0 0 1 17.31584-12.38016c6.98368-2.93376 13.91616-5.12 20.80768-6.51776 6.8864-1.4848 13.35808-2.18624 19.4048-2.18624 14.05952 0 26.95168 2.65216 38.63552 8.00768V534.528L388.608 583.07584v145.21856a27.22816 27.22816 0 0 1-6.79936 18.2016 45.66016 45.66016 0 0 1-17.31584 12.38016 112.64 112.64 0 0 1-20.80256 6.56384c-6.9376 1.39776-13.40416 2.14016-19.40992 2.14016-6.05184 0-12.52352-0.69632-19.40992-2.14016a112.50176 112.50176 0 0 1-20.80768-6.51776A45.70624 45.70624 0 0 1 266.752 746.496a27.23328 27.23328 0 0 1-6.84544-18.24768c0-6.79424 2.3296-12.89216 6.84544-18.19648a45.70624 45.70624 0 0 1 17.31072-12.38016c6.98368-2.93376 13.92128-5.12 20.80768-6.56384a94.80192 94.80192 0 0 1 19.4048-2.14016c14.05952 0 26.9568 2.65216 38.63552 7.95648V498.87232a19.87584 19.87584 0 0 1 13.68576-18.84672l167.28576-52.41344c1.80224-0.57856 3.6864-0.86016 5.5808-0.83968 5.4016 0 9.96352 1.90976 13.68576 5.72928 3.77344 3.86048 5.632 8.46848 5.632 13.96224z" fill="#FFFFFF" p-id="2041"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

1
src/assets/icons/file-type-office.svg

@ -0,0 +1 @@
<svg t="1619506408333" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2778" width="64" height="64"><path d="M116.91008 0h552.05888l262.77376 257.70496V995.4304c0 15.7696-13.19936 28.5696-29.46048 28.5696H116.91008c-16.26112 0-29.45536-12.8-29.45536-28.5696V28.5696C87.4496 12.8 100.64896 0 116.91008 0z" fill="#4A8DFF" p-id="2779"></path><path d="M668.96896 0v229.13536c0 15.79008 13.19936 28.5696 29.46048 28.5696h233.31328L668.96896 0z" fill="#E5F0FF" p-id="2780"></path><path d="M722.7648 823.53664c25.3952 26.96192 71.7056 9.55392 71.7056-26.96704V371.44064h-82.51392v321.97632l-163.96288-174.1824c-7.81312-8.29952-18.87232-13.0304-30.464-13.0304-11.58656 0-22.64064 4.73088-30.45888 13.0304l-163.96288 174.1824v-321.9968H240.62976v425.14944c0 36.52096 46.31552 53.92896 71.71072 26.96704l205.2096-217.99424 205.2096 217.99424z" fill="#FFFFFF" p-id="2781"></path></svg>

After

Width:  |  Height:  |  Size: 919 B

1
src/assets/icons/file-type-pdf.svg

@ -0,0 +1 @@
<svg t="1619506497326" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3076" width="64" height="64"><path d="M116.91008 0h552.05888l262.77376 257.70496V995.4304c0 15.7696-13.19936 28.5696-29.46048 28.5696H116.91008c-16.26112 0-29.45536-12.8-29.45536-28.5696V28.5696C87.4496 12.8 100.64896 0 116.91008 0z" fill="#FF4867" p-id="3077"></path><path d="M668.96896 0v229.13536c0 15.79008 13.19936 28.5696 29.46048 28.5696h233.31328L668.96896 0z" fill="#FF97A9" p-id="3078"></path><path d="M721.3056 854.87104c-52.31616 0-99.24608-87.0912-123.91936-143.70304-41.5232-16.80896-87.31648-32.512-131.77856-42.66496-38.912 24.89856-105.12384 62.17216-155.9808 62.17216-31.54944 0-54.26688-15.39584-62.60736-42.1888-6.38976-22.05184-0.98816-37.2736 5.87776-45.5168 13.40928-17.7664 41.0368-26.79296 82.4064-26.79296 33.52064 0 76.01664 5.69856 123.4432 16.7936a785.0496 785.0496 0 0 0 89.26208-71.3728c-12.2624-56.45824-25.67168-147.968 8.3456-190.1568 16.83456-20.14208 42.496-26.79808 73.5488-17.7664 34.01728 9.5232 46.93504 29.66016 50.85696 45.5168 14.39232 55.04-50.85696 129.25952-94.82752 172.88192 9.81504 37.7344 22.7328 77.55264 38.43072 114.0224 63.09888 27.2896 138.1376 68.0448 146.65216 112.47104 3.4304 15.36-1.4848 29.6448-14.39744 42.17344-11.12064 8.87296-22.87616 14.11072-35.31776 14.11072v0.02048z m-47.03744-96.02048c11.50464 24.85248 22.48704 36.57728 28.27776 36.57728 0.88064 0 2.14528-0.37888 3.92192-1.8944 2.1504-2.28864 2.1504-3.80928 1.792-5.20704-1.19296-6.51776-10.9056-17.23904-33.9968-29.47584zM342.07744 658.2784c-18.4064 0-23.4752 4.31616-25.00608 6.33344-0.44032 0.64512-1.76128 2.59072-0.44032 7.60832 1.09056 4.32128 4.1728 8.92928 13.70624 8.92928 11.95008 0 29.24544-6.5536 49.34144-18.26304-14.37696-3.08736-27.04384-4.608-37.60128-4.608z m180.18816-22.18496c10.99776 2.9696 22.4 6.8096 32.99328 10.752a280.9344 280.9344 0 0 1-9.58976-29.696 722.85696 722.85696 0 0 1-23.3984 18.944z m77.7216-214.3744a11.71456 11.71456 0 0 0-9.1136 4.00896c-7.28064 8.89344-8.0896 31.29856-2.4576 59.99104 21.3248-22.20544 32.9216-42.5984 30.03392-53.504-0.40448-1.60768-1.6384-6.48704-11.5968-9.30304a24.01792 24.01792 0 0 0-6.8608-1.19296z" fill="#FFFFFF" p-id="3079"></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

1
src/assets/icons/file-type-ppt.svg

@ -0,0 +1 @@
<svg t="1619506516185" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3224" width="64" height="64"><path d="M116.91008 0h552.05888l262.77376 257.70496V995.4304c0 15.7696-13.19936 28.5696-29.46048 28.5696H116.91008c-16.26112 0-29.45536-12.8-29.45536-28.5696V28.5696C87.4496 12.8 100.64896 0 116.91008 0z" fill="#FF7861" p-id="3225"></path><path d="M668.96896 0v229.13536c0 15.79008 13.19936 28.5696 29.46048 28.5696h233.31328L668.96896 0z" fill="#FFB0A4" p-id="3226"></path><path d="M695.59296 473.25696c9.18528 9.09824 14.0288 22.19008 14.0288 41.59488 0 19.42016-4.84352 32.512-14.0288 41.60512-9.79968 9.728-26.3936 16.9472-50.90816 20.6848h-363.9808c-22.784 0-41.25184 17.90976-41.25184 40.00768v179.42016c0 14.29504 7.8592 27.50464 20.62336 34.65216a42.35776 42.35776 0 0 0 41.24672 0c12.76416-7.14752 20.62848-20.35712 20.62336-34.65216v-139.43296h325.82144c1.93536 0 3.8912-0.12288 5.82144-0.39424 88.11008-12.20096 138.5216-62.22336 138.5216-141.89056 0-79.65184-50.41152-129.67424-138.5216-141.8752a42.04544 42.04544 0 0 0-5.82144-0.4096H280.69888c-22.784 0-41.24672 17.90976-41.24672 40.00256 0 22.0928 18.46784 40.00256 41.24672 40.00256h363.98592c24.51456 3.7376 41.10848 10.9568 50.90816 20.66944v0.01536z" fill="#FFFFFF" p-id="3227"></path></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
src/assets/icons/file-type-psd.svg

@ -0,0 +1 @@
<svg t="1619506202991" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1446" width="64" height="64"><path d="M391.432258 376.567742h-67.716129v142.03871h67.716129c24.774194 0 42.941935-4.954839 54.503226-16.516129 11.56129-11.56129 16.516129-29.729032 16.516129-54.503226s-6.606452-42.941935-18.167742-52.851613-31.380645-18.167742-52.851613-18.167742z" fill="#169DFB" p-id="1447"></path><path d="M758.090323 36.335484L736.619355 14.864516h-545.032258c-46.245161 0-82.580645 36.335484-82.580645 82.580645v825.806452c0 46.245161 36.335484 82.580645 82.580645 82.580645h644.129032c46.245161 0 82.580645-36.335484 82.580645-82.580645V201.496774l-160.206451-165.16129zM391.432258 551.63871h-67.716129v113.96129c0 6.606452-1.651613 11.56129-4.954839 14.864516s-8.258065 4.954839-14.864516 4.954839-13.212903-1.651613-14.864516-4.954839c-3.303226-3.303226-4.954839-8.258065-4.954839-14.864516V365.006452c0-9.909677 1.651613-16.516129 4.954839-19.819355 3.303226-3.303226 11.56129-4.954839 23.122581-4.954839h77.625806c37.987097 0 64.412903 8.258065 82.580645 24.774194s26.425806 42.941935 26.425807 79.277419c1.651613 72.670968-34.683871 107.354839-107.354839 107.354839z m343.535484 74.32258c-4.954839 13.212903-13.212903 23.122581-21.470968 33.032258s-21.470968 16.516129-34.683871 21.470968c-13.212903 4.954839-28.077419 8.258065-44.593548 8.258065-13.212903 0-24.774194-1.651613-36.335484-4.954839-11.56129-3.303226-23.122581-6.606452-33.032258-11.56129-14.864516-6.606452-23.122581-14.864516-28.077419-23.122581-4.954839-8.258065-4.954839-14.864516 0-21.470968 3.303226-4.954839 8.258065-6.606452 13.212903-6.606451 4.954839 0 9.909677 1.651613 14.864516 6.606451 9.909677 6.606452 21.470968 11.56129 33.032258 16.516129 11.56129 4.954839 24.774194 6.606452 37.987097 6.606452 8.258065 0 16.516129-1.651613 24.774193-4.954839 8.258065-3.303226 14.864516-8.258065 21.470968-13.212903 6.606452-4.954839 11.56129-13.212903 14.864516-19.819355 3.303226-8.258065 4.954839-16.516129 4.954839-26.425806 0-9.909677-1.651613-18.167742-4.954839-24.774194-3.303226-8.258065-8.258065-14.864516-14.864516-19.819355l-19.819355-14.864516c-8.258065-3.303226-14.864516-8.258065-21.470968-9.909677-13.212903-4.954839-26.425806-11.56129-37.987096-16.516129-11.56129-4.954839-21.470968-11.56129-29.729033-19.819355-8.258065-6.606452-14.864516-16.516129-19.819354-26.425807-4.954839-9.909677-6.606452-23.122581-6.606452-36.335483 0-13.212903 3.303226-24.774194 8.258064-36.335484 4.954839-9.909677 11.56129-19.819355 21.470968-26.425807 8.258065-8.258065 18.167742-13.212903 29.729032-16.516129 11.56129-3.303226 23.122581-4.954839 36.335484-4.954839 13.212903 0 24.774194 1.651613 36.335484 4.954839 11.56129 3.303226 21.470968 8.258065 29.729032 13.212903 8.258065 4.954839 14.864516 11.56129 18.167742 16.516129 3.303226 6.606452 3.303226 13.212903 0 19.819355-3.303226 4.954839-6.606452 6.606452-13.212903 6.606452s-11.56129-1.651613-16.516129-4.954839c-6.606452-4.954839-14.864516-9.909677-24.774193-14.864516-9.909677-4.954839-19.819355-6.606452-31.380646-6.606452-16.516129 0-29.729032 4.954839-39.638709 14.864517-9.909677 9.909677-14.864516 21.470968-14.864516 36.335483 0 9.909677 1.651613 16.516129 6.606451 23.122581 4.954839 6.606452 9.909677 13.212903 16.516129 16.516129 6.606452 4.954839 14.864516 9.909677 24.774194 13.212903 8.258065 4.954839 18.167742 8.258065 28.077419 13.212904 13.212903 4.954839 23.122581 11.56129 33.032258 18.167741s18.167742 13.212903 24.774194 19.819355c6.606452 8.258065 11.56129 16.516129 16.516129 26.425807 3.303226 9.909677 4.954839 21.470968 4.954839 34.683871 1.651613 16.516129 0 29.729032-6.606452 41.290322z" fill="#169DFB" p-id="1448"></path><path d="M736.619355 14.864516h-3.303226v132.129032c0 16.516129 8.258065 52.851613 57.806452 52.851613h127.174193L736.619355 14.864516z" fill="#8BCEFD" p-id="1449"></path></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

1
src/assets/icons/file-type-txt.svg

@ -0,0 +1 @@
<svg t="1619506392432" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2630" width="64" height="64"><path d="M137.90246 0.00041a48.573421 48.573421 0 0 0-35.589106 15.293433A53.964778 53.964778 0 0 0 87.0404 50.934149V968.345622a48.706541 48.706541 0 0 0 15.272954 35.640306 49.97118 49.97118 0 0 0 35.589106 15.293434h746.336982a48.639981 48.639981 0 0 0 35.589105-15.293434 50.37054 50.37054 0 0 0 15.272954-35.640306V288.717094L646.727857 0.00041H137.90246z" fill="#E5E5E5" p-id="2631"></path><path d="M935.101501 288.717094h-237.445025c-27.822069-0.6656-50.22718-23.075831-50.928619-50.93374V0.00041l288.373644 288.716684z" fill="#CCCCCC" p-id="2632"></path><path d="M248.125776 365.184264h220.518312a25.51807 25.51807 0 0 0 24.19199-25.49759 25.51807 25.51807 0 0 0-24.19711-25.50271H248.125776a25.51807 25.51807 0 0 0-24.19711 25.49759 25.51807 25.51807 0 0 0 24.19711 25.49759z m0 169.825212h525.82379a25.44639 25.44639 0 0 0 25.431029-25.46687 25.44639 25.44639 0 0 0-25.431029-25.46687h-525.82379a25.44639 25.44639 0 0 0-25.43103 25.46687 25.44639 25.44639 0 0 0 25.43103 25.46687z m525.82379 118.886352h-525.82379a25.51807 25.51807 0 0 0-24.19711 25.49759 25.51807 25.51807 0 0 0 24.19711 25.50271h525.82379a25.51807 25.51807 0 0 0 24.19711-25.49759 25.51807 25.51807 0 0 0-24.19711-25.49759z" fill="#FFFFFF" p-id="2633"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

1
src/assets/icons/file-type-unknown.svg

@ -0,0 +1 @@
<svg t="1619506160661" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1299" width="64" height="64"><path d="M758.090323 36.335484L736.619355 14.864516h-545.032258c-46.245161 0-82.580645 36.335484-82.580645 82.580645v825.806452c0 46.245161 36.335484 82.580645 82.580645 82.580645h644.129032c46.245161 0 82.580645-36.335484 82.580645-82.580645V201.496774l-160.206451-165.16129zM531.819355 715.148387c-6.606452 6.606452-16.516129 11.56129-26.425807 11.56129-11.56129 0-19.819355-3.303226-28.077419-11.56129-6.606452-6.606452-11.56129-16.516129-11.56129-26.425806 0-11.56129 3.303226-19.819355 11.56129-26.425807 6.606452-6.606452 16.516129-11.56129 28.077419-11.56129s19.819355 3.303226 26.425807 11.56129c6.606452 6.606452 11.56129 16.516129 11.56129 26.425807 0 9.909677-4.954839 18.167742-11.56129 26.425806z m87.535484-272.516129c-4.954839 13.212903-16.516129 28.077419-33.032258 42.941936-11.56129 9.909677-19.819355 19.819355-26.425807 26.425806-6.606452 8.258065-11.56129 14.864516-14.864516 21.470968-3.303226 6.606452-4.954839 11.56129-6.606452 18.167742-1.651613 6.606452-1.651613 13.212903-1.651612 19.819355 0 11.56129-3.303226 19.819355-9.909678 24.774193-6.606452 4.954839-14.864516 8.258065-24.774193 8.258065s-18.167742-3.303226-24.774194-8.258065c-6.606452-4.954839-9.909677-13.212903-9.909677-24.774193 0-6.606452 1.651613-13.212903 3.303225-21.470968s6.606452-18.167742 9.909678-28.07742c4.954839-9.909677 9.909677-19.819355 16.516129-29.729032s14.864516-19.819355 23.122581-28.077419c6.606452-6.606452 13.212903-13.212903 16.516129-18.167742s8.258065-11.56129 11.56129-16.516129c3.303226-4.954839 4.954839-9.909677 4.954839-14.864516 1.651613-4.954839 1.651613-11.56129 1.651612-16.516129 0-14.864516-4.954839-26.425806-13.212903-31.380645-8.258065-4.954839-18.167742-8.258065-31.380645-8.258065-11.56129 0-23.122581 3.303226-31.380645 6.606452-9.909677 4.954839-14.864516 11.56129-14.864516 21.470967 0 9.909677-4.954839 16.516129-13.212903 21.470968-8.258065 4.954839-18.167742 6.606452-26.425807 6.606452-11.56129 0-19.819355-3.303226-24.774193-8.258065-4.954839-4.954839-6.606452-11.56129-6.606452-21.470968 0-13.212903 3.303226-24.774194 9.909677-36.335483 6.606452-11.56129 14.864516-19.819355 24.774194-28.07742s23.122581-14.864516 37.987097-18.167742c14.864516-4.954839 29.729032-6.606452 46.245161-8.258064 16.516129 0 31.380645 1.651613 44.593548 6.606451 13.212903 4.954839 26.425806 11.56129 36.335484 19.819355 9.909677 8.258065 18.167742 19.819355 23.122581 33.032258 4.954839 13.212903 8.258065 28.077419 8.258064 46.245162 3.303226 14.864516 1.651613 29.729032-4.954838 42.941935z" fill="#DCDCDC" p-id="1300"></path><path d="M736.619355 14.864516h-3.303226v132.129032c0 16.516129 8.258065 52.851613 57.806452 52.851613h127.174193L736.619355 14.864516z" fill="#EEEEEE" p-id="1301"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

1
src/assets/icons/file-type-video.svg

@ -0,0 +1 @@
<svg t="1619506277769" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1889" width="64" height="64"><path d="M594.944 0l335.12448 341.31968v563.2c0 65.9968-52.50048 119.48032-117.29408 119.48032H209.54624c-64.7936 0-117.2992-53.5296-117.2992-119.48032V119.48032C92.25216 53.48352 144.75776 0 209.55136 0H594.944z" fill="#627CFE" p-id="1890"></path><path d="M930.06848 341.31968h-211.9168c-64.74752 0-123.20768-59.48928-123.20768-125.4912V0l335.12448 341.31968z" fill="#FFFFFF" fill-opacity=".4" p-id="1891"></path><path d="M519.68 606.62784v66.42176l120.832 73.40032v-213.0432z" fill="#FFFFFF" opacity=".99" p-id="1892"></path><path d="M534.528 512.13824H282.25024a22.48192 22.48192 0 0 0-22.2976 22.62016v210.52928c0 12.38016 9.91744 22.66624 22.25152 22.66624h252.50816a22.48192 22.48192 0 0 0 22.25152-22.66624v-210.7136a22.62016 22.62016 0 0 0-22.43584-22.43584zM356.2496 719.36v-158.81216L483.328 639.9488 356.2496 719.36z" fill="#FFFFFF" opacity=".99" p-id="1893"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

1
src/assets/icons/file-type-zip.svg

@ -0,0 +1 @@
<svg t="1619506466646" class="icon" viewBox="0 0 1140 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2926" width="64" height="64"><path d="M1079.048591 1022.302497H56.746094c-31.187329 0-56.746094-21.778899-56.746094-48.224215V681.537289h1135.574765v292.389798c0 26.452188-25.565638 48.231087-56.746094 48.231087l0.21992 0.144323z" fill="#5ACC9B" p-id="2927"></path><path d="M56.746094 0H1079.048591c31.187329 0 56.746094 21.785772 56.746094 48.224215v292.540993H0.219919V48.382282C0.219919 21.923221 25.785557 0.151195 56.972886 0.151195L56.746094 0z" fill="#6CCBFF" p-id="2928"></path><path d="M0.219919 340.840805h1135.423571v340.620886H0.219919z" fill="#FFD766" p-id="2929"></path><path d="M378.694443 0.219919h378.474523v1021.862658H378.694443z" fill="#FF5562" p-id="2930"></path><path d="M487.444617 56.972886h75.714148v56.814819H487.444617V56.966013zM563.158765 0.213047H638.86604V56.972886H563.158765V0.219919z m0 113.567785H638.86604v56.5949H563.158765v-56.5949z m-75.714148 56.5949h75.714148v56.746094H487.444617v-56.746094z m75.714148 56.746094H638.86604v56.821691H563.158765v-56.821691z m-75.714148 56.821691h75.714148v56.814819H487.444617v-56.814819z m75.714148 56.746094H638.86604v56.746094H563.158765v-56.746094zM487.444617 397.435705h75.714148v56.814818H487.444617v-56.814818z m75.714148 56.814818H638.86604v56.897289H563.158765v-56.897289z m-75.714148 56.897289h75.714148v56.746094H487.444617v-56.746094z m75.714148 56.746094H638.86604V624.708725H563.158765v-56.814819zM487.444617 624.708725h75.714148v56.821691H487.444617V624.708725z m75.714148 56.746094H638.86604v56.746094H563.158765v-56.746094z m-75.714148 56.746094h75.714148v56.821691H487.444617v-56.821691z m75.714148 56.821691H638.86604v56.890416H563.158765v-56.890416z m-75.714148 56.890416h75.714148v56.746094H487.444617v-56.746094z m0 113.567785h75.714148v56.814819H487.444617v-56.814819z m75.714148-56.821691H638.86604v56.821691H563.158765v-56.821691z" fill="#FFFFFF" p-id="2931"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

39
src/assets/icons/light.svg

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

14
src/assets/icons/locale.svg

@ -0,0 +1,14 @@
<svg
viewBox="0 0 24 24"
focusable="false"
width="1em"
height="1em"
fill="currentColor"
aria-hidden="true"
>
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z "
className="css-c4d79v"
/>
</svg>

After

Width:  |  Height:  |  Size: 645 B

68
src/assets/icons/login.svg

@ -0,0 +1,68 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Group 21</title>
<desc>Created with Sketch.</desc>
<defs/>
<g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
<g id="Group-21" transform="translate(77.000000, 73.000000)">
<g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
<ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"/>
<ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"/>
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"/>
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"/>
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"/>
<g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
<ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"/>
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "/>
</g>
</g>
<g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"/>
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"/>
<ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"/>
<ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"/>
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"/>
<g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
<ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"/>
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "/>
</g>
<ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"/>
<ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"/>
<ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"/>
<path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"/>
</g>
<g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
<ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"/>
<g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
<ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"/>
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"/>
</g>
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"/>
<ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"/>
<ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"/>
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"/>
</g>
<g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
<g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
<circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"/>
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "/>
</g>
<circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"/>
<path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"/>
<path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"/>
<polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"/>
<path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"/>
<path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"/>
<path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"/>
<circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"/>
<circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"/>
<circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"/>
<circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"/>
<circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"/>
</g>
</g>
</g>
</g>
<div xmlns="" id="divScriptsUsed" style="display: none"/><script xmlns="" id="globalVarsDetection" src="chrome-extension://cmkdbmfndkfgebldhnkbfhlneefdaaip/js/wrs_env.js"/></svg>

After

Width:  |  Height:  |  Size: 8.5 KiB

48
src/assets/icons/logo.svg

@ -0,0 +1,48 @@
<svg width="182" height="166" xmlns="http://www.w3.org/2000/svg">
<!-- Generator: Sketch 54 (76480) - https://sketchapp.com -->
<title>未命名</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="0.00542331488%" y1="50.0074349%" x2="100.011743%" y2="50.0074349%" id="linearGradient-1">
<stop stop-color="#3668E7" offset="0.3030465%"/>
<stop stop-color="#44BAE1" offset="100%"/>
</linearGradient>
<linearGradient x1="0.0117592657%" y1="50.0044985%" x2="100.006481%" y2="50.0044985%" id="linearGradient-2">
<stop stop-color="#44BCE1" offset="0%"/>
<stop stop-color="#44BAE1" offset="36.72%"/>
<stop stop-color="#43B2E2" offset="54.5%"/>
<stop stop-color="#41A5E2" offset="68.19%"/>
<stop stop-color="#3F93E4" offset="79.8%"/>
<stop stop-color="#3C7BE5" offset="89.99%"/>
<stop stop-color="#385EE7" offset="99.21%"/>
<stop stop-color="#385BE7" offset="100%"/>
</linearGradient>
<linearGradient x1="0.0148416278%" y1="50.0005568%" x2="100.005996%" y2="50.0005568%" id="linearGradient-3">
<stop stop-color="#44BCE1" offset="0%"/>
<stop stop-color="#43B6E1" offset="14.76%"/>
<stop stop-color="#41A4E2" offset="34.89%"/>
<stop stop-color="#3E88E4" offset="58.12%"/>
<stop stop-color="#3960E7" offset="83.49%"/>
<stop stop-color="#385BE7" offset="86.36%"/>
</linearGradient>
</defs>
<g>
<title>background</title>
<rect x="-1" y="-1" width="184" height="168" id="canvas_background" fill="none"/>
</g>
<g>
<title>Layer 1</title>
<g id="logo3-01的副本" fill-rule="nonzero" stroke="null" transform="rotate(-1 88.74440002441469,82.13372802734358) ">
<g id="图层_2" stroke="null" transform="rotate(-1 88.74440002441469,82.13372802734358) ">
<g id="XMLID_5_" stroke="null">
<g id="编组" stroke="null">
<path d="m85.59977,58.69808c-18.60016,1.68406 -33.15083,17.00902 -33.15083,35.70211c0,19.80456 16.36951,35.83683 36.51396,35.83683c0,0 74.57221,12.15893 56.93295,-76.69217c10.15802,16.70589 19.42378,33.51283 19.42378,33.51283c0,0 22.64964,38.36292 -2.47087,61.80507c0,0 -14.585,13.40513 -41.55865,13.40513c-7.41261,0 -16.30088,0 -25.36073,0c-47.22107,-8.15086 -51.20191,-49.17461 -51.20191,-49.17461c-3.26018,-52.74481 40.8723,-54.5636 40.8723,-54.5636l0,0.16841z" id="路径" fill="url(#linearGradient-1)" stroke="null" stroke-width="0"/>
<path d="m145.89585,53.54485c17.63926,88.8511 -56.93295,76.69217 -56.93295,76.69217c20.17877,0 36.51396,-16.03227 36.51396,-35.83683c0,-3.33445 -0.48045,-6.56784 -1.33839,-9.63284c0,0 0,-0.03368 0,-0.03368c-0.37749,-1.91983 -6.0399,-28.3596 -33.52833,-38.63238c-12.52593,-4.68169 -26.35594,-4.34488 -38.91619,0.20209c-8.81963,3.19972 -19.18356,8.75712 -26.83639,18.32259l19.04629,-32.90656c4.35833,-7.51092 10.05506,-14.17981 16.98722,-19.50144c12.11413,-9.22866 31.19473,-16.97534 52.91779,-0.97676c0,0 6.52035,3.63758 19.86991,23.13901c3.84358,5.62477 8.13328,12.3947 12.21708,19.16463z" id="路径" fill="url(#linearGradient-2)" stroke="null" stroke-width="0"/>
<path d="m124.13847,84.73367c0.85794,3.06499 1.33839,6.29839 1.33839,9.63284c0,19.80456 -16.36951,35.83683 -36.51396,35.83683c-20.14445,0 -36.51396,-16.03227 -36.51396,-35.83683c0,-18.69309 14.55067,-34.01805 33.15083,-35.70212c1.09817,-0.10104 2.23065,-0.13472 3.36313,-0.13472c16.747,0 30.88587,11.08113 35.17557,26.204c0,0 0,0 0,0z" id="路径" fill="#FFFFFF" stroke="null" stroke-width="0"/>
<path d="m44.72747,113.09327c0,0 3.94653,41.02375 51.20191,49.17461c-15.47726,0 -31.332,0 -40.63208,0c-10.39824,0 -20.69353,-2.35769 -29.85634,-7.20779c-12.21708,-6.50048 -24.64006,-18.38995 -22.3751,-39.44073c1.64725,-15.32496 20.79649,-49.24196 20.79649,-49.24196l0.99521,-1.71775c0,0 0,0 0,-0.03368c7.61851,-9.59915 17.98244,-15.12287 26.83639,-18.32259c12.56025,-4.54697 26.39026,-4.88378 38.91619,-0.20209c27.48843,10.30646 33.15084,36.74623 33.52833,38.63238c-4.2897,-15.08919 -18.42857,-26.17032 -35.17557,-26.17032c-1.13249,0 -2.26497,0.06736 -3.36313,0.13472l0,-0.13472c0,-0.03368 -44.13248,1.78511 -40.8723,54.52992z" id="路径" fill="url(#linearGradient-3)" stroke="null" stroke-width="0"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

1
src/assets/icons/moon.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path fill="currentColor" d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938A7.999 7.999 0 0 0 4 12z"/></svg>

After

Width:  |  Height:  |  Size: 366 B

39
src/assets/icons/realDark.svg

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#001529d9" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-11" fill="#001529d9" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
<rect id="Rectangle-18" fill="#001529a6" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

1
src/assets/icons/searchoutlined.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1685331542289" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2045" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M898.7 853.7L675.1 630.1C719.4 575.8 746 506.5 746 431c0-174-141-315-315-315S116 257 116 431s141 315 315 315c75.5 0 144.8-26.6 199.1-70.9l223.6 223.6c6.2 6.2 14.3 9.3 22.5 9.3 8.1 0 16.3-3.1 22.5-9.3 12.4-12.4 12.4-32.6 0-45z m-370-191.4C497.8 675.4 464.9 682 431 682s-66.8-6.6-97.7-19.7c-29.9-12.6-56.7-30.7-79.8-53.8s-41.2-49.9-53.8-79.8C186.6 497.8 180 464.9 180 431s6.6-66.8 19.7-97.7c12.6-29.9 30.7-56.7 53.8-79.8s49.9-41.2 79.8-53.8c30.9-13.1 63.8-19.7 97.7-19.7s66.8 6.6 97.7 19.7c29.9 12.6 56.7 30.7 79.8 53.8s41.2 49.9 53.8 79.8c13.1 30.9 19.7 63.8 19.7 97.7s-6.6 66.8-19.7 97.7c-12.6 29.9-30.7 56.7-53.8 79.8s-49.9 41.2-79.8 53.8z" p-id="2046"></path></svg>

After

Width:  |  Height:  |  Size: 1000 B

39
src/assets/icons/sidemenu.svg

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

1
src/assets/icons/sun.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path fill="currentColor" d="M12 18a6 6 0 1 1 0-12a6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8a4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636L5.636 7.05L3.515 4.93zM16.95 18.364l1.414-1.414l2.121 2.121l-1.414 1.414l-2.121-2.121zm2.121-14.85l1.414 1.415l-2.121 2.121l-1.414-1.414l2.121-2.121zM5.636 16.95l1.414 1.414l-2.121 2.121l-1.414-1.414l2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></svg>

After

Width:  |  Height:  |  Size: 571 B

39
src/assets/icons/topmenu.svg

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="white" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-18" fill="#f0f2f5" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
<rect id="Rectangle-11" fill="#001529" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

1
src/assets/icons/users.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1685515461184" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6438" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M725.333333 938.666667c-25.6 0-42.666667-17.066667-42.666666-42.666667v-85.333333c0-72.533333-55.466667-128-128-128H213.333333c-72.533333 0-128 55.466667-128 128v85.333333c0 25.6-17.066667 42.666667-42.666666 42.666667s-42.666667-17.066667-42.666667-42.666667v-85.333333c0-119.466667 93.866667-213.333333 213.333333-213.333334h341.333334c119.466667 0 213.333333 93.866667 213.333333 213.333334v85.333333c0 25.6-17.066667 42.666667-42.666667 42.666667zM384 512c-119.466667 0-213.333333-93.866667-213.333333-213.333333s93.866667-213.333333 213.333333-213.333334 213.333333 93.866667 213.333333 213.333334-93.866667 213.333333-213.333333 213.333333z m0-341.333333C311.466667 170.666667 256 226.133333 256 298.666667s55.466667 128 128 128 128-55.466667 128-128-55.466667-128-128-128zM981.333333 938.666667c-25.6 0-42.666667-17.066667-42.666666-42.666667v-85.333333c0-59.733333-38.4-110.933333-98.133334-123.733334-21.333333-4.266667-34.133333-29.866667-29.866666-51.2 4.266667-21.333333 29.866667-34.133333 51.2-29.866666 93.866667 25.6 157.866667 110.933333 157.866666 209.066666v85.333334c4.266667 21.333333-12.8 38.4-38.4 38.4zM682.666667 507.733333c-17.066667 0-34.133333-12.8-42.666667-29.866666-8.533333-21.333333 8.533333-46.933333 29.866667-51.2 46.933333-12.8 81.066667-51.2 93.866666-93.866667 17.066667-68.266667-25.6-140.8-93.866666-157.866667-21.333333-4.266667-38.4-29.866667-29.866667-51.2 4.266667-21.333333 29.866667-38.4 51.2-29.866666 110.933333 25.6 183.466667 145.066667 153.6 260.266666-21.333333 76.8-81.066667 132.266667-153.6 153.6H682.666667z" p-id="6439" fill="#efbd47"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
src/assets/images/no-preview.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src/assets/images/tool.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

68
src/assets/login.svg

@ -0,0 +1,68 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Group 21</title>
<desc>Created with Sketch.</desc>
<defs/>
<g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
<g id="Group-21" transform="translate(77.000000, 73.000000)">
<g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
<ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"/>
<ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"/>
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"/>
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"/>
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"/>
<g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
<ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"/>
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "/>
</g>
</g>
<g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"/>
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"/>
<ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"/>
<ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"/>
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"/>
<g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
<ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"/>
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "/>
</g>
<ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"/>
<ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"/>
<ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"/>
<path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"/>
</g>
<g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
<ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"/>
<g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
<ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"/>
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"/>
</g>
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"/>
<ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"/>
<ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"/>
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"/>
</g>
<g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
<g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
<circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"/>
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "/>
</g>
<circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"/>
<path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"/>
<path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"/>
<polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"/>
<path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"/>
<path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"/>
<path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"/>
<circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"/>
<circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"/>
<circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"/>
<circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"/>
<circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"/>
</g>
</g>
</g>
</g>
<div xmlns="" id="divScriptsUsed" style="display: none"/><script xmlns="" id="globalVarsDetection" src="chrome-extension://cmkdbmfndkfgebldhnkbfhlneefdaaip/js/wrs_env.js"/></svg>

After

Width:  |  Height:  |  Size: 8.5 KiB

3
src/components/basic/ContextMenu/index.ts

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

209
src/components/basic/ContextMenu/src/ContextMenu.vue

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

83
src/components/basic/ContextMenu/src/createContextMenu.ts

@ -0,0 +1,83 @@
import contextMenuVue from './ContextMenu.vue';
import { isClient } from '@/utils/is';
import { CreateContextOptions, ContextMenuProps } from './typing';
import { createVNode, render } from 'vue';
// import { usePermission } from '@/hooks/web/usePermission';
import {hasPermission} from '@/utils/permission/hasPermission';
const menuManager: {
domList: Element[];
resolve: Fn;
} = {
domList: [],
resolve: () => {},
};
export const createContextMenu = function (options: CreateContextOptions) {
const { event } = options || {};
event && event?.preventDefault();
if (!isClient) {
return;
}
return new Promise((resolve) => {
const body = document.body;
const container = document.createElement('div');
const propsData: Partial<ContextMenuProps> = {};
if (options.styles) {
propsData.styles = options.styles;
}
if (options.items) {
// const { isPermission } = usePermission();
propsData.items = options.items.filter((item) => {
return hasPermission(item.auth as string);
// return true;
});
console.log('propsData.items: ', propsData.items);
}
if (options.event) {
propsData.customEvent = event;
propsData.axis = { x: event.clientX, y: event.clientY };
}
const vm = createVNode(contextMenuVue, propsData);
console.log('propsData: ', propsData);
render(vm, container);
const handleClick = function () {
menuManager.resolve('');
};
menuManager.domList.push(container);
const remove = function () {
menuManager.domList.forEach((dom: Element) => {
try {
dom && body.removeChild(dom);
} catch (error) {}
});
body.removeEventListener('click', handleClick);
body.removeEventListener('scroll', handleClick);
};
menuManager.resolve = function (arg) {
remove();
resolve(arg);
};
remove();
body.appendChild(container);
body.addEventListener('click', handleClick);
body.addEventListener('scroll', handleClick);
});
};
export const destroyContextMenu = function () {
if (menuManager) {
menuManager.resolve('');
menuManager.domList = [];
}
};

40
src/components/basic/ContextMenu/src/typing.ts

@ -0,0 +1,40 @@
import { PermModeEnum, RoleEnum } from '@/enums/roleEnum';
export interface Axis {
x: number;
y: number;
}
export interface ContextMenuItem {
label: string;
icon?: string;
hidden?: boolean;
disabled?: boolean;
handler?: Fn;
divider?: boolean;
children?: ContextMenuItem[];
auth?: string | string[] | RoleEnum | RoleEnum[];
authMode?: PermModeEnum;
}
export interface CreateContextOptions {
event: MouseEvent;
icon?: string;
styles?: any;
items?: ContextMenuItem[];
}
export interface ContextMenuProps {
event?: MouseEvent;
styles?: any;
items: ContextMenuItem[];
customEvent?: MouseEvent;
axis?: Axis;
width?: number;
showIcon?: boolean;
}
export interface ItemContentProps {
showIcon: boolean | undefined;
item: ContextMenuItem;
handler: Fn;
}

1
src/components/basic/basic-arrow/index.ts

@ -0,0 +1 @@
export { default as BasicArrow } from './index.vue';

24
src/components/basic/basic-arrow/index.vue

@ -0,0 +1,24 @@
<template>
<DownOutlined class="collapse-icon" />
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { DownOutlined } from '@ant-design/icons-vue';
const props = defineProps({
expand: { type: Boolean },
});
/**
* @description 展开/收起 图标旋转转数
*/
const turn = computed(() => `${props.expand ? 0 : 0.5}turn`);
</script>
<style lang="less" scoped>
.collapse-icon {
transform: rotate(v-bind(turn));
transition: transform 0.3s;
}
</style>

94
src/components/basic/basic-help/index.vue

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

16
src/components/basic/button/button.ts

@ -0,0 +1,16 @@
import buttonProps from 'ant-design-vue/es/button/buttonTypes';
import type { ButtonType as AButtonType } from 'ant-design-vue/es/button/buttonTypes';
import type { ExtractPropTypes } from 'vue';
export type ButtonType = AButtonType | 'warning' | 'success';
export declare type ButtonProps = Partial<ExtractPropTypes<typeof buttonProps>>;
export const typeColorMap = {
warning: 'var(--ant-warning-color)',
success: 'var(--ant-success-color)',
} as const;
export const buttonTypes = ['default', 'primary', 'ghost', 'dashed', 'link', 'text'];
export { buttonProps };

48
src/components/basic/button/button.vue

@ -0,0 +1,48 @@
<template>
<Button
v-bind="{ ...$attrs, ...props }"
:type="buttonType"
:class="[`ant-btn-${type}`, { 'basic-btn': colorVar }]"
>
<template v-for="(_, key) in $slots" #[key]>
<slot :name="key"></slot>
</template>
</Button>
</template>
<script lang="ts" setup>
import { computed, type ComputedRef } from 'vue';
import { Button } from 'ant-design-vue';
import { buttonProps, typeColorMap, buttonTypes } from './button';
import type { ButtonType } from './button';
import type { ButtonType as AButtonType } from 'ant-design-vue/es/button';
const props = defineProps({
...buttonProps(),
type: {
type: String as PropType<ButtonType>,
},
//
color: String,
});
const buttonType = computed(() => {
const type = props.type!;
return buttonTypes.includes(type)
? (type as ButtonType)
: Reflect.has(typeColorMap, type) || props.color
? 'primary'
: 'default';
}) as ComputedRef<AButtonType>;
const colorVar = computed(() => {
return props.color || typeColorMap[props.type!];
});
</script>
<style scoped>
.basic-btn {
--ant-primary-color: v-bind(colorVar);
--ant-primary-color-hover: v-bind(colorVar);
--ant-primary-color-active: v-bind(colorVar);
}
</style>

9
src/components/basic/button/index.ts

@ -0,0 +1,9 @@
import AButton from './button.vue';
export default AButton;
export const Button = AButton;
export * from './button';
export { AButton };

54
src/components/basic/check-box/index.vue

@ -0,0 +1,54 @@
<template>
<Checkbox v-bind="getProps" v-model:checked="checkedModel" @change="handleChange">
<slot></slot>
</Checkbox>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { checkboxProps } from 'ant-design-vue/es/checkbox';
import { omit } from 'lodash-es';
import { Checkbox } from 'ant-design-vue';
defineOptions({
inheritAttrs: false,
});
const props = defineProps({
...checkboxProps(),
trueValue: {
type: [Number, Boolean, String],
default: true,
},
falseValue: {
type: [Number, Boolean, String],
default: false,
},
});
const emit = defineEmits(['update:checked', 'change']);
const getProps = computed(() => {
return omit(props, ['onUpdate:checked', 'onChange']);
});
const checkedModel = computed<boolean>({
get() {
return props.checked === props.trueValue;
},
set(val) {
emit('update:checked', val ? props.trueValue : props.falseValue);
},
});
const handleChange = (e) => {
const evt = {
...e,
target: {
...e.target,
checked: e.target.checked ? props.trueValue : props.falseValue,
},
};
emit('change', evt);
};
</script>

72
src/components/basic/iconfont/icon-font.tsx

@ -0,0 +1,72 @@
import { defineComponent, unref, computed } from 'vue';
import { createFromIconfontCN } from '@ant-design/icons-vue';
import type { PropType } from 'vue';
import { isString } from '@/utils/is';
let scriptUrls = [`${import.meta.env.BASE_URL}iconfont.js`];
let MyIconFont = createFromIconfontCN({
// scriptUrl: '//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js',
// scriptUrl: '//at.alicdn.com/t/font_2184398_zflo1kjcemp.js',
// iconfont字体图标本地化,详见:/public/iconfont.js
scriptUrl: scriptUrls,
});
export default defineComponent({
name: 'IconFont',
props: {
type: {
type: String as PropType<string>,
default: '',
},
prefix: {
type: String,
default: 'icon-',
},
color: {
type: String as PropType<string>,
default: 'unset',
},
size: {
type: [Number, String] as PropType<number | string>,
default: 14,
},
scriptUrl: {
// 阿里图库字体图标路径
type: String as PropType<string | string[]>,
default: '',
},
},
setup(props, { attrs }) {
// 如果外部传进来字体图标路径,则覆盖默认的
if (props.scriptUrl) {
scriptUrls = [...new Set(scriptUrls.concat(props.scriptUrl))];
MyIconFont = createFromIconfontCN({
scriptUrl: scriptUrls,
});
}
const wrapStyleRef = computed(() => {
const { color, size } = props;
const fs = isString(size) ? parseFloat(size) : size;
return {
color,
fontSize: `${fs}px`,
};
});
return () => {
const { type, prefix } = props;
return type ? (
<MyIconFont
type={type.startsWith(prefix) ? type : `${prefix}${type}`}
{...attrs}
style={unref(wrapStyleRef)}
/>
) : null;
};
},
});

2
src/components/basic/iconfont/index.ts

@ -0,0 +1,2 @@
import IconFont from './icon-font';
export { IconFont };

1311
src/components/basic/icons-select/icons.json

File diff suppressed because it is too large

71
src/components/basic/icons-select/index.vue

@ -0,0 +1,71 @@
<template>
<Popover v-model:visible="visible" placement="bottomLeft" trigger="focus">
<template #content>
<div class="select-box">
<template v-for="iconItem in glyphs" :key="iconItem.font_class">
<div
:title="iconItem.name"
class="select-box-item"
:class="{ active: modelValue?.replace('icon-', '') == iconItem.font_class }"
@click="selectIcon(iconItem)"
>
<icon-font :type="iconItem.font_class" size="20" />
</div>
</template>
</div>
</template>
<a-input v-model:value="modelValue" :placeholder="placeholder">
<template v-if="modelValue" #prefix>
<icon-font :type="modelValue" size="22" />
</template>
</a-input>
</Popover>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import { Popover } from 'ant-design-vue';
import icons from './icons.json';
import { IconFont } from '@/components/basic/iconfont';
const { glyphs } = icons;
interface Props {
value: string;
placeholder?: string;
}
interface Emits {
(e: 'update:value', val: string): void;
}
const props = withDefaults(defineProps<Props>(), {
value: '',
placeholder: '请选择',
});
const emit = defineEmits<Emits>();
const visible = ref(false);
const modelValue = useVModel(props, 'value', emit);
const selectIcon = (iconItem: typeof glyphs[number]) => {
modelValue.value = iconItem.font_class;
visible.value = false;
};
</script>
<style lang="less" scoped>
.select-box {
@apply grid grid-cols-9 h-300px overflow-auto;
&-item {
@apply flex m-2px p-6px;
border: 1px solid #e5e7eb;
&:hover,
&.active {
@apply border-blue-600;
}
}
}
</style>

1
src/components/basic/locale-picker/index.ts

@ -0,0 +1 @@
export { default as LocalePicker } from './index.vue';

60
src/components/basic/locale-picker/index.vue

@ -0,0 +1,60 @@
<template>
<Dropdown placement="bottomRight">
<SvgIcon name="locale" />
<span v-if="showText" class="ml-1">{{ getLocaleText }}</span>
<template #overlay>
<Menu v-model:selectedKeys="selectedKeys" @click="handleMenuClick">
<Menu.Item v-for="item in localeList" :key="item.lang">
<a href="javascript:;">{{ item.icon }} {{ item.label }}</a>
</Menu.Item>
</Menu>
</template>
</Dropdown>
</template>
<script lang="ts" setup>
import { ref, watchEffect, unref, computed } from 'vue';
import { Dropdown, Menu } from 'ant-design-vue';
import { useLocale } from '@/locales/useLocale';
import { type LocaleType, localeList } from '@/locales/config';
import { SvgIcon } from '@/components/basic/svg-icon';
const props = defineProps({
/**
* Whether to display text
*/
showText: { type: Boolean, default: true },
/**
* Whether to refresh the interface when changing
*/
reload: { type: Boolean },
});
const selectedKeys = ref<string[]>([]);
const { changeLocale, getLocale } = useLocale();
const getLocaleText = computed(() => {
const key = selectedKeys.value[0];
if (!key) {
return '';
}
return localeList.find((item) => item.lang === key)?.label;
});
watchEffect(() => {
selectedKeys.value = [unref(getLocale)];
});
async function toggleLocale(lang: LocaleType | string) {
await changeLocale(lang as LocaleType);
selectedKeys.value = [lang as string];
props.reload && location.reload();
}
function handleMenuClick({ key }) {
if (unref(getLocale) === key) {
return;
}
toggleLocale(key as string);
}
</script>

3
src/components/basic/split-panel/index.ts

@ -0,0 +1,3 @@
import SplitPanel from './index.vue';
export { SplitPanel };

102
src/components/basic/split-panel/index.vue

@ -0,0 +1,102 @@
<template>
<div class="split-wrapper">
<div ref="scalable" class="scalable">
<div class="left-content">
<slot name="left-content"> 右边内容区 </slot>
</div>
<div ref="separator" class="separator" @mousedown="startDrag"><i></i><i></i></div>
</div>
<div class="right-content">
<slot name="right-content"> 右边内容区 </slot>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { throttle } from 'lodash-es';
const scalable = ref<HTMLDivElement>();
let startX: number;
let startWidth: number;
//
// @throttle(20)
const onDrag = throttle(function (e: MouseEvent) {
scalable.value && (scalable.value.style.width = `${startWidth + e.clientX - startX}px`);
}, 20);
//
const dragEnd = () => {
document.documentElement.style.userSelect = 'unset';
document.documentElement.removeEventListener('mousemove', onDrag);
document.documentElement.removeEventListener('mouseup', dragEnd);
};
//
const startDrag = (e: MouseEvent) => {
startX = e.clientX;
scalable.value && (startWidth = parseInt(window.getComputedStyle(scalable.value).width, 10));
document.documentElement.style.userSelect = 'none';
document.documentElement.addEventListener('mousemove', onDrag);
document.documentElement.addEventListener('mouseup', dragEnd);
};
</script>
<style lang="less">
@import '@/styles/theme.less';
@classNames: split-wrapper, separator;
.themeBgColor(@classNames);
.split-wrapper {
display: flex;
width: 100%;
height: 100%;
.scalable {
position: relative;
width: 240px;
max-width: 50vw;
min-width: 100px;
overflow: auto;
.left-content {
height: 100%;
padding: 12px 20px 12px 12px;
}
.separator {
position: absolute;
top: 0;
right: 0;
display: flex;
width: 14px;
height: 100%;
cursor: col-resize;
box-shadow: -4px -2px 4px -5px rgba(0, 0, 0, 0.35), 4px 3px 4px -5px rgba(0, 0, 0, 0.35);
align-items: center;
justify-content: center;
i {
width: 1px;
height: 14px;
margin: 0 1px;
background-color: #e9e9e9;
}
}
}
.right-content {
flex: 1;
}
.left-content,
.right-content {
overflow: auto;
}
}
</style>

3
src/components/basic/svg-icon/index.ts

@ -0,0 +1,3 @@
import SvgIcon from './svg-icon.vue';
export { SvgIcon };

50
src/components/basic/svg-icon/svg-icon.vue

@ -0,0 +1,50 @@
<template>
<svg v-bind="$attrs" class="svg-icon" :style="getStyle" aria-hidden="true">
<use :xlink:href="symbolId" />
</svg>
</template>
<script lang="ts" setup>
import { computed, type CSSProperties } from 'vue';
defineOptions({
name: 'svg-icon',
});
const props = defineProps({
prefix: {
type: String,
default: 'svg-icon',
},
name: {
type: String,
required: true,
},
size: {
type: [Number, String],
default: 16,
},
color:{
type: String,
required: false,
}
});
const symbolId = computed(() => `#${props.prefix}-${props.name}`);
const getStyle = computed((): CSSProperties => {
const { size,color } = props;
const s = `${size}`.replace('px', '').concat('px');
return {
width: s,
height: s,
color
};
});
</script>
<style lang="less">
.svg-icon {
overflow: hidden;
vertical-align: -0.15em;
fill: currentColor;
cursor: pointer;
}
</style>

1
src/components/basic/title-i18n/index.ts

@ -0,0 +1 @@
export { default as TitleI18n } from './index.vue';

26
src/components/basic/title-i18n/index.vue

@ -0,0 +1,26 @@
<template>
<i18n-t tag="span" :keypath="getTitle" scope="global" />
</template>
<script setup lang="ts">
import { type PropType, computed } from 'vue';
import { useLocaleStore } from '@/store/modules/locale';
const props = defineProps({
title: {
type: [String, Object] as PropType<string | Title18n>,
required: true,
default: '',
},
});
const localeStore = useLocaleStore();
const getTitle = computed(() => {
const { title = '' } = props;
if (typeof title === 'object') {
return title?.[localeStore.locale] ?? title;
}
return title;
});
</script>

6
src/components/core/Container/index.ts

@ -0,0 +1,6 @@
import { withInstall } from '@/utils';
import collapseContainer from './src/collapse/CollapseContainer.vue';
export const CollapseContainer = withInstall<any>(collapseContainer);
export * from './src/typing';

109
src/components/core/Container/src/collapse/CollapseContainer.vue

@ -0,0 +1,109 @@
<template>
<div :class="prefixCls">
<CollapseHeader v-bind="props" :prefixCls="prefixCls" :show="show" @expand="handleExpand">
<template #title>
<slot name="title"></slot>
</template>
<template #action>
<slot name="action"></slot>
</template>
</CollapseHeader>
<div class="p-2">
<CollapseTransition :enable="canExpan">
<Skeleton v-if="loading" :active="loading" />
<div :class="`${prefixCls}__body`" v-else v-show="show">
<slot></slot>
</div>
</CollapseTransition>
</div>
<div :class="`${prefixCls}__footer`" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from 'vue';
import { ref } from 'vue';
import { isNil } from 'lodash-es';
// component
import { Skeleton } from 'ant-design-vue';
import { CollapseTransition } from '@/components/core/Transition';
import CollapseHeader from './CollapseHeader.vue';
import { triggerWindowResize } from '@/utils/event';
// hook
import { useTimeoutFn } from '@/hooks/useTimeout';
const props = defineProps({
title: { type: String, default: '' },
loading: { type: Boolean },
/**
* Can it be expanded
*/
canExpan: { type: Boolean, default: true },
/**
* Warm reminder on the right side of the title
*/
helpMessage: {
type: [Array, String] as PropType<string[] | string>,
default: '',
},
/**
* Whether to trigger window.resize when expanding and contracting,
* Can adapt to tables and forms, when the form shrinks, the form triggers resize to adapt to the height
*/
triggerWindowResize: { type: Boolean },
/**
* Delayed loading time
*/
lazyTime: { type: Number, default: 0 },
});
const show = ref(true);
const prefixCls = 'collapse-container';
/**
* @description: Handling development events
*/
function handleExpand(val: boolean) {
show.value = isNil(val) ? !show.value : val;
if (props.triggerWindowResize) {
// 200 milliseconds here is because the expansion has animation,
useTimeoutFn(triggerWindowResize, 200);
}
}
defineExpose({
handleExpand,
});
</script>
<style lang="less">
@prefix-cls: ~'collapse-container';
.@{prefix-cls} {
background-color: @component-background;
border-radius: 2px;
transition: all 0.3s ease-in-out;
&__header {
display: flex;
height: 32px;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #d9d9d9;
}
&__footer {
border-top: 1px solid #d9d9d9;
}
&__action {
display: flex;
text-align: right;
flex: 1;
align-items: center;
justify-content: flex-end;
}
}
</style>

40
src/components/core/Container/src/collapse/CollapseHeader.vue

@ -0,0 +1,40 @@
<template>
<div :class="[`${prefixCls}__header px-2 py-5`, $attrs.class]">
<!-- <BasicTitle :helpMessage="helpMessage" normal> -->
<template v-if="title">
{{ title }}
</template>
<template v-else>
<slot name="title"></slot>
</template>
<!-- </BasicTitle> -->
<div :class="`${prefixCls}__action`">
<slot name="action"></slot>
<BasicArrow v-if="canExpan" up :expand="show" @click="$emit('expand')" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { BasicArrow } from '@/components/basic/basic-arrow';
const props = {
prefixCls: { type: String },
helpMessage: {
type: [Array, String] as PropType<string[] | string>,
default: '',
},
title: { type: String },
show: { type: Boolean },
canExpan: { type: Boolean },
};
export default defineComponent({
components: {
BasicArrow
},
inheritAttrs: false,
props,
emits: ['expand'],
});
</script>

17
src/components/core/Container/src/typing.ts

@ -0,0 +1,17 @@
export type ScrollType = 'default' | 'main';
export interface CollapseContainerOptions {
canExpand?: boolean;
title?: string;
helpMessage?: Array<any> | string;
}
export interface ScrollContainerOptions {
enableScroll?: boolean;
type?: ScrollType;
}
export type ScrollActionType = RefType<{
scrollBottom: () => void;
getScrollWrap: () => Nullable<HTMLElement>;
scrollTo: (top: number) => void;
}>;

27
src/components/core/Transition/index.ts

@ -0,0 +1,27 @@
import { createSimpleTransition, createJavascriptTransition } from './src/CreateTransition';
import ExpandTransitionGenerator from './src/ExpandTransition';
export { default as CollapseTransition } from './src/CollapseTransition.vue';
export const FadeTransition = createSimpleTransition('fade-transition');
export const ScaleTransition = createSimpleTransition('scale-transition');
export const SlideYTransition = createSimpleTransition('slide-y-transition');
export const ScrollYTransition = createSimpleTransition('scroll-y-transition');
export const SlideYReverseTransition = createSimpleTransition('slide-y-reverse-transition');
export const ScrollYReverseTransition = createSimpleTransition('scroll-y-reverse-transition');
export const SlideXTransition = createSimpleTransition('slide-x-transition');
export const ScrollXTransition = createSimpleTransition('scroll-x-transition');
export const SlideXReverseTransition = createSimpleTransition('slide-x-reverse-transition');
export const ScrollXReverseTransition = createSimpleTransition('scroll-x-reverse-transition');
export const ScaleRotateTransition = createSimpleTransition('scale-rotate-transition');
export const ExpandXTransition = createJavascriptTransition(
'expand-x-transition',
ExpandTransitionGenerator('', true),
);
export const ExpandTransition = createJavascriptTransition(
'expand-transition',
ExpandTransitionGenerator(''),
);

78
src/components/core/Transition/src/CollapseTransition.vue

@ -0,0 +1,78 @@
<template>
<transition mode="out-in" v-on="on">
<slot></slot>
</transition>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { addClass, removeClass } from '@/utils/domUtils';
export default defineComponent({
name: 'CollapseTransition',
setup() {
return {
on: {
beforeEnter(el) {
addClass(el, 'collapse-transition');
if (!el.dataset) el.dataset = {};
el.dataset.oldPaddingTop = el.style.paddingTop;
el.dataset.oldPaddingBottom = el.style.paddingBottom;
el.style.height = '0';
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
},
enter(el) {
el.dataset.oldOverflow = el.style.overflow;
if (el.scrollHeight !== 0) {
el.style.height = el.scrollHeight + 'px';
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
} else {
el.style.height = '';
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
}
el.style.overflow = 'hidden';
},
afterEnter(el) {
removeClass(el, 'collapse-transition');
el.style.height = '';
el.style.overflow = el.dataset.oldOverflow;
},
beforeLeave(el) {
if (!el.dataset) el.dataset = {};
el.dataset.oldPaddingTop = el.style.paddingTop;
el.dataset.oldPaddingBottom = el.style.paddingBottom;
el.dataset.oldOverflow = el.style.overflow;
el.style.height = el.scrollHeight + 'px';
el.style.overflow = 'hidden';
},
leave(el) {
if (el.scrollHeight !== 0) {
addClass(el, 'collapse-transition');
el.style.height = 0;
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
}
},
afterLeave(el) {
removeClass(el, 'collapse-transition');
el.style.height = '';
el.style.overflow = el.dataset.oldOverflow;
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
},
},
};
},
});
</script>

73
src/components/core/Transition/src/CreateTransition.tsx

@ -0,0 +1,73 @@
import type { PropType } from 'vue';
import { defineComponent, Transition, TransitionGroup } from 'vue';
import { getSlot } from '@/utils/helper/tsxHelper';
type Mode = 'in-out' | 'out-in' | 'default' | undefined;
export function createSimpleTransition(name: string, origin = 'top center 0', mode?: Mode) {
return defineComponent({
name,
props: {
group: {
type: Boolean as PropType<boolean>,
default: false,
},
mode: {
type: String as PropType<Mode>,
default: mode,
},
origin: {
type: String as PropType<string>,
default: origin,
},
},
setup(props, { slots, attrs }) {
const onBeforeEnter = (el: HTMLElement) => {
el.style.transformOrigin = props.origin;
};
return () => {
const Tag = !props.group ? Transition : TransitionGroup;
return (
<Tag name={name} mode={props.mode} {...attrs} onBeforeEnter={onBeforeEnter}>
{() => getSlot(slots)}
</Tag>
);
};
},
});
}
export function createJavascriptTransition(
name: string,
functions: Recordable,
mode: Mode = 'in-out',
) {
return defineComponent({
name,
props: {
mode: {
type: String as PropType<Mode>,
default: mode,
},
},
setup(props, { attrs, slots }) {
return () => {
return (
<Transition
name={name}
mode={props.mode}
{...attrs}
onBeforeEnter={functions.beforeEnter}
onEnter={functions.enter}
onLeave={functions.leave}
onAfterLeave={functions.afterLeave}
onLeaveCancelled={functions.afterLeave}
>
{() => getSlot(slots)}
</Transition>
);
};
},
});
}

89
src/components/core/Transition/src/ExpandTransition.ts

@ -0,0 +1,89 @@
/**
* Makes the first character of a string uppercase
*/
export function upperFirst(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
interface HTMLExpandElement extends HTMLElement {
_parent?: (Node & ParentNode & HTMLElement) | null;
_initialStyle: {
transition: string;
overflow: string | null;
height?: string | null;
width?: string | null;
};
}
export default function (expandedParentClass = '', x = false) {
const sizeProperty = x ? 'width' : ('height' as 'width' | 'height');
const offsetProperty = `offset${upperFirst(sizeProperty)}` as 'offsetHeight' | 'offsetWidth';
return {
beforeEnter(el: HTMLExpandElement) {
el._parent = el.parentNode as (Node & ParentNode & HTMLElement) | null;
el._initialStyle = {
transition: el.style.transition,
overflow: el.style.overflow,
[sizeProperty]: el.style[sizeProperty],
};
},
enter(el: HTMLExpandElement) {
const initialStyle = el._initialStyle;
el.style.setProperty('transition', 'none', 'important');
el.style.overflow = 'hidden';
// const offset = `${el[offsetProperty]}px`;
// el.style[sizeProperty] = '0';
void el.offsetHeight; // force reflow
el.style.transition = initialStyle.transition;
if (expandedParentClass && el._parent) {
el._parent.classList.add(expandedParentClass);
}
requestAnimationFrame(() => {
// el.style[sizeProperty] = offset;
});
},
afterEnter: resetStyles,
enterCancelled: resetStyles,
leave(el: HTMLExpandElement) {
el._initialStyle = {
transition: '',
overflow: el.style.overflow,
[sizeProperty]: el.style[sizeProperty],
};
el.style.overflow = 'hidden';
el.style[sizeProperty] = `${el[offsetProperty]}px`;
/* eslint-disable-next-line */
void el.offsetHeight; // force reflow
requestAnimationFrame(() => (el.style[sizeProperty] = '0'));
},
afterLeave,
leaveCancelled: afterLeave,
};
function afterLeave(el: HTMLExpandElement) {
if (expandedParentClass && el._parent) {
el._parent.classList.remove(expandedParentClass);
}
resetStyles(el);
}
function resetStyles(el: HTMLExpandElement) {
const size = el._initialStyle[sizeProperty];
el.style.overflow = el._initialStyle.overflow!;
if (size != null) el.style[sizeProperty] = size;
Reflect.deleteProperty(el, '_initialStyle');
}
}

7
src/components/core/Tree/index.ts

@ -0,0 +1,7 @@
import BasicTree from './src/BasicTree.vue';
import './style';
export { BasicTree };
// export type { ContextMenuItem } from '/@/hooks/web/useContextMenu';
export * from './src/types/tree';
export { TreeIcon } from './src/TreeIcon';

468
src/components/core/Tree/src/BasicTree.vue

@ -0,0 +1,468 @@
<script lang="tsx">
import type { CSSProperties } from 'vue';
import type {
FieldNames,
TreeState,
TreeItem,
KeyType,
CheckKeys,
TreeActionType,
} from './types/tree';
import {
defineComponent,
reactive,
computed,
unref,
ref,
watchEffect,
toRaw,
watch,
onMounted,
} from 'vue';
import { Tree, Spin, Empty } from 'ant-design-vue';
import { TreeIcon } from './TreeIcon';
import { omit, get, difference, cloneDeep } from 'lodash-es';
import { isArray, isBoolean, isEmpty, isFunction } from '@/utils/is';
import { extendSlots, getSlot } from '@/utils/helper/tsxHelper';
import { filter, treeToList, eachTree } from '@/utils/helper/treeHelper';
import { useTree } from './hooks/useTree';
import { treeEmits, treeProps } from './types/tree';
import TreeHeader from './components/TreeHeader.vue';
import { useContextMenu } from '@/hooks/useContextMenu';
import { CreateContextOptions } from '@/components/basic/ContextMenu';
import { hasPermission } from '@/utils/permission/hasPermission';
export default defineComponent({
name: 'BasicTree',
inheritAttrs: false,
props: treeProps,
emits: treeEmits,
setup(props, { attrs, slots, emit, expose }) {
const state = reactive<TreeState>({
checkStrictly: props.checkStrictly,
expandedKeys: props.expandedKeys || [],
selectedKeys: props.selectedKeys || [],
checkedKeys: props.checkedKeys || [],
});
const searchState = reactive({
startSearch: false,
searchText: '',
searchData: [] as TreeItem[],
});
const treeDataRef = ref<TreeItem[]>([]);
const [createContextMenu] = useContextMenu();
const getFieldNames = computed((): Required<FieldNames> => {
const { fieldNames } = props;
return {
children: 'children',
title: 'title',
key: 'key',
...fieldNames,
};
});
const getBindValues = computed(() => {
let propsData = {
blockNode: true,
...attrs,
...props,
expandedKeys: state.expandedKeys,
selectedKeys: state.selectedKeys,
checkedKeys: state.checkedKeys,
checkStrictly: state.checkStrictly,
fieldNames: unref(getFieldNames),
'onUpdate:expandedKeys': (v: KeyType[]) => {
state.expandedKeys = v;
emit('update:expandedKeys', v);
},
'onUpdate:selectedKeys': (v: KeyType[], row) => {
state.selectedKeys = v;
emit('update:selectedKeys', v, row);
},
onCheck: (v: CheckKeys, e) => {
let currentValue = toRaw(state.checkedKeys) as KeyType[];
if (isArray(currentValue) && searchState.startSearch) {
const value = e.node.eventKey;
currentValue = difference(currentValue, getChildrenKeys(value));
if (e.checked) {
currentValue.push(value);
}
state.checkedKeys = currentValue;
} else {
state.checkedKeys = v;
}
const rawVal = toRaw(state.checkedKeys);
emit('update:value', rawVal);
emit('check', rawVal, e);
},
onRightClick: handleRightClick,
};
return omit(propsData, 'treeData', 'class');
});
const getTreeData = computed((): TreeItem[] =>
searchState.startSearch ? searchState.searchData : unref(treeDataRef),
);
const getNotFound = computed((): boolean => {
return !getTreeData.value || getTreeData.value.length === 0;
});
const {
deleteNodeByKey,
insertNodeByKey,
insertNodesByKey,
filterByLevel,
updateNodeByKey,
getAllKeys,
getChildrenKeys,
getEnabledKeys,
getSelectedNode,
} = useTree(treeDataRef, getFieldNames);
function getIcon(params: Recordable, icon?: string) {
if (!icon) {
if (props.renderIcon && isFunction(props.renderIcon)) {
return props.renderIcon(params);
}
}
return icon;
}
async function handleRightClick({ event, node }: Recordable) {
const { rightMenuList: menuList = [], beforeRightClick } = props;
let contextMenuOptions: CreateContextOptions = { event, items: [] };
if (beforeRightClick && isFunction(beforeRightClick)) {
let result = await beforeRightClick(node, event);
if (Array.isArray(result)) {
contextMenuOptions.items = result;
} else {
Object.assign(contextMenuOptions, result);
}
} else {
contextMenuOptions.items = menuList;
}
if (!contextMenuOptions.items?.length) return;
contextMenuOptions.items = contextMenuOptions.items.filter((item) => !item.hidden);
createContextMenu(contextMenuOptions);
}
function setExpandedKeys(keys: KeyType[]) {
state.expandedKeys = keys;
}
function getExpandedKeys() {
return state.expandedKeys;
}
function setSelectedKeys(keys: KeyType[]) {
state.selectedKeys = keys;
}
function getSelectedKeys() {
return state.selectedKeys;
}
function setCheckedKeys(keys: CheckKeys) {
state.checkedKeys = keys;
}
function getCheckedKeys() {
return state.checkedKeys;
}
function checkAll(checkAll: boolean) {
state.checkedKeys = checkAll ? getEnabledKeys() : ([] as KeyType[]);
}
function expandAll(expandAll: boolean) {
state.expandedKeys = expandAll ? getAllKeys() : ([] as KeyType[]);
}
function onStrictlyChange(strictly: boolean) {
state.checkStrictly = strictly;
}
watch(
() => props.searchValue,
(val) => {
if (val !== searchState.searchText) {
searchState.searchText = val;
}
},
{
immediate: true,
},
);
watch(
() => props.treeData,
(val) => {
if (val) {
handleSearch(searchState.searchText);
}
},
);
function handleSearch(searchValue: string) {
if (searchValue !== searchState.searchText) searchState.searchText = searchValue;
emit('update:searchValue', searchValue);
if (!searchValue) {
searchState.startSearch = false;
return;
}
const { filterFn, checkable, expandOnSearch, checkOnSearch, selectedOnSearch } =
unref(props);
searchState.startSearch = true;
const { title: titleField, key: keyField } = unref(getFieldNames);
const matchedKeys: string[] = [];
searchState.searchData = filter(
unref(treeDataRef),
(node) => {
const result = filterFn
? filterFn(searchValue, node, unref(getFieldNames))
: node[titleField]?.includes(searchValue) ?? false;
if (result) {
matchedKeys.push(node[keyField]);
}
return result;
},
unref(getFieldNames),
);
if (expandOnSearch) {
const expandKeys = treeToList(searchState.searchData).map((val) => {
return val[keyField];
});
if (expandKeys && expandKeys.length) {
setExpandedKeys(expandKeys);
}
}
if (checkOnSearch && checkable && matchedKeys.length) {
setCheckedKeys(matchedKeys);
}
if (selectedOnSearch && matchedKeys.length) {
setSelectedKeys(matchedKeys);
}
}
function handleClickNode(key: string, children: TreeItem[]) {
if (!props.clickRowToExpand || !children || children.length === 0) return;
if (!state.expandedKeys.includes(key)) {
setExpandedKeys([...state.expandedKeys, key]);
} else {
const keys = [...state.expandedKeys];
const index = keys.findIndex((item) => item === key);
if (index !== -1) {
keys.splice(index, 1);
}
setExpandedKeys(keys);
}
}
watchEffect(() => {
treeDataRef.value = props.treeData as TreeItem[];
});
onMounted(() => {
const level = parseInt(props.defaultExpandLevel);
if (level > 0) {
state.expandedKeys = filterByLevel(level);
} else if (props.defaultExpandAll) {
expandAll(true);
}
});
watchEffect(() => {
state.expandedKeys = props.expandedKeys;
});
watchEffect(() => {
state.selectedKeys = props.selectedKeys;
});
watchEffect(() => {
state.checkedKeys = props.checkedKeys;
});
watch(
() => props.value,
() => {
state.checkedKeys = toRaw(props.value || []);
},
{ immediate: true },
);
watch(
() => state.checkedKeys,
() => {
const v = toRaw(state.checkedKeys);
emit('update:value', v);
emit('change', v);
},
);
watchEffect(() => {
state.checkStrictly = props.checkStrictly;
});
const instance: TreeActionType = {
setExpandedKeys,
getExpandedKeys,
setSelectedKeys,
getSelectedKeys,
setCheckedKeys,
getCheckedKeys,
insertNodeByKey,
insertNodesByKey,
deleteNodeByKey,
updateNodeByKey,
getSelectedNode,
checkAll,
expandAll,
getAllKeys,
filterByLevel: (level: number) => {
state.expandedKeys = filterByLevel(level);
},
setSearchValue: (value: string) => {
handleSearch(value);
},
getSearchValue: () => {
return searchState.searchText;
},
};
// const { isPermission } = usePermission();
function renderAction(node: TreeItem) {
const { actionList } = props;
if (!actionList || actionList.length === 0) return;
return actionList.map((item, index) => {
let nodeShow = true;
if (isFunction(item.show)) {
nodeShow = item.show?.(node);
} else if (isBoolean(item.show)) {
nodeShow = item.show;
}
if (!nodeShow) return null;
if (!hasPermission(item.auth as string)) {
return null;
}
return (
<span key={index} class="action">
{item.render(node)}
</span>
);
});
}
const treeData = computed(() => {
const data = cloneDeep(getTreeData.value);
eachTree(data, (item, _parent) => {
const searchText = searchState.searchText;
const { highlight } = unref(props);
const {
title: titleField,
key: keyField,
children: childrenField,
} = unref(getFieldNames);
const icon = getIcon(item, item.icon);
const title = get(item, titleField);
const searchIdx = searchText ? title.indexOf(searchText) : -1;
const isHighlight =
searchState.startSearch && !isEmpty(searchText) && highlight && searchIdx !== -1;
const highlightStyle = `color: ${isBoolean(highlight) ? '#f50' : highlight}`;
const titleDom = isHighlight ? (
<span class={unref(getBindValues)?.blockNode ? `${'content'}` : ''}>
<span>{title.substr(0, searchIdx)}</span>
<span style={highlightStyle}>{searchText}</span>
<span>{title.substr(searchIdx + (searchText as string).length)}</span>
</span>
) : (
title
);
item[titleField] = (
<span
class={`tree-title pl-2`}
onClick={handleClickNode.bind(null, item[keyField], item[childrenField])}
>
{item?.slots?.title ? (
getSlot(slots, 'title', item)
) : (
<>
{icon && <TreeIcon icon={icon} />}
{item?.slots?.titleBefore && getSlot(slots, item?.slots?.titleBefore, item)}
{titleDom}
{item?.slots?.titleAfter && getSlot(slots, item?.slots?.titleAfter, item)}
<span class="tree-actions">{renderAction(item)}</span>
</>
)}
</span>
);
delete item?.slots;
return item;
});
return data;
});
expose(instance);
return () => {
const { title, helpMessage, toolbar, toolbarStrictly, search, checkable } = props;
const showTitle = title || toolbar || search || slots.headerTitle;
const scrollStyle: CSSProperties = { height: 'calc(100% - 38px)' };
return (
<div class={['basic-tree', 'h-full', attrs.class]}>
{showTitle && (
<TreeHeader
checkable={checkable}
checkAll={checkAll}
expandAll={expandAll}
title={title}
search={search}
toolbar={toolbar}
toolbarStrictly={toolbarStrictly}
helpMessage={helpMessage}
onStrictlyChange={onStrictlyChange}
onSearch={handleSearch}
searchText={searchState.searchText}
>
{extendSlots(slots)}
</TreeHeader>
)}
<Spin
wrapperClassName={unref(props.treeWrapperClassName)}
spinning={unref(props.loading)}
tip="加载中..."
>
{/* <ScrollContainer style={scrollStyle} v-show={!unref(getNotFound)}> */}
<Tree {...unref(getBindValues)} showIcon={false} treeData={treeData.value} />
{/* </ScrollContainer> */}
<Empty
v-show={unref(getNotFound)}
image={Empty.PRESENTED_IMAGE_SIMPLE}
class="!mt-4"
/>
</Spin>
</div>
);
};
},
});
</script>

14
src/components/core/Tree/src/TreeIcon.ts

@ -0,0 +1,14 @@
import type { VNode, FunctionalComponent } from 'vue';
import { h } from 'vue';
import { isString } from '@vue/shared';
import { IconFont } from '@/components/basic/iconfont';
export const TreeIcon: FunctionalComponent = ({ icon }: { icon: VNode | string }) => {
if (!icon) return null;
if (isString(icon)) {
return h(IconFont, { type:icon, class: 'mr-1' });
}
return IconFont;
};

172
src/components/core/Tree/src/components/TreeHeader.vue

@ -0,0 +1,172 @@
<template>
<div class="flex px-2 py-1.5 items-center">
<slot name="headerTitle" v-if="slots.headerTitle"></slot>
{{ title }}
<!-- <BasicTitle :helpMessage="helpMessage" v-if="!slots.headerTitle && title">
{{ title }}
</BasicTitle> -->
<div
class="flex items-center flex-1 cursor-pointer justify-self-stretch"
v-if="search || toolbar"
>
<div :class="getInputSearchCls" v-if="search">
<InputSearch placeholder="请输入" size="small" allowClear v-model:value="searchValue" />
</div>
<Dropdown @click.prevent v-if="toolbar">
<!-- <Icon icon="ion:ellipsis-vertical" /> -->
<more-outlined />
<template #overlay>
<Menu @click="handleMenuClick">
<template v-for="item in toolbarList" :key="item.value">
<MenuItem v-bind="{ key: item.value }">
{{ item.label }}
</MenuItem>
<MenuDivider v-if="item.divider" />
</template>
</Menu>
</template>
</Dropdown>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, watch, useSlots } from 'vue';
import { Dropdown, Menu, MenuItem, MenuDivider, InputSearch } from 'ant-design-vue';
import { MoreOutlined } from '@ant-design/icons-vue';
import { useDebounceFn } from '@vueuse/core';
import { ToolbarEnum } from '../types/tree';
const searchValue = ref('');
const props = defineProps({
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
default: '',
},
title: {
type: String,
default: '',
},
toolbar: {
type: Boolean,
default: false,
},
checkable: {
type: Boolean,
default: false,
},
search: {
type: Boolean,
default: false,
},
searchText: {
type: String,
default: '',
},
checkAll: {
type: Function,
default: undefined,
},
expandAll: {
type: Function,
default: undefined,
},
//
toolbarStrictly: {
type: Boolean,
default: true,
},
} as const);
const emit = defineEmits(['strictly-change', 'search']);
const slots = useSlots();
const getInputSearchCls = computed(() => {
const titleExists = slots.headerTitle || props.title;
return [
'mr-1',
'w-full',
{
['ml-5']: titleExists,
},
];
});
const toolbarList = computed(() => {
const { checkable, toolbarStrictly } = props;
const defaultToolbarList = [
{ label: '展开全部', value: ToolbarEnum.EXPAND_ALL },
{
label: '折叠全部',
value: ToolbarEnum.UN_EXPAND_ALL,
divider: checkable,
},
];
const strictlyList = toolbarStrictly
? [
{ label: '层级关联', value: ToolbarEnum.CHECK_STRICTLY },
{ label: '层级独立', value: ToolbarEnum.CHECK_UN_STRICTLY },
]
: [];
return checkable
? [
{ label: '选择全部', value: ToolbarEnum.SELECT_ALL },
{
label: '取消选择',
value: ToolbarEnum.UN_SELECT_ALL,
divider: checkable,
},
...defaultToolbarList,
...strictlyList,
]
: defaultToolbarList;
});
function handleMenuClick(e: { key: ToolbarEnum }) {
const { key } = e;
switch (key) {
case ToolbarEnum.SELECT_ALL:
props.checkAll?.(true);
break;
case ToolbarEnum.UN_SELECT_ALL:
props.checkAll?.(false);
break;
case ToolbarEnum.EXPAND_ALL:
props.expandAll?.(true);
break;
case ToolbarEnum.UN_EXPAND_ALL:
props.expandAll?.(false);
break;
case ToolbarEnum.CHECK_STRICTLY:
emit('strictly-change', false);
break;
case ToolbarEnum.CHECK_UN_STRICTLY:
emit('strictly-change', true);
break;
}
}
function emitChange(value?: string): void {
emit('search', value);
}
const debounceEmitChange = useDebounceFn(emitChange, 200);
watch(
() => searchValue.value,
(v) => {
debounceEmitChange(v);
},
);
watch(
() => props.searchText,
(v) => {
if (v !== searchValue.value) {
searchValue.value = v;
}
},
);
</script>

207
src/components/core/Tree/src/hooks/useTree.ts

@ -0,0 +1,207 @@
import type { InsertNodeParams, KeyType, FieldNames, TreeItem } from '../types/tree';
import type { Ref, ComputedRef } from 'vue';
import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
import { cloneDeep } from 'lodash-es';
import { unref } from 'vue';
import { forEach } from '@/utils/helper/treeHelper';
export function useTree(treeDataRef: Ref<TreeDataItem[]>, getFieldNames: ComputedRef<FieldNames>) {
function getAllKeys(list?: TreeDataItem[]) {
const keys: string[] = [];
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return keys;
for (let index = 0; index < treeData.length; index++) {
const node = treeData[index];
keys.push(node[keyField]!);
const children = node[childrenField];
if (children && children.length) {
keys.push(...(getAllKeys(children) as string[]));
}
}
return keys as KeyType[];
}
// get keys that can be checked and selected
function getEnabledKeys(list?: TreeDataItem[]) {
const keys: string[] = [];
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return keys;
for (let index = 0; index < treeData.length; index++) {
const node = treeData[index];
node.disabled !== true && node.selectable !== false && keys.push(node[keyField]!);
const children = node[childrenField];
if (children && children.length) {
keys.push(...(getEnabledKeys(children) as string[]));
}
}
return keys as KeyType[];
}
function getChildrenKeys(nodeKey: string | number, list?: TreeDataItem[]) {
const keys: KeyType[] = [];
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return keys;
for (let index = 0; index < treeData.length; index++) {
const node = treeData[index];
const children = node[childrenField];
if (nodeKey === node[keyField]) {
keys.push(node[keyField]!);
if (children && children.length) {
keys.push(...(getAllKeys(children) as string[]));
}
} else {
if (children && children.length) {
keys.push(...getChildrenKeys(nodeKey, children));
}
}
}
return keys as KeyType[];
}
// Update node
function updateNodeByKey(key: string, node: TreeDataItem, list?: TreeDataItem[]) {
if (!key) return;
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return;
for (let index = 0; index < treeData.length; index++) {
const element: any = treeData[index];
const children = element[childrenField];
if (element[keyField] === key) {
treeData[index] = { ...treeData[index], ...node };
break;
} else if (children && children.length) {
updateNodeByKey(key, node, element[childrenField]);
}
}
}
// Expand the specified level
function filterByLevel(level = 1, list?: TreeDataItem[], currentLevel = 1) {
if (!level) {
return [];
}
const res: (string | number)[] = [];
const data = list || unref(treeDataRef) || [];
for (let index = 0; index < data.length; index++) {
const item = data[index];
const { key: keyField, children: childrenField } = unref(getFieldNames);
const key = keyField ? item[keyField] : '';
const children = childrenField ? item[childrenField] : [];
res.push(key);
if (children && children.length && currentLevel < level) {
currentLevel += 1;
res.push(...filterByLevel(level, children, currentLevel));
}
}
return res as string[] | number[];
}
/**
*
*/
function insertNodeByKey({ parentKey = null, node, push = 'push' }: InsertNodeParams) {
const treeData: any = cloneDeep(unref(treeDataRef));
if (!parentKey) {
treeData[push](node);
treeDataRef.value = treeData;
return;
}
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return;
forEach(treeData, (treeItem) => {
if (treeItem[keyField] === parentKey) {
treeItem[childrenField] = treeItem[childrenField] || [];
treeItem[childrenField][push](node);
return true;
}
});
treeDataRef.value = treeData;
}
/**
*
*/
function insertNodesByKey({ parentKey = null, list, push = 'push' }: InsertNodeParams) {
const treeData: any = cloneDeep(unref(treeDataRef));
if (!list || list.length < 1) {
return;
}
if (!parentKey) {
for (let i = 0; i < list.length; i++) {
treeData[push](list[i]);
}
} else {
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return;
forEach(treeData, (treeItem) => {
if (treeItem[keyField] === parentKey) {
treeItem[childrenField] = treeItem[childrenField] || [];
for (let i = 0; i < list.length; i++) {
treeItem[childrenField][push](list[i]);
}
treeDataRef.value = treeData;
return true;
}
});
}
}
// Delete node
function deleteNodeByKey(key: string, list?: TreeDataItem[]) {
if (!key) return;
const treeData = list || unref(treeDataRef);
const { key: keyField, children: childrenField } = unref(getFieldNames);
if (!childrenField || !keyField) return;
for (let index = 0; index < treeData.length; index++) {
const element: any = treeData[index];
const children = element[childrenField];
if (element[keyField] === key) {
treeData.splice(index, 1);
break;
} else if (children && children.length) {
deleteNodeByKey(key, element[childrenField]);
}
}
}
// Get selected node
function getSelectedNode(key: KeyType, list?: TreeItem[], selectedNode?: TreeItem | null) {
if (!key && key !== 0) return null;
const treeData = list || unref(treeDataRef);
treeData.forEach((item) => {
if (selectedNode?.key || selectedNode?.key === 0) return selectedNode;
if (item.key === key) {
selectedNode = item;
return;
}
if (item.children && item.children.length) {
selectedNode = getSelectedNode(key, item.children, selectedNode);
}
});
return selectedNode || null;
}
return {
deleteNodeByKey,
insertNodeByKey,
insertNodesByKey,
filterByLevel,
updateNodeByKey,
getAllKeys,
getChildrenKeys,
getEnabledKeys,
getSelectedNode,
};
}

204
src/components/core/Tree/src/types/tree.ts

@ -0,0 +1,204 @@
import type { ExtractPropTypes } from 'vue';
import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
import { buildProps } from '@/utils/props';
import { RoleEnum } from '@/enums/roleEnum';
// import { PermModeEnum, RoleEnum } from '/@/enums/roleEnum';
export enum ToolbarEnum {
SELECT_ALL,
UN_SELECT_ALL,
EXPAND_ALL,
UN_EXPAND_ALL,
CHECK_STRICTLY,
CHECK_UN_STRICTLY,
}
export const treeEmits = [
'update:expandedKeys',
'update:selectedKeys',
'update:value',
'change',
'check',
'update:searchValue',
];
export interface TreeState {
expandedKeys: KeyType[];
selectedKeys: KeyType[];
checkedKeys: CheckKeys;
checkStrictly: boolean;
}
export interface FieldNames {
children?: string;
title?: string;
key?: string;
}
export type KeyType = string | number;
export type CheckKeys =
| KeyType[]
| { checked: string[] | number[]; halfChecked: string[] | number[] };
export const treeProps = buildProps({
value: {
type: [Object, Array] as PropType<KeyType[] | CheckKeys>,
},
renderIcon: {
type: Function as PropType<(params: Recordable) => string>,
},
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
default: '',
},
title: {
type: String,
default: '',
},
toolbar: Boolean,
search: Boolean,
searchValue: {
type: String,
default: '',
},
checkStrictly: Boolean,
clickRowToExpand: {
type: Boolean,
default: false,
},
checkable: Boolean,
defaultExpandLevel: {
type: [String, Number] as PropType<string | number>,
default: '',
},
defaultExpandAll: Boolean,
// 工具栏是否显示 层级关联
toolbarStrictly: {
type: Boolean,
default: true,
},
fieldNames: {
type: Object as PropType<FieldNames>,
},
treeData: {
type: Array as PropType<TreeDataItem[]>,
},
actionList: {
type: Array as PropType<TreeActionItem[]>,
default: () => [],
},
expandedKeys: {
type: Array as PropType<KeyType[]>,
default: () => [],
},
selectedKeys: {
type: Array as PropType<KeyType[]>,
default: () => [],
},
checkedKeys: {
type: Array as PropType<CheckKeys>,
default: () => [],
},
beforeRightClick: {
type: Function as PropType<(...arg: any) => ContextMenuItem[] | ContextMenuOptions>,
default: undefined,
},
rightMenuList: {
type: Array as PropType<ContextMenuItem[]>,
},
// 自定义数据过滤判断方法(注: 不是整个过滤方法,而是内置过滤的判断方法,用于增强原本仅能通过title进行过滤的方式)
filterFn: {
type: Function as PropType<
(searchValue: any, node: TreeItem, fieldNames: FieldNames) => boolean
>,
default: undefined,
},
// 高亮搜索值,仅高亮具体匹配值(通过title)值为true时使用默认色值,值为#xxx时使用此值替代且高亮开启
highlight: {
type: [Boolean, String] as PropType<Boolean | String>,
default: false,
},
// 搜索完成时自动展开结果
expandOnSearch: Boolean,
// 搜索完成自动选中所有结果,当且仅当 checkable===true 时生效
checkOnSearch: Boolean,
// 搜索完成自动select所有结果
selectedOnSearch: Boolean,
loading: {
type: Boolean,
default: false,
},
treeWrapperClassName: String,
});
export type TreeProps = ExtractPropTypes<typeof treeProps>;
export interface ContextMenuItem {
label: string;
icon?: string;
hidden?: boolean;
disabled?: boolean;
handler?: Fn;
divider?: boolean;
children?: ContextMenuItem[];
auth?: string | string[] | RoleEnum | RoleEnum[];
}
export interface ContextMenuOptions {
icon?: string;
styles?: any;
items?: ContextMenuItem[];
}
export interface TreeItem extends TreeDataItem {
icon?: any;
}
export interface TreeActionItem {
render: (record: Recordable) => any;
show?: boolean | ((record: Recordable) => boolean);
auth?: string | string[] | RoleEnum | RoleEnum[];
// authMode?: PermModeEnum;
}
export interface InsertNodeParams {
parentKey: string | null;
node: TreeDataItem;
list?: TreeDataItem[];
push?: 'push' | 'unshift';
}
export interface TreeActionType {
checkAll: (checkAll: boolean) => void;
expandAll: (expandAll: boolean) => void;
setExpandedKeys: (keys: KeyType[]) => void;
getExpandedKeys: () => KeyType[];
setSelectedKeys: (keys: KeyType[]) => void;
getSelectedKeys: () => KeyType[];
setCheckedKeys: (keys: CheckKeys) => void;
getCheckedKeys: () => CheckKeys;
filterByLevel: (level: number) => void;
insertNodeByKey: (opt: InsertNodeParams) => void;
insertNodesByKey: (opt: InsertNodeParams) => void;
deleteNodeByKey: (key: string) => void;
updateNodeByKey: (key: string, node: Omit<TreeDataItem, 'key'>) => void;
setSearchValue: (value: string) => void;
getSearchValue: () => string;
getSelectedNode: (
key: KeyType,
treeList?: TreeItem[],
selectNode?: TreeItem | null,
) => TreeItem | null;
}

46
src/components/core/Tree/style/index.less

@ -0,0 +1,46 @@
.tree {
background-color: #fff;
.ant-tree-node-content-wrapper {
position: relative;
.ant-tree-title {
position: absolute;
left: 0;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
&-title {
position: relative;
display: flex;
align-items: center;
width: 100%;
padding-right: 10px;
&:hover {
.action {
visibility: visible;
}
}
}
&__content {
overflow: hidden;
}
&-actions {
position: absolute;
right: 10px;
display: flex;
.action {
margin-left: 4px;
visibility: hidden;
}
}
&-header {
border-bottom: 1px solid @border-color-base;
}
}

1
src/components/core/Tree/style/index.ts

@ -0,0 +1 @@
import './index.less';

1
src/components/core/draggable-modal/index.ts

@ -0,0 +1 @@
export { default as DraggableModal } from './index.vue';

354
src/components/core/draggable-modal/index.vue

@ -0,0 +1,354 @@
<template>
<teleport :to="getContainer()">
<div ref="modalWrapRef" class="draggable-modal" :class="{ fullscreen: fullscreenModel }">
<Modal
v-bind="omit(props, ['visible', 'onCancel', 'onOk', 'onUpdate:visible'])"
v-model:visible="visibleModel"
:mask-closable="false"
:get-container="() => modalWrapRef"
:width="innerWidth || width"
@ok="emit('ok')"
@cancel="emit('cancel')"
>
<template #title>
<slot name="title">{{ $attrs.title || '标题' }}</slot>
</template>
<template #closeIcon>
<slot name="closeIcon">
<Space class="ant-modal-operate" @click.stop>
<fullscreen-outlined v-if="!fullscreenModel" @click="fullscreenModel = true" />
<fullscreen-exit-outlined v-else @click="restore" />
<close-outlined @click="closeModal" />
</Space>
</slot>
</template>
<slot>
窗口可以拖动<br />
窗口可以通过八个方向改变大小<br />
窗口可以最小化最大化还原关闭<br />
限制窗口最小宽度/高度
</slot>
<template v-if="$slots.footer" #footer>
<slot name="footer"></slot>
</template>
</Modal>
</div>
</teleport>
</template>
<script lang="ts" setup>
import { ref, watch, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import { modalProps } from 'ant-design-vue/es/modal/Modal';
import { CloseOutlined, FullscreenOutlined, FullscreenExitOutlined } from '@ant-design/icons-vue';
import { useVModel } from '@vueuse/core';
import { throttle, omit } from 'lodash-es';
import { Modal, Space } from 'ant-design-vue';
const props = defineProps({
...modalProps(),
fullscreen: {
type: Boolean,
default: false,
},
getContainer: {
type: Function,
default: () => document.body,
},
});
const emit = defineEmits(['update:visible', 'update:fullscreen', 'ok', 'cancel']);
const route = useRoute();
const visibleModel = useVModel(props, 'visible');
const fullscreenModel = ref(props.fullscreen);
const innerWidth = ref('');
const cursorStyle = {
top: 'n-resize',
left: 'w-resize',
right: 'e-resize',
bottom: 's-resize',
topLeft: 'nw-resize',
topright: 'ne-resize',
bottomLeft: 'sw-resize',
bottomRight: 'se-resize',
auto: 'auto',
} as const;
//
let inited = false;
const modalWrapRef = ref<HTMLDivElement>();
const closeModal = () => {
visibleModel.value = false;
emit('cancel');
};
//
const centerModal = async () => {
await nextTick();
const modalEl = modalWrapRef.value?.querySelector<HTMLDivElement>('.ant-modal');
if (modalEl && modalEl.getBoundingClientRect().left < 1) {
modalEl.style.left = `${(document.documentElement.clientWidth - modalEl.offsetWidth) / 2}px`;
}
};
const restore = async () => {
fullscreenModel.value = false;
centerModal();
};
const registerDragTitle = (dragEl: HTMLDivElement, handleEl: HTMLDivElement) => {
handleEl.style.cursor = 'move';
handleEl.onmousedown = throttle((e: MouseEvent) => {
if (fullscreenModel.value) return;
document.body.style.userSelect = 'none';
const disX = e.clientX - dragEl.getBoundingClientRect().left;
const disY = e.clientY - dragEl.getBoundingClientRect().top;
const mousemove = (event: MouseEvent) => {
if (fullscreenModel.value) return;
let iL = event.clientX - disX;
let iT = event.clientY - disY;
const maxL = document.documentElement.clientWidth - dragEl.offsetWidth;
const maxT = document.documentElement.clientHeight - dragEl.offsetHeight;
iL <= 0 && (iL = 0);
iT <= 0 && (iT = 0);
iL >= maxL && (iL = maxL);
iT >= maxT && (iT = maxT);
dragEl.style.left = `${iL}px`;
dragEl.style.top = `${iT}px`;
};
const mouseup = () => {
document.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', mouseup);
document.body.style.userSelect = 'auto';
};
document.addEventListener('mousemove', mousemove);
document.addEventListener('mouseup', mouseup);
}, 20);
};
const initDrag = async () => {
await nextTick();
const modalWrapRefEl = modalWrapRef.value!;
const modalWrapEl = modalWrapRefEl.querySelector<HTMLDivElement>('.ant-modal-wrap');
const modalEl = modalWrapRefEl.querySelector<HTMLDivElement>('.ant-modal');
if (modalWrapEl && modalEl) {
centerModal();
const headerEl = modalEl.querySelector<HTMLDivElement>('.ant-modal-header');
headerEl && registerDragTitle(modalEl, headerEl);
modalWrapEl.onmousemove = throttle((event: MouseEvent) => {
if (fullscreenModel.value) return;
const left = event.clientX - modalEl.offsetLeft;
const top = event.clientY - modalEl.offsetTop;
const right = event.clientX - modalEl.offsetWidth - modalEl.offsetLeft;
const bottom = event.clientY - modalEl.offsetHeight - modalEl.offsetTop;
const isLeft = left <= 0 && left > -8;
const isTop = top < 5 && top > -8;
const isRight = right >= 0 && right < 8;
const isBottom = bottom > -5 && bottom < 8;
//
if (isLeft && top > 5 && bottom < -5) {
modalWrapEl.style.cursor = cursorStyle.left;
//
} else if (isTop && left > 5 && right < -5) {
modalWrapEl.style.cursor = cursorStyle.top;
//
} else if (isRight && top > 5 && bottom < -5) {
modalWrapEl.style.cursor = cursorStyle.right;
//
} else if (isBottom && left > 5 && right < -5) {
modalWrapEl.style.cursor = cursorStyle.bottom;
//
} else if (left > -8 && left <= 5 && top <= 5 && top > -8) {
modalWrapEl.style.cursor = cursorStyle.topLeft;
//
} else if (left > -8 && left <= 5 && bottom <= 5 && bottom > -8) {
modalWrapEl.style.cursor = cursorStyle.bottomLeft;
//
} else if (right < 8 && right >= -5 && top <= 5 && top > -8) {
modalWrapEl.style.cursor = cursorStyle.topright;
//
} else if (right < 8 && right >= -5 && bottom <= 5 && bottom > -8) {
modalWrapEl.style.cursor = cursorStyle.bottomRight;
} else {
modalWrapEl.style.cursor = cursorStyle.auto;
}
}, 20);
modalWrapEl.onmousedown = (e: MouseEvent) => {
if (fullscreenModel.value) return;
const {
top: iParentTop,
bottom: iParentBottom,
left: iParentLeft,
right: iParentRight,
} = modalEl.getBoundingClientRect();
const disX = e.clientX - iParentLeft;
const disY = e.clientY - iParentTop;
const iParentWidth = modalEl.offsetWidth;
const iParentHeight = modalEl.offsetHeight;
const cursor = modalWrapEl.style.cursor;
const mousemove = throttle((event: MouseEvent) => {
if (fullscreenModel.value) return;
if (cursor !== cursorStyle.auto) {
document.body.style.userSelect = 'none';
}
const mLeft = `${Math.max(0, event.clientX - disX)}px`;
const mTop = `${Math.max(0, event.clientY - disY)}px`;
const mLeftWidth = `${Math.min(
iParentRight,
iParentWidth + iParentLeft - event.clientX,
)}px`;
const mRightWidth = `${Math.min(
window.innerWidth - iParentLeft,
event.clientX - iParentLeft,
)}px`;
const mTopHeight = `${Math.min(
iParentBottom,
iParentHeight + iParentTop - event.clientY,
)}px`;
const mBottomHeight = `${Math.min(
window.innerHeight - iParentTop,
event.clientY - iParentTop,
)}px`;
//
if (cursor === cursorStyle.left) {
modalEl.style.left = mLeft;
modalEl.style.width = mLeftWidth;
//
} else if (cursor === cursorStyle.top) {
modalEl.style.top = mTop;
modalEl.style.height = mTopHeight;
//
} else if (cursor === cursorStyle.right) {
modalEl.style.width = mRightWidth;
//
} else if (cursor === cursorStyle.bottom) {
modalEl.style.height = mBottomHeight;
//
} else if (cursor === cursorStyle.topLeft) {
modalEl.style.left = mLeft;
modalEl.style.top = mTop;
modalEl.style.height = mTopHeight;
modalEl.style.width = mLeftWidth;
//
} else if (cursor === cursorStyle.topright) {
modalEl.style.top = mTop;
modalEl.style.width = mRightWidth;
modalEl.style.height = mTopHeight;
//
} else if (cursor === cursorStyle.bottomLeft) {
modalEl.style.left = mLeft;
modalEl.style.width = mLeftWidth;
modalEl.style.height = mBottomHeight;
//
} else if (cursor === cursorStyle.bottomRight) {
modalEl.style.width = mRightWidth;
modalEl.style.height = mBottomHeight;
}
innerWidth.value = modalEl.style.width;
}, 20);
const mouseup = () => {
document.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', mouseup);
document.body.style.userSelect = 'auto';
modalWrapEl.style.cursor = cursorStyle.auto;
};
document.addEventListener('mousemove', mousemove);
document.addEventListener('mouseup', mouseup);
};
}
inited = true;
};
watch(visibleModel, async (val) => {
if ((val && Object.is(inited, false)) || props.destroyOnClose) {
initDrag();
}
});
watch(() => route.fullPath, closeModal);
</script>
<style lang="less">
.draggable-modal {
&.fullscreen {
.ant-modal {
top: 0 !important;
right: 0 !important;
bottom: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
max-width: 100vw !important;
}
.ant-modal-content {
width: 100% !important;
height: 100% !important;
}
}
.ant-modal {
position: fixed;
padding: 0;
min-height: 200px;
min-width: 200px;
.ant-modal-close {
top: 6px;
right: 6px;
&:hover,
&:focus {
color: rgba(0, 0, 0, 0.45);
}
.ant-space-item:hover .anticon,
.ant-space-item:focus .anticon {
color: rgba(0, 0, 0, 0.75);
text-decoration: none;
}
.ant-modal-close-x {
width: 50px;
height: 50px;
line-height: 44px;
.ant-space {
width: 100%;
height: 100%;
}
}
}
.ant-modal-content {
/* width: ~'v-bind("props.width")px'; */
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
min-height: 200px;
min-width: 200px;
overflow: hidden;
.ant-modal-body {
flex: auto;
overflow: auto;
height: 100%;
}
}
}
}
</style>

13
src/components/core/dynamic-table/index.ts

@ -0,0 +1,13 @@
import DynamicTable from './src/dynamic-table.vue';
import type { DefineComponent, Ref } from 'vue';
import type { DynamicTableInstance, DynamicTableProps } from './src/dynamic-table';
export { DynamicTable };
export * from './src/types/';
export * from './src/hooks/';
export * from './src/dynamic-table';
export type DynamicTableRef = Ref<DynamicTableInstance>;
// TODO 暂时是为了解决ts error(如果你有解决方法 请务必联系我~): JSX element type 'DynamicTable' does not have any construct or call signatures.
export default DynamicTable as unknown as DefineComponent<Partial<DynamicTableProps>>;

190
src/components/core/dynamic-table/src/components/editable-cell/index.vue

@ -0,0 +1,190 @@
<template>
<Spin :spinning="saving">
<div class="editable-cell">
<Popover :visible="!!errorMsgs?.length" placement="topRight">
<template #content>
<template v-for="err in errorMsgs" :key="err">
<a-typography-text type="danger">{{ err }}</a-typography-text>
</template>
</template>
<a-row type="flex" :gutter="[8]">
<SchemaFormItem
v-if="(getIsEditable || getIsCellEdit) && getSchema"
v-model:form-model="editFormModel"
:schema="getSchema"
:table-instance="tableContext"
:table-row-key="rowKey"
>
<template v-for="item in Object.keys($slots)" #[item]="data" :key="item">
<slot :name="item" v-bind="data || {}"></slot>
</template>
</SchemaFormItem>
<a-col v-if="getIsCellEdit" :span="4" class="!flex items-center">
<CheckOutlined @click="handleSaveCell" />
<CloseOutlined @click="handleCancelSaveCell" />
</a-col>
</a-row>
</Popover>
<template v-if="!isCellEdit && editableType === 'cell'">
<slot />
<EditOutlined @click="startEditCell" />
</template>
</div>
</Spin>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue';
import { EditOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons-vue';
import { Popover, Typography, Row, Col, Spin } from 'ant-design-vue';
import { useTableContext } from '../../hooks';
import type { PropType } from 'vue';
import type { CustomRenderParams, EditableType } from '@/components/core/dynamic-table/src/types';
import { schemaFormItemProps, SchemaFormItem } from '@/components/core/schema-form';
import { isAsyncFunction } from '@/utils/is';
export default defineComponent({
name: 'EditableCell',
components: {
SchemaFormItem,
EditOutlined,
CloseOutlined,
CheckOutlined,
Popover,
Spin,
[Col.name]: Col,
[Row.name]: Row,
'a-typography-text': Typography.Text,
},
props: {
...schemaFormItemProps,
rowKey: [String, Number] as PropType<Key>,
editableType: [String] as PropType<EditableType>,
column: [Object] as PropType<CustomRenderParams>,
},
setup(props) {
const saving = ref(false);
const isCellEdit = ref(props.column?.column?.defaultEditable);
const tableContext = useTableContext();
const {
editFormModel,
editTableFormRef,
editFormErrorMsgs,
editableCellKeys,
isEditable,
startCellEditable,
cancelCellEditable,
validateCell,
} = tableContext;
const dataIndex = computed(() => {
return String(props.column?.column?.dataIndex);
});
const getSchema = computed(() => {
const field = props.schema.field;
const schema = editTableFormRef.value?.getSchemaByFiled(field) || props.schema;
return {
...schema,
colProps: {
...schema.colProps,
span: props.editableType === 'cell' ? 20 : 24,
},
};
});
const getIsEditable = computed(() => {
return props.rowKey && isEditable(props.rowKey);
});
const getIsCellEdit = computed(() => {
const { rowKey } = props;
return (
isCellEdit.value &&
props.editableType === 'cell' &&
editableCellKeys.value.has(`${rowKey}.${dataIndex.value}`)
);
});
const errorMsgs = computed(() => {
const field = props.schema.field;
return editFormErrorMsgs.value.get(field);
});
const startEditCell = () => {
startCellEditable(props.rowKey!, dataIndex.value, props.column?.record);
isCellEdit.value = true;
};
const handleSaveCell = async () => {
const { rowKey, column } = props;
await validateCell(rowKey!, dataIndex.value);
if (isAsyncFunction(tableContext?.onSave)) {
saving.value = true;
await tableContext
.onSave(rowKey!, editFormModel.value[rowKey!], column?.record)
.finally(() => (saving.value = false));
cancelCellEditable(rowKey!, dataIndex.value);
isCellEdit.value = false;
}
};
const handleCancelSaveCell = () => {
const { rowKey, column } = props;
tableContext?.onCancel?.(rowKey!, editFormModel.value[rowKey!], column?.record);
isCellEdit.value = false;
cancelCellEditable(props.rowKey!, dataIndex.value);
};
//
if (isCellEdit.value && props.editableType === 'cell') {
startEditCell();
}
return {
saving,
isCellEdit,
editableCellKeys,
editFormModel,
getSchema,
getIsEditable,
getIsCellEdit,
errorMsgs,
tableContext,
startEditCell,
handleSaveCell,
handleCancelSaveCell,
};
},
});
</script>
<style lang="less" scoped>
.editable-cell {
position: relative;
padding: 5px 0;
&:hover {
.anticon-edit {
display: block;
}
}
.anticon-edit {
display: none;
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
}
}
:deep(.ant-form-item-explain) {
display: none;
}
:deep(.ant-form-item-with-help) {
margin: 0;
}
</style>

3
src/components/core/dynamic-table/src/components/index.ts

@ -0,0 +1,3 @@
export { default as TableAction } from './table-action.vue';
export { default as ToolBar } from './tool-bar/index.vue';
export { default as EditableCell } from './editable-cell/index.vue';

130
src/components/core/dynamic-table/src/components/table-action.vue

@ -0,0 +1,130 @@
<template>
<div class="flex items-center justify-around">
<template v-for="(actionItem, index) in actionFilters" :key="`${index}-${actionItem.label}`">
<component
:is="actionItem.popConfirm ? 'a-popconfirm' : 'span'"
:title="actionItem.title"
v-bind="actionItem.popConfirm"
>
<div class="flex items-center">
<SvgIcon
v-if="actionItem.icon"
:size="actionItem.size"
:name="actionItem.icon"
:color="actionItem.color"
@click="actionItem.onClick"
/>
<a-button
v-else
type="link"
:loading="loadingMap.get(getKey(actionItem, index))"
:disabled="actionItem.disabled"
:title="actionItem.title"
@click="actionItem.onClick"
>
{{ actionItem.label }}
</a-button>
<!-- <Divider type="vertical" class="action-divider" v-if="index < actionFilters.length - 1" /> -->
</div>
</component>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue';
import { Popconfirm } from 'ant-design-vue';
import type { PropType } from 'vue';
import type { ActionItem } from '../types/tableAction';
import type { CustomRenderParams } from '../types/column';
import { hasPermission } from '@/utils/permission/hasPermission';
import { isString, isObject, isAsyncFunction, isBoolean, isFunction } from '@/utils/is';
import { Divider } from 'ant-design-vue';
import { SvgIcon } from '@/components/basic/svg-icon';
export default defineComponent({
components: { [Popconfirm.name]: Popconfirm, SvgIcon, Divider },
props: {
actions: {
//
type: Array as PropType<ActionItem[]>,
default: () => [],
},
columnParams: {
type: Object as PropType<CustomRenderParams>,
default: () => ({}),
},
rowKey: [String, Number] as PropType<Key>,
},
setup(props) {
const loadingMap = ref(new Map<string, boolean>());
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(action);
}
return isIfShow;
}
const actionFilters = computed(() => {
return props.actions
.filter((item) => isIfShow(item))
.filter((item) => {
const auth = item.auth;
if (Object.is(auth, undefined)) {
return true;
}
if (isString(auth)) {
const isValid = hasPermission(auth);
item.disabled ??= !isValid;
if (item.disabled && !isValid) {
item.title = '对不起,您没有该操作权限!';
}
return isValid;
}
if (isObject(auth)) {
const isValid = hasPermission(auth.perm);
const isDisable = auth.effect !== 'delete';
item.disabled ??= !isValid && isDisable;
if (item.disabled && !isValid) {
item.title = '对不起,您没有该操作权限!';
}
return isValid || isDisable;
}
})
.map((item, index) => {
const onClick = item.onClick;
if (isAsyncFunction(onClick)) {
item.onClick = async () => {
const key = getKey(item, index);
loadingMap.value.set(key, true);
await onClick(props.columnParams).finally(() => {
loadingMap.value.delete(key);
});
};
}
return item;
});
});
const getKey = (actionItem: ActionItem, index: number) => {
return `${props.rowKey}${index}${actionItem.label}`;
};
return {
actionFilters,
loadingMap,
getKey,
};
},
});
</script>

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

Loading…
Cancel
Save