From 00d947e2f81105194b0622d33768f999e37ad28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=87=AF=E9=BE=99?= <502431556@qq.com> Date: Tue, 26 Apr 2022 15:03:48 +0800 Subject: [PATCH] feat: add useCrudSchemas hook --- mock/dict/index.ts | 63 +++++ src/api/common/index.ts | 13 ++ .../src/ContentDetailWrap.vue | 2 +- src/hooks/web/useCrudSchemas.ts | 217 ++++++++++++++++++ src/permission.ts | 13 ++ src/store/modules/dict.ts | 38 +++ src/views/Example/Dialog/ExampleDialog.vue | 112 +++++++-- .../Example/Dialog/components/Detail.vue | 37 +-- src/views/Example/Dialog/components/Write.vue | 103 +-------- src/views/Example/Page/ExamplePage.vue | 30 +-- types/componentType/form.d.ts | 2 + 11 files changed, 468 insertions(+), 162 deletions(-) create mode 100644 mock/dict/index.ts create mode 100644 src/api/common/index.ts create mode 100644 src/hooks/web/useCrudSchemas.ts create mode 100644 src/store/modules/dict.ts diff --git a/mock/dict/index.ts b/mock/dict/index.ts new file mode 100644 index 0000000..b27f3b3 --- /dev/null +++ b/mock/dict/index.ts @@ -0,0 +1,63 @@ +import { config } from '@/config/axios/config' +import { MockMethod } from 'vite-plugin-mock' + +const { result_code } = config + +const timeout = 1000 + +const dictObj: Recordable = { + importance: [ + { + value: 0, + label: 'tableDemo.commonly' + }, + { + value: 1, + label: 'tableDemo.good' + }, + { + value: 2, + label: 'tableDemo.important' + } + ] +} + +export default [ + // 字典接口 + { + url: '/dict/list', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: dictObj + } + } + }, + // 获取某个字典 + { + url: '/dict/one', + method: 'get', + timeout, + response: () => { + return { + code: result_code, + data: [ + { + label: 'test1', + value: 0 + }, + { + label: 'test2', + value: 1 + }, + { + label: 'test3', + value: 2 + } + ] + } + } + } +] as MockMethod[] diff --git a/src/api/common/index.ts b/src/api/common/index.ts new file mode 100644 index 0000000..44c66ba --- /dev/null +++ b/src/api/common/index.ts @@ -0,0 +1,13 @@ +import { useAxios } from '@/hooks/web/useAxios' + +const request = useAxios() + +// 获取所有字典 +export const getDictApi = () => { + return request.get({ url: '/dict/list' }) +} + +// 模拟获取某个字典 +export const getDictOneApi = () => { + return request.get({ url: '/dict/one' }) +} diff --git a/src/components/ContentDetailWrap/src/ContentDetailWrap.vue b/src/components/ContentDetailWrap/src/ContentDetailWrap.vue index d0b639e..22aeb83 100644 --- a/src/components/ContentDetailWrap/src/ContentDetailWrap.vue +++ b/src/components/ContentDetailWrap/src/ContentDetailWrap.vue @@ -29,7 +29,7 @@ onMounted(() => {
diff --git a/src/hooks/web/useCrudSchemas.ts b/src/hooks/web/useCrudSchemas.ts new file mode 100644 index 0000000..e7f8a21 --- /dev/null +++ b/src/hooks/web/useCrudSchemas.ts @@ -0,0 +1,217 @@ +import { reactive } from 'vue' +import { eachTree, treeMap, filter } from '@/utils/tree' +import { findIndex } from '@/utils' +import { useDictStoreWithOut } from '@/store/modules/dict' +import { useI18n } from '@/hooks/web/useI18n' +import type { AxiosPromise } from 'axios' + +export type CrudSchema = Omit & { + search?: CrudSearchParams + table?: CrudTableParams + form?: CrudFormParams + detail?: CrudDescriptionsParams + children?: CrudSchema[] +} + +type CrudSearchParams = { + // 是否显示在查询项 + show?: boolean + // 字典名称,会去取全局的字典 + dictName?: string + // 接口路径 + dictUrl?: string +} & Omit + +type CrudTableParams = { + // 是否显示表头 + show?: boolean +} & Omit + +type CrudFormParams = { + // 是否显示表单项 + show?: boolean +} & Omit + +type CrudDescriptionsParams = { + // 是否显示表单项 + show?: boolean +} & Omit + +const dictStore = useDictStoreWithOut() + +const { t } = useI18n() + +interface AllSchemas { + searchSchema: FormSchema[] + tableColumns: TableColumn[] + formSchema: FormSchema[] + detailSchema: DescriptionsSchema[] +} + +// 过滤所有结构 +export const useCrudSchemas = ( + crudSchema: CrudSchema[] +): { + allSchemas: AllSchemas +} => { + // 所有结构数据 + const allSchemas = reactive({ + searchSchema: [], + tableColumns: [], + formSchema: [], + detailSchema: [] + }) + + const searchSchema = filterSearchSchema(crudSchema, allSchemas) + allSchemas.searchSchema = searchSchema || [] + + const tableColumns = filterTableSchema(crudSchema) + allSchemas.tableColumns = tableColumns || [] + + const formSchema = filterFormSchema(crudSchema) + allSchemas.formSchema = formSchema + + const detailSchema = filterDescriptionsSchema(crudSchema) + allSchemas.detailSchema = detailSchema + + return { + allSchemas + } +} + +// 过滤 Search 结构 +const filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => { + const searchSchema: FormSchema[] = [] + + // 获取字典列表队列 + const searchRequestTask: Array<() => Promise> = [] + + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.search?.show) { + const searchSchemaItem = { + // 默认为 input + component: schemaItem.search.component || 'Input', + componentProps: {}, + ...schemaItem.search, + field: schemaItem.field, + label: schemaItem.label + } + + if (searchSchemaItem.dictName) { + // 如果有 dictName 则证明是从字典中获取数据 + const dictArr = dictStore.getDictObj[searchSchemaItem.dictName] + searchSchemaItem.componentProps!.options = filterOptions(dictArr) + } else if (searchSchemaItem.api) { + searchRequestTask.push(async () => { + const res = await (searchSchemaItem.api as () => AxiosPromise)() + if (res) { + const index = findIndex(allSchemas.searchSchema, (v: FormSchema) => { + return v.field === searchSchemaItem.field + }) + if (index !== -1) { + allSchemas.searchSchema[index]!.componentProps!.options = filterOptions( + res.data, + searchSchemaItem.componentProps.optionsAlias?.labelField + ) + } + } + }) + } + + // 删除不必要的字段 + delete searchSchemaItem.show + delete searchSchemaItem.dictName + + searchSchema.push(searchSchemaItem) + } + }) + + for (const task of searchRequestTask) { + task() + } + + return searchSchema +} + +// 过滤 table 结构 +const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => { + const tableColumns = treeMap(crudSchema, { + conversion: (schema: CrudSchema) => { + if (schema?.table?.show !== false) { + return { + ...schema.table, + ...schema + } + } + } + }) + + // 第一次过滤会有 undefined 所以需要二次过滤 + return filter(tableColumns as TableColumn[], (data) => { + if (data.children === void 0) { + delete data.children + } + return !!data.field + }) +} + +// 过滤 form 结构 +const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => { + const formSchema: FormSchema[] = [] + + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.form?.show !== false) { + const formSchemaItem = { + // 默认为 input + component: (schemaItem.form && schemaItem.form.component) || 'Input', + ...schemaItem.form, + field: schemaItem.field, + label: schemaItem.label + } + + // 删除不必要的字段 + delete formSchemaItem.show + + formSchema.push(formSchemaItem) + } + }) + + return formSchema +} + +// 过滤 descriptions 结构 +const filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[] => { + const descriptionsSchema: FormSchema[] = [] + + eachTree(crudSchema, (schemaItem: CrudSchema) => { + // 判断是否显示 + if (schemaItem?.detail?.show !== false) { + const descriptionsSchemaItem = { + ...schemaItem.detail, + field: schemaItem.field, + label: schemaItem.label + } + + // 删除不必要的字段 + delete descriptionsSchemaItem.show + + descriptionsSchema.push(descriptionsSchemaItem) + } + }) + + return descriptionsSchema +} + +// 给options添加国际化 +const filterOptions = (options: Recordable, labelField?: string) => { + return options.map((v: Recordable) => { + if (labelField) { + v['labelField'] = t(v.labelField) + } else { + v['label'] = t(v.label) + } + return v + }) +} diff --git a/src/permission.ts b/src/permission.ts index d688813..e7e7ecf 100644 --- a/src/permission.ts +++ b/src/permission.ts @@ -5,12 +5,16 @@ import type { RouteRecordRaw } from 'vue-router' import { useTitle } from '@/hooks/web/useTitle' import { useNProgress } from '@/hooks/web/useNProgress' import { usePermissionStoreWithOut } from '@/store/modules/permission' +import { useDictStoreWithOut } from '@/store/modules/dict' import { usePageLoading } from '@/hooks/web/usePageLoading' +import { getDictApi } from '@/api/common' const permissionStore = usePermissionStoreWithOut() const appStore = useAppStoreWithOut() +const dictStore = useDictStoreWithOut() + const { wsCache } = useCache() const { start, done } = useNProgress() @@ -31,6 +35,15 @@ router.beforeEach(async (to, from, next) => { return } + if (!dictStore.getIsSetDict) { + // 获取所有字典 + const res = await getDictApi() + if (res) { + dictStore.setDictObj(res.data) + dictStore.setIsSetDict(true) + } + } + // 开发者可根据实际情况进行修改 const roleRouters = wsCache.get('roleRouters') || [] const userInfo = wsCache.get(appStore.getUserInfo) diff --git a/src/store/modules/dict.ts b/src/store/modules/dict.ts new file mode 100644 index 0000000..446b925 --- /dev/null +++ b/src/store/modules/dict.ts @@ -0,0 +1,38 @@ +import { defineStore } from 'pinia' +import { store } from '../index' + +export interface DictState { + isSetDict: boolean + dictObj: Recordable +} + +export const useDictStore = defineStore({ + id: 'dict', + state: (): DictState => ({ + isSetDict: false, + dictObj: {} + }), + persist: { + enabled: true + }, + getters: { + getDictObj(): Recordable { + return this.dictObj + }, + getIsSetDict(): boolean { + return this.isSetDict + } + }, + actions: { + setDictObj(dictObj: Recordable) { + this.dictObj = dictObj + }, + setIsSetDict(isSetDict: boolean) { + this.isSetDict = isSetDict + } + } +}) + +export const useDictStoreWithOut = () => { + return useDictStore(store) +} diff --git a/src/views/Example/Dialog/ExampleDialog.vue b/src/views/Example/Dialog/ExampleDialog.vue index ab084ed..1e5a28f 100644 --- a/src/views/Example/Dialog/ExampleDialog.vue +++ b/src/views/Example/Dialog/ExampleDialog.vue @@ -8,9 +8,10 @@ import { Table } from '@/components/Table' import { getTableListApi, saveTableApi, delTableListApi } from '@/api/table' import { useTable } from '@/hooks/web/useTable' import { TableData } from '@/api/table/types' -import { h, reactive, ref, unref } from 'vue' +import { h, ref, unref, reactive } from 'vue' import Write from './components/Write.vue' import Detail from './components/Detail.vue' +import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas' const { register, tableObject, methods } = useTable< { @@ -33,24 +34,32 @@ getList() const { t } = useI18n() -const searchData: FormSchema[] = [ - { - label: t('exampleDemo.title'), - value: '', - component: 'Input', - field: 'title' - } -] - -const columns = reactive([ +const crudSchemas = reactive([ { field: 'index', label: t('tableDemo.index'), - type: 'index' + type: 'index', + form: { + show: false + }, + detail: { + show: false + } }, { field: 'title', - label: t('tableDemo.title') + label: t('tableDemo.title'), + search: { + show: true + }, + form: { + colProps: { + span: 24 + } + }, + detail: { + span: 24 + } }, { field: 'author', @@ -58,7 +67,14 @@ const columns = reactive([ }, { field: 'display_time', - label: t('tableDemo.displayTime') + label: t('tableDemo.displayTime'), + form: { + component: 'DatePicker', + componentProps: { + type: 'datetime', + valueFormat: 'YYYY-MM-DD HH:mm:ss' + } + } }, { field: 'importance', @@ -76,19 +92,66 @@ const columns = reactive([ ? t('tableDemo.good') : t('tableDemo.commonly') ) + }, + form: { + component: 'Select', + componentProps: { + options: [ + { + label: '重要', + value: 3 + }, + { + label: '良好', + value: 2 + }, + { + label: '一般', + value: 1 + } + ] + } } }, { field: 'pageviews', - label: t('tableDemo.pageviews') + label: t('tableDemo.pageviews'), + form: { + component: 'InputNumber', + value: 0 + } + }, + { + field: 'content', + label: t('exampleDemo.content'), + table: { + show: false + }, + form: { + component: 'Editor', + colProps: { + span: 24 + } + }, + detail: { + span: 24 + } }, { field: 'action', width: '260px', - label: t('tableDemo.action') + label: t('tableDemo.action'), + form: { + show: false + }, + detail: { + show: false + } } ]) +const { allSchemas } = useCrudSchemas(crudSchemas) + const dialogVisible = ref(false) const dialogTitle = ref('') @@ -152,7 +215,7 @@ const save = async () => {