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 () => {
-
+
{{ t('exampleDemo.add') }}
@@ -164,7 +227,7 @@ const save = async () => {
-
+
-
+
diff --git a/src/views/Example/Dialog/components/Detail.vue b/src/views/Example/Dialog/components/Detail.vue
index b89ed4d..0b280b7 100644
--- a/src/views/Example/Dialog/components/Detail.vue
+++ b/src/views/Example/Dialog/components/Detail.vue
@@ -1,5 +1,5 @@
-
+
{{
diff --git a/src/views/Example/Dialog/components/Write.vue b/src/views/Example/Dialog/components/Write.vue
index e59ebd0..f5a864c 100644
--- a/src/views/Example/Dialog/components/Write.vue
+++ b/src/views/Example/Dialog/components/Write.vue
@@ -3,103 +3,19 @@ import { Form } from '@/components/Form'
import { useForm } from '@/hooks/web/useForm'
import { PropType, reactive, watch } from 'vue'
import { TableData } from '@/api/table/types'
-import { useI18n } from '@/hooks/web/useI18n'
import { required } from '@/utils/formRules'
-import { IDomEditor } from '@wangeditor/editor'
const props = defineProps({
currentRow: {
type: Object as PropType>,
default: () => null
+ },
+ formSchema: {
+ type: Array as PropType,
+ default: () => []
}
})
-const { t } = useI18n()
-
-const schema = reactive([
- {
- field: 'title',
- label: t('exampleDemo.title'),
- component: 'Input',
- formItemProps: {
- rules: [required]
- },
- colProps: {
- span: 24
- }
- },
- {
- field: 'author',
- label: t('exampleDemo.author'),
- component: 'Input',
- formItemProps: {
- rules: [required]
- }
- },
- {
- field: 'display_time',
- label: t('exampleDemo.displayTime'),
- component: 'DatePicker',
- componentProps: {
- type: 'datetime',
- valueFormat: 'YYYY-MM-DD HH:mm:ss'
- },
- formItemProps: {
- rules: [required]
- }
- },
- {
- field: 'importance',
- label: t('exampleDemo.importance'),
- component: 'Select',
- formItemProps: {
- rules: [required]
- },
- componentProps: {
- options: [
- {
- label: '重要',
- value: 3
- },
- {
- label: '良好',
- value: 2
- },
- {
- label: '一般',
- value: 1
- }
- ]
- }
- },
- {
- field: 'pageviews',
- label: t('exampleDemo.pageviews'),
- component: 'InputNumber',
- value: 0,
- formItemProps: {
- rules: [required]
- }
- },
- {
- field: 'content',
- component: 'Editor',
- colProps: {
- span: 24
- },
- componentProps: {
- defaultHtml: '',
- onChange: (edit: IDomEditor) => {
- const { setValues } = methods
- setValues({
- content: edit.getHtml()
- })
- }
- },
- label: t('exampleDemo.content')
- }
-])
-
const rules = reactive({
title: [required],
author: [required],
@@ -110,22 +26,15 @@ const rules = reactive({
})
const { register, methods, elFormRef } = useForm({
- schema
+ schema: props.formSchema
})
watch(
() => props.currentRow,
(currentRow) => {
if (!currentRow) return
- const { setValues, setSchema } = methods
+ const { setValues } = methods
setValues(currentRow)
- setSchema([
- {
- field: 'content',
- path: 'componentProps.defaultHtml',
- value: currentRow.content
- }
- ])
},
{
deep: true,
diff --git a/src/views/Example/Page/ExamplePage.vue b/src/views/Example/Page/ExamplePage.vue
index 1e3fa44..616efa0 100644
--- a/src/views/Example/Page/ExamplePage.vue
+++ b/src/views/Example/Page/ExamplePage.vue
@@ -10,6 +10,7 @@ import { TableData } from '@/api/table/types'
import { h, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useEmitt } from '@/hooks/web/useEmitt'
+import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
defineOptions({
name: 'ExamplePage'
@@ -48,16 +49,7 @@ useEmitt({
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'),
@@ -65,7 +57,10 @@ const columns = reactive([
},
{
field: 'title',
- label: t('tableDemo.title')
+ label: t('tableDemo.title'),
+ search: {
+ show: true
+ }
},
{
field: 'author',
@@ -97,6 +92,13 @@ const columns = reactive([
field: 'pageviews',
label: t('tableDemo.pageviews')
},
+ {
+ field: 'content',
+ label: t('exampleDemo.content'),
+ table: {
+ show: false
+ }
+ },
{
field: 'action',
width: '260px',
@@ -104,6 +106,8 @@ const columns = reactive([
}
])
+const { allSchemas } = useCrudSchemas(crudSchemas)
+
const AddAction = () => {
push('/example/example-add')
}
@@ -130,7 +134,7 @@ const action = (row: TableData, type: string) => {
-
+
{{ t('exampleDemo.add') }}
@@ -142,7 +146,7 @@ const action = (row: TableData, type: string) => {