mirror of
				https://github.com/soybeanjs/soybean-admin.git
				synced 2025-10-31 22:03:41 +08:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			thin-v0.10
			...
			fast-crud
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 032dbc6815 | ||
|  | 3c33f89ec1 | ||
|  | f7090d3dbc | ||
|  | 59af204a4c | 
							
								
								
									
										295
									
								
								mock/api/crud/base.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								mock/api/crud/base.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,295 @@ | |||||||
|  | export type ListItem = { | ||||||
|  |   id?: number; | ||||||
|  |   children?: ListItem[]; | ||||||
|  |   [key: string]: any; | ||||||
|  | }; | ||||||
|  | export type BaseMockOptions = { name: string; copyTimes?: number; list: ListItem[]; idGenerator: number }; | ||||||
|  | type CopyListParams = { originList: ListItem[]; newList: ListItem[]; options: BaseMockOptions; parentId?: number }; | ||||||
|  |  | ||||||
|  | function copyList(props: CopyListParams) { | ||||||
|  |   const { originList, newList, options, parentId } = props; | ||||||
|  |   for (const item of originList) { | ||||||
|  |     const newItem: ListItem = { ...item, parentId }; | ||||||
|  |     newItem.id = options.idGenerator; | ||||||
|  |     options.idGenerator += 1; | ||||||
|  |     newList.push(newItem); | ||||||
|  |     if (item.children) { | ||||||
|  |       newItem.children = []; | ||||||
|  |       copyList({ | ||||||
|  |         originList: item.children, | ||||||
|  |         newList: newItem.children, | ||||||
|  |         options, | ||||||
|  |         parentId: newItem.id | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function delById(req: Service.MockOption, list: any[]) { | ||||||
|  |   for (let i = 0; i < list.length; i += 1) { | ||||||
|  |     const item = list[i]; | ||||||
|  |     if (item.id === parseInt(req.query.id, 10)) { | ||||||
|  |       list.splice(i, 1); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     if (item.children && item.children.length > 0) { | ||||||
|  |       delById(req, item.children); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function findById(id: number, list: ListItem[]): any { | ||||||
|  |   for (const item of list) { | ||||||
|  |     if (item.id === id) { | ||||||
|  |       return item; | ||||||
|  |     } | ||||||
|  |     if (item.children && item.children.length > 0) { | ||||||
|  |       const sub = findById(id, item.children); | ||||||
|  |       if (sub !== null && sub !== undefined) { | ||||||
|  |         return sub; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return null; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function matchWithArrayCondition(value: any[], item: ListItem, key: string) { | ||||||
|  |   if (value.length === 0) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   let matched = false; | ||||||
|  |   for (const i of value) { | ||||||
|  |     if (item[key] instanceof Array) { | ||||||
|  |       for (const j of item[key]) { | ||||||
|  |         if (i === j) { | ||||||
|  |           matched = true; | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (matched) { | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |     } else if (item[key] === i || (typeof item[key] === 'string' && item[key].indexOf(`${i}`) >= 0)) { | ||||||
|  |       matched = true; | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     if (matched) { | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return matched; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function matchWithObjectCondition(value: any, item: ListItem, key: string) { | ||||||
|  |   let matched = true; | ||||||
|  |   for (const key2 of Object.keys(value)) { | ||||||
|  |     const v = value[key2]; | ||||||
|  |     if (v && item[key] && v !== item[key][key2]) { | ||||||
|  |       matched = false; | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   return matched; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function searchFromList(list: ListItem[], query: any) { | ||||||
|  |   const filter = (item: ListItem) => { | ||||||
|  |     let allFound = true; // 是否所有条件都符合 | ||||||
|  |     for (const key of Object.keys(query)) { | ||||||
|  |       const value = query[key]; | ||||||
|  |       if (value === undefined || value === null || value === '') { | ||||||
|  |         // no nothing | ||||||
|  |       } else if (value instanceof Array) { | ||||||
|  |         // 如果条件中的value是数组的话,只要查到一个就行 | ||||||
|  |         const matched = matchWithArrayCondition(value, item, key); | ||||||
|  |         if (!matched) { | ||||||
|  |           allFound = false; | ||||||
|  |         } | ||||||
|  |       } else if (value instanceof Object) { | ||||||
|  |         // 如果条件中的value是对象的话,需要每个key都匹配 | ||||||
|  |         const matched = matchWithObjectCondition(value, item, key); | ||||||
|  |         if (!matched) { | ||||||
|  |           allFound = false; | ||||||
|  |         } | ||||||
|  |       } else if (item[key] !== value) { | ||||||
|  |         allFound = false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return allFound; | ||||||
|  |   }; | ||||||
|  |   return list.filter(filter); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   buildMock(options: BaseMockOptions) { | ||||||
|  |     const name = options.name; | ||||||
|  |     if (!options.copyTimes) { | ||||||
|  |       options.copyTimes = 29; | ||||||
|  |     } | ||||||
|  |     const list: any[] = []; | ||||||
|  |     for (let i = 0; i < options.copyTimes; i += 1) { | ||||||
|  |       copyList({ | ||||||
|  |         originList: options.list, | ||||||
|  |         newList: list, | ||||||
|  |         options | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     options.list = list; | ||||||
|  |     return [ | ||||||
|  |       { | ||||||
|  |         path: `/mock/${name}/page`, | ||||||
|  |         method: 'post', | ||||||
|  |         handle(req: Service.MockOption) { | ||||||
|  |           let data = [...list]; | ||||||
|  |           let limit = 20; | ||||||
|  |           let offset = 0; | ||||||
|  |           for (const item of list) { | ||||||
|  |             if (item.children && item.children.length === 0) { | ||||||
|  |               item.hasChildren = false; | ||||||
|  |               item.lazy = false; | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |           let orderAsc: any; | ||||||
|  |           let orderProp: any; | ||||||
|  |           if (req && req.body) { | ||||||
|  |             const { page, query } = req.body; | ||||||
|  |             if (page.limit) { | ||||||
|  |               limit = parseInt(page.limit, 10); | ||||||
|  |             } | ||||||
|  |             if (page.offset) { | ||||||
|  |               offset = parseInt(page.offset, 10); | ||||||
|  |             } | ||||||
|  |             if (Object.keys(query).length > 0) { | ||||||
|  |               data = searchFromList(list, query); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           const start = offset; | ||||||
|  |           let end = offset + limit; | ||||||
|  |           if (data.length < end) { | ||||||
|  |             end = data.length; | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           if (orderProp) { | ||||||
|  |             // 排序 | ||||||
|  |             data.sort((a, b) => { | ||||||
|  |               let ret = 0; | ||||||
|  |               if (a[orderProp] > b[orderProp]) { | ||||||
|  |                 ret = 1; | ||||||
|  |               } else if (a[orderProp] < b[orderProp]) { | ||||||
|  |                 ret = -1; | ||||||
|  |               } | ||||||
|  |               return orderAsc ? ret : -ret; | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           const records = data.slice(start, end); | ||||||
|  |           const lastOffset = data.length - (data.length % limit); | ||||||
|  |           if (offset > lastOffset) { | ||||||
|  |             offset = lastOffset; | ||||||
|  |           } | ||||||
|  |           return { | ||||||
|  |             code: 200, | ||||||
|  |             message: 'success', | ||||||
|  |             data: { | ||||||
|  |               records, | ||||||
|  |               total: data.length, | ||||||
|  |               limit, | ||||||
|  |               offset | ||||||
|  |             } | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         path: `/mock/${name}/get`, | ||||||
|  |         method: 'get', | ||||||
|  |         handle(req: Service.MockOption) { | ||||||
|  |           let id = req.query.id; | ||||||
|  |           id = parseInt(id, 10); | ||||||
|  |           let current = null; | ||||||
|  |           for (const item of list) { | ||||||
|  |             if (item.id === id) { | ||||||
|  |               current = item; | ||||||
|  |               break; | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |           return { | ||||||
|  |             code: 200, | ||||||
|  |             message: 'success', | ||||||
|  |             data: current | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         path: `/mock/${name}/add`, | ||||||
|  |         method: 'post', | ||||||
|  |         handle(req: Service.MockOption) { | ||||||
|  |           req.body.id = options.idGenerator; | ||||||
|  |           options.idGenerator += 1; | ||||||
|  |           list.unshift(req.body); | ||||||
|  |           return { | ||||||
|  |             code: 200, | ||||||
|  |             message: 'success', | ||||||
|  |             data: req.body.id | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         path: `/mock/${name}/update`, | ||||||
|  |         method: 'post', | ||||||
|  |         handle(req: Service.MockOption) { | ||||||
|  |           const item = findById(req.body.id, list); | ||||||
|  |           if (item) { | ||||||
|  |             Object.assign(item, req.body); | ||||||
|  |           } | ||||||
|  |           return { | ||||||
|  |             code: 200, | ||||||
|  |             message: 'success', | ||||||
|  |             data: null | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         path: `/mock/${name}/delete`, | ||||||
|  |         method: 'post', | ||||||
|  |         handle(req: Service.MockOption) { | ||||||
|  |           delById(req, list); | ||||||
|  |           return { | ||||||
|  |             code: 200, | ||||||
|  |             message: 'success', | ||||||
|  |             data: null | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         path: `/mock/${name}/batchDelete`, | ||||||
|  |         method: 'post', | ||||||
|  |         handle(req: Service.MockOption) { | ||||||
|  |           const ids = req.body.ids; | ||||||
|  |           for (let i = list.length - 1; i >= 0; i -= 1) { | ||||||
|  |             const item = list[i]; | ||||||
|  |             if (ids.indexOf(item.id) >= 0) { | ||||||
|  |               list.splice(i, 1); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |           return { | ||||||
|  |             code: 200, | ||||||
|  |             message: 'success', | ||||||
|  |             data: null | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         path: `/mock/${name}/all`, | ||||||
|  |         method: 'post', | ||||||
|  |         handle() { | ||||||
|  |           return { | ||||||
|  |             code: 200, | ||||||
|  |             message: 'success', | ||||||
|  |             data: list | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
|  | }; | ||||||
							
								
								
									
										5
									
								
								mock/api/crud/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								mock/api/crud/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | import demo from './modules/demo'; | ||||||
|  | import headerGroup from './modules/header-group'; | ||||||
|  |  | ||||||
|  | const crudApis = [...demo, ...headerGroup]; | ||||||
|  | export default crudApis; | ||||||
							
								
								
									
										56
									
								
								mock/api/crud/modules/demo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								mock/api/crud/modules/demo.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | |||||||
|  | import type { MethodType, MockMethod } from 'vite-plugin-mock'; | ||||||
|  | import type { BaseMockOptions } from '../base'; | ||||||
|  | import mockBase from '../base'; | ||||||
|  | import MockOption = Service.MockOption; | ||||||
|  |  | ||||||
|  | const options: BaseMockOptions = { | ||||||
|  |   name: 'crud/demo', | ||||||
|  |   idGenerator: 0, | ||||||
|  |   list: [ | ||||||
|  |     { | ||||||
|  |       select: '1', | ||||||
|  |       text: '文本测试', | ||||||
|  |       copyable: '文本可复制', | ||||||
|  |       avatar: 'http://greper.handsfree.work/extends/avatar.jpg', | ||||||
|  |       richtext: '富文本', | ||||||
|  |       datetime: '2023-01-30 11:11:11' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       select: '2' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       select: '0' | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | }; | ||||||
|  | const mockedApis = mockBase.buildMock(options); | ||||||
|  |  | ||||||
|  | const apis: MockMethod[] = [ | ||||||
|  |   { | ||||||
|  |     url: `/mock/${options.name}/dict`, | ||||||
|  |     method: 'get', | ||||||
|  |     response: () => { | ||||||
|  |       return { | ||||||
|  |         code: 200, | ||||||
|  |         message: '', | ||||||
|  |         data: [ | ||||||
|  |           { value: '0', label: '关', color: 'warning' }, | ||||||
|  |           { value: '1', label: '开', color: 'success' }, | ||||||
|  |           { value: '2', label: '停' } | ||||||
|  |         ] | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | for (const mockedApi of mockedApis) { | ||||||
|  |   apis.push({ | ||||||
|  |     url: mockedApi.path, | ||||||
|  |     method: mockedApi.method as MethodType, | ||||||
|  |     response: (request: MockOption) => { | ||||||
|  |       return mockedApi.handle(request); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default apis; | ||||||
							
								
								
									
										46
									
								
								mock/api/crud/modules/header-group.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								mock/api/crud/modules/header-group.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | import type { MethodType, MockMethod } from 'vite-plugin-mock'; | ||||||
|  | import type { BaseMockOptions } from '../base'; | ||||||
|  | import mockBase from '../base'; | ||||||
|  | import MockOption = Service.MockOption; | ||||||
|  |  | ||||||
|  | const options: BaseMockOptions = { | ||||||
|  |   name: 'crud/header-group', | ||||||
|  |   idGenerator: 0, | ||||||
|  |   list: [ | ||||||
|  |     { | ||||||
|  |       name: '张三', | ||||||
|  |       age: 18, | ||||||
|  |       province: '广东省', | ||||||
|  |       city: '深圳市', | ||||||
|  |       county: '南山区', | ||||||
|  |       street: '粤海街道' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: '李四', | ||||||
|  |       age: 26, | ||||||
|  |       province: '浙江省', | ||||||
|  |       city: '杭州市', | ||||||
|  |       county: '西湖区', | ||||||
|  |       street: '西湖街道' | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: '王五', | ||||||
|  |       age: 24 | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | }; | ||||||
|  | const mockedApis = mockBase.buildMock(options); | ||||||
|  |  | ||||||
|  | const apis: MockMethod[] = []; | ||||||
|  |  | ||||||
|  | for (const mockedApi of mockedApis) { | ||||||
|  |   apis.push({ | ||||||
|  |     url: mockedApi.path, | ||||||
|  |     method: mockedApi.method as MethodType, | ||||||
|  |     response: (request: MockOption) => { | ||||||
|  |       return mockedApi.handle(request); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default apis; | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| import auth from './auth'; | import auth from './auth'; | ||||||
| import route from './route'; | import route from './route'; | ||||||
| import management from './management'; | import management from './management'; | ||||||
|  | import crud from './crud'; | ||||||
|  |  | ||||||
| export default [...auth, ...route, ...management]; | export default [...auth, ...route, ...management, ...crud]; | ||||||
|   | |||||||
| @@ -56,6 +56,11 @@ | |||||||
|     "prepare": "soy init-git-hooks" |     "prepare": "soy init-git-hooks" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |     "@ant-design/icons-vue": "^6.1.0", | ||||||
|  |     "@fast-crud/fast-crud": "^1.13.6", | ||||||
|  |     "@fast-crud/fast-extends": "^1.13.6", | ||||||
|  |     "@fast-crud/ui-naive": "^1.13.6", | ||||||
|  |     "@fast-crud/ui-interface": "^1.13.6", | ||||||
|     "@antv/data-set": "^0.11.8", |     "@antv/data-set": "^0.11.8", | ||||||
|     "@antv/g2": "^4.2.10", |     "@antv/g2": "^4.2.10", | ||||||
|     "@better-scroll/core": "^2.5.1", |     "@better-scroll/core": "^2.5.1", | ||||||
|   | |||||||
							
								
								
									
										3005
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3005
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -7,7 +7,9 @@ | |||||||
|     class="h-full" |     class="h-full" | ||||||
|   > |   > | ||||||
|     <naive-provider> |     <naive-provider> | ||||||
|       <router-view /> |       <fs-ui-context> | ||||||
|  |         <router-view /> | ||||||
|  |       </fs-ui-context> | ||||||
|     </naive-provider> |     </naive-provider> | ||||||
|   </n-config-provider> |   </n-config-provider> | ||||||
| </template> | </template> | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ import App from './App.vue'; | |||||||
| import AppLoading from './components/common/app-loading.vue'; | import AppLoading from './components/common/app-loading.vue'; | ||||||
| import { setupDirectives } from './directives'; | import { setupDirectives } from './directives'; | ||||||
| import { setupRouter } from './router'; | import { setupRouter } from './router'; | ||||||
| import { setupAssets } from './plugins'; | import { setupAssets, setupFastCrud } from './plugins'; | ||||||
| import { setupStore } from './store'; | import { setupStore } from './store'; | ||||||
| import { setupI18n } from './locales'; | import { setupI18n } from './locales'; | ||||||
|  |  | ||||||
| @@ -29,6 +29,8 @@ async function setupApp() { | |||||||
|  |  | ||||||
|   setupI18n(app); |   setupI18n(app); | ||||||
|  |  | ||||||
|  |   setupFastCrud(app); | ||||||
|  |  | ||||||
|   // mount app |   // mount app | ||||||
|   app.mount('#app'); |   app.mount('#app'); | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								src/plugins/fast-crud/common.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/plugins/fast-crud/common.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | html:root { | ||||||
|  | 	--baseColor: #fff; | ||||||
|  | } | ||||||
|  | /* 深色模式 */ | ||||||
|  | html.dark:root { | ||||||
|  | 	--baseColor: #000; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .fs-container { | ||||||
|  | 	background-color: var(--baseColor); | ||||||
|  | } | ||||||
							
								
								
									
										171
									
								
								src/plugins/fast-crud/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								src/plugins/fast-crud/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,171 @@ | |||||||
|  | import type { App } from 'vue'; | ||||||
|  | import type { FsSetupOptions, PageQuery } from '@fast-crud/fast-crud'; | ||||||
|  | // eslint-disable-next-line import/order | ||||||
|  | import { FastCrud } from '@fast-crud/fast-crud'; | ||||||
|  | import '@fast-crud/fast-crud/dist/style.css'; | ||||||
|  | import './common.scss'; | ||||||
|  |  | ||||||
|  | import type { FsUploaderOptions } from '@fast-crud/fast-extends'; | ||||||
|  | import { | ||||||
|  |   FsExtendsCopyable, | ||||||
|  |   FsExtendsEditor, | ||||||
|  |   FsExtendsJson, | ||||||
|  |   FsExtendsTime, | ||||||
|  |   FsExtendsUploader | ||||||
|  | } from '@fast-crud/fast-extends'; | ||||||
|  | import '@fast-crud/fast-extends/dist/style.css'; | ||||||
|  | import UiNaive from '@fast-crud/ui-naive'; | ||||||
|  | import axios from 'axios'; | ||||||
|  | import type { VueI18n } from 'vue-i18n'; | ||||||
|  | import { mockRequest, request } from '@/service/request'; | ||||||
|  | import { setupNaive } from '@/plugins/fast-crud/naive'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  fast-crud的安装方法 | ||||||
|  |  *  注意:在App.vue中,需要用fs-ui-context组件包裹RouterView,让fs-crud拥有message、notification、dialog的能力 | ||||||
|  |  * @param app | ||||||
|  |  * @param options | ||||||
|  |  */ | ||||||
|  | export type FsSetupOpts = { | ||||||
|  |   i18n?: VueI18n; | ||||||
|  | }; | ||||||
|  | function install(app: App, options: FsSetupOpts = {}) { | ||||||
|  |   // 安装naive ui 常用组件 | ||||||
|  |   setupNaive(app); | ||||||
|  |   app.use(UiNaive); | ||||||
|  |   app.use(FastCrud, { | ||||||
|  |     i18n: options.i18n, | ||||||
|  |     async dictRequest(context: { url: string }) { | ||||||
|  |       const url = context.url; | ||||||
|  |       let res: Service.SuccessResult | Service.FailedResult; | ||||||
|  |       if (url && url.startsWith('/mock')) { | ||||||
|  |         // 如果是crud开头的dict请求视为mock | ||||||
|  |         res = await mockRequest.get(url.replace('/mock', '')); | ||||||
|  |       } else { | ||||||
|  |         res = await request.get(url); | ||||||
|  |       } | ||||||
|  |       res = res || {}; | ||||||
|  |       return res.data || []; | ||||||
|  |     }, | ||||||
|  |     /** | ||||||
|  |      * useCrud时会被执行 | ||||||
|  |      */ | ||||||
|  |     commonOptions() { | ||||||
|  |       return { | ||||||
|  |         table: { | ||||||
|  |           size: 'small', | ||||||
|  |           pagination: false | ||||||
|  |         }, | ||||||
|  |         search: { | ||||||
|  |           options: { | ||||||
|  |             size: 'medium' | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         rowHandle: { | ||||||
|  |           buttons: { | ||||||
|  |             view: { text: null, icon: 'EyeOutlined', size: 'small' }, | ||||||
|  |             edit: { text: null, icon: 'EditOutlined', size: 'small' }, | ||||||
|  |             remove: { type: 'error', text: null, icon: 'DeleteOutlined', size: 'small' } | ||||||
|  |           }, | ||||||
|  |           dropdown: { | ||||||
|  |             more: { size: 'small' } | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         request: { | ||||||
|  |           // 查询参数转换 | ||||||
|  |           transformQuery: (query: PageQuery) => { | ||||||
|  |             const { page, form, sort } = query; | ||||||
|  |             const limit = page.pageSize; | ||||||
|  |             const currentPage = page.currentPage ?? 1; | ||||||
|  |             const offset = limit * (currentPage - 1); | ||||||
|  |  | ||||||
|  |             return { | ||||||
|  |               page: { | ||||||
|  |                 limit, | ||||||
|  |                 offset | ||||||
|  |               }, | ||||||
|  |               query: form, | ||||||
|  |               sort: sort || {} | ||||||
|  |             }; | ||||||
|  |           }, | ||||||
|  |           // page请求结果转换 | ||||||
|  |           transformRes: originPageRes => { | ||||||
|  |             const { res } = originPageRes; | ||||||
|  |             const pageSize = res.limit; | ||||||
|  |             let currentPage = res.offset / pageSize; | ||||||
|  |             if (res.offset % pageSize === 0) { | ||||||
|  |               currentPage += 1; | ||||||
|  |             } | ||||||
|  |             return { currentPage, pageSize, ...res }; | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         form: { | ||||||
|  |           display: 'flex', // 表单布局 | ||||||
|  |           labelWidth: '120px' // 表单label宽度 | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |       // 从 useCrud({permission}) 里获取permission参数,去设置各个按钮的权限 | ||||||
|  |       // const crudPermission = useCrudPermission(context); | ||||||
|  |       // return crudPermission.merge(opts); | ||||||
|  |     } | ||||||
|  |   } as FsSetupOptions); | ||||||
|  |  | ||||||
|  |   // fast-extends里面的扩展组件均为异步组件,只有在使用时才会被加载,并不会影响首页加载速度 | ||||||
|  |   // 安装editor | ||||||
|  |   app.use(FsExtendsEditor, { | ||||||
|  |     // 编辑器的公共配置 | ||||||
|  |     wangEditor: {} | ||||||
|  |   }); | ||||||
|  |   app.use(FsExtendsJson); | ||||||
|  |   app.use(FsExtendsCopyable); | ||||||
|  |   // 安装uploader 公共参数 | ||||||
|  |   const uploaderOptions: FsUploaderOptions = { | ||||||
|  |     defaultType: 'form', | ||||||
|  |     form: { | ||||||
|  |       action: 'http://www.docmirror.cn:7070/api/upload/form/upload', | ||||||
|  |       name: 'file', | ||||||
|  |       withCredentials: false, | ||||||
|  |       uploadRequest: async props => { | ||||||
|  |         const { action, file, onProgress } = props; | ||||||
|  |         const data = new FormData(); | ||||||
|  |         data.append('file', file); | ||||||
|  |         const res = await axios.post(action, data, { | ||||||
|  |           headers: { | ||||||
|  |             'Content-Type': 'multipart/form-data' | ||||||
|  |           }, | ||||||
|  |           timeout: 60000, | ||||||
|  |           onUploadProgress(progress) { | ||||||
|  |             onProgress({ percent: Math.round((progress.loaded / progress.total!) * 100) }); | ||||||
|  |           } | ||||||
|  |         }); | ||||||
|  |         // 上传完成后的结果,一般返回个url 或者key,具体看你的后台返回啥 | ||||||
|  |         return res.data.data; | ||||||
|  |       }, | ||||||
|  |       async successHandle(ret: string) { | ||||||
|  |         // 上传完成后的结果处理, 此处应转换格式为{url:xxx,key:xxx} | ||||||
|  |         return { | ||||||
|  |           url: `http://www.docmirror.cn:7070${ret}`, | ||||||
|  |           key: ret.replace('/api/upload/form/download?key=', '') | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |   app.use(FsExtendsUploader, uploaderOptions); | ||||||
|  |  | ||||||
|  |   // 安装editor | ||||||
|  |   app.use(FsExtendsEditor, { | ||||||
|  |     // 编辑器的公共配置 | ||||||
|  |     wangEditor: {} | ||||||
|  |   }); | ||||||
|  |   app.use(FsExtendsJson); | ||||||
|  |   app.use(FsExtendsTime); | ||||||
|  |   app.use(FsExtendsCopyable); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   install | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export function setupFastCrud(app: App<Element>, options: FsSetupOpts = {}) { | ||||||
|  |   install(app, options); | ||||||
|  | } | ||||||
							
								
								
									
										59
									
								
								src/plugins/fast-crud/naive.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/plugins/fast-crud/naive.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | import type { App } from 'vue'; | ||||||
|  | import * as NaiveUI from 'naive-ui'; | ||||||
|  |  | ||||||
|  | const naive = NaiveUI.create({ | ||||||
|  |   components: [ | ||||||
|  |     NaiveUI.NInput, | ||||||
|  |     NaiveUI.NButton, | ||||||
|  |     NaiveUI.NForm, | ||||||
|  |     NaiveUI.NFormItem, | ||||||
|  |     NaiveUI.NCheckboxGroup, | ||||||
|  |     NaiveUI.NCheckbox, | ||||||
|  |     NaiveUI.NIcon, | ||||||
|  |     NaiveUI.NDropdown, | ||||||
|  |     NaiveUI.NTooltip, | ||||||
|  |     NaiveUI.NTabs, | ||||||
|  |     NaiveUI.NTabPane, | ||||||
|  |     NaiveUI.NCard, | ||||||
|  |     NaiveUI.NRow, | ||||||
|  |     NaiveUI.NCol, | ||||||
|  |     NaiveUI.NDrawer, | ||||||
|  |     NaiveUI.NDrawerContent, | ||||||
|  |     NaiveUI.NDivider, | ||||||
|  |     NaiveUI.NSwitch, | ||||||
|  |     NaiveUI.NBadge, | ||||||
|  |     NaiveUI.NAlert, | ||||||
|  |     NaiveUI.NTag, | ||||||
|  |     NaiveUI.NProgress, | ||||||
|  |     NaiveUI.NDatePicker, | ||||||
|  |     NaiveUI.NGrid, | ||||||
|  |     NaiveUI.NGridItem, | ||||||
|  |     NaiveUI.NDataTable, | ||||||
|  |     NaiveUI.NPagination, | ||||||
|  |     NaiveUI.NSelect, | ||||||
|  |     NaiveUI.NRadioGroup, | ||||||
|  |     NaiveUI.NRadio, | ||||||
|  |     NaiveUI.NInputGroup, | ||||||
|  |     NaiveUI.NTable, | ||||||
|  |     NaiveUI.NInputNumber, | ||||||
|  |     NaiveUI.NLoadingBarProvider, | ||||||
|  |     NaiveUI.NModal, | ||||||
|  |     NaiveUI.NUpload, | ||||||
|  |     NaiveUI.NTree, | ||||||
|  |     NaiveUI.NSpin, | ||||||
|  |     NaiveUI.NTimePicker, | ||||||
|  |  | ||||||
|  |     // add by fs | ||||||
|  |     NaiveUI.NCascader, | ||||||
|  |     NaiveUI.NRadioButton, | ||||||
|  |     NaiveUI.NTreeSelect, | ||||||
|  |     NaiveUI.NImageGroup, | ||||||
|  |     NaiveUI.NImage, | ||||||
|  |     NaiveUI.NCollapse, | ||||||
|  |     NaiveUI.NCollapseItem | ||||||
|  |   ] | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export function setupNaive(app: App<Element>) { | ||||||
|  |   app.use(naive); | ||||||
|  | } | ||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | import { setupFastCrud } from '@/plugins/fast-crud'; | ||||||
| import setupAssets from './assets'; | import setupAssets from './assets'; | ||||||
|  |  | ||||||
|  | export { setupFastCrud }; | ||||||
| export { setupAssets }; | export { setupAssets }; | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								src/router/modules/crud.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/router/modules/crud.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | const component: any = { | ||||||
|  |   name: 'crud', | ||||||
|  |   path: '/crud', | ||||||
|  |   component: 'basic', | ||||||
|  |   meta: { | ||||||
|  |     title: 'CRUD示例', | ||||||
|  |     requiresAuth: true, | ||||||
|  |     icon: 'mdi:table-large', | ||||||
|  |     order: 4 | ||||||
|  |   }, | ||||||
|  |   children: [ | ||||||
|  |     { | ||||||
|  |       name: 'crud_demo', | ||||||
|  |       path: '/crud/demo', | ||||||
|  |       component: 'self', | ||||||
|  |       meta: { | ||||||
|  |         title: '基本示例', | ||||||
|  |         requiresAuth: true, | ||||||
|  |         icon: 'mdi:button-cursor' | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: 'crud_header_group', | ||||||
|  |       path: '/crud/header_group', | ||||||
|  |       component: 'self', | ||||||
|  |       meta: { | ||||||
|  |         title: '多级表头', | ||||||
|  |         requiresAuth: true, | ||||||
|  |         icon: 'mdi:button-cursor' | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       name: 'crud_doc', | ||||||
|  |       path: '/crud/doc', | ||||||
|  |       component: 'self', | ||||||
|  |       meta: { | ||||||
|  |         title: 'FastCrud文档', | ||||||
|  |         requiresAuth: true, | ||||||
|  |         icon: 'logos:vue' | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default component; | ||||||
							
								
								
									
										10
									
								
								src/typings/page-route.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								src/typings/page-route.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -30,6 +30,12 @@ declare namespace PageRoute { | |||||||
|     | 'component_button' |     | 'component_button' | ||||||
|     | 'component_card' |     | 'component_card' | ||||||
|     | 'component_table' |     | 'component_table' | ||||||
|  |     | 'crud' | ||||||
|  |     | 'crud_demo' | ||||||
|  |     | 'crud_doc' | ||||||
|  |     | 'crud_header' | ||||||
|  |     | 'crud_header_group' | ||||||
|  |     | 'crud_source' | ||||||
|     | 'dashboard' |     | 'dashboard' | ||||||
|     | 'dashboard_analysis' |     | 'dashboard_analysis' | ||||||
|     | 'dashboard_workbench' |     | 'dashboard_workbench' | ||||||
| @@ -89,6 +95,10 @@ declare namespace PageRoute { | |||||||
|     | 'component_button' |     | 'component_button' | ||||||
|     | 'component_card' |     | 'component_card' | ||||||
|     | 'component_table' |     | 'component_table' | ||||||
|  |     | 'crud_demo' | ||||||
|  |     | 'crud_doc' | ||||||
|  |     | 'crud_header_group' | ||||||
|  |     | 'crud_source' | ||||||
|     | 'dashboard_analysis' |     | 'dashboard_analysis' | ||||||
|     | 'dashboard_workbench' |     | 'dashboard_workbench' | ||||||
|     | 'document_naive' |     | 'document_naive' | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								src/views/crud/demo/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/views/crud/demo/api.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | import type { UserPageQuery } from '@fast-crud/fast-crud'; | ||||||
|  | import { mockRequest } from '@/service/request'; | ||||||
|  |  | ||||||
|  | const request = mockRequest; | ||||||
|  | const apiPrefix = '/crud/demo'; | ||||||
|  |  | ||||||
|  | export type DemoRecord = { | ||||||
|  |   id: number; | ||||||
|  |   [key: string]: any; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | function resHandle(res: any) { | ||||||
|  |   return res.data; | ||||||
|  | } | ||||||
|  | export async function GetList(query: UserPageQuery) { | ||||||
|  |   const res = await request.post(`${apiPrefix}/page`, query); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function AddObj(obj: DemoRecord) { | ||||||
|  |   const res = await request.post(`${apiPrefix}/add`, obj); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function UpdateObj(obj: DemoRecord) { | ||||||
|  |   const res = await request.post(`${apiPrefix}/update`, obj); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function DelObj(id: number) { | ||||||
|  |   const res = await request.post(`${apiPrefix}/delete`, { id }); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function GetObj(id: number) { | ||||||
|  |   const res = await request.get(`${apiPrefix}/info`, { params: { id } }); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function BatchDelete(ids: number[]) { | ||||||
|  |   const res = await request.post(`${apiPrefix}/batchDelete`, { ids }); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
							
								
								
									
										114
									
								
								src/views/crud/demo/crud.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/views/crud/demo/crud.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | |||||||
|  | import type { AddReq, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from '@fast-crud/fast-crud'; | ||||||
|  | import { dict } from '@fast-crud/fast-crud'; | ||||||
|  | import dayjs from 'dayjs'; | ||||||
|  | import * as api from './api'; | ||||||
|  |  | ||||||
|  | export default function createCrudOptions(): CreateCrudOptionsRet { | ||||||
|  |   const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => { | ||||||
|  |     return api.GetList(query); | ||||||
|  |   }; | ||||||
|  |   const editRequest = async (ctx: EditReq) => { | ||||||
|  |     const { form, row } = ctx; | ||||||
|  |     form.id = row.id; | ||||||
|  |     return api.UpdateObj(form); | ||||||
|  |   }; | ||||||
|  |   const delRequest = async (ctx: DelReq) => { | ||||||
|  |     const { row } = ctx; | ||||||
|  |     return api.DelObj(row.id); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const addRequest = async (req: AddReq) => { | ||||||
|  |     const { form } = req; | ||||||
|  |     return api.AddObj(form); | ||||||
|  |   }; | ||||||
|  |   return { | ||||||
|  |     crudOptions: { | ||||||
|  |       container: { | ||||||
|  |         is: 'fs-layout-card' | ||||||
|  |       }, | ||||||
|  |       request: { | ||||||
|  |         pageRequest, | ||||||
|  |         addRequest, | ||||||
|  |         editRequest, | ||||||
|  |         delRequest | ||||||
|  |       }, | ||||||
|  |       columns: { | ||||||
|  |         id: { | ||||||
|  |           title: 'ID', | ||||||
|  |           key: 'id', | ||||||
|  |           type: 'number', | ||||||
|  |           column: { | ||||||
|  |             width: 50 | ||||||
|  |           }, | ||||||
|  |           form: { | ||||||
|  |             show: false | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         datetime: { | ||||||
|  |           title: '时间', | ||||||
|  |           type: 'datetime', | ||||||
|  |           // naive 默认仅支持数字类型时间戳作为日期输入与输出 | ||||||
|  |           // 字符串类型的时间需要转换格式 | ||||||
|  |           valueBuilder(context) { | ||||||
|  |             const { value, row, key } = context; | ||||||
|  |             if (value) { | ||||||
|  |               // naive 默认仅支持时间戳作为日期输入与输出 | ||||||
|  |               row[key] = dayjs(value).valueOf(); | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           valueResolve(context) { | ||||||
|  |             const { value, form, key } = context; | ||||||
|  |             if (value) { | ||||||
|  |               form[key] = dayjs(value).format('YYYY-MM-DD HH:mm:ss'); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         select: { | ||||||
|  |           title: '状态', | ||||||
|  |           search: { show: true }, | ||||||
|  |           type: 'dict-select', | ||||||
|  |           dict: dict({ | ||||||
|  |             url: '/mock/crud/demo/dict' | ||||||
|  |           }) | ||||||
|  |         }, | ||||||
|  |         text: { | ||||||
|  |           title: '文本', | ||||||
|  |           type: 'text', | ||||||
|  |           search: { show: true } | ||||||
|  |         }, | ||||||
|  |         copyable: { | ||||||
|  |           title: '可复制', | ||||||
|  |           type: ['text', 'copyable'], | ||||||
|  |           search: { show: true } | ||||||
|  |         }, | ||||||
|  |         avatar: { | ||||||
|  |           title: '头像裁剪', | ||||||
|  |           type: 'cropper-uploader' | ||||||
|  |         }, | ||||||
|  |         upload: { | ||||||
|  |           title: '文件上传', | ||||||
|  |           type: 'file-uploader' | ||||||
|  |         }, | ||||||
|  |         richtext: { | ||||||
|  |           title: '富文本', | ||||||
|  |           type: 'editor-wang5', | ||||||
|  |           column: { | ||||||
|  |             // cell中不显示 | ||||||
|  |             show: false | ||||||
|  |           }, | ||||||
|  |           form: { | ||||||
|  |             col: { | ||||||
|  |               // 横跨两列 | ||||||
|  |               span: 24 | ||||||
|  |             }, | ||||||
|  |             component: { | ||||||
|  |               style: { | ||||||
|  |                 height: '300px' | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								src/views/crud/demo/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/views/crud/demo/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="h-full"> | ||||||
|  |     <fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import { defineComponent, onMounted } from 'vue'; | ||||||
|  | import { useFs } from '@fast-crud/fast-crud'; | ||||||
|  | import createCrudOptions from './crud'; | ||||||
|  |  | ||||||
|  | export default defineComponent({ | ||||||
|  |   name: 'ComponentCrud', | ||||||
|  |   setup() { | ||||||
|  |     const { crudRef, crudBinding, crudExpose } = useFs({ createCrudOptions }); | ||||||
|  |  | ||||||
|  |     // 页面打开后获取列表数据 | ||||||
|  |     onMounted(() => { | ||||||
|  |       crudExpose.doRefresh(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |       crudBinding, | ||||||
|  |       crudRef | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | </script> | ||||||
							
								
								
									
										13
									
								
								src/views/crud/doc/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/views/crud/doc/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="h-full"> | ||||||
|  |     <iframe class="wh-full" :src="src"></iframe> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { ref } from 'vue'; | ||||||
|  |  | ||||||
|  | const src = ref('http://fast-crud.docmirror.cn/'); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped></style> | ||||||
							
								
								
									
										43
									
								
								src/views/crud/header_group/api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/views/crud/header_group/api.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | import type { UserPageQuery } from '@fast-crud/fast-crud'; | ||||||
|  | import { mockRequest } from '@/service/request'; | ||||||
|  |  | ||||||
|  | const request = mockRequest; | ||||||
|  | const apiPrefix = '/crud/header-group'; | ||||||
|  |  | ||||||
|  | export type HeaderGroupRecord = { | ||||||
|  |   id: number; | ||||||
|  |   [key: string]: any; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | function resHandle(res: any) { | ||||||
|  |   return res.data; | ||||||
|  | } | ||||||
|  | export async function GetList(query: UserPageQuery) { | ||||||
|  |   const res = await request.post(`${apiPrefix}/page`, query); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function AddObj(obj: HeaderGroupRecord) { | ||||||
|  |   const res = await request.post(`${apiPrefix}/add`, obj); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function UpdateObj(obj: HeaderGroupRecord) { | ||||||
|  |   const res = await request.post(`${apiPrefix}/update`, obj); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function DelObj(id: number) { | ||||||
|  |   const res = await request.post(`${apiPrefix}/delete`, { id }); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function GetObj(id: number) { | ||||||
|  |   const res = await request.get(`${apiPrefix}/info`, { params: { id } }); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function BatchDelete(ids: number[]) { | ||||||
|  |   const res = await request.post(`${apiPrefix}/batchDelete`, { ids }); | ||||||
|  |   return resHandle(res); | ||||||
|  | } | ||||||
							
								
								
									
										96
									
								
								src/views/crud/header_group/crud.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/views/crud/header_group/crud.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | import type { CreateCrudOptionsRet, UserPageQuery, UserPageRes } from '@fast-crud/fast-crud'; | ||||||
|  | import type { HeaderGroupRecord } from './api'; | ||||||
|  | import * as api from './api'; | ||||||
|  |  | ||||||
|  | export default function createCrudOptions(): CreateCrudOptionsRet { | ||||||
|  |   const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => { | ||||||
|  |     return api.GetList(query); | ||||||
|  |   }; | ||||||
|  |   const editRequest = async (ctx: { form: HeaderGroupRecord; row: HeaderGroupRecord }) => { | ||||||
|  |     const { form, row } = ctx; | ||||||
|  |     form.id = row.id; | ||||||
|  |     return api.UpdateObj(form); | ||||||
|  |   }; | ||||||
|  |   const delRequest = async (ctx: { row: HeaderGroupRecord }) => { | ||||||
|  |     const { row } = ctx; | ||||||
|  |     return api.DelObj(row.id); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const addRequest = async (ctx: { form: HeaderGroupRecord }) => { | ||||||
|  |     const { form } = ctx; | ||||||
|  |     return api.AddObj(form); | ||||||
|  |   }; | ||||||
|  |   return { | ||||||
|  |     crudOptions: { | ||||||
|  |       container: { | ||||||
|  |         // is: 'fs-layout-card' | ||||||
|  |       }, | ||||||
|  |       request: { | ||||||
|  |         pageRequest, | ||||||
|  |         addRequest, | ||||||
|  |         editRequest, | ||||||
|  |         delRequest | ||||||
|  |       }, | ||||||
|  |       form: { | ||||||
|  |         layout: 'flex', | ||||||
|  |         labelWidth: '100px' // 表单label宽度 | ||||||
|  |       }, | ||||||
|  |       table: { size: 'small' }, | ||||||
|  |       columns: { | ||||||
|  |         id: { | ||||||
|  |           title: 'ID', | ||||||
|  |           key: 'id', | ||||||
|  |           type: 'number', | ||||||
|  |           column: { | ||||||
|  |             width: 50 | ||||||
|  |           }, | ||||||
|  |           form: { | ||||||
|  |             show: false | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         user: { | ||||||
|  |           title: '用户信息', | ||||||
|  |           children: { | ||||||
|  |             name: { | ||||||
|  |               title: '姓名', | ||||||
|  |               type: 'text' | ||||||
|  |             }, | ||||||
|  |             age: { | ||||||
|  |               title: '年龄', | ||||||
|  |               type: 'number' | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         address: { | ||||||
|  |           title: '地址', | ||||||
|  |           children: { | ||||||
|  |             area: { | ||||||
|  |               title: '地区', | ||||||
|  |               children: { | ||||||
|  |                 province: { | ||||||
|  |                   title: '省', | ||||||
|  |                   type: 'text', | ||||||
|  |                   search: { show: true } | ||||||
|  |                 }, | ||||||
|  |                 city: { | ||||||
|  |                   title: '市', | ||||||
|  |                   search: { show: true }, | ||||||
|  |                   type: 'text' | ||||||
|  |                 }, | ||||||
|  |                 county: { | ||||||
|  |                   title: '区', | ||||||
|  |                   search: { show: true }, | ||||||
|  |                   type: 'text' | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             }, | ||||||
|  |             street: { | ||||||
|  |               title: '街道', | ||||||
|  |               type: 'text' | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								src/views/crud/header_group/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/views/crud/header_group/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="h-full fs-page-header-group"> | ||||||
|  |     <fs-crud ref="crudRef" v-bind="crudBinding" /> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import { defineComponent, onMounted } from 'vue'; | ||||||
|  | import { useFs } from '@fast-crud/fast-crud'; | ||||||
|  | import createCrudOptions from './crud'; | ||||||
|  |  | ||||||
|  | export default defineComponent({ | ||||||
|  |   name: 'CrudHeaderGroup', | ||||||
|  |   setup() { | ||||||
|  |     const { crudRef, crudBinding, crudExpose } = useFs({ createCrudOptions }); | ||||||
|  |  | ||||||
|  |     // 页面打开后获取列表数据 | ||||||
|  |     onMounted(() => { | ||||||
|  |       crudExpose.doRefresh(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |       crudBinding, | ||||||
|  |       crudRef | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | <style lang="scss"> | ||||||
|  | .fs-page-header-group { | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										13
									
								
								src/views/crud/source/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/views/crud/source/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="h-full"> | ||||||
|  |     <iframe class="wh-full" :src="src"></iframe> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { ref } from 'vue'; | ||||||
|  |  | ||||||
|  | const src = ref('https://github.com/fast-crud/fast-crud'); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style scoped></style> | ||||||
| @@ -16,6 +16,10 @@ export const views: Record< | |||||||
|   component_button: () => import('./component/button/index.vue'), |   component_button: () => import('./component/button/index.vue'), | ||||||
|   component_card: () => import('./component/card/index.vue'), |   component_card: () => import('./component/card/index.vue'), | ||||||
|   component_table: () => import('./component/table/index.vue'), |   component_table: () => import('./component/table/index.vue'), | ||||||
|  |   crud_demo: () => import('./crud/demo/index.vue'), | ||||||
|  |   crud_doc: () => import('./crud/doc/index.vue'), | ||||||
|  |   crud_header_group: () => import('./crud/header_group/index.vue'), | ||||||
|  |   crud_source: () => import('./crud/source/index.vue'), | ||||||
|   dashboard_analysis: () => import('./dashboard/analysis/index.vue'), |   dashboard_analysis: () => import('./dashboard/analysis/index.vue'), | ||||||
|   dashboard_workbench: () => import('./dashboard/workbench/index.vue'), |   dashboard_workbench: () => import('./dashboard/workbench/index.vue'), | ||||||
|   document_naive: () => import('./document/naive/index.vue'), |   document_naive: () => import('./document/naive/index.vue'), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user