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

This commit is contained in:
vben 2021-05-23 09:17:16 +08:00
commit 14da175611
66 changed files with 2664 additions and 1240 deletions

View File

@ -5,6 +5,8 @@
- 新增图形编辑器示例
- 新增代码编辑器(包含 Json 编辑器)
- 新增 `JsonPreview`Json 数据查看组件
- 表格的数据列(column)和操作列(actionColumn)的字段可以根据权限和业务来控制是否显示
- 新增权限控制表格示例(AuthColumn.vue)
### ⚡ Performance Improvements

View File

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

View File

@ -7,7 +7,7 @@ import styleImport from 'vite-plugin-style-import';
export function configStyleImportPlugin(isBuild: boolean) {
if (!isBuild) return [];
const pwaPlugin = styleImport({
const styleImportPlugin = styleImport({
libs: [
{
libraryName: 'ant-design-vue',
@ -18,5 +18,5 @@ export function configStyleImportPlugin(isBuild: boolean) {
},
],
});
return pwaPlugin;
return styleImportPlugin;
}

View File

@ -27,10 +27,13 @@ export function configThemePlugin(isBuild: boolean): Plugin[] {
switch (s) {
case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
return '.ant-steps-item-icon > .ant-steps-icon';
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover':
case '.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):active':
return s;
case '.ant-steps-item-icon > .ant-steps-icon':
return s;
}
return `[data-theme] ${s}`;
},
colorVariables: [...getThemeColors(), ...colors],
@ -58,5 +61,5 @@ export function configThemePlugin(isBuild: boolean): Plugin[] {
}),
];
return (plugin as unknown) as Plugin[];
return plugin as unknown as Plugin[];
}

View File

@ -7,6 +7,7 @@ function createFakeUserList() {
userId: '1',
username: 'vben',
realName: 'Vben Admin',
avatar: 'http://q1.qlogo.cn/g?b=qq&nk=190848757&s=640',
desc: 'manager',
password: '123456',
token: 'fakeToken1',
@ -22,6 +23,7 @@ function createFakeUserList() {
username: 'test',
password: '123456',
realName: 'test user',
avatar: 'http://q1.qlogo.cn/g?b=qq&nk=339449197&s=640',
desc: 'tester',
token: 'fakeToken2',
roles: [

View File

@ -8,7 +8,7 @@
},
"scripts": {
"bootstrap": "yarn install",
"serve": "cross-env --max_old_space_size=4096 vite",
"serve": "npm run dev",
"dev": "cross-env --max_old_space_size=4096 vite",
"build": "vite build && esno ./build/script/postBuild.ts",
"build:no-cache": "yarn clean:cache && npm run build",
@ -32,13 +32,13 @@
"postinstall": "npm run install:husky"
},
"dependencies": {
"@iconify/iconify": "^2.0.0-rc.6",
"@vueuse/core": "^4.9.0",
"@iconify/iconify": "^2.0.1",
"@vueuse/core": "^4.11.0",
"@zxcvbn-ts/core": "^0.3.0",
"ant-design-vue": "^2.1.2",
"ant-design-vue": "^2.1.6",
"axios": "^0.21.1",
"crypto-js": "^4.0.0",
"echarts": "^5.1.0",
"echarts": "^5.1.1",
"lodash-es": "^4.17.21",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
@ -48,15 +48,16 @@
"sortablejs": "^1.13.0",
"vue": "3.0.11",
"vue-i18n": "9.0.0",
"vue-router": "^4.0.6",
"vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.8",
"vue-types": "^3.0.2"
},
"devDependencies": {
"@commitlint/cli": "^12.1.1",
"@commitlint/config-conventional": "^12.1.1",
"@iconify/json": "^1.1.331",
"@commitlint/cli": "^12.1.4",
"@commitlint/config-conventional": "^12.1.4",
"@iconify/json": "^1.1.347",
"@purge-icons/generated": "^0.7.0",
"@types/codemirror": "^0.0.109",
"@types/codemirror": "^5.60.0",
"@types/crypto-js": "^4.0.1",
"@types/fs-extra": "^9.0.11",
"@types/inquirer": "^7.3.1",
@ -66,60 +67,59 @@
"@types/qrcode": "^1.4.0",
"@types/qs": "^6.9.6",
"@types/sortablejs": "^1.10.6",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"@vitejs/plugin-legacy": "^1.3.2",
"@vitejs/plugin-vue": "^1.2.1",
"@vitejs/plugin-vue-jsx": "^1.1.3",
"@typescript-eslint/eslint-plugin": "^4.24.0",
"@typescript-eslint/parser": "^4.24.0",
"@vitejs/plugin-legacy": "^1.4.0",
"@vitejs/plugin-vue": "^1.2.2",
"@vitejs/plugin-vue-jsx": "^1.1.4",
"@vue/compiler-sfc": "3.0.11",
"autoprefixer": "^10.2.5",
"body-parser": "^1.19.0",
"commitizen": "^4.2.3",
"commitizen": "^4.2.4",
"conventional-changelog-cli": "^2.1.1",
"cross-env": "^7.0.3",
"dotenv": "^8.2.0",
"eslint": "^7.25.0",
"eslint-config-prettier": "^8.2.0",
"dotenv": "^9.0.2",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
"eslint-define-config": "^1.0.8",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.9.0",
"esno": "^0.5.0",
"fs-extra": "^9.1.0",
"fs-extra": "^10.0.0",
"http-server": "^0.12.3",
"husky": "^6.0.0",
"inquirer": "^8.0.0",
"inquirer": "^8.1.0",
"is-ci": "^3.0.0",
"less": "^4.1.1",
"lint-staged": "^10.5.4",
"postcss": "^8.2.12",
"prettier": "^2.2.1",
"lint-staged": "^11.0.0",
"postcss": "^8.3.0",
"prettier": "^2.3.0",
"pretty-quick": "^3.1.0",
"rimraf": "^3.0.2",
"rollup-plugin-visualizer": "5.3.4",
"stylelint": "^13.12.0",
"rollup-plugin-visualizer": "5.5.0",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^21.0.0",
"stylelint-config-standard": "^22.0.0",
"stylelint-order": "^4.1.0",
"ts-node": "^9.1.1",
"typescript": "4.2.4",
"vite": "2.1.5",
"vite-plugin-compression": "^0.2.4",
"vite": "2.3.3",
"vite-plugin-compression": "^0.2.5",
"vite-plugin-html": "^2.0.7",
"vite-plugin-imagemin": "^0.3.0",
"vite-plugin-imagemin": "^0.3.2",
"vite-plugin-mock": "^2.5.0",
"vite-plugin-purge-icons": "^0.7.0",
"vite-plugin-pwa": "^0.7.2",
"vite-plugin-pwa": "^0.7.3",
"vite-plugin-style-import": "^0.10.0",
"vite-plugin-svg-icons": "^0.4.3",
"vite-plugin-svg-icons": "^0.5.0",
"vite-plugin-theme": "^0.7.1",
"vite-plugin-windicss": "0.14.6",
"vite-plugin-windicss": "0.15.10",
"vue-eslint-parser": "^7.6.0",
"vue-tsc": "^0.0.25"
"vue-tsc": "^0.1.3"
},
"resolutions": {
"//": "Used to install imagemin dependencies, because imagemin may not be installed in China.If it is abroad, you can delete it",
"//": "Used to install imagemin dependencies, because imagemin may not be installed in China. If it is abroad, you can delete it",
"bin-wrapper": "npm:bin-wrapper-china",
"rollup": "^2.45.2"
"rollup": "^2.48.0"
},
"repository": {
"type": "git",

View File

@ -38,6 +38,8 @@ export interface GetUserInfoByUserIdModel {
username: string;
// 真实名字
realName: string;
// 头像
avatar: string;
// 介绍
desc?: string;
}

View File

@ -1,6 +1,11 @@
<template>
<div class="h-full">
<CodeMirrorEditor :value="getValue" @change="handleValueChange" :mode="mode" />
<CodeMirrorEditor
:value="getValue"
@change="handleValueChange"
:mode="mode"
:readonly="readonly"
/>
</div>
</template>
<script lang="ts">
@ -24,6 +29,10 @@
type: String,
default: MODE.JSON,
},
readonly: {
type: Boolean,
default: false,
},
},
emits: ['change'],
setup(props, { emit }) {

View File

@ -62,11 +62,11 @@
},
imageStyle: {
type: Object as PropType<CSSProperties>,
default: () => {},
default: () => ({}),
},
options: {
type: Object as PropType<Options>,
default: () => {},
default: () => ({}),
},
},
emits: ['cropperedInfo'],
@ -76,22 +76,18 @@
const isReady = ref(false);
const getImageStyle = computed(
(): CSSProperties => {
return {
height: props.height,
maxWidth: '100%',
...props.imageStyle,
};
}
);
const getImageStyle = computed((): CSSProperties => {
return {
height: props.height,
maxWidth: '100%',
...props.imageStyle,
};
});
const getWrapperStyle = computed(
(): CSSProperties => {
const { height } = props;
return { height: `${height}`.replace(/px/, '') + 'px' };
}
);
const getWrapperStyle = computed((): CSSProperties => {
const { height } = props;
return { height: `${height}`.replace(/px/, '') + 'px' };
});
async function init() {
const imgEl = unref(imgElRef);

View File

@ -32,12 +32,12 @@
props: {
flowOptions: {
type: Object as PropType<Definition>,
default: () => {},
default: () => ({}),
},
data: {
type: Object as PropType<any>,
default: () => {},
default: () => ({}),
},
toolbar: {
@ -55,7 +55,7 @@
const appStore = useAppStore();
const [register, { openModal }] = useModal();
createFlowChartContext({
logicFlow: (lfInstance as unknown) as LogicFlow,
logicFlow: lfInstance as unknown as LogicFlow,
});
const getFlowOptions = computed(() => {

View File

@ -1,5 +1,11 @@
<template>
<Form v-bind="{ ...$attrs, ...$props }" :class="getFormClass" ref="formElRef" :model="formModel">
<Form
v-bind="{ ...$attrs, ...$props }"
:class="getFormClass"
ref="formElRef"
:model="formModel"
@keypress.enter="handleEnterPress"
>
<Row :style="getRowWrapStyle">
<slot name="formHeader"></slot>
<template v-for="schema in getSchema" :key="schema.field">
@ -35,17 +41,7 @@
import type { AdvanceState } from './types/hooks';
import type { CSSProperties, Ref } from 'vue';
import {
defineComponent,
reactive,
ref,
computed,
unref,
onMounted,
watch,
toRefs,
nextTick,
} from 'vue';
import { defineComponent, reactive, ref, computed, unref, onMounted, watch, nextTick } from 'vue';
import { Form, Row } from 'ant-design-vue';
import FormItem from './components/FormItem.vue';
import FormAction from './components/FormAction.vue';
@ -91,11 +87,9 @@
const { prefixCls } = useDesign('basic-form');
// Get the basic configuration of the form
const getProps = computed(
(): FormProps => {
return { ...props, ...unref(propsRef) } as FormProps;
}
);
const getProps = computed((): FormProps => {
return { ...props, ...unref(propsRef) } as FormProps;
});
const getFormClass = computed(() => {
return [
@ -107,12 +101,10 @@
});
// Get uniform row style
const getRowWrapStyle = computed(
(): CSSProperties => {
const { baseRowStyle = {} } = unref(getProps);
return baseRowStyle;
}
);
const getRowWrapStyle = computed((): CSSProperties => {
const { baseRowStyle = {} } = unref(getProps);
return baseRowStyle;
});
const getSchema = computed((): FormSchema[] => {
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
@ -143,13 +135,8 @@
defaultValueRef,
});
const { transformDateFunc, fieldMapToTime, autoFocusFirstItem } = toRefs(
unref(getProps)
) as any;
const { handleFormValues, initDefault } = useFormValues({
transformDateFuncRef: transformDateFunc,
fieldMapToTimeRef: fieldMapToTime,
getProps,
defaultValueRef,
getSchema,
formModel,
@ -157,7 +144,7 @@
useAutoFocus({
getSchema,
autoFocusFirstItem,
getProps,
isInitedDefault: isInitedDefaultRef,
formElRef: formElRef as Ref<FormActionType>,
});
@ -228,6 +215,17 @@
formModel[key] = value;
}
function handleEnterPress(e: KeyboardEvent) {
const { autoSubmitOnEnter } = unref(getProps);
if (!autoSubmitOnEnter) return;
if (e.key === 'Enter' && e.target && e.target instanceof HTMLElement) {
const target: HTMLElement = e.target as HTMLElement;
if (target && target.tagName && target.tagName.toUpperCase() == 'INPUT') {
handleSubmit();
}
}
}
const formActionType: Partial<FormActionType> = {
getFieldsValue,
setFieldsValue,
@ -251,6 +249,7 @@
return {
handleToggleAdvanced,
handleEnterPress,
formModel,
defaultValueRef,
advanceState,

View File

@ -16,6 +16,7 @@ import {
Switch,
TimePicker,
TreeSelect,
Slider,
} from 'ant-design-vue';
import RadioButtonGroup from './components/RadioButtonGroup.vue';
@ -44,6 +45,7 @@ componentMap.set('RadioGroup', Radio.Group);
componentMap.set('Checkbox', Checkbox);
componentMap.set('CheckboxGroup', Checkbox.Group);
componentMap.set('Cascader', Cascader);
componentMap.set('Slider', Slider);
componentMap.set('DatePicker', DatePicker);
componentMap.set('MonthPicker', DatePicker.MonthPicker);

View File

@ -55,7 +55,7 @@
// api params
params: {
type: Object as PropType<Recordable>,
default: () => {},
default: () => ({}),
},
// support xxx.xxx.xx
resultField: propTypes.string.def(''),

View File

@ -67,15 +67,15 @@
showAdvancedButton: propTypes.bool.def(true),
resetButtonOptions: {
type: Object as PropType<ButtonOptions>,
default: () => {},
default: () => ({}),
},
submitButtonOptions: {
type: Object as PropType<ButtonOptions>,
default: () => {},
default: () => ({}),
},
actionColOptions: {
type: Object as PropType<Partial<ColEx>>,
default: () => {},
default: () => ({}),
},
actionSpan: propTypes.number.def(6),
isAdvanced: propTypes.bool,
@ -99,16 +99,14 @@
return actionColOpt;
});
const getResetBtnOptions = computed(
(): ButtonOptions => {
return Object.assign(
{
text: t('common.resetText'),
},
props.resetButtonOptions
);
}
);
const getResetBtnOptions = computed((): ButtonOptions => {
return Object.assign(
{
text: t('common.resetText'),
},
props.resetButtonOptions
);
});
const getSubmitBtnOptions = computed(() => {
return Object.assign(

View File

@ -10,7 +10,7 @@
import { componentMap } from '../componentMap';
import { BasicHelp } from '/@/components/Basic';
import { isBoolean, isFunction } from '/@/utils/is';
import { isBoolean, isFunction, isNull } from '/@/utils/is';
import { getSlot } from '/@/utils/helper/tsxHelper';
import { createPlaceholderMessage, setComponentRuleType } from '../helper';
import { upperFirst, cloneDeep } from 'lodash-es';
@ -24,19 +24,19 @@
props: {
schema: {
type: Object as PropType<FormSchema>,
default: () => {},
default: () => ({}),
},
formProps: {
type: Object as PropType<FormProps>,
default: () => {},
default: () => ({}),
},
allDefaultValues: {
type: Object as PropType<Recordable>,
default: () => {},
default: () => ({}),
},
formModel: {
type: Object as PropType<Recordable>,
default: () => {},
default: () => ({}),
},
setFormModel: {
type: Function as PropType<(key: string, value: any) => void>,
@ -141,15 +141,47 @@
}
let rules: ValidationRule[] = cloneDeep(defRules) as ValidationRule[];
const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps;
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
? rulesMessageJoinLabel
: globalRulesMessageJoinLabel;
const defaultMsg = createPlaceholderMessage(component) + `${joinLabel ? label : ''}`;
function validator(rule: any, value: any) {
const msg = rule.message || defaultMsg;
if (value === undefined || isNull(value)) {
//
return Promise.reject(msg);
} else if (Array.isArray(value) && value.length === 0) {
//
return Promise.reject(msg);
} else if (typeof value === 'string' && value.trim() === '') {
//
return Promise.reject(msg);
} else if (
typeof value === 'object' &&
Reflect.has(value, 'checked') &&
Reflect.has(value, 'halfChecked') &&
Array.isArray(value.checked) &&
Array.isArray(value.halfChecked) &&
value.checked.length === 0 &&
value.halfChecked.length === 0
) {
// tree
return Promise.reject(msg);
}
return Promise.resolve();
}
if ((!rules || rules.length === 0) && required) {
rules = [{ required, type: 'string' }];
rules = [{ required, validator }];
}
const requiredRuleIndex: number = rules.findIndex(
(rule) => Reflect.has(rule, 'required') && !Reflect.has(rule, 'validator')
);
const { rulesMessageJoinLabel: globalRulesMessageJoinLabel } = props.formProps;
if (requiredRuleIndex !== -1) {
const rule = rules[requiredRuleIndex];
const { isShow } = getShow();
@ -160,12 +192,8 @@
if (!Reflect.has(rule, 'type')) {
rule.type = component === 'InputNumber' ? 'number' : 'string';
}
const joinLabel = Reflect.has(props.schema, 'rulesMessageJoinLabel')
? rulesMessageJoinLabel
: globalRulesMessageJoinLabel;
rule.message =
rule.message || createPlaceholderMessage(component) + `${joinLabel ? label : ''}`;
rule.message = rule.message || defaultMsg;
if (component.includes('Input') || component.includes('Textarea')) {
rule.whitespace = true;
@ -220,13 +248,11 @@
};
const isCreatePlaceholder = !propsData.disabled && autoSetPlaceHolder;
let placeholder;
// RangePicker place is an array
if (isCreatePlaceholder && component !== 'RangePicker' && component) {
placeholder =
propsData.placeholder =
unref(getComponentsProps)?.placeholder || createPlaceholderMessage(component);
}
propsData.placeholder = placeholder;
propsData.codeField = field;
propsData.formValues = unref(getValues);
@ -261,13 +287,16 @@
) : (
label
);
if (!helpMessage || (Array.isArray(helpMessage) && helpMessage.length === 0)) {
const getHelpMessage = isFunction(helpMessage)
? helpMessage(unref(getValues))
: helpMessage;
if (!getHelpMessage || (Array.isArray(getHelpMessage) && getHelpMessage.length === 0)) {
return renderLabel;
}
return (
<span>
{renderLabel}
<BasicHelp placement="top" class="mx-1" text={helpMessage} {...helpComponentProps} />
<BasicHelp placement="top" class="mx-1" text={getHelpMessage} {...helpComponentProps} />
</span>
);
}

View File

@ -1,22 +1,22 @@
import type { ComputedRef, Ref } from 'vue';
import type { FormSchema, FormActionType } from '../types/form';
import type { FormSchema, FormActionType, FormProps } from '../types/form';
import { unref, nextTick, watchEffect } from 'vue';
interface UseAutoFocusContext {
getSchema: ComputedRef<FormSchema[]>;
autoFocusFirstItem: Ref<boolean>;
getProps: ComputedRef<FormProps>;
isInitedDefault: Ref<boolean>;
formElRef: Ref<FormActionType>;
}
export async function useAutoFocus({
getSchema,
autoFocusFirstItem,
getProps,
formElRef,
isInitedDefault,
}: UseAutoFocusContext) {
watchEffect(async () => {
if (unref(isInitedDefault) || !unref(autoFocusFirstItem)) return;
if (unref(isInitedDefault) || !unref(getProps).autoFocusFirstItem) return;
await nextTick();
const schemas = unref(getSchema);
const formEl = unref(formElRef);

View File

@ -76,7 +76,7 @@ export function useFormEvents({
const { componentProps } = schema || {};
let _props = componentProps as any;
if (typeof componentProps === 'function') {
_props = _props();
_props = _props({ formModel });
}
formModel[key] = value ? (_props?.valueFormat ? value : dateUtil(value)) : null;
}

View File

@ -3,21 +3,21 @@ import { dateUtil } from '/@/utils/dateUtil';
import { unref } from 'vue';
import type { Ref, ComputedRef } from 'vue';
import type { FieldMapToTime, FormSchema } from '../types/form';
import type { FormProps, FormSchema } from '../types/form';
import { set } from 'lodash-es';
interface UseFormValuesContext {
transformDateFuncRef: Ref<Fn>;
fieldMapToTimeRef: Ref<FieldMapToTime>;
defaultValueRef: Ref<any>;
getSchema: ComputedRef<FormSchema[]>;
getProps: ComputedRef<FormProps>;
formModel: Recordable;
}
export function useFormValues({
transformDateFuncRef,
fieldMapToTimeRef,
defaultValueRef,
getSchema,
formModel,
getProps,
}: UseFormValuesContext) {
// Processing form values
function handleFormValues(values: Recordable) {
@ -31,18 +31,18 @@ export function useFormValues({
if ((isArray(value) && value.length === 0) || isFunction(value)) {
continue;
}
const transformDateFunc = unref(transformDateFuncRef);
const transformDateFunc = unref(getProps).transformDateFunc;
if (isObject(value)) {
value = transformDateFunc(value);
value = transformDateFunc?.(value);
}
if (isArray(value) && value[0]?._isAMomentObject && value[1]?._isAMomentObject) {
value = value.map((item) => transformDateFunc(item));
value = value.map((item) => transformDateFunc?.(item));
}
// Remove spaces
if (isString(value)) {
value = value.trim();
}
res[key] = value;
set(res, key, value);
}
return handleRangeTimeValue(res);
}
@ -51,7 +51,7 @@ export function useFormValues({
* @description: Processing time interval parameters
*/
function handleRangeTimeValue(values: Recordable) {
const fieldMapToTime = unref(fieldMapToTimeRef);
const fieldMapToTime = unref(getProps).fieldMapToTime;
if (!fieldMapToTime || !Array.isArray(fieldMapToTime)) {
return values;

View File

@ -37,6 +37,8 @@ export const basicProps = {
type: Object as PropType<Partial<ColEx>>,
},
autoSetPlaceHolder: propTypes.bool.def(true),
// 在INPUT组件上单击回车时是否自动提交
autoSubmitOnEnter: propTypes.bool.def(false),
submitOnReset: propTypes.bool,
size: propTypes.oneOf(['default', 'small', 'large']).def('default'),
// 禁用表单

View File

@ -83,6 +83,8 @@ export interface FormProps {
fieldMapToTime?: FieldMapToTime;
// Placeholder is set automatically
autoSetPlaceHolder?: boolean;
// Auto submit on press enter on input
autoSubmitOnEnter?: boolean;
// Check whether the information is added to the label
rulesMessageJoinLabel?: boolean;
// Whether to show collapse and expand buttons
@ -125,7 +127,10 @@ export interface FormSchema {
// Auxiliary text
subLabel?: string;
// Help text on the right side of the text
helpMessage?: string | string[];
helpMessage?:
| string
| string[]
| ((renderCallbackParams: RenderCallbackParams) => string | string[]);
// BaseHelp component props
helpComponentProps?: Partial<HelpComponentProps>;
// Label width, if it is passed, the labelCol and WrapperCol configured by itemProps will be invalid

View File

@ -108,4 +108,5 @@ export type ComponentType =
| 'StrengthMeter'
| 'Upload'
| 'IconPicker'
| 'Render';
| 'Render'
| 'Slider';

View File

@ -107,14 +107,12 @@
}
// Custom title component: get title
const getMergeProps = computed(
(): ModalProps => {
return {
...props,
...(unref(propsRef) as any),
};
}
);
const getMergeProps = computed((): ModalProps => {
return {
...props,
...(unref(propsRef) as any),
};
});
const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
modalWrapperRef,
@ -122,30 +120,28 @@
wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
});
// modal component does not need title
const getProps = computed(
(): ModalProps => {
const opt = {
...unref(getMergeProps),
visible: unref(visibleRef),
title: undefined,
};
return {
...opt,
wrapClassName: unref(getWrapClassName),
};
}
);
// modal component does not need title and origin buttons
const getProps = computed((): ModalProps => {
const opt = {
...unref(getMergeProps),
visible: unref(visibleRef),
okButtonProps: undefined,
cancelButtonProps: undefined,
title: undefined,
};
return {
...opt,
wrapClassName: unref(getWrapClassName),
};
});
const getBindValue = computed(
(): Recordable => {
const attr = { ...attrs, ...unref(getProps) };
if (unref(fullScreenRef)) {
return omit(attr, 'height');
}
return attr;
const getBindValue = computed((): Recordable => {
const attr = { ...attrs, ...unref(getProps) };
if (unref(fullScreenRef)) {
return omit(attr, 'height');
}
);
return attr;
});
const getWrapperHeight = computed(() => {
if (unref(fullScreenRef)) return undefined;

View File

@ -8,6 +8,7 @@
import { toCanvas, QRCodeRenderersOptions, LogoType } from './qrcodePlus';
import { toDataURL } from 'qrcode';
import { downloadByUrl } from '/@/utils/file/download';
import { QrcodeDoneEventParams } from './types';
export default defineComponent({
name: 'QrCode',
@ -38,10 +39,9 @@
validator: (v: string) => ['canvas', 'img'].includes(v),
},
},
emits: { done: (url: string) => !!url, error: (error: any) => !!error },
emits: { done: (data: QrcodeDoneEventParams) => !!data, error: (error: any) => !!error },
setup(props, { emit }) {
const wrapRef = ref<HTMLCanvasElement | HTMLImageElement | null>(null);
const urlRef = ref<string>('');
async function createQrcode() {
try {
const { tag, value, options = {}, width, logo } = props;
@ -58,8 +58,7 @@
content: renderValue,
options: options || {},
});
urlRef.value = url;
emit('done', url);
emit('done', { url, ctx: (wrapEl as HTMLCanvasElement).getContext('2d') });
return;
}
@ -70,8 +69,7 @@
...options,
});
(unref(wrapRef) as HTMLImageElement).src = url;
urlRef.value = url;
emit('done', url);
emit('done', { url });
}
} catch (error) {
emit('error', error);
@ -81,7 +79,13 @@
* file download
*/
function download(fileName?: string) {
const url = unref(urlRef);
let url = '';
const wrapEl = unref(wrapRef);
if (wrapEl instanceof HTMLCanvasElement) {
url = wrapEl.toDataURL();
} else if (wrapEl instanceof HTMLImageElement) {
url = wrapEl.src;
}
if (!url) return;
downloadByUrl({
url,

View File

@ -31,3 +31,8 @@ export type ToCanvasFn = (options: RenderQrCodeParams) => Promise<unknown>;
export interface QrCodeActionType {
download: (fileName?: string) => void;
}
export interface QrcodeDoneEventParams {
url: string;
ctx?: CanvasRenderingContext2D | null;
}

View File

@ -14,7 +14,7 @@
props: {
item: {
type: Object as PropType<Menu>,
default: () => {},
default: () => ({}),
},
dot: propTypes.bool,
collapseParent: propTypes.bool,

View File

@ -64,7 +64,7 @@
props: {
item: {
type: Object as PropType<Menu>,
default: () => {},
default: () => ({}),
},
parent: propTypes.bool,
collapsedShowTitle: propTypes.bool,

View File

@ -176,12 +176,8 @@
getDataSourceRef
);
const {
getFormProps,
replaceFormSlotKey,
getFormSlotKeys,
handleSearchInfoChange,
} = useTableForm(getProps, slots, fetch, getLoading);
const { getFormProps, replaceFormSlotKey, getFormSlotKeys, handleSearchInfoChange } =
useTableForm(getProps, slots, fetch, getLoading);
const getBindValues = computed(() => {
const dataSource = unref(getDataSourceRef);
@ -300,7 +296,7 @@
.@{prefix-cls} {
&-row__striped {
td {
background-color: content-background;
background-color: @app-content-background;
}
}
@ -331,6 +327,7 @@
border-radius: 2px;
.ant-table-title {
min-height: 40px;
padding: 0 0 8px 0 !important;
}

View File

@ -23,7 +23,7 @@
props: {
column: {
type: Object as PropType<BasicColumn>,
default: () => {},
default: () => ({}),
},
},
setup(props) {

View File

@ -25,17 +25,19 @@
</template>
<script lang="ts">
import { defineComponent, PropType, computed, toRaw } from 'vue';
import { MoreOutlined } from '@ant-design/icons-vue';
import { Divider } from 'ant-design-vue';
import Icon from '/@/components/Icon/index';
import { ActionItem, TableActionType } from '/@/components/Table';
import { PopConfirmButton } from '/@/components/Button';
import { Divider } from 'ant-design-vue';
import { Dropdown } from '/@/components/Dropdown';
import { useDesign } from '/@/hooks/web/useDesign';
import { useTableContext } from '../hooks/useTableContext';
import { usePermission } from '/@/hooks/web/usePermission';
import { isBoolean, isFunction } from '/@/utils/is';
import { propTypes } from '/@/utils/propTypes';
import { ACTION_COLUMN_FLAG } from '../const';
@ -61,33 +63,56 @@
table = useTableContext();
}
const { hasPermission } = usePermission();
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(action);
}
return isIfShow;
}
const getActions = computed(() => {
return (toRaw(props.actions) || []).map((action) => {
const { popConfirm } = action;
return {
type: 'link',
size: 'small',
...action,
...(popConfirm || {}),
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
enable: !!popConfirm,
};
});
return (toRaw(props.actions) || [])
.filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
})
.map((action) => {
const { popConfirm } = action;
return {
type: 'link',
size: 'small',
...action,
...(popConfirm || {}),
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
enable: !!popConfirm,
};
});
});
const getDropdownList = computed(() => {
return (toRaw(props.dropDownActions) || []).map((action, index) => {
const { label, popConfirm } = action;
return {
...action,
...popConfirm,
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
text: label,
divider: index < props.dropDownActions.length - 1 ? props.divider : false,
};
});
return (toRaw(props.dropDownActions) || [])
.filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
})
.map((action, index) => {
const { label, popConfirm } = action;
return {
...action,
...popConfirm,
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
text: label,
divider: index < props.dropDownActions.length - 1 ? props.divider : false,
};
});
});
const getAlign = computed(() => {

View File

@ -60,7 +60,7 @@
},
column: {
type: Object as PropType<BasicColumn>,
default: () => {},
default: () => ({}),
},
index: propTypes.number,
},
@ -131,16 +131,14 @@
return option?.label ?? value;
});
const getWrapperStyle = computed(
(): CSSProperties => {
if (unref(getIsCheckComp) || unref(getRowEditable)) {
return {};
}
return {
width: 'calc(100% - 48px)',
};
const getWrapperStyle = computed((): CSSProperties => {
if (unref(getIsCheckComp) || unref(getRowEditable)) {
return {};
}
);
return {
width: 'calc(100% - 48px)',
};
});
const getRowEditable = computed(() => {
const { editable } = props.record || {};

View File

@ -30,23 +30,21 @@
props: {
setting: {
type: Object as PropType<TableSetting>,
default: () => {},
default: () => ({}),
},
},
setup(props) {
const { t } = useI18n();
const getSetting = computed(
(): TableSetting => {
return {
redo: true,
size: true,
setting: true,
fullScreen: false,
...props.setting,
};
}
);
const getSetting = computed((): TableSetting => {
return {
redo: true,
size: true,
setting: true,
fullScreen: false,
...props.setting,
};
});
return { getSetting, t };
},

View File

@ -6,6 +6,7 @@ import { unref, Ref, computed, watch, ref, toRaw } from 'vue';
import { renderEditCell } from '../components/editable';
import { usePermission } from '/@/hooks/web/usePermission';
import { useI18n } from '/@/hooks/web/useI18n';
import { isBoolean, isArray, isString, isObject, isFunction } from '/@/utils/is';
@ -108,11 +109,11 @@ export function useColumns(
propsRef: ComputedRef<BasicTableProps>,
getPaginationRef: ComputedRef<boolean | PaginationProps>
) {
const columnsRef = (ref(unref(propsRef).columns) as unknown) as Ref<BasicColumn[]>;
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>;
let cacheColumns = unref(propsRef).columns;
const getColumnsRef = computed(() => {
const columns = unref(columnsRef);
const columns = cloneDeep(unref(columnsRef));
handleIndexColumn(propsRef, getPaginationRef, columns);
handleActionColumn(propsRef, columns);
@ -121,8 +122,7 @@ export function useColumns(
}
const { ellipsis } = unref(propsRef);
const cloneColumns = cloneDeep(columns);
cloneColumns.forEach((item) => {
columns.forEach((item) => {
const { customRender, slots } = item;
handleItem(
@ -130,34 +130,53 @@ export function useColumns(
Reflect.has(item, 'ellipsis') ? !!item.ellipsis : !!ellipsis && !customRender && !slots
);
});
return cloneColumns;
return columns;
});
function isIfShow(column: BasicColumn): boolean {
const ifShow = column.ifShow;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(column);
}
return isIfShow;
}
const { hasPermission } = usePermission();
const getViewColumns = computed(() => {
const viewColumns = sortFixedColumn(unref(getColumnsRef));
const columns = cloneDeep(viewColumns);
columns.forEach((column) => {
const { slots, dataIndex, customRender, format, edit, editRow, flag } = column;
return columns
.filter((column) => {
return hasPermission(column.auth) && isIfShow(column);
})
.map((column) => {
const { slots, dataIndex, customRender, format, edit, editRow, flag } = column;
if (!slots || !slots?.title) {
column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
column.customTitle = column.title;
Reflect.deleteProperty(column, 'title');
}
const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!);
if (!customRender && format && !edit && !isDefaultAction) {
column.customRender = ({ text, record, index }) => {
return formatCell(text, format, record, index);
};
}
if (!slots || !slots?.title) {
column.slots = { title: `header-${dataIndex}`, ...(slots || {}) };
column.customTitle = column.title;
Reflect.deleteProperty(column, 'title');
}
const isDefaultAction = [INDEX_COLUMN_FLAG, ACTION_COLUMN_FLAG].includes(flag!);
if (!customRender && format && !edit && !isDefaultAction) {
column.customRender = ({ text, record, index }) => {
return formatCell(text, format, record, index);
};
}
// edit table
if ((edit || editRow) && !isDefaultAction) {
column.customRender = renderEditCell(column);
}
});
return columns;
// edit table
if ((edit || editRow) && !isDefaultAction) {
column.customRender = renderEditCell(column);
}
return column;
});
});
watch(

View File

@ -36,37 +36,40 @@ export function useCustomRow(
const customRow = (record: Recordable, index: number) => {
return {
onClick: (e: Event) => {
emit('row-click', record, index, e);
e?.stopPropagation();
const { rowSelection, rowKey, clickToRowSelect } = unref(propsRef);
if (!rowSelection || !clickToRowSelect) return;
const keys = getSelectRowKeys();
const key = getKey(record, rowKey, unref(getAutoCreateKey));
if (!key) return;
function handleClick() {
const { rowSelection, rowKey, clickToRowSelect } = unref(propsRef);
if (!rowSelection || !clickToRowSelect) return;
const keys = getSelectRowKeys();
const key = getKey(record, rowKey, unref(getAutoCreateKey));
if (!key) return;
const isCheckbox = rowSelection.type === 'checkbox';
if (isCheckbox) {
if (!keys.includes(key)) {
setSelectedRowKeys([...keys, key]);
return;
}
const keyIndex = keys.findIndex((item) => item === key);
keys.splice(keyIndex, 1);
setSelectedRowKeys(keys);
return;
}
const isRadio = rowSelection.type === 'radio';
if (isRadio) {
if (!keys.includes(key)) {
if (keys.length) {
clearSelectedRowKeys();
const isCheckbox = rowSelection.type === 'checkbox';
if (isCheckbox) {
if (!keys.includes(key)) {
setSelectedRowKeys([...keys, key]);
return;
}
setSelectedRowKeys([key]);
const keyIndex = keys.findIndex((item) => item === key);
keys.splice(keyIndex, 1);
setSelectedRowKeys(keys);
return;
}
clearSelectedRowKeys();
const isRadio = rowSelection.type === 'radio';
if (isRadio) {
if (!keys.includes(key)) {
if (keys.length) {
clearSelectedRowKeys();
}
setSelectedRowKeys([key]);
return;
}
clearSelectedRowKeys();
}
}
handleClick();
emit('row-click', record, index, e);
},
onDblclick: (event: Event) => {
emit('row-dbClick', record, index, event);

View File

@ -150,15 +150,8 @@ export function useDataSource(
}
async function fetch(opt?: FetchParams) {
const {
api,
searchInfo,
fetchSetting,
beforeFetch,
afterFetch,
useSearchForm,
pagination,
} = unref(propsRef);
const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } =
unref(propsRef);
if (!api || !isFunction(api)) return;
try {
setLoading(true);

View File

@ -15,9 +15,7 @@ type UseTableMethod = TableActionType & {
getForm: () => FormActionType;
};
export function useTable(
tableProps?: Props
): [
export function useTable(tableProps?: Props): [
(instance: TableActionType, formInstance: UseTableMethod) => void,
TableActionType & {
getForm: () => FormActionType;
@ -129,7 +127,7 @@ export function useTable(
return toRaw(getTableInstance().getCacheColumns());
},
getForm: () => {
return (unref(formRef) as unknown) as FormActionType;
return unref(formRef) as unknown as FormActionType;
},
setShowPagination: async (show: boolean) => {
getTableInstance().setShowPagination(show);

View File

@ -9,18 +9,16 @@ export function useTableForm(
fetch: (opt?: FetchParams | undefined) => Promise<void>,
getLoading: ComputedRef<boolean | undefined>
) {
const getFormProps = computed(
(): Partial<FormProps> => {
const { formConfig } = unref(propsRef);
const { submitButtonOptions } = formConfig || {};
return {
showAdvancedButton: true,
...formConfig,
submitButtonOptions: { loading: unref(getLoading), ...submitButtonOptions },
compact: true,
};
}
);
const getFormProps = computed((): Partial<FormProps> => {
const { formConfig } = unref(propsRef);
const { submitButtonOptions } = formConfig || {};
return {
showAdvancedButton: true,
...formConfig,
submitButtonOptions: { loading: unref(getLoading), ...submitButtonOptions },
compact: true,
};
});
const getFormSlotKeys = computed(() => {
const keys = Object.keys(slots);

View File

@ -8,41 +8,39 @@ import { isString } from '/@/utils/is';
import { getSlot } from '/@/utils/helper/tsxHelper';
export function useTableHeader(propsRef: ComputedRef<BasicTableProps>, slots: Slots) {
const getHeaderProps = computed(
(): Recordable => {
const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef);
const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
if (hideTitle && !isString(title)) {
return {};
}
return {
title: hideTitle
? null
: () =>
h(
TableHeader,
{
title,
titleHelpMessage,
showTableSetting,
tableSetting,
} as Recordable,
{
...(slots.toolbar
? {
toolbar: () => getSlot(slots, 'toolbar'),
}
: {}),
...(slots.tableTitle
? {
tableTitle: () => getSlot(slots, 'tableTitle'),
}
: {}),
}
),
};
const getHeaderProps = computed((): Recordable => {
const { title, showTableSetting, titleHelpMessage, tableSetting } = unref(propsRef);
const hideTitle = !slots.tableTitle && !title && !slots.toolbar && !showTableSetting;
if (hideTitle && !isString(title)) {
return {};
}
);
return {
title: hideTitle
? null
: () =>
h(
TableHeader,
{
title,
titleHelpMessage,
showTableSetting,
tableSetting,
} as Recordable,
{
...(slots.toolbar
? {
toolbar: () => getSlot(slots, 'toolbar'),
}
: {}),
...(slots.tableTitle
? {
tableTitle: () => getSlot(slots, 'tableTitle'),
}
: {}),
}
),
};
});
return { getHeaderProps };
}

View File

@ -8,6 +8,7 @@ import type {
import { ComponentType } from './componentType';
import { VueNode } from '/@/utils/propTypes';
import { RoleEnum } from '/@/enums/roleEnum';
export declare type SortOrder = 'ascend' | 'descend';
@ -421,4 +422,8 @@ export interface BasicColumn extends ColumnProps {
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
editValueMap?: (value: any) => string;
onEditRow?: () => void;
// 权限编码控制是否显示
auth?: RoleEnum | RoleEnum[] | string | string[];
// 业务控制是否显示
ifShow?: boolean | ((column: BasicColumn) => boolean);
}

View File

@ -1,4 +1,5 @@
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
import { RoleEnum } from '/@/enums/roleEnum';
export interface ActionItem extends ButtonProps {
onClick?: Fn;
label: string;
@ -7,6 +8,10 @@ export interface ActionItem extends ButtonProps {
popConfirm?: PopConfirm;
disabled?: boolean;
divider?: boolean;
// 权限编码控制是否显示
auth?: RoleEnum | RoleEnum[] | string | string[];
// 业务控制是否显示
ifShow?: boolean | ((action: ActionItem) => boolean);
}
export interface PopConfirm {

View File

@ -23,11 +23,14 @@
import { filter } from '/@/utils/helper/treeHelper';
import { useTree } from './useTree';
import { useContextMenu, ContextMenuItem } from '/@/hooks/web/useContextMenu';
import { useContextMenu } from '/@/hooks/web/useContextMenu';
import { useExpose } from '/@/hooks/core/useExpose';
import { useDesign } from '/@/hooks/web/useDesign';
import { basicProps } from './props';
import { CreateContextOptions } from '/@/components/ContextMenu';
import { CheckEvent } from './types';
interface State {
expandedKeys: Keys;
@ -58,17 +61,15 @@
const [createContextMenu] = useContextMenu();
const { prefixCls } = useDesign('basic-tree');
const getReplaceFields = computed(
(): Required<ReplaceFields> => {
const { replaceFields } = props;
return {
children: 'children',
title: 'title',
key: 'key',
...replaceFields,
};
}
);
const getReplaceFields = computed((): Required<ReplaceFields> => {
const { replaceFields } = props;
return {
children: 'children',
title: 'title',
key: 'key',
...replaceFields,
};
});
const getBindValues = computed(() => {
let propsData = {
@ -88,12 +89,11 @@
state.selectedKeys = v;
emit('update:selectedKeys', v);
},
onCheck: (v: CheckKeys) => {
onCheck: (v: CheckKeys, e: CheckEvent) => {
state.checkedKeys = v;
const rawVal = toRaw(v);
emit('change', rawVal);
emit('check', rawVal);
emit('update:value', rawVal);
emit('check', rawVal, e);
},
onRightClick: handleRightClick,
};
@ -109,13 +109,8 @@
return searchState.startSearch && searchState.searchData?.length === 0;
});
const {
deleteNodeByKey,
insertNodeByKey,
filterByLevel,
updateNodeByKey,
getAllKeys,
} = useTree(treeDataRef, getReplaceFields);
const { deleteNodeByKey, insertNodeByKey, filterByLevel, updateNodeByKey, getAllKeys } =
useTree(treeDataRef, getReplaceFields);
function getIcon(params: Recordable, icon?: string) {
if (!icon) {
@ -128,18 +123,20 @@
async function handleRightClick({ event, node }: Recordable) {
const { rightMenuList: menuList = [], beforeRightClick } = props;
let rightMenuList: ContextMenuItem[] = [];
let contextMenuOptions: CreateContextOptions = { event, items: [] };
if (beforeRightClick && isFunction(beforeRightClick)) {
rightMenuList = await beforeRightClick(node);
let result = await beforeRightClick(node, event);
if (Array.isArray(result)) {
contextMenuOptions.items = result;
} else {
Object.assign(contextMenuOptions, result);
}
} else {
rightMenuList = menuList;
contextMenuOptions.items = menuList;
}
if (!rightMenuList.length) return;
createContextMenu({
event,
items: rightMenuList,
});
if (!contextMenuOptions.items?.length) return;
createContextMenu(contextMenuOptions);
}
function setExpandedKeys(keys: Keys) {
@ -185,9 +182,13 @@
searchState.startSearch = true;
const { title: titleField } = unref(getReplaceFields);
searchState.searchData = filter(unref(treeDataRef), (node) => {
return node[titleField]?.includes(searchValue) ?? false;
});
searchState.searchData = filter(
unref(treeDataRef),
(node) => {
return node[titleField]?.includes(searchValue) ?? false;
},
unref(getReplaceFields)
);
}
function handleClickNode(key: string, children: TreeItem[]) {
@ -227,6 +228,15 @@
}
);
watch(
() => state.checkedKeys,
() => {
const v = toRaw(state.checkedKeys);
emit('update:value', v);
emit('change', v);
}
);
// watchEffect(() => {
// console.log('======================');
// console.log(props.value);
@ -285,9 +295,11 @@
return null;
}
return data.map((item) => {
const { title: titleField, key: keyField, children: childrenField } = unref(
getReplaceFields
);
const {
title: titleField,
key: keyField,
children: childrenField,
} = unref(getReplaceFields);
const propsData = omit(item, 'title');
const icon = getIcon({ ...item, level }, item.icon);
@ -305,7 +317,11 @@
) : (
<>
{icon && <TreeIcon icon={icon} />}
<span class={`${prefixCls}__content`}>{get(item, titleField)}</span>
<span
class={unref(getBindValues)?.blockNode ? `${prefixCls}__content` : ''}
>
{get(item, titleField)}
</span>
<span class={`${prefixCls}__actions`}>
{renderAction({ ...item, level })}
</span>

View File

@ -1,12 +1,12 @@
import type { PropType } from 'vue';
import type { ReplaceFields, ActionItem, Keys, CheckKeys } from './types';
import type { ReplaceFields, ActionItem, Keys, CheckKeys, ContextMenuOptions } from './types';
import type { ContextMenuItem } from '/@/hooks/web/useContextMenu';
import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
import { propTypes } from '/@/utils/propTypes';
export const basicProps = {
value: {
type: Array as PropType<Keys>,
type: [Object, Array] as PropType<Keys | CheckKeys>,
},
renderIcon: {
type: Function as PropType<(params: Recordable) => string>,
@ -53,7 +53,7 @@ export const basicProps = {
},
beforeRightClick: {
type: Function as PropType<(...arg: any) => ContextMenuItem[]>,
type: Function as PropType<(...arg: any) => ContextMenuItem[] | ContextMenuOptions>,
default: null,
},

View File

@ -1,4 +1,5 @@
import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
import type { TreeDataItem, CheckEvent as CheckEventOrigin } from 'ant-design-vue/es/tree/Tree';
import { ContextMenuItem } from '/@/hooks/web/useContextMenu';
export interface ActionItem {
render: (record: Recordable) => any;
show?: boolean | ((record: Recordable) => boolean);
@ -40,3 +41,11 @@ export interface InsertNodeParams {
list?: TreeDataItem[];
push?: 'push' | 'unshift';
}
export interface ContextMenuOptions {
icon?: string;
styles?: any;
items?: ContextMenuItem[];
}
export type CheckEvent = CheckEventOrigin;

View File

@ -8,7 +8,7 @@
>
<div :class="`${prefixCls}__entry`">
<div :class="`${prefixCls}__header`">
<img :src="headerImg" :class="`${prefixCls}__header-img`" />
<img :src="avatar" :class="`${prefixCls}__header-img`" />
<p :class="`${prefixCls}__header-name`">
{{ getRealName }}
</p>
@ -71,6 +71,11 @@
await resetFields();
}
const avatar = computed(() => {
const { avatar } = userStore.getUserInfo;
return avatar || headerImg;
});
return {
t,
prefixCls,
@ -78,7 +83,7 @@
register,
registerForm,
handleLock,
headerImg,
avatar,
};
},
});

View File

@ -1,7 +1,7 @@
<template>
<Dropdown placement="bottomLeft" :overlayClassName="`${prefixCls}-dropdown-overlay`">
<span :class="[prefixCls, `${prefixCls}--${theme}`]" class="flex">
<img :class="`${prefixCls}__header`" :src="headerImg" />
<img :class="`${prefixCls}__header`" :src="getUserInfo.avatar" />
<span :class="`${prefixCls}__info hidden md:block`">
<span :class="`${prefixCls}__name `" class="truncate">
{{ getUserInfo.realName }}
@ -19,6 +19,7 @@
/>
<MenuDivider v-if="getShowDoc" />
<MenuItem
v-if="getUseLockPage"
key="lock"
:text="t('layout.header.tooltipLock')"
icon="ion:lock-closed-outline"
@ -70,12 +71,12 @@
setup() {
const { prefixCls } = useDesign('header-user-dropdown');
const { t } = useI18n();
const { getShowDoc } = useHeaderSetting();
const { getShowDoc, getUseLockPage } = useHeaderSetting();
const userStore = useUserStore();
const getUserInfo = computed(() => {
const { realName = '', desc } = userStore.getUserInfo || {};
return { realName, desc };
const { realName = '', avatar, desc } = userStore.getUserInfo || {};
return { realName, avatar: avatar || headerImg, desc };
});
const [register, { openModal }] = useModal();
@ -114,8 +115,8 @@
getUserInfo,
handleMenuClick,
getShowDoc,
headerImg,
register,
getUseLockPage,
};
},
});

View File

@ -112,15 +112,11 @@
getMenuWidth,
getIsMixSidebar,
} = useMenuSetting();
const {
getUseErrorHandle,
getShowSettingButton,
getSettingButtonPosition,
} = useRootSetting();
const { getUseErrorHandle, getShowSettingButton, getSettingButtonPosition } =
useRootSetting();
const {
getHeaderTheme,
getUseLockPage,
getShowFullScreen,
getShowNotice,
getShowContent,
@ -189,7 +185,6 @@
getShowLocalePicker,
getShowFullScreen,
getShowNotice,
getUseLockPage,
getUseErrorHandle,
getLogoWidth,
getIsMixSidebar,

View File

@ -23,7 +23,6 @@
props: {
event: {
type: Number as PropType<HandlerEnum>,
default: () => {},
},
title: {
type: String,

View File

@ -25,7 +25,6 @@
props: {
event: {
type: Number as PropType<HandlerEnum>,
default: () => {},
},
disabled: {
type: Boolean,

View File

@ -25,7 +25,6 @@
props: {
event: {
type: Number as PropType<HandlerEnum>,
default: () => {},
},
disabled: {
type: Boolean,

View File

@ -35,7 +35,6 @@
},
event: {
type: Number as PropType<HandlerEnum>,
default: () => {},
},
def: {
type: String,

View File

@ -35,7 +35,7 @@
},
handler: {
type: Function as PropType<Fn>,
default: () => {},
default: () => ({}),
},
def: {
type: String,

View File

@ -3,6 +3,7 @@ import type { I18n, I18nOptions } from 'vue-i18n';
import { createI18n } from 'vue-i18n';
import { setLoadLocalePool } from './useLocale';
import { localeSetting } from '/@/settings/localeSetting';
import { useLocaleStoreWithOut } from '/@/store/modules/locale';
@ -16,6 +17,10 @@ async function createI18nOptions(): Promise<I18nOptions> {
const defaultLocal = await import(`./lang/${locale}.ts`);
const message = defaultLocal.default?.message ?? {};
setLoadLocalePool((loadLocalePool) => {
loadLocalePool.push(locale);
});
return {
legacy: false,
locale,

View File

@ -17,6 +17,10 @@ interface LangModule {
const loadLocalePool: LocaleType[] = [];
export function setLoadLocalePool(cb: (loadLocalePool: LocaleType[]) => void) {
cb(loadLocalePool);
}
function setI18nLanguage(locale: LocaleType) {
const localeStore = useLocaleStoreWithOut();

View File

@ -136,10 +136,10 @@ export const useMultipleTabStore = defineStore({
curTab.query = query || curTab.query;
curTab.fullPath = fullPath || curTab.fullPath;
this.tabList.splice(updateIndex, 1, curTab);
return;
} else {
// Add tab
this.tabList.push(route);
}
// Add tab
this.tabList.push(route);
this.updateCacheTab();
cacheTab && Persistent.setLocal(MULTIPLE_TABS_KEY, this.tabList);
},

View File

@ -43,7 +43,7 @@ export class AesEncryption {
}
export function encryptByBase64(cipherText: string) {
return Base64.parse(cipherText).toString(UTF8);
return UTF8.parse(cipherText).toString(Base64);
}
export function decodeByBase64(cipherText: string) {

View File

@ -17,10 +17,10 @@ export function getStorageShortName() {
export function getAppEnvConfig() {
const ENV_NAME = getConfigFileName(import.meta.env);
const ENV = ((import.meta.env.DEV
const ENV = (import.meta.env.DEV
? // Get the global configuration (the configuration will be extracted independently when packaging)
((import.meta.env as unknown) as GlobEnvConfig)
: window[ENV_NAME as any]) as unknown) as GlobEnvConfig;
(import.meta.env as unknown as GlobEnvConfig)
: window[ENV_NAME as any]) as unknown as GlobEnvConfig;
const {
VITE_GLOB_APP_TITLE,
@ -30,7 +30,7 @@ export function getAppEnvConfig() {
VITE_GLOB_UPLOAD_URL,
} = ENV;
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
warn(
`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`
);

View File

@ -155,7 +155,7 @@ export class VAxios {
// support form-data
supportFormData(config: AxiosRequestConfig) {
const headers = config.headers;
const headers = config.headers || this.options.headers;
const contentType = headers?.['Content-Type'] || headers?.['content-type'];
if (
@ -212,7 +212,7 @@ export class VAxios {
ret !== errorResult ? resolve(ret) : reject(new Error('request error!'));
return;
}
resolve((res as unknown) as Promise<T>);
resolve(res as unknown as Promise<T>);
})
.catch((e: Error) => {
if (requestCatchHook && isFunction(requestCatchHook)) {

View File

@ -35,7 +35,11 @@ const transform: AxiosTransform = {
*/
transformRequestHook: (res: AxiosResponse<Result>, options: RequestOptions) => {
const { t } = useI18n();
const { isTransformRequestResult } = options;
const { isTransformRequestResult, isReturnNativeResponse } = options;
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
if (isReturnNativeResponse) {
return res;
}
// 不进行任何处理,直接返回
// 用于页面代码可能需要直接获取codedatamessage这些信息时开启
if (!isTransformRequestResult) {
@ -192,6 +196,8 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
requestOptions: {
// 默认将prefix 添加到url
joinPrefix: true,
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformRequestResult: true,
// post请求的时候添加参数到url

View File

@ -7,6 +7,8 @@ export interface RequestOptions {
formatDate?: boolean;
// Whether to process the request result
isTransformRequestResult?: boolean;
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
isReturnNativeResponse?: boolean;
// Whether to join url
joinPrefix?: boolean;
// Interface address, use the default apiUrl if you leave it blank

View File

@ -1,8 +1,8 @@
<template>
<div class="lg:flex">
<Avatar :src="headerImg" :size="72" class="!mx-auto !block" />
<Avatar :src="userinfo.avatar || headerImg" :size="72" class="!mx-auto !block" />
<div class="md:ml-6 flex flex-col justify-center md:mt-0 mt-2">
<h1 class="md:text-lg text-md">早安, Vben, 开始您一天的工作吧</h1>
<h1 class="md:text-lg text-md">早安, {{ userinfo.realName }}, 开始您一天的工作吧</h1>
<span class="text-secondary"> 今日晴20 - 32 </span>
</div>
<div class="flex flex-1 justify-end md:mt-0 mt-4">
@ -23,15 +23,18 @@
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { computed, defineComponent } from 'vue';
import { Avatar } from 'ant-design-vue';
import { useUserStore } from '/@/store/modules/user';
import headerImg from '/@/assets/images/header.jpg';
export default defineComponent({
components: { Avatar },
setup() {
return { headerImg };
const userStore = useUserStore();
const userinfo = computed(() => userStore.getUserInfo);
return { userinfo, headerImg };
},
});
</script>

View File

@ -1,118 +0,0 @@
<template>
<PageWrapper title="表单增删示例">
<CollapseContainer title="表单增删">
<BasicForm @register="register" @submit="handleSubmit">
<template #add="{ field }">
<Button v-if="Number(field) === 0" @click="add">+</Button>
<Button v-if="field > 0" @click="del(field)">-</Button>
</template>
</BasicForm>
</CollapseContainer>
</PageWrapper>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { CollapseContainer } from '/@/components/Container/index';
import { Input } from 'ant-design-vue';
import { PageWrapper } from '/@/components/Page';
import { Button } from '/@/components/Button';
export default defineComponent({
components: { BasicForm, CollapseContainer, PageWrapper, [Input.name]: Input, Button },
setup() {
const [register, { appendSchemaByField, removeSchemaByFiled, validate }] = useForm({
schemas: [
{
field: 'field0a',
component: 'Input',
label: '字段0',
colProps: {
span: 8,
},
required: true,
},
{
field: 'field0b',
component: 'Input',
label: '字段0',
colProps: {
span: 8,
},
required: true,
},
{
field: '0',
component: 'Input',
label: ' ',
colProps: {
span: 8,
},
slot: 'add',
},
],
labelWidth: 100,
actionColOptions: { span: 24 },
});
async function handleSubmit() {
try {
const data = await validate();
console.log(data);
} catch (e) {
console.log(e);
}
}
const n = ref(1);
function add() {
appendSchemaByField(
{
field: `field${n.value}a`,
component: 'Input',
label: '字段' + n.value,
colProps: {
span: 8,
},
required: true,
},
''
);
appendSchemaByField(
{
field: `field${n.value}b`,
component: 'Input',
label: '字段' + n.value,
colProps: {
span: 8,
},
required: true,
},
''
);
appendSchemaByField(
{
field: `${n.value}`,
component: 'Input',
label: ' ',
colProps: {
span: 8,
},
slot: 'add',
},
''
);
n.value++;
}
function del(field) {
removeSchemaByFiled([`field${field}a`, `field${field}b`, `${field}`]);
n.value--;
}
return { register, handleSubmit, add, del };
},
});
</script>

View File

@ -4,7 +4,7 @@
<div class="flex justify-between items-center">
<span class="flex-1">
<a :href="GITHUB_URL" target="_blank">{{ name }}</a>
是一个基于Vue3.01Vite Ant-Design-Vue TypeScript
是一个基于Vue3.0Vite Ant-Design-Vue TypeScript
的后台解决方案目标是为中大型项目开发,提供现成的开箱解决方案及丰富的示例,原则上不会限制任何代码用于商用
</span>
</div>

View File

@ -5,7 +5,23 @@
>
<div
:class="`${prefixCls}__unlock`"
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"
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
"
@click="handleShowForm(false)"
v-show="showDate"
>
@ -28,9 +44,9 @@
<div :class="`${prefixCls}-entry`" v-show="!showDate">
<div :class="`${prefixCls}-entry-content`">
<div :class="`${prefixCls}-entry__header enter-x`">
<img :src="headerImg" :class="`${prefixCls}-entry__header-img`" />
<img :src="userinfo.avatar || headerImg" :class="`${prefixCls}-entry__header-img`" />
<p :class="`${prefixCls}-entry__header-name`">
{{ realName }}
{{ userinfo.realName }}
</p>
</div>
<InputPassword
@ -108,9 +124,8 @@
const { t } = useI18n();
const realName = computed(() => {
const { realName } = userStore.getUserInfo || {};
return realName;
const userinfo = computed(() => {
return userStore.getUserInfo || {};
});
/**
@ -141,7 +156,7 @@
return {
goLogin,
realName,
userinfo,
unLock,
errMsg,
loading,

View File

@ -1,6 +1,13 @@
<template>
<LoginFormTitle v-show="getShow" class="enter-x" />
<Form class="p-4 enter-x" :model="formData" :rules="getFormRules" ref="formRef" v-show="getShow">
<Form
class="p-4 enter-x"
:model="formData"
:rules="getFormRules"
ref="formRef"
v-show="getShow"
@keypress.enter="handleLogin"
>
<FormItem name="account" class="enter-x">
<Input size="large" v-model:value="formData.account" :placeholder="t('sys.login.userName')" />
</FormItem>
@ -88,7 +95,7 @@
import { useUserStore } from '/@/store/modules/user';
import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from './useLogin';
import { useDesign } from '/@/hooks/web/useDesign';
import { onKeyStroke } from '@vueuse/core';
//import { onKeyStroke } from '@vueuse/core';
export default defineComponent({
name: 'LoginForm',
@ -129,7 +136,7 @@
const { validForm } = useFormValid(formRef);
onKeyStroke('Enter', handleLogin);
//onKeyStroke('Enter', handleLogin);
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN);

View File

@ -33,6 +33,7 @@ export interface UserInfo {
userId: string | number;
username: string;
realName: string;
avatar: string;
desc?: string;
}

View File

@ -69,7 +69,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
},
// Turning off brotliSize display can slightly reduce packaging time
brotliSize: false,
chunkSizeWarningLimit: 1500,
chunkSizeWarningLimit: 2000,
},
define: {
// setting vue-i18-next

2755
yarn.lock

File diff suppressed because it is too large Load Diff