Browse Source

Merge remote-tracking branch 'origin/main' into main_fehu

pull/1934/head
yfh01 4 years ago
parent
commit
e980f8ab6d
  1. 1
      .gitignore
  2. 2
      README.md
  3. 5543
      pnpm-lock.yaml
  4. 3
      src/components/ContextMenu/src/ContextMenu.vue
  5. 1
      src/components/ContextMenu/src/typing.ts
  6. 21
      src/components/Form/src/hooks/useFormEvents.ts
  7. 2
      src/components/Table/src/hooks/useColumns.ts
  8. 18
      src/components/Table/src/types/pagination.ts
  9. 6
      src/components/Tree/src/Tree.vue
  10. 6
      src/components/Tree/src/tree.ts
  11. 20
      src/components/Tree/src/useTree.ts
  12. 1
      src/locales/lang/en/routes/demo.ts
  13. 1
      src/locales/lang/zh-CN/routes/demo.ts
  14. 9
      src/main.ts
  15. 4
      src/router/index.ts
  16. 1
      src/router/routes/index.ts
  17. 4
      src/router/routes/modules/about.ts
  18. 8
      src/router/routes/modules/demo/comp.ts
  19. 2
      src/utils/http/axios/Axios.ts
  20. 2
      src/utils/lib/echarts.ts
  21. 136
      src/views/demo/form/TabsForm.vue
  22. 2
      src/views/demo/page/account/center/data.tsx
  23. 8
      src/views/demo/tree/ActionTree.vue
  24. 2
      src/views/sys/login/LoginForm.vue

1
.gitignore

@ -27,3 +27,4 @@ pnpm-debug.log*
*.njsproj
*.sln
*.sw?
/package-lock.json

2
README.md

@ -21,7 +21,7 @@ Vue Vben Admin is a free and open source middle and back-end template. Using the
- **Authority** Built-in complete dynamic routing permission generation scheme.
- **Component** Multiple commonly used components are encapsulated twice
## 预览
## Preview
- [vue-vben-admin](https://vvbin.cn/next/) - Full version Chinese site
- [vue-vben-admin-gh-pages](https://anncwb.github.io/vue-vben-admin/) - Full version of the github site

5543
pnpm-lock.yaml

File diff suppressed because it is too large

3
src/components/ContextMenu/src/ContextMenu.vue

@ -89,7 +89,8 @@
}
function renderMenuItem(items: ContextMenuItem[]) {
return items.map((item) => {
const visibleItems = items.filter((item) => !item.hidden);
return visibleItems.map((item) => {
const { disabled, label, children, divider = false } = item;
const contentProps = {

1
src/components/ContextMenu/src/typing.ts

@ -6,6 +6,7 @@ export interface Axis {
export interface ContextMenuItem {
label: string;
icon?: string;
hidden?: boolean;
disabled?: boolean;
handler?: Fn;
divider?: boolean;

21
src/components/Form/src/hooks/useFormEvents.ts

@ -2,7 +2,7 @@ import type { ComputedRef, Ref } from 'vue';
import type { FormProps, FormSchema, FormActionType } from '../types/form';
import type { NamePath } from 'ant-design-vue/lib/form/interface';
import { unref, toRaw, nextTick } from 'vue';
import { isArray, isFunction, isNullOrUnDef, isObject, isString } from '/@/utils/is';
import { isArray, isFunction, isObject, isString, isDef, isNullOrUnDef } from '/@/utils/is';
import { deepMerge } from '/@/utils';
import { dateItemType, handleInputNumberValue, defaultValueComponents } from '../helper';
import { dateUtil } from '/@/utils/dateUtil';
@ -55,6 +55,10 @@ export function useFormEvents({
.map((item) => item.field)
.filter(Boolean);
// key 支持 a.b.c 的嵌套写法
const delimiter = '.';
const nestKeyArray = fields.filter((item) => item.indexOf(delimiter) >= 0);
const validKeys: string[] = [];
Object.keys(values).forEach((key) => {
const schema = unref(getSchema).find((item) => item.field === key);
@ -85,6 +89,21 @@ export function useFormEvents({
formModel[key] = value;
}
validKeys.push(key);
} else {
nestKeyArray.forEach((nestKey: string) => {
try {
const value = eval('values' + delimiter + nestKey);
if (isDef(value)) {
formModel[nestKey] = value;
validKeys.push(nestKey);
}
} catch (e) {
// key not exist
if (isDef(defaultValueRef.value[nestKey])) {
formModel[nestKey] = defaultValueRef.value[nestKey];
}
}
});
}
});
validateFields(validKeys).catch((_) => {});

2
src/components/Table/src/hooks/useColumns.ts

@ -298,7 +298,7 @@ export function formatCell(text: string, format: CellFormat, record: Recordable,
try {
// date type
const DATE_FORMAT_PREFIX = 'date|';
if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX)) {
if (isString(format) && format.startsWith(DATE_FORMAT_PREFIX) && text) {
const dateFormat = format.replace(DATE_FORMAT_PREFIX, '');
if (!dateFormat) {

18
src/components/Table/src/types/pagination.ts

@ -7,9 +7,18 @@ interface PaginationRenderProps {
originalElement: any;
}
type PaginationPositon =
| 'topLeft'
| 'topCenter'
| 'topRight'
| 'bottomLeft'
| 'bottomCenter'
| 'bottomRight';
export declare class PaginationConfig extends Pagination {
position?: 'top' | 'bottom' | 'both';
position?: PaginationPositon[];
}
export interface PaginationProps {
/**
* total number of data items
@ -96,4 +105,11 @@ export interface PaginationProps {
* @type Function
*/
itemRender?: (props: PaginationRenderProps) => VNodeChild | JSX.Element;
/**
* specify the position of Pagination
* @default ['bottomRight']
* @type string[]
*/
position?: PaginationPositon[];
}

6
src/components/Tree/src/Tree.vue

@ -71,7 +71,7 @@
selectedKeys: state.selectedKeys,
checkedKeys: state.checkedKeys,
checkStrictly: state.checkStrictly,
filedNames: unref(getFieldNames),
fieldNames: unref(getFieldNames),
'onUpdate:expandedKeys': (v: KeyType[]) => {
state.expandedKeys = v;
emit('update:expandedKeys', v);
@ -119,6 +119,7 @@
getAllKeys,
getChildrenKeys,
getEnabledKeys,
getSelectedNode,
} = useTree(treeDataRef, getFieldNames);
function getIcon(params: Recordable, icon?: string) {
@ -145,6 +146,7 @@
contextMenuOptions.items = menuList;
}
if (!contextMenuOptions.items?.length) return;
contextMenuOptions.items = contextMenuOptions.items.filter((item) => !item.hidden);
createContextMenu(contextMenuOptions);
}
@ -293,6 +295,7 @@
() => {
state.checkedKeys = toRaw(props.value || []);
},
{ immediate: true },
);
watch(
@ -319,6 +322,7 @@
insertNodesByKey,
deleteNodeByKey,
updateNodeByKey,
getSelectedNode,
checkAll,
expandAll,
filterByLevel: (level: number) => {

6
src/components/Tree/src/tree.ts

@ -141,6 +141,7 @@ export type TreeProps = ExtractPropTypes<typeof treeProps>;
export interface ContextMenuItem {
label: string;
icon?: string;
hidden?: boolean;
disabled?: boolean;
handler?: Fn;
divider?: boolean;
@ -185,4 +186,9 @@ export interface TreeActionType {
updateNodeByKey: (key: string, node: Omit<TreeDataItem, 'key'>) => void;
setSearchValue: (value: string) => void;
getSearchValue: () => string;
getSelectedNode: (
key: KeyType,
treeList?: TreeItem[],
selectNode?: TreeItem | null,
) => TreeItem | null;
}

20
src/components/Tree/src/useTree.ts

@ -1,4 +1,4 @@
import type { InsertNodeParams, KeyType, FieldNames } from './tree';
import type { InsertNodeParams, KeyType, FieldNames, TreeItem } from './tree';
import type { Ref, ComputedRef } from 'vue';
import type { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
@ -176,6 +176,23 @@ export function useTree(treeDataRef: Ref<TreeDataItem[]>, getFieldNames: Compute
}
}
}
// Get selected node
function getSelectedNode(key: KeyType, list?: TreeItem[], selectedNode?: TreeItem | null) {
if (!key && key !== 0) return null;
const treeData = list || unref(treeDataRef);
treeData.forEach((item) => {
if (selectedNode?.key || selectedNode?.key === 0) return selectedNode;
if (item.key === key) {
selectedNode = item;
return;
}
if (item.children && item.children.length) {
selectedNode = getSelectedNode(key, item.children, selectedNode);
}
});
return selectedNode || null;
}
return {
deleteNodeByKey,
insertNodeByKey,
@ -185,5 +202,6 @@ export function useTree(treeDataRef: Ref<TreeDataItem[]>, getFieldNames: Compute
getAllKeys,
getChildrenKeys,
getEnabledKeys,
getSelectedNode,
};
}

1
src/locales/lang/en/routes/demo.ts

@ -111,6 +111,7 @@ export default {
dynamicForm: 'Dynamic',
customerForm: 'Custom',
appendForm: 'Append',
tabsForm: 'TabsForm',
},
iframe: {
frame: 'External',

1
src/locales/lang/zh-CN/routes/demo.ts

@ -107,6 +107,7 @@ export default {
dynamicForm: '动态表单',
customerForm: '自定义组件',
appendForm: '表单增删示例',
tabsForm: '标签页+多级field',
},
iframe: {
frame: '外部页面',

9
src/main.ts

@ -19,28 +19,37 @@ async function bootstrap() {
const app = createApp(App);
// Configure store
// 配置 store
setupStore(app);
// Initialize internal system configuration
// 初始化内部系统配置
initAppConfigStore();
// Register global components
// 注册全局组件
registerGlobComp(app);
// Multilingual configuration
// 多语言配置
// Asynchronous case: language files may be obtained from the server side
// 异步案例:语言文件可能从服务器端获取
await setupI18n(app);
// Configure routing
// 配置路由
setupRouter(app);
// router-guard
// 路由守卫
setupRouterGuard(router);
// Register global directive
// 注册全局指令
setupGlobDirectives(app);
// Configure global error handling
// 配置全局错误处理
setupErrorHandle(app);
// https://next.router.vuejs.org/api/#isready

4
src/router/index.ts

@ -14,9 +14,13 @@ const getRouteNames = (array: any[]) =>
getRouteNames(basicRoutes);
// app router
// 创建一个可以被 Vue 应用程序使用的路由实例
export const router = createRouter({
// 创建一个 hash 历史记录。
history: createWebHashHistory(import.meta.env.VITE_PUBLIC_PATH),
// 应该添加到路由的初始路由列表。
routes: basicRoutes as unknown as RouteRecordRaw[],
// 是否应该禁止尾部斜杠。默认为假
strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }),
});

1
src/router/routes/index.ts

@ -37,6 +37,7 @@ export const LoginRoute: AppRouteRecordRaw = {
};
// Basic routing without permission
// 未经许可的基本路由
export const basicRoutes = [
LoginRoute,
RootRoute,

4
src/router/routes/modules/about.ts

@ -3,7 +3,7 @@ import type { AppRouteModule } from '/@/router/types';
import { LAYOUT } from '/@/router/constant';
import { t } from '/@/hooks/web/useI18n';
const dashboard: AppRouteModule = {
const about: AppRouteModule = {
path: '/about',
name: 'About',
component: LAYOUT,
@ -28,4 +28,4 @@ const dashboard: AppRouteModule = {
],
};
export default dashboard;
export default about;

8
src/router/routes/modules/demo/comp.ts

@ -98,6 +98,14 @@ const comp: AppRouteModule = {
title: t('routes.demo.form.appendForm'),
},
},
{
path: 'tabsForm',
name: 'tabsFormDemo',
component: () => import('/@/views/demo/form/TabsForm.vue'),
meta: {
title: t('routes.demo.form.tabsForm'),
},
},
],
},
{

2
src/utils/http/axios/Axios.ts

@ -113,7 +113,7 @@ export class VAxios {
isFunction(responseInterceptorsCatch) &&
this.axiosInstance.interceptors.response.use(undefined, (error) => {
// @ts-ignore
responseInterceptorsCatch(this.axiosInstance, error);
return responseInterceptorsCatch(this.axiosInstance, error);
});
}

2
src/utils/lib/echarts.ts

@ -7,6 +7,7 @@ import {
MapChart,
PictorialBarChart,
RadarChart,
ScatterChart,
} from 'echarts/charts';
import {
@ -50,6 +51,7 @@ echarts.use([
TimelineComponent,
CalendarComponent,
GraphicComponent,
ScatterChart,
]);
export default echarts;

136
src/views/demo/form/TabsForm.vue

@ -0,0 +1,136 @@
<template>
<PageWrapper title="标签页+多级field表单" v-loading="loading">
<div class="mb-4">
<a-button @click="handleReset" class="mr-2"> 重置表单 </a-button>
<a-button @click="handleSetValues" class="mr-2"> 设置默认值 </a-button>
<a-button @click="handleSubmit" class="mr-2" type="primary"> 提交表单 </a-button>
</div>
<CollapseContainer title="标签页+多级field表单">
<Tabs v-model:activeKey="activeKey">
<TabPane
v-for="item in tabsFormSchema"
:key="item.key"
v-bind="omit(item, ['Form', 'key'])"
>
<BasicForm @register="item.Form[0]" />
</TabPane>
</Tabs>
</CollapseContainer>
</PageWrapper>
</template>
<script lang="ts">
import { ref, defineComponent } from 'vue';
import { Tabs } from 'ant-design-vue';
import { PageWrapper } from '/@/components/Page';
import { CollapseContainer } from '/@/components/Container';
import { useMessage } from '/@/hooks/web/useMessage';
import { omit } from 'lodash-es';
import { deepMerge } from '/@/utils';
import { BasicForm, FormSchema, useForm, FormProps, UseFormReturnType } from '/@/components/Form';
export default defineComponent({
name: 'TabsFormDemo',
components: { Tabs, TabPane: Tabs.TabPane, PageWrapper, CollapseContainer, BasicForm },
setup() {
type TabsFormType = {
key: string;
tab: string;
forceRender?: boolean;
Form: UseFormReturnType;
};
const { createMessage } = useMessage();
const activeKey = ref('tabs2');
const loading = ref(false);
const tabsFormSchema: TabsFormType[] = [];
// 公共属性
const baseFormConfig: Partial<FormProps> = {
showActionButtonGroup: false,
labelWidth: 100,
};
// 为每个字段模拟默认值, { tabs1: { field1: '', field2: '' }, tabs2: { field1: '' }, ... }
const mockDefaultValue: Recordable = {};
// 模拟5个标签页
for (let i = 1; i <= 5; ++i) {
const tabsKey = `tabs${i}`;
// 每个标签页8个字段
const schemas: FormSchema[] = [];
const row: Recordable = {};
for (let j = 1; j <= 8; ++j) {
schemas.push({
field: `${tabsKey}.field${j}`,
label: `${tabsKey}-field${j}`,
component: 'Input',
colProps: { span: 24 },
});
row[`field${j}`] = `field: ${tabsKey}.field${j}, default value`;
}
mockDefaultValue[tabsKey] = row;
tabsFormSchema.push({
key: tabsKey,
tab: tabsKey,
forceRender: true,
Form: useForm(Object.assign({ schemas }, baseFormConfig) as FormProps),
});
}
async function handleReset() {
for (const item of tabsFormSchema) {
const { resetFields } = item.Form[1];
await resetFields();
}
}
async function handleSubmit() {
let lastKey = '';
loading.value = true;
try {
const values: Recordable = {};
for (const item of tabsFormSchema) {
lastKey = item.key;
const { validate, getFieldsValue } = item.Form[1];
await validate();
// 表单已支持多级key
deepMerge(values, getFieldsValue());
}
console.log('submit values: ', values);
createMessage.success('提交成功!请打开控制台查看');
} catch (e) {
// 验证失败或出错,切换到对应标签页
activeKey.value = lastKey;
console.log(e);
} finally {
loading.value = false;
}
}
async function handleSetValues() {
console.log('默认值为: ', mockDefaultValue);
for (const item of tabsFormSchema) {
const { setFieldsValue } = item.Form[1];
await setFieldsValue(mockDefaultValue);
}
}
return {
omit,
loading,
activeKey,
tabsFormSchema,
handleReset,
handleSubmit,
handleSetValues,
};
},
});
</script>
<style scoped></style>

2
src/views/demo/page/account/center/data.tsx

@ -19,7 +19,7 @@ export const tags: string[] = [
'前端开发',
'vue3',
];
<span class="iconify" data-icon="jam:codepen-circle" data-inline="false"></span>;
export const teams: ListItem[] = [
{
icon: 'ri:alipay-fill',

8
src/views/demo/tree/ActionTree.vue

@ -13,6 +13,7 @@
<a-button @click="handleGetCheckData" class="mr-2"> 获取勾选数据 </a-button>
<a-button @click="handleSetSelectData" class="mr-2"> 设置选中数据 </a-button>
<a-button @click="handleGetSelectData" class="mr-2"> 获取选中数据 </a-button>
<a-button @click="handleGetSelectNode" class="mr-2"> 获取选中节点 </a-button>
<a-button @click="handleSetExpandData" class="mr-2"> 设置展开数据 </a-button>
<a-button @click="handleGetExpandData" class="mr-2"> 获取展开数据 </a-button>
@ -69,6 +70,12 @@
createMessage.success(JSON.stringify(keys));
}
function handleGetSelectNode() {
const keys = getTree().getSelectedKeys();
const node = getTree().getSelectedNode(keys[0]);
createMessage.success(node !== null ? JSON.stringify(node) : null);
}
function handleSetExpandData() {
getTree().setExpandedKeys(['0-0']);
}
@ -120,6 +127,7 @@
handleGetSelectData,
handleSetExpandData,
handleGetExpandData,
handleGetSelectNode,
appendNodeByKey,
deleteNodeByKey,
updateNodeByKey,

2
src/views/sys/login/LoginForm.vue

@ -63,7 +63,7 @@
{{ t('sys.login.qrSignInFormTitle') }}
</Button>
</ACol>
<ACol :md="7" :xs="24">
<ACol :md="6" :xs="24">
<Button block @click="setLoginState(LoginStateEnum.REGISTER)">
{{ t('sys.login.registerButton') }}
</Button>

Loading…
Cancel
Save