Merge pull request #227 from fast-crud/for-pr

feat(projects): integration fast-crud
This commit is contained in:
Soybean 2023-05-22 22:12:05 +08:00 committed by GitHub
commit 032dbc6815
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 3658 additions and 449 deletions

295
mock/api/crud/base.ts Normal file
View 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
View File

@ -0,0 +1,5 @@
import demo from './modules/demo';
import headerGroup from './modules/header-group';
const crudApis = [...demo, ...headerGroup];
export default crudApis;

View 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;

View 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;

View File

@ -1,5 +1,6 @@
import auth from './auth';
import route from './route';
import management from './management';
import crud from './crud';
export default [...auth, ...route, ...management];
export default [...auth, ...route, ...management, ...crud];

View File

@ -56,6 +56,11 @@
"prepare": "soy init-git-hooks"
},
"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/g2": "^4.2.10",
"@better-scroll/core": "^2.5.1",

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,9 @@
class="h-full"
>
<naive-provider>
<router-view />
<fs-ui-context>
<router-view />
</fs-ui-context>
</naive-provider>
</n-config-provider>
</template>

View File

@ -3,7 +3,7 @@ import App from './App.vue';
import AppLoading from './components/common/app-loading.vue';
import { setupDirectives } from './directives';
import { setupRouter } from './router';
import { setupAssets } from './plugins';
import { setupAssets, setupFastCrud } from './plugins';
import { setupStore } from './store';
import { setupI18n } from './locales';
@ -29,6 +29,8 @@ async function setupApp() {
setupI18n(app);
setupFastCrud(app);
// mount app
app.mount('#app');
}

View File

@ -0,0 +1,11 @@
html:root {
--baseColor: #fff;
}
/* 深色模式 */
html.dark:root {
--baseColor: #000;
}
.fs-container {
background-color: var(--baseColor);
}

View 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组件包裹RouterViewfs-crud拥有messagenotificationdialog的能力
* @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);
}

View 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);
}

View File

@ -1,3 +1,5 @@
import { setupFastCrud } from '@/plugins/fast-crud';
import setupAssets from './assets';
export { setupFastCrud };
export { setupAssets };

View 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;

View File

@ -30,6 +30,12 @@ declare namespace PageRoute {
| 'component_button'
| 'component_card'
| 'component_table'
| 'crud'
| 'crud_demo'
| 'crud_doc'
| 'crud_header'
| 'crud_header_group'
| 'crud_source'
| 'dashboard'
| 'dashboard_analysis'
| 'dashboard_workbench'
@ -89,6 +95,10 @@ declare namespace PageRoute {
| 'component_button'
| 'component_card'
| 'component_table'
| 'crud_demo'
| 'crud_doc'
| 'crud_header_group'
| 'crud_source'
| 'dashboard_analysis'
| 'dashboard_workbench'
| 'document_naive'

View 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);
}

View 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'
}
}
}
}
}
}
};
}

View 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>

View 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>

View 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);
}

View 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'
}
}
}
}
}
};
}

View 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>

View 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>

View File

@ -16,6 +16,10 @@ export const views: Record<
component_button: () => import('./component/button/index.vue'),
component_card: () => import('./component/card/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_workbench: () => import('./dashboard/workbench/index.vue'),
document_naive: () => import('./document/naive/index.vue'),