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

This commit is contained in:
vben 2021-10-20 09:19:54 +08:00
commit 8efcba4861
63 changed files with 5304 additions and 4917 deletions

View File

@ -1,3 +1,30 @@
### ✨ 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) ## 2.7.2(2021-09-14)
### ✨ Features ### ✨ Features

View File

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

View File

@ -28,9 +28,9 @@ export function wrapperEnv(envConf: Recordable): ViteEnv {
if (envName === 'VITE_PORT') { if (envName === 'VITE_PORT') {
realName = Number(realName); realName = Number(realName);
} }
if (envName === 'VITE_PROXY') { if (envName === 'VITE_PROXY' && realName) {
try { try {
realName = JSON.parse(realName); realName = JSON.parse(realName.replace(/'/g, '"'));
} catch (error) { } catch (error) {
realName = ''; realName = '';
} }

View File

@ -14,7 +14,52 @@ export function configStyleImportPlugin(isBuild: boolean) {
libraryName: 'ant-design-vue', libraryName: 'ant-design-vue',
esModule: true, esModule: true,
resolveStyle: (name) => { resolveStyle: (name) => {
return `ant-design-vue/es/${name}/style/index`; // 这里是“子组件”列表,无需额外引入样式文件
const ignoreList = [
'typography-text',
'typography-title',
'typography-paragraph',
'typography-link',
'anchor-link',
'sub-menu',
'menu-item',
'menu-item-group',
'dropdown-button',
'breadcrumb-item',
'breadcrumb-separator',
'input-password',
'input-search',
'input-group',
'form-item',
'radio-group',
'checkbox-group',
'layout-sider',
'layout-content',
'layout-footer',
'layout-header',
'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',
];
return ignoreList.includes(name) ? '' : `ant-design-vue/es/${name}/style/index`;
}, },
}, },
], ],

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ function getRandomPics(count = 10): string[] {
const demoList = (() => { const demoList = (() => {
const result: any[] = []; const result: any[] = [];
for (let index = 0; index < 60; index++) { for (let index = 0; index < 200; index++) {
result.push({ result.push({
id: `${index}`, id: `${index}`,
beginTime: '@datetime', beginTime: '@datetime',

View File

@ -35,98 +35,100 @@
}, },
"dependencies": { "dependencies": {
"@iconify/iconify": "^2.0.4", "@iconify/iconify": "^2.0.4",
"@vueuse/core": "^6.3.3", "@vueuse/core": "^6.6.2",
"@zxcvbn-ts/core": "^1.0.0-beta.0", "@zxcvbn-ts/core": "^1.0.0-beta.0",
"ant-design-vue": "2.2.7", "ant-design-vue": "2.2.8",
"axios": "^0.21.4", "axios": "^0.23.0",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"echarts": "^5.2.0", "echarts": "^5.2.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mockjs": "^1.1.0", "mockjs": "^1.1.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"pinia": "2.0.0-rc.9", "pinia": "2.0.0-rc.14",
"print-js": "^1.6.0",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"sortablejs": "^1.14.0", "sortablejs": "^1.14.0",
"vue": "3.2.11", "vue": "^3.2.20",
"vue-i18n": "9.1.7", "vue-i18n": "^9.1.9",
"vue-router": "^4.0.11", "vue-json-pretty": "^2.0.4",
"vue-types": "^4.1.0" "vue-router": "^4.0.12",
"vue-types": "^4.1.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^13.1.0", "@commitlint/cli": "^13.2.1",
"@commitlint/config-conventional": "^13.1.0", "@commitlint/config-conventional": "^13.2.0",
"@iconify/json": "^1.1.401", "@iconify/json": "^1.1.416",
"@purge-icons/generated": "^0.7.0", "@purge-icons/generated": "^0.7.0",
"@types/codemirror": "^5.60.2", "@types/codemirror": "^5.60.5",
"@types/crypto-js": "^4.0.2", "@types/crypto-js": "^4.0.2",
"@types/fs-extra": "^9.0.12", "@types/fs-extra": "^9.0.13",
"@types/inquirer": "^8.1.1", "@types/inquirer": "^8.1.3",
"@types/intro.js": "^3.0.2", "@types/intro.js": "^3.0.2",
"@types/jest": "^27.0.1", "@types/jest": "^27.0.2",
"@types/lodash-es": "^4.17.5", "@types/lodash-es": "^4.17.5",
"@types/mockjs": "^1.0.4", "@types/mockjs": "^1.0.4",
"@types/node": "^16.9.1", "@types/node": "^16.11.1",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.1", "@types/qrcode": "^1.4.1",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@types/showdown": "^1.9.4", "@types/showdown": "^1.9.4",
"@types/sortablejs": "^1.10.7", "@types/sortablejs": "^1.10.7",
"@typescript-eslint/eslint-plugin": "^4.31.0", "@typescript-eslint/eslint-plugin": "^5.1.0",
"@typescript-eslint/parser": "^4.31.0", "@typescript-eslint/parser": "^5.1.0",
"@vitejs/plugin-legacy": "^1.5.3", "@vitejs/plugin-legacy": "^1.6.2",
"@vitejs/plugin-vue": "^1.6.2", "@vitejs/plugin-vue": "^1.9.3",
"@vitejs/plugin-vue-jsx": "^1.1.8", "@vitejs/plugin-vue-jsx": "^1.2.0",
"@vue/compiler-sfc": "3.2.11", "@vue/compiler-sfc": "3.2.20",
"@vue/test-utils": "^2.0.0-rc.14", "@vue/test-utils": "^2.0.0-rc.16",
"autoprefixer": "^10.3.4", "autoprefixer": "^10.3.7",
"commitizen": "^4.2.4", "commitizen": "^4.2.4",
"conventional-changelog-cli": "^2.1.1", "conventional-changelog-cli": "^2.1.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"eslint": "^7.32.0", "eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-define-config": "^1.0.9", "eslint-define-config": "^1.1.1",
"eslint-plugin-jest": "^24.4.0", "eslint-plugin-jest": "^25.2.2",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^7.17.0", "eslint-plugin-vue": "^7.19.1",
"esno": "^0.9.1", "esno": "^0.10.1",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"http-server": "^13.0.1", "http-server": "^14.0.0",
"husky": "^7.0.2", "husky": "^7.0.2",
"inquirer": "^8.1.2", "inquirer": "^8.2.0",
"is-ci": "^3.0.0", "is-ci": "^3.0.0",
"jest": "^27.2.0", "jest": "^27.3.1",
"less": "^4.1.1", "less": "^4.1.2",
"lint-staged": "^11.1.2", "lint-staged": "^11.2.3",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "^8.3.6", "postcss": "^8.3.9",
"prettier": "^2.4.0", "prettier": "^2.4.1",
"pretty-quick": "^3.1.1", "pretty-quick": "^3.1.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup-plugin-visualizer": "5.5.2", "rollup-plugin-visualizer": "^5.5.2",
"stylelint": "^13.13.1", "stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2", "stylelint-config-prettier": "^9.0.3",
"stylelint-config-standard": "^22.0.0", "stylelint-config-standard": "^22.0.0",
"stylelint-order": "^4.1.0", "stylelint-order": "^4.1.0",
"ts-jest": "^27.0.5", "ts-jest": "^27.0.7",
"ts-node": "^10.2.1", "ts-node": "^10.3.0",
"typescript": "4.4.3", "typescript": "^4.4.4",
"vite": "2.5.7", "vite": "^2.6.10",
"vite-plugin-compression": "^0.3.5", "vite-plugin-compression": "^0.3.5",
"vite-plugin-html": "^2.1.0", "vite-plugin-html": "^2.1.1",
"vite-plugin-imagemin": "^0.4.5", "vite-plugin-imagemin": "^0.4.6",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.7.0", "vite-plugin-purge-icons": "^0.7.0",
"vite-plugin-pwa": "^0.11.2", "vite-plugin-pwa": "^0.11.3",
"vite-plugin-style-import": "^1.2.1", "vite-plugin-style-import": "^1.2.1",
"vite-plugin-svg-icons": "^1.0.4", "vite-plugin-svg-icons": "^1.0.5",
"vite-plugin-theme": "^0.8.1", "vite-plugin-theme": "^0.8.1",
"vite-plugin-vue-setup-extend": "^0.1.0", "vite-plugin-vue-setup-extend": "^0.1.0",
"vite-plugin-windicss": "^1.4.2", "vite-plugin-windicss": "^1.4.12",
"vue-eslint-parser": "^7.11.0", "vue-eslint-parser": "^8.0.0",
"vue-tsc": "^0.3.0" "vue-tsc": "^0.28.7"
}, },
"resolutions": { "resolutions": {
"//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it", "//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it",

View File

@ -87,6 +87,7 @@
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
transition: all 0.5s; transition: all 0.5s;
line-height: normal;
} }
} }
</style> </style>

View File

@ -4,3 +4,5 @@ import jsonPreview from './src/json-preview/JsonPreview.vue';
export const CodeEditor = withInstall(codeEditor); export const CodeEditor = withInstall(codeEditor);
export const JsonPreview = withInstall(jsonPreview); export const JsonPreview = withInstall(jsonPreview);
export * from './src/typing';

View File

@ -8,22 +8,22 @@
/> />
</div> </div>
</template> </template>
<script lang="ts">
export const MODE = {
JSON: 'application/json',
html: 'htmlmixed',
js: 'javascript',
};
</script>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue';
import CodeMirrorEditor from './codemirror/CodeMirror.vue'; import CodeMirrorEditor from './codemirror/CodeMirror.vue';
import { isString } from '/@/utils/is'; import { isString } from '/@/utils/is';
import { MODE } from './typing';
const props = defineProps({ const props = defineProps({
value: { type: [Object, String] as PropType<Record<string, any> | string> }, value: { type: [Object, String] as PropType<Record<string, any> | string> },
mode: { type: String, default: MODE.JSON }, mode: {
type: String as PropType<MODE>,
default: MODE.JSON,
validator(value: any) {
//
return Object.values(MODE).includes(value);
},
},
readonly: { type: Boolean }, readonly: { type: Boolean },
autoFormat: { type: Boolean, default: true }, autoFormat: { type: Boolean, default: true },
}); });

View File

@ -8,6 +8,7 @@
import { useAppStore } from '/@/store/modules/app'; import { useAppStore } from '/@/store/modules/app';
import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn'; import { useWindowSizeFn } from '/@/hooks/event/useWindowSizeFn';
import CodeMirror from 'codemirror'; import CodeMirror from 'codemirror';
import { MODE } from './../typing';
// css // css
import './codemirror.css'; import './codemirror.css';
import 'codemirror/theme/idea.css'; import 'codemirror/theme/idea.css';
@ -18,7 +19,14 @@
import 'codemirror/mode/htmlmixed/htmlmixed'; import 'codemirror/mode/htmlmixed/htmlmixed';
const props = defineProps({ const props = defineProps({
mode: { type: String, default: 'application/json' }, mode: {
type: String as PropType<MODE>,
default: MODE.JSON,
validator(value: any) {
//
return Object.values(MODE).includes(value);
},
},
value: { type: String, default: '' }, value: { type: String, default: '' },
readonly: { type: Boolean, default: false }, readonly: { type: Boolean, default: false },
}); });

View File

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

View File

@ -1,17 +1,17 @@
<template> <template>
<Dropdown :trigger="trigger" v-bind="$attrs"> <a-dropdown :trigger="trigger" v-bind="$attrs">
<span> <span>
<slot></slot> <slot></slot>
</span> </span>
<template #overlay> <template #overlay>
<Menu :selectedKeys="selectedKeys"> <a-menu :selectedKeys="selectedKeys">
<template v-for="item in dropMenuList" :key="`${item.event}`"> <template v-for="item in dropMenuList" :key="`${item.event}`">
<MenuItem <a-menu-item
v-bind="getAttr(item.event)" v-bind="getAttr(item.event)"
@click="handleClickMenu(item)" @click="handleClickMenu(item)"
:disabled="item.disabled" :disabled="item.disabled"
> >
<Popconfirm <a-popconfirm
v-if="popconfirm && item.popConfirm" v-if="popconfirm && item.popConfirm"
v-bind="getPopConfirmAttrs(item.popConfirm)" v-bind="getPopConfirmAttrs(item.popConfirm)"
> >
@ -22,40 +22,34 @@
<Icon :icon="item.icon" v-if="item.icon" /> <Icon :icon="item.icon" v-if="item.icon" />
<span class="ml-1">{{ item.text }}</span> <span class="ml-1">{{ item.text }}</span>
</div> </div>
</Popconfirm> </a-popconfirm>
<template v-else> <template v-else>
<Icon :icon="item.icon" v-if="item.icon" /> <Icon :icon="item.icon" v-if="item.icon" />
<span class="ml-1">{{ item.text }}</span> <span class="ml-1">{{ item.text }}</span>
</template> </template>
</MenuItem> </a-menu-item>
<MenuDivider v-if="item.divider" :key="`d-${item.event}`" /> <a-menu-divider v-if="item.divider" :key="`d-${item.event}`" />
</template> </template>
</Menu> </a-menu>
</template> </template>
</Dropdown> </a-dropdown>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { computed, PropType } from 'vue'; import { computed, PropType } from 'vue';
import type { DropMenu } from './typing'; import type { DropMenu } from './typing';
import { defineComponent } from 'vue';
import { Dropdown, Menu, Popconfirm } from 'ant-design-vue'; import { Dropdown, Menu, Popconfirm } from 'ant-design-vue';
import { Icon } from '/@/components/Icon'; import { Icon } from '/@/components/Icon';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
export default defineComponent({ const ADropdown = Dropdown;
name: 'BasicDropdown', const AMenu = Menu;
components: { const AMenuItem = Menu.Item;
Dropdown, const AMenuDivider = Menu.Divider;
Menu, const APopconfirm = Popconfirm;
MenuItem: Menu.Item,
MenuDivider: Menu.Divider, const props = defineProps({
Icon,
Popconfirm,
},
props: {
popconfirm: Boolean, popconfirm: Boolean,
/** /**
* the trigger mode which executes the drop-down action * the trigger mode which executes the drop-down action
@ -76,9 +70,10 @@
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
default: () => [], default: () => [],
}, },
}, });
emits: ['menuEvent'],
setup(props, { emit }) { const emit = defineEmits(['menuEvent']);
function handleClickMenu(item: DropMenu) { function handleClickMenu(item: DropMenu) {
const { event } = item; const { event } = item;
const menu = props.dropMenuList.find((item) => `${item.event}` === `${event}`); const menu = props.dropMenuList.find((item) => `${item.event}` === `${event}`);
@ -97,11 +92,5 @@
}; };
}); });
return { const getAttr = (key: string | number) => ({ key });
handleClickMenu,
getPopConfirmAttrs,
getAttr: (key: string | number) => ({ key }),
};
},
});
</script> </script>

View File

@ -15,12 +15,25 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, unref } from 'vue'; import { defineComponent, ref, unref } from 'vue';
import XLSX from 'xlsx'; import XLSX from 'xlsx';
import { dateUtil } from '/@/utils/dateUtil';
import type { ExcelData } from './typing'; import type { ExcelData } from './typing';
export default defineComponent({ export default defineComponent({
name: 'ImportExcel', name: 'ImportExcel',
props: {
// Date
dateFormat: {
type: String,
},
// +08:00
// https://github.com/SheetJS/sheetjs/issues/1470#issuecomment-501108554
timeZone: {
type: Number,
default: 8,
},
},
emits: ['success', 'error'], emits: ['success', 'error'],
setup(_, { emit }) { setup(props, { emit }) {
const inputRef = ref<HTMLInputElement | null>(null); const inputRef = ref<HTMLInputElement | null>(null);
const loadingRef = ref<Boolean>(false); const loadingRef = ref<Boolean>(false);
@ -51,10 +64,28 @@
*/ */
function getExcelData(workbook: XLSX.WorkBook) { function getExcelData(workbook: XLSX.WorkBook) {
const excelData: ExcelData[] = []; const excelData: ExcelData[] = [];
const { dateFormat, timeZone } = props;
for (const sheetName of workbook.SheetNames) { for (const sheetName of workbook.SheetNames) {
const worksheet = workbook.Sheets[sheetName]; const worksheet = workbook.Sheets[sheetName];
const header: string[] = getHeaderRow(worksheet); const header: string[] = getHeaderRow(worksheet);
const results = XLSX.utils.sheet_to_json(worksheet); let results = XLSX.utils.sheet_to_json(worksheet, {
raw: true,
dateNF: dateFormat, //Not worked
}) as object[];
results = results.map((row: object) => {
for (let field in row) {
if (row[field] instanceof Date) {
if (timeZone === 8) {
row[field].setSeconds(row[field].getSeconds() + 43);
}
if (dateFormat) {
row[field] = dateUtil(row[field]).format(dateFormat);
}
}
}
return row;
});
excelData.push({ excelData.push({
header, header,
results, results,
@ -76,7 +107,7 @@
reader.onload = async (e) => { reader.onload = async (e) => {
try { try {
const data = e.target && e.target.result; const data = e.target && e.target.result;
const workbook = XLSX.read(data, { type: 'array' }); const workbook = XLSX.read(data, { type: 'array', cellDates: true });
// console.log(workbook); // console.log(workbook);
/* DO SOMETHING WITH workbook HERE */ /* DO SOMETHING WITH workbook HERE */
const excelData = getExcelData(workbook); const excelData = getExcelData(workbook);

View File

@ -9,5 +9,6 @@ export { useForm } from './src/hooks/useForm';
export { default as ApiSelect } from './src/components/ApiSelect.vue'; export { default as ApiSelect } from './src/components/ApiSelect.vue';
export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue'; export { default as RadioButtonGroup } from './src/components/RadioButtonGroup.vue';
export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue'; export { default as ApiTreeSelect } from './src/components/ApiTreeSelect.vue';
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
export { BasicForm }; export { BasicForm };

View File

@ -21,6 +21,7 @@ import {
Divider, Divider,
} from 'ant-design-vue'; } from 'ant-design-vue';
import ApiRadioGroup from './components/ApiRadioGroup.vue';
import RadioButtonGroup from './components/RadioButtonGroup.vue'; import RadioButtonGroup from './components/RadioButtonGroup.vue';
import ApiSelect from './components/ApiSelect.vue'; import ApiSelect from './components/ApiSelect.vue';
import ApiTreeSelect from './components/ApiTreeSelect.vue'; import ApiTreeSelect from './components/ApiTreeSelect.vue';
@ -43,6 +44,7 @@ componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect); componentMap.set('ApiSelect', ApiSelect);
componentMap.set('TreeSelect', TreeSelect); componentMap.set('TreeSelect', TreeSelect);
componentMap.set('ApiTreeSelect', ApiTreeSelect); componentMap.set('ApiTreeSelect', ApiTreeSelect);
componentMap.set('ApiRadioGroup', ApiRadioGroup);
componentMap.set('Switch', Switch); componentMap.set('Switch', Switch);
componentMap.set('RadioButtonGroup', RadioButtonGroup); componentMap.set('RadioButtonGroup', RadioButtonGroup);
componentMap.set('RadioGroup', Radio.Group); componentMap.set('RadioGroup', Radio.Group);

View File

@ -0,0 +1,130 @@
<!--
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
-->
<template>
<RadioGroup v-bind="attrs" v-model:value="state" button-style="solid" @change="handleChange">
<template v-for="item in getOptions" :key="`${item.value}`">
<RadioButton v-if="props.isBtn" :value="item.value" :disabled="item.disabled">
{{ item.label }}
</RadioButton>
<Radio v-else :value="item.value" :disabled="item.disabled">
{{ item.label }}
</Radio>
</template>
</RadioGroup>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, watchEffect, computed, unref, watch } from 'vue';
import { Radio } from 'ant-design-vue';
import { isFunction } from '/@/utils/is';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { propTypes } from '/@/utils/propTypes';
import { get, omit } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n';
type OptionsItem = { label: string; value: string | number | boolean; disabled?: boolean };
export default defineComponent({
name: 'ApiRadioGroup',
components: {
RadioGroup: Radio.Group,
RadioButton: Radio.Button,
Radio,
},
props: {
api: {
type: Function as PropType<(arg?: Recordable | string) => Promise<OptionsItem[]>>,
default: null,
},
params: {
type: [Object, String] as PropType<Recordable | string>,
default: () => ({}),
},
value: {
type: [String, Number, Boolean] as PropType<string | number | boolean>,
},
isBtn: {
type: [Boolean] as PropType<boolean>,
default: false,
},
numberToString: propTypes.bool,
resultField: propTypes.string.def(''),
labelField: propTypes.string.def('label'),
valueField: propTypes.string.def('value'),
immediate: propTypes.bool.def(true),
},
emits: ['options-change', 'change'],
setup(props, { emit }) {
const options = ref<OptionsItem[]>([]);
const loading = ref(false);
const isFirstLoad = ref(true);
const emitData = ref<any[]>([]);
const attrs = useAttrs();
const { t } = useI18n();
// Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props);
// Processing options value
const getOptions = computed(() => {
const { labelField, valueField, numberToString } = props;
return unref(options).reduce((prev, next: Recordable) => {
if (next) {
const value = next[valueField];
prev.push({
label: next[labelField],
value: numberToString ? `${value}` : value,
...omit(next, [labelField, valueField]),
});
}
return prev;
}, [] as OptionsItem[]);
});
watchEffect(() => {
props.immediate && fetch();
});
watch(
() => props.params,
() => {
!unref(isFirstLoad) && fetch();
},
{ deep: true },
);
async function fetch() {
const api = props.api;
if (!api || !isFunction(api)) return;
options.value = [];
try {
loading.value = true;
const res = await api(props.params);
if (Array.isArray(res)) {
options.value = res;
emitChange();
return;
}
if (props.resultField) {
options.value = get(res, props.resultField) || [];
}
emitChange();
} catch (error) {
console.warn(error);
} finally {
loading.value = false;
}
}
function emitChange() {
emit('options-change', unref(getOptions));
}
function handleChange(_, ...args) {
emitData.value = args;
}
return { state, getOptions, attrs, loading, t, handleChange, props };
},
});
</script>

View File

@ -77,9 +77,9 @@
if (next) { if (next) {
const value = next[valueField]; const value = next[valueField];
prev.push({ prev.push({
...omit(next, [labelField, valueField]),
label: next[labelField], label: next[labelField],
value: numberToString ? `${value}` : value, value: numberToString ? `${value}` : value,
...omit(next, [labelField, valueField]),
}); });
} }
return prev; return prev;

View File

@ -44,7 +44,7 @@
watch( watch(
() => props.params, () => props.params,
() => { () => {
isFirstLoaded.value && fetch(); !unref(isFirstLoaded) && fetch();
}, },
{ deep: true }, { deep: true },
); );

View File

@ -340,7 +340,7 @@
wrapperCol={wrapperCol} wrapperCol={wrapperCol}
> >
<div style="display:flex"> <div style="display:flex">
<div style="flex:1">{getContent()}</div> <div style="flex:1;">{getContent()}</div>
{showSuffix && <span class="suffix">{getSuffix}</span>} {showSuffix && <span class="suffix">{getSuffix}</span>}
</div> </div>
</Form.Item> </Form.Item>

View File

@ -129,7 +129,7 @@ export interface FormSchema {
// Variable name bound to v-model Default value // Variable name bound to v-model Default value
valueField?: string; valueField?: string;
// Label name // Label name
label: string; label: string | VNode;
// Auxiliary text // Auxiliary text
subLabel?: string; subLabel?: string;
// Help text on the right side of the text // Help text on the right side of the text

View File

@ -92,6 +92,7 @@ export type ComponentType =
| 'ApiSelect' | 'ApiSelect'
| 'TreeSelect' | 'TreeSelect'
| 'ApiTreeSelect' | 'ApiTreeSelect'
| 'ApiRadioGroup'
| 'RadioButtonGroup' | 'RadioButtonGroup'
| 'RadioGroup' | 'RadioGroup'
| 'Checkbox' | 'Checkbox'

View File

@ -7,7 +7,7 @@
v-model:value="currentSelect" v-model:value="currentSelect"
> >
<template #addonAfter> <template #addonAfter>
<Popover <a-popover
placement="bottomLeft" placement="bottomLeft"
trigger="click" trigger="click"
v-model="visible" v-model="visible"
@ -17,7 +17,7 @@
<div class="flex justify-between"> <div class="flex justify-between">
<a-input <a-input
:placeholder="t('component.icon.search')" :placeholder="t('component.icon.search')"
@change="handleSearchChange" @change="debounceHandleSearchChange"
allowClear allowClear
/> />
</div> </div>
@ -53,7 +53,7 @@
</ul> </ul>
</ScrollContainer> </ScrollContainer>
<div class="flex py-2 items-center justify-center" v-if="getTotal >= pageSize"> <div class="flex py-2 items-center justify-center" v-if="getTotal >= pageSize">
<Pagination <a-pagination
showLessItems showLessItems
size="small" size="small"
:pageSize="pageSize" :pageSize="pageSize"
@ -63,7 +63,7 @@
</div> </div>
</div> </div>
<template v-else <template v-else
><div class="p-5"><Empty /></div> ><div class="p-5"><a-empty /></div>
</template> </template>
</template> </template>
@ -71,16 +71,14 @@
<SvgIcon :name="currentSelect" /> <SvgIcon :name="currentSelect" />
</span> </span>
<Icon :icon="currentSelect || 'ion:apps-outline'" class="cursor-pointer px-2 py-1" v-else /> <Icon :icon="currentSelect || 'ion:apps-outline'" class="cursor-pointer px-2 py-1" v-else />
</Popover> </a-popover>
</template> </template>
</a-input> </a-input>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, ref, watchEffect, watch, unref } from 'vue'; import { ref, watchEffect, watch, unref } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign'; import { useDesign } from '/@/hooks/web/useDesign';
import { ScrollContainer } from '/@/components/Container'; import { ScrollContainer } from '/@/components/Container';
import { Input, Popover, Pagination, Empty } from 'ant-design-vue'; import { Input, Popover, Pagination, Empty } from 'ant-design-vue';
import Icon from './Icon.vue'; import Icon from './Icon.vue';
import SvgIcon from './SvgIcon.vue'; import SvgIcon from './SvgIcon.vue';
@ -94,6 +92,12 @@
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import svgIcons from 'virtual:svg-icons-names'; import svgIcons from 'virtual:svg-icons-names';
// 使WebStormunused
const AInput = Input;
const APopover = Popover;
const APagination = Pagination;
const AEmpty = Empty;
function getIcons() { function getIcons() {
const data = iconsData as any; const data = iconsData as any;
const prefix: string = data?.prefix ?? ''; const prefix: string = data?.prefix ?? '';
@ -110,19 +114,16 @@
return svgIcons.map((icon) => icon.replace('icon-', '')); return svgIcons.map((icon) => icon.replace('icon-', ''));
} }
export default defineComponent({ const props = defineProps({
name: 'IconPicker',
components: { [Input.name]: Input, Icon, Popover, ScrollContainer, Pagination, Empty, SvgIcon },
inheritAttrs: false,
props: {
value: propTypes.string, value: propTypes.string,
width: propTypes.string.def('100%'), width: propTypes.string.def('100%'),
pageSize: propTypes.number.def(140), pageSize: propTypes.number.def(140),
copy: propTypes.bool.def(false), copy: propTypes.bool.def(false),
mode: propTypes.oneOf<('svg' | 'iconify')[]>(['svg', 'iconify']).def('iconify'), mode: propTypes.oneOf<('svg' | 'iconify')[]>(['svg', 'iconify']).def('iconify'),
}, });
emits: ['change', 'update:value'],
setup(props, { emit }) { const emit = defineEmits(['change', 'update:value']);
const isSvgMode = props.mode === 'svg'; const isSvgMode = props.mode === 'svg';
const icons = isSvgMode ? getSvgIcons() : getIcons(); const icons = isSvgMode ? getSvgIcons() : getIcons();
@ -177,21 +178,6 @@
} }
currentList.value = icons.filter((item) => item.includes(value)); currentList.value = icons.filter((item) => item.includes(value));
} }
return {
t,
prefixCls,
visible,
isSvgMode,
getTotal,
getPaginationList,
handlePageChange,
handleClick,
currentSelect,
handleSearchChange: debounceHandleSearchChange,
};
},
});
</script> </script>
<style lang="less"> <style lang="less">
@prefix-cls: ~'@{namespace}-icon-picker'; @prefix-cls: ~'@{namespace}-icon-picker';

View File

@ -1,5 +1,10 @@
<template> <template>
<section class="full-loading" :class="{ absolute }" v-show="loading"> <section
class="full-loading"
:class="{ absolute, [theme]: !!theme }"
:style="[background ? `background-color: ${background}` : '']"
v-show="loading"
>
<Spin v-bind="$attrs" :tip="tip" :size="size" :spinning="loading" /> <Spin v-bind="$attrs" :tip="tip" :size="size" :spinning="loading" />
</section> </section>
</template> </template>
@ -35,6 +40,9 @@
background: { background: {
type: String as PropType<string>, type: String as PropType<string>,
}, },
theme: {
type: String as PropType<'dark' | 'light'>,
},
}, },
}); });
</script> </script>
@ -60,8 +68,12 @@
} }
html[data-theme='dark'] { html[data-theme='dark'] {
.full-loading { .full-loading:not(.light) {
background-color: @modal-mask-bg; background-color: @modal-mask-bg;
} }
} }
.full-loading.dark {
background-color: @modal-mask-bg;
}
</style> </style>

View File

@ -134,7 +134,9 @@
isClickGo.value = false; isClickGo.value = false;
return; return;
} }
const path = (route || unref(currentRoute)).path; const path =
(route || unref(currentRoute)).meta?.currentActiveMenu ||
(route || unref(currentRoute)).path;
setOpenKeys(path); setOpenKeys(path);
if (unref(currentActiveMenu)) return; if (unref(currentActiveMenu)) return;
if (props.isHorizontal && unref(getSplit)) { if (props.isHorizontal && unref(getSplit)) {

View File

@ -1,5 +1,5 @@
<template> <template>
<Modal v-bind="getBindValue"> <Modal v-bind="getBindValue" @cancel="handleCancel">
<template #closeIcon v-if="!$slots.closeIcon"> <template #closeIcon v-if="!$slots.closeIcon">
<ModalClose <ModalClose
:canFullscreen="getProps.canFullscreen" :canFullscreen="getProps.canFullscreen"
@ -72,6 +72,7 @@
import { basicProps } from './props'; import { basicProps } from './props';
import { useFullScreen } from './hooks/useModalFullScreen'; import { useFullScreen } from './hooks/useModalFullScreen';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
import { useDesign } from '/@/hooks/web/useDesign';
export default defineComponent({ export default defineComponent({
name: 'BasicModal', name: 'BasicModal',
@ -83,6 +84,7 @@
const visibleRef = ref(false); const visibleRef = ref(false);
const propsRef = ref<Partial<ModalProps> | null>(null); const propsRef = ref<Partial<ModalProps> | null>(null);
const modalWrapperRef = ref<any>(null); const modalWrapperRef = ref<any>(null);
const { prefixCls } = useDesign('basic-modal');
// modal Bottom and top height // modal Bottom and top height
const extHeightRef = ref(0); const extHeightRef = ref(0);
@ -175,7 +177,8 @@
// //
async function handleCancel(e: Event) { async function handleCancel(e: Event) {
e?.stopPropagation(); e?.stopPropagation();
//
if ((e.target as HTMLElement)?.classList?.contains(prefixCls + '-close--custom')) return;
if (props.closeFunc && isFunction(props.closeFunc)) { if (props.closeFunc && isFunction(props.closeFunc)) {
const isClose: boolean = await props.closeFunc(); const isClose: boolean = await props.closeFunc();
visibleRef.value = !isClose; visibleRef.value = !isClose;

View File

@ -9,6 +9,7 @@ export default defineComponent({
name: 'Modal', name: 'Modal',
inheritAttrs: false, inheritAttrs: false,
props: basicProps, props: basicProps,
emits: ['cancel'],
setup(props, { slots }) { setup(props, { slots }) {
const { visible, draggable, destroyOnClose } = toRefs(props); const { visible, draggable, destroyOnClose } = toRefs(props);
const attrs = useAttrs(); const attrs = useAttrs();

View File

@ -97,7 +97,7 @@
} }
} }
& span:nth-child(2) { & span:last-child {
&:hover { &:hover {
color: @error-color; color: @error-color;
} }

View File

@ -17,5 +17,6 @@
}, },
title: { type: String }, title: { type: String },
}, },
emits: ['dblclick'],
}); });
</script> </script>

View File

@ -222,7 +222,6 @@
const getBindValues = computed(() => { const getBindValues = computed(() => {
const dataSource = unref(getDataSourceRef); const dataSource = unref(getDataSourceRef);
let propsData: Recordable = { let propsData: Recordable = {
size: 'middle',
// ...(dataSource.length === 0 ? { getPopupContainer: () => document.body } : {}), // ...(dataSource.length === 0 ? { getPopupContainer: () => document.body } : {}),
...attrs, ...attrs,
customRow, customRow,
@ -365,12 +364,6 @@
} }
} }
&--inset {
.ant-table-wrapper {
padding: 0;
}
}
.ant-tag { .ant-tag {
margin-right: 0; margin-right: 0;
} }
@ -431,5 +424,11 @@
padding: 12px 8px; padding: 12px 8px;
} }
} }
&--inset {
.ant-table-wrapper {
padding: 0;
}
}
} }
</style> </style>

View File

@ -246,7 +246,7 @@
if (!record) return false; if (!record) return false;
const { key, dataIndex } = column; const { key, dataIndex } = column;
const value = unref(currentValueRef); const value = unref(currentValueRef);
if (!key || !dataIndex) return; if (!key && !dataIndex) return;
const dataKey = (dataIndex || key) as string; const dataKey = (dataIndex || key) as string;

View File

@ -2,7 +2,14 @@ import componentSetting from '/@/settings/componentSetting';
const { table } = componentSetting; const { table } = componentSetting;
const { pageSizeOptions, defaultPageSize, fetchSetting, defaultSortFn, defaultFilterFn } = table; const {
pageSizeOptions,
defaultPageSize,
fetchSetting,
defaultSize,
defaultSortFn,
defaultFilterFn,
} = table;
export const ROW_KEY = 'key'; export const ROW_KEY = 'key';
@ -15,6 +22,9 @@ export const PAGE_SIZE = defaultPageSize;
// Common interface field settings // Common interface field settings
export const FETCH_SETTING = fetchSetting; export const FETCH_SETTING = fetchSetting;
// Default Size
export const DEFAULT_SIZE = defaultSize;
// Configure general sort function // Configure general sort function
export const DEFAULT_SORT_FN = defaultSortFn; export const DEFAULT_SORT_FN = defaultSortFn;

View File

@ -46,6 +46,14 @@ export function useCustomRow(
const isCheckbox = rowSelection.type === 'checkbox'; const isCheckbox = rowSelection.type === 'checkbox';
if (isCheckbox) { if (isCheckbox) {
// 找到tr
const tr: HTMLElement = (e as MouseEvent)
.composedPath?.()
.find((dom: HTMLElement) => dom.tagName === 'TR') as HTMLElement;
if (!tr) return;
// 找到Checkbox检查是否为disabled
const checkBox = tr.querySelector('input[type=checkbox]');
if (!checkBox || checkBox.hasAttribute('disabled')) return;
if (!keys.includes(key)) { if (!keys.includes(key)) {
setSelectedRowKeys([...keys, key]); setSelectedRowKeys([...keys, key]);
return; return;

View File

@ -160,21 +160,39 @@ export function useDataSource(
} }
} }
function deleteTableDataRecord(record: Recordable | Recordable[]): Recordable | undefined { function deleteTableDataRecord(rowKey: string | number | string[] | number[]) {
if (!dataSourceRef.value || dataSourceRef.value.length == 0) return; if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
const records = !Array.isArray(record) ? [record] : record; const rowKeyName = unref(getRowKey);
const recordIndex = records if (!rowKeyName) return;
.map((item) => dataSourceRef.value.findIndex((s) => s.key === item.key)) // 取序号 const rowKeys = !Array.isArray(rowKey) ? [rowKey] : rowKey;
.filter((item) => item !== undefined) for (const key of rowKeys) {
.sort((a, b) => b - a); // 从大到小排序 let index: number | undefined = dataSourceRef.value.findIndex((row) => {
for (const index of recordIndex) { let targetKeyName: string;
unref(dataSourceRef).splice(index, 1); if (typeof rowKeyName === 'function') {
targetKeyName = rowKeyName(row);
} else {
targetKeyName = rowKeyName as string;
}
return row[targetKeyName] === key;
});
if (index >= 0) {
dataSourceRef.value.splice(index, 1);
}
index = unref(propsRef).dataSource?.findIndex((row) => {
let targetKeyName: string;
if (typeof rowKeyName === 'function') {
targetKeyName = rowKeyName(row);
} else {
targetKeyName = rowKeyName as string;
}
return row[targetKeyName] === key;
});
if (typeof index !== 'undefined' && index !== -1)
unref(propsRef).dataSource?.splice(index, 1); unref(propsRef).dataSource?.splice(index, 1);
} }
setPagination({ setPagination({
total: unref(propsRef).dataSource?.length, total: unref(propsRef).dataSource?.length,
}); });
return unref(propsRef).dataSource;
} }
function insertTableDataRecord(record: Recordable, index: number): Recordable | undefined { function insertTableDataRecord(record: Recordable, index: number): Recordable | undefined {
@ -223,8 +241,16 @@ export function useDataSource(
} }
async function fetch(opt?: FetchParams) { async function fetch(opt?: FetchParams) {
const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } = const {
unref(propsRef); api,
searchInfo,
defSort,
fetchSetting,
beforeFetch,
afterFetch,
useSearchForm,
pagination,
} = unref(propsRef);
if (!api || !isFunction(api)) return; if (!api || !isFunction(api)) return;
try { try {
setLoading(true); setLoading(true);
@ -251,6 +277,7 @@ export function useDataSource(
...(useSearchForm ? getFieldsValue() : {}), ...(useSearchForm ? getFieldsValue() : {}),
...searchInfo, ...searchInfo,
...(opt?.searchInfo ?? {}), ...(opt?.searchInfo ?? {}),
...defSort,
...sortInfo, ...sortInfo,
...filterInfo, ...filterInfo,
...(opt?.sortInfo ?? {}), ...(opt?.sortInfo ?? {}),
@ -275,7 +302,7 @@ export function useDataSource(
setPagination({ setPagination({
current: currentTotalPage, current: currentTotalPage,
}); });
fetch(opt); return await fetch(opt);
} }
} }
@ -295,6 +322,7 @@ export function useDataSource(
items: unref(resultItems), items: unref(resultItems),
total: resultTotal, total: resultTotal,
}); });
return resultItems;
} catch (error) { } catch (error) {
emit('fetch-error', error); emit('fetch-error', error);
dataSourceRef.value = []; dataSourceRef.value = [];
@ -319,7 +347,7 @@ export function useDataSource(
} }
async function reload(opt?: FetchParams) { async function reload(opt?: FetchParams) {
await fetch(opt); return await fetch(opt);
} }
onMounted(() => { onMounted(() => {

View File

@ -1,6 +1,6 @@
import type { PaginationProps } from '../types/pagination'; import type { PaginationProps } from '../types/pagination';
import type { BasicTableProps } from '../types/table'; import type { BasicTableProps } from '../types/table';
import { computed, unref, ref, ComputedRef, watchEffect } from 'vue'; import { computed, unref, ref, ComputedRef, watch } from 'vue';
import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'; import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue';
import { isBoolean } from '/@/utils/is'; import { isBoolean } from '/@/utils/is';
import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../const'; import { PAGE_SIZE, PAGE_SIZE_OPTIONS } from '../const';
@ -27,15 +27,17 @@ export function usePagination(refProps: ComputedRef<BasicTableProps>) {
const configRef = ref<PaginationProps>({}); const configRef = ref<PaginationProps>({});
const show = ref(true); const show = ref(true);
watchEffect(() => { watch(
const { pagination } = unref(refProps); () => unref(refProps).pagination,
(pagination) => {
if (!isBoolean(pagination) && pagination) { if (!isBoolean(pagination) && pagination) {
configRef.value = { configRef.value = {
...unref(configRef), ...unref(configRef),
...(pagination ?? {}), ...(pagination ?? {}),
}; };
} }
}); },
);
const getPaginationInfo = computed((): PaginationProps | boolean => { const getPaginationInfo = computed((): PaginationProps | boolean => {
const { pagination } = unref(refProps); const { pagination } = unref(refProps);

View File

@ -68,7 +68,7 @@ export function useTable(tableProps?: Props): [
getForm: () => FormActionType; getForm: () => FormActionType;
} = { } = {
reload: async (opt?: FetchParams) => { reload: async (opt?: FetchParams) => {
getTableInstance().reload(opt); return await getTableInstance().reload(opt);
}, },
setProps: (props: Partial<BasicTableProps>) => { setProps: (props: Partial<BasicTableProps>) => {
getTableInstance().setProps(props); getTableInstance().setProps(props);
@ -122,8 +122,8 @@ export function useTable(tableProps?: Props): [
updateTableData: (index: number, key: string, value: any) => { updateTableData: (index: number, key: string, value: any) => {
return getTableInstance().updateTableData(index, key, value); return getTableInstance().updateTableData(index, key, value);
}, },
deleteTableDataRecord: (record: Recordable | Recordable[]) => { deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => {
return getTableInstance().deleteTableDataRecord(record); return getTableInstance().deleteTableDataRecord(rowKey);
}, },
insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => { insertTableDataRecord: (record: Recordable | Recordable[], index?: number) => {
return getTableInstance().insertTableDataRecord(record, index); return getTableInstance().insertTableDataRecord(record, index);

View File

@ -7,9 +7,10 @@ import type {
SorterResult, SorterResult,
TableCustomRecord, TableCustomRecord,
TableRowSelection, TableRowSelection,
SizeType,
} from './types/table'; } from './types/table';
import type { FormProps } from '/@/components/Form'; import type { FormProps } from '/@/components/Form';
import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING } from './const'; import { DEFAULT_FILTER_FN, DEFAULT_SORT_FN, FETCH_SETTING, DEFAULT_SIZE } from './const';
import { propTypes } from '/@/utils/propTypes'; import { propTypes } from '/@/utils/propTypes';
export const basicProps = { export const basicProps = {
@ -69,6 +70,11 @@ export const basicProps = {
type: Object as PropType<Recordable>, type: Object as PropType<Recordable>,
default: null, default: null,
}, },
// 默认的排序参数
defSort: {
type: Object as PropType<Recordable>,
default: null,
},
// 使用搜索表单 // 使用搜索表单
useSearchForm: propTypes.bool, useSearchForm: propTypes.bool,
// 表单配置 // 表单配置
@ -136,4 +142,8 @@ export const basicProps = {
}) => Promise<any> }) => Promise<any>
>, >,
}, },
size: {
type: String as PropType<SizeType>,
default: DEFAULT_SIZE,
},
}; };

View File

@ -95,7 +95,7 @@ export interface TableActionType {
setPagination: (info: Partial<PaginationProps>) => void; setPagination: (info: Partial<PaginationProps>) => void;
setTableData: <T = Recordable>(values: T[]) => void; setTableData: <T = Recordable>(values: T[]) => void;
updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void; updateTableDataRecord: (rowKey: string | number, record: Recordable) => Recordable | void;
deleteTableDataRecord: (record: Recordable | Recordable[]) => Recordable | void; deleteTableDataRecord: (rowKey: string | number | string[] | number[]) => void;
insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void; insertTableDataRecord: (record: Recordable, index?: number) => Recordable | void;
findTableDataRecord: (rowKey: string | number) => Recordable | void; findTableDataRecord: (rowKey: string | number) => Recordable | void;
getColumns: (opt?: GetColumnsParams) => BasicColumn[]; getColumns: (opt?: GetColumnsParams) => BasicColumn[];
@ -176,6 +176,8 @@ export interface BasicTableProps<T = any> {
emptyDataIsShowTable?: boolean; emptyDataIsShowTable?: boolean;
// 额外的请求参数 // 额外的请求参数
searchInfo?: Recordable; searchInfo?: Recordable;
// 默认的排序参数
defSort?: Recordable;
// 使用搜索表单 // 使用搜索表单
useSearchForm?: boolean; useSearchForm?: boolean;
// 表单配置 // 表单配置

View File

@ -422,8 +422,8 @@
class={`${prefixCls}-title pl-2`} class={`${prefixCls}-title pl-2`}
onClick={handleClickNode.bind(null, item[keyField], item[childrenField])} onClick={handleClickNode.bind(null, item[keyField], item[childrenField])}
> >
{slots?.title ? ( {item.slots?.title ? (
getSlot(slots, 'title', item) getSlot(slots, item.slots?.title, item)
) : ( ) : (
<> <>
{icon && <TreeIcon icon={icon} />} {icon && <TreeIcon icon={icon} />}

View File

@ -187,8 +187,12 @@
item.status = UploadResultStatus.UPLOADING; item.status = UploadResultStatus.UPLOADING;
const { data } = await props.api?.( const { data } = await props.api?.(
{ {
data: {
...(props.uploadParams || {}), ...(props.uploadParams || {}),
},
file: item.file, file: item.file,
name: props.name,
filename: props.filename,
}, },
function onUploadProgress(progressEvent: ProgressEvent) { function onUploadProgress(progressEvent: ProgressEvent) {
const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0; const complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0;

View File

@ -34,6 +34,14 @@ export const basicProps = {
default: null, default: null,
required: true, required: true,
}, },
name: {
type: String as PropType<string>,
default: 'file',
},
filename: {
type: String as PropType<string>,
default: null,
},
}; };
export const uploadContainerProps = { export const uploadContainerProps = {

View File

@ -1,4 +1,4 @@
import type { UnwrapRef, Ref } from 'vue'; import type { UnwrapRef, Ref, WritableComputedRef, DeepReadonly } from 'vue';
import { import {
reactive, reactive,
readonly, readonly,
@ -12,6 +12,13 @@ import {
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
export function useRuleFormItem<T extends Recordable, K extends keyof T, V = UnwrapRef<T[K]>>(
props: T,
key?: K,
changeEvent?,
emitData?: Ref<any[]>,
): [WritableComputedRef<V>, (val: V) => void, DeepReadonly<V>];
export function useRuleFormItem<T extends Recordable>( export function useRuleFormItem<T extends Recordable>(
props: T, props: T,
key: keyof T = 'value', key: keyof T = 'value',

View File

@ -3,6 +3,7 @@ import { useI18n } from '/@/hooks/web/useI18n';
import { useTitle as usePageTitle } from '@vueuse/core'; import { useTitle as usePageTitle } from '@vueuse/core';
import { useGlobSetting } from '/@/hooks/setting'; import { useGlobSetting } from '/@/hooks/setting';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useLocaleStore } from '/@/store/modules/locale';
import { REDIRECT_NAME } from '/@/router/constant'; import { REDIRECT_NAME } from '/@/router/constant';
@ -13,11 +14,12 @@ export function useTitle() {
const { title } = useGlobSetting(); const { title } = useGlobSetting();
const { t } = useI18n(); const { t } = useI18n();
const { currentRoute } = useRouter(); const { currentRoute } = useRouter();
const localeStore = useLocaleStore();
const pageTitle = usePageTitle(); const pageTitle = usePageTitle();
watch( watch(
() => currentRoute.value.path, [() => currentRoute.value.path, () => localeStore.getLocale],
() => { () => {
const route = unref(currentRoute); const route = unref(currentRoute);

View File

@ -101,12 +101,12 @@
if (!meta) { if (!meta) {
return !!name; return !!name;
} }
const { title, hideBreadcrumb, hideMenu } = meta; const { title, hideBreadcrumb } = meta;
if (!title || hideBreadcrumb || hideMenu) { if (!title || hideBreadcrumb) {
return false; return false;
} }
return true; return true;
}).filter((item) => !item.meta?.hideBreadcrumb || !item.meta?.hideMenu); }).filter((item) => !item.meta?.hideBreadcrumb);
} }
function handleClick(route: RouteLocationMatched, paths: string[], e: Event) { function handleClick(route: RouteLocationMatched, paths: string[], e: Event) {

View File

@ -34,18 +34,21 @@ export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: Comp
const { meta } = unref(getTargetTab); const { meta } = unref(getTargetTab);
const { path } = unref(currentRoute); const { path } = unref(currentRoute);
// Refresh button
const curItem = state.current; const curItem = state.current;
const isCurItem = curItem ? curItem.path === path : false;
// Refresh button
const index = state.currentIndex; const index = state.currentIndex;
const refreshDisabled = curItem ? curItem.path !== path : true; const refreshDisabled = !isCurItem;
// Close left // Close left
const closeLeftDisabled = index === 0; const closeLeftDisabled = index === 0 || !isCurItem;
const disabled = tabStore.getTabList.length === 1; const disabled = tabStore.getTabList.length === 1;
// Close right // Close right
const closeRightDisabled = const closeRightDisabled =
index === tabStore.getTabList.length - 1 && tabStore.getLastDragEndIndex >= 0; !isCurItem || (index === tabStore.getTabList.length - 1 && tabStore.getLastDragEndIndex >= 0);
const dropMenuList: DropMenu[] = [ const dropMenuList: DropMenu[] = [
{ {
icon: 'ion:reload-sharp', icon: 'ion:reload-sharp',
@ -78,7 +81,7 @@ export function useTabDropdown(tabContentProps: TabContentProps, getIsTabs: Comp
icon: 'dashicons:align-center', icon: 'dashicons:align-center',
event: MenuEventEnum.CLOSE_OTHER, event: MenuEventEnum.CLOSE_OTHER,
text: t('layout.multipleTab.closeOther'), text: t('layout.multipleTab.closeOther'),
disabled: disabled, disabled: disabled || !isCurItem,
}, },
{ {
icon: 'clarity:minus-line', icon: 'clarity:minus-line',

View File

@ -84,6 +84,7 @@ export default {
breadcrumb: 'Breadcrumbs', breadcrumb: 'Breadcrumbs',
breadcrumbIcon: 'Breadcrumbs Icon', breadcrumbIcon: 'Breadcrumbs Icon',
tabs: 'Tabs', tabs: 'Tabs',
tabDetail: 'Tab Detail',
tabsQuickBtn: 'Tabs quick button', tabsQuickBtn: 'Tabs quick button',
tabsRedoBtn: 'Tabs redo button', tabsRedoBtn: 'Tabs redo button',
tabsFoldBtn: 'Tabs flod button', tabsFoldBtn: 'Tabs flod button',

View File

@ -67,6 +67,7 @@ export default {
feat: 'Page Function', feat: 'Page Function',
icon: 'Icon', icon: 'Icon',
tabs: 'Tabs', tabs: 'Tabs',
tabDetail: 'Tab Detail',
sessionTimeout: 'Session Timeout', sessionTimeout: 'Session Timeout',
print: 'Print', print: 'Print',
contextMenu: 'Context Menu', contextMenu: 'Context Menu',

View File

@ -84,6 +84,7 @@ export default {
breadcrumb: '面包屑', breadcrumb: '面包屑',
breadcrumbIcon: '面包屑图标', breadcrumbIcon: '面包屑图标',
tabs: '标签页', tabs: '标签页',
tabDetail: '标签详情页',
tabsQuickBtn: '标签页快捷按钮', tabsQuickBtn: '标签页快捷按钮',
tabsRedoBtn: '标签页刷新按钮', tabsRedoBtn: '标签页刷新按钮',
tabsFoldBtn: '标签页折叠按钮', tabsFoldBtn: '标签页折叠按钮',

View File

@ -67,6 +67,7 @@ export default {
icon: '图标', icon: '图标',
sessionTimeout: '登录过期', sessionTimeout: '登录过期',
tabs: '标签页操作', tabs: '标签页操作',
tabDetail: '标签详情页',
print: '打印', print: '打印',
contextMenu: '右键菜单', contextMenu: '右键菜单',
download: '文件下载', download: '文件下载',

View File

@ -4,7 +4,7 @@ export const PARENT_LAYOUT_NAME = 'ParentLayout';
export const PAGE_NOT_FOUND_NAME = 'PageNotFound'; export const PAGE_NOT_FOUND_NAME = 'PageNotFound';
export const EXCEPTION_COMPONENT = () => import('../views/sys/exception/Exception.vue'); export const EXCEPTION_COMPONENT = () => import('/@/views/sys/exception/Exception.vue');
/** /**
* @description: default layout * @description: default layout

View File

@ -8,12 +8,12 @@ import { removeTabChangeListener } from '/@/logics/mitt/routeChange';
export function createStateGuard(router: Router) { export function createStateGuard(router: Router) {
router.afterEach((to) => { router.afterEach((to) => {
// Just enter the login page and clear the authentication information
if (to.path === PageEnum.BASE_LOGIN) {
const tabStore = useMultipleTabStore(); const tabStore = useMultipleTabStore();
const userStore = useUserStore(); const userStore = useUserStore();
const appStore = useAppStore(); const appStore = useAppStore();
const permissionStore = usePermissionStore(); const permissionStore = usePermissionStore();
// Just enter the login page and clear the authentication information
if (to.path === PageEnum.BASE_LOGIN) {
appStore.resetAllState(); appStore.resetAllState();
permissionStore.resetState(); permissionStore.resetState();
tabStore.resetState(); tabStore.resetState();

View File

@ -1,7 +1,7 @@
import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types'; import type { AppRouteModule, AppRouteRecordRaw } from '/@/router/types';
import type { Router, RouteRecordNormalized } from 'vue-router'; import type { Router, RouteRecordNormalized } from 'vue-router';
import { getParentLayout, LAYOUT } from '/@/router/constant'; import { getParentLayout, LAYOUT, EXCEPTION_COMPONENT } from '/@/router/constant';
import { cloneDeep, omit } from 'lodash-es'; import { cloneDeep, omit } from 'lodash-es';
import { warn } from '/@/utils/log'; import { warn } from '/@/utils/log';
import { createRouter, createWebHashHistory } from 'vue-router'; import { createRouter, createWebHashHistory } from 'vue-router';
@ -27,7 +27,7 @@ function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
const { component, name } = item; const { component, name } = item;
const { children } = item; const { children } = item;
if (component) { if (component) {
const layoutFound = LayoutMap.get(component as string); const layoutFound = LayoutMap.get(component.toUpperCase());
if (layoutFound) { if (layoutFound) {
item.component = layoutFound; item.component = layoutFound;
} else { } else {
@ -46,20 +46,24 @@ function dynamicImport(
) { ) {
const keys = Object.keys(dynamicViewsModules); const keys = Object.keys(dynamicViewsModules);
const matchKeys = keys.filter((key) => { const matchKeys = keys.filter((key) => {
let k = key.replace('../../views', ''); const k = key.replace('../../views', '');
const lastIndex = k.lastIndexOf('.'); const startFlag = component.startsWith('/');
k = k.substring(0, lastIndex); const endFlag = component.endsWith('.vue') || component.endsWith('.tsx');
return k === component; const startIndex = startFlag ? 0 : 1;
const lastIndex = endFlag ? k.length : k.lastIndexOf('.');
return k.substring(startIndex, lastIndex) === component;
}); });
if (matchKeys?.length === 1) { if (matchKeys?.length === 1) {
const matchKey = matchKeys[0]; const matchKey = matchKeys[0];
return dynamicViewsModules[matchKey]; return dynamicViewsModules[matchKey];
} } else if (matchKeys?.length > 1) {
if (matchKeys?.length > 1) {
warn( warn(
'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure', 'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure',
); );
return; return;
} else {
warn('在src/views/下找不到`' + component + '.vue` 或 `' + component + '.tsx`, 请自行创建!');
return EXCEPTION_COMPONENT;
} }
} }
@ -80,6 +84,8 @@ export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModul
meta.affix = false; meta.affix = false;
route.meta = meta; route.meta = meta;
} }
} else {
warn('请正确配置路由:' + route?.name + '的component属性');
} }
route.children && asyncImportRoute(route.children); route.children && asyncImportRoute(route.children);
}); });

View File

@ -21,15 +21,21 @@ export default {
pageSizeOptions: ['10', '50', '80', '100'], pageSizeOptions: ['10', '50', '80', '100'],
// Default display quantity on one page // Default display quantity on one page
defaultPageSize: 10, defaultPageSize: 10,
// Default Size
defaultSize: 'middle',
// Custom general sort function // Custom general sort function
defaultSortFn: (sortInfo: SorterResult) => { defaultSortFn: (sortInfo: SorterResult) => {
const { field, order } = sortInfo; const { field, order } = sortInfo;
if (field && order) {
return { return {
// The sort field passed to the backend you // The sort field passed to the backend you
field, field,
// Sorting method passed to the background asc/desc // Sorting method passed to the background asc/desc
order, order,
}; };
} else {
return {};
}
}, },
// Custom general filter function // Custom general filter function
defaultFilterFn: (data: Partial<Recordable<string[]>>) => { defaultFilterFn: (data: Partial<Recordable<string[]>>) => {

View File

@ -26,6 +26,15 @@ function handleGotoPage(router: Router) {
go(unref(router.currentRoute).path, true); go(unref(router.currentRoute).path, true);
} }
const getToTarget = (tabItem: RouteLocationNormalized) => {
const { params, path, query } = tabItem;
return {
params: params || {},
path,
query: query || {},
};
};
const cacheTab = projectSetting.multiTabsSetting.cache; const cacheTab = projectSetting.multiTabsSetting.cache;
export const useMultipleTabStore = defineStore({ export const useMultipleTabStore = defineStore({
@ -110,7 +119,7 @@ export const useMultipleTabStore = defineStore({
}, },
async addTab(route: RouteLocationNormalized) { async addTab(route: RouteLocationNormalized) {
const { path, name, fullPath, params, query } = getRawRoute(route); const { path, name, fullPath, params, query, meta } = getRawRoute(route);
// 404 The page does not need to add a tab // 404 The page does not need to add a tab
if ( if (
path === PageEnum.ERROR_PAGE || path === PageEnum.ERROR_PAGE ||
@ -140,6 +149,22 @@ export const useMultipleTabStore = defineStore({
this.tabList.splice(updateIndex, 1, curTab); this.tabList.splice(updateIndex, 1, curTab);
} else { } else {
// Add tab // Add tab
// 获取动态路由打开数,超过 0 即代表需要控制打开数
const dynamicLevel = meta?.dynamicLevel ?? -1;
if (dynamicLevel > 0) {
// 如果动态路由层级大于 0 了,那么就要限制该路由的打开数限制了
// 首先获取到真实的路由,使用配置方式减少计算开销.
// const realName: string = path.match(/(\S*)\//)![1];
const realPath = meta?.realPath ?? '';
// 获取到已经打开的动态路由数, 判断是否大于某一个值
if (
this.tabList.filter((e) => e.meta?.realPath ?? '' === realPath).length >= dynamicLevel
) {
// 关闭第一个
const index = this.tabList.findIndex((item) => item.meta.realPath === realPath);
index !== -1 && this.tabList.splice(index, 1);
}
}
this.tabList.push(route); this.tabList.push(route);
} }
this.updateCacheTab(); this.updateCacheTab();
@ -147,15 +172,6 @@ export const useMultipleTabStore = defineStore({
}, },
async closeTab(tab: RouteLocationNormalized, router: Router) { async closeTab(tab: RouteLocationNormalized, router: Router) {
const getToTarget = (tabItem: RouteLocationNormalized) => {
const { params, path, query } = tabItem;
return {
params: params || {},
path,
query: query || {},
};
};
const close = (route: RouteLocationNormalized) => { const close = (route: RouteLocationNormalized) => {
const { fullPath, meta: { affix } = {} } = route; const { fullPath, meta: { affix } = {} } = route;
if (affix) { if (affix) {
@ -196,13 +212,36 @@ export const useMultipleTabStore = defineStore({
toTarget = getToTarget(page); toTarget = getToTarget(page);
} }
close(currentRoute.value); close(currentRoute.value);
replace(toTarget); await replace(toTarget);
}, },
// Close according to key // Close according to key
async closeTabByKey(key: string, router: Router) { async closeTabByKey(key: string, router: Router) {
const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === key); const index = this.tabList.findIndex((item) => (item.fullPath || item.path) === key);
index !== -1 && this.closeTab(this.tabList[index], router); if (index !== -1) {
await this.closeTab(this.tabList[index], router);
const { currentRoute, replace } = router;
// 检查当前路由是否存在于tabList中
const isActivated = this.tabList.findIndex((item) => {
return item.fullPath === currentRoute.value.fullPath;
});
// 如果当前路由不存在于TabList中尝试切换到其它路由
if (isActivated === -1) {
let pageIndex;
if (index > 0) {
pageIndex = index - 1;
} else if (index < this.tabList.length - 1) {
pageIndex = index + 1;
} else {
pageIndex = -1;
}
if (pageIndex >= 0) {
const page = this.tabList[index - 1];
const toTarget = getToTarget(page);
await replace(toTarget);
}
}
}
}, },
// Sort the tabs // Sort the tabs

View File

@ -1,11 +1,11 @@
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse, AxiosError } from 'axios'; import type { AxiosRequestConfig, AxiosInstance, AxiosResponse, AxiosError } from 'axios';
import type { RequestOptions, Result, UploadFileParams } from '../../../../types/axios'; import type { RequestOptions, Result, UploadFileParams } from '/#/axios';
import type { CreateAxiosOptions } from './axiosTransform'; import type { CreateAxiosOptions } from './axiosTransform';
import axios from 'axios'; import axios from 'axios';
import qs from 'qs'; import qs from 'qs';
import { AxiosCanceler } from './axiosCancel'; import { AxiosCanceler } from './axiosCancel';
import { isFunction } from '/@/utils/is'; import { isFunction } from '/@/utils/is';
import { cloneDeep, omit } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { ContentTypeEnum } from '/@/enums/httpEnum'; import { ContentTypeEnum } from '/@/enums/httpEnum';
import { RequestEnum } from '/@/enums/httpEnum'; import { RequestEnum } from '/@/enums/httpEnum';
@ -121,11 +121,17 @@ export class VAxios {
*/ */
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) { uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
const formData = new window.FormData(); const formData = new window.FormData();
const customFilename = params.name || 'file';
if (params.filename) {
formData.append(customFilename, params.file, params.filename);
} else {
formData.append(customFilename, params.file);
}
if (params.data) { if (params.data) {
Object.keys(params.data).forEach((key) => { Object.keys(params.data).forEach((key) => {
if (!params.data) return; const value = params.data![key];
const value = params.data[key];
if (Array.isArray(value)) { if (Array.isArray(value)) {
value.forEach((item) => { value.forEach((item) => {
formData.append(`${key}[]`, item); formData.append(`${key}[]`, item);
@ -133,15 +139,9 @@ export class VAxios {
return; return;
} }
formData.append(key, params.data[key]); formData.append(key, params.data![key]);
}); });
} }
formData.append(params.name || 'file', params.file, params.filename);
const customParams = omit(params, 'file', 'filename', 'file');
Object.keys(customParams).forEach((key) => {
formData.append(key, customParams[key]);
});
return this.axiosInstance.request<T>({ return this.axiosInstance.request<T>({
...config, ...config,

View File

@ -15,6 +15,7 @@ import { setObjToUrlParams, deepMerge } from '/@/utils';
import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog'; import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog';
import { useI18n } from '/@/hooks/web/useI18n'; import { useI18n } from '/@/hooks/web/useI18n';
import { joinTimestamp, formatRequestDate } from './helper'; import { joinTimestamp, formatRequestDate } from './helper';
import { useUserStoreWithOut } from '/@/store/modules/user';
const globSetting = useGlobSetting(); const globSetting = useGlobSetting();
const urlPrefix = globSetting.urlPrefix; const urlPrefix = globSetting.urlPrefix;
@ -61,6 +62,9 @@ const transform: AxiosTransform = {
switch (code) { switch (code) {
case ResultEnum.TIMEOUT: case ResultEnum.TIMEOUT:
timeoutMsg = t('sys.api.timeoutMessage'); timeoutMsg = t('sys.api.timeoutMessage');
const userStore = useUserStoreWithOut();
userStore.setToken(undefined);
userStore.logout(true);
break; break;
default: default:
if (message) { if (message) {
@ -136,7 +140,7 @@ const transform: AxiosTransform = {
const token = getToken(); const token = getToken();
if (token && (config as Recordable)?.requestOptions?.withToken !== false) { if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
// jwt token // jwt token
config.headers.Authorization = options.authenticationScheme (config as Recordable).headers.Authorization = options.authenticationScheme
? `${options.authenticationScheme} ${token}` ? `${options.authenticationScheme} ${token}`
: token; : token;
} }
@ -180,7 +184,7 @@ const transform: AxiosTransform = {
return Promise.reject(error); return Promise.reject(error);
} }
} catch (error) { } catch (error) {
throw new Error(error); throw new Error(error as unknown as string);
} }
checkStatus(error?.response?.status, msg, errorMessageMode); checkStatus(error?.response?.status, msg, errorMessageMode);

View File

@ -82,7 +82,7 @@
</Form> </Form>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, toRaw, unref, computed } from 'vue'; import { reactive, ref, unref, computed } from 'vue';
import { Checkbox, Form, Input, Row, Col, Button, Divider } from 'ant-design-vue'; import { Checkbox, Form, Input, Row, Col, Button, Divider } from 'ant-design-vue';
import { import {
@ -134,13 +134,11 @@
if (!data) return; if (!data) return;
try { try {
loading.value = true; loading.value = true;
const userInfo = await userStore.login( const userInfo = await userStore.login({
toRaw({
password: data.password, password: data.password,
username: data.account, username: data.account,
mode: 'none', // mode: 'none', //
}), });
);
if (userInfo) { if (userInfo) {
notification.success({ notification.success({
message: t('sys.login.loginSuccessTitle'), message: t('sys.login.loginSuccessTitle'),
@ -151,7 +149,7 @@
} catch (error) { } catch (error) {
createErrorModal({ createErrorModal({
title: t('sys.api.errorTip'), title: t('sys.api.errorTip'),
content: error.message || t('sys.api.networkExceptionMsg'), content: (error as unknown as Error).message || t('sys.api.networkExceptionMsg'),
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body, getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body,
}); });
} finally { } finally {

View File

@ -11,7 +11,7 @@
}, },
"dependencies": { "dependencies": {
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"koa": "^2.13.1", "koa": "^2.13.4",
"koa-body": "^4.2.0", "koa-body": "^4.2.0",
"koa-bodyparser": "^4.3.0", "koa-bodyparser": "^4.3.0",
"koa-route": "^3.2.0", "koa-route": "^3.2.0",
@ -24,13 +24,13 @@
"@types/koa": "^2.13.4", "@types/koa": "^2.13.4",
"@types/koa-bodyparser": "^5.0.2", "@types/koa-bodyparser": "^5.0.2",
"@types/koa-router": "^7.4.4", "@types/koa-router": "^7.4.4",
"@types/node": "^16.9.1", "@types/node": "^16.11.1",
"nodemon": "^2.0.12", "nodemon": "^2.0.14",
"pm2": "^5.1.1", "pm2": "^5.1.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-node": "^10.2.1", "ts-node": "^10.3.0",
"tsconfig-paths": "^3.11.0", "tsconfig-paths": "^3.11.0",
"tsup": "^4.14.0", "tsup": "^5.4.2",
"typescript": "^4.4.3" "typescript": "^4.4.4"
} }
} }

View File

@ -5,6 +5,10 @@ declare module 'vue-router' {
orderNo?: number; orderNo?: number;
// title // title
title: string; title: string;
// dynamic router level.
dynamicLevel?: number;
// dynamic router real route path (For performance).
realPath?: string;
// Whether to ignore permissions // Whether to ignore permissions
ignoreAuth?: boolean; ignoreAuth?: boolean;
// role info // role info

View File

@ -1,4 +1,3 @@
import colors from 'windicss/colors';
import { defineConfig } from 'vite-plugin-windicss'; import { defineConfig } from 'vite-plugin-windicss';
import { primaryColor } from './build/config/themeConfig'; import { primaryColor } from './build/config/themeConfig';
@ -11,7 +10,6 @@ export default defineConfig({
'-1': '-1', '-1': '-1',
}, },
colors: { colors: {
...colors,
primary: primaryColor, primary: primaryColor,
}, },
screens: { screens: {

9098
yarn.lock

File diff suppressed because it is too large Load Diff