mirror of
https://github.com/vbenjs/vben-admin-thin-next.git
synced 2025-02-02 18:08:40 +08:00
chore: merge branch 'main' of github.com:anncwb/vue-vben-admin into main
This commit is contained in:
commit
8efcba4861
@ -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
|
||||||
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
@ -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 = '';
|
||||||
}
|
}
|
||||||
|
@ -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`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -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:\/\//;
|
||||||
|
|
||||||
|
@ -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[];
|
||||||
|
@ -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[];
|
||||||
|
@ -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',
|
||||||
|
102
package.json
102
package.json
@ -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",
|
||||||
|
@ -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>
|
||||||
|
@ -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';
|
||||||
|
@ -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 },
|
||||||
});
|
});
|
||||||
|
@ -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 },
|
||||||
});
|
});
|
||||||
|
5
src/components/CodeEditor/src/typing.ts
Normal file
5
src/components/CodeEditor/src/typing.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum MODE {
|
||||||
|
JSON = 'application/json',
|
||||||
|
HTML = 'htmlmixed',
|
||||||
|
JS = 'javascript',
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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);
|
||||||
|
@ -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 };
|
||||||
|
@ -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);
|
||||||
|
130
src/components/Form/src/components/ApiRadioGroup.vue
Normal file
130
src/components/Form/src/components/ApiRadioGroup.vue
Normal 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>
|
@ -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;
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
watch(
|
watch(
|
||||||
() => props.params,
|
() => props.params,
|
||||||
() => {
|
() => {
|
||||||
isFirstLoaded.value && fetch();
|
!unref(isFirstLoaded) && fetch();
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -92,6 +92,7 @@ export type ComponentType =
|
|||||||
| 'ApiSelect'
|
| 'ApiSelect'
|
||||||
| 'TreeSelect'
|
| 'TreeSelect'
|
||||||
| 'ApiTreeSelect'
|
| 'ApiTreeSelect'
|
||||||
|
| 'ApiRadioGroup'
|
||||||
| 'RadioButtonGroup'
|
| 'RadioButtonGroup'
|
||||||
| 'RadioGroup'
|
| 'RadioGroup'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
// 没有使用别名引入,是因为WebStorm当前版本还不能正确识别,会报unused警告
|
||||||
|
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';
|
||||||
|
@ -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>
|
||||||
|
@ -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)) {
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
@ -97,7 +97,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& span:nth-child(2) {
|
& span:last-child {
|
||||||
&:hover {
|
&:hover {
|
||||||
color: @error-color;
|
color: @error-color;
|
||||||
}
|
}
|
||||||
|
@ -17,5 +17,6 @@
|
|||||||
},
|
},
|
||||||
title: { type: String },
|
title: { type: String },
|
||||||
},
|
},
|
||||||
|
emits: ['dblclick'],
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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(() => {
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
// 表单配置
|
// 表单配置
|
||||||
|
@ -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} />}
|
||||||
|
@ -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;
|
||||||
|
@ -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 = {
|
||||||
|
@ -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',
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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',
|
||||||
|
@ -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',
|
||||||
|
@ -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',
|
||||||
|
@ -84,6 +84,7 @@ export default {
|
|||||||
breadcrumb: '面包屑',
|
breadcrumb: '面包屑',
|
||||||
breadcrumbIcon: '面包屑图标',
|
breadcrumbIcon: '面包屑图标',
|
||||||
tabs: '标签页',
|
tabs: '标签页',
|
||||||
|
tabDetail: '标签详情页',
|
||||||
tabsQuickBtn: '标签页快捷按钮',
|
tabsQuickBtn: '标签页快捷按钮',
|
||||||
tabsRedoBtn: '标签页刷新按钮',
|
tabsRedoBtn: '标签页刷新按钮',
|
||||||
tabsFoldBtn: '标签页折叠按钮',
|
tabsFoldBtn: '标签页折叠按钮',
|
||||||
|
@ -67,6 +67,7 @@ export default {
|
|||||||
icon: '图标',
|
icon: '图标',
|
||||||
sessionTimeout: '登录过期',
|
sessionTimeout: '登录过期',
|
||||||
tabs: '标签页操作',
|
tabs: '标签页操作',
|
||||||
|
tabDetail: '标签详情页',
|
||||||
print: '打印',
|
print: '打印',
|
||||||
contextMenu: '右键菜单',
|
contextMenu: '右键菜单',
|
||||||
download: '文件下载',
|
download: '文件下载',
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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[]>>) => {
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
@ -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 {
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
types/vue-router.d.ts
vendored
4
types/vue-router.d.ts
vendored
@ -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
|
||||||
|
@ -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: {
|
||||||
|
Loading…
Reference in New Issue
Block a user