提交 418ca240 作者: zuihou

Merge remote-tracking branch 'origin/main'

// @ts-check module.exports = {
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true, root: true,
env: { env: {
browser: true, browser: true,
...@@ -20,9 +18,7 @@ module.exports = defineConfig({ ...@@ -20,9 +18,7 @@ module.exports = defineConfig({
extends: [ extends: [
'plugin:vue/vue3-recommended', 'plugin:vue/vue3-recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'prettier',
'plugin:prettier/recommended', 'plugin:prettier/recommended',
'plugin:jest/recommended',
], ],
rules: { rules: {
'vue/script-setup-uses-vars': 'error', 'vue/script-setup-uses-vars': 'error',
...@@ -77,4 +73,4 @@ module.exports = defineConfig({ ...@@ -77,4 +73,4 @@ module.exports = defineConfig({
], ],
'vue/multi-word-component-names': 'off', 'vue/multi-word-component-names': 'off',
}, },
}); };
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
"**/yarn.lock": true "**/yarn.lock": true
}, },
"stylelint.enable": true, "stylelint.enable": true,
"stylelint.packageManager": "yarn", "stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
"path-intellisense.mappings": { "path-intellisense.mappings": {
"/@/": "${workspaceRoot}/src" "/@/": "${workspaceRoot}/src"
}, },
...@@ -88,7 +88,8 @@ ...@@ -88,7 +88,8 @@
}, },
"[vue]": { "[vue]": {
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": false "source.fixAll.eslint": true,
"source.fixAll.stylelint": true
} }
}, },
"i18n-ally.localesPaths": ["src/locales/lang"], "i18n-ally.localesPaths": ["src/locales/lang"],
......
import path from 'path'; import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import inquirer from 'inquirer'; import inquirer from 'inquirer';
import chalk from 'chalk'; import colors from 'picocolors';
import pkg from '../../../package.json'; import pkg from '../../../package.json';
async function generateIcon() { async function generateIcon() {
...@@ -64,7 +64,7 @@ async function generateIcon() { ...@@ -64,7 +64,7 @@ async function generateIcon() {
} }
fs.emptyDir(path.join(process.cwd(), 'node_modules/.vite')); fs.emptyDir(path.join(process.cwd(), 'node_modules/.vite'));
console.log( console.log(
`✨ ${chalk.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`, `✨ ${colors.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`,
); );
}); });
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
*/ */
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant'; 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 colors from 'picocolors';
import { getEnvConfig, getRootPath } from '../utils'; import { getEnvConfig, getRootPath } from '../utils';
import { getConfigFileName } from '../getConfigFileName'; import { getConfigFileName } from '../getConfigFileName';
...@@ -31,10 +31,10 @@ function createConfig(params: CreateConfigParams) { ...@@ -31,10 +31,10 @@ function createConfig(params: CreateConfigParams) {
fs.mkdirp(getRootPath(OUTPUT_DIR)); fs.mkdirp(getRootPath(OUTPUT_DIR));
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr); writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);
console.log(chalk.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`); console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
console.log(chalk.gray(OUTPUT_DIR + '/' + chalk.green(configFileName)) + '\n'); console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n');
} catch (error) { } catch (error) {
console.log(chalk.red('configuration file configuration file failed to package:\n' + error)); console.log(colors.red('configuration file configuration file failed to package:\n' + error));
} }
} }
......
// #!/usr/bin/env node // #!/usr/bin/env node
import { runBuildConfig } from './buildConf'; import { runBuildConfig } from './buildConf';
import chalk from 'chalk'; import colors from 'picocolors';
import pkg from '../../package.json'; import pkg from '../../package.json';
...@@ -14,9 +14,9 @@ export const runBuild = async () => { ...@@ -14,9 +14,9 @@ export const runBuild = async () => {
runBuildConfig(); runBuildConfig();
} }
console.log(`✨ ${chalk.cyan(`[${pkg.name}]`)}` + ' - build successfully!'); console.log(`✨ ${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!');
} catch (error) { } catch (error) {
console.log(chalk.red('vite build error:\n' + error)); console.log(colors.red('vite build error:\n' + error));
process.exit(1); process.exit(1);
} }
}; };
......
...@@ -2,16 +2,16 @@ ...@@ -2,16 +2,16 @@
* Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
* https://github.com/anncwb/vite-plugin-compression * https://github.com/anncwb/vite-plugin-compression
*/ */
import type { Plugin } from 'vite'; import type { PluginOption } from 'vite';
import compressPlugin from 'vite-plugin-compression'; import compressPlugin from 'vite-plugin-compression';
export function configCompressPlugin( export function configCompressPlugin(
compress: 'gzip' | 'brotli' | 'none', compress: 'gzip' | 'brotli' | 'none',
deleteOriginFile = false, deleteOriginFile = false,
): Plugin | Plugin[] { ): PluginOption | PluginOption[] {
const compressList = compress.split(','); const compressList = compress.split(',');
const plugins: Plugin[] = []; const plugins: PluginOption[] = [];
if (compressList.includes('gzip')) { if (compressList.includes('gzip')) {
plugins.push( plugins.push(
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
* Plugin to minimize and use ejs template syntax in index.html. * Plugin to minimize and use ejs template syntax in index.html.
* https://github.com/anncwb/vite-plugin-html * https://github.com/anncwb/vite-plugin-html
*/ */
import type { Plugin } from 'vite'; import type { PluginOption } from 'vite';
import html from 'vite-plugin-html'; import { createHtmlPlugin } from 'vite-plugin-html';
import pkg from '../../../package.json'; import pkg from '../../../package.json';
import { GLOB_CONFIG_FILE_NAME } from '../../constant'; import { GLOB_CONFIG_FILE_NAME } from '../../constant';
...@@ -16,7 +16,7 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) { ...@@ -16,7 +16,7 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`; return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
}; };
const htmlPlugin: Plugin[] = html({ const htmlPlugin: PluginOption[] = createHtmlPlugin({
minify: isBuild, minify: isBuild,
inject: { inject: {
// Inject data into ejs template // Inject data into ejs template
......
import type { Plugin } from 'vite'; import { PluginOption } from 'vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx'; import vueJsx from '@vitejs/plugin-vue-jsx';
import legacy from '@vitejs/plugin-legacy'; import legacy from '@vitejs/plugin-legacy';
import purgeIcons from 'vite-plugin-purge-icons'; import purgeIcons from 'vite-plugin-purge-icons';
import windiCSS from 'vite-plugin-windicss'; import windiCSS from 'vite-plugin-windicss';
import VitePluginCertificate from 'vite-plugin-mkcert';
import vueSetupExtend from 'vite-plugin-vue-setup-extend'; import vueSetupExtend from 'vite-plugin-vue-setup-extend';
import { configHtmlPlugin } from './html'; import { configHtmlPlugin } from './html';
import { configPwaConfig } from './pwa'; import { configPwaConfig } from './pwa';
...@@ -24,13 +25,16 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) { ...@@ -24,13 +25,16 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE,
} = viteEnv; } = viteEnv;
const vitePlugins: (Plugin | Plugin[])[] = [ const vitePlugins: (PluginOption | PluginOption[])[] = [
// have to // have to
vue(), vue(),
// have to // have to
vueJsx(), vueJsx(),
// support name // support name
vueSetupExtend(), vueSetupExtend(),
VitePluginCertificate({
source: 'coding',
}),
]; ];
// vite-plugin-windicss // vite-plugin-windicss
......
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
* Introduces component library styles on demand. * Introduces component library styles on demand.
* https://github.com/anncwb/vite-plugin-style-import * https://github.com/anncwb/vite-plugin-style-import
*/ */
import styleImport from 'vite-plugin-style-import'; import { createStyleImportPlugin } from 'vite-plugin-style-import';
export function configStyleImportPlugin(_isBuild: boolean) { export function configStyleImportPlugin(_isBuild: boolean) {
// if (!isBuild) { // if (!isBuild) {
// return []; // return [];
// } // }
const styleImportPlugin = styleImport({ const styleImportPlugin = createStyleImportPlugin({
libs: [ libs: [
{ {
libraryName: 'ant-design-vue', libraryName: 'ant-design-vue',
......
...@@ -3,11 +3,11 @@ ...@@ -3,11 +3,11 @@
* https://github.com/anncwb/vite-plugin-svg-icons * https://github.com/anncwb/vite-plugin-svg-icons
*/ */
import SvgIconsPlugin from 'vite-plugin-svg-icons'; import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
import path from 'path'; import path from 'path';
export function configSvgIconsPlugin(isBuild: boolean) { export function configSvgIconsPlugin(isBuild: boolean) {
const svgIconsPlugin = SvgIconsPlugin({ const svgIconsPlugin = createSvgIconsPlugin({
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')], iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
svgoOptions: isBuild, svgoOptions: isBuild,
// default // default
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* Vite plugin for website theme color switching * Vite plugin for website theme color switching
* https://github.com/anncwb/vite-plugin-theme * https://github.com/anncwb/vite-plugin-theme
*/ */
import type { Plugin } from 'vite'; import type { PluginOption } from 'vite';
import path from 'path'; import path from 'path';
import { import {
viteThemePlugin, viteThemePlugin,
...@@ -14,7 +14,7 @@ import { ...@@ -14,7 +14,7 @@ import {
import { getThemeColors, generateColors } from '../../config/themeConfig'; import { getThemeColors, generateColors } from '../../config/themeConfig';
import { generateModifyVars } from '../../generate/generateModifyVars'; import { generateModifyVars } from '../../generate/generateModifyVars';
export function configThemePlugin(isBuild: boolean): Plugin[] { export function configThemePlugin(isBuild: boolean): PluginOption[] {
const colors = generateColors({ const colors = generateColors({
mixDarken, mixDarken,
mixLighten, mixLighten,
...@@ -85,5 +85,5 @@ export function configThemePlugin(isBuild: boolean): Plugin[] { ...@@ -85,5 +85,5 @@ export function configThemePlugin(isBuild: boolean): Plugin[] {
}), }),
]; ];
return plugin as unknown as Plugin[]; return plugin as unknown as PluginOption[];
} }
export default {
preset: 'ts-jest',
roots: ['<rootDir>/tests/'],
clearMocks: true,
moduleDirectories: ['node_modules', 'src'],
moduleFileExtensions: ['js', 'ts', 'vue', 'tsx', 'jsx', 'json', 'node'],
modulePaths: ['<rootDir>/src', '<rootDir>/node_modules'],
testMatch: [
'**/tests/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[tj]s?(x)',
'(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$',
],
testPathIgnorePatterns: [
'<rootDir>/tests/server/',
'<rootDir>/tests/__mocks__/',
'/node_modules/',
],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
transformIgnorePatterns: ['<rootDir>/tests/__mocks__/', '/node_modules/'],
// A map from regular expressions to module names that allow to stub out resources with a single module
moduleNameMapper: {
'\\.(vs|fs|vert|frag|glsl|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/tests/__mocks__/fileMock.ts',
'\\.(sass|s?css|less)$': '<rootDir>/tests/__mocks__/styleMock.ts',
'\\?worker$': '<rootDir>/tests/__mocks__/workerMock.ts',
'^/@/(.*)$': '<rootDir>/src/$1',
},
testEnvironment: 'jsdom',
verbose: true,
collectCoverage: false,
coverageDirectory: 'coverage',
collectCoverageFrom: ['src/**/*.{js,ts,vue}'],
coveragePathIgnorePatterns: ['^.+\\.d\\.ts$'],
};
...@@ -111,4 +111,12 @@ export default [ ...@@ -111,4 +111,12 @@ export default [
return resultSuccess(undefined, { message: 'Token has been destroyed' }); return resultSuccess(undefined, { message: 'Token has been destroyed' });
}, },
}, },
{
url: '/basic-api/testRetry',
statusCode: 405,
method: 'get',
response: () => {
return resultError('Error!');
},
},
] as MockMethod[]; ] as MockMethod[];
...@@ -25,7 +25,6 @@ ...@@ -25,7 +25,6 @@
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", "lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged", "lint:lint-staged": "lint-staged",
"test:unit": "jest", "test:unit": "jest",
"test:unit-coverage": "jest --coverage",
"test:gzip": "npx http-server dist --cors --gzip -c-1", "test:gzip": "npx http-server dist --cors --gzip -c-1",
"test:br": "npx http-server dist --cors --brotli -c-1", "test:br": "npx http-server dist --cors --brotli -c-1",
"reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap", "reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
...@@ -34,117 +33,115 @@ ...@@ -34,117 +33,115 @@
}, },
"dependencies": { "dependencies": {
"@ant-design/colors": "^6.0.0", "@ant-design/colors": "^6.0.0",
"@ant-design/icons-vue": "^6.0.1", "@ant-design/icons-vue": "^6.1.0",
"@iconify/iconify": "^2.1.0", "@iconify/iconify": "^2.1.2",
"@logicflow/core": "^0.7.16", "@logicflow/core": "^1.1.7",
"@logicflow/extension": "^0.7.16", "@logicflow/extension": "^1.1.7",
"@vue/runtime-core": "^3.2.26", "@vue/runtime-core": "^3.2.31",
"@vue/shared": "^3.2.26", "@vue/shared": "^3.2.31",
"@vueuse/core": "^7.4.1", "@vueuse/core": "^8.1.1",
"@vueuse/shared": "^7.4.1", "@vueuse/shared": "^8.1.1",
"@zxcvbn-ts/core": "^1.2.0", "@zxcvbn-ts/core": "^2.0.1",
"ant-design-vue": "3.0.0-beta.3", "ant-design-vue": "3.1.0-rc.1",
"axios": "^0.24.0", "axios": "^0.26.1",
"codemirror": "^5.65.0", "codemirror": "^5.65.2",
"cropperjs": "^1.5.12", "cropperjs": "^1.5.12",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"dayjs": "^1.10.7", "dayjs": "^1.11.0",
"echarts": "^5.2.2", "echarts": "^5.3.1",
"intro.js": "^4.3.0", "intro.js": "^5.0.0",
"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.9", "pinia": "2.0.12",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"qrcode": "^1.5.0", "qrcode": "^1.5.0",
"qs": "^6.10.2", "qs": "^6.10.3",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"showdown": "^1.9.1", "showdown": "^2.0.3",
"sortablejs": "^1.14.0", "sortablejs": "^1.14.0",
"tinymce": "^5.10.2", "tinymce": "^5.10.3",
"vditor": "^3.8.10", "vditor": "^3.8.12",
"vue": "^3.2.26", "vue": "^3.2.31",
"vue-i18n": "^9.1.9", "vue-i18n": "^9.1.9",
"vue-json-pretty": "^1.8.2", "vue-json-pretty": "^2.0.6",
"vue-router": "^4.0.12", "vue-router": "^4.0.14",
"vue-types": "^4.1.1", "vue-types": "^4.1.1",
"xlsx": "^0.17.4" "xlsx": "^0.18.4"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^16.0.1", "@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.0.0", "@commitlint/config-conventional": "^16.2.1",
"@iconify/json": "^2.0.16", "@iconify/json": "^2.1.17",
"@purge-icons/generated": "^0.7.0", "@purge-icons/generated": "^0.8.1",
"@types/codemirror": "^5.60.5", "@types/codemirror": "^5.60.5",
"@types/crypto-js": "^4.0.2", "@types/crypto-js": "^4.1.1",
"@types/fs-extra": "^9.0.13", "@types/fs-extra": "^9.0.13",
"@types/inquirer": "^8.1.3", "@types/inquirer": "^8.2.0",
"@types/intro.js": "^3.0.2", "@types/intro.js": "^3.0.2",
"@types/jest": "^27.0.3", "@types/lodash-es": "^4.17.6",
"@types/lodash-es": "^4.17.5", "@types/mockjs": "^1.0.6",
"@types/mockjs": "^1.0.4", "@types/node": "^17.0.21",
"@types/node": "^17.0.5",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.2", "@types/qrcode": "^1.4.2",
"@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": "^5.8.1", "@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.8.1", "@typescript-eslint/parser": "^5.15.0",
"@vitejs/plugin-legacy": "^1.6.4", "@vitejs/plugin-legacy": "^1.7.1",
"@vitejs/plugin-vue": "^2.0.1", "@vitejs/plugin-vue": "^2.2.4",
"@vitejs/plugin-vue-jsx": "^1.3.3", "@vitejs/plugin-vue-jsx": "^1.3.8",
"@vue/compiler-sfc": "3.2.26", "@vue/compiler-sfc": "3.2.31",
"@vue/test-utils": "^2.0.0-rc.18", "@vue/test-utils": "^2.0.0-rc.18",
"autoprefixer": "^10.4.0", "autoprefixer": "^10.4.4",
"commitizen": "^4.2.4", "commitizen": "^4.2.4",
"conventional-changelog-cli": "^2.2.2", "conventional-changelog-cli": "^2.2.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dotenv": "^10.0.0", "dotenv": "^16.0.0",
"eslint": "^8.5.0", "eslint": "^8.11.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.5.0",
"eslint-define-config": "^1.2.1",
"eslint-plugin-jest": "^25.3.2",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.2.0", "eslint-plugin-vue": "^8.5.0",
"esno": "^0.13.0", "esno": "^0.14.1",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.1",
"husky": "^7.0.4", "husky": "^7.0.4",
"inquirer": "^8.2.0", "inquirer": "^8.2.1",
"jest": "^27.4.5",
"less": "^4.1.2", "less": "^4.1.2",
"lint-staged": "12.1.4", "lint-staged": "12.3.7",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss": "^8.4.5", "picocolors": "^1.0.0",
"postcss": "^8.4.12",
"postcss-html": "^1.3.0", "postcss-html": "^1.3.0",
"postcss-less": "^5.0.0", "postcss-less": "^6.0.0",
"prettier": "^2.5.1", "prettier": "^2.6.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup-plugin-visualizer": "^5.5.2", "rollup": "^2.70.1",
"stylelint": "^14.2.0", "rollup-plugin-visualizer": "^5.6.0",
"stylelint-config-html": "^1.0.0", "stylelint": "^14.6.0",
"stylelint-config-prettier": "^9.0.3", "stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended": "^6.0.0", "stylelint-config-recommended": "^7.0.0",
"stylelint-config-standard": "^24.0.0", "stylelint-config-recommended-vue": "^1.3.0",
"stylelint-config-standard": "^25.0.0",
"stylelint-order": "^5.0.0", "stylelint-order": "^5.0.0",
"ts-jest": "^27.1.2", "ts-node": "^10.7.0",
"ts-node": "^10.4.0", "typescript": "^4.6.2",
"typescript": "^4.5.4", "vite": "^2.9.0-beta.3",
"vite": "^2.7.8", "vite-plugin-compression": "^0.5.1",
"vite-plugin-compression": "^0.4.0", "vite-plugin-html": "^3.2.0",
"vite-plugin-html": "^2.1.2", "vite-plugin-imagemin": "^0.6.1",
"vite-plugin-imagemin": "^0.5.1", "vite-plugin-mkcert": "^1.6.0",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-purge-icons": "^0.7.0", "vite-plugin-purge-icons": "^0.8.1",
"vite-plugin-pwa": "^0.11.12", "vite-plugin-pwa": "^0.11.13",
"vite-plugin-style-import": "^1.4.1", "vite-plugin-style-import": "^2.0.0",
"vite-plugin-svg-icons": "^1.0.5", "vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-theme": "^0.8.1", "vite-plugin-theme": "^0.8.1",
"vite-plugin-vue-setup-extend": "^0.3.0", "vite-plugin-vue-setup-extend": "^0.4.0",
"vite-plugin-windicss": "^1.6.1", "vite-plugin-windicss": "^1.8.3",
"vue-eslint-parser": "^8.0.1", "vue-eslint-parser": "^8.3.0",
"vue-tsc": "^0.30.1" "vue-tsc": "^0.33.2"
}, },
"resolutions": { "resolutions": {
"bin-wrapper": "npm:bin-wrapper-china", "bin-wrapper": "npm:bin-wrapper-china",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -8,6 +8,7 @@ enum Api { ...@@ -8,6 +8,7 @@ enum Api {
Logout = '/logout', Logout = '/logout',
GetUserInfo = '/getUserInfo', GetUserInfo = '/getUserInfo',
GetPermCode = '/getPermCode', GetPermCode = '/getPermCode',
TestRetry = '/testRetry',
} }
/** /**
...@@ -39,3 +40,16 @@ export function getPermCode() { ...@@ -39,3 +40,16 @@ export function getPermCode() {
export function doLogout() { export function doLogout() {
return defHttp.get({ url: Api.Logout }); return defHttp.get({ url: Api.Logout });
} }
export function testRetry() {
return defHttp.get(
{ url: Api.TestRetry },
{
retryRequest: {
isOpenRetry: true,
count: 5,
waitTime: 1000,
},
},
);
}
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
--> -->
<template> <template>
<Dropdown <Dropdown
placement="bottomCenter" placement="bottom"
:trigger="['click']" :trigger="['click']"
:dropMenuList="localeList" :dropMenuList="localeList"
:selectedKeys="selectedKeys" :selectedKeys="selectedKeys"
@menuEvent="handleMenuEvent" @menu-event="handleMenuEvent"
overlayClassName="app-locale-picker-overlay" overlayClassName="app-locale-picker-overlay"
> >
<span class="cursor-pointer flex items-center"> <span class="cursor-pointer flex items-center">
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
<div class="p-4 mb-2 bg-white"> <div class="p-4 mb-2 bg-white">
<BasicForm @register="registerForm" /> <BasicForm @register="registerForm" />
</div> </div>
{{ sliderProp.width }}
<div class="p-2 bg-white"> <div class="p-2 bg-white">
<List <List
:grid="{ gutter: 5, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: grid }" :grid="{ gutter: 5, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: grid }"
...@@ -39,7 +38,7 @@ ...@@ -39,7 +38,7 @@
<Image :src="item.imgs[0]" /> <Image :src="item.imgs[0]" />
</div> </div>
</template> </template>
<template class="ant-card-actions" #actions> <template #actions>
<!-- <SettingOutlined key="setting" />--> <!-- <SettingOutlined key="setting" />-->
<EditOutlined key="edit" /> <EditOutlined key="edit" />
<Dropdown <Dropdown
......
...@@ -64,6 +64,7 @@ ...@@ -64,6 +64,7 @@
width: `${width}px`, width: `${width}px`,
left: `${left + 1}px`, left: `${left + 1}px`,
top: `${top + 1}px`, top: `${top + 1}px`,
zIndex: 9999,
}; };
}); });
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<CopperModal <CopperModal
@register="register" @register="register"
@uploadSuccess="handleUploadSuccess" @upload-success="handleUploadSuccess"
:uploadApi="uploadApi" :uploadApi="uploadApi"
:src="sourceValue" :src="sourceValue"
/> />
......
import xlsx from 'xlsx'; import * as xlsx from 'xlsx';
import type { WorkBook } from 'xlsx'; import type { WorkBook } from 'xlsx';
import type { JsonToSheet, AoAToSheet } from './typing'; import type { JsonToSheet, AoAToSheet } from './typing';
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, unref } from 'vue'; import { defineComponent, ref, unref } from 'vue';
import XLSX from 'xlsx'; import * as XLSX from 'xlsx';
import { dateUtil } from '/@/utils/dateUtil'; import { dateUtil } from '/@/utils/dateUtil';
import type { ExcelData } from './typing'; import type { ExcelData } from './typing';
......
...@@ -9,6 +9,7 @@ export { useForm } from './src/hooks/useForm'; ...@@ -9,6 +9,7 @@ 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 ApiTree } from './src/components/ApiTree.vue';
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue'; export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
export { default as ApiCascader } from './src/components/ApiCascader.vue'; export { default as ApiCascader } from './src/components/ApiCascader.vue';
......
...@@ -24,6 +24,7 @@ import { ...@@ -24,6 +24,7 @@ import {
import ApiRadioGroup from './components/ApiRadioGroup.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 ApiTree from './components/ApiTree.vue';
import ApiTreeSelect from './components/ApiTreeSelect.vue'; import ApiTreeSelect from './components/ApiTreeSelect.vue';
import ApiCascader from './components/ApiCascader.vue'; import ApiCascader from './components/ApiCascader.vue';
import { BasicUpload } from '/@/components/Upload'; import { BasicUpload } from '/@/components/Upload';
...@@ -43,6 +44,7 @@ componentMap.set('AutoComplete', AutoComplete); ...@@ -43,6 +44,7 @@ componentMap.set('AutoComplete', AutoComplete);
componentMap.set('Select', Select); componentMap.set('Select', Select);
componentMap.set('ApiSelect', ApiSelect); componentMap.set('ApiSelect', ApiSelect);
componentMap.set('ApiTree', ApiTree);
componentMap.set('TreeSelect', TreeSelect); componentMap.set('TreeSelect', TreeSelect);
componentMap.set('ApiTreeSelect', ApiTreeSelect); componentMap.set('ApiTreeSelect', ApiTreeSelect);
componentMap.set('ApiRadioGroup', ApiRadioGroup); componentMap.set('ApiRadioGroup', ApiRadioGroup);
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
import { get, omit } from 'lodash-es'; import { get, omit } from 'lodash-es';
import { useRuleFormItem } from '/@/hooks/component/useFormItem'; import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { LoadingOutlined } from '@ant-design/icons-vue'; import { LoadingOutlined } from '@ant-design/icons-vue';
import { useI18n } from '/@/hooks/web/useI18n';
interface Option { interface Option {
value: string; value: string;
label: string; label: string;
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
const emitData = ref<any[]>([]); const emitData = ref<any[]>([]);
const isFirstLoad = ref(true); const isFirstLoad = ref(true);
const { t } = useI18n();
// Embedded in the form, just use the hook binding to perform form verification // Embedded in the form, just use the hook binding to perform form verification
const [state] = useRuleFormItem(props, 'value', 'change', emitData); const [state] = useRuleFormItem(props, 'value', 'change', emitData);
...@@ -188,6 +188,7 @@ ...@@ -188,6 +188,7 @@
state, state,
options, options,
loading, loading,
t,
handleChange, handleChange,
loadData, loadData,
handleRenderDisplay, handleRenderDisplay,
......
<template>
<a-tree v-bind="getAttrs" @change="handleChange">
<template #[item]="data" v-for="item in Object.keys($slots)">
<slot :name="item" v-bind="data || {}"></slot>
</template>
<template #suffixIcon v-if="loading">
<LoadingOutlined spin />
</template>
</a-tree>
</template>
<script lang="ts">
import { computed, defineComponent, watch, ref, onMounted, unref } from 'vue';
import { Tree } from 'ant-design-vue';
import { isArray, isFunction } from '/@/utils/is';
import { get } from 'lodash-es';
import { propTypes } from '/@/utils/propTypes';
import { LoadingOutlined } from '@ant-design/icons-vue';
export default defineComponent({
name: 'ApiTree',
components: { ATree: Tree, LoadingOutlined },
props: {
api: { type: Function as PropType<(arg?: Recordable) => Promise<Recordable>> },
params: { type: Object },
immediate: { type: Boolean, default: true },
resultField: propTypes.string.def(''),
},
emits: ['options-change', 'change'],
setup(props, { attrs, emit }) {
const treeData = ref<Recordable[]>([]);
const isFirstLoaded = ref<Boolean>(false);
const loading = ref(false);
const getAttrs = computed(() => {
return {
...(props.api ? { treeData: unref(treeData) } : {}),
...attrs,
};
});
function handleChange(...args) {
emit('change', ...args);
}
watch(
() => props.params,
() => {
!unref(isFirstLoaded) && fetch();
},
{ deep: true },
);
watch(
() => props.immediate,
(v) => {
v && !isFirstLoaded.value && fetch();
},
);
onMounted(() => {
props.immediate && fetch();
});
async function fetch() {
const { api } = props;
if (!api || !isFunction(api)) return;
loading.value = true;
treeData.value = [];
let result;
try {
result = await api(props.params);
} catch (e) {
console.error(e);
}
loading.value = false;
if (!result) return;
if (!isArray(result)) {
result = get(result, props.resultField);
}
treeData.value = (result as Recordable[]) || [];
isFirstLoaded.value = true;
emit('options-change', treeData.value);
}
return { getAttrs, loading, handleChange };
},
});
</script>
...@@ -2,7 +2,7 @@ import type { ComputedRef, Ref } from 'vue'; ...@@ -2,7 +2,7 @@ import type { ComputedRef, Ref } from 'vue';
import type { FormProps, FormSchema, FormActionType } from '../types/form'; import type { FormProps, FormSchema, FormActionType } from '../types/form';
import type { NamePath } from 'ant-design-vue/lib/form/interface'; import type { NamePath } from 'ant-design-vue/lib/form/interface';
import { unref, toRaw, nextTick } from 'vue'; import { unref, toRaw, nextTick } from 'vue';
import { isArray, isFunction, isObject, isString } from '/@/utils/is'; import { isArray, isFunction, isNullOrUnDef, isObject, isString } from '/@/utils/is';
import { deepMerge } from '/@/utils'; import { deepMerge } from '/@/utils';
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper'; import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper';
import { dateUtil } from '/@/utils/dateUtil'; import { dateUtil } from '/@/utils/dateUtil';
...@@ -132,11 +132,14 @@ export function useFormEvents({ ...@@ -132,11 +132,14 @@ export function useFormEvents({
if (!prefixField || index === -1 || first) { if (!prefixField || index === -1 || first) {
first ? schemaList.unshift(schema) : schemaList.push(schema); first ? schemaList.unshift(schema) : schemaList.push(schema);
schemaRef.value = schemaList; schemaRef.value = schemaList;
_setDefaultValue(schema);
return; return;
} }
if (index !== -1) { if (index !== -1) {
schemaList.splice(index + 1, 0, schema); schemaList.splice(index + 1, 0, schema);
} }
_setDefaultValue(schema);
schemaRef.value = schemaList; schemaRef.value = schemaList;
} }
...@@ -192,9 +195,34 @@ export function useFormEvents({ ...@@ -192,9 +195,34 @@ export function useFormEvents({
} }
}); });
}); });
_setDefaultValue(schema);
schemaRef.value = uniqBy(schema, 'field'); schemaRef.value = uniqBy(schema, 'field');
} }
function _setDefaultValue(data: FormSchema | FormSchema[]) {
let schemas: FormSchema[] = [];
if (isObject(data)) {
schemas.push(data as FormSchema);
}
if (isArray(data)) {
schemas = [...data];
}
const obj: Recordable = {};
schemas.forEach((item) => {
if (
item.component != 'Divider' &&
Reflect.has(item, 'field') &&
item.field &&
!isNullOrUnDef(item.defaultValue)
) {
obj[item.field] = item.defaultValue;
}
});
setFieldsValue(obj);
}
function getFieldsValue(): Recordable { function getFieldsValue(): Recordable {
const formEl = unref(formElRef); const formEl = unref(formElRef);
if (!formEl) return {}; if (!formEl) return {};
......
...@@ -11,6 +11,43 @@ interface UseFormValuesContext { ...@@ -11,6 +11,43 @@ interface UseFormValuesContext {
getProps: ComputedRef<FormProps>; getProps: ComputedRef<FormProps>;
formModel: Recordable; formModel: Recordable;
} }
/**
* @desription deconstruct array-link key. This method will mutate the target.
*/
function tryDeconstructArray(key: string, value: any, target: Recordable) {
const pattern = /^\[(.+)\]$/;
if (pattern.test(key)) {
const match = key.match(pattern);
if (match && match[1]) {
const keys = match[1].split(',');
value = Array.isArray(value) ? value : [value];
keys.forEach((k, index) => {
set(target, k.trim(), value[index]);
});
return true;
}
}
}
/**
* @desription deconstruct object-link key. This method will mutate the target.
*/
function tryDeconstructObject(key: string, value: any, target: Recordable) {
const pattern = /^\{(.+)\}$/;
if (pattern.test(key)) {
const match = key.match(pattern);
if (match && match[1]) {
const keys = match[1].split(',');
value = isObject(value) ? value : {};
keys.forEach((k) => {
set(target, k.trim(), value[k.trim()]);
});
return true;
}
}
}
export function useFormValues({ export function useFormValues({
defaultValueRef, defaultValueRef,
getSchema, getSchema,
...@@ -41,8 +78,11 @@ export function useFormValues({ ...@@ -41,8 +78,11 @@ export function useFormValues({
if (isString(value)) { if (isString(value)) {
value = value.trim(); value = value.trim();
} }
if (!tryDeconstructArray(key, value, res) && !tryDeconstructObject(key, value, res)) {
// 没有解构成功的,按原样赋值
set(res, key, value); set(res, key, value);
} }
}
return handleRangeTimeValue(res); return handleRangeTimeValue(res);
} }
......
...@@ -91,6 +91,7 @@ export type ComponentType = ...@@ -91,6 +91,7 @@ export type ComponentType =
| 'Select' | 'Select'
| 'ApiSelect' | 'ApiSelect'
| 'TreeSelect' | 'TreeSelect'
| 'ApiTree'
| 'ApiTreeSelect' | 'ApiTreeSelect'
| 'ApiRadioGroup' | 'ApiRadioGroup'
| 'RadioButtonGroup' | 'RadioButtonGroup'
......
...@@ -31,18 +31,7 @@ ...@@ -31,18 +31,7 @@
v-for="icon in getPaginationList" v-for="icon in getPaginationList"
:key="icon" :key="icon"
:class="currentSelect === icon ? 'border border-primary' : ''" :class="currentSelect === icon ? 'border border-primary' : ''"
class=" class="p-2 w-1/8 cursor-pointer mr-1 mt-1 flex justify-center items-center border border-solid hover:border-primary"
p-2
w-1/8
cursor-pointer
mr-1
mt-1
flex
justify-center
items-center
border border-solid
hover:border-primary
"
@click="handleClick(icon)" @click="handleClick(icon)"
:title="icon" :title="icon"
> >
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed,defineProps } from 'vue'; import { computed, defineProps } from 'vue';
import showdown from 'showdown'; import showdown from 'showdown';
const converter = new showdown.Converter(); const converter = new showdown.Converter();
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
:openKeys="getOpenKeys" :openKeys="getOpenKeys"
:inlineIndent="inlineIndent" :inlineIndent="inlineIndent"
:theme="theme" :theme="theme"
@openChange="handleOpenChange" @open-change="handleOpenChange"
:class="getMenuClass" :class="getMenuClass"
@click="handleMenuClick" @click="handleMenuClick"
:subMenuOpenDelay="0.2" :subMenuOpenDelay="0.2"
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
:overlayClassName="`${prefixCls}-menu-popover`" :overlayClassName="`${prefixCls}-menu-popover`"
v-else v-else
:visible="getIsOpend" :visible="getIsOpend"
@visibleChange="handleVisibleChange" @visible-change="handleVisibleChange"
:overlayStyle="getOverlayStyle" :overlayStyle="getOverlayStyle"
:align="{ offset: [0, 0] }" :align="{ offset: [0, 0] }"
> >
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
bottom: 0; bottom: 0;
display: block; display: block;
width: 2px; width: 2px;
background-color: @primary-color;
content: ''; content: '';
background-color: @primary-color;
} }
} }
...@@ -45,8 +45,8 @@ ...@@ -45,8 +45,8 @@
position: absolute; position: absolute;
top: 50%; top: 50%;
right: 18px; right: 18px;
transform: translateY(-50%) rotate(-90deg);
transition: transform @transition-time @ease-in-out; transition: transform @transition-time @ease-in-out;
transform: translateY(-50%) rotate(-90deg);
} }
} }
...@@ -128,12 +128,12 @@ ...@@ -128,12 +128,12 @@
position: relative; position: relative;
z-index: 1; z-index: 1;
display: flex; display: flex;
align-items: center;
font-size: @font-size-base; font-size: @font-size-base;
color: inherit; color: inherit;
list-style: none; list-style: none;
cursor: pointer; cursor: pointer;
outline: none; outline: none;
align-items: center;
&:hover, &:hover,
&:active { &:active {
...@@ -178,8 +178,8 @@ ...@@ -178,8 +178,8 @@
&-vertical &-submenu-collapse { &-vertical &-submenu-collapse {
.@{submenu-popup-prefix-cls} { .@{submenu-popup-prefix-cls} {
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
justify-content: center;
} }
.@{menu-prefix-cls}-submenu-collapsed-show-tit { .@{menu-prefix-cls}-submenu-collapsed-show-tit {
flex-direction: column; flex-direction: column;
...@@ -244,8 +244,8 @@ ...@@ -244,8 +244,8 @@
left: 0; left: 0;
width: 3px; width: 3px;
height: 100%; height: 100%;
background-color: @primary-color;
content: ''; content: '';
background-color: @primary-color;
} }
} }
} }
...@@ -276,8 +276,8 @@ ...@@ -276,8 +276,8 @@
left: 0; left: 0;
width: 3px; width: 3px;
height: 100%; height: 100%;
background-color: @primary-color;
content: ''; content: '';
background-color: @primary-color;
} }
.@{menu-prefix-cls}-submenu-collapse { .@{menu-prefix-cls}-submenu-collapse {
......
...@@ -25,10 +25,12 @@ ...@@ -25,10 +25,12 @@
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item"> <template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
<slot :name="item" v-bind="data || {}"></slot> <slot :name="item" v-bind="data || {}"></slot>
</template> </template>
<template #headerCell="{ column }">
<template #[`header-${column.dataIndex}`] v-for="(column, index) in columns" :key="index">
<HeaderCell :column="column" /> <HeaderCell :column="column" />
</template> </template>
<!-- <template #[`header-${column.dataIndex}`] v-for="(column, index) in columns" :key="index">-->
<!-- <HeaderCell :column="column" />-->
<!-- </template>-->
</Table> </Table>
</div> </div>
</template> </template>
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
const { prefixCls } = useDesign('basic-table-header-cell'); const { prefixCls } = useDesign('basic-table-header-cell');
const getIsEdit = computed(() => !!props.column?.edit); const getIsEdit = computed(() => !!props.column?.edit);
const getTitle = computed(() => props.column?.customTitle); const getTitle = computed(() => props.column?.customTitle || props.column?.title);
const getHelpMessage = computed(() => props.column?.helpMessage); const getHelpMessage = computed(() => props.column?.helpMessage);
return { prefixCls, getIsEdit, getTitle, getHelpMessage }; return { prefixCls, getIsEdit, getTitle, getHelpMessage };
......
<template> <script lang="tsx">
<div :class="prefixCls">
<div
v-show="!isEdit"
:class="{ [`${prefixCls}__normal`]: true, 'ellipsis-cell': column.ellipsis }"
@click="handleEdit"
>
<div class="cell-content" :title="column.ellipsis ? getValues ?? '' : ''">
{{ getValues || getValues === 0 ? getValues : '&nbsp;' }}
</div>
<FormOutlined :class="`${prefixCls}__normal-icon`" v-if="!column.editRow" />
</div>
<a-spin v-if="isEdit" :spinning="spinning">
<div :class="`${prefixCls}__wrapper`" v-click-outside="onClickOutside">
<CellComponent
v-bind="getComponentProps"
:component="getComponent"
:style="getWrapperStyle"
:popoverVisible="getRuleVisible"
:rule="getRule"
:ruleMessage="ruleMessage"
:class="getWrapperClass"
ref="elRef"
@change="handleChange"
@options-change="handleOptionsChange"
@pressEnter="handleEnter"
/>
<div :class="`${prefixCls}__action`" v-if="!getRowEditable">
<CheckOutlined :class="[`${prefixCls}__icon`, 'mx-2']" @click="handleSubmitClick" />
<CloseOutlined :class="`${prefixCls}__icon `" @click="handleCancel" />
</div>
</div>
</a-spin>
</div>
</template>
<script lang="ts">
import type { CSSProperties, PropType } from 'vue'; import type { CSSProperties, PropType } from 'vue';
import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue'; import { computed, defineComponent, nextTick, ref, toRaw, unref, watchEffect } from 'vue';
import type { BasicColumn } from '../../types/table'; import type { BasicColumn } from '../../types/table';
...@@ -56,7 +20,7 @@ ...@@ -56,7 +20,7 @@
export default defineComponent({ export default defineComponent({
name: 'EditableCell', name: 'EditableCell',
components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, ASpin: Spin }, components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent, Spin },
directives: { directives: {
clickOutside, clickOutside,
}, },
...@@ -100,13 +64,6 @@ ...@@ -100,13 +64,6 @@
}); });
const getComponentProps = computed(() => { const getComponentProps = computed(() => {
const compProps = props.column?.editComponentProps ?? {};
const component = unref(getComponent);
const apiSelectProps: Recordable = {};
if (component === 'ApiSelect') {
apiSelectProps.cache = true;
}
const isCheckValue = unref(getIsCheckComp); const isCheckValue = unref(getIsCheckComp);
const valueField = isCheckValue ? 'checked' : 'value'; const valueField = isCheckValue ? 'checked' : 'value';
...@@ -114,19 +71,30 @@ ...@@ -114,19 +71,30 @@
const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val; const value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
let compProps = props.column?.editComponentProps ?? {};
const { record, column, index } = props;
if (isFunction(compProps)) {
compProps = compProps({ text: val, record, column, index }) ?? {};
}
const component = unref(getComponent);
const apiSelectProps: Recordable = {};
if (component === 'ApiSelect') {
apiSelectProps.cache = true;
}
return { return {
size: 'small', size: 'small',
getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body, getPopupContainer: () => unref(table?.wrapRef.value) ?? document.body,
getCalendarContainer: () => unref(table?.wrapRef.value) ?? document.body,
placeholder: createPlaceholderMessage(unref(getComponent)), placeholder: createPlaceholderMessage(unref(getComponent)),
...apiSelectProps, ...apiSelectProps,
...omit(compProps, 'onChange'), ...omit(compProps, 'onChange'),
[valueField]: value, [valueField]: value,
}; } as any;
}); });
const getValues = computed(() => { const getValues = computed(() => {
const { editComponentProps, editValueMap } = props.column; const { editValueMap } = props.column;
const value = unref(currentValueRef); const value = unref(currentValueRef);
...@@ -139,7 +107,8 @@ ...@@ -139,7 +107,8 @@
return value; return value;
} }
const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []); const options: LabelValueOptions =
unref(getComponentProps)?.options ?? (unref(optionsRef) || []);
const option = options.find((item) => `${item.value}` === `${value}`); const option = options.find((item) => `${item.value}` === `${value}`);
return option?.label ?? value; return option?.label ?? value;
...@@ -199,7 +168,7 @@ ...@@ -199,7 +168,7 @@
} else if (isString(e) || isBoolean(e) || isNumber(e)) { } else if (isString(e) || isBoolean(e) || isNumber(e)) {
currentValueRef.value = e; currentValueRef.value = e;
} }
const onChange = props.column?.editComponentProps?.onChange; const onChange = unref(getComponentProps)?.onChange;
if (onChange && isFunction(onChange)) onChange(...arguments); if (onChange && isFunction(onChange)) onChange(...arguments);
table.emit?.('edit-change', { table.emit?.('edit-change', {
...@@ -267,7 +236,7 @@ ...@@ -267,7 +236,7 @@
result = await beforeEditSubmit({ result = await beforeEditSubmit({
record: pick(record, keys), record: pick(record, keys),
index, index,
key: key as string, key: dataKey as string,
value, value,
}); });
} catch (e) { } catch (e) {
...@@ -283,7 +252,7 @@ ...@@ -283,7 +252,7 @@
set(record, dataKey, value); set(record, dataKey, value);
//const record = await table.updateTableData(index, dataKey, value); //const record = await table.updateTableData(index, dataKey, value);
needEmit && table.emit?.('edit-end', { record, index, key, value }); needEmit && table.emit?.('edit-end', { record, index, key: dataKey, value });
isEdit.value = false; isEdit.value = false;
} }
...@@ -324,7 +293,7 @@ ...@@ -324,7 +293,7 @@
// only ApiSelect or TreeSelect // only ApiSelect or TreeSelect
function handleOptionsChange(options: LabelValueOptions) { function handleOptionsChange(options: LabelValueOptions) {
const { replaceFields } = props.column?.editComponentProps ?? {}; const { replaceFields } = unref(getComponentProps);
const component = unref(getComponent); const component = unref(getComponent);
if (component === 'ApiTreeSelect') { if (component === 'ApiTreeSelect') {
const { title = 'title', value = 'value', children = 'children' } = replaceFields || {}; const { title = 'title', value = 'value', children = 'children' } = replaceFields || {};
...@@ -357,7 +326,7 @@ ...@@ -357,7 +326,7 @@
if (props.column.dataIndex) { if (props.column.dataIndex) {
if (!props.record.editValueRefs) props.record.editValueRefs = {}; if (!props.record.editValueRefs) props.record.editValueRefs = {};
props.record.editValueRefs[props.column.dataIndex] = currentValueRef; props.record.editValueRefs[props.column.dataIndex as any] = currentValueRef;
} }
/* eslint-disable */ /* eslint-disable */
props.record.onCancelEdit = () => { props.record.onCancelEdit = () => {
...@@ -400,6 +369,59 @@ ...@@ -400,6 +369,59 @@
spinning, spinning,
}; };
}, },
render() {
return (
<div class={this.prefixCls}>
<div
v-show={!this.isEdit}
class={{ [`${this.prefixCls}__normal`]: true, 'ellipsis-cell': this.column.ellipsis }}
onClick={this.handleEdit}
>
<div class="cell-content" title={this.column.ellipsis ? this.getValues ?? '' : ''}>
{this.column.editRender
? this.column.editRender({
text: this.value,
record: this.record as Recordable,
column: this.column,
index: this.index,
})
: this.getValues
? this.getValues
: '\u00A0'}
</div>
{!this.column.editRow && <FormOutlined class={`${this.prefixCls}__normal-icon`} />}
</div>
{this.isEdit && (
<Spin spinning={this.spinning}>
<div class={`${this.prefixCls}__wrapper`} v-click-outside={this.onClickOutside}>
<CellComponent
{...this.getComponentProps}
component={this.getComponent}
style={this.getWrapperStyle}
popoverVisible={this.getRuleVisible}
rule={this.getRule}
ruleMessage={this.ruleMessage}
class={this.getWrapperClass}
ref="elRef"
onChange={this.handleChange}
onOptionsChange={this.handleOptionsChange}
onPressEnter={this.handleEnter}
/>
{!this.getRowEditable && (
<div class={`${this.prefixCls}__action`}>
<CheckOutlined
class={[`${this.prefixCls}__icon`, 'mx-2']}
onClick={this.handleSubmitClick}
/>
<CloseOutlined class={`${this.prefixCls}__icon `} onClick={this.handleCancel} />
</div>
)}
</div>
</Spin>
)}
</div>
);
},
}); });
</script> </script>
<style lang="less"> <style lang="less">
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<Popover <Popover
placement="bottomLeft" placement="bottomLeft"
trigger="click" trigger="click"
@visibleChange="handleVisibleChange" @visible-change="handleVisibleChange"
:overlayClassName="`${prefixCls}__cloumn-list`" :overlayClassName="`${prefixCls}__cloumn-list`"
:getPopupContainer="getPopupContainer" :getPopupContainer="getPopupContainer"
> >
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<span>{{ t('component.table.settingDens') }}</span> <span>{{ t('component.table.settingDens') }}</span>
</template> </template>
<Dropdown placement="bottomCenter" :trigger="['click']" :getPopupContainer="getPopupContainer"> <Dropdown placement="bottom" :trigger="['click']" :getPopupContainer="getPopupContainer">
<ColumnHeightOutlined /> <ColumnHeightOutlined />
<template #overlay> <template #overlay>
<Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef"> <Menu @click="handleTitleClick" selectable v-model:selectedKeys="selectedKeysRef">
......
...@@ -152,10 +152,10 @@ export function useColumns( ...@@ -152,10 +152,10 @@ export function useColumns(
return hasPermission(column.auth) && isIfShow(column); return hasPermission(column.auth) && isIfShow(column);
}) })
.map((column) => { .map((column) => {
const { slots, dataIndex, customRender, format, edit, editRow, flag } = column; const { slots, customRender, format, edit, editRow, flag } = column;
if (!slots || !slots?.title) { if (!slots || !slots?.title) {
column.slots = { title: `header-${dataIndex}`, ...(slots || {}) }; // column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
column.customTitle = column.title; column.customTitle = column.title;
Reflect.deleteProperty(column, 'title'); Reflect.deleteProperty(column, 'title');
} }
...@@ -197,7 +197,7 @@ export function useColumns( ...@@ -197,7 +197,7 @@ export function useColumns(
* set columns * set columns
* @param columnList key|column * @param columnList key|column
*/ */
function setColumns(columnList: Partial<BasicColumn>[] | string[]) { function setColumns(columnList: Partial<BasicColumn>[] | (string | string[])[]) {
const columns = cloneDeep(columnList); const columns = cloneDeep(columnList);
if (!isArray(columns)) return; if (!isArray(columns)) return;
...@@ -210,23 +210,23 @@ export function useColumns( ...@@ -210,23 +210,23 @@ export function useColumns(
const cacheKeys = cacheColumns.map((item) => item.dataIndex); const cacheKeys = cacheColumns.map((item) => item.dataIndex);
if (!isString(firstColumn)) { if (!isString(firstColumn) && !isArray(firstColumn)) {
columnsRef.value = columns as BasicColumn[]; columnsRef.value = columns as BasicColumn[];
} else { } else {
const columnKeys = columns as string[]; const columnKeys = (columns as (string | string[])[]).map(m => m.toString());
const newColumns: BasicColumn[] = []; const newColumns: BasicColumn[] = [];
cacheColumns.forEach((item) => { cacheColumns.forEach((item) => {
newColumns.push({ newColumns.push({
...item, ...item,
defaultHidden: !columnKeys.includes(item.dataIndex! || (item.key as string)), defaultHidden: !columnKeys.includes(item.dataIndex?.toString() || (item.key as string))
}); });
}); });
// Sort according to another array // Sort according to another array
if (!isEqual(cacheKeys, columns)) { if (!isEqual(cacheKeys, columns)) {
newColumns.sort((prev, next) => { newColumns.sort((prev, next) => {
return ( return (
columnKeys.indexOf(prev.dataIndex as string) - columnKeys.indexOf(prev.dataIndex?.toString() as string) -
columnKeys.indexOf(next.dataIndex as string) columnKeys.indexOf(next.dataIndex?.toString() as string)
); );
}); });
} }
......
...@@ -292,7 +292,7 @@ export function useDataSource( ...@@ -292,7 +292,7 @@ export function useDataSource(
const isArrayResult = Array.isArray(res); const isArrayResult = Array.isArray(res);
let resultItems: Recordable[] = isArrayResult ? res : get(res, listField); let resultItems: Recordable[] = isArrayResult ? res : get(res, listField);
const resultTotal: number = isArrayResult ? 0 : get(res, totalField); const resultTotal: number = isArrayResult ? res.length : get(res, totalField);
// 假如数据变少,导致总页数变少并小于当前选中页码,通过getPaginationRef获取到的页码是不正确的,需获取正确的页码再次执行 // 假如数据变少,导致总页数变少并小于当前选中页码,通过getPaginationRef获取到的页码是不正确的,需获取正确的页码再次执行
if (resultTotal) { if (resultTotal) {
......
...@@ -412,7 +412,7 @@ export type CellFormat = ...@@ -412,7 +412,7 @@ export type CellFormat =
| Map<string | number, any>; | Map<string | number, any>;
// @ts-ignore // @ts-ignore
export interface BasicColumn extends ColumnProps { export interface BasicColumn extends ColumnProps<Recordable> {
children?: BasicColumn[]; children?: BasicColumn[];
filters?: { filters?: {
text: string; text: string;
...@@ -441,7 +441,14 @@ export interface BasicColumn extends ColumnProps { ...@@ -441,7 +441,14 @@ export interface BasicColumn extends ColumnProps {
editRow?: boolean; editRow?: boolean;
editable?: boolean; editable?: boolean;
editComponent?: ComponentType; editComponent?: ComponentType;
editComponentProps?: Recordable; editComponentProps?:
| ((opt: {
text: string | number | boolean | Recordable;
record: Recordable;
column: BasicColumn;
index: number;
}) => Recordable)
| Recordable;
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>); editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
editValueMap?: (value: any) => string; editValueMap?: (value: any) => string;
onEditRow?: () => void; onEditRow?: () => void;
...@@ -449,6 +456,13 @@ export interface BasicColumn extends ColumnProps { ...@@ -449,6 +456,13 @@ export interface BasicColumn extends ColumnProps {
auth?: RoleEnum | RoleEnum[] | string | string[]; auth?: RoleEnum | RoleEnum[] | string | string[];
// 业务控制是否显示 // 业务控制是否显示
ifShow?: boolean | ((column: BasicColumn) => boolean); ifShow?: boolean | ((column: BasicColumn) => boolean);
// 自定义修改后显示的内容
editRender?: (opt: {
text: string | number | boolean | Recordable;
record: Recordable;
column: BasicColumn;
index: number;
}) => VNodeChild | JSX.Element;
} }
export type ColumnChangeParam = { export type ColumnChangeParam = {
......
...@@ -2,11 +2,6 @@ ...@@ -2,11 +2,6 @@
@import './input.less'; @import './input.less';
@import './btn.less'; @import './btn.less';
// TODO beta.11 fix
.ant-col {
width: 100%;
}
.ant-image-preview-root { .ant-image-preview-root {
img { img {
display: unset; display: unset;
......
...@@ -53,6 +53,9 @@ ...@@ -53,6 +53,9 @@
{ {
field: 'password', field: 'password',
label: t('layout.header.lockScreenPassword'), label: t('layout.header.lockScreenPassword'),
colProps: {
span: 24,
},
component: 'InputPassword', component: 'InputPassword',
required: true, required: true,
}, },
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
name: 'DropdownMenuItem', name: 'DropdownMenuItem',
components: { MenuItem: Menu.Item, Icon }, components: { MenuItem: Menu.Item, Icon },
props: { props: {
// eslint-disable-next-line
key: propTypes.string, key: propTypes.string,
text: propTypes.string, text: propTypes.string,
icon: propTypes.string, icon: propTypes.string,
......
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
:items="childrenMenus" :items="childrenMenus"
:theme="getMenuTheme" :theme="getMenuTheme"
mixSider mixSider
@menuClick="handleMenuClick" @menu-click="handleMenuClick"
/> />
</ScrollContainer> </ScrollContainer>
<div <div
......
<template> <template>
<Dropdown :dropMenuList="getDropMenuList" :trigger="getTrigger" @menuEvent="handleMenuEvent"> <Dropdown
:dropMenuList="getDropMenuList"
:trigger="getTrigger"
placement="bottom"
overlayClassName="multiple-tabs__dropdown"
@menu-event="handleMenuEvent"
>
<div :class="`${prefixCls}__info`" @contextmenu="handleContext" v-if="getIsTabs"> <div :class="`${prefixCls}__info`" @contextmenu="handleContext" v-if="getIsTabs">
<span class="ml-1">{{ getTitle }}</span> <span class="ml-1">{{ getTitle }}</span>
</div> </div>
......
...@@ -180,3 +180,28 @@ html[data-theme='light'] { ...@@ -180,3 +180,28 @@ html[data-theme='light'] {
} }
} }
} }
.ant-tabs-dropdown-menu {
&-title-content {
display: flex;
align-items: center;
.@{prefix-cls} {
&-content__info {
width: auto;
margin-left: 0;
line-height: 28px;
}
}
}
&-item-remove {
margin-left: auto;
}
}
.multiple-tabs__dropdown {
.ant-dropdown-content {
width: 172px;
}
}
...@@ -92,6 +92,7 @@ export default { ...@@ -92,6 +92,7 @@ export default {
breadcrumb: 'Breadcrumbs', breadcrumb: 'Breadcrumbs',
breadcrumbFlat: 'Flat Mode', breadcrumbFlat: 'Flat Mode',
breadcrumbFlatDetail: 'Flat mode details', breadcrumbFlatDetail: 'Flat mode details',
requestDemo: 'Retry request demo',
breadcrumbChildren: 'Level mode', breadcrumbChildren: 'Level mode',
breadcrumbChildrenDetail: 'Level mode detail', breadcrumbChildrenDetail: 'Level mode detail',
......
...@@ -88,6 +88,7 @@ export default { ...@@ -88,6 +88,7 @@ export default {
ws: 'websocket测试', ws: 'websocket测试',
breadcrumb: '面包屑导航', breadcrumb: '面包屑导航',
breadcrumbFlat: '平级模式', breadcrumbFlat: '平级模式',
requestDemo: '测试请求重试',
breadcrumbFlatDetail: '平级详情', breadcrumbFlatDetail: '平级详情',
breadcrumbChildren: '层级模式', breadcrumbChildren: '层级模式',
breadcrumbChildrenDetail: '层级详情', breadcrumbChildrenDetail: '层级详情',
......
...@@ -32,6 +32,15 @@ const feat: AppRouteModule = { ...@@ -32,6 +32,15 @@ const feat: AppRouteModule = {
}, },
}, },
{ {
path: 'request',
name: 'RequestDemo',
// @ts-ignore
component: () => import('/@/views/demo/feat/request-demo/index.vue'),
meta: {
title: t('routes.demo.feat.requestDemo'),
},
},
{
path: 'session-timeout', path: 'session-timeout',
name: 'SessionTimeout', name: 'SessionTimeout',
component: () => import('/@/views/demo/feat/session-timeout/index.vue'), component: () => import('/@/views/demo/feat/session-timeout/index.vue'),
......
...@@ -58,7 +58,12 @@ export class Memory<T = any, V = any> { ...@@ -58,7 +58,12 @@ export class Memory<T = any, V = any> {
return value; return value;
} }
const now = new Date().getTime(); const now = new Date().getTime();
item.time = now + this.alive; /**
* Prevent overflow of the setTimeout Maximum delay value
* Maximum delay value 2,147,483,647 ms
* https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value
*/
item.time = expires > now ? expires : now + expires;
item.timeoutId = setTimeout( item.timeoutId = setTimeout(
() => { () => {
this.remove(key); this.remove(key);
......
...@@ -111,7 +111,10 @@ export class VAxios { ...@@ -111,7 +111,10 @@ export class VAxios {
// Response result interceptor error capture // Response result interceptor error capture
responseInterceptorsCatch && responseInterceptorsCatch &&
isFunction(responseInterceptorsCatch) && isFunction(responseInterceptorsCatch) &&
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch); this.axiosInstance.interceptors.response.use(undefined, (error) => {
// @ts-ignore
responseInterceptorsCatch(this.axiosInstance, error);
});
} }
/** /**
......
import { AxiosError, AxiosInstance } from 'axios';
/**
* 请求重试机制
*/
export class AxiosRetry {
/**
* 重试
*/
retry(AxiosInstance: AxiosInstance, error: AxiosError) {
// @ts-ignore
const { config } = error.response;
const { waitTime, count } = config?.requestOptions?.retryRequest;
config.__retryCount = config.__retryCount || 0;
if (config.__retryCount >= count) {
return Promise.reject(error);
}
config.__retryCount += 1;
return this.delay(waitTime).then(() => AxiosInstance(config));
}
/**
* 延迟
*/
private delay(waitTime: number) {
return new Promise((resolve) => setTimeout(resolve, waitTime));
}
}
...@@ -48,5 +48,5 @@ export abstract class AxiosTransform { ...@@ -48,5 +48,5 @@ export abstract class AxiosTransform {
/** /**
* @description: 请求之后的拦截器错误处理 * @description: 请求之后的拦截器错误处理
*/ */
responseInterceptorsCatch?: (error: Error) => void; responseInterceptorsCatch?: (axiosInstance: AxiosResponse, error: Error) => void;
} }
...@@ -17,6 +17,7 @@ import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog'; ...@@ -17,6 +17,7 @@ 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'; import { useUserStoreWithOut } from '/@/store/modules/user';
import { AxiosRetry } from '/@/utils/http/axios/axiosRetry';
const globSetting = useGlobSetting(); const globSetting = useGlobSetting();
const urlPrefix = globSetting.urlPrefix; const urlPrefix = globSetting.urlPrefix;
...@@ -158,7 +159,7 @@ const transform: AxiosTransform = { ...@@ -158,7 +159,7 @@ const transform: AxiosTransform = {
/** /**
* @description: 响应错误处理 * @description: 响应错误处理
*/ */
responseInterceptorsCatch: (error: any) => { responseInterceptorsCatch: (axiosInstance: AxiosResponse, error: any) => {
const { t } = useI18n(); const { t } = useI18n();
const errorLogStore = useErrorLogStoreWithOut(); const errorLogStore = useErrorLogStoreWithOut();
errorLogStore.addAjaxErrorInfo(error); errorLogStore.addAjaxErrorInfo(error);
...@@ -189,6 +190,14 @@ const transform: AxiosTransform = { ...@@ -189,6 +190,14 @@ const transform: AxiosTransform = {
} }
checkStatus(error?.response?.status, msg, errorMessageMode); checkStatus(error?.response?.status, msg, errorMessageMode);
// 添加自动重试机制 保险起见 只针对GET请求
const retryRequest = new AxiosRetry();
const { isOpenRetry } = config.requestOptions.retryRequest;
config.method?.toUpperCase() === RequestEnum.GET &&
isOpenRetry &&
// @ts-ignore
retryRequest.retry(axiosInstance, error);
return Promise.reject(error); return Promise.reject(error);
}, },
}; };
...@@ -234,6 +243,11 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) { ...@@ -234,6 +243,11 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
ignoreCancelToken: true, ignoreCancelToken: true,
// 是否携带token // 是否携带token
withToken: true, withToken: true,
retryRequest: {
isOpenRetry: true,
count: 5,
waitTime: 100,
},
}, },
}, },
opt || {}, opt || {},
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
:tab-list="tabListTitle" :tab-list="tabListTitle"
v-bind="$attrs" v-bind="$attrs"
:active-tab-key="activeKey" :active-tab-key="activeKey"
@tabChange="onTabChange" @tab-change="onTabChange"
> >
<p v-if="activeKey === 'tab1'"> <p v-if="activeKey === 'tab1'">
<VisitAnalysis /> <VisitAnalysis />
......
...@@ -39,28 +39,22 @@ ...@@ -39,28 +39,22 @@
splitNumber: 8, splitNumber: 8,
indicator: [ indicator: [
{ {
text: '电脑', name: '电脑',
max: 100,
}, },
{ {
text: '充电器', name: '充电器',
max: 100,
}, },
{ {
text: '耳机', name: '耳机',
max: 100,
}, },
{ {
text: '手机', name: '手机',
max: 100,
}, },
{ {
text: 'Ipad', name: 'Ipad',
max: 100,
}, },
{ {
text: '耳机', name: '耳机',
max: 100,
}, },
], ],
}, },
......
...@@ -39,28 +39,22 @@ ...@@ -39,28 +39,22 @@
splitNumber: 8, splitNumber: 8,
indicator: [ indicator: [
{ {
text: '2017', name: '2017',
max: 100,
}, },
{ {
text: '2017', name: '2017',
max: 100,
}, },
{ {
text: '2018', name: '2018',
max: 100,
}, },
{ {
text: '2019', name: '2019',
max: 100,
}, },
{ {
text: '2020', name: '2020',
max: 100,
}, },
{ {
text: '2021', name: '2021',
max: 100,
}, },
], ],
}, },
......
...@@ -43,28 +43,22 @@ ...@@ -43,28 +43,22 @@
splitNumber: 8, splitNumber: 8,
indicator: [ indicator: [
{ {
text: '2017', name: '2017',
max: 100,
}, },
{ {
text: '2017', name: '2017',
max: 100,
}, },
{ {
text: '2018', name: '2018',
max: 100,
}, },
{ {
text: '2019', name: '2019',
max: 100,
}, },
{ {
text: '2020', name: '2020',
max: 100,
}, },
{ {
text: '2021', name: '2021',
max: 100,
}, },
], ],
}, },
......
<template> <template>
<PageWrapper title="卡片列表示例" content="基础封装"> <PageWrapper title="卡片列表示例" content="基础封装">
<CardList :params="params" :api="demoListApi" @getMethod="getMethod" @delete="handleDel"> <CardList :params="params" :api="demoListApi" @get-method="getMethod" @delete="handleDel">
<template #header> <template #header>
<Button type="primary" color="error"> 按钮1 </Button> <Button type="primary" color="error"> 按钮1 </Button>
<Button type="primary" color="success"> 按钮2 </Button> <Button type="primary" color="success"> 按钮2 </Button>
......
<template> <template>
<PageWrapper title="点内外部触发事件"> <PageWrapper title="点内外部触发事件">
<ClickOutSide @clickOutside="handleClickOutside" class="flex justify-center"> <ClickOutSide @click-outside="handleClickOutside" class="flex justify-center">
<div @click="innerClick" class="demo-box"> <div @click="innerClick" class="demo-box">
{{ text }} {{ text }}
</div> </div>
......
<template>
<div class="request-box">
<a-button @click="handleClick" type="primary"> 点击会重新发起请求5次 </a-button>
<p>打开浏览器的network面板,可以看到发出了六次请求</p>
</div>
</template>
<script lang="ts" setup>
import { testRetry } from '/@/api/sys/user';
// @ts-ignore
const handleClick = async () => {
await testRetry();
};
</script>
<style lang="less">
.request-box {
margin: 50px;
}
p {
margin-top: 10px;
}
</style>
...@@ -142,6 +142,9 @@ ...@@ -142,6 +142,9 @@
field: 'divider-basic', field: 'divider-basic',
component: 'Divider', component: 'Divider',
label: '基础字段', label: '基础字段',
colProps: {
span: 24,
},
}, },
{ {
field: 'field1', field: 'field1',
...@@ -340,6 +343,9 @@ ...@@ -340,6 +343,9 @@
field: 'divider-api-select', field: 'divider-api-select',
component: 'Divider', component: 'Divider',
label: '远程下拉演示', label: '远程下拉演示',
colProps: {
span: 24,
},
}, },
{ {
field: 'field30', field: 'field30',
...@@ -458,6 +464,9 @@ ...@@ -458,6 +464,9 @@
field: 'divider-linked', field: 'divider-linked',
component: 'Divider', component: 'Divider',
label: '字段联动', label: '字段联动',
colProps: {
span: 24,
},
}, },
{ {
field: 'province', field: 'province',
...@@ -509,6 +518,9 @@ ...@@ -509,6 +518,9 @@
component: 'Divider', component: 'Divider',
label: '互斥多选', label: '互斥多选',
helpMessage: ['两个Select共用数据源', '但不可选择对方已选中的项目'], helpMessage: ['两个Select共用数据源', '但不可选择对方已选中的项目'],
colProps: {
span: 24,
},
}, },
{ {
field: 'selectA', field: 'selectA',
...@@ -531,9 +543,31 @@ ...@@ -531,9 +543,31 @@
}, },
}, },
{ {
field: 'divider-deconstruct',
component: 'Divider',
label: '字段解构',
helpMessage: ['如果组件的值是 array 或者 object', '可以根据 ES6 的解构语法分别取值'],
colProps: {
span: 24,
},
},
{
field: '[startTime, endTime]',
label: '时间范围',
component: 'RangePicker',
componentProps: {
format: 'YYYY-MM-DD HH:mm:ss',
placeholder: ['开始时间', '结束时间'],
showTime: { format: 'HH:mm:ss' },
},
},
{
field: 'divider-others', field: 'divider-others',
component: 'Divider', component: 'Divider',
label: '其它', label: '其它',
colProps: {
span: 24,
},
}, },
{ {
field: 'field20', field: 'field20',
...@@ -602,6 +636,7 @@ ...@@ -602,6 +636,7 @@
keyword.value = ''; keyword.value = '';
}, },
handleSubmit: (values: any) => { handleSubmit: (values: any) => {
console.log('values', values);
createMessage.success('click search,values:' + JSON.stringify(values)); createMessage.success('click search,values:' + JSON.stringify(values));
}, },
check, check,
......
import { FormSchema } from '/@/components/Form'; import { FormSchema } from '/@/components/Form';
const colProps = {
span: 8,
};
export const schemas: FormSchema[] = [ export const schemas: FormSchema[] = [
{ {
field: 'title', field: 'title',
component: 'Input', component: 'Input',
label: '标题', label: '标题',
colProps,
componentProps: { componentProps: {
placeholder: '给目标起个名字', placeholder: '给目标起个名字',
}, },
...@@ -14,12 +18,37 @@ export const schemas: FormSchema[] = [ ...@@ -14,12 +18,37 @@ export const schemas: FormSchema[] = [
field: 'time', field: 'time',
component: 'RangePicker', component: 'RangePicker',
label: '起止日期', label: '起止日期',
colProps,
required: true, required: true,
}, },
{ {
field: 'client',
component: 'Input',
colProps,
label: '客户',
helpMessage: '目标的服务对象',
subLabel: '( 选填 )',
componentProps: {
placeholder: '请描述你服务的客户,内部客户直接 @姓名/工号',
},
},
{
field: 'weights',
component: 'InputNumber',
label: '权重',
colProps,
subLabel: '( 选填 )',
componentProps: {
formatter: (value: string) => (value ? `${value}%` : ''),
parser: (value: string) => value.replace('%', ''),
placeholder: '请输入',
},
},
{
field: 'target', field: 'target',
component: 'InputTextArea', component: 'InputTextArea',
label: '目标描述', label: '目标描述',
colProps,
componentProps: { componentProps: {
placeholder: '请输入你的阶段性工作目标', placeholder: '请输入你的阶段性工作目标',
rows: 4, rows: 4,
...@@ -30,46 +59,33 @@ export const schemas: FormSchema[] = [ ...@@ -30,46 +59,33 @@ export const schemas: FormSchema[] = [
field: 'metrics', field: 'metrics',
component: 'InputTextArea', component: 'InputTextArea',
label: '衡量标准', label: '衡量标准',
colProps,
componentProps: { componentProps: {
placeholder: '请输入衡量标准', placeholder: '请输入衡量标准',
rows: 4, rows: 4,
}, },
required: true, required: true,
}, },
{
field: 'client',
component: 'Input',
label: '客户',
helpMessage: '目标的服务对象',
subLabel: '( 选填 )',
componentProps: {
placeholder: '请描述你服务的客户,内部客户直接 @姓名/工号',
},
},
{ {
field: 'inviteer', field: 'inviteer',
component: 'Input', component: 'Input',
label: '邀评人', label: '邀评人',
subLabel: '( 选填 )', colProps: {
componentProps: { span: 8,
placeholder: '请直接 @姓名/工号,最多可邀请 5 人',
},
}, },
{
field: 'weights',
component: 'InputNumber',
label: '权重',
subLabel: '( 选填 )', subLabel: '( 选填 )',
componentProps: { componentProps: {
formatter: (value: string) => (value ? `${value}%` : ''), placeholder: '请直接 @姓名/工号,最多可邀请 5 人',
parser: (value: string) => value.replace('%', ''),
placeholder: '请输入',
}, },
}, },
{ {
field: 'disclosure', field: 'disclosure',
component: 'RadioGroup', component: 'RadioGroup',
label: '目标公开', label: '目标公开',
colProps: {
span: 16,
},
itemProps: { itemProps: {
extra: '客户、邀评人默认被分享', extra: '客户、邀评人默认被分享',
}, },
...@@ -91,9 +107,12 @@ export const schemas: FormSchema[] = [ ...@@ -91,9 +107,12 @@ export const schemas: FormSchema[] = [
}, },
}, },
{ {
field: 'disclosurer', field: 'disclosure',
component: 'Select', component: 'Select',
label: ' ', label: ' ',
colProps: {
span: 8,
},
show: ({ model }) => { show: ({ model }) => {
return model.disclosure === '2'; return model.disclosure === '2';
}, },
......
...@@ -25,12 +25,12 @@ ...@@ -25,12 +25,12 @@
span: 8, span: 8,
}, },
wrapperCol: { wrapperCol: {
span: 10, span: 15,
}, },
schemas: schemas, schemas: schemas,
actionColOptions: { actionColOptions: {
offset: 8, offset: 8,
span: 12, span: 23,
}, },
submitButtonOptions: { submitButtonOptions: {
text: '提交', text: '提交',
......
...@@ -34,6 +34,7 @@ ...@@ -34,6 +34,7 @@
const tableRef = ref<{ getDataSource: () => any } | null>(null); const tableRef = ref<{ getDataSource: () => any } | null>(null);
const [register, { validate }] = useForm({ const [register, { validate }] = useForm({
layout: 'vertical',
baseColProps: { baseColProps: {
span: 6, span: 6,
}, },
...@@ -42,6 +43,7 @@ ...@@ -42,6 +43,7 @@
}); });
const [registerTask, { validate: validateTaskForm }] = useForm({ const [registerTask, { validate: validateTaskForm }] = useForm({
layout: 'vertical',
baseColProps: { baseColProps: {
span: 6, span: 6,
}, },
......
...@@ -15,6 +15,9 @@ export const step1Schemas: FormSchema[] = [ ...@@ -15,6 +15,9 @@ export const step1Schemas: FormSchema[] = [
}, },
], ],
}, },
colProps: {
span: 24,
},
}, },
{ {
field: 'fac', field: 'fac',
...@@ -23,6 +26,9 @@ export const step1Schemas: FormSchema[] = [ ...@@ -23,6 +26,9 @@ export const step1Schemas: FormSchema[] = [
required: true, required: true,
defaultValue: 'test@example.com', defaultValue: 'test@example.com',
slot: 'fac', slot: 'fac',
colProps: {
span: 24,
},
}, },
{ {
field: 'pay', field: 'pay',
...@@ -37,6 +43,9 @@ export const step1Schemas: FormSchema[] = [ ...@@ -37,6 +43,9 @@ export const step1Schemas: FormSchema[] = [
label: '收款人姓名', label: '收款人姓名',
defaultValue: 'Vben', defaultValue: 'Vben',
required: true, required: true,
colProps: {
span: 24,
},
}, },
{ {
field: 'money', field: 'money',
...@@ -49,6 +58,9 @@ export const step1Schemas: FormSchema[] = [ ...@@ -49,6 +58,9 @@ export const step1Schemas: FormSchema[] = [
prefix: () => '¥', prefix: () => '¥',
}; };
}, },
colProps: {
span: 24,
},
}, },
]; ];
...@@ -59,5 +71,8 @@ export const step2Schemas: FormSchema[] = [ ...@@ -59,5 +71,8 @@ export const step2Schemas: FormSchema[] = [
label: '支付密码', label: '支付密码',
required: true, required: true,
defaultValue: '123456', defaultValue: '123456',
colProps: {
span: 24,
},
}, },
]; ];
...@@ -32,13 +32,13 @@ ...@@ -32,13 +32,13 @@
{ {
title: 'ID', title: 'ID',
dataIndex: 'id', dataIndex: 'id',
slots: { customRender: 'id' }, // slots: { customRender: 'id' },
}, },
{ {
title: '头像', title: '头像',
dataIndex: 'avatar', dataIndex: 'avatar',
width: 100, width: 100,
slots: { customRender: 'avatar' }, // slots: { customRender: 'avatar' },
}, },
{ {
title: '分类', title: '分类',
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
width: 80, width: 80,
align: 'center', align: 'center',
defaultHidden: true, defaultHidden: true,
slots: { customRender: 'category' }, // slots: { customRender: 'category' },
}, },
{ {
title: '姓名', title: '姓名',
...@@ -58,13 +58,13 @@ ...@@ -58,13 +58,13 @@
dataIndex: 'imgArr', dataIndex: 'imgArr',
helpMessage: ['这是简单模式的图片列表', '只会显示一张在表格中', '但点击可预览多张图片'], helpMessage: ['这是简单模式的图片列表', '只会显示一张在表格中', '但点击可预览多张图片'],
width: 140, width: 140,
slots: { customRender: 'img' }, // slots: { customRender: 'img' },
}, },
{ {
title: '照片列表2', title: '照片列表2',
dataIndex: 'imgs', dataIndex: 'imgs',
width: 160, width: 160,
slots: { customRender: 'imgs' }, // slots: { customRender: 'imgs' },
}, },
{ {
title: '地址', title: '地址',
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
{ {
title: '编号', title: '编号',
dataIndex: 'no', dataIndex: 'no',
slots: { customRender: 'no' }, // slots: { customRender: 'no' },
}, },
{ {
title: '开始时间', title: '开始时间',
......
...@@ -9,13 +9,14 @@ ...@@ -9,13 +9,14 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, h } from 'vue';
import { BasicTable, useTable, BasicColumn } from '/@/components/Table'; import { BasicTable, useTable, BasicColumn } from '/@/components/Table';
import { optionsListApi } from '/@/api/demo/select'; import { optionsListApi } from '/@/api/demo/select';
import { demoListApi } from '/@/api/demo/table'; import { demoListApi } from '/@/api/demo/table';
import { treeOptionsListApi } from '/@/api/demo/tree'; import { treeOptionsListApi } from '/@/api/demo/tree';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { Progress } from 'ant-design-vue';
const columns: BasicColumn[] = [ const columns: BasicColumn[] = [
{ {
title: '输入框', title: '输入框',
...@@ -60,6 +61,15 @@ ...@@ -60,6 +61,15 @@
editRule: true, editRule: true,
editComponent: 'InputNumber', editComponent: 'InputNumber',
width: 200, width: 200,
editComponentProps: () => {
return {
max: 100,
min: 0,
};
},
editRender: ({ text }) => {
return h(Progress, { percent: Number(text) });
},
}, },
{ {
title: '下拉框', title: '下拉框',
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
actionColumn: { actionColumn: {
width: 160, width: 160,
title: 'Action', title: 'Action',
slots: { customRender: 'action' }, // slots: { customRender: 'action' },
}, },
}); });
function handleDelete(record: Recordable) { function handleDelete(record: Recordable) {
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
width: 160, width: 160,
title: 'Action', title: 'Action',
dataIndex: 'action', dataIndex: 'action',
slots: { customRender: 'action' }, // slots: { customRender: 'action' },
}, },
}); });
function handleDelete(record: Recordable) { function handleDelete(record: Recordable) {
......
...@@ -123,13 +123,13 @@ export function getCustomHeaderColumns(): BasicColumn[] { ...@@ -123,13 +123,13 @@ export function getCustomHeaderColumns(): BasicColumn[] {
// title: '姓名', // title: '姓名',
dataIndex: 'name', dataIndex: 'name',
width: 120, width: 120,
slots: { title: 'customTitle' }, // slots: { title: 'customTitle' },
}, },
{ {
// title: '地址', // title: '地址',
dataIndex: 'address', dataIndex: 'address',
width: 120, width: 120,
slots: { title: 'customAddress' }, // slots: { title: 'customAddress' },
sorter: true, sorter: true,
}, },
......
...@@ -5,23 +5,7 @@ ...@@ -5,23 +5,7 @@
> >
<div <div
:class="`${prefixCls}__unlock`" :class="`${prefixCls}__unlock`"
class=" class="absolute top-0 left-1/2 flex pt-5 h-16 items-center justify-center sm:text-md xl:text-xl text-white flex-col cursor-pointer transform translate-x-1/2"
absolute
top-0
left-1/2
flex
pt-5
h-16
items-center
justify-center
sm:text-md
xl:text-xl
text-white
flex-col
cursor-pointer
transform
translate-x-1/2
"
@click="handleShowForm(false)" @click="handleShowForm(false)"
v-show="showDate" v-show="showDate"
> >
......
...@@ -32,23 +32,7 @@ ...@@ -32,23 +32,7 @@
<div class="flex w-full h-full py-5 xl:h-auto xl:py-0 xl:my-0 xl:w-6/12"> <div class="flex w-full h-full py-5 xl:h-auto xl:py-0 xl:my-0 xl:w-6/12">
<div <div
:class="`${prefixCls}-form`" :class="`${prefixCls}-form`"
class=" class="relative w-full px-5 py-8 mx-auto my-auto rounded-md shadow-md xl:ml-16 xl:bg-transparent sm:px-8 xl:p-4 xl:shadow-none sm:w-3/4 lg:w-2/4 xl:w-auto enter-x"
relative
w-full
px-5
py-8
mx-auto
my-auto
rounded-md
shadow-md
xl:ml-16 xl:bg-transparent
sm:px-8
xl:p-4 xl:shadow-none
sm:w-3/4
lg:w-2/4
xl:w-auto
enter-x
"
> >
<LoginForm /> <LoginForm />
<ForgetPasswordForm /> <ForgetPasswordForm />
......
module.exports = { module.exports = {
root: true, root: true,
plugins: ['stylelint-order'], plugins: ['stylelint-order'],
customSyntax: 'postcss-html',
extends: ['stylelint-config-standard', 'stylelint-config-prettier'], extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
customSyntax: 'postcss-html',
rules: { rules: {
'function-no-unknown': null,
'selector-class-pattern': null, 'selector-class-pattern': null,
'selector-pseudo-class-no-unknown': [ 'selector-pseudo-class-no-unknown': [
true, true,
...@@ -35,6 +36,7 @@ module.exports = { ...@@ -35,6 +36,7 @@ module.exports = {
}, },
], ],
'no-empty-source': null, 'no-empty-source': null,
'string-quotes': null,
'named-grid-areas-no-invalid': null, 'named-grid-areas-no-invalid': null,
'unicode-bom': 'never', 'unicode-bom': 'never',
'no-descending-specificity': null, 'no-descending-specificity': null,
...@@ -72,7 +74,7 @@ module.exports = { ...@@ -72,7 +74,7 @@ module.exports = {
overrides: [ overrides: [
{ {
files: ['*.vue', '**/*.vue', '*.html', '**/*.html'], files: ['*.vue', '**/*.vue', '*.html', '**/*.html'],
extends: ['stylelint-config-recommended', 'stylelint-config-html'], extends: ['stylelint-config-recommended'],
rules: { rules: {
'keyframes-name-pattern': null, 'keyframes-name-pattern': null,
'selector-pseudo-class-no-unknown': [ 'selector-pseudo-class-no-unknown': [
...@@ -89,5 +91,10 @@ module.exports = { ...@@ -89,5 +91,10 @@ module.exports = {
], ],
}, },
}, },
{
files: ['*.less', '**/*.less'],
customSyntax: 'postcss-less',
extends: ['stylelint-config-standard', 'stylelint-config-recommended-vue'],
},
], ],
}; };
export default jest.fn().mockImplementation(() => ({
postMessage: jest.fn(),
onmessage: jest.fn(),
onerror: jest.fn(),
}));
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
"stop": "npx pm2 stop ecosystem.config.js" "stop": "npx pm2 stop ecosystem.config.js"
}, },
"dependencies": { "dependencies": {
"fs-extra": "^10.0.0", "fs-extra": "^10.0.1",
"koa": "^2.13.4", "koa": "^2.13.4",
"koa-body": "^4.2.0", "koa-body": "^4.2.0",
"koa-bodyparser": "^4.3.0", "koa-bodyparser": "^4.3.0",
...@@ -24,13 +24,13 @@ ...@@ -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": "^17.0.5", "@types/node": "^17.0.21",
"nodemon": "^2.0.15", "nodemon": "^2.0.15",
"pm2": "^5.1.2", "pm2": "^5.2.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-node": "^10.4.0", "ts-node": "^10.7.0",
"tsconfig-paths": "^3.12.0", "tsconfig-paths": "^3.14.0",
"tsup": "^5.11.9", "tsup": "^5.12.1",
"typescript": "^4.5.4" "typescript": "^4.6.2"
} }
} }
// import { mount } from '@vue/test-utils';
// import { Button } from '/@/components/Button';
test('if jest is normal.', async () => {
expect('jest').toEqual('jest');
});
// TODO Vue component testing is not supported temporarily
// test('is a Vue instance.', async () => {
// const wrapper = mount(Button, {
// slots: {
// default: 'Button text',
// },
// });
// expect(wrapper.html()).toContain('Button text');
// });
...@@ -23,8 +23,15 @@ export interface RequestOptions { ...@@ -23,8 +23,15 @@ export interface RequestOptions {
ignoreCancelToken?: boolean; ignoreCancelToken?: boolean;
// Whether to send token in header // Whether to send token in header
withToken?: boolean; withToken?: boolean;
// 请求重试机制
retryRequest?: RetryRequest;
} }
export interface RetryRequest {
isOpenRetry: boolean;
count: number;
waitTime: number;
}
export interface Result<T = any> { export interface Result<T = any> {
code: number; code: number;
type: 'success' | 'error' | 'warning'; type: 'success' | 'error' | 'warning';
......
...@@ -53,24 +53,31 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { ...@@ -53,24 +53,31 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
], ],
}, },
server: { server: {
https: true,
// Listening on all local IPs // Listening on all local IPs
host: true, host: true,
port: VITE_PORT, port: VITE_PORT,
// Load proxy configuration from .env // Load proxy configuration from .env
proxy: createProxy(VITE_PROXY), proxy: createProxy(VITE_PROXY),
}, },
esbuild: {
pure: VITE_DROP_CONSOLE ? ['console.log', 'debugger'] : [],
},
build: { build: {
minify: false, target: 'es2015',
// target: 'es2015', cssTarget: 'chrome80',
// cssTarget: 'chrome86',
outDir: OUTPUT_DIR, outDir: OUTPUT_DIR,
terserOptions: { // minify: 'terser',
compress: { /**
keep_infinity: true, * 当 minify=“minify:'terser'” 解开注释
// Used to delete console in production environment * Uncomment when minify="minify:'terser'"
drop_console: VITE_DROP_CONSOLE, */
}, // terserOptions: {
}, // compress: {
// keep_infinity: true,
// drop_console: VITE_DROP_CONSOLE,
// },
// },
// Turning off brotliSize display can slightly reduce packaging time // Turning off brotliSize display can slightly reduce packaging time
brotliSize: false, brotliSize: false,
chunkSizeWarningLimit: 2000, chunkSizeWarningLimit: 2000,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论