mirror of
https://github.com/vbenjs/vben-admin-thin-next.git
synced 2025-02-02 18:08:40 +08:00
chore: merge branch 'main' of https://github.com/anncwb/vue-vben-admin into main
This commit is contained in:
commit
14da175611
@ -5,6 +5,8 @@
|
||||
- 新增图形编辑器示例
|
||||
- 新增代码编辑器(包含 Json 编辑器)
|
||||
- 新增 `JsonPreview`Json 数据查看组件
|
||||
- 表格的数据列(column)和操作列(actionColumn)的字段可以根据权限和业务来控制是否显示
|
||||
- 新增权限控制表格示例(AuthColumn.vue)
|
||||
|
||||
### ⚡ Performance Improvements
|
||||
|
||||
|
@ -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({
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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[];
|
||||
}
|
||||
|
@ -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: [
|
||||
|
74
package.json
74
package.json
@ -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",
|
||||
|
@ -38,6 +38,8 @@ export interface GetUserInfoByUserIdModel {
|
||||
username: string;
|
||||
// 真实名字
|
||||
realName: string;
|
||||
// 头像
|
||||
avatar: string;
|
||||
// 介绍
|
||||
desc?: string;
|
||||
}
|
||||
|
@ -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 }) {
|
||||
|
@ -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);
|
||||
|
@ -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(() => {
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -55,7 +55,7 @@
|
||||
// api params
|
||||
params: {
|
||||
type: Object as PropType<Recordable>,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
// support xxx.xxx.xx
|
||||
resultField: propTypes.string.def(''),
|
||||
|
@ -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(
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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'),
|
||||
// 禁用表单
|
||||
|
@ -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
|
||||
|
@ -108,4 +108,5 @@ export type ComponentType =
|
||||
| 'StrengthMeter'
|
||||
| 'Upload'
|
||||
| 'IconPicker'
|
||||
| 'Render';
|
||||
| 'Render'
|
||||
| 'Slider';
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
props: {
|
||||
item: {
|
||||
type: Object as PropType<Menu>,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
dot: propTypes.bool,
|
||||
collapseParent: propTypes.bool,
|
||||
|
@ -64,7 +64,7 @@
|
||||
props: {
|
||||
item: {
|
||||
type: Object as PropType<Menu>,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
parent: propTypes.bool,
|
||||
collapsedShowTitle: propTypes.bool,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
props: {
|
||||
column: {
|
||||
type: Object as PropType<BasicColumn>,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
|
@ -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(() => {
|
||||
|
@ -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 || {};
|
||||
|
@ -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 };
|
||||
},
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 };
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
},
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -23,7 +23,6 @@
|
||||
props: {
|
||||
event: {
|
||||
type: Number as PropType<HandlerEnum>,
|
||||
default: () => {},
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
|
@ -25,7 +25,6 @@
|
||||
props: {
|
||||
event: {
|
||||
type: Number as PropType<HandlerEnum>,
|
||||
default: () => {},
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
|
@ -25,7 +25,6 @@
|
||||
props: {
|
||||
event: {
|
||||
type: Number as PropType<HandlerEnum>,
|
||||
default: () => {},
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
|
@ -35,7 +35,6 @@
|
||||
},
|
||||
event: {
|
||||
type: Number as PropType<HandlerEnum>,
|
||||
default: () => {},
|
||||
},
|
||||
def: {
|
||||
type: String,
|
||||
|
@ -35,7 +35,7 @@
|
||||
},
|
||||
handler: {
|
||||
type: Function as PropType<Fn>,
|
||||
default: () => {},
|
||||
default: () => ({}),
|
||||
},
|
||||
def: {
|
||||
type: String,
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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) {
|
||||
|
@ -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.`
|
||||
);
|
||||
|
@ -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)) {
|
||||
|
@ -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;
|
||||
}
|
||||
// 不进行任何处理,直接返回
|
||||
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
|
||||
if (!isTransformRequestResult) {
|
||||
@ -192,6 +196,8 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
|
||||
requestOptions: {
|
||||
// 默认将prefix 添加到url
|
||||
joinPrefix: true,
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
isReturnNativeResponse: false,
|
||||
// 需要对返回数据进行处理
|
||||
isTransformRequestResult: true,
|
||||
// post请求的时候添加参数到url
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -4,7 +4,7 @@
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="flex-1">
|
||||
<a :href="GITHUB_URL" target="_blank">{{ name }}</a>
|
||||
是一个基于Vue3.01、Vite、 Ant-Design-Vue 、TypeScript
|
||||
是一个基于Vue3.0、Vite、 Ant-Design-Vue 、TypeScript
|
||||
的后台解决方案,目标是为中大型项目开发,提供现成的开箱解决方案及丰富的示例,原则上不会限制任何代码用于商用。
|
||||
</span>
|
||||
</div>
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -33,6 +33,7 @@ export interface UserInfo {
|
||||
userId: string | number;
|
||||
username: string;
|
||||
realName: string;
|
||||
avatar: string;
|
||||
desc?: string;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user