Compare commits

..

1 Commits

Author SHA1 Message Date
Vben
228b6cb934 chore: bump 2.3.0 2021-04-10 23:22:13 +08:00
738 changed files with 20651 additions and 33652 deletions

View File

@@ -1,3 +1,5 @@
VITE_PORT = 3100
# Whether to open mock
VITE_USE_MOCK = true
@@ -6,7 +8,7 @@ VITE_PUBLIC_PATH = /
# Cross-domain proxy, you can configure multiple
# Please note that no line breaks
VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3300/upload"]]
VITE_PROXY = [["/basic-api","http://localhost:3000"],["/upload","http://localhost:3001/upload"]]
# VITE_PROXY=[["/api","https://vvbin.cn/test"]]
# Delete console

View File

@@ -1,36 +0,0 @@
NODE_ENV=production
# Whether to open mock
VITE_USE_MOCK = true
# public path
VITE_PUBLIC_PATH = /
# Delete console
VITE_DROP_CONSOLE = true
# Whether to enable gzip or brotli compression
# Optional: gzip | brotli | none
# If you need multiple forms, you can use `,` to separate
VITE_BUILD_COMPRESS = 'none'
# Whether to delete origin files when using compress, default false
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
# Basic interface address SPA
VITE_GLOB_API_URL=/basic-api
# File upload address optional
# It can be forwarded by nginx or write the actual address directly
VITE_GLOB_UPLOAD_URL=/upload
# Interface prefix
VITE_GLOB_API_URL_PREFIX=
# Whether to enable image compression
VITE_USE_IMAGEMIN= true
# use pwa
VITE_USE_PWA = false
# Is it compatible with older browsers
VITE_LEGACY = false

View File

@@ -22,10 +22,8 @@ module.exports = defineConfig({
'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended',
'plugin:jest/recommended',
],
rules: {
'vue/script-setup-uses-vars': 'error',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',

View File

@@ -1,28 +0,0 @@
---
name: 🐛 Bug 报告
about: 向我们报告一个Bug以帮助我们改进
title: ''
labels: 'bug: pending triage'
assignees: ''
---
**⚠️ 重要 ⚠️ 在进一步操作之前,请检查下列选项。如果您忽视此模板或者没有提供关键信息,您的 Issue 将直接被关闭**
- [ ] 已阅读 [文档](https://anncwb.github.io/vue-vben-admin-doc/).
- [ ] 确保您的代码已是最新或者所报告的 Bug 在最新版本中可以重现. (部分 Bug 可能已经在最近的代码中修复)
- [ ] 已在 Issues 中搜索了相关的关键词
- [ ] 不是 ant design vue 组件库的 Bug
### 描述 Bug
请清晰地描述此 Bug 的具体表现。
### 复现 Bug
请描述在演示页面中复现 Bug 的详细步骤,以确保我们可以理解并定位问题。部分 Bug 如果未在 Demo 中涉及,请务必提供关键代码
## 系统信息
- 操作系统:
- Node 版本:
- 包管理器 (npm/yarn/pnpm) 及其版本:

View File

@@ -68,10 +68,10 @@ jobs:
sed -i "s#VITE_BUILD_COMPRESS\s*=.*#VITE_BUILD_COMPRESS = 'gzip'#g" ./.env.production
cat ./.env.production
- name: use Node.js 16
- name: use Node.js 14
uses: actions/setup-node@v2.1.2
with:
node-version: '16.x'
node-version: '14.x'
- name: Get yarn cache
id: yarn-cache

View File

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

4
.gitignore vendored
View File

@@ -4,14 +4,12 @@ dist
.npmrc
.cache
tests/server/static
tests/server/static/upload
test/upload-server/static
.local
# local env files
.env.local
.env.*.local
.eslintcache
# Log files
npm-debug.log*

1
.husky/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
_

View File

@@ -6,3 +6,5 @@
# Format and submit code according to lintstagedrc.js configuration
npm run lint:lint-staged
npm run lint:pretty

2
.vscode/launch.json vendored
View File

@@ -8,6 +8,6 @@
"url": "http://localhost:3100",
"webRoot": "${workspaceFolder}/src",
"sourceMaps": true
}
},
]
}

132
.vscode/settings.json vendored
View File

@@ -1,16 +1,56 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"volar.tsPlugin": true,
"volar.tsPluginStatus": false,
//===========================================
//============= Editor ======================
//===========================================
"explorer.openEditors.visible": 0,
"editor.tabSize": 2,
"editor.renderControlCharacters": true,
"editor.minimap.renderCharacters": false,
"editor.minimap.maxColumn": 300,
"editor.minimap.showSlider": "always",
"editor.cursorBlinking": "phase",
"editor.cursorSmoothCaretAnimation": true,
"editor.detectIndentation": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"diffEditor.ignoreTrimWhitespace": false,
"javascript.format.insertSpaceBeforeFunctionParenthesis": true,
"editor.suggestSelection": "first",
"editor.trimAutoWhitespace": true,
"editor.quickSuggestions": {
"other": true,
"comments": true,
"strings": true
},
//===========================================
//============= Other =======================
//===========================================
"breadcrumbs.enabled": true,
"open-in-browser.default": "chrome",
//===========================================
//============= emmet =======================
//===========================================
"emmet.triggerExpansionOnTab": true,
"emmet.showAbbreviationSuggestions": true,
"emmet.showExpandedAbbreviation": "always",
"emmet.syntaxProfiles": {
"vue-html": "html",
"vue": "html",
"xml": {
"attr_quotes": "single"
}
},
"emmet.includeLanguages": {
"jsx-sublime-babel-tags": "javascriptreact"
},
//===========================================
//============= files =======================
//===========================================
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.eol": "\n",
"search.exclude": {
"**/node_modules": true,
@@ -33,14 +73,9 @@
"CHANGELOG.md": true,
"examples": true,
"res": true,
"screenshots": true,
"yarn-error.log": true,
"**/.yarn": true
"screenshots": true
},
"files.exclude": {
"**/.cache": true,
"**/.editorconfig": true,
"**/.eslintcache": true,
"**/bower_components": true,
"**/.idea": true,
"**/tmp": true,
@@ -62,9 +97,56 @@
},
"stylelint.enable": true,
"stylelint.packageManager": "yarn",
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// ===========================================
// ================ Eslint ===================
// ===========================================
"eslint.alwaysShowStatus": true,
"eslint.options": {
"plugins": ["html", "vue", "javascript", "jsx", "typescript"],
"extensions": [".js", ".jsx", ".ts", ".tsx", ".vue"]
},
"eslint.validate": [
"javascript",
"typescript",
"reacttypescript",
"reactjavascript",
"html",
"vue"
],
// ===========================================
// ================ Vetur ====================
// ===========================================
"vetur.experimental.templateInterpolationService": true,
"vetur.format.options.tabSize": 2,
"vetur.format.defaultFormatter.html": "js-beautify-html",
"vetur.format.defaultFormatter.scss": "prettier",
"vetur.format.defaultFormatter.css": "prettier",
"vetur.format.defaultFormatter.ts": "prettier-tslint",
"vetur.format.defaultFormatter.js": "prettier",
"vetur.languageFeatures.codeActions": false,
"vetur.format.defaultFormatterOptions": {
"js-beautify-html": {
"wrap_attributes": "force-expand-multiline"
},
"prettier": {
"eslintIntegration": true,
"arrowParens": "always",
"semi": false,
"singleQuote": true
}
},
"liveServer.settings.donotShowInfoMsg": true,
"terminal.integrated.rendererType": "dom",
"telemetry.enableCrashReporter": false,
"telemetry.enableTelemetry": false,
"workbench.settings.enableNaturalLanguageSearch": false,
"path-intellisense.mappings": {
"/@/": "${workspaceRoot}/src"
},
"prettier.requireConfig": true,
"typescript.updateImportsOnFileMove.enabled": "always",
"workbench.sideBar.location": "left",
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
@@ -104,39 +186,5 @@
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.enabledParsers": ["ts"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.displayLanguage": "zh-CN",
"i18n-ally.enabledFrameworks": ["vue", "react"],
"cSpell.words": [
"vben",
"windi",
"browserslist",
"tailwindcss",
"esnext",
"antv",
"tinymce",
"qrcode",
"sider",
"pinia",
"sider",
"nprogress",
"INTLIFY",
"stylelint",
"esno",
"vitejs",
"sortablejs",
"mockjs",
"codemirror",
"iconify",
"commitlint",
"vditor",
"echarts",
"cropperjs",
"logicflow",
"vueuse",
"zxcvbn",
"lintstagedrc",
"brotli",
"tailwindcss",
"sider"
]
"i18n-ally.enabledFrameworks": ["vue", "react"]
}

View File

@@ -1,354 +1,3 @@
## 2.8.0(2021-11.03)
### Upgrade Instructions
- Package manager changed from `yarn` to `pnpm`
- Delete `node_modules` and `yarn.lock`, install `pnpm` globally
- Execute `pnpm install`
### ✨ Features
- **Others**
- The `VITE_PROXY` configuration in the `.env` file supports single quotes
- Remove warnings during build
### 🐛 Bug Fixes
- **BasicTable**
- Fix the issue that editable cells cannot be submitted in some cases
- Fix the problem that the `inset` attribute does not work
- Fix the problem that the performance of `useTable` and `reload` method `await` of `BasicTable` instance are inconsistent
- Fix the issue that `clickToRowSelect` would ignore the disabled state of the row selection box
- Fix the problem that the page of `BasicTable` will be reset in some cases
- Modify the `deleteTableDataRecord` method
- **BasicModal**
- Fixed the problem that `Modal` could not be closed even when clicking on the mask and pressing the `Esc` key
- Fixed the issue that clicking the close button and the blank area next to the maximize button would also cause `Modal` to close
- **BasicTree** Fix the problem that the node slot does not work
- **CodeEditor** Fix the problem that may cause `Build` failure
- **BasicForm** Fix the problem that the content width of the custom FormItem component may be out of range
- **ApiTreeSelect** Fix the problem that the change of `params` failed to trigger the re-request of api data
- **Others** -Fixed an issue where multiple tabs would not jump to routing when closing tabs in some cases
- Fix the issue that some components may cause abnormal hot update
- Fix the problem that some sub-components of `antdv` will be reported in the build process when directly `import` part of the `antdv`, such as: TabPane, RadioGroup
## 2.7.2(2021-09-14)
### ✨ Features
- **BasicForm** New `Divider` in the form component for dividing the area of longer forms
- **BasicTable**
- Cell editor adds submit callback, which will decide whether to submit data to the form based on the result returned by the callback function
- Add check method for row editing, allowing only check but not submit value, so asynchronously save data successfully before submit to table
- Fix the problem that the `rowClassName` property cannot be used at the same time as `striped`.
- New component **MarkdownViewer** for displaying rich text in Markdown format
### 🐛 Bug Fixes
- **CodeEditor** Fix JSON editor throwing exception when formatting invalid JSON text
- **Tinymce** fixes an issue where inline mode throws an exception in some scenarios
- **BasicTable**
- Repair the problem that the editing icon is not displayed when the content of editable cell is empty
- Repair the problem that the total row at the end of the table sometimes fails to align with the columns in the main part of the table.
- **MarkDown** Repair the problem that the value of initial value property does not work.
- **BasicUpload** Repair the problem that `accept` property does not support `MIME` and suffix name starting with dot.
- **ApiSelect** Fix the problem of type definition of `value` property.
- **Other**
- Repair the problem that some wrapper components give error when using slots.
- Repair the problem that `theme` parameter of `useECharts` does not work.
- Repair the problem that when `Token` is invalid, pressing F5 to refresh the page may cause abnormal page loading.
- Repair the problem that the improper call of `useRedo` may lead to `path` redirection abnormality.
- Repair the problem that `vite` custom mode name does not support underscore.
## 2.7.1(2021-08-16)
- Upgrade vue 3.2, if the operation fails, delete node_modules and reinstall it
### ✨ Features
- **BasicTree** Add search function related properties and methods
- **BasicForm** added `alwaysShowLines` to set the number of lines kept displayed when folding
### 🐛 Bug Fixes
- **Cropper** Fix the problem of failure to destroy in time
- **BasicTable**
- Fix the problem that `CellFormat` cannot use `Map` type data
- Fixed an issue where the editable cell failed to display the `0` value correctly
- Fixed the issue that selection-change event failed to trigger correctly when unchecked
- Fix the problem that the background color of the full screen state under the light theme is incorrect
- Fix the problem of obtaining complete data when `getSelectRows` does not support remote data cross-page selection
- Fix the issue that the `size` property provided for editing components in `editComponentProps` is invalid
- **Qrcode** Fixed the problem that the QR code component could not be drawn in time when it was created
- **BasicModal** Fix the problem that the `helpMessage` property does not work
- **BasicButton** Fix the problem that the button style performance is inconsistent with the official antd
- **Others** Fix the problem that `useRedo` (reload the current route) will lose route `params` data
## 2.7.0(2021-08-03)
## (Breaking changes) Breaking changes
- Restore the project `tailwindcss` back to `windicss`, tried `tailwindcss`, there may be a lot of problems, first switch back to `windicss` to improve development efficiency and lower switching costs.
- There are currently incompatible areas of the project
- The wording of `xl:!m-4` needs to be changed to `!xl:m-4`, note that only `!` is incompatible. If you dont use it, you dont need to change it.
- The memory overflow problem may still exist (low frequency, just restart, restart vite faster)
### ✨ Features
- **Preview** Add new properties and events
- **Dark Theme** added support for tailwindcss night mode
- **Others** add setTip method for useLoading
### 🐛 Bug Fixes
- **ApiTreeSelect** Fixed the problem of failing to monitor `params` changes correctly
- **ImgRotateDragVerify** Fix the problem that the component `resume` method cannot be called
- **TableAction** Fix the problem that the stopButtonPropagation property does not work in some cases
- **PageWrapper** Fix the problem of invalid `class` attribute
- **BasicTree** Fix the problem that the `checkAll` method will affect the `disabled` state node
- **BasicTable**
- Fix the issue that editable cells do not support `ellipsis` configuration
- Fixed the problem that the pop-up layer of sub-components (popconfirm and edit components such as select and treeSelect) cannot be seen in full-screen mode
- Fixed an issue where when `expandRowByClick` is enabled, clicking non-expandable rows may cause style errors
- Fix the problem that the dynamic change of `pagination` property does not take effect
- Fix the problem that `getSelectRows` does not support the child data of the tree table -**Dark Theme** Fix the color matching problem under the dark theme
- Fix the background color of the selected node of the `Tree` component
- Fix the color configuration of the `Alert` component
- Fix the problem of the button color of `link` type in the disabled state
- Fix the style problem of checked checkboxes in `Tree` -**Others** Fix the problem that useScript failed to automatically remove the script node
## 2.6.1(2021-07-19)
### ✨ Features
- **NoticeList** Add pagination, auto omit for overlength, title click event, title strikethrough, etc.
- **MixSider** Optimize the style of the bottom collapse button in the Mix menu layout to be consistent with the style of other menu layouts
- **ApiTreeSelect** Extend `TreeSelect` component of `antdv` to support remote data source, similar to `ApiSelect`.
- **BasicTable** New `ApiTreeSelect` editing component
- Different backend home pages can be specified for different users.
- Add `homePath` field (optional) to the user information returned by the `getUserInfo` interface to customize the home page path for the current user
### 🐛 Bug Fixes
- **BasicTable**
- Fix scrollbar style issue (removed scroll style patch)
- Fix the alignment problem of cells with expanded icons in tree tables
- Add `headerTop` slot.
- Fix the color display of the operation column button in disabled state.
- Repair the problem that the values of editable cells cannot be updated by modifying `dataSource` directly.
- Repair the problem of data replay when using `ApiSelect` to edit components.
- Repair the problem that editing components may report `onXXX` type error in some scenarios.
- **TableAction**
- Create Tooltip component only if `action.tooltip` exists.
- Fix the problem that the content of the round button inside the component is not centered
- **AppSearch** Fix the problem that the hidden menu may be searched.
- **BasicUpload** Repair the problem of error when handling non-`array` values.
- **Form** Repair the `suffix` slot style problem of `FormItem`.
- **Menu**
- Repair the hovering trigger logic of the left mixed menu
- Repair the problem that the top bar menu is wrong when displaying menu items that need to be hidden.
- Fix the left mixed menu in hover trigger mode will jump to route directly when there is no submenu and it is activated
- **Breadcrumb** Repair the problem that the menu with redirection cannot be jumped when clicked
- **Markdown** fixes an initialization exception and an issue where value was not set dynamically correctly
- **Modal** Make sure props are passed correctly
- **MultipleTab** fixes an issue that could accidentally create login route tabs
- **BasicTree** Fix the problem that the search function may cause `checkedKeys` to be lost
- **CodeEditor** Fix the problem that value does not support v-model usage.
- **CountdownInput** Fix the problem that `input` slot is not supported.
- **ApiSelect** Fix the problem that the `options-change` event parameter is not the standard `options` data used by `select
- **Other**
- Fix the problem that the configuration of default menu collapse does not work
- Repair the problem that `safari` browser reports an error and the website cannot be opened.
- Repair the problem that eslint keeps error due to endOfLine after pulling the code on window.
- Fix `Vue Router warn` caused by dynamic routing
### 🎫 Chores
- Add test environment test command
## 2.6.0(2021-07-04)
### ✨ Features
- **Axios** New `withToken` configuration to control whether the request carries a token or not
- **BasicUpload**
- New `preview-delete` event triggered when deleting a file in preview `Modal`.
- `value` supports `v-model` usage
- **Route configuration**
- Add `ignoreRoute` to generate menu only in `ROUTE_MAPPING` or `BACK` permission mode
- Add `hidePathForChildren` configuration to ignore this level `path` when generating menus for child items
- **TableAction** Add `tooltip` configuration to add tooltip hint for button
- **CropperAvatar**
- Added `value` to set the current avatar
- Added `onChange` to accept avatar cropping and upload success event
- New `btnText`, `btnProps` for customizing the text and properties of the upload button
- Add tooltips to the action buttons in `Modal` for cropping
- **Modal** Add tooltip for action button in top right corner
### 🐛 Bug Fixes
- **Modal**
- Fix the problem that the mask cannot be closed by clicking on it.
- Fix `setModalProps` does not support setting `defaultFullscreen`.
- **Table**
- Fix the problem that `editComponentProps` doesn't support `onChange`.
- Fix the problem that `selection-change` event is not triggered when `clickToRowSelect` is enabled.
- Fix the problem that global configuration `fetchSetting` may be accidentally modified by local configuration.
- Fix the problem that the parameter of `handleSearchInfoFn` contains redundant blank keys.
- Repair the problem that when rowSelection.onChange is provided for table, the selected items of table cannot be changed manually.
- Fix the problem that the scrollbar continues to be displayed even when it is not needed to be displayed.
- **Icon** Repair the problem that SvgIcon is missing some styles.
- **Menu**
- Repair the problem that single-level menu refreshing will not be activated in route mapping mode.
- Repair the problem that the collapse customization at the bottom of the side menu is invalid.
- **Form** Repair the type definition of `submitButtonOptions` and `resetButtonOptions`.
- **PopConfirmButton** Remove the redundant `title` on `Button`.
- **Axios** Fix the problem that `params` and `data` data cannot be submitted at the same time when non-`GET` requests are made
- **Other**
- Repair the problem that the lock screen function can skip the lock state by refreshing the page or copying the URL to open a new browser tab
- Repair the problem that `Token` won't be synchronized when multiple windows open pages at the same time.
- Repair the problem that `hasPermission` does not work in `ROLE` permission mode.
- **Table** Repair the problem that the parameter of `handleSearchInfoFn` contains extra blank keys.
- **Tailwindcss** Remove console warning
## 2.5.2(2021-06-27)
### ⚡ Performance Improvements
- **Icon** Remove the global registration of Icon components to prevent hot update issues under certain circumstances
### ✨ Features
- **Menu** Added `permissionMode=PermissionModeEnum.ROUTE_MAPPING` mode
- The project is changed to this mode by default, and the original menu file is deleted
- If you have written the menu before, you can change to `PermissionModeEnum.ROLE` mode
## 2.5.1(2021-06-26)
### ⚡ Performance Improvements
- Upgrade `vue` and `ant-design-vue` versions to solve compatibility issues
- **Tree** Performance optimization
### 🐛 Bug Fixes
- **Table** Fix page jitter problem
- **Upload** Make sure to carry custom parameters
- **Dropdown** Fix the icon display problem of popConfirm
- **Table** Fix the problem that the editing event of the tree table is abnormal
- **Table** Fix the problem that when the table data is empty, the value returned by getDataSource is not the data source used by the table
## 2.5.0(2021-06-20)
## (Breaking changes) Breaking changes
- Change the project `windicss` to `tailwindcss` to solve the memory overflow problem
- There are currently incompatible areas of the project
- The wording of `!xl:m-4` needs to be changed to `xl:!m-4`, note that only `!` is incompatible. If you dont use it, you dont need to change it.
- The new features of `windicss` itself need to be adjusted, for example, `Attribute` mode is not compatible
### ✨ Refactor
- Remove `useExpose` and use `expose` provided by the component itself instead
### ⚡ Performance Improvements
- **Locale** merge multi-language files to reduce the number of files
- **Utils** Mitt default export is changed from `Class` to `Function`
- **Axios** `isTransformRequestResult` is renamed to `isTransformResponse`
### ✨ Features
- **CropperImage** `Cropper` Avatar cropping adds circular cropping function
- **CropperAvatar** Added avatar upload component
- **Drawer** `useDrawer` added `closeDrawer` function
- **Preview** Added `createImgPreview` picture preview function
- **Setup** New guide page example
- **Tests** Add jest test suite, Vue component single test is not currently supported
- **Axios** Added `authenticationScheme` configuration to specify the authentication scheme
- **Setting** Added `sessionTimeoutProcessing` project configuration item, used to configure how to deal with session timeout
### 🐛 Bug Fixes
- **Modal** fix full screen height calculation error
- **Modal** Fix the problem that the shutdown event is triggered multiple times
- **PageWrapper** fix the height calculation problem
- **FlowChart** Repair drag and drop menu missing
- Fixed Iframe routing error in background mode
- **PageWrapper** Fix the height calculation problem when footer and global footer are opened at the same time
- **Menu** Fix the jitter problem of menu folding animation
- **Store** fixed type error after pinia version upgrade
## 2.4.2(2021-06-10)
### ✨ Refactor
- `CountTo` component refactoring
### ✨ Features
- `radioButtonGroup` supports `boolean` value
- `useModalInner` added `redoModalHeight` to reset the height of `Modal` inside Modal
- `useECharts` added `getInstance` to obtain instances of `echart`
- `TableAction` added `stopButtonPropagation` to prevent the action button click event from bubbling
- `BasicTable` in the row edit mode, you can get or set the value of other editing components in the column
- The `ApiSelect` component will automatically re-fetch the data after the `params` is changed
- `TableImg` component improvement
- `BasicTable` added `columns-change` event to monitor the user to change the sorting, display, and fixed status of columns
- `Tinymce` supports dynamic modification readonly
- `BasicTable` added `updateTableDataRecord` method to update the specified row data
- `useModal` added `closeModal` method to close `Modal`
### 🐛 Bug Fixes
- Fix the problem that `redoModalHeight` cannot reduce the height
- Fix the problem that the schema data of `BasicForm` does not take effect
- Fix the problem that multiple tags may cause `KeepAlive` to fail
- Fix the problem that the default `axios` interceptor cannot handle custom code
- Fix the height issue of the lock screen pop-up window
- Fixed the problem that the half-selected state of the `Column Display` checkbox of `BaiscTable` was incorrectly displayed
- Fixed the problem that the preview list of the `BasicUpload` component could not be displayed in some cases
- Fix the problem that the `options` setting of ` RadioButtonGroup``disabled ` does not take effect
- Fix the problem that the button for uploading pictures in the read-only mode of the `Tinymce` component is still available
- Fix the stuttering problem of `BasicForm` under certain circumstances
- Fix the problem that "directory" routing does not work
## 2.4.1(2021-06-01)
### ✨ Features
- Add `DatePicker` and `TimePicker` components to editable tables
- Added `defaultExpandLevel` configuration to `Tree` component
### ⚡ Performance Improvements
-Menu search default focus
### 🐛 Bug Fixes
- Fix known issues of `CodeEditor`
- Fix the issue of `i18n` console warning
- Fix the problem that the editable table `align` configuration does not take effect
- Ensure that `axios` only processes `Object` parameters
- Fix the failure of the `defaultExpandAll` configuration of the `Tree` component
- Fix the problem of missing dividing line in `TableAction`
- Fix the known issues of the table
- Fix that the lang attribute of HTML will not be set when reloading due to the first loading or changing the language
## 2.4.0 (2021-05-25)
### ✨ Features
-New graphical editor example -New code editor (including Json editor) -Added `JsonPreview`Json data viewing component -The fields of the data column and actionColumn of the table can be controlled according to the authority and business. -Added an example of a permission control table (AuthColumn.vue) -Added user login expiration example
### ⚡ Performance Improvements
-Consolidate some language files to reduce the number of files
### 🐛 Bug Fixes
-Fix the flashing white screen when the dark theme refreshes -Fix the problem that other functions are invalid when the tab is closed -Fix known issues in the form -Fix the automatic lock screen failure
## 2.3.0 (2021-04-10)
## (Breaking changes) Breaking changes

File diff suppressed because it is too large Load Diff

View File

@@ -1,370 +1,3 @@
## 2.8.0(2021-11.03)
### 升级说明
- 包管理器由`yarn`改为 `pnpm`
- 删除`node_modules``yarn.lock`,全局安装`pnpm`
- 执行`pnpm install`
### ✨ Features
- **其它**
- `.env`文件中的`VITE_PROXY`配置支持单引号
- 移除 build 过程中的警告
### 🐛 Bug Fixes
- **BasicTable**
- 修复可编辑单元格某些情况下无法提交的问题
- 修复`inset`属性不起作用的问题
- 修复`useTable``BasicTable`实例的`reload`方法`await`表现不一致的问题
- 修复`clickToRowSelect`会无视行选择框 disabled 状态的问题
- 修复`BasicTable`在某些情况下,分页会被重置的问题
- 修改 `deleteTableDataRecord` 方法
- **BasicModal**
- 修复点击遮罩、按下`Esc`键都不能关闭`Modal`的问题
- 修复点击关闭按钮、最大化按钮旁边的空白区域也会导致`Modal`关闭的问题
- **BasicTree** 修复节点插槽不起作用的问题
- **CodeEditor** 修复可能会造成的`Build`失败的问题
- **BasicForm** 修复自定义 FormItem 组件的内容宽度可能超出范围的问题
- **ApiTreeSelect** 修复`params`变化未能触发重新请求 api 数据的问题
- **其它**
- 修复多标签在某些情况下关闭页签不会跳转路由的问题
- 修复部分组件可能会造成热更新异常的问题
- 修复直接`import`部分`antdv`子组件时会在 build 过程中报错的问题TabPane、RadioGroup
## 2.7.2(2021-09-14)
### ✨ Features
- **BasicForm** 表单组件新增`Divider`,用于较长表单的区域分割
- **BasicTable**
- 单元格编辑新增提交回调,将根据回调函数返回的结果来决定是否将数据提交到表格
- 行编辑添加校验方法,允许只校验而不提交值,以便异步保存数据成功后才提交倒表格
- 修复`rowClassName`属性无法和`striped`同时使用的问题
- 新增组件 **MarkdownViewer** 用于显示 Markdown 格式的富文本
### 🐛 Bug Fixes
- **CodeEditor** 修复 JSON 编辑器在格式化无效 JSON 文本时会抛出异常的问题
- **Tinymce** 修复 inline 模式在一些场景下会出现异常的问题
- **BasicTable**
- 修复可编辑单元格的内容为空时,不会显示编辑图标的问题
- 修复表尾合计行与表格主体部分的列有时候未能对齐的问题
- **MarkDown** 修复初始 value 属性的值不起作用的问题
- **BasicUpload** 修复`accept`属性不支持`MIME`及点开头的后缀名的问题
- **ApiSelect** 修复`value`属性的类型定义问题
- **其它**
- 修复部分封装组件在使用插槽时报错的问题
- 修复`useECharts``theme`参数不起作用的问题
- 修复`Token`失效时,按 F5 刷新页面可能会出现页面加载异常的问题
- 修复`useRedo`的不当调用可能会导致重定向`path`异常的问题
- 修复`vite`自定义模式名称不支持下划线的问题
## 2.7.1(2021-08-16)
- 升级 vue 3.2,如果运行失败,删除 node_modules 后重装即可
### ✨ Features
- **BasicTree** 添加搜索功能相关属性和方法
- **BasicForm** 新增`alwaysShowLines`用于设置折叠时保留显示的行数
### 🐛 Bug Fixes
- **Cropper** 修复未能及时销毁的问题
- **BasicTable**
- 修复`CellFormat`无法使用`Map`类型数据的问题
- 修复可编辑单元格未能正确显示`0`值的问题
- 修复 selection-change 事件在取消勾选时未能正确触发的问题
- 修复浅色主题下的全屏状态背景颜色不正确的问题
- 修复`getSelectRows`不支持远程数据跨页选择时获取完整数据的问题
- 修复在`editComponentProps`中为编辑组件提供的`size`属性无效的问题
- **Qrcode** 修复二维码组件在创建时未能及时绘制的问题
- **BasicModal** 修复`helpMessage`属性不起作用的问题
- **BasicButton** 修复按钮样式表现与 antd 官方不一致的问题
- **其它** 修复`useRedo`(重新加载当前路由)会丢失路由`params`数据的问题
## 2.7.0(2021-08-03)
## (破坏性更新) Breaking changes
- 将项目`tailwindcss`还原回`windicss`,尝试了`tailwindcss`,问题可能还挺多,先切换回`windicss`提高开发效率,切换成本较低。
- 目前项目不兼容地方有
- `xl:!m-4` 之类的写法需要改为`!xl:m-4`,注意只有`!`这个不兼容,没用到则不用改
- 内存溢出问题可能还在(频率低,重启下即可,重启 vite 较快)
### ✨ Features
- **Preview** 添加新的属性及事件
- **Dark Theme** 新增对 tailwindcss 夜间模式的支持
- **其它** 为 useLoading 添加 setTip 方法
### 🐛 Bug Fixes
- **ApiTreeSelect** 修复未能正确监听`params`变化的问题
- **ImgRotateDragVerify** 修复组件`resume`方法无法调用的问题
- **TableAction** 修复 stopButtonPropagation 属性某些情况下不起作用的问题
- **PageWrapper** 修复`class`属性无效的问题
- **BasicTree** 修复`checkAll`方法会影响到`disabled`状态节点的问题
- **BasicTable**
- 修复可编辑单元格不支持`ellipsis`配置的问题
- 修复全屏模式下看不到子组件弹出层popconfirm 以及 select、treeSelect 等编辑组件)的问题
- 修复启用`expandRowByClick`时,点击不可展开的行可能会导致样式错误的问题
- 修复`pagination`属性动态改变不生效的问题
- 修复`getSelectRows`不支持树形表格子级数据的问题
- **Dark Theme** 黑暗主题下的配色问题修正
- 修复`Tree`组件被选中节点的背景颜色
- 修复`Alert`组件的颜色配置
- 修复禁用状态下的`link`类型的按钮颜色问题
- 修复`Tree`已勾选的复选框的样式问题
- **其它** 修复 useScript 未能自动移除 script 节点的问题
## 2.6.1(2021-07-19)
### ✨ Features
- **NoticeList** 添加分页、超长自动省略、标题点击事件、标题删除线等功能
- **MixSider** 优化 Mix 菜单布局时 底部折叠按钮 的样式,与其它菜单布局时的风格保持一致
- **ApiTreeSelect** 扩展`antdv``TreeSelect`组件,支持远程数据源,用法类似`ApiSelect`
- **BasicTable**
- 新增`ApiTreeSelect`编辑组件
- 新增`headerTop`插槽
- **其它** 可以为不同的用户指定不同的后台首页:
-`getUserInfo`接口返回的用户信息中增加`homePath`字段(可选)即可为当前用户定制首页路径
### 🐛 Bug Fixes
- **BasicTable**
- 修复滚动条样式问题(移除了滚动样式补丁)
- 修复树形表格的带有展开图标的单元格的内容对齐问题
- 修复操作列的按钮在 disabled 状态下的颜色显示
- 修复可编辑单元格的值不能直接通过修改`dataSource`来更新显示的问题
- 修复使用`ApiSelect`编辑组件时的数据回显问题
- 修复在部分场景下编辑组件可能会报`onXXX`类型错误的问题
- **TableAction**
- 仅在 `action.tooltip`存在的情况下 才创建 Tooltip 组件
- 修复组件内的圆形按钮内容没有居中的问题
- **AppSearch** 修复可能会搜索隐藏菜单的问题
- **BasicUpload** 修复处理非`array`值时报错的问题
- **Form** 修复`FormItem``suffix`插槽样式问题
- **Menu**
- 修复左侧混合菜单的悬停触发逻辑
- 修复顶栏菜单在显示包含需要隐藏的菜单项目时出错的问题
- 修复悬停触发模式下左侧混合菜单会在没有子菜单且被激活时直接跳转路由
- **Breadcrumb** 修复带有重定向的菜单点击无法跳转的问题
- **Markdown** 修复初始化异常以及不能正确地动态设置 value 的问题
- **Modal** 确保 props 正确被传递
- **MultipleTab** 修复可能会意外创建登录路由标签的问题
- **BasicTree** 修复搜索功能可能导致`checkedKeys`丢失的问题
- **CodeEditor** 修复 value 不支持 v-model 用法的问题
- **CountdownInput** 修复不支持`input`插槽的问题
- **ApiSelect** 修复`options-change`事件参数不是`select`所使用的标准`options`数据的问题
- **其它**
- 修复菜单默认折叠的配置不起作用的问题
- 修复`safari`浏览器报错导致网站打不开
- 修复在 window 上,拉取代码后 eslint 因 endOfLine 而报错问题
- 修复因动态路由而产生的 `Vue Router warn`
### 🎫 Chores
- 添加 test 环境测试命令
## 2.6.0(2021-07-04)
### ✨ Features
- **Axios** 新增`withToken`配置,用于控制请求是否携带 token
- **BasicUpload**
- 新增在预览 `Modal` 中删除文件时触发`preview-delete` 事件
- `value` 支持 `v-model` 用法
- **Route 配置**
- 增加`ignoreRoute`用于在`ROUTE_MAPPING``BACK`权限模式下仅生成菜单
- 增加`hidePathForChildren`配置,标识为子项目生成菜单时忽略本级`path`
- **TableAction** 新增`tooltip`配置,可以为按钮增加 tooltip 提示
- **CropperAvatar**
- 新增`value`用于设置当前头像
- 新增`onChange`用于接受头像剪裁并上传成功事件
- 新增`btnText``btnProps` 用于自定义上传按钮文案和属性
- 为剪裁`Modal`内的操作按钮添加工具提示
- **Modal** 为右上角的操作按钮添加工具提示
### 🐛 Bug Fixes
- **Modal**
- 修复点击遮罩不能关闭的问题
- 修复 `setModalProps` 不支持设置 `defaultFullscreen` 的问题
- **Table**
- 修复 `editComponentProps` 不支持 `onChange`的问题
- 修复启用`clickToRowSelect`时,点击行不会触发`selection-change`事件的问题
- 修复全局配置`fetchSetting`可能会被局部配置意外修改的问题
- 修复`handleSearchInfoFn`的参数包含多余空白键的问题
- 修复为 table 提供 rowSelection.onChange 时,无法手动变更 table 的选中项的问题
- 修复滚动条在无需显示的时候仍然持续显示的问题
- **Icon** 修复 SvgIcon 缺少部分样式的问题
- **Menu**
- 修复路由映射模式下,单级菜单刷新不会激活
- 修复侧边菜单底部的折叠自定义失效的问题
- **Form** 修复`submitButtonOptions``resetButtonOptions`的类型定义
- **PopConfirmButton** 移除`Button`上多余的`title`
- **Axios** 修复非`GET`请求时,无法同时提交`params``data`数据的问题
- **其它**
- 修复锁屏功能可以通过刷新页面或复制 URL 打开新的浏览器标签来跳过锁定状态的问题
- 修复多个窗口同时打开页面时,`Token` 不会同步的问题
- 修复`ROLE`权限模式下`hasPermission`不工作的问题
- **Table** 修复`handleSearchInfoFn`的参数包含多余空白键的问题
- **Tailwindcss** 移除控制台警告
## 2.5.2(2021-06-27)
### ⚡ Performance Improvements
- **Icon** 移除 Icon 组件全局注册,防止特定情况下热更新问题
### ✨ Features
- **Menu** 新增 `permissionMode=PermissionModeEnum.ROUTE_MAPPING`模式
- 项目默认改为该模式,删除原有菜单文件
- 如果之前已经写好了菜单,可以更改为`PermissionModeEnum.ROLE`模式即可
### 🐛 Bug Fixes
- **Drawer** 修复`visible`状态异常
## 2.5.1(2021-06-26)
### ⚡ Performance Improvements
- 升级`vue``ant-design-vue`版本,解决兼容问题
- **Tree** 性能优化
### 🐛 Bug Fixes
- **Table** 修复分页抖动问题
- **Upload** 确保携带自定义参数
- **Dropdown** 修复 popConfirm 的图标显示问题
- **Table** 修复树形表格的编辑事件不正常的问题
- **Table** 修复当表格数据为空时getDataSource 返回的值不是表格所使用的数据源的问题
## 2.5.0(2021-06-20)
## (破坏性更新) Breaking changes
- 将项目`windicss`改为`tailwindcss`,解决内存溢出问题
- 目前项目不兼容地方有
- `!xl:m-4` 之类的写法需要改为`xl:!m-4`,注意只有`!`这个不兼容,没用到则不用改
- `windicss`自身新增的特性需要调整,比如`Attribute`模式不兼容
### ✨ Refactor
- 移除`useExpose`,使用组件自身提供的`expose`代替
### ⚡ Performance Improvements
- **Locale** 合并多语言文件,减少文件数量
- **Utils** Mitt 默认导出由 `Class` 改为 `Function`
- **Axios** `isTransformRequestResult`更名为`isTransformResponse`
### ✨ Features
- **CropperImage** `Cropper` 头像裁剪新增圆形裁剪功能
- **CropperAvatar** 新增头像上传组件
- **Drawer** `useDrawer`新增`closeDrawer`函数
- **Preview** 新增`createImgPreview`图片预览函数
- **Setup** 新增引导页示例
- **Tests** 添加 jest 测试套件,暂不支持 Vue 组件单测
- **Axios** 新增`authenticationScheme`配置,用于指定认证方案
- **Setting** 新增 `sessionTimeoutProcessing` 项目配置项,用于配置会话超时如何处理
### 🐛 Bug Fixes
- **Modal** 修复全屏高度计算错误
- **Modal** 修复关闭事件触发多次问题
- **PageWrapper** 修复高度计算问题
- **FlowChart** 修复拖放菜单丢失
- 修复后台模式下Iframe 路由错误
- **PageWrapper** 修复 footer 与全局页脚同时开启时的高度计算问题
- **Menu** 修复菜单折叠动画抖动问题
- **Store**修复 pinia 版本升级之后类型错误
## 2.4.2(2021-06-10)
### ✨ Refactor
- `CountTo`组件重构
### ✨ Features
- `radioButtonGroup` 支持`boolean`
- `useModalInner` 新增 `redoModalHeight`用于在 Modal 内部重设`Modal`高度
- `useECharts` 新增`getInstance`用于获取`echart`实例
- `TableAction` 新增 `stopButtonPropagation` 阻止操作按钮点击事件冒泡
- `BasicTable` 在行编辑模式下,可以获取或设置其它处于列的编辑组件的值
- `ApiSelect` 组件在`params`改变后会自动重新`fetch`数据
- `TableImg` 组件改进
- `BasicTable` 新增 `columns-change` 事件用于监听用户改变列排序、展示、固定状态
- `Tinymce`支持动态修改 readonly
- `BasicTable`新增`updateTableDataRecord`方法用于更新指定行数据
- `useModal`新增`closeModal`方法用于关闭`Modal`
### 🐛 Bug Fixes
- 修复`redoModalHeight`不能减小高度的问题
- 修复 `BasicForm`设置 schemas 数据不生效的问题
- 修复多标签可能导致`KeepAlive`失效的问题
- 修复默认的`axios`拦截器不能处理自定义 code 的问题
- 修复锁屏弹窗的高度问题
- 修复`BaiscTable``列展示`复选框的半选状态显示不正确的问题
- 修复`BasicUpload`组件的预览列表某些情况下不能显示的问题
- 修复`RadioButtonGroup``options`设置`disabled`不生效的问题
- 修复`Tinymce`组件在只读模式下上传图片的按钮仍然可用的问题
- 修复`BasicForm`特定情况下的卡顿问题
- 修复"目录"路由不起作用的问题
## 2.4.1(2021-06-01)
### ✨ Features
- 可编辑表格新增`DatePicker``TimePicker`组件
- `Tree` 组件新增`defaultExpandLevel`配置
### ⚡ Performance Improvements
- 菜单搜索默认聚焦
### 🐛 Bug Fixes
- 修复`CodeEditor`已知问题
- 修复`i18n`控制台警告问题
- 修复可编辑表格`align`配置不生效问题
- 确保`axios`只对`Object`参数进行处理
- 修复`Tree`组件 `defaultExpandAll` 配置失效
- 修复`TableAction` 分割线丢失问题
- 修复表格已知问题
- 修复首次加载或改变语言导致重载时,不会设置 HTML 的 lang 属性
## 2.4.0 (2021-05-25)
### ✨ Features
- 新增图形编辑器示例
- 新增代码编辑器(包含 Json 编辑器)
- 新增 `JsonPreview`Json 数据查看组件
- 表格的数据列(column)和操作列(actionColumn)的字段可以根据权限和业务来控制是否显示
- 新增权限控制表格示例(AuthColumn.vue)
- 新增用户登录过期示例
### ⚡ Performance Improvements
- 合并部分语言文件,减少文件数量
### 🐛 Bug Fixes
- 修复黑暗主题刷新闪烁的白屏
- 修复标签页关闭其他功能失效问题
- 修复表单已知问题
- 修复自动锁屏失效
## 2.3.0 (2021-04-10)
## (破坏性更新) Breaking changes

View File

@@ -150,7 +150,6 @@ yarn build
## 后台整合示例
- [lamp-cloud](https://github.com/zuihou/lamp-cloud) - 基于 SpringCloud Alibaba 的微服务中后台快速开发平台
- [matecloud](https://github.com/matevip/matecloud) - MateCloud 微服务脚手架,基于 Spring Cloud 2020.0.3、SpringBoot 2.5.3 的全开源平台
## 维护者

View File

@@ -6,8 +6,6 @@ export const darkMode = 'light';
type Fn = (...arg: any) => any;
type GenerateTheme = 'default' | 'dark';
export interface GenerateColorsParams {
mixLighten: Fn;
mixDarken: Fn;
@@ -15,19 +13,19 @@ export interface GenerateColorsParams {
color?: string;
}
export function generateAntColors(color: string, theme: GenerateTheme = 'default') {
export function generateAntColors(color: string) {
return generate(color, {
theme,
theme: 'default',
});
}
export function getThemeColors(color?: string) {
const tc = color || primaryColor;
const lightColors = generateAntColors(tc);
const primary = lightColors[5];
const modeColors = generateAntColors(primary, 'dark');
const colors = generateAntColors(tc);
const primary = colors[5];
const modeColors = generateAntColors(primary);
return [...lightColors, ...modeColors];
return [...colors, ...modeColors];
}
export function generateColors({

View File

@@ -28,10 +28,9 @@ export function generateModifyVars(dark = false) {
'success-color': '#55D187', // Success color
'error-color': '#ED6F6F', // False color
'warning-color': '#EFBD47', // Warning color
//'border-color-base': '#EEEEEE',
'border-color-base': '#EEEEEE',
'font-size-base': '14px', // Main font size
'border-radius-base': '2px', // Component/float fillet
'link-color': primary, // Link color
'app-content-background': '#fafafa', // Link color
};
}

View File

@@ -52,19 +52,19 @@ async function generateIcon() {
const { prefix } = data;
const isLocal = useType === 'local';
const icons = Object.keys(data.icons).map(
(item) => `${isLocal ? prefix + ':' : ''}${item}`,
(item) => `${isLocal ? prefix + ':' : ''}${item}`
);
await fs.writeFileSync(
path.join(output, `icons.data.ts`),
`export default ${isLocal ? JSON.stringify(icons) : JSON.stringify({ prefix, icons })}`,
`export default ${isLocal ? JSON.stringify(icons) : JSON.stringify({ prefix, icons })}`
);
prefixSet.push(prefix);
}
}
fs.emptyDir(path.join(process.cwd(), 'node_modules/.vite'));
console.log(
`${chalk.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`,
`${chalk.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`
);
});
}

View File

@@ -5,19 +5,18 @@ import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant';
import fs, { writeFileSync } from 'fs-extra';
import chalk from 'chalk';
import { getEnvConfig, getRootPath } from '../utils';
import { getRootPath, getEnvConfig } from '../utils';
import { getConfigFileName } from '../getConfigFileName';
import pkg from '../../package.json';
interface CreateConfigParams {
configName: string;
config: any;
configFileName?: string;
}
function createConfig(params: CreateConfigParams) {
const { configName, config, configFileName } = params;
function createConfig(
{
configName,
config,
configFileName = GLOB_CONFIG_FILE_NAME,
}: { configName: string; config: any; configFileName?: string } = { configName: '', config: {} }
) {
try {
const windowConf = `window.${configName}`;
// Ensure that the variable will not be modified
@@ -41,5 +40,5 @@ function createConfig(params: CreateConfigParams) {
export function runBuildConfig() {
const config = getEnvConfig();
const configFileName = getConfigFileName(config);
createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME });
createConfig({ config, configName: configFileName });
}

View File

@@ -11,7 +11,7 @@ export const runBuild = async () => {
// Generate configuration file
if (!argvList.includes('disabled-config')) {
runBuildConfig();
await runBuildConfig();
}
console.log(`${chalk.cyan(`[${pkg.name}]`)}` + ' - build successfully!');

View File

@@ -28,54 +28,33 @@ export function wrapperEnv(envConf: Recordable): ViteEnv {
if (envName === 'VITE_PORT') {
realName = Number(realName);
}
if (envName === 'VITE_PROXY' && realName) {
if (envName === 'VITE_PROXY') {
try {
realName = JSON.parse(realName.replace(/'/g, '"'));
} catch (error) {
realName = '';
}
realName = JSON.parse(realName);
} catch (error) {}
}
ret[envName] = realName;
if (typeof realName === 'string') {
process.env[envName] = realName;
} else if (typeof realName === 'object') {
process.env[envName] = JSON.stringify(realName);
}
process.env[envName] = realName;
}
return ret;
}
/**
* 获取当前环境下生效的配置文件名
*/
function getConfFiles() {
const script = process.env.npm_lifecycle_script;
const reg = new RegExp('--mode ([a-z_\\d]+)');
const result = reg.exec(script as string) as any;
if (result) {
const mode = result[1] as string;
return ['.env', `.env.${mode}`];
}
return ['.env', '.env.production'];
}
/**
* Get the environment variables starting with the specified prefix
* @param match prefix
* @param confFiles ext
*/
export function getEnvConfig(match = 'VITE_GLOB_', confFiles = getConfFiles()) {
export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.production']) {
let envConfig = {};
confFiles.forEach((item) => {
try {
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
envConfig = { ...envConfig, ...env };
} catch (e) {
console.error(`Error in parsing ${item}`, e);
}
} catch (error) {}
});
const reg = new RegExp(`^(${match})`);
Object.keys(envConfig).forEach((key) => {
const reg = new RegExp(`^(${match})`);
if (!reg.test(key)) {
Reflect.deleteProperty(envConfig, key);
}

View File

@@ -3,11 +3,12 @@
* https://github.com/anncwb/vite-plugin-compression
*/
import type { Plugin } from 'vite';
import compressPlugin from 'vite-plugin-compression';
export function configCompressPlugin(
compress: 'gzip' | 'brotli' | 'none',
deleteOriginFile = false,
deleteOriginFile: boolean = false
): Plugin | Plugin[] {
const compressList = compress.split(',');
@@ -18,17 +19,16 @@ export function configCompressPlugin(
compressPlugin({
ext: '.gz',
deleteOriginFile,
}),
})
);
}
if (compressList.includes('brotli')) {
plugins.push(
compressPlugin({
ext: '.br',
algorithm: 'brotliCompress',
deleteOriginFile,
}),
})
);
}
return plugins;

View File

@@ -11,14 +11,10 @@ export function configHmrPlugin(): Plugin {
name: 'singleHMR',
handleHotUpdate({ modules, file }) {
if (file.match(/xml$/)) return [];
modules.forEach((m) => {
if (!m.url.match(/\.(css|less)/)) {
m.importedModules = new Set();
m.importers = new Set();
}
m.importedModules = new Set();
m.importers = new Set();
});
return modules;
},
};

View File

@@ -3,7 +3,9 @@
* https://github.com/anncwb/vite-plugin-html
*/
import type { Plugin } from 'vite';
import html from 'vite-plugin-html';
import pkg from '../../../package.json';
import { GLOB_CONFIG_FILE_NAME } from '../../constant';
@@ -20,7 +22,7 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
minify: isBuild,
inject: {
// Inject data into ejs template
data: {
injectData: {
title: VITE_GLOB_APP_TITLE,
},
// Embed the generated app.config.js file

View File

@@ -1,5 +1,6 @@
// Image resource files used to compress the output of the production environment
// https://github.com/anncwb/vite-plugin-imagemin
import viteImagemin from 'vite-plugin-imagemin';
export function configImageminPlugin() {
@@ -12,7 +13,7 @@ export function configImageminPlugin() {
optimizationLevel: 7,
},
mozjpeg: {
quality: 20,
quality: 8,
},
pngquant: {
quality: [0.8, 0.9],
@@ -21,11 +22,10 @@ export function configImageminPlugin() {
svgo: {
plugins: [
{
name: 'removeViewBox',
removeViewBox: false,
},
{
name: 'removeEmptyAttrs',
active: false,
removeEmptyAttrs: false,
},
],
},

View File

@@ -1,10 +1,11 @@
import type { Plugin } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import legacy from '@vitejs/plugin-legacy';
import purgeIcons from 'vite-plugin-purge-icons';
import windiCSS from 'vite-plugin-windicss';
import vueSetupExtend from 'vite-plugin-vue-setup-extend';
import PurgeIcons from 'vite-plugin-purge-icons';
import { configHtmlPlugin } from './html';
import { configPwaConfig } from './pwa';
import { configMockPlugin } from './mock';
@@ -13,6 +14,7 @@ import { configStyleImportPlugin } from './styleImport';
import { configVisualizerConfig } from './visualizer';
import { configThemePlugin } from './theme';
import { configImageminPlugin } from './imagemin';
import { configWindiCssPlugin } from './windicss';
import { configSvgIconsPlugin } from './svgSprite';
import { configHmrPlugin } from './hmr';
@@ -30,13 +32,8 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
vue(),
// have to
vueJsx(),
// support name
vueSetupExtend(),
];
// vite-plugin-windicss
vitePlugins.push(windiCSS());
// TODO
!isBuild && vitePlugins.push(configHmrPlugin());
@@ -49,11 +46,14 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
// vite-plugin-svg-icons
vitePlugins.push(configSvgIconsPlugin(isBuild));
// vite-plugin-windicss
vitePlugins.push(configWindiCssPlugin());
// vite-plugin-mock
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild));
// vite-plugin-purge-icons
vitePlugins.push(purgeIcons());
vitePlugins.push(PurgeIcons());
// vite-plugin-style-import
vitePlugins.push(configStyleImportPlugin(isBuild));
@@ -71,7 +71,7 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
// rollup-plugin-gzip
vitePlugins.push(
configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE),
configCompressPlugin(VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE)
);
// vite-plugin-pwa

View File

@@ -2,6 +2,7 @@
* Zero-config PWA for Vite
* https://github.com/antfu/vite-plugin-pwa
*/
import { VitePWA } from 'vite-plugin-pwa';
export function configPwaConfig(env: ViteEnv) {

View File

@@ -2,77 +2,21 @@
* Introduces component library styles on demand.
* https://github.com/anncwb/vite-plugin-style-import
*/
import styleImport from 'vite-plugin-style-import';
export function configStyleImportPlugin(isBuild: boolean) {
if (!isBuild) {
return [];
}
const styleImportPlugin = styleImport({
if (!isBuild) return [];
const pwaPlugin = styleImport({
libs: [
{
libraryName: 'ant-design-vue',
esModule: true,
resolveStyle: (name) => {
// 这里是无需额外引入样式文件的“子组件”列表
const ignoreList = [
'anchor-link',
'sub-menu',
'menu-item',
'menu-item-group',
'breadcrumb-item',
'breadcrumb-separator',
'form-item',
'step',
'select-option',
'select-opt-group',
'card-grid',
'card-meta',
'collapse-panel',
'descriptions-item',
'list-item',
'list-item-meta',
'table-column',
'table-column-group',
'tab-pane',
'tab-content',
'timeline-item',
'tree-node',
'skeleton-input',
'skeleton-avatar',
'skeleton-title',
'skeleton-paragraph',
'skeleton-image',
'skeleton-button',
];
// 这里是需要额外引入样式的子组件列表
// 单独引入子组件时需引入组件样式,否则会在打包后导致子组件样式丢失
const replaceList = {
'typography-text': 'typography',
'typography-title': 'typography',
'typography-paragraph': 'typography',
'typography-link': 'typography',
'dropdown-button': 'dropdown',
'input-password': 'input',
'input-search': 'input',
'input-group': 'input',
'radio-group': 'radio',
'checkbox-group': 'checkbox',
'layout-sider': 'layout',
'layout-content': 'layout',
'layout-footer': 'layout',
'layout-header': 'layout',
'month-picker': 'date-picker',
};
return ignoreList.includes(name)
? ''
: replaceList.hasOwnProperty(name)
? `ant-design-vue/es/${replaceList[name]}/style/index`
: `ant-design-vue/es/${name}/style/index`;
return `ant-design-vue/es/${name}/style/index`;
},
},
],
});
return styleImportPlugin;
return pwaPlugin;
}

View File

@@ -27,28 +27,17 @@ export function configThemePlugin(isBuild: boolean): Plugin[] {
switch (s) {
case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
return '.ant-steps-item-icon > .ant-steps-icon';
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active':
return s;
case '.ant-steps-item-icon > .ant-steps-icon':
return s;
case '.ant-select-item-option-selected:not(.ant-select-item-option-disabled)':
return s;
default:
if (s.indexOf('.ant-btn') >= -1) {
// 按钮被重新定制过需要过滤掉class防止覆盖
return s;
}
}
return s.startsWith('[data-theme') ? s : `[data-theme] ${s}`;
return `[data-theme] ${s}`;
},
colorVariables: [...getThemeColors(), ...colors],
}),
antdDarkThemePlugin({
preloadFiles: [
path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/antd.less'),
//path.resolve(process.cwd(), 'node_modules/ant-design-vue/dist/antd.dark.less'),
path.resolve(process.cwd(), 'src/design/index.less'),
],
filter: (id) => (isBuild ? !id.endsWith('antd.less') : true),
@@ -56,34 +45,17 @@ export function configThemePlugin(isBuild: boolean): Plugin[] {
darkModifyVars: {
...generateModifyVars(true),
'text-color': '#c9d1d9',
'primary-1': 'rgb(255 255 255 / 8%)',
'text-color-base': '#c9d1d9',
'component-background': '#151515',
'heading-color': 'rgb(255 255 255 / 65%)',
// black: '#0e1117',
// #8b949e
'text-color-secondary': '#8b949e',
'border-color-base': '#303030',
// 'border-color-split': '#30363d',
'item-active-bg': '#111b26',
'app-content-background': '#1e1e1e',
'tree-node-selected-bg': '#11263c',
'alert-success-border-color': '#274916',
'alert-success-bg-color': '#162312',
'alert-success-icon-color': '#49aa19',
'alert-info-border-color': '#153450',
'alert-info-bg-color': '#111b26',
'alert-info-icon-color': '#177ddc',
'alert-warning-border-color': '#594214',
'alert-warning-bg-color': '#2b2111',
'alert-warning-icon-color': '#d89614',
'alert-error-border-color': '#58181c',
'alert-error-bg-color': '#2a1215',
'alert-error-icon-color': '#a61d24',
},
}),
];
return plugin as unknown as Plugin[];
return (plugin as unknown) as Plugin[];
}

View File

@@ -0,0 +1,12 @@
import type { Plugin } from 'vite';
import windiCSS from 'vite-plugin-windicss';
export function configWindiCssPlugin(): Plugin[] {
return windiCSS({
safelist: 'no-select',
preflight: {
enableAll: true,
},
});
}

View File

@@ -7,7 +7,7 @@ type ProxyItem = [string, string];
type ProxyList = ProxyItem[];
type ProxyTargetList = Record<string, ProxyOptions>;
type ProxyTargetList = Record<string, ProxyOptions & { rewrite: (path: string) => string }>;
const httpsRE = /^https:\/\//;

View File

@@ -1,13 +1,37 @@
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
parserPreset: {
parserOpts: {
headerPattern: /^(\w*|[\u4e00-\u9fa5]*)(?:[\(\](.*)[\)\])?[\:\] (.*)/,
headerCorrespondence: ['type', 'scope', 'subject'],
referenceActions: [
'close',
'closes',
'closed',
'fix',
'fixes',
'fixed',
'resolve',
'resolves',
'resolved',
],
issuePrefixes: ['#'],
noteKeywords: ['BREAKING CHANGE'],
fieldPattern: /^-(.*?)-$/,
revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (\w*)\./,
revertCorrespondence: ['header', 'hash'],
warn() {},
mergePattern: null,
mergeCorrespondence: null,
},
},
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'subject-case': [0],
'type-enum': [
2,
'always',

View File

@@ -26,7 +26,7 @@
<div id="app">
<style>
html[data-theme='dark'] .app-loading {
background-color: #2c344a;
background: #2c344a;
}
html[data-theme='dark'] .app-loading .app-loading-title {
@@ -40,7 +40,7 @@
justify-content: center;
align-items: center;
flex-direction: column;
background-color: #f4f7f9;
background: #f4f7f9;
}
.app-loading .app-loading-wrap {

View File

@@ -1,36 +0,0 @@
export default {
preset: 'ts-jest',
roots: ['<rootDir>/tests/'],
clearMocks: true,
moduleDirectories: ['node_modules', 'src'],
moduleFileExtensions: ['js', 'ts', 'vue', 'tsx', 'jsx', 'json', 'node'],
modulePaths: ['<rootDir>/src', '<rootDir>/node_modules'],
testMatch: [
'**/tests/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[tj]s?(x)',
'(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$',
],
testPathIgnorePatterns: [
'<rootDir>/tests/server/',
'<rootDir>/tests/__mocks__/',
'/node_modules/',
],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
transformIgnorePatterns: ['<rootDir>/tests/__mocks__/', '/node_modules/'],
// A map from regular expressions to module names that allow to stub out resources with a single module
moduleNameMapper: {
'\\.(vs|fs|vert|frag|glsl|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/tests/__mocks__/fileMock.ts',
'\\.(sass|s?css|less)$': '<rootDir>/tests/__mocks__/styleMock.ts',
'\\?worker$': '<rootDir>/tests/__mocks__/workerMock.ts',
'^/@/(.*)$': '<rootDir>/src/$1',
},
testEnvironment: 'jsdom',
verbose: true,
collectCoverage: false,
coverageDirectory: 'coverage',
collectCoverageFrom: ['src/**/*.{js,ts,vue}'],
coveragePathIgnorePatterns: ['^.+\\.d\\.ts$'],
};

View File

@@ -13,7 +13,7 @@ export function resultPageSuccess<T = any>(
page: number,
pageSize: number,
list: T[],
{ message = 'ok' } = {},
{ message = 'ok' } = {}
) {
const pageData = pagination(page, pageSize, list);
@@ -43,18 +43,3 @@ export function pagination<T = any>(pageNo: number, pageSize: number, array: T[]
: array.slice(offset, offset + Number(pageSize));
return ret;
}
export interface requestParams {
method: string;
body: any;
headers?: { authorization?: string };
query: any;
}
/**
* @description 本函数用于从request数据中获取token请根据项目的实际情况修改
*
*/
export function getRequestToken({ headers }: requestParams): string | undefined {
return headers?.authorization;
}

View File

@@ -1,6 +1,5 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess, resultError } from '../_util';
import { ResultEnum } from '../../src/enums/httpEnum';
import { resultSuccess } from '../_util';
const userInfo = {
name: 'Vben',
@@ -52,20 +51,4 @@ export default [
return resultSuccess(userInfo);
},
},
{
url: '/basic-api/user/sessionTimeout',
method: 'post',
statusCode: 401,
response: () => {
return resultError();
},
},
{
url: '/basic-api/user/tokenExpired',
method: 'post',
statusCode: 200,
response: () => {
return resultError('Token Expired!', { code: ResultEnum.TIMEOUT as number });
},
},
] as MockMethod[];

View File

@@ -1,325 +0,0 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess } from '../_util';
const areaList: any[] = [
{
id: '530825900854620160',
code: '430000',
parentCode: '100000',
levelType: 1,
name: '湖南省',
province: '湖南省',
city: null,
district: null,
town: null,
village: null,
parentPath: '430000',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 16:33:42',
customized: false,
usable: true,
},
{
id: '530825900883980288',
code: '430100',
parentCode: '430000',
levelType: 2,
name: '长沙市',
province: '湖南省',
city: '长沙市',
district: null,
town: null,
village: null,
parentPath: '430000,430100',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 16:33:42',
customized: false,
usable: true,
},
{
id: '530825900951089152',
code: '430102',
parentCode: '430100',
levelType: 3,
name: '芙蓉区',
province: '湖南省',
city: '长沙市',
district: '芙蓉区',
town: null,
village: null,
parentPath: '430000,430100,430102',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 16:33:42',
customized: false,
usable: true,
},
{
id: '530825901014003712',
code: '430104',
parentCode: '430100',
levelType: 3,
name: '岳麓区',
province: '湖南省',
city: '长沙市',
district: '岳麓区',
town: null,
village: null,
parentPath: '430000,430100,430104',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 16:33:42',
customized: false,
usable: true,
},
{
id: '530825900988837888',
code: '430103',
parentCode: '430100',
levelType: 3,
name: '天心区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: null,
village: null,
parentPath: '430000,430100,430103',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 16:33:42',
customized: false,
usable: true,
},
{
id: '530826672489115648',
code: '430103002',
parentCode: '430103',
levelType: 4,
name: '坡子街街道',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: null,
parentPath: '430000,430100,430103,430103002',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-12-14 15:26:43',
customized: false,
usable: true,
},
{
id: '530840241171607552',
code: '430103002001',
parentCode: '430103002',
levelType: 5,
name: '八角亭社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '八角亭社区',
parentPath: '430000,430100,430103,430103002,430103002001',
createTime: '2020-11-30 15:47:31',
updateTime: '2021-01-20 14:07:23',
customized: false,
usable: true,
},
{
id: '530840241200967680',
code: '430103002002',
parentCode: '430103002',
levelType: 5,
name: '西牌楼社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '西牌楼社区',
parentPath: '430000,430100,430103,430103002,430103002002',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241230327808',
code: '430103002003',
parentCode: '430103002',
levelType: 5,
name: '太平街社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '太平街社区',
parentPath: '430000,430100,430103,430103002,430103002003',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241259687936',
code: '430103002005',
parentCode: '430103002',
levelType: 5,
name: '坡子街社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '坡子街社区',
parentPath: '430000,430100,430103,430103002,430103002005',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241284853760',
code: '430103002006',
parentCode: '430103002',
levelType: 5,
name: '青山祠社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '青山祠社区',
parentPath: '430000,430100,430103,430103002,430103002006',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241310019584',
code: '430103002007',
parentCode: '430103002',
levelType: 5,
name: '沙河社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '沙河社区',
parentPath: '430000,430100,430103,430103002,430103002007',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241381322752',
code: '430103002008',
parentCode: '430103002',
levelType: 5,
name: '碧湘社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '碧湘社区',
parentPath: '430000,430100,430103,430103002,430103002008',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241410682880',
code: '430103002009',
parentCode: '430103002',
levelType: 5,
name: '创远社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '创远社区',
parentPath: '430000,430100,430103,430103002,430103002009',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241431654400',
code: '430103002010',
parentCode: '430103002',
levelType: 5,
name: '楚湘社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '楚湘社区',
parentPath: '430000,430100,430103,430103002,430103002010',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241465208832',
code: '430103002011',
parentCode: '430103002',
levelType: 5,
name: '西湖社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '西湖社区',
parentPath: '430000,430100,430103,430103002,430103002011',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241502957568',
code: '430103002012',
parentCode: '430103002',
levelType: 5,
name: '登仁桥社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '登仁桥社区',
parentPath: '430000,430100,430103,430103002,430103002012',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
{
id: '530840241553289216',
code: '430103002013',
parentCode: '430103002',
levelType: 5,
name: '文庙坪社区',
province: '湖南省',
city: '长沙市',
district: '天心区',
town: '坡子街街道',
village: '文庙坪社区',
parentPath: '430000,430100,430103,430103002,430103002013',
createTime: '2020-11-30 15:47:31',
updateTime: '2020-11-30 17:30:41',
customized: false,
usable: true,
},
];
export default [
{
url: '/basic-api/cascader/getAreaRecord',
timeout: 1000,
method: 'post',
response: ({ body }) => {
const { parentCode } = body || {};
if (!parentCode) {
return resultSuccess(areaList.filter((it) => it.code === '430000'));
}
return resultSuccess(areaList.filter((it) => it.parentCode === parentCode));
},
},
] as MockMethod[];

View File

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

View File

@@ -1,5 +1,5 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultError, resultPageSuccess, resultSuccess } from '../_util';
import { resultPageSuccess, resultSuccess } from '../_util';
const accountList = (() => {
const result: any[] = [];
@@ -22,13 +22,12 @@ const roleList = (() => {
const result: any[] = [];
for (let index = 0; index < 4; index++) {
result.push({
id: index + 1,
id: `${index}`,
orderNo: `${index + 1}`,
roleName: ['超级管理员', '管理员', '文章管理员', '普通用户'][index],
roleValue: '@first',
createTime: '@datetime',
remark: '@cword(10,20)',
menu: [['0', '1', '2'], ['0', '1'], ['0', '2'], ['2']][index],
'status|1': ['0', '1'],
});
}
@@ -73,7 +72,6 @@ const menuList = (() => {
id: `${index}`,
icon: ['ion:layers-outline', 'ion:git-compare-outline', 'ion:tv-outline'][index],
component: 'LAYOUT',
type: '0',
menuName: ['Dashboard', '权限管理', '功能'][index],
permission: '',
orderNo: index + 1,
@@ -84,7 +82,6 @@ const menuList = (() => {
for (let j = 0; j < 4; j++) {
children.push({
id: `${index}-${j}`,
type: '1',
menuName: ['菜单1', '菜单2', '菜单3', '菜单4'][j],
icon: 'ion:document',
permission: ['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index],
@@ -98,33 +95,7 @@ const menuList = (() => {
createTime: '@datetime',
'status|1': ['0', '1'],
parentMenu: `${index}`,
children: (() => {
const children: any[] = [];
for (let k = 0; k < 4; k++) {
children.push({
id: `${index}-${j}-${k}`,
type: '2',
menuName: '按钮' + (j + 1) + '-' + (k + 1),
icon: '',
permission:
['menu1:view', 'menu2:add', 'menu3:update', 'menu4:del'][index] +
':btn' +
(k + 1),
component: [
'/dashboard/welcome/index',
'/dashboard/analysis/index',
'/dashboard/workbench/index',
'/dashboard/test/index',
][j],
orderNo: j + 1,
createTime: '@datetime',
'status|1': ['0', '1'],
parentMenu: `${index}-${j}`,
children: undefined,
});
}
return children;
})(),
children: undefined,
});
}
return children;
@@ -153,15 +124,6 @@ export default [
return resultPageSuccess(page, pageSize, roleList);
},
},
{
url: '/basic-api/system/setRoleStatus',
timeout: 500,
method: 'post',
response: ({ query }) => {
const { id, status } = query;
return resultSuccess({ id, status });
},
},
{
url: '/basic-api/system/getAllRoleList',
timeout: 100,
@@ -186,17 +148,4 @@ export default [
return resultSuccess(menuList);
},
},
{
url: '/basic-api/system/accountExist',
timeout: 500,
method: 'post',
response: ({ body }) => {
const { account } = body || {};
if (account && account.indexOf('admin') !== -1) {
return resultError('该字段不能包含admin');
} else {
return resultSuccess(`${account} can use`);
}
},
},
] as MockMethod[];

View File

@@ -1,18 +1,9 @@
import { MockMethod } from 'vite-plugin-mock';
import { Random } from 'mockjs';
import { resultPageSuccess } from '../_util';
function getRandomPics(count = 10): string[] {
const arr: string[] = [];
for (let i = 0; i < count; i++) {
arr.push(Random.image('800x600', Random.color(), Random.color(), Random.title()));
}
return arr;
}
const demoList = (() => {
const result: any[] = [];
for (let index = 0; index < 200; index++) {
for (let index = 0; index < 60; index++) {
result.push({
id: `${index}`,
beginTime: '@datetime',
@@ -27,11 +18,6 @@ const demoList = (() => {
name6: '@cname()',
name7: '@cname()',
name8: '@cname()',
avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()),
imgArr: getRandomPics(Math.ceil(Math.random() * 3) + 1),
imgs: getRandomPics(Math.ceil(Math.random() * 3) + 1),
date: `@date('yyyy-MM-dd')`,
time: `@time('HH:mm')`,
'no|100000-10000000': 100000,
'status|1': ['normal', 'enable', 'disable'],
});

View File

@@ -1,38 +0,0 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultSuccess } from '../_util';
const demoTreeList = (keyword) => {
const result = {
list: [] as Recordable[],
};
for (let index = 0; index < 5; index++) {
const children: Recordable[] = [];
for (let j = 0; j < 3; j++) {
children.push({
title: `${keyword ?? ''}选项${index}-${j}`,
value: `${index}-${j}`,
key: `${index}-${j}`,
});
}
result.list.push({
title: `${keyword ?? ''}选项${index}`,
value: `${index}`,
key: `${index}`,
children,
});
}
return result;
};
export default [
{
url: '/basic-api/tree/getDemoOptions',
timeout: 1000,
method: 'get',
response: ({ query }) => {
const { keyword } = query;
console.log(keyword);
return resultSuccess(demoTreeList(keyword));
},
},
] as MockMethod[];

View File

@@ -1,46 +1,59 @@
import { resultSuccess, resultError, getRequestToken, requestParams } from '../_util';
import { resultSuccess } from '../_util';
import { MockMethod } from 'vite-plugin-mock';
import { createFakeUserList } from './user';
// single
const dashboardRoute = {
path: '/dashboard',
name: 'Dashboard',
component: 'LAYOUT',
redirect: '/dashboard/analysis',
name: 'Welcome',
component: '/dashboard/analysis/index',
meta: {
title: 'routes.dashboard.dashboard',
hideChildrenInMenu: true,
title: 'routes.dashboard.analysis',
affix: true,
icon: 'bx:bx-home',
},
};
const frontRoute = {
path: 'front',
name: 'PermissionFrontDemo',
meta: {
title: 'routes.demo.permission.front',
},
children: [
{
path: 'analysis',
name: 'Analysis',
component: '/dashboard/analysis/index',
path: 'page',
name: 'FrontPageAuth',
component: '/demo/permission/front/index',
meta: {
hideMenu: true,
hideBreadcrumb: true,
title: 'routes.dashboard.analysis',
currentActiveMenu: '/dashboard',
icon: 'bx:bx-home',
title: 'routes.demo.permission.frontPage',
},
},
{
path: 'workbench',
name: 'Workbench',
component: '/dashboard/workbench/index',
path: 'btn',
name: 'FrontBtnAuth',
component: '/demo/permission/front/Btn',
meta: {
hideMenu: true,
hideBreadcrumb: true,
title: 'routes.dashboard.workbench',
currentActiveMenu: '/dashboard',
icon: 'bx:bx-home',
title: 'routes.demo.permission.frontBtn',
},
},
{
path: 'auth-pageA',
name: 'FrontAuthPageA',
component: '/demo/permission/front/AuthPageA',
meta: {
title: 'routes.demo.permission.frontTestA',
},
},
{
path: 'auth-pageB',
name: 'FrontAuthPageB',
component: '/demo/permission/front/AuthPageB',
meta: {
title: 'routes.demo.permission.frontTestB',
},
},
],
};
const backRoute = {
path: 'back',
name: 'PermissionBackDemo',
@@ -67,8 +80,19 @@ const backRoute = {
},
],
};
const authRoute = {
path: '/permission',
name: 'Permission',
component: 'LAYOUT',
redirect: '/permission/front/page',
meta: {
icon: 'carbon:user-role',
title: 'routes.demo.permission.permission',
},
children: [frontRoute, backRoute],
};
const authRoute1 = {
path: '/permission',
name: 'Permission',
component: 'LAYOUT',
@@ -135,136 +159,19 @@ const levelRoute = {
},
],
};
const sysRoute = {
path: '/system',
name: 'System',
component: 'LAYOUT',
redirect: '/system/account',
meta: {
icon: 'ion:settings-outline',
title: 'routes.demo.system.moduleName',
},
children: [
{
path: 'account',
name: 'AccountManagement',
meta: {
title: 'routes.demo.system.account',
ignoreKeepAlive: true,
},
component: '/demo/system/account/index',
},
{
path: 'account_detail/:id',
name: 'AccountDetail',
meta: {
hideMenu: true,
title: 'routes.demo.system.account_detail',
ignoreKeepAlive: true,
showMenu: false,
currentActiveMenu: '/system/account',
},
component: '/demo/system/account/AccountDetail',
},
{
path: 'role',
name: 'RoleManagement',
meta: {
title: 'routes.demo.system.role',
ignoreKeepAlive: true,
},
component: '/demo/system/role/index',
},
{
path: 'menu',
name: 'MenuManagement',
meta: {
title: 'routes.demo.system.menu',
ignoreKeepAlive: true,
},
component: '/demo/system/menu/index',
},
{
path: 'dept',
name: 'DeptManagement',
meta: {
title: 'routes.demo.system.dept',
ignoreKeepAlive: true,
},
component: '/demo/system/dept/index',
},
{
path: 'changePassword',
name: 'ChangePassword',
meta: {
title: 'routes.demo.system.password',
ignoreKeepAlive: true,
},
component: '/demo/system/password/index',
},
],
};
const linkRoute = {
path: '/link',
name: 'Link',
component: 'LAYOUT',
meta: {
icon: 'ion:tv-outline',
title: 'routes.demo.iframe.frame',
},
children: [
{
path: 'doc',
name: 'Doc',
meta: {
title: 'routes.demo.iframe.doc',
frameSrc: 'https://vvbin.cn/doc-next/',
},
},
{
path: 'https://vvbin.cn/doc-next/',
name: 'DocExternal',
component: 'LAYOUT',
meta: {
title: 'routes.demo.iframe.docExternal',
},
},
],
};
export default [
{
url: '/basic-api/getMenuList',
url: '/basic-api/getMenuListById',
timeout: 1000,
method: 'get',
response: (request: requestParams) => {
const token = getRequestToken(request);
if (!token) {
return resultError('Invalid token!');
response: ({ query }) => {
const { id } = query;
if (!id || id === '1') {
return resultSuccess([dashboardRoute, authRoute, levelRoute]);
}
const checkUser = createFakeUserList().find((item) => item.token === token);
if (!checkUser) {
return resultError('Invalid user token!');
if (id === '2') {
return resultSuccess([dashboardRoute, authRoute1, levelRoute]);
}
const id = checkUser.userId;
let menu: Object[];
switch (id) {
case '1':
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[0].path;
menu = [dashboardRoute, authRoute, levelRoute, sysRoute, linkRoute];
break;
case '2':
dashboardRoute.redirect = dashboardRoute.path + '/' + dashboardRoute.children[1].path;
menu = [dashboardRoute, authRoute, levelRoute, linkRoute];
break;
default:
menu = [];
}
return resultSuccess(menu);
},
},
] as MockMethod[];

View File

@@ -1,17 +1,15 @@
import { MockMethod } from 'vite-plugin-mock';
import { resultError, resultSuccess, getRequestToken, requestParams } from '../_util';
import { resultError, resultSuccess } from '../_util';
export function createFakeUserList() {
function createFakeUserList() {
return [
{
userId: '1',
username: 'vben',
realName: 'Vben Admin',
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=190848757&s=640',
desc: 'manager',
password: '123456',
token: 'fakeToken1',
homePath: '/dashboard/analysis',
roles: [
{
roleName: 'Super Admin',
@@ -24,10 +22,8 @@ export function createFakeUserList() {
username: 'test',
password: '123456',
realName: 'test user',
avatar: 'https://q1.qlogo.cn/g?b=qq&nk=339449197&s=640',
desc: 'tester',
token: 'fakeToken2',
homePath: '/dashboard/workbench',
roles: [
{
roleName: 'Tester',
@@ -52,7 +48,7 @@ export default [
response: ({ body }) => {
const { username, password } = body;
const checkUser = createFakeUserList().find(
(item) => item.username === username && password === item.password,
(item) => item.username === username && password === item.password
);
if (!checkUser) {
return resultError('Incorrect account or password');
@@ -69,12 +65,11 @@ export default [
},
},
{
url: '/basic-api/getUserInfo',
url: '/basic-api/getUserInfoById',
method: 'get',
response: (request: requestParams) => {
const token = getRequestToken(request);
if (!token) return resultError('Invalid token');
const checkUser = createFakeUserList().find((item) => item.token === token);
response: ({ query }) => {
const { userId } = query;
const checkUser = createFakeUserList().find((item) => item.userId === userId);
if (!checkUser) {
return resultError('The corresponding user information was not obtained!');
}
@@ -82,33 +77,17 @@ export default [
},
},
{
url: '/basic-api/getPermCode',
url: '/basic-api/getPermCodeByUserId',
timeout: 200,
method: 'get',
response: (request: requestParams) => {
const token = getRequestToken(request);
if (!token) return resultError('Invalid token');
const checkUser = createFakeUserList().find((item) => item.token === token);
if (!checkUser) {
return resultError('Invalid token!');
response: ({ query }) => {
const { userId } = query;
if (!userId) {
return resultError('userId is not null!');
}
const codeList = fakeCodeList[checkUser.userId];
const codeList = fakeCodeList[userId];
return resultSuccess(codeList);
},
},
{
url: '/basic-api/logout',
timeout: 200,
method: 'get',
response: (request: requestParams) => {
const token = getRequestToken(request);
if (!token) return resultError('Invalid token');
const checkUser = createFakeUserList().find((item) => item.token === token);
if (!checkUser) {
return resultError('Invalid token!');
}
return resultSuccess(undefined, { message: 'Token has been destroyed' });
},
},
] as MockMethod[];

View File

@@ -1,6 +1,6 @@
{
"name": "vben-admin",
"version": "2.7.2",
"version": "2.3.0",
"author": {
"name": "vben",
"email": "anncwb@126.com",
@@ -8,145 +8,120 @@
},
"scripts": {
"bootstrap": "yarn install",
"serve": "npm run dev",
"dev": "vite",
"build": "cross-env NODE_ENV=production vite build && esno ./build/script/postBuild.ts",
"build:test": "cross-env vite build --mode test && esno ./build/script/postBuild.ts",
"serve": "cross-env --max_old_space_size=4096 vite",
"dev": "cross-env --max_old_space_size=4096 vite",
"build": "vite build && esno ./build/script/postBuild.ts",
"build:no-cache": "yarn clean:cache && npm run build",
"report": "cross-env REPORT=true npm run build",
"type:check": "vue-tsc --noEmit --skipLibCheck",
"report": "cross-env REPORT=true npm run build ",
"preview": "npm run build && vite preview",
"preview:dist": "vite preview",
"log": "conventional-changelog -p angular -i CHANGELOG.md -s",
"clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
"clean:lib": "rimraf node_modules",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
"test:unit": "jest",
"test:unit-coverage": "jest --coverage",
"test:gzip": "npx http-server dist --cors --gzip -c-1",
"test:br": "npx http-server dist --cors --brotli -c-1",
"lint:pretty": "pretty-quick --staged",
"test:gzip": "http-server dist --cors --gzip -c-1",
"test:br": "http-server dist --cors --brotli -c-1",
"reinstall": "rimraf yarn.lock && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
"prepare": "husky install",
"gen:icon": "esno ./build/generate/icon/index.ts"
"install:husky": "is-ci || husky install",
"gen:icon": "esno ./build/generate/icon/index.ts",
"postinstall": "npm run install:husky"
},
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^6.0.1",
"@iconify/iconify": "^2.0.4",
"@logicflow/core": "^0.7.2",
"@logicflow/extension": "^0.7.2",
"@vueuse/core": "^6.7.4",
"@vueuse/shared": "^6.7.4",
"@zxcvbn-ts/core": "^1.0.0-beta.0",
"ant-design-vue": "2.2.8",
"axios": "^0.24.0",
"codemirror": "^5.63.3",
"cropperjs": "^1.5.12",
"crypto-js": "^4.1.1",
"echarts": "^5.2.2",
"intro.js": "^4.2.2",
"@iconify/iconify": "^2.0.0-rc.6",
"@vueuse/core": "^4.8.1",
"@zxcvbn-ts/core": "^0.3.0",
"ant-design-vue": "^2.1.2",
"axios": "^0.21.1",
"cropperjs": "^1.5.11",
"crypto-js": "^4.0.0",
"echarts": "^5.0.2",
"lodash-es": "^4.17.21",
"mockjs": "^1.1.0",
"moment": "^2.29.1",
"nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0",
"pinia": "2.0.0",
"pinia": "^2.0.0-alpha.12",
"print-js": "^1.6.0",
"qrcode": "^1.4.4",
"qs": "^6.10.1",
"resize-observer-polyfill": "^1.5.1",
"showdown": "^1.9.1",
"sortablejs": "^1.14.0",
"tinymce": "^5.10.0",
"vditor": "^3.8.7",
"vue": "^3.2.21",
"vue-i18n": "^9.1.9",
"vue-json-pretty": "^2.0.4",
"vue-router": "^4.0.12",
"vue-types": "^4.1.1",
"xlsx": "^0.17.3"
"sortablejs": "^1.13.0",
"tinymce": "^5.7.1",
"vditor": "^3.8.4",
"vue": "3.0.11",
"vue-i18n": "9.0.0",
"vue-router": "^4.0.6",
"vue-types": "^3.0.2",
"xlsx": "^0.16.9"
},
"devDependencies": {
"@commitlint/cli": "^14.1.0",
"@commitlint/config-conventional": "^14.1.0",
"@iconify/json": "^1.1.422",
"@commitlint/cli": "^12.1.1",
"@commitlint/config-conventional": "^12.1.1",
"@iconify/json": "^1.1.327",
"@purge-icons/generated": "^0.7.0",
"@types/codemirror": "^5.60.5",
"@types/crypto-js": "^4.0.2",
"@types/fs-extra": "^9.0.13",
"@types/inquirer": "^8.1.3",
"@types/intro.js": "^3.0.2",
"@types/jest": "^27.0.2",
"@types/lodash-es": "^4.17.5",
"@types/mockjs": "^1.0.4",
"@types/node": "^16.11.6",
"@types/crypto-js": "^4.0.1",
"@types/fs-extra": "^9.0.10",
"@types/inquirer": "^7.3.1",
"@types/lodash-es": "^4.17.4",
"@types/mockjs": "^1.0.3",
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.1",
"@types/qs": "^6.9.7",
"@types/showdown": "^1.9.4",
"@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^5.3.0",
"@vitejs/plugin-legacy": "^1.6.2",
"@vitejs/plugin-vue": "^1.9.4",
"@vitejs/plugin-vue-jsx": "^1.2.0",
"@vue/compiler-sfc": "3.2.21",
"@vue/test-utils": "^2.0.0-rc.16",
"autoprefixer": "^10.4.0",
"commitizen": "^4.2.4",
"@types/qrcode": "^1.4.0",
"@types/qs": "^6.9.6",
"@types/sortablejs": "^1.10.6",
"@typescript-eslint/eslint-plugin": "^4.21.0",
"@typescript-eslint/parser": "^4.21.0",
"@vitejs/plugin-legacy": "^1.3.2",
"@vitejs/plugin-vue": "^1.2.1",
"@vitejs/plugin-vue-jsx": "^1.1.3",
"@vue/compiler-sfc": "3.0.11",
"autoprefixer": "^10.2.5",
"commitizen": "^4.2.3",
"conventional-changelog-cli": "^2.1.1",
"cross-env": "^7.0.3",
"dotenv": "^10.0.0",
"eslint": "^8.1.0",
"eslint-config-prettier": "^8.3.0",
"eslint-define-config": "^1.1.2",
"eslint-plugin-jest": "^25.2.2",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3",
"esno": "^0.10.1",
"fs-extra": "^10.0.0",
"husky": "^7.0.4",
"inquirer": "^8.2.0",
"jest": "^27.3.1",
"less": "^4.1.2",
"lint-staged": "11.2.6",
"npm-run-all": "^4.1.5",
"postcss": "^8.3.11",
"postcss-html": "^1.2.0",
"postcss-less": "^5.0.0",
"prettier": "^2.4.1",
"dotenv": "^8.2.0",
"eslint": "^7.24.0",
"eslint-config-prettier": "^8.1.0",
"eslint-define-config": "^1.0.7",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.8.0",
"esno": "^0.5.0",
"fs-extra": "^9.1.0",
"http-server": "^0.12.3",
"husky": "^6.0.0",
"inquirer": "^8.0.0",
"is-ci": "^3.0.0",
"less": "^4.1.1",
"lint-staged": "^10.5.4",
"postcss": "^8.2.9",
"prettier": "^2.2.1",
"pretty-quick": "^3.1.0",
"rimraf": "^3.0.2",
"rollup-plugin-visualizer": "^5.5.2",
"stylelint": "^14.0.1",
"stylelint-config-html": "^1.0.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^23.0.0",
"stylelint-order": "^5.0.0",
"ts-jest": "^27.0.7",
"ts-node": "^10.4.0",
"typescript": "^4.4.4",
"vite": "^2.6.13",
"vite-plugin-compression": "^0.3.5",
"vite-plugin-html": "^2.1.1",
"vite-plugin-imagemin": "^0.4.6",
"vite-plugin-mock": "^2.9.6",
"rollup-plugin-visualizer": "5.3.0",
"stylelint": "^13.12.0",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^21.0.0",
"stylelint-order": "^4.1.0",
"ts-node": "^9.1.1",
"typescript": "4.2.4",
"vite": "2.1.5",
"vite-plugin-compression": "^0.2.4",
"vite-plugin-html": "^2.0.6",
"vite-plugin-imagemin": "^0.3.0",
"vite-plugin-mock": "^2.5.0",
"vite-plugin-purge-icons": "^0.7.0",
"vite-plugin-pwa": "^0.11.3",
"vite-plugin-style-import": "^1.3.0",
"vite-plugin-svg-icons": "^1.0.5",
"vite-plugin-theme": "^0.8.1",
"vite-plugin-vue-setup-extend": "^0.1.0",
"vite-plugin-windicss": "^1.4.12",
"vue-eslint-parser": "^8.0.1",
"vue-tsc": "^0.28.10"
"vite-plugin-pwa": "^0.7.0",
"vite-plugin-style-import": "^0.9.2",
"vite-plugin-svg-icons": "^0.4.1",
"vite-plugin-theme": "^0.6.4",
"vite-plugin-windicss": "0.13.2",
"vue-eslint-parser": "^7.6.0"
},
"resolutions": {
"//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it",
"//": "Used to install imagemin dependencies, because imagemin may not be installed in China.If it is abroad, you can delete it",
"bin-wrapper": "npm:bin-wrapper-china",
"rollup": "^2.56.3"
"rollup": "^2.45.1",
"esbuild": "^0.11.7"
},
"repository": {
"type": "git",

11963
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,20 @@
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
vueIndentScriptAndStyle: true,
singleQuote: true,
trailingComma: 'all',
quoteProps: 'as-needed',
bracketSpacing: true,
trailingComma: 'es5',
jsxBracketSameLine: false,
jsxSingleQuote: false,
arrowParens: 'always',
insertPragma: false,
requirePragma: false,
proseWrap: 'never',
htmlWhitespaceSensitivity: 'strict',
endOfLine: 'auto',
endOfLine: 'lf',
rangeStart: 0,
};

View File

@@ -1,20 +1,38 @@
<template>
<ConfigProvider :locale="getAntdLocale">
<ConfigProvider v-bind="lockEvent" :locale="getAntdLocale">
<AppProvider>
<RouterView />
</AppProvider>
</ConfigProvider>
</template>
<script lang="ts" setup>
<script lang="ts">
import { defineComponent } from 'vue';
import { ConfigProvider } from 'ant-design-vue';
import { AppProvider } from '/@/components/Application';
import { initAppConfigStore } from '/@/logics/initAppConfig';
import { useLockPage } from '/@/hooks/web/useLockPage';
import { useTitle } from '/@/hooks/web/useTitle';
import { useLocale } from '/@/locales/useLocale';
// support Multi-language
const { getAntdLocale } = useLocale();
export default defineComponent({
name: 'App',
components: { ConfigProvider, AppProvider },
setup() {
// Initialize vuex internal system configuration
initAppConfigStore();
// Listening to page changes and dynamically changing site titles
useTitle();
useTitle();
// support Multi-language
const { getAntdLocale } = useLocale();
// Create a lock screen monitor
const lockEvent = useLockPage();
return { getAntdLocale, lockEvent };
},
});
</script>

View File

@@ -3,14 +3,8 @@ import { GetAccountInfoModel } from './model/accountModel';
enum Api {
ACCOUNT_INFO = '/account/getAccountInfo',
SESSION_TIMEOUT = '/user/sessionTimeout',
TOKEN_EXPIRED = '/user/tokenExpired',
}
// Get personal center-basic settings
export const accountInfoApi = () => defHttp.get<GetAccountInfoModel>({ url: Api.ACCOUNT_INFO });
export const sessionTimeoutApi = () => defHttp.post<void>({ url: Api.SESSION_TIMEOUT });
export const tokenExpiredApi = () => defHttp.post<void>({ url: Api.TOKEN_EXPIRED });

View File

@@ -1,9 +0,0 @@
import { defHttp } from '/@/utils/http/axios';
import { AreaModel, AreaParams } from '/@/api/demo/model/areaModel';
enum Api {
AREA_RECORD = '/cascader/getAreaRecord',
}
export const areaRecord = (data: AreaParams) =>
defHttp.post<AreaModel>({ url: Api.AREA_RECORD, data });

View File

@@ -1,12 +0,0 @@
export interface AreaModel {
id: string;
code: string;
parentCode: string;
name: string;
levelType: number;
[key: string]: string | number;
}
export interface AreaParams {
parentCode: string;
}

View File

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

View File

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

View File

@@ -14,9 +14,7 @@ import { defHttp } from '/@/utils/http/axios';
enum Api {
AccountList = '/system/getAccountList',
IsAccountExist = '/system/accountExist',
DeptList = '/system/getDeptList',
setRoleStatus = '/system/setRoleStatus',
MenuList = '/system/getMenuList',
RolePageList = '/system/getRoleListByPage',
GetAllRoleList = '/system/getAllRoleList',
@@ -36,9 +34,3 @@ export const getRoleListByPage = (params?: RolePageParams) =>
export const getAllRoleList = (params?: RoleParams) =>
defHttp.get<RoleListGetResultModel>({ url: Api.GetAllRoleList, params });
export const setRoleStatus = (id: number, status: string) =>
defHttp.post({ url: Api.setRoleStatus, params: { id, status } });
export const isAccountExist = (account: string) =>
defHttp.post({ url: Api.IsAccountExist, params: { account } }, { errorMessageMode: 'none' });

View File

@@ -1,11 +0,0 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
TREE_OPTIONS_LIST = '/tree/getDemoOptions',
}
/**
* @description: Get sample options value
*/
export const treeOptionsListApi = (params?: Recordable) =>
defHttp.get<Recordable[]>({ url: Api.TREE_OPTIONS_LIST, params });

View File

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

View File

@@ -1,14 +1,14 @@
import { defHttp } from '/@/utils/http/axios';
import { getMenuListResultModel } from './model/menuModel';
import { getMenuListByIdParams, getMenuListByIdParamsResultModel } from './model/menuModel';
enum Api {
GetMenuList = '/getMenuList',
GetMenuListById = '/getMenuListById',
}
/**
* @description: Get user menu based on id
*/
export const getMenuList = () => {
return defHttp.get<getMenuListResultModel>({ url: Api.GetMenuList });
export const getMenuListById = (params: getMenuListByIdParams) => {
return defHttp.get<getMenuListByIdParamsResultModel>({ url: Api.GetMenuListById, params });
};

View File

@@ -1,4 +1,4 @@
import type { RouteMeta } from 'vue-router';
import { RouteMeta } from '/@/router/types';
export interface RouteItem {
path: string;
component: any;
@@ -10,7 +10,14 @@ export interface RouteItem {
children?: RouteItem[];
}
/**
* @description: Get menu interface
*/
export interface getMenuListByIdParams {
id: number | string;
}
/**
* @description: Get menu return value
*/
export type getMenuListResultModel = RouteItem[];
export type getMenuListByIdParamsResultModel = RouteItem[];

View File

@@ -6,6 +6,13 @@ export interface LoginParams {
password: string;
}
/**
* @description: Get user information
*/
export interface GetUserInfoByUserIdParams {
userId: string | number;
}
export interface RoleInfo {
roleName: string;
value: string;
@@ -23,7 +30,7 @@ export interface LoginResultModel {
/**
* @description: Get user information return value
*/
export interface GetUserInfoModel {
export interface GetUserInfoByUserIdModel {
roles: RoleInfo[];
// 用户id
userId: string | number;
@@ -31,8 +38,6 @@ export interface GetUserInfoModel {
username: string;
// 真实名字
realName: string;
// 头像
avatar: string;
// 介绍
desc?: string;
}

View File

@@ -1,6 +1,6 @@
import { UploadApiResult } from './model/uploadModel';
import { defHttp } from '/@/utils/http/axios';
import { UploadFileParams } from '/#/axios';
import { UploadFileParams } from '/@/utils/http/axios/types';
import { useGlobSetting } from '/@/hooks/setting';
const { uploadUrl = '' } = useGlobSetting();
@@ -10,13 +10,13 @@ const { uploadUrl = '' } = useGlobSetting();
*/
export function uploadApi(
params: UploadFileParams,
onUploadProgress: (progressEvent: ProgressEvent) => void,
onUploadProgress: (progressEvent: ProgressEvent) => void
) {
return defHttp.uploadFile<UploadApiResult>(
{
url: uploadUrl,
onUploadProgress,
},
params,
params
);
}

View File

@@ -1,13 +1,17 @@
import { defHttp } from '/@/utils/http/axios';
import { LoginParams, LoginResultModel, GetUserInfoModel } from './model/userModel';
import {
LoginParams,
LoginResultModel,
GetUserInfoByUserIdParams,
GetUserInfoByUserIdModel,
} from './model/userModel';
import { ErrorMessageMode } from '/#/axios';
import { ErrorMessageMode } from '/@/utils/http/axios/types';
enum Api {
Login = '/login',
Logout = '/logout',
GetUserInfo = '/getUserInfo',
GetPermCode = '/getPermCode',
GetUserInfoById = '/getUserInfoById',
GetPermCodeByUserId = '/getPermCodeByUserId',
}
/**
@@ -21,21 +25,23 @@ export function loginApi(params: LoginParams, mode: ErrorMessageMode = 'modal')
},
{
errorMessageMode: mode,
},
}
);
}
/**
* @description: getUserInfo
* @description: getUserInfoById
*/
export function getUserInfo() {
return defHttp.get<GetUserInfoModel>({ url: Api.GetUserInfo }, { errorMessageMode: 'none' });
export function getUserInfoById(params: GetUserInfoByUserIdParams) {
return defHttp.get<GetUserInfoByUserIdModel>({
url: Api.GetUserInfoById,
params,
});
}
export function getPermCode() {
return defHttp.get<string[]>({ url: Api.GetPermCode });
}
export function doLogout() {
return defHttp.get({ url: Api.Logout });
export function getPermCodeByUserId(params: GetUserInfoByUserIdParams) {
return defHttp.get<string[]>({
url: Api.GetPermCodeByUserId,
params,
});
}

View File

@@ -1,7 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="6395" height="1079" viewBox="0 0 6395 1079">
<defs>
<clipPath id="clip-path">
<rect width="6395" height="1079" transform="translate(-5391)" fill="#fff"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="6395" height="1079" transform="translate(-5391)" fill="#fff"/>
</clipPath>
<linearGradient id="linear-gradient" x1="0.747" y1="0.222" x2="0.973" y2="0.807" gradientUnits="objectBoundingBox">
<stop offset="0" stop-color="#2c41b4"/>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

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

Before

Width:  |  Height:  |  Size: 3.0 KiB

View File

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

Before

Width:  |  Height:  |  Size: 996 B

View File

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

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.0 KiB

View File

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

View File

@@ -1,45 +1,68 @@
<template>
<div v-if="getShowDarkModeToggle" :class="getClass" @click="toggleDarkMode">
<div :class="`${prefixCls}-inner`"></div>
<div
v-if="getShowDarkModeToggle"
:class="[
prefixCls,
{
[`${prefixCls}--dark`]: isDark,
},
]"
@click="toggleDarkMode"
>
<div :class="`${prefixCls}-inner`"> </div>
<SvgIcon size="14" name="sun" />
<SvgIcon size="14" name="moon" />
</div>
</template>
<script lang="ts" setup>
import { computed, unref } from 'vue';
import { SvgIcon } from '/@/components/Icon';
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { SvgIcon } from '/@/components/Icon';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
import { updateHeaderBgColor, updateSidebarBgColor } from '/@/logics/theme/updateBackground';
import { updateDarkTheme } from '/@/logics/theme/dark';
import { ThemeEnum } from '/@/enums/appEnum';
const { prefixCls } = useDesign('dark-switch');
const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting();
export default defineComponent({
name: 'DarkModeToggle',
components: { SvgIcon },
// props: {
// size: {
// type: String,
// default: 'default',
// validate: (val) => ['default', 'large'].includes(val),
// },
// },
setup() {
const { prefixCls } = useDesign('dark-mode-toggle');
const { getDarkMode, setDarkMode, getShowDarkModeToggle } = useRootSetting();
const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK);
function toggleDarkMode() {
const darkMode = getDarkMode.value === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK;
setDarkMode(darkMode);
updateDarkTheme(darkMode);
updateHeaderBgColor();
updateSidebarBgColor();
}
const isDark = computed(() => getDarkMode.value === ThemeEnum.DARK);
const getClass = computed(() => [
prefixCls,
{
[`${prefixCls}--dark`]: unref(isDark),
return {
isDark,
prefixCls,
toggleDarkMode,
getShowDarkModeToggle,
};
},
]);
function toggleDarkMode() {
const darkMode = getDarkMode.value === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK;
setDarkMode(darkMode);
updateDarkTheme(darkMode);
updateHeaderBgColor();
updateSidebarBgColor();
}
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-dark-switch';
@prefix-cls: ~'@{namespace}-dark-mode-toggle';
html[data-theme='dark'] {
.@{prefix-cls} {
border: 1px solid rgb(196 188 188);
border: 1px solid rgb(196, 188, 188);
}
}
@@ -72,5 +95,16 @@
transform: translateX(calc(100% + 2px));
}
}
// &--large {
// width: 70px;
// height: 34px;
// padding: 0 10px;
// .@{prefix-cls}-inner {
// width: 26px;
// height: 26px;
// }
// }
}
</style>

View File

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

View File

@@ -3,59 +3,66 @@
* @Description: logo component
-->
<template>
<div class="anticon" :class="getAppLogoClass" @click="goHome">
<div
class="anticon"
:class="[prefixCls, theme, { 'collapsed-show-title': getCollapsedShowTitle }]"
@click="handleGoHome"
>
<img src="../../../assets/images/logo.png" />
<div class="ml-2 truncate md:opacity-100" :class="getTitleClass" v-show="showTitle">
<div
class="ml-2 truncate md:opacity-100"
:class="[
`${prefixCls}__title`,
{
'xs:opacity-0': !alwaysShowTitle,
},
]"
v-show="showTitle"
>
{{ title }}
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, unref } from 'vue';
<script lang="ts">
import { defineComponent } from 'vue';
import { useGlobSetting } from '/@/hooks/setting';
import { useGo } from '/@/hooks/web/usePage';
import { useMenuSetting } from '/@/hooks/setting/useMenuSetting';
import { useDesign } from '/@/hooks/web/useDesign';
import { PageEnum } from '/@/enums/pageEnum';
import { useUserStore } from '/@/store/modules/user';
import { propTypes } from '/@/utils/propTypes';
const props = defineProps({
/**
* The theme of the current parent component
*/
theme: { type: String, validator: (v: string) => ['light', 'dark'].includes(v) },
/**
* Whether to show title
*/
showTitle: { type: Boolean, default: true },
/**
* The title is also displayed when the menu is collapsed
*/
alwaysShowTitle: { type: Boolean },
});
const { prefixCls } = useDesign('app-logo');
const { getCollapsedShowTitle } = useMenuSetting();
const userStore = useUserStore();
const { title } = useGlobSetting();
const go = useGo();
const getAppLogoClass = computed(() => [
prefixCls,
props.theme,
{ 'collapsed-show-title': unref(getCollapsedShowTitle) },
]);
const getTitleClass = computed(() => [
`${prefixCls}__title`,
{
'xs:opacity-0': !props.alwaysShowTitle,
export default defineComponent({
name: 'AppLogo',
props: {
/**
* The theme of the current parent component
*/
theme: propTypes.oneOf(['light', 'dark']),
// Whether to show title
showTitle: propTypes.bool.def(true),
alwaysShowTitle: propTypes.bool.def(false),
},
]);
setup() {
const { prefixCls } = useDesign('app-logo');
const { getCollapsedShowTitle } = useMenuSetting();
const { title } = useGlobSetting();
const go = useGo();
function goHome() {
go(userStore.getUserInfo.homePath || PageEnum.BASE_HOME);
}
function handleGoHome(): void {
go(PageEnum.BASE_HOME);
}
return {
handleGoHome,
title,
prefixCls,
getCollapsedShowTitle,
};
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-app-logo';
@@ -87,7 +94,6 @@
font-size: 16px;
font-weight: 700;
transition: all 0.5s;
line-height: normal;
}
}
</style>

View File

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

View File

@@ -1,14 +1,20 @@
<script lang="tsx">
import { defineComponent, ref, unref } from 'vue';
import { Tooltip } from 'ant-design-vue';
import { SearchOutlined } from '@ant-design/icons-vue';
import AppSearchModal from './AppSearchModal.vue';
import { useHeaderSetting } from '/@/hooks/setting/useHeaderSetting';
import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({
name: 'AppSearch',
components: { AppSearchModal, Tooltip },
setup() {
const showModal = ref(false);
const { getShowSearch } = useHeaderSetting();
const { t } = useI18n();
function changeModal(show: boolean) {
@@ -16,6 +22,9 @@
}
return () => {
if (!unref(getShowSearch)) {
return null;
}
return (
<div class="p-1" onClick={changeModal.bind(null, true)}>
<Tooltip>

View File

@@ -1,21 +1,36 @@
<template>
<div :class="`${prefixCls}`">
<AppSearchKeyItem :class="`${prefixCls}-item`" icon="ant-design:enter-outlined" />
<AppSearchKeyItem :class="`${prefixCls}__item`" icon="ant-design:enter-outlined" />
<span>{{ t('component.app.toSearch') }}</span>
<AppSearchKeyItem :class="`${prefixCls}-item`" icon="ion:arrow-up-outline" />
<AppSearchKeyItem :class="`${prefixCls}-item`" icon="ion:arrow-down-outline" />
<AppSearchKeyItem :class="`${prefixCls}__item`" icon="ion:arrow-up-outline" />
<AppSearchKeyItem :class="`${prefixCls}__item`" icon="ion:arrow-down-outline" />
<span>{{ t('component.app.toNavigate') }}</span>
<AppSearchKeyItem :class="`${prefixCls}-item`" icon="mdi:keyboard-esc" />
<AppSearchKeyItem :class="`${prefixCls}__item`" icon="mdi:keyboard-esc" />
<span>{{ t('common.closeText') }}</span>
</div>
</template>
<script lang="ts" setup>
<script lang="ts">
import { defineComponent } from 'vue';
import AppSearchKeyItem from './AppSearchKeyItem.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useI18n } from '/@/hooks/web/useI18n';
const { prefixCls } = useDesign('app-search-footer');
const { t } = useI18n();
export default defineComponent({
name: 'AppSearchFooter',
components: { AppSearchKeyItem },
setup() {
const { prefixCls } = useDesign('app-search-footer');
const { t } = useI18n();
return {
prefixCls,
t,
};
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-app-search-footer';
@@ -27,22 +42,22 @@
padding: 0 16px;
font-size: 12px;
color: #666;
background-color: @component-background;
background: @component-background;
border-top: 1px solid @border-color-base;
border-radius: 0 0 16px 16px;
align-items: center;
flex-shrink: 0;
&-item {
&__item {
display: flex;
width: 20px;
height: 18px;
padding-bottom: 2px;
margin-right: 0.4em;
background-color: linear-gradient(-225deg, #d5dbe4, #f8f8f8);
background: linear-gradient(-225deg, #d5dbe4, #f8f8f8);
border-radius: 2px;
box-shadow: inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff,
0 1px 2px 1px rgb(30 35 90 / 40%);
0 1px 2px 1px rgba(30, 35, 90, 0.4);
align-items: center;
justify-content: center;

View File

@@ -1,11 +1,13 @@
<template>
<span :class="$attrs.class">
<Icon :icon="icon" />
<g-icon :icon="icon" />
</span>
</template>
<script lang="ts" setup>
import { Icon } from '/@/components/Icon';
defineProps({
icon: String,
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
icon: String,
},
});
</script>

View File

@@ -7,11 +7,11 @@
<a-input
:class="`${prefixCls}-input`"
:placeholder="t('common.searchText')"
ref="inputRef"
allow-clear
@change="handleSearch"
>
<template #prefix>
<!-- <Icon icon="ion:search"/> -->
<SearchOutlined />
</template>
</a-input>
@@ -56,62 +56,84 @@
</transition>
</Teleport>
</template>
<script lang="ts">
import { defineComponent, computed, unref, ref } from 'vue';
<script lang="ts" setup>
import { computed, unref, ref, watch, nextTick } from 'vue';
import { SearchOutlined } from '@ant-design/icons-vue';
import { Input } from 'ant-design-vue';
import AppSearchFooter from './AppSearchFooter.vue';
import Icon from '/@/components/Icon';
// @ts-ignore
import vClickOutside from '/@/directives/clickOutside';
import clickOutside from '/@/directives/clickOutside';
import { useDesign } from '/@/hooks/web/useDesign';
import { useRefs } from '/@/hooks/core/useRefs';
import { useMenuSearch } from './useMenuSearch';
import { useI18n } from '/@/hooks/web/useI18n';
import { useAppInject } from '/@/hooks/web/useAppInject';
const props = defineProps({
visible: { type: Boolean },
});
import { propTypes } from '/@/utils/propTypes';
const emit = defineEmits(['close']);
const scrollWrap = ref(null);
const inputRef = ref<Nullable<HTMLElement>>(null);
const { t } = useI18n();
const { prefixCls } = useDesign('app-search-modal');
const [refs, setRefs] = useRefs();
const { getIsMobile } = useAppInject();
const { handleSearch, searchResult, keyword, activeIndex, handleEnter, handleMouseenter } =
useMenuSearch(refs, scrollWrap, emit);
const getIsNotData = computed(() => !keyword || unref(searchResult).length === 0);
const getClass = computed(() => {
return [
prefixCls,
{
[`${prefixCls}--mobile`]: unref(getIsMobile),
},
];
});
watch(
() => props.visible,
(visible: boolean) => {
visible &&
nextTick(() => {
unref(inputRef)?.focus();
});
export default defineComponent({
name: 'AppSearchModal',
components: { Icon, SearchOutlined, AppSearchFooter, [Input.name]: Input },
directives: {
clickOutside,
},
);
props: {
visible: propTypes.bool,
},
emits: ['close'],
setup(_, { emit }) {
const scrollWrap = ref<ElRef>(null);
const { prefixCls } = useDesign('app-search-modal');
const { t } = useI18n();
const [refs, setRefs] = useRefs();
const { getIsMobile } = useAppInject();
function handleClose() {
searchResult.value = [];
emit('close');
}
const {
handleSearch,
searchResult,
keyword,
activeIndex,
handleEnter,
handleMouseenter,
} = useMenuSearch(refs, scrollWrap, emit);
const getIsNotData = computed(() => {
return !keyword || unref(searchResult).length === 0;
});
const getClass = computed(() => {
return [
prefixCls,
{
[`${prefixCls}--mobile`]: unref(getIsMobile),
},
];
});
function handleClose() {
searchResult.value = [];
emit('close');
}
return {
t,
prefixCls,
getClass,
handleSearch,
searchResult,
activeIndex,
getIsNotData,
handleEnter,
setRefs,
scrollWrap,
handleMouseenter,
handleClose,
};
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-app-search-modal';
@@ -125,7 +147,7 @@
width: 100%;
height: 100%;
padding-top: 50px;
background-color: rgb(0 0 0 / 25%);
background: rgba(0, 0, 0, 0.25);
justify-content: center;
&--mobile {
@@ -159,7 +181,7 @@
&__item {
&-enter {
opacity: 0% !important;
opacity: 0 !important;
}
}
}
@@ -168,16 +190,16 @@
&-content {
position: relative;
width: 632px;
margin: 0 auto auto;
background-color: @component-background;
margin: 0 auto auto auto;
background: @component-background;
border-radius: 16px;
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 25%);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
flex-direction: column;
}
&-input__wrapper {
display: flex;
padding: 14px 14px 0;
padding: 14px 14px 0 14px;
justify-content: space-between;
align-items: center;
}
@@ -229,7 +251,7 @@
font-size: 14px;
color: @text-color-base;
cursor: pointer;
background-color: @component-background;
background: @component-background;
border-radius: 4px;
box-shadow: 0 1px 3px 0 #d4d9e1;
align-items: center;
@@ -242,10 +264,10 @@
&--active {
color: #fff;
background-color: @primary-color;
background: @primary-color;
.@{prefix-cls}-list__item-enter {
opacity: 100%;
opacity: 1;
}
}
@@ -259,7 +281,7 @@
&-enter {
width: 30px;
opacity: 0%;
opacity: 0;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,44 +7,44 @@
<Icon icon="ion:chevron-forward" :style="$attrs.iconStyle" />
</span>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { Icon } from '/@/components/Icon';
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
const props = defineProps({
/**
* Arrow expand state
*/
expand: { type: Boolean },
/**
* Arrow up by default
*/
up: { type: Boolean },
/**
* Arrow down by default
*/
down: { type: Boolean },
/**
* Cancel padding/margin for inline
*/
inset: { type: Boolean },
});
import { propTypes } from '/@/utils/propTypes';
const { prefixCls } = useDesign('basic-arrow');
import { Icon } from '/@/components/Icon';
// get component class
const getClass = computed(() => {
const { expand, up, down, inset } = props;
return [
prefixCls,
{
[`${prefixCls}--active`]: expand,
up,
inset,
down,
},
];
export default defineComponent({
name: 'BasicArrow',
components: { Icon },
props: {
expand: propTypes.bool,
top: propTypes.bool,
bottom: propTypes.bool,
inset: propTypes.bool,
},
setup(props) {
const { prefixCls } = useDesign('basic-arrow');
const getClass = computed(() => {
const { expand, top, bottom, inset } = props;
return [
prefixCls,
{
[`${prefixCls}--active`]: expand,
top,
inset,
bottom,
},
];
});
return {
getClass,
};
},
});
</script>
<style lang="less" scoped>
@@ -52,7 +52,6 @@
.@{prefix-cls} {
display: inline-block;
cursor: pointer;
transform: rotate(0deg);
transition: all 0.3s ease 0.1s;
transform-origin: center center;
@@ -65,19 +64,19 @@
line-height: 0px;
}
&.up {
&.top {
transform: rotate(-90deg);
}
&.down {
&.bottom {
transform: rotate(90deg);
}
&.up.@{prefix-cls}--active {
&.top.@{prefix-cls}--active {
transform: rotate(90deg);
}
&.down.@{prefix-cls}--active {
&.bottom.@{prefix-cls}--active {
transform: rotate(-90deg);
}
}

View File

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

View File

@@ -1,43 +1,41 @@
<template>
<span :class="getClass">
<slot></slot>
<BasicHelp :class="`${prefixCls}-help`" v-if="helpMessage" :text="helpMessage" />
<BasicHelp :class="`${prefixCls}__help`" v-if="helpMessage" :text="helpMessage" />
</span>
</template>
<script lang="ts" setup>
<script lang="ts">
import type { PropType } from 'vue';
import { useSlots, computed } from 'vue';
import { defineComponent, computed } from 'vue';
import BasicHelp from './BasicHelp.vue';
import { useDesign } from '/@/hooks/web/useDesign';
const props = defineProps({
/**
* Help text list or string
* @default: ''
*/
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
default: '',
},
/**
* Whether the color block on the left side of the title
* @default: false
*/
span: { type: Boolean },
/**
* Whether to default the text, that is, not bold
* @default: false
*/
normal: { type: Boolean },
});
import { propTypes } from '/@/utils/propTypes';
const { prefixCls } = useDesign('basic-title');
const slots = useSlots();
const getClass = computed(() => [
prefixCls,
{ [`${prefixCls}-show-span`]: props.span && slots.default },
{ [`${prefixCls}-normal`]: props.normal },
]);
export default defineComponent({
name: 'BasicTitle',
components: { BasicHelp },
props: {
helpMessage: {
type: [String, Array] as PropType<string | string[]>,
default: '',
},
span: propTypes.bool,
normal: propTypes.bool.def(false),
},
setup(props, { slots }) {
const { prefixCls } = useDesign('basic-title');
const getClass = computed(() => [
prefixCls,
{ [`${prefixCls}-show-span`]: props.span && slots.default },
{ [`${prefixCls}-normal`]: props.normal },
]);
return { prefixCls, getClass };
},
});
</script>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-basic-title';
@@ -65,11 +63,11 @@
width: 3px;
height: 16px;
margin-right: 4px;
background-color: @primary-color;
background: @primary-color;
content: '';
}
&-help {
&__help {
margin-left: 10px;
}
}

View File

@@ -1,9 +1,4 @@
import { withInstall } from '/@/utils';
import type { ExtractPropTypes } from 'vue';
import button from './src/BasicButton.vue';
import popConfirmButton from './src/PopConfirmButton.vue';
import { buttonProps } from './src/props';
import Button from './src/BasicButton.vue';
import PopConfirmButton from './src/PopConfirmButton.vue';
export const Button = withInstall(button);
export const PopConfirmButton = withInstall(popConfirmButton);
export declare type ButtonProps = Partial<ExtractPropTypes<typeof buttonProps>>;
export { Button, PopConfirmButton };

View File

@@ -1,40 +1,47 @@
<template>
<Button v-bind="getBindValue" :class="getButtonClass" @click="onClick">
<Button v-bind="getBindValue" :class="[getColor, $attrs.class]" @click="onClick">
<template #default="data">
<Icon :icon="preIcon" v-if="preIcon" :size="iconSize" />
<slot v-bind="data || {}"></slot>
<Icon :icon="postIcon" v-if="postIcon" :size="iconSize" />
<Icon :icon="preIcon" v-if="preIcon" :size="14" />
<slot v-bind="data"></slot>
<Icon :icon="postIcon" v-if="postIcon" :size="14" />
</template>
</Button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defineComponent, computed } from 'vue';
import { Button } from 'ant-design-vue';
import Icon from '/@/components/Icon';
import { propTypes } from '/@/utils/propTypes';
export default defineComponent({
name: 'AButton',
components: { Button, Icon },
inheritAttrs: false,
props: {
type: propTypes.oneOf(['primary', 'default', 'danger', 'dashed', 'link']).def('default'),
color: propTypes.oneOf(['error', 'warning', 'success', '']),
loading: propTypes.bool,
disabled: propTypes.bool,
preIcon: propTypes.string,
postIcon: propTypes.string,
onClick: propTypes.func,
},
setup(props, { attrs }) {
const getColor = computed(() => {
const { color, disabled } = props;
return {
[`ant-btn-${color}`]: !!color,
[`is-disabled`]: disabled,
};
});
const getBindValue = computed((): any => {
return { ...attrs, ...props };
});
return { getBindValue, getColor };
},
});
</script>
<script lang="ts" setup>
import { computed, unref } from 'vue';
import { Button } from 'ant-design-vue';
import Icon from '/@/components/Icon/src/Icon.vue';
import { buttonProps } from './props';
import { useAttrs } from '/@/hooks/core/useAttrs';
const props = defineProps(buttonProps);
// get component class
const attrs = useAttrs({ excludeDefaultKeys: false });
const getButtonClass = computed(() => {
const { color, disabled } = props;
return [
{
[`ant-btn-${color}`]: !!color,
[`is-disabled`]: disabled,
},
];
});
// get inherit binding value
const getBindValue = computed(() => ({ ...unref(attrs), ...props }));
</script>

View File

@@ -1,53 +1,51 @@
<script lang="ts">
import { computed, defineComponent, h, unref } from 'vue';
import BasicButton from './BasicButton.vue';
import { defineComponent, h, unref, computed } from 'vue';
import { Popconfirm } from 'ant-design-vue';
import BasicButton from './BasicButton.vue';
import { propTypes } from '/@/utils/propTypes';
import { extendSlots } from '/@/utils/helper/tsxHelper';
import { omit } from 'lodash-es';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { useI18n } from '/@/hooks/web/useI18n';
const props = {
/**
* Whether to enable the drop-down menu
* @default: true
*/
enable: {
type: Boolean,
default: true,
},
};
export default defineComponent({
name: 'PopButton',
components: { Popconfirm, BasicButton },
inheritAttrs: false,
props,
props: {
size: propTypes.oneOf(['large', 'default', 'small']).def(),
enable: propTypes.bool.def(true),
okText: propTypes.string,
cancelText: propTypes.string,
},
setup(props, { slots }) {
const { t } = useI18n();
const attrs = useAttrs();
// get inherit binding value
const getBindValues = computed(() => {
return Object.assign(
const popValues = Object.assign(
{
okText: t('common.okText'),
cancelText: t('common.cancelText'),
},
{ ...props, ...unref(attrs) },
{ ...props, ...unref(attrs) }
);
return popValues;
});
return () => {
const bindValues = omit(unref(getBindValues), 'icon');
const btnBind = omit(bindValues, 'title') as Recordable;
if (btnBind.disabled) btnBind.color = '';
const Button = h(BasicButton, btnBind, extendSlots(slots));
const values = omit(unref(getBindValues), 'icon');
const Button = h(BasicButton, values, extendSlots(slots));
// If it is not enabled, it is a normal button
if (!props.enable) {
return Button;
}
return h(Popconfirm, bindValues, { default: () => Button });
return h(Popconfirm, values, { default: () => Button });
};
},
});

View File

@@ -1,19 +0,0 @@
export const buttonProps = {
color: { type: String, validator: (v) => ['error', 'warning', 'success', ''].includes(v) },
loading: { type: Boolean },
disabled: { type: Boolean },
/**
* Text before icon.
*/
preIcon: { type: String },
/**
* Text after icon.
*/
postIcon: { type: String },
/**
* preIcon and postIcon icon size.
* @default: 14
*/
iconSize: { type: Number, default: 14 },
onClick: { type: Function as PropType<(...args) => any>, default: null },
};

View File

@@ -1,4 +0,0 @@
import { withInstall } from '/@/utils';
import cardList from './src/CardList.vue';
export const CardList = withInstall(cardList);

View File

@@ -1,178 +0,0 @@
<template>
<div class="p-2">
<div class="p-4 mb-2 bg-white">
<BasicForm @register="registerForm" />
</div>
{{ sliderProp.width }}
<div class="p-2 bg-white">
<List
:grid="{ gutter: 5, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: grid }"
:data-source="data"
:pagination="paginationProp"
>
<template #header>
<div class="flex justify-end space-x-2"
><slot name="header"></slot>
<Tooltip>
<template #title>
<div class="w-50">每行显示数量</div
><Slider
id="slider"
v-bind="sliderProp"
v-model:value="grid"
@change="sliderChange"
/></template>
<Button><TableOutlined /></Button>
</Tooltip>
<Tooltip @click="fetch">
<template #title>刷新</template>
<Button><RedoOutlined /></Button>
</Tooltip>
</div>
</template>
<template #renderItem="{ item }">
<ListItem>
<Card>
<template #title></template>
<template #cover>
<div :class="height">
<Image :src="item.imgs[0]" />
</div>
</template>
<template class="ant-card-actions" #actions>
<!-- <SettingOutlined key="setting" />-->
<EditOutlined key="edit" />
<Dropdown
:trigger="['hover']"
:dropMenuList="[
{
text: '删除',
event: '1',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, item.id),
},
},
]"
popconfirm
>
<EllipsisOutlined key="ellipsis" />
</Dropdown>
</template>
<CardMeta>
<template #title>
<TypographyText :content="item.name" :ellipsis="{ tooltip: item.address }" />
</template>
<template #avatar>
<Avatar :src="item.avatar" />
</template>
<template #description>{{ item.time }}</template>
</CardMeta>
</Card>
</ListItem>
</template>
</List>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import {
EditOutlined,
EllipsisOutlined,
RedoOutlined,
TableOutlined,
} from '@ant-design/icons-vue';
import { List, Card, Image, Typography, Tooltip, Slider, Avatar } from 'ant-design-vue';
import { Dropdown } from '/@/components/Dropdown';
import { BasicForm, useForm } from '/@/components/Form';
import { propTypes } from '/@/utils/propTypes';
import { Button } from '/@/components/Button';
import { isFunction } from '/@/utils/is';
import { useSlider, grid } from './data';
const ListItem = List.Item;
const CardMeta = Card.Meta;
const TypographyText = Typography.Text;
// 获取slider属性
const sliderProp = computed(() => useSlider(4));
// 组件接收参数
const props = defineProps({
// 请求API的参数
params: propTypes.object.def({}),
//api
api: propTypes.func,
});
//暴露内部方法
const emit = defineEmits(['getMethod', 'delete']);
//数据
const data = ref([]);
// 切换每行个数
// cover图片自适应高度
//修改pageSize并重新请求数据
const height = computed(() => {
return `h-${120 - grid.value * 6}`;
});
//表单
const [registerForm, { validate }] = useForm({
schemas: [{ field: 'type', component: 'Input', label: '类型' }],
labelWidth: 80,
baseColProps: { span: 6 },
actionColOptions: { span: 24 },
autoSubmitOnEnter: true,
submitFunc: handleSubmit,
});
//表单提交
async function handleSubmit() {
const data = await validate();
await fetch(data);
}
function sliderChange(n) {
pageSize.value = n * 4;
fetch();
}
// 自动请求并暴露内部方法
onMounted(() => {
fetch();
emit('getMethod', fetch);
});
async function fetch(p = {}) {
const { api, params } = props;
if (api && isFunction(api)) {
const res = await api({ ...params, page: page.value, pageSize: pageSize.value, ...p });
data.value = res.items;
total.value = res.total;
}
}
//分页相关
const page = ref(1);
const pageSize = ref(36);
const total = ref(0);
const paginationProp = ref({
showSizeChanger: false,
showQuickJumper: true,
pageSize,
current: page,
total,
showTotal: (total) => `${total}`,
onChange: pageChange,
onShowSizeChange: pageSizeChange,
});
function pageChange(p, pz) {
page.value = p;
pageSize.value = pz;
fetch();
}
function pageSizeChange(_current, size) {
pageSize.value = size;
fetch();
}
async function handleDelete(id) {
emit('delete', id);
}
</script>

View File

@@ -1,25 +0,0 @@
import { ref } from 'vue';
//每行个数
export const grid = ref(12);
// slider属性
export const useSlider = (min = 6, max = 12) => {
// 每行显示个数滑动条
const getMarks = () => {
const l = {};
for (let i = min; i < max + 1; i++) {
l[i] = {
style: {
color: '#fff',
},
label: i,
};
}
return l;
};
return {
min,
max,
marks: getMarks(),
step: 1,
};
};

View File

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

View File

@@ -1,19 +0,0 @@
<template>
<div ref="wrap">
<slot></slot>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import { onClickOutside } from '@vueuse/core';
const emit = defineEmits(['mounted', 'clickOutside']);
const wrap = ref<ElRef>(null);
onClickOutside(wrap, () => {
emit('clickOutside');
});
onMounted(() => {
emit('mounted');
});
</script>

View File

@@ -0,0 +1,26 @@
<template>
<div ref="wrap">
<slot></slot>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue';
import { onClickOutside } from '@vueuse/core';
export default defineComponent({
name: 'ClickOutSide',
emits: ['mounted', 'clickOutside'],
setup(_, { emit }) {
const wrap = ref<ElRef>(null);
onClickOutside(wrap, () => {
emit('clickOutside');
});
onMounted(() => {
emit('mounted');
});
return { wrap };
},
});
</script>

View File

@@ -1,8 +0,0 @@
import { withInstall } from '/@/utils';
import codeEditor from './src/CodeEditor.vue';
import jsonPreview from './src/json-preview/JsonPreview.vue';
export const CodeEditor = withInstall(codeEditor);
export const JsonPreview = withInstall(jsonPreview);
export * from './src/typing';

View File

@@ -1,54 +0,0 @@
<template>
<div class="h-full">
<CodeMirrorEditor
:value="getValue"
@change="handleValueChange"
:mode="mode"
:readonly="readonly"
/>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import CodeMirrorEditor from './codemirror/CodeMirror.vue';
import { isString } from '/@/utils/is';
import { MODE } from './typing';
const props = defineProps({
value: { type: [Object, String] as PropType<Record<string, any> | string> },
mode: {
type: String as PropType<MODE>,
default: MODE.JSON,
validator(value: any) {
// 这个值必须匹配下列字符串中的一个
return Object.values(MODE).includes(value);
},
},
readonly: { type: Boolean },
autoFormat: { type: Boolean, default: true },
});
const emit = defineEmits(['change', 'update:value', 'format-error']);
const getValue = computed(() => {
const { value, mode, autoFormat } = props;
if (!autoFormat || mode !== MODE.JSON) {
return value as string;
}
let result = value;
if (isString(value)) {
try {
result = JSON.parse(value);
} catch (e) {
emit('format-error', value);
return value as string;
}
}
return JSON.stringify(result, null, 2);
});
function handleValueChange(v) {
emit('update:value', v);
emit('change', v);
}
</script>

View File

@@ -1,113 +0,0 @@
<template>
<div class="relative !h-full w-full overflow-hidden" ref="el"></div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, watchEffect, watch, unref, nextTick } from 'vue';
import { useDebounceFn } from '@vueuse/core';
import { useAppStore } from '/@/store/modules/app';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import CodeMirror from 'codemirror';
import { MODE } from './../typing';
// css
import './codemirror.css';
import 'codemirror/theme/idea.css';
import 'codemirror/theme/material-palenight.css';
// modes
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/css/css';
import 'codemirror/mode/htmlmixed/htmlmixed';
const props = defineProps({
mode: {
type: String as PropType<MODE>,
default: MODE.JSON,
validator(value: any) {
// 这个值必须匹配下列字符串中的一个
return Object.values(MODE).includes(value);
},
},
value: { type: String, default: '' },
readonly: { type: Boolean, default: false },
});
const emit = defineEmits(['change']);
const el = ref();
let editor: Nullable<CodeMirror.Editor>;
const debounceRefresh = useDebounceFn(refresh, 100);
const appStore = useAppStore();
watch(
() => props.value,
async (value) => {
await nextTick();
const oldValue = editor?.getValue();
if (value !== oldValue) {
editor?.setValue(value ? value : '');
}
},
{ flush: 'post' },
);
watchEffect(() => {
editor?.setOption('mode', props.mode);
});
watch(
() => appStore.getDarkMode,
async () => {
setTheme();
},
{
immediate: true,
},
);
function setTheme() {
unref(editor)?.setOption(
'theme',
appStore.getDarkMode === 'light' ? 'idea' : 'material-palenight',
);
}
function refresh() {
editor?.refresh();
}
async function init() {
const addonOptions = {
autoCloseBrackets: true,
autoCloseTags: true,
foldGutter: true,
gutters: ['CodeMirror-linenumbers'],
};
editor = CodeMirror(el.value!, {
value: '',
mode: props.mode,
readOnly: props.readonly,
tabSize: 2,
theme: 'material-palenight',
lineWrapping: true,
lineNumbers: true,
...addonOptions,
});
editor?.setValue(props.value);
setTheme();
editor?.on('change', () => {
emit('change', editor?.getValue());
});
}
onMounted(async () => {
await nextTick();
init();
useWindowSizeFn(debounceRefresh);
});
onUnmounted(() => {
editor = null;
});
</script>

View File

@@ -1,21 +0,0 @@
import CodeMirror from 'codemirror';
import './codemirror.css';
import 'codemirror/theme/idea.css';
import 'codemirror/theme/material-palenight.css';
// import 'codemirror/addon/lint/lint.css';
// modes
import 'codemirror/mode/javascript/javascript';
import 'codemirror/mode/css/css';
import 'codemirror/mode/htmlmixed/htmlmixed';
// addons
// import 'codemirror/addon/edit/closebrackets';
// import 'codemirror/addon/edit/closetag';
// import 'codemirror/addon/comment/comment';
// import 'codemirror/addon/fold/foldcode';
// import 'codemirror/addon/fold/foldgutter';
// import 'codemirror/addon/fold/brace-fold';
// import 'codemirror/addon/fold/indent-fold';
// import 'codemirror/addon/lint/json-lint';
// import 'codemirror/addon/fold/comment-fold';
export { CodeMirror };

View File

@@ -1,525 +0,0 @@
/* BASICS */
.CodeMirror {
--base: #545281;
--comment: hsl(210deg 25% 60%);
--keyword: #af4ab1;
--variable: #0055d1;
--function: #c25205;
--string: #2ba46d;
--number: #c25205;
--tags: #d00;
--qualifier: #ff6032;
--important: var(--string);
position: relative;
height: auto;
height: 100%;
overflow: hidden;
font-family: var(--font-code);
background: white;
direction: ltr;
}
/* PADDING */
.CodeMirror-lines {
min-height: 1px; /* prevents collapsing before first draw */
padding: 4px 0; /* Vertical padding around content */
cursor: text;
}
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
position: absolute;
top: 0;
left: 0;
z-index: 3;
min-height: 100%;
white-space: nowrap;
background-color: transparent;
border-right: 1px solid #ddd;
}
.CodeMirror-linenumber {
min-width: 20px;
padding: 0 3px 0 5px;
color: var(--comment);
text-align: right;
white-space: nowrap;
opacity: 60%;
}
.CodeMirror-guttermarker {
color: black;
}
.CodeMirror-guttermarker-subtle {
color: #999;
}
/* FOLD GUTTER */
.CodeMirror-foldmarker {
font-family: arial;
line-height: 0.3;
color: #414141;
text-shadow: #f96 1px 1px 2px, #f96 -1px -1px 2px, #f96 1px -1px 2px, #f96 -1px 1px 2px;
cursor: pointer;
}
.CodeMirror-foldgutter {
width: 0.7em;
}
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded {
cursor: pointer;
}
.CodeMirror-foldgutter-open::after,
.CodeMirror-foldgutter-folded::after {
position: relative;
top: -0.1em;
display: inline-block;
font-size: 0.8em;
content: '>';
opacity: 80%;
transform: rotate(90deg);
transition: transform 0.2s;
}
.CodeMirror-foldgutter-folded::after {
transform: none;
}
/* CURSOR */
.CodeMirror-cursor {
position: absolute;
width: 0;
pointer-events: none;
border-right: none;
border-left: 1px solid black;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
background: #7e7;
border: 0 !important;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgb(20 255 20 / 50%);
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
background-color: #7e7;
border: 0;
animation: blink 1.06s steps(1) infinite;
}
@keyframes blink {
50% {
background-color: transparent;
}
}
@keyframes blink {
50% {
background-color: transparent;
}
}
@keyframes blink {
50% {
background-color: transparent;
}
}
.cm-tab {
display: inline-block;
text-decoration: inherit;
}
.CodeMirror-rulers {
position: absolute;
top: -50px;
right: 0;
bottom: -20px;
left: 0;
overflow: hidden;
}
.CodeMirror-ruler {
position: absolute;
top: 0;
bottom: 0;
border-left: 1px solid #ccc;
}
/* DEFAULT THEME */
.cm-s-default.CodeMirror {
background-color: transparent;
}
.cm-s-default .cm-header {
color: blue;
}
.cm-s-default .cm-quote {
color: #090;
}
.cm-negative {
color: #d44;
}
.cm-positive {
color: #292;
}
.cm-header,
.cm-strong {
font-weight: bold;
}
.cm-em {
font-style: italic;
}
.cm-link {
text-decoration: underline;
}
.cm-strikethrough {
text-decoration: line-through;
}
.cm-s-default .cm-atom,
.cm-s-default .cm-def,
.cm-s-default .cm-property,
.cm-s-default .cm-variable-2,
.cm-s-default .cm-variable-3,
.cm-s-default .cm-punctuation {
color: var(--base);
}
.cm-s-default .cm-hr,
.cm-s-default .cm-comment {
color: var(--comment);
}
.cm-s-default .cm-attribute,
.cm-s-default .cm-keyword {
color: var(--keyword);
}
.cm-s-default .cm-variable {
color: var(--variable);
}
.cm-s-default .cm-bracket,
.cm-s-default .cm-tag {
color: var(--tags);
}
.cm-s-default .cm-number {
color: var(--number);
}
.cm-s-default .cm-string,
.cm-s-default .cm-string-2 {
color: var(--string);
}
.cm-s-default .cm-type {
color: #085;
}
.cm-s-default .cm-meta {
color: #555;
}
.cm-s-default .cm-qualifier {
color: var(--qualifier);
}
.cm-s-default .cm-builtin {
color: #7539ff;
}
.cm-s-default .cm-link {
color: var(--flash);
}
.cm-s-default .cm-error {
color: #ff008c;
}
.cm-invalidchar {
color: #ff008c;
}
.CodeMirror-composing {
border-bottom: 2px solid;
}
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {
color: #0b0;
}
div.CodeMirror span.CodeMirror-nonmatchingbracket {
color: #a22;
}
.CodeMirror-matchingtag {
background: rgb(255 150 0 / 30%);
}
.CodeMirror-activeline-background {
background: #e8f2ff;
}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror-scroll {
position: relative;
height: 100%;
padding-bottom: 30px;
margin-right: -30px;
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px;
overflow: scroll !important; /* Things will break if this is overridden */
outline: none; /* Prevent dragging from highlighting the element */
}
.CodeMirror-sizer {
position: relative;
margin-bottom: 20px !important;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar,
.CodeMirror-hscrollbar,
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
top: 0;
right: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0;
left: 0;
overflow-x: scroll;
overflow-y: hidden;
}
.CodeMirror-scrollbar-filler {
right: 0;
bottom: 0;
}
.CodeMirror-gutter-filler {
bottom: 0;
left: 0;
}
.CodeMirror-gutter {
display: inline-block;
height: 100%;
margin-bottom: -30px;
white-space: normal;
vertical-align: top;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0;
bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
z-index: 4;
cursor: default;
}
.CodeMirror-gutter-wrapper ::selection {
background-color: transparent;
}
.CodeMirrorwrapper ::selection {
background-color: transparent;
}
.CodeMirror pre {
position: relative;
z-index: 2;
padding: 0 4px; /* Horizontal padding of content */
margin: 0;
overflow: visible;
font-family: inherit;
font-size: inherit;
line-height: inherit;
color: inherit;
word-wrap: normal;
white-space: pre;
background: transparent;
border-width: 0;
/* Reset some styles that the rest of the page might have set */
border-radius: 0;
-webkit-tap-highlight-color: transparent;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre {
word-break: normal;
word-wrap: break-word;
white-space: pre-wrap;
}
.CodeMirror-linebackground {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-rtl pre {
direction: rtl;
}
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-measure pre {
position: static;
}
div.CodeMirror-cursors {
position: relative;
z-index: 3;
visibility: hidden;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected {
background: #d9d9d9;
}
.CodeMirror-focused .CodeMirror-selected {
background: #d7d4f0;
}
.CodeMirror-crosshair {
cursor: crosshair;
}
.CodeMirror-line::selection,
.CodeMirror-line > span::selection,
.CodeMirror-line > span > span::selection {
background: #d7d4f0;
}
.cm-searching {
background-color: #ffa;
background-color: rgb(255 255 0 / 40%);
}
/* Used to force a border model for a node */
.cm-force-border {
padding-right: 0.1px;
}
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack::after {
content: '';
}
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext {
background: none;
}

View File

@@ -1,12 +0,0 @@
<template>
<vue-json-pretty :path="'res'" :deep="3" :showLength="true" :data="data" />
</template>
<script lang="ts" setup>
import VueJsonPretty from 'vue-json-pretty';
import 'vue-json-pretty/lib/styles.css';
defineProps({
data: Object,
});
</script>

View File

@@ -1,5 +0,0 @@
export enum MODE {
JSON = 'application/json',
HTML = 'htmlmixed',
JS = 'javascript',
}

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