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

This commit is contained in:
vben 2021-07-04 22:28:10 +08:00
commit f3831fd2e9
57 changed files with 1531 additions and 893 deletions

1
.husky/.gitignore vendored
View File

@ -1 +0,0 @@
_

View File

@ -1,3 +1,48 @@
## 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

View File

@ -1,3 +1,53 @@
# [2.6.0](https://github.com/anncwb/vue-vben-admin/compare/v2.5.2...v2.6.0) (2021-07-04)
### Bug Fixes
- **axios:** option `withToken` not work ([d509e89](https://github.com/anncwb/vue-vben-admin/commit/d509e897be5753c852e912112e70dac6247ba467))
- **demo:** account list fetch loss param ([424b171](https://github.com/anncwb/vue-vben-admin/commit/424b171e0db727f5e0157cbcfd5460f15f8ea609)), closes [#830](https://github.com/anncwb/vue-vben-admin/issues/830)
- **demo:** fix async tree demo, fixed: [#823](https://github.com/anncwb/vue-vben-admin/issues/823) ([5637588](https://github.com/anncwb/vue-vben-admin/commit/5637588fce880b01137191cc82c73e0fce621e8c))
- **form:** fix some prop declaration ([b5046f0](https://github.com/anncwb/vue-vben-admin/commit/b5046f07a27e8ca7fc8b961b74fa5e1b0d715149))
- **lock-screen:** ensure lock info is saved ([d38ff66](https://github.com/anncwb/vue-vben-admin/commit/d38ff6670a37478b31447f8058e786c4b044e218))
- **lock-screen:** fix lock-screen can skip on new window ([d7b84c7](https://github.com/anncwb/vue-vben-admin/commit/d7b84c78744f7d0077a779b232e1358040b50383))
- **menu:** make sure the menu is activated correctly ([cdb10cc](https://github.com/anncwb/vue-vben-admin/commit/cdb10cc4ac5e5e8f9cce3ff18d8fbd29ef10c86f))
- **modal:** `setModalProps` support `defaultFullscreen` ([c7de65e](https://github.com/anncwb/vue-vben-admin/commit/c7de65ebba53941771153f18b184d3d4d31c0dbf))
- **modal:** maskClosable not work ([f750ff4](https://github.com/anncwb/vue-vben-admin/commit/f750ff435fee06acee78d6b9633e6e18d91685f8))
- **modal:** remove console log ([3dbbde2](https://github.com/anncwb/vue-vben-admin/commit/3dbbde2662352780377a9b216598d9348522f6ba))
- **popconfirm-button:** remove button excess `title` ([73654b7](https://github.com/anncwb/vue-vben-admin/commit/73654b7862c59d623d6d5dc7dcf6ff2704564d9a))
- **sider:** bottom trigger not work ([1bde404](https://github.com/anncwb/vue-vben-admin/commit/1bde4041211229d5d9d01ce0ca806fa99356b6de)), closes [#820](https://github.com/anncwb/vue-vben-admin/issues/820)
- **sider:** custom trigger does not take effect ([5005e6e](https://github.com/anncwb/vue-vben-admin/commit/5005e6e56b1cc7763a1cc23e1162dfb49452013b))
- **svg-icon:** fix SvgIcon style ([99829c7](https://github.com/anncwb/vue-vben-admin/commit/99829c79ab41a2319f40c5595a7d82d9e406ba18))
- **table:** auto hide unnecessary scrollbar ([735028c](https://github.com/anncwb/vue-vben-admin/commit/735028c43055e8e80ebc7344af0cd0f51c744f98))
- **table:** global configuration accidentally modified ([b4a3f93](https://github.com/anncwb/vue-vben-admin/commit/b4a3f936cd19bf1fff3a331bacad60e79d2d6c22))
- **table:** param of `handleSearchInfoFn` ([791b323](https://github.com/anncwb/vue-vben-admin/commit/791b323dbd30acd7fabfe9c3fb6e528916311ffd))
- **tailwindcss:** remove console warnings ([acacb32](https://github.com/anncwb/vue-vben-admin/commit/acacb32bb592345cd0a90b4bbeb60a9b6ab1ac3c))
- `hasPermission` not work in `ROLE` Mode ([76a5f87](https://github.com/anncwb/vue-vben-admin/commit/76a5f87c0ce871cca48b9e4c32331353a796e7d2))
- routes filter can't effective when permission mode set to ROUTE_MAPPING ([#836](https://github.com/anncwb/vue-vben-admin/issues/836)) ([3871204](https://github.com/anncwb/vue-vben-admin/commit/3871204d08d481b8984440cd60bbf2bacb58d063))
- **table:** selection-change not triggered on row click ([6f845b5](https://github.com/anncwb/vue-vben-admin/commit/6f845b53bdc4c33fbca3e65f10f64c63166bed0e))
- multi windows token sharing ([e5f3788](https://github.com/anncwb/vue-vben-admin/commit/e5f37885ffb32d04d244f0ef39ac660dda6b71e1)), closes [#761](https://github.com/anncwb/vue-vben-admin/issues/761)
- support various vite modes of build, not just production ([#832](https://github.com/anncwb/vue-vben-admin/issues/832)) ([95c16a5](https://github.com/anncwb/vue-vben-admin/commit/95c16a5d26f9fd9a1d11894afe1146ca495eee93))
- **table:** editComponentProps support onChange ([829b366](https://github.com/anncwb/vue-vben-admin/commit/829b366cb2abf27e69d9665e5be022b3d3f15655))
- **table:** fix rowSelection.onChange not work ([df0f000](https://github.com/anncwb/vue-vben-admin/commit/df0f00085c1113eddd7a15954818ccece3538068)), closes [#825](https://github.com/anncwb/vue-vben-admin/issues/825)
### Features
- **avatar-cropper:** add action tooltip ([6cbac4b](https://github.com/anncwb/vue-vben-admin/commit/6cbac4b7ece60a1a7c1fda931cfffce42dfe3e51))
- **avatar-cropper:** more props added ([b96ea07](https://github.com/anncwb/vue-vben-admin/commit/b96ea0753bfd769693a368cf1e3d8316688c0dcb))
- **axios:** add `withToken` option ([c99cf5e](https://github.com/anncwb/vue-vben-admin/commit/c99cf5e53f057cdc332ab6c0635adf9c2d27de29))
- **axios:** use `defHttp` like `axios` ([49f39de](https://github.com/anncwb/vue-vben-admin/commit/49f39de7b40e3ec8343bdeaf3eb00fd79d395746)), closes [#850](https://github.com/anncwb/vue-vben-admin/issues/850)
- **basic-upload:** `value` support v-model ([16c5d32](https://github.com/anncwb/vue-vben-admin/commit/16c5d327f1209f7c7437acde2ab0fa031da6a641))
- **basic-upload:** add preview-delete event ([49e72a8](https://github.com/anncwb/vue-vben-admin/commit/49e72a8e76b78fe54e19de9e23d7c72a19427f01)), closes [#835](https://github.com/anncwb/vue-vben-admin/issues/835)
- **modal:** add `tooltip` for action buttons ([c3b9076](https://github.com/anncwb/vue-vben-admin/commit/c3b907656a5fad7a9b241562179f7a0f6fe0e6f0))
- **param-menu:** feature: menu with params ([#845](https://github.com/anncwb/vue-vben-admin/issues/845)) ([48fcd76](https://github.com/anncwb/vue-vben-admin/commit/48fcd7684cabff66e8648b71527c6cb4ce7d03be))
- **route:** add `hidePathForChildren` in `meta` ([d52b0de](https://github.com/anncwb/vue-vben-admin/commit/d52b0de83e69f7505c28e6f59ec84bbe526ecd0d))
- **table:** support asynchrony in beforeFetch and afterFetch ([#827](https://github.com/anncwb/vue-vben-admin/issues/827)) ([749ba5c](https://github.com/anncwb/vue-vben-admin/commit/749ba5c1daf459625518937c239787b756c0a780))
- **table-action:** support `tooltip` option ([5fab267](https://github.com/anncwb/vue-vben-admin/commit/5fab267a69600fdf5d7a7f9e4d9fff859d09dede)), closes [#848](https://github.com/anncwb/vue-vben-admin/issues/848)
- **tree:** add `insertNodesByKey` method ([5a20df4](https://github.com/anncwb/vue-vben-admin/commit/5a20df45ad36b523d48bf7fe11bdb10a6d03df64))
- routers support `ignoreRoute` option ([72ac240](https://github.com/anncwb/vue-vben-admin/commit/72ac240f2858cd74cb62b7647ca89d63bb71d247))
### Performance Improvements
- **scrollbar:** scrollbar update when slot changed ([e9e51b2](https://github.com/anncwb/vue-vben-admin/commit/e9e51b2fdc879a66d8df08504a0955c9c21e3e27))
## [2.5.1](https://github.com/anncwb/vue-vben-admin/compare/v2.4.0...v2.5.1) (2021-06-26)
### Bug Fixes

View File

@ -1,3 +1,48 @@
## 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

View File

@ -39,22 +39,37 @@ export function wrapperEnv(envConf: Recordable): ViteEnv {
return ret;
}
/**
*
*/
function getConfFiles() {
const script = process.env.npm_lifecycle_script;
const reg = new RegExp('--mode ([a-z]+) ');
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 = ['.env', '.env.production']) {
export function getEnvConfig(match = 'VITE_GLOB_', confFiles = getConfFiles()) {
let envConfig = {};
confFiles.forEach((item) => {
try {
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
envConfig = { ...envConfig, ...env };
} catch (error) {}
} catch (e) {
console.error(`Error in parsing ${item}`, e);
}
});
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

@ -95,4 +95,18 @@ export default [
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.5.2",
"version": "2.6.0",
"author": {
"name": "vben",
"email": "anncwb@126.com",
@ -10,7 +10,7 @@
"bootstrap": "yarn install",
"serve": "npm run dev",
"dev": "vite",
"build": "vite build && esno ./build/script/postBuild.ts",
"build": "cross-env NODE_ENV=production 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",
@ -29,15 +29,16 @@
"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",
"install:husky": "is-ci || husky install",
"gen:icon": "esno ./build/generate/icon/index.ts",
"postinstall": "npm run install:husky"
"prepare": "husky install",
"gen:icon": "esno ./build/generate/icon/index.ts"
},
"dependencies": {
"@iconify/iconify": "^2.0.2",
"@iconify/iconify": "^2.0.3",
"@logicflow/core": "^0.5.0",
"@logicflow/extension": "^0.5.0",
"@vueuse/core": "^5.0.3",
"@zxcvbn-ts/core": "^1.0.0-beta.0",
"ant-design-vue": "2.2.0-beta.6",
"ant-design-vue": "2.2.0-rc.1",
"axios": "^0.21.1",
"crypto-js": "^4.0.0",
"echarts": "^5.1.2",
@ -47,17 +48,17 @@
"path-to-regexp": "^6.2.0",
"pinia": "^2.0.0-beta.3",
"qrcode": "^1.4.4",
"resize-observer-polyfill": "^1.5.1",
"sortablejs": "^1.13.0",
"vue": "3.1.2",
"vue": "3.1.4",
"vue-i18n": "9.1.6",
"vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.10",
"vue-types": "^4.0.0"
},
"devDependencies": {
"@commitlint/cli": "^12.1.4",
"@commitlint/config-conventional": "^12.1.4",
"@iconify/json": "^1.1.361",
"@iconify/json": "^1.1.369",
"@purge-icons/generated": "^0.7.0",
"@types/codemirror": "^5.60.1",
"@types/crypto-js": "^4.0.1",
@ -67,36 +68,36 @@
"@types/jest": "^26.0.23",
"@types/lodash-es": "^4.17.4",
"@types/mockjs": "^1.0.3",
"@types/node": "^15.12.5",
"@types/node": "^16.0.0",
"@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.0",
"@types/qs": "^6.9.6",
"@types/sortablejs": "^1.10.6",
"@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.28.0",
"@vitejs/plugin-legacy": "^1.4.2",
"@vitejs/plugin-vue": "^1.2.3",
"@vitejs/plugin-vue-jsx": "^1.1.5",
"@vue/compiler-sfc": "3.1.2",
"@typescript-eslint/eslint-plugin": "^4.28.1",
"@typescript-eslint/parser": "^4.28.1",
"@vitejs/plugin-legacy": "^1.4.3",
"@vitejs/plugin-vue": "^1.2.4",
"@vitejs/plugin-vue-jsx": "^1.1.6",
"@vue/compiler-sfc": "3.1.4",
"@vue/test-utils": "^2.0.0-rc.9",
"autoprefixer": "^10.2.6",
"commitizen": "^4.2.4",
"conventional-changelog-cli": "^2.1.1",
"cross-env": "^7.0.3",
"dotenv": "^10.0.0",
"eslint": "^7.29.0",
"eslint": "^7.30.0",
"eslint-config-prettier": "^8.3.0",
"eslint-define-config": "^1.0.8",
"eslint-define-config": "^1.0.9",
"eslint-plugin-jest": "^24.3.6",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.12.1",
"esno": "^0.7.3",
"fs-extra": "^10.0.0",
"http-server": "^0.12.3",
"husky": "^6.0.0",
"husky": "^7.0.0",
"inquirer": "^8.1.1",
"is-ci": "^3.0.0",
"jest": "^27.0.5",
"jest": "^27.0.6",
"less": "^4.1.1",
"lint-staged": "^11.0.0",
"npm-run-all": "^4.1.5",
@ -104,7 +105,7 @@
"prettier": "^2.3.2",
"pretty-quick": "^3.1.1",
"rimraf": "^3.0.2",
"rollup-plugin-visualizer": "5.5.0",
"rollup-plugin-visualizer": "5.5.1",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^22.0.0",
@ -112,8 +113,8 @@
"tailwindcss": "^2.2.4",
"ts-jest": "^27.0.3",
"ts-node": "^10.0.0",
"typescript": "4.3.4",
"vite": "2.3.8",
"typescript": "4.3.5",
"vite": "2.4.0-beta.2",
"vite-plugin-compression": "^0.2.5",
"vite-plugin-html": "^2.0.7",
"vite-plugin-imagemin": "^0.3.2",
@ -123,13 +124,13 @@
"vite-plugin-style-import": "^1.0.1",
"vite-plugin-svg-icons": "^1.0.0",
"vite-plugin-theme": "^0.8.1",
"vue-eslint-parser": "^7.6.0",
"vue-eslint-parser": "^7.7.2",
"vue-tsc": "^0.2.0"
},
"resolutions": {
"//": "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.52.3"
"rollup": "^2.52.7"
},
"repository": {
"type": "git",

View File

@ -5,6 +5,7 @@ import { ErrorMessageMode } from '/#/axios';
enum Api {
Login = '/login',
Logout = '/logout',
GetUserInfo = '/getUserInfo',
GetPermCode = '/getPermCode',
}
@ -34,3 +35,7 @@ export function getUserInfo() {
export function getPermCode() {
return defHttp.get<string[]>({ url: Api.GetPermCode });
}
export function doLogout() {
return defHttp.get({ url: Api.Logout });
}

View File

@ -1,6 +1,9 @@
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';
export const Button = withInstall(button);
export const PopConfirmButton = withInstall(popConfirmButton);
export declare type ButtonProps = Partial<ExtractPropTypes<typeof buttonProps>>;

View File

@ -10,33 +10,14 @@
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { Button } from 'ant-design-vue';
import { Icon } from '/@/components/Icon';
const props = {
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 },
};
import Icon from '/@/components/Icon/src/Icon.vue';
import { buttonProps } from './props';
export default defineComponent({
name: 'AButton',
components: { Button, Icon },
inheritAttrs: false,
props,
props: buttonProps,
setup(props, { attrs }) {
// get component class
const getButtonClass = computed(() => {

View File

@ -41,7 +41,7 @@
return () => {
const bindValues = omit(unref(getBindValues), 'icon');
const Button = h(BasicButton, bindValues, extendSlots(slots));
const Button = h(BasicButton, omit(bindValues, 'title'), extendSlots(slots));
// If it is not enabled, it is a normal button
if (!props.enable) {

View File

@ -0,0 +1,19 @@
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

@ -23,57 +23,80 @@
<div :class="`${prefixCls}-toolbar`">
<Upload :fileList="[]" accept="image/*" :beforeUpload="handleBeforeUpload">
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
<Tooltip :title="t('component.cropper.selectImage')" placement="bottom">
<a-button size="small" preIcon="ant-design:upload-outlined" type="primary" />
</Tooltip>
</Upload>
<Space>
<a-button
type="primary"
preIcon="ant-design:reload-outlined"
size="small"
@click="handlerToolbar('reset')"
/>
<a-button
type="primary"
preIcon="ant-design:rotate-left-outlined"
size="small"
@click="handlerToolbar('rotate', -45)"
/>
<a-button
type="primary"
preIcon="ant-design:rotate-right-outlined"
size="small"
@click="handlerToolbar('rotate', 45)"
/>
<a-button
type="primary"
preIcon="vaadin:arrows-long-h"
size="small"
@click="handlerToolbar('scaleX')"
/>
<a-button
type="primary"
preIcon="vaadin:arrows-long-v"
size="small"
@click="handlerToolbar('scaleY')"
/>
<a-button
type="primary"
preIcon="ant-design:zoom-in-outlined"
size="small"
@click="handlerToolbar('zoom', 0.1)"
/>
<a-button
type="primary"
preIcon="ant-design:zoom-out-outlined"
size="small"
@click="handlerToolbar('zoom', -0.1)"
/>
<Tooltip :title="t('component.cropper.btn_reset')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:reload-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('reset')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_left')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:rotate-left-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('rotate', -45)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_rotate_right')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:rotate-right-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('rotate', 45)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_x')" placement="bottom">
<a-button
type="primary"
preIcon="vaadin:arrows-long-h"
size="small"
:disabled="!src"
@click="handlerToolbar('scaleX')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_scale_y')" placement="bottom">
<a-button
type="primary"
preIcon="vaadin:arrows-long-v"
size="small"
:disabled="!src"
@click="handlerToolbar('scaleY')"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_in')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:zoom-in-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('zoom', 0.1)"
/>
</Tooltip>
<Tooltip :title="t('component.cropper.btn_zoom_out')" placement="bottom">
<a-button
type="primary"
preIcon="ant-design:zoom-out-outlined"
size="small"
:disabled="!src"
@click="handlerToolbar('zoom', -0.1)"
/>
</Tooltip>
</Space>
</div>
</div>
<div :class="`${prefixCls}-right`">
<div :class="`${prefixCls}-preview`">
<img :src="previewSource" v-if="previewSource" />
<img :src="previewSource" v-if="previewSource" :alt="t('component.cropper.preview')" />
</div>
<template v-if="previewSource">
<div :class="`${prefixCls}-group`">
@ -92,7 +115,7 @@
import { defineComponent, ref } from 'vue';
import CropperImage from './Cropper.vue';
import { Space, Upload, Avatar } from 'ant-design-vue';
import { Space, Upload, Avatar, Tooltip } from 'ant-design-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { dataURLtoBlob } from '/@/utils/file/base64Conver';
@ -102,13 +125,13 @@
const props = {
circled: { type: Boolean, default: true },
uploadApi: {
type: Function as PropType<({ file: Blob, name: stirng, filename: string }) => Promise<any>>,
type: Function as PropType<({ file: Blob, name: string, filename: string }) => Promise<any>>,
},
};
export default defineComponent({
name: 'CropperAvatar',
components: { BasicModal, Space, CropperImage, Upload, Avatar },
components: { BasicModal, Space, CropperImage, Upload, Avatar, Tooltip },
props,
emits: ['uploadSuccess', 'register'],
setup(props, { emit }) {

View File

@ -1,35 +1,70 @@
<template>
<div :class="getClass" :style="getStyle">
<div :class="`${prefixCls}-image-wrapper`" :style="getImageWrapperStyle" @click="openModal">
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
<Icon
icon="ant-design:cloud-upload-outlined"
:size="getIconWidth"
:style="getImageWrapperStyle"
color="#d6d6d6"
/>
</div>
<img :src="sourceValue" v-if="sourceValue" alt="avatar" />
</div>
<a-button :class="`${prefixCls}-upload-btn`" @click="openModal">
{{ t('component.cropper.selectImage') }}
<a-button
:class="`${prefixCls}-upload-btn`"
@click="openModal"
v-if="showBtn"
v-bind="btnProps"
>
{{ btnText ? btnText : t('component.cropper.selectImage') }}
</a-button>
<CopperModal @register="register" @uploadSuccess="handleUploadSuccess" :uploadApi="uploadApi" />
<CopperModal
@register="register"
@uploadSuccess="handleUploadSuccess"
:uploadApi="uploadApi"
:src="sourceValue"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, CSSProperties, unref, ref } from 'vue';
import {
defineComponent,
computed,
CSSProperties,
unref,
ref,
watchEffect,
watch,
PropType,
} from 'vue';
import CopperModal from './CopperModal.vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useI18n } from '/@/hooks/web/useI18n';
import type { ButtonProps } from '/@/components/Button';
import Icon from '/@/components/Icon';
const props = {
width: { type: [String, Number], default: '200px' },
value: { type: String },
showBtn: { type: Boolean, default: true },
btnProps: { type: Object as PropType<ButtonProps> },
btnText: { type: String, default: '' },
uploadApi: { type: Function as PropType<({ file: Blob, name: string }) => Promise<void>> },
};
export default defineComponent({
name: 'CropperAvatar',
components: { CopperModal },
components: { CopperModal, Icon },
props,
setup(props) {
const sourceValue = ref('');
emits: ['update:value', 'change'],
setup(props, { emit, expose }) {
const sourceValue = ref(props.value || '');
const { prefixCls } = useDesign('cropper-avatar');
const [register, { openModal }] = useModal();
const [register, { openModal, closeModal }] = useModal();
const { createMessage } = useMessage();
const { t } = useI18n();
@ -37,22 +72,39 @@
const getWidth = computed(() => `${props.width}`.replace(/px/, '') + 'px');
const getIconWidth = computed(() => parseInt(`${props.width}`.replace(/px/, '')) / 2 + 'px');
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
const getImageWrapperStyle = computed(
(): CSSProperties => ({ width: unref(getWidth), height: unref(getWidth) })
);
watchEffect(() => {
sourceValue.value = props.value || '';
});
watch(
() => sourceValue.value,
(v: string) => {
emit('update:value', v);
}
);
function handleUploadSuccess({ source }) {
sourceValue.value = source;
emit('change', source);
createMessage.success(t('component.cropper.uploadSuccess'));
}
expose({ openModal: openModal.bind(null, true), closeModal });
return {
t,
prefixCls,
register,
openModal,
getIconWidth,
sourceValue,
getClass,
getImageWrapperStyle,
@ -82,6 +134,27 @@
}
}
&-image-mask {
opacity: 0;
position: absolute;
width: inherit;
height: inherit;
border-radius: inherit;
border: inherit;
background: rgba(0, 0, 0, 0.4);
cursor: pointer;
-webkit-transition: opacity 0.4s;
transition: opacity 0.4s;
::v-deep(svg) {
margin: auto;
}
}
&-image-mask:hover {
opacity: 40;
}
&-upload-btn {
margin: 10px auto;
}

View File

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

View File

@ -26,7 +26,7 @@ export function useFormValues({
for (const item of Object.entries(values)) {
let [, value] = item;
const [key] = item;
if ((isArray(value) && value.length === 0) || isFunction(value)) {
if (!key || (isArray(value) && value.length === 0) || isFunction(value)) {
continue;
}
const transformDateFunc = unref(getProps).transformDateFunc;

View File

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

View File

@ -1,5 +1,11 @@
<template>
<SvgIcon :size="size" :name="getSvgIcon" v-if="isSvgIcon" :class="[$attrs.class]" :spin="spin" />
<SvgIcon
:size="size"
:name="getSvgIcon"
v-if="isSvgIcon"
:class="[$attrs.class, 'anticon']"
:spin="spin"
/>
<span
v-else
ref="elRef"
@ -19,7 +25,6 @@
computed,
CSSProperties,
} from 'vue';
import SvgIcon from './SvgIcon.vue';
import Iconify from '@purge-icons/generated';
import { isString } from '/@/utils/is';
@ -27,7 +32,7 @@
const SVG_END_WITH_FLAG = '|svg';
export default defineComponent({
name: 'GIcon',
name: 'Icon',
components: { SvgIcon },
props: {
// icon name

View File

@ -1,5 +1,5 @@
<template>
<Modal v-bind="getBindValue">
<Modal v-bind="getBindValue" @cancel="handleCancel">
<template #closeIcon v-if="!$slots.closeIcon">
<ModalClose
:canFullscreen="getProps.canFullscreen"
@ -187,8 +187,12 @@
function setModalProps(props: Partial<ModalProps>): void {
// Keep the last setModalProps
propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
if (!Reflect.has(props, 'visible')) return;
visibleRef.value = !!props.visible;
if (Reflect.has(props, 'visible')) {
visibleRef.value = !!props.visible;
}
if (Reflect.has(props, 'defaultFullscreen')) {
fullScreenRef.value = !!props.defaultFullscreen;
}
}
function handleOk(e: Event) {

View File

@ -1,20 +1,28 @@
<template>
<div :class="getClass">
<template v-if="canFullscreen">
<FullscreenExitOutlined role="full" @click="handleFullScreen" v-if="fullScreen" />
<FullscreenOutlined role="close" @click="handleFullScreen" v-else />
<Tooltip :title="t('component.modal.restore')" placement="bottom" v-if="fullScreen">
<FullscreenExitOutlined role="full" @click="handleFullScreen" />
</Tooltip>
<Tooltip :title="t('component.modal.maximize')" placement="bottom" v-else>
<FullscreenOutlined role="close" @click="handleFullScreen" />
</Tooltip>
</template>
<CloseOutlined @click="handleCancel" />
<Tooltip :title="t('component.modal.close')" placement="bottom">
<CloseOutlined @click="handleCancel" />
</Tooltip>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue';
import { useDesign } from '/@/hooks/web/useDesign';
import { Tooltip } from 'ant-design-vue';
import { useI18n } from '/@/hooks/web/useI18n';
export default defineComponent({
name: 'ModalClose',
components: { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined },
components: { Tooltip, FullscreenExitOutlined, FullscreenOutlined, CloseOutlined },
props: {
canFullscreen: { type: Boolean, default: true },
fullScreen: { type: Boolean },
@ -22,6 +30,7 @@
emits: ['cancel', 'fullscreen'],
setup(props, { emit }) {
const { prefixCls } = useDesign('basic-modal-close');
const { t } = useI18n();
const getClass = computed(() => {
return [
@ -44,6 +53,7 @@
}
return {
t,
getClass,
prefixCls,
handleCancel,

View File

@ -39,6 +39,7 @@ export interface ModalProps {
// 是否可以进行全屏
canFullscreen?: boolean;
defaultFullscreen?: boolean;
visible?: boolean;
// 温馨提醒信息
helpMessage: string | string[];

View File

@ -42,9 +42,7 @@
import { propTypes } from '/@/utils/propTypes';
import { omit } from 'lodash-es';
import { PageHeader } from 'ant-design-vue';
import { useLayoutHeight } from '/@/layouts/default/content/useContentViewHeight';
import { useContentHeight } from './useContentHeight';
import { WrapperProps } from './types';
import { useContentHeight } from '/@/hooks/web/useContentHeight';
export default defineComponent({
name: 'PageWrapper',
@ -64,25 +62,23 @@
fixedHeight: propTypes.bool,
},
setup(props, { slots }) {
const wrapperRef = ref<ElRef>(null);
const headerRef = ref<ComponentRef>(null);
const contentRef = ref<ElRef>(null);
const footerRef = ref<ComponentRef>(null);
const wrapperRef = ref(null);
const headerRef = ref(null);
const contentRef = ref(null);
const footerRef = ref(null);
const { prefixCls } = useDesign('page-wrapper');
const { footerHeightRef } = useLayoutHeight();
const getProps = computed(() => {
return props as WrapperProps;
const getIsContentFullHeight = computed(() => {
return props.contentFullHeight;
});
const { redoHeight, contentHeight } = useContentHeight(
getProps,
const { redoHeight, setCompensation, contentHeight } = useContentHeight(
getIsContentFullHeight,
wrapperRef,
headerRef,
contentRef,
footerRef,
footerHeightRef
[headerRef, footerRef],
[contentRef]
);
setCompensation({ useLayoutFooter: true, elements: [footerRef] });
const getClass = computed(() => {
return [
@ -125,7 +121,7 @@
});
watch(
() => [getShowFooter.value, footerHeightRef.value],
() => [getShowFooter.value],
() => {
redoHeight();
},

View File

@ -1,13 +0,0 @@
import { CSSProperties } from 'vue';
export interface WrapperProps {
title?: string;
dense: boolean;
ghost: boolean;
content: string;
contentStyle?: CSSProperties;
contentBackground: boolean;
contentFullHeight: boolean;
contentClass?: string;
fixedHeight: boolean;
}

View File

@ -1,93 +0,0 @@
import { ComputedRef, nextTick, Ref, ref, unref } from 'vue';
import { WrapperProps } from './types';
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { getViewportOffset } from '/@/utils/domUtils';
export function useContentHeight(
propsRef: ComputedRef<WrapperProps>,
wrapperRef: Ref<ElRef>,
headerRef?: Ref<ComponentRef>,
contentRef?: Ref<ElRef>,
footerRef?: Ref<ComponentRef>,
layoutFooterHeightRef: Ref<number> = ref(0),
offsetHeightRef: Ref<number> = ref(0)
) {
const contentHeight: Ref<Nullable<number>> = ref(null);
const redoHeight = () => {
nextTick(() => {
calcContentHeight();
});
};
const subtractMargin = (element: HTMLElement | null | undefined): number => {
let subtractHeight = 0;
const ZERO_PX = '0px';
let marginBottom = ZERO_PX;
let marginTop = ZERO_PX;
if (element) {
const cssStyle = getComputedStyle(element);
marginBottom = cssStyle?.marginBottom ?? ZERO_PX;
marginTop = cssStyle?.marginTop ?? ZERO_PX;
}
if (marginBottom) {
const contentMarginBottom = Number(marginBottom.replace(/[^\d]/g, ''));
subtractHeight += contentMarginBottom;
}
if (marginTop) {
const contentMarginTop = Number(marginTop.replace(/[^\d]/g, ''));
subtractHeight += contentMarginTop;
}
return subtractHeight;
};
const calcContentHeight = async () => {
const { contentFullHeight } = unref(propsRef);
if (!contentFullHeight) {
return;
}
// Add a delay to get the correct height
await nextTick();
const wrapperEl = unref(wrapperRef);
if (!wrapperEl) {
return;
}
const { bottomIncludeBody } = getViewportOffset(wrapperEl);
const headerHeight = unref(headerRef)?.$el.offsetHeight ?? 0;
const footerHeight = unref(footerRef)?.$el.offsetHeight ?? 0;
// content's subtract
const substractHeight = subtractMargin(unref(contentRef));
let height =
bottomIncludeBody -
unref(layoutFooterHeightRef) -
unref(offsetHeightRef) -
headerHeight -
footerHeight -
substractHeight;
// fix: compensation height both layout's footer and page's footer was shown
if (unref(layoutFooterHeightRef) > 0 && footerHeight > 0) {
height += footerHeight;
}
contentHeight.value = height;
};
onMountedOrActivated(() => {
nextTick(() => {
calcContentHeight();
});
});
useWindowSizeFn(
() => {
calcContentHeight();
},
50,
{ immediate: true }
);
return { redoHeight, contentHeight };
}

View File

@ -21,10 +21,8 @@
import type { MenuState } from './types';
import type { Menu as MenuType } from '/@/router/types';
import type { RouteLocationNormalizedLoaded } from 'vue-router';
import { defineComponent, computed, ref, unref, reactive, toRefs, watch } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import Menu from './components/Menu.vue';
import SimpleSubMenu from './SimpleSubMenu.vue';
import { listenerRouteChange } from '/@/logics/mitt/routeChange';
@ -123,6 +121,7 @@
return;
}
const path = (route || unref(currentRoute)).path;
menuState.activeName = path;
setOpenKeys(path);

View File

@ -1,10 +1,12 @@
<template>
<div :class="[prefixCls, getAlign]" @click="onCellClick">
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
<PopConfirmButton v-bind="action">
<Icon :icon="action.icon" class="mr-1" v-if="action.icon" />
{{ action.label }}
</PopConfirmButton>
<Tooltip v-bind="getTooltip(action.tooltip)">
<PopConfirmButton v-bind="action">
<Icon :icon="action.icon" class="mr-1" v-if="action.icon" />
{{ action.label }}
</PopConfirmButton>
</Tooltip>
<Divider
type="vertical"
class="action-divider"
@ -31,7 +33,7 @@
<script lang="ts">
import { defineComponent, PropType, computed, toRaw } from 'vue';
import { MoreOutlined } from '@ant-design/icons-vue';
import { Divider } from 'ant-design-vue';
import { Divider, Tooltip, TooltipProps } from 'ant-design-vue';
import Icon from '/@/components/Icon/index';
import { ActionItem, TableActionType } from '/@/components/Table';
import { PopConfirmButton } from '/@/components/Button';
@ -39,13 +41,13 @@
import { useDesign } from '/@/hooks/web/useDesign';
import { useTableContext } from '../hooks/useTableContext';
import { usePermission } from '/@/hooks/web/usePermission';
import { isBoolean, isFunction } from '/@/utils/is';
import { isBoolean, isFunction, isString } from '/@/utils/is';
import { propTypes } from '/@/utils/propTypes';
import { ACTION_COLUMN_FLAG } from '../const';
export default defineComponent({
name: 'TableAction',
components: { Icon, PopConfirmButton, Divider, Dropdown, MoreOutlined },
components: { Icon, PopConfirmButton, Divider, Dropdown, MoreOutlined, Tooltip },
props: {
actions: {
type: Array as PropType<ActionItem[]>,
@ -124,6 +126,16 @@
return actionColumn?.align ?? 'left';
});
const getTooltip = computed(() => {
return (data: string | TooltipProps): TooltipProps => {
if (isString(data)) {
return { title: data, placement: 'bottom' };
} else {
return Object.assign({ placement: 'bottom' }, data);
}
};
});
function onCellClick(e: MouseEvent) {
if (!props.stopButtonPropagation) return;
const target = e.target as HTMLElement;
@ -132,7 +144,7 @@
}
}
return { prefixCls, getActions, getDropdownList, getAlign, onCellClick };
return { prefixCls, getActions, getDropdownList, getAlign, onCellClick, getTooltip };
},
});
</script>

View File

@ -44,7 +44,7 @@
import { propTypes } from '/@/utils/propTypes';
import { isString, isBoolean, isFunction, isNumber, isArray } from '/@/utils/is';
import { createPlaceholderMessage } from './helper';
import { set } from 'lodash-es';
import { set, omit } from 'lodash-es';
export default defineComponent({
name: 'EditableCell',
@ -108,7 +108,7 @@
return {
placeholder: createPlaceholderMessage(unref(getComponent)),
...apiSelectProps,
...compProps,
...omit(compProps, 'onChange'),
[valueField]: value,
};
});
@ -184,6 +184,8 @@
} else if (isString(e) || isBoolean(e) || isNumber(e)) {
currentValueRef.value = e;
}
const onChange = props.column?.editComponentProps?.onChange;
if (onChange && isFunction(onChange)) onChange(...arguments);
table.emit?.('edit-change', {
column: props.column,

View File

@ -176,6 +176,7 @@ export function useDataSource(
try {
setLoading(true);
const { pageField, sizeField, listField, totalField } = Object.assign(
{},
FETCH_SETTING,
fetchSetting
);
@ -203,7 +204,7 @@ export function useDataSource(
...(opt?.filterInfo ?? {}),
};
if (beforeFetch && isFunction(beforeFetch)) {
params = beforeFetch(params) || params;
params = (await beforeFetch(params)) || params;
}
const res = await api(params);
@ -225,7 +226,7 @@ export function useDataSource(
}
if (afterFetch && isFunction(afterFetch)) {
resultItems = afterFetch(resultItems) || resultItems;
resultItems = (await afterFetch(resultItems)) || resultItems;
}
dataSourceRef.value = resultItems;
setPagination({

View File

@ -1,6 +1,8 @@
import { isFunction } from '/@/utils/is';
import type { BasicTableProps, TableRowSelection } from '../types/table';
import { computed, ref, unref, ComputedRef, Ref, toRaw } from 'vue';
import { computed, ref, unref, ComputedRef, Ref, toRaw, watch, nextTick } from 'vue';
import { ROW_KEY } from '../const';
import { omit } from 'lodash-es';
export function useRowSelection(
propsRef: ComputedRef<BasicTableProps>,
@ -22,15 +24,35 @@ export function useRowSelection(
onChange: (selectedRowKeys: string[], selectedRows: Recordable[]) => {
selectedRowKeysRef.value = selectedRowKeys;
selectedRowRef.value = selectedRows;
emit('selection-change', {
keys: selectedRowKeys,
rows: selectedRows,
});
},
...(rowSelection === undefined ? {} : rowSelection),
...omit(rowSelection === undefined ? {} : rowSelection, ['onChange']),
};
});
watch(
() => unref(propsRef).rowSelection?.selectedRowKeys,
(v: string[]) => {
setSelectedRowKeys(v);
}
);
watch(
() => unref(selectedRowKeysRef),
() => {
nextTick(() => {
const { rowSelection } = unref(propsRef);
if (rowSelection) {
const { onChange } = rowSelection;
if (onChange && isFunction(onChange)) onChange(getSelectRowKeys(), getSelectRows());
}
emit('selection-change', {
keys: getSelectRowKeys(),
rows: getSelectRows(),
});
});
}
);
const getAutoCreateKey = computed(() => {
return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
});

View File

@ -1,8 +1,9 @@
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip';
import { RoleEnum } from '/@/enums/roleEnum';
export interface ActionItem extends ButtonProps {
onClick?: Fn;
label: string;
label?: string;
color?: 'success' | 'error' | 'warning';
icon?: string;
popConfirm?: PopConfirm;
@ -12,6 +13,7 @@ export interface ActionItem extends ButtonProps {
auth?: RoleEnum | RoleEnum[] | string | string[];
// 业务控制是否显示
ifShow?: boolean | ((action: ActionItem) => boolean);
tooltip?: string | TooltipProps;
}
export interface PopConfirm {

View File

@ -97,8 +97,7 @@
},
onRightClick: handleRightClick,
};
propsData = omit(propsData, 'treeData', 'class');
return propsData;
return omit(propsData, 'treeData', 'class');
});
const getTreeData = computed((): TreeItem[] =>
@ -106,11 +105,17 @@
);
const getNotFound = computed((): boolean => {
return searchState.startSearch && searchState.searchData?.length === 0;
return !getTreeData.value || getTreeData.value.length === 0;
});
const { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey, getAllKeys } =
useTree(treeDataRef, getReplaceFields);
const {
deleteNodeByKey,
insertNodeByKey,
insertNodesByKey,
filterByLevel,
updateNodeByKey,
getAllKeys,
} = useTree(treeDataRef, getReplaceFields);
function getIcon(params: Recordable, icon?: string) {
if (!icon) {
@ -267,6 +272,7 @@
setCheckedKeys,
getCheckedKeys,
insertNodeByKey,
insertNodesByKey,
deleteNodeByKey,
updateNodeByKey,
checkAll,
@ -375,7 +381,7 @@
</Tree>
</ScrollContainer>
<Empty v-show={unref(getNotFound)} class="!mt-4" />
<Empty v-show={unref(getNotFound)} image={Empty.PRESENTED_IMAGE_SIMPLE} class="!mt-4" />
</div>
);
};

View File

@ -32,6 +32,7 @@
:value="fileList"
@register="registerPreviewModal"
@list-change="handlePreviewChange"
@delete="handlePreviewDelete"
/>
</div>
</template>
@ -50,7 +51,7 @@
name: 'BasicUpload',
components: { UploadModal, UploadPreviewModal, Icon, Tooltip },
props: uploadContainerProps,
emits: ['change', 'delete'],
emits: ['change', 'delete', 'preview-delete', 'update:value'],
setup(props, { emit, attrs }) {
const { t } = useI18n();
@ -84,12 +85,14 @@
// modal
function handleChange(urls: string[]) {
fileList.value = [...unref(fileList), ...(urls || [])];
emit('update:value', fileList.value);
emit('change', fileList.value);
}
// modal
function handlePreviewChange(urls: string[]) {
fileList.value = [...(urls || [])];
emit('update:value', fileList.value);
emit('change', fileList.value);
}
@ -97,6 +100,10 @@
emit('delete', record);
}
function handlePreviewDelete(url: string) {
emit('preview-delete', url);
}
return {
registerUploadModal,
openUploadModal,
@ -108,6 +115,7 @@
showPreview,
bindValue,
handleDelete,
handlePreviewDelete,
t,
};
},

View File

@ -24,7 +24,7 @@
export default defineComponent({
components: { BasicModal, FileList },
props: previewProps,
emits: ['list-change', 'register'],
emits: ['list-change', 'register', 'delete'],
setup(props, { emit }) {
const [register, { closeModal }] = useModalInner();
const { t } = useI18n();
@ -50,7 +50,8 @@
function handleRemove(record: PreviewFileItem) {
const index = fileListRef.value.findIndex((item) => item.url === record.url);
if (index !== -1) {
fileListRef.value.splice(index, 1);
const removed = fileListRef.value.splice(index, 1);
emit('delete', removed[0].url);
emit(
'list-change',
fileListRef.value.map((item) => item.url)

View File

@ -59,7 +59,7 @@
}
&.ant-btn-link.is-disabled {
color: rgba(0, 0, 0, 0.25) !important;
color: rgba(0, 0, 0, 0.25);
text-shadow: none;
cursor: not-allowed !important;
background-color: transparent !important;

View File

@ -1,6 +1,7 @@
@import './pagination.less';
@import './input.less';
@import './btn.less';
@import './table.less';
// TODO beta.11 fix
.ant-col {

32
src/design/ant/table.less Normal file
View File

@ -0,0 +1,32 @@
@prefix-cls: ~'@{namespace}-basic-table';
// fix table unnecessary scrollbar
.@{prefix-cls} {
.ant-table-wrapper {
.ant-spin-nested-loading {
.ant-spin-container {
.ant-table {
.ant-table-content {
.ant-table-scroll {
.ant-table-hide-scrollbar {
overflow-y: auto !important;
}
.ant-table-body {
overflow: auto !important;
}
}
.ant-table-fixed-right {
.ant-table-body-outer {
.ant-table-body-inner {
overflow-y: auto !important;
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,147 @@
import { ComputedRef, nextTick, Ref, ref, unref, watch } from 'vue';
import { onMountedOrActivated } from '/@/hooks/core/onMountedOrActivated';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import { useLayoutHeight } from '/@/layouts/default/content/useContentViewHeight';
import { getViewportOffset } from '/@/utils/domUtils';
export interface CompensationHeight {
// 使用 layout Footer 高度作为判断补偿高度的条件
useLayoutFooter: boolean;
// refs HTMLElement
elements?: Ref[];
}
/**
* dom最下坐标到屏幕最下坐标dom的高度paddingmargin等值进行动态计算
*
*
* @param flag
* @param anchorRef Ref<ElRef | ComponentRef>
* @param subtractHeightRefs Ref<ElRef | ComponentRef>
* @param substractSpaceRefs (margins/paddings) Ref<ElRef | ComponentRef>
* @param offsetHeightRef
* @returns
*/
export function useContentHeight(
flag: ComputedRef<Boolean>,
anchorRef: Ref,
subtractHeightRefs: Ref[],
substractSpaceRefs: Ref[],
offsetHeightRef: Ref<number> = ref(0)
) {
const contentHeight: Ref<Nullable<number>> = ref(null);
const { footerHeightRef: layoutFooterHeightRef } = useLayoutHeight();
let compensationHeight: CompensationHeight = {
useLayoutFooter: true,
};
const setCompensation = (params: CompensationHeight) => {
compensationHeight = params;
};
function redoHeight() {
nextTick(() => {
calcContentHeight();
});
}
function calcSubtractSpace(element: HTMLDivElement | null | undefined): number {
let subtractHeight = 0;
const ZERO_PX = '0px';
let marginBottom = ZERO_PX;
let marginTop = ZERO_PX;
if (element) {
const cssStyle = getComputedStyle(element);
marginBottom = cssStyle?.marginBottom ?? ZERO_PX;
marginTop = cssStyle?.marginTop ?? ZERO_PX;
}
if (marginBottom) {
const contentMarginBottom = Number(marginBottom.replace(/[^\d]/g, ''));
subtractHeight += contentMarginBottom;
}
if (marginTop) {
const contentMarginTop = Number(marginTop.replace(/[^\d]/g, ''));
subtractHeight += contentMarginTop;
}
return subtractHeight;
}
function getEl(element: any): Nullable<HTMLDivElement> {
if (element == null) {
return null;
}
return (element instanceof HTMLDivElement ? element : element.$el) as HTMLDivElement;
}
async function calcContentHeight() {
if (!flag.value) {
return;
}
// Add a delay to get the correct height
await nextTick();
const wrapperEl = getEl(unref(anchorRef));
if (!wrapperEl) {
return;
}
const { bottomIncludeBody } = getViewportOffset(wrapperEl);
// substract elements height
let substractHeight = 0;
subtractHeightRefs.forEach((item) => {
substractHeight += getEl(unref(item))?.offsetHeight ?? 0;
});
// subtract margins / paddings
let substractSpaceHeight = 0;
substractSpaceRefs.forEach((item) => {
substractSpaceHeight += calcSubtractSpace(getEl(unref(item)));
});
let height =
bottomIncludeBody -
unref(layoutFooterHeightRef) -
unref(offsetHeightRef) -
substractHeight -
substractSpaceHeight;
// compensation height
const calcCompensationHeight = () => {
compensationHeight.elements?.forEach((item) => {
height += getEl(unref(item))?.offsetHeight ?? 0;
});
};
if (compensationHeight.useLayoutFooter && unref(layoutFooterHeightRef) > 0) {
calcCompensationHeight();
} else {
calcCompensationHeight();
}
contentHeight.value = height;
}
onMountedOrActivated(() => {
nextTick(() => {
calcContentHeight();
});
});
useWindowSizeFn(
() => {
calcContentHeight();
},
50,
{ immediate: true }
);
watch(
() => [layoutFooterHeightRef.value],
() => {
calcContentHeight();
},
{
flush: 'post',
immediate: true,
}
);
return { redoHeight, setCompensation, contentHeight };
}

View File

@ -57,13 +57,14 @@ export function usePermission() {
* Determine whether there is permission
*/
function hasPermission(value?: RoleEnum | RoleEnum[] | string | string[], def = true): boolean {
// Visible by default
if (!value) {
return def;
}
const permMode = projectSetting.permissionMode;
if (PermissionModeEnum.ROUTE_MAPPING === permMode) {
// Visible by default
if (!value) {
return def;
}
if ([PermissionModeEnum.ROUTE_MAPPING, PermissionModeEnum.ROLE].includes(permMode)) {
if (!isArray(value)) {
return userStore.getRoleList?.includes(value as RoleEnum);
}
@ -71,10 +72,6 @@ export function usePermission() {
}
if (PermissionModeEnum.BACK === permMode) {
// Visible by default
if (!value) {
return def;
}
const allCodeList = permissionStore.getPermCodeList as string[];
if (!isArray(value)) {
return allCodeList.includes(value);

View File

@ -15,6 +15,8 @@
:collapsedWidth="getCollapsedWidth"
:theme="getMenuTheme"
@breakpoint="onBreakpointChange"
@collapse="toggleCollapsed"
:trigger="getTrigger"
v-bind="getTriggerAttr"
>
<template #trigger v-if="getShowTrigger">
@ -25,7 +27,7 @@
</Sider>
</template>
<script lang="ts">
import { computed, defineComponent, ref, unref, CSSProperties } from 'vue';
import { computed, defineComponent, ref, unref, CSSProperties, h } from 'vue';
import { Layout } from 'ant-design-vue';
import LayoutMenu from '../menu/index.vue';
@ -55,6 +57,7 @@
getMenuHidden,
getMenuFixed,
getIsMixMode,
toggleCollapsed,
} = useMenuSetting();
const { prefixCls } = useDesign('layout-sideBar');
@ -101,6 +104,10 @@
};
});
// 使sider
// andv trigger
const getTrigger = h(LayoutTrigger);
return {
prefixCls,
sideRef,
@ -108,6 +115,7 @@
getIsMobile,
getHiddenDomStyle,
getSiderClass,
getTrigger,
getTriggerAttr,
getCollapsedWidth,
getMenuFixed,
@ -119,6 +127,7 @@
getMode,
getSplitType,
getShowTrigger,
toggleCollapsed,
};
},
});

View File

@ -13,6 +13,13 @@ export default {
uploadSuccess: 'Uploaded success!',
modalTitle: 'Avatar upload',
okText: 'Confirm and upload',
btn_reset: 'Reset',
btn_rotate_left: 'Counterclockwise rotation',
btn_rotate_right: 'Clockwise rotation',
btn_scale_x: 'Flip horizontal',
btn_scale_y: 'Flip vertical',
btn_zoom_in: 'Zoom in',
btn_zoom_out: 'Zoom out',
},
drawer: {
loadingText: 'Loading...',
@ -41,6 +48,9 @@ export default {
modal: {
cancelText: 'Close',
okText: 'Confirm',
close: 'Close',
maximize: 'Maximize',
restore: 'Restore',
},
table: {
settingDens: 'Density',

View File

@ -81,6 +81,9 @@ export default {
tab: 'Tab with parameters',
tab1: 'Tab with parameter 1',
tab2: 'Tab with parameter 2',
menu: 'Menu with parameters',
menu1: 'Menu with parameters 1',
menu2: 'Menu with parameters 2',
ws: 'Websocket test',

View File

@ -13,6 +13,13 @@ export default {
uploadSuccess: '上传成功',
modalTitle: '头像上传',
okText: '确认并上传',
btn_reset: '重置',
btn_rotate_left: '逆时针旋转',
btn_rotate_right: '顺时针旋转',
btn_scale_x: '水平翻转',
btn_scale_y: '垂直翻转',
btn_zoom_in: '放大',
btn_zoom_out: '缩小',
},
drawer: {
loadingText: '加载中...',
@ -43,6 +50,9 @@ export default {
modal: {
cancelText: '关闭',
okText: '确认',
close: '关闭',
maximize: '最大化',
restore: '还原',
},
table: {
settingDens: '密度',

View File

@ -80,6 +80,9 @@ export default {
tab: 'Tab带参',
tab1: 'Tab带参1',
tab2: 'Tab带参2',
menu: 'Menu带参',
menu1: 'Menu带参1',
menu2: 'Menu带参2',
ws: 'websocket测试',
breadcrumb: '面包屑导航',
breadcrumbFlat: '平级模式',

View File

@ -11,6 +11,7 @@ import { createPermissionGuard } from './permissionGuard';
import { createStateGuard } from './stateGuard';
import nProgress from 'nprogress';
import projectSetting from '/@/settings/projectSetting';
import { createParamMenuGuard } from './paramMenuGuard';
// Don't change the order of creation
export function setupRouterGuard(router: Router) {
@ -21,6 +22,7 @@ export function setupRouterGuard(router: Router) {
createMessageGuard(router);
createProgressGuard(router);
createPermissionGuard(router);
createParamMenuGuard(router); // must after createPermissionGuard (menu has been built.)
createStateGuard(router);
}

View File

@ -0,0 +1,47 @@
import type { Router } from 'vue-router';
import { configureDynamicParamsMenu } from '../helper/menuHelper';
import { Menu } from '../types';
import { PermissionModeEnum } from '/@/enums/appEnum';
import { useAppStoreWithOut } from '/@/store/modules/app';
import { usePermissionStoreWithOut } from '/@/store/modules/permission';
export function createParamMenuGuard(router: Router) {
const permissionStore = usePermissionStoreWithOut();
router.beforeEach(async (to, _, next) => {
// filter no name route
if (!to.name) {
next();
return;
}
// menu has been built.
if (!permissionStore.getIsDynamicAddedRoute) {
next();
return;
}
let menus: Menu[] = [];
if (isBackMode()) {
menus = permissionStore.getBackMenuList;
} else if (isRouteMappingMode()) {
menus = permissionStore.getFrontMenuList;
}
menus.forEach((item) => configureDynamicParamsMenu(item, to.params));
next();
});
}
const getPermissionMode = () => {
const appStore = useAppStoreWithOut();
return appStore.getProjectConfig.permissionMode;
};
const isBackMode = () => {
return getPermissionMode() === PermissionModeEnum.BACK;
};
const isRouteMappingMode = () => {
return getPermissionMode() === PermissionModeEnum.ROUTE_MAPPING;
};

View File

@ -1,9 +1,10 @@
import { AppRouteModule } from '/@/router/types';
import type { MenuModule, Menu, AppRouteRecordRaw } from '/@/router/types';
import { findPath, treeMap } from '/@/utils/helper/treeHelper';
import { cloneDeep } from 'lodash-es';
import { isUrl } from '/@/utils/is';
import { RouteParams } from 'vue-router';
import { toRaw } from 'vue';
export function getAllParentPath<T = Recordable>(treeData: T[], path: string) {
const menuList = findPath(treeData, (n) => n.path === path) as Menu[];
@ -21,7 +22,7 @@ function joinParentPath(menus: Menu[], parentPath = '') {
menu.path = `${parentPath}/${menu.path}`;
}
if (menu?.children?.length) {
joinParentPath(menu.children, menu.path);
joinParentPath(menu.children, menu.meta?.hidePathForChildren ? parentPath : menu.path);
}
}
}
@ -36,11 +37,14 @@ export function transformMenuModule(menuModule: MenuModule): Menu {
return menuList[0];
}
export function transformRouteToMenu(routeModList: AppRouteModule[]) {
export function transformRouteToMenu(routeModList: AppRouteModule[], routerMapping = false) {
const cloneRouteModList = cloneDeep(routeModList);
const routeList: AppRouteRecordRaw[] = [];
cloneRouteModList.forEach((item) => {
if (routerMapping && item.meta.hideChildrenInMenu && typeof item.redirect === 'string') {
item.path = item.redirect;
}
if (item.meta?.single) {
const realItem = item?.children?.[0];
realItem && routeList.push(realItem);
@ -64,3 +68,25 @@ export function transformRouteToMenu(routeModList: AppRouteModule[]) {
joinParentPath(list);
return cloneDeep(list);
}
/**
* config menu with given params
*/
const menuParamRegex = /(?<=:)([\s\S]+?)((?=\/)|$)/g;
export function configureDynamicParamsMenu(menu: Menu, params: RouteParams) {
const { path, paramPath } = toRaw(menu);
let realPath = paramPath ? paramPath : path;
const matchArr = realPath.match(menuParamRegex);
matchArr?.forEach((it) => {
if (params[it]) {
realPath = realPath.replace(`:${it}`, params[it] as string);
}
});
// save original param path.
if (!paramPath && matchArr && matchArr.length > 0) {
menu.paramPath = path;
}
menu.path = realPath;
// children
menu.children?.forEach((item) => configureDynamicParamsMenu(item, params));
}

View File

@ -31,6 +31,9 @@ export interface Menu {
path: string;
// path contains param, auto assignment.
paramPath?: string;
disabled?: boolean;
children?: Menu[];

View File

@ -23,10 +23,10 @@ export const useLockStore = defineStore({
actions: {
setLockInfo(info: LockInfo) {
this.lockInfo = Object.assign({}, this.lockInfo, info);
Persistent.setLocal(LOCK_INFO_KEY, this.lockInfo);
Persistent.setLocal(LOCK_INFO_KEY, this.lockInfo, true);
},
resetLockInfo() {
Persistent.removeLocal(LOCK_INFO_KEY);
Persistent.removeLocal(LOCK_INFO_KEY, true);
this.lockInfo = null;
},
// Unlock

View File

@ -111,6 +111,12 @@ export const usePermissionStore = defineStore({
return roleList.some((role) => roles.includes(role));
};
const routeRmoveIgnoreFilter = (route: AppRouteRecordRaw) => {
const { meta } = route;
const { ignoreRoute } = meta || {};
return !ignoreRoute;
};
switch (permissionMode) {
case PermissionModeEnum.ROLE:
routes = filter(asyncRoutes, routeFilter);
@ -122,10 +128,13 @@ export const usePermissionStore = defineStore({
case PermissionModeEnum.ROUTE_MAPPING:
routes = filter(asyncRoutes, routeFilter);
routes = routes.filter(routeFilter);
const menuList = transformRouteToMenu(asyncRoutes);
const menuList = transformRouteToMenu(routes, true);
routes = filter(routes, routeRmoveIgnoreFilter);
routes = routes.filter(routeRmoveIgnoreFilter);
menuList.sort((a, b) => {
return (a.meta?.orderNo || 0) - (b.meta?.orderNo || 0);
});
this.setFrontMenuList(menuList);
// Convert multi-level routing to level 2 routing
routes = flatMultiLevelRoutes(routes);
@ -157,6 +166,10 @@ export const usePermissionStore = defineStore({
const backMenuList = transformRouteToMenu(routeList);
this.setBackMenuList(backMenuList);
// remove meta.ignoreRoute item
routeList = filter(routeList, routeRmoveIgnoreFilter);
routeList = routeList.filter(routeRmoveIgnoreFilter);
routeList = flatMultiLevelRoutes(routeList);
routes = [PAGE_NOT_FOUND_ROUTE, ...routeList];
break;

View File

@ -7,7 +7,7 @@ import { PageEnum } from '/@/enums/pageEnum';
import { ROLES_KEY, TOKEN_KEY, USER_INFO_KEY } from '/@/enums/cacheEnum';
import { getAuthCache, setAuthCache } from '/@/utils/auth';
import { GetUserInfoModel, LoginParams } from '/@/api/sys/model/userModel';
import { getUserInfo, loginApi } from '/@/api/sys/user';
import { doLogout, getUserInfo, loginApi } from '/@/api/sys/user';
import { useI18n } from '/@/hooks/web/useI18n';
import { useMessage } from '/@/hooks/web/useMessage';
import { router } from '/@/router';
@ -105,7 +105,14 @@ export const useUserStore = defineStore({
/**
* @description: logout
*/
logout(goLogin = false) {
async logout(goLogin = false) {
try {
await doLogout();
} catch {
console.log('注销Token失败');
}
this.setToken(undefined);
this.setSessionTimeout(false);
goLogin && router.push(PageEnum.BASE_LOGIN);
},

View File

@ -19,3 +19,8 @@ export function setAuthCache(key: BasicKeys, value) {
const fn = isLocal ? Persistent.setLocal : Persistent.setSession;
return fn(key, value, true);
}
export function clearAuthCache(immediate = true) {
const fn = isLocal ? Persistent.clearLocal : Persistent.clearSession;
return fn(immediate);
}

View File

@ -16,6 +16,7 @@ import {
} from '/@/enums/cacheEnum';
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
import { toRaw } from 'vue';
import { pick, omit } from 'lodash-es';
interface BasicStore {
[TOKEN_KEY]: string | number | null | undefined;
@ -57,12 +58,14 @@ export class Persistent {
immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
}
static removeLocal(key: LocalKeys): void {
static removeLocal(key: LocalKeys, immediate = false): void {
localMemory.remove(key);
immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
}
static clearLocal(): void {
static clearLocal(immediate = false): void {
localMemory.clear();
immediate && ls.clear();
}
static getSession<T>(key: SessionKeys) {
@ -74,22 +77,36 @@ export class Persistent {
immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache);
}
static removeSession(key: SessionKeys): void {
static removeSession(key: SessionKeys, immediate = false): void {
sessionMemory.remove(key);
immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache);
}
static clearSession(): void {
static clearSession(immediate = false): void {
sessionMemory.clear();
immediate && ss.clear();
}
static clearAll() {
static clearAll(immediate = false) {
sessionMemory.clear();
localMemory.clear();
if (immediate) {
ls.clear();
ss.clear();
}
}
}
window.addEventListener('beforeunload', function () {
ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache);
// TOKEN_KEY 在登录或注销时已经写入到storage了此处为了解决同时打开多个窗口时token不同步的问题
// LOCK_INFO_KEY 在锁屏和解锁时写入,此处也不应修改
ls.set(APP_LOCAL_CACHE_KEY, {
...omit(localMemory.getCache, LOCK_INFO_KEY),
...pick(ls.get(APP_LOCAL_CACHE_KEY), [TOKEN_KEY, USER_INFO_KEY, LOCK_INFO_KEY]),
});
ss.set(APP_SESSION_CACHE_KEY, {
...omit(sessionMemory.getCache, LOCK_INFO_KEY),
...pick(ss.get(APP_SESSION_CACHE_KEY), [TOKEN_KEY, USER_INFO_KEY, LOCK_INFO_KEY]),
});
});
function storageChange(e: any) {

View File

@ -61,6 +61,7 @@ const transform: AxiosTransform = {
switch (code) {
case ResultEnum.TIMEOUT:
timeoutMsg = t('sys.api.timeoutMessage');
break;
default:
if (message) {
timeoutMsg = message;
@ -90,6 +91,8 @@ const transform: AxiosTransform = {
config.url = `${apiUrl}${config.url}`;
}
const params = config.params || {};
const data = config.data || false;
formatDate && data && !isString(data) && formatRequestDate(data);
if (config.method?.toUpperCase() === RequestEnum.GET) {
if (!isString(params)) {
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。
@ -102,10 +105,19 @@ const transform: AxiosTransform = {
} else {
if (!isString(params)) {
formatDate && formatRequestDate(params);
config.data = params;
config.params = undefined;
if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length > 0) {
config.data = data;
config.params = params;
} else {
// 非GET请求如果没有提供data则将params视为data
config.data = params;
config.params = undefined;
}
if (joinParamsToUrl) {
config.url = setObjToUrlParams(config.url as string, config.data);
config.url = setObjToUrlParams(
config.url as string,
Object.assign({}, config.params, config.data)
);
}
} else {
// 兼容restful风格
@ -122,7 +134,7 @@ const transform: AxiosTransform = {
requestInterceptors: (config, options) => {
// 请求之前处理config
const token = getToken();
if (token) {
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
// jwt token
config.headers.Authorization = options.authenticationScheme
? `${options.authenticationScheme} ${token}`
@ -214,6 +226,8 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
joinTime: true,
// 忽略重复请求
ignoreCancelToken: true,
// 是否携带token
withToken: true,
},
},
opt || {}

View File

@ -1,11 +1,9 @@
const { sky: color_sky, ...colors } = require('tailwindcss/colors');
module.exports = {
mode: 'jit',
// darkMode: 'class',
plugins: [createEnterPlugin()],
purge: {
enabled: false,
enable: process.env.NODE_ENV === 'production',
content: ['./index.html', './src/**/*.{vue,ts,tsx}'],
},
theme: {
@ -13,21 +11,19 @@ module.exports = {
zIndex: {
'-1': '-1',
},
},
colors: {
...colors,
sky: color_sky,
primary: {
DEFAULT: '#0960bd',
// dark: primaryColorDark,
colors: {
primary: {
DEFAULT: '#0960bd',
// dark: primaryColorDark,
},
},
screens: {
sm: '576px',
md: '768px',
lg: '992px',
xl: '1200px',
'2xl': '1600px',
},
},
screens: {
sm: '576px',
md: '768px',
lg: '992px',
xl: '1200px',
'2xl': '1600px',
},
},
};

2
types/axios.d.ts vendored
View File

@ -19,6 +19,8 @@ export interface RequestOptions {
// Whether to add a timestamp
joinTime?: boolean;
ignoreCancelToken?: boolean;
// Whether to send token in header
withToken?: boolean;
}
export interface Result<T = any> {

View File

@ -33,5 +33,9 @@ declare module 'vue-router' {
// Never show in menu
hideMenu?: boolean;
isLink?: boolean;
// only build for Menu
ignoreRoute?: boolean;
// Hide path for children
hidePathForChildren?: boolean;
}
}

1194
yarn.lock

File diff suppressed because it is too large Load Diff