---
description: cl-crud 组件示例
globs: *.tsx, *.ts, *.vue
---
## 完整示例 示例

```vue
<template>
	<div class="scope">
		<div class="h">
			<el-tag size="small" effect="dark" disable-transitions>all</el-tag>
			<span>完整示例</span>
		</div>

		<div class="c">
			<el-button @click="open">预览</el-button>
			<demo-code :files="['crud/all.vue']" />

			<cl-dialog v-model="visible" title="完整示例" width="80%">
				<cl-crud ref="Crud">
					<cl-row>
						<!-- 刷新按钮 -->
						<cl-refresh-btn />

						<!-- 新增按钮 -->
						<cl-add-btn />

						<!-- 批量删除按钮 -->
						<cl-multi-delete-btn />

						<!-- 筛选 -->
						<cl-filter label="状态筛选">
							<!-- 配置prop，选择后会自动过滤列表 -->
							<cl-select :options="options.status" prop="status" :width="120" />
						</cl-filter>

						<!-- 字典 -->
						<cl-filter label="工作（字典）">
							<cl-select
								tree
								:options="dict.get('occupation')"
								prop="occupation"
								:width="140"
								check-strictly
							/>
						</cl-filter>

						<cl-flex1 />

						<!-- 导入 -->
						<cl-import-btn template="/用户导入模版.xlsx" />

						<!-- 导出 -->
						<cl-export-btn :columns="Table?.columns" />

						<!-- 自定义列 -->
						<cl-column-custom
							:ref="setRefs('columnCustom')"
							:columns="Table?.columns"
						/>

						<!-- 关键字搜索 -->
						<cl-search-key placeholder="搜索姓名、手机号" :width="250" />

						<!-- 高级搜索按钮 -->
						<cl-adv-btn />
					</cl-row>

					<cl-row>
						<!-- 表格 -->
						<cl-table
							ref="Table"
							show-summary
							:summary-method="onSummaryMethod"
							:auto-height="false"
						>
							<!-- 展开信息 -->
							<template #column-detail="{ scope }">
								<div style="padding: 0 10px">
									<el-descriptions border :column="3">
										<el-descriptions-item label="ID">
											{{ scope.row.id }}
										</el-descriptions-item>

										<el-descriptions-item label="姓名">
											{{ scope.row.name }}
										</el-descriptions-item>

										<el-descriptions-item label="存款">
											{{ scope.row.wages }}
										</el-descriptions-item>

										<el-descriptions-item label="出生年月">
											{{ scope.row.createTime }}
										</el-descriptions-item>
									</el-descriptions>
								</div>
							</template>

							<!-- 自定义列 -->
							<template #column-wages="{ scope }">
								<span>{{ scope.row.wages }}🤑</span>
							</template>
						</cl-table>
					</cl-row>

					<cl-row>
						<cl-flex1 />

						<!-- 分页 -->
						<cl-pagination />
					</cl-row>

					<!-- 新增、编辑 -->
					<cl-upsert ref="Upsert" />

					<!-- 高级搜索 -->
					<cl-adv-search ref="AdvSearch" />
				</cl-crud>
			</cl-dialog>
		</div>

		<div class="f">
			<span class="date">2024-01-01</span>
		</div>
	</div>
</template>

<script lang="tsx" setup>
defineOptions({
	name: 'demo-crud'
});

import { useCrud, useUpsert, useTable, useAdvSearch, useSearch } from '@cool-vue/crud';
import { useDict } from '/$/dict';
import { reactive, ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useCool } from '/@/cool';

// 基础
const { service, refs, setRefs } = useCool();

// 字典
const { dict } = useDict();

// 选项，统一命名options，存放所有的下拉等其他选项列表数据
const options = reactive({
	status: [
		{
			label: '启用',
			value: 1
		},
		{
			label: '禁用',
			type: 'danger',
			value: 0
		}
	]
});

// 合计数据
const subData = reactive({
	wages: 0
});

// crud
const Crud = useCrud(
	{
		// 绑定的服务，如：service.demo.goods、service.base.sys.user
		service: 'test',

		// 刷新事件
		async onRefresh(params, { next }) {
			const res = await next(params);
			Object.assign(subData, res.subData);
		}
	},
	app => {
		// Crud 加载完，默认刷新一次
		app.refresh({
			size: 10
			// status: 1 // 带额外参数的请求
		});
	}
);

// 刷新列表，统一调用这个方法去刷新
function refresh(params?: any) {
	Crud.value?.refresh(params);
}

// 新增、编辑
// 插入类型 <Eps.UserInfoEntity>，prop 和 data 会有提示
const Upsert = useUpsert<Eps.UserInfoEntity>({
	items: [
		// 分组
		{
			type: 'tabs',
			props: {
				type: 'card',
				labels: [
					{
						label: '基础信息',
						value: 'base'
					},
					{
						label: '其他配置',
						value: 'other'
					}
				]
			}
		},
		{
			label: '头像',
			prop: 'avatarUrl',
			group: 'base',
			component: {
				name: 'cl-upload'
			}
		},
		{
			label: '账号',
			group: 'base',
			prop: 'account',
			component: {
				name: 'el-input'
			}
		},
		// 动态配置，新增显示、编辑隐藏
		() => {
			return () => {
				return {
					label: '密码',
					group: 'base',
					prop: 'password',
					hidden: Upsert.value?.mode == 'update', // 通过 mode 参数判断
					component: {
						name: 'el-input',
						props: {
							type: 'password'
						}
					}
				};
			};
		},
		{
			group: 'base',
			prop: 'user',
			component: {
				name: 'cl-form-card',
				props: {
					label: '用户信息（多层级展示）'
				}
			},
			children: [
				{
					label: '姓名',
					prop: 'name',
					required: true,
					component: {
						name: 'el-input'
					}
				},
				{
					label: '存款',
					prop: 'wages',
					component: {
						name: 'el-input-number'
					}
				}
			]
		},
		{
			group: 'base',
			prop: 'contact',
			component: {
				name: 'cl-form-card',
				props: {
					label: '联系信息',
					expand: false
				}
			},
			children: [
				{
					label: '手机号',
					prop: 'phone',
					component: {
						name: 'el-input'
					}
				},
				{
					label: '省市区',
					prop: 'pca',
					group: 'base',
					component: {
						name: 'cl-distpicker'
					}
				}
			]
		},
		{
			group: 'other',
			label: '工作',
			prop: 'occupation',
			component: {
				name: 'el-tree-select',
				props: {
					data: dict.get('occupation'),
					checkStrictly: true
				}
			}
		},
		{
			group: 'other',
			label: '身份证照片',
			prop: 'idCardPic',
			component: {
				name: 'cl-upload',
				props: {
					isSpace: true,
					size: [200, 300]
				}
			}
		}
	],

	// 详情钩子
	onInfo(data, { next, done }) {
		// 继续请求 info 接口，可以带其他自定义参数
		// next({
		// 	id: data.id,
		//	status: 1
		// });

		// 使用其他接口
		// service.demo.goods.info({ id: data.id }).then((res) => {
		// 	done(res);
		// });

		// 直接取列表的数据返回
		done(data);
	},

	// 提交钩子
	onSubmit(data, { next, close, done }) {
		console.log('onSubmit', data);
		// 继续请求 update/add 接口
		next(data);

		// 自定义接口
		// service.demo.goods
		// 	.update(data)
		// 	.then(() => {
		// 		ElMessage.success("保存成功");

		// 		// 操作完，刷新列表
		// 		refresh();

		// 		// 关闭窗口
		// 		close();
		// 	})
		// 	.catch((err) => {
		// 		ElMessage.error(err.message);

		// 		// 关闭加载状态
		// 		done();
		// 	});
	},

	// 打开后，数据加载完，onInfo 之后
	onOpened(data) {
		if (Upsert.value?.mode != 'info') {
			ElMessage.info('编辑中');
		}
	},

	// 关闭钩子
	onClose(action, done) {
		if (Upsert.value?.mode == 'update') {
			if (action == 'close') {
				return ElMessageBox.confirm('还没填完，确定关闭不？', '提示', {
					type: 'warning'
				})
					.then(() => {
						done();
						ElMessage.info('好吧');
					})
					.catch(() => {
						ElMessage.success('请继续编辑');
					});
			}
		}

		done();
	}
});

// 表格
const Table = useTable({
	columns: [
		{
			type: 'selection',
			width: 60
		},
		// 展开列
		{
			label: '展开',
			type: 'expand',
			prop: 'detail',
			width: 60
		},
		{
			label: '头像',
			prop: 'avatar',
			width: 100,
			component: {
				name: 'cl-image',
				props: {
					size: 40
				}
			}
		},
		{
			label: '姓名',
			prop: 'name',
			minWidth: 120
		},
		{
			label: '手机号',
			prop: 'phone',
			minWidth: 140,

			// 带搜索组件
			search: {
				component: {
					name: 'el-input',
					props: {
						placeholder: '搜索手机号'
					}
				}
			}
		},
		{
			label: '账号',
			prop: 'account',
			minWidth: 150
		},
		{
			label: '存款(元)',
			prop: 'wages',
			minWidth: 150,
			sortable: 'desc' // 默认倒序
		},
		{
			label: '工作',
			prop: 'occupation',
			dict: dict.get('occupation'),
			dictColor: true,
			minWidth: 150,
			dictAllLevels: true, // 显示所有等级

			// 带搜索组件
			search: {
				component: {
					name: 'cl-select',
					props: {
						options: dict.get('occupation')
					}
				}
			}
		},
		{
			label: '状态',
			orderNum: 2,
			prop: 'status',
			minWidth: 100,
			component: {
				name: 'cl-switch'
			}
		},
		{
			label: '出生年月',
			orderNum: 1,
			minWidth: 165,
			prop: 'createTime',
			sortable: 'custom',
			search: {
				component: {
					name: 'cl-date-picker',
					props: {
						type: 'date',
						valueFormat: 'YYYY-MM-DD',
						placeholder: '搜索日期'
					}
				}
			}
		},
		{
			type: 'op',
			width: 340,
			// 静态配置按钮
			// buttons: ["info", "edit", "delete"],
			// 动态配置按钮
			buttons({ scope }) {
				return [
					'info',
					'edit',
					'delete',
					{
						label: '自定义',
						onClick() {
							ElMessage.info(`他是：${scope.row.name}`);
						}
					}
				];
			}
		}
	]
});

// 合计
function onSummaryMethod() {
	// 添加自定义列组件后
	return ['合计', '', ...refs.columnCustom.summary(subData)];
}

// 高级搜索
const AdvSearch = useAdvSearch({
	items: [
		{
			label: '姓名',
			prop: 'name',
			component: {
				name: 'el-input',
				props: {
					clearable: true
				}
			}
		},
		{
			label: '手机号',
			prop: 'phone',
			component: {
				name: 'el-input',
				props: {
					clearable: true
				}
			}
		},
		{
			label: '工作',
			prop: 'occupation',
			hook: {
				bind: 'string'
			},
			component: {
				name: 'el-select',
				options: dict.get('occupation')
			}
		}
	]
});

// 搜索
const Search = useSearch({
	items: [
		{
			label: '姓名',
			prop: 'name',
			component: {
				name: 'el-input',
				props: {
					clearable: true
				}
			}
		}
	]
});

const visible = ref(false);

function open() {
	visible.value = true;
}
</script>

```

## 起步 示例

```vue
<template>
	<div class="scope">
		<div class="h">
			<el-tag size="small" effect="dark" disable-transitions>base</el-tag>
			<span>起步</span>
		</div>

		<div class="c">
			<el-button @click="open">预览</el-button>
			<demo-code :files="['crud/base.vue']" />

			<!-- 自定义表格组件 -->
			<cl-dialog v-model="visible" title="起步" width="80%">
				<cl-crud ref="Crud">
					<cl-row>
						<cl-refresh-btn />
						<cl-add-btn />
						<cl-multi-delete-btn />

						<cl-flex1 />

						<cl-search-key />
					</cl-row>

					<cl-row>
						<cl-table ref="Table" />
					</cl-row>

					<cl-row>
						<cl-flex1 />
						<cl-pagination />
					</cl-row>

					<!-- 新增、编辑 -->
					<cl-upsert ref="Upsert" />
				</cl-crud>
			</cl-dialog>
		</div>

		<div class="f">
			<span class="date">2024-01-01</span>
		</div>
	</div>
</template>

<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { useCool } from '/@/cool';

const { service } = useCool();
const { dict } = useDict();

// cl-crud 配置
const Crud = useCrud(
	{
		// test 为测试数据模式，详细说明移步到 service 例子
		service: 'test'
	},
	app => {
		//【很重要】首次请求，数据一并添加到请求参数中
		app.refresh({
			size: 10,
			status: 1
		});
	}
);

// cl-table 配置
const Table = useTable({
	autoHeight: false,
	contextMenu: ['refresh'],

	columns: [
		{
			type: 'selection'
		},
		{
			label: '姓名',
			prop: 'name',
			minWidth: 140
		},
		{
			label: '手机号',
			prop: 'phone',
			minWidth: 140
		},
		{
			label: '工作',
			prop: 'occupation',
			dict: dict.get('occupation'),
			minWidth: 140
		},
		{
			label: '创建时间',
			prop: 'createTime',
			minWidth: 170,
			sortable: 'desc'
		},
		{
			type: 'op',
			buttons: ['edit', 'delete']
		}
	]
});

// cl-upsert 配置
const Upsert = useUpsert({
	items: [
		{
			label: '姓名',
			prop: 'name',
			component: {
				name: 'el-input'
			}
		},
		{
			label: '手机号',
			prop: 'phone',
			component: {
				name: 'el-input'
			}
		},
		{
			label: '工作',
			prop: 'occupation',
			component: {
				name: 'cl-select',
				props: {
					tree: true,
					checkStrictly: true,
					options: dict.get('occupation')
				}
			}
		}
	]
});

const visible = ref(false);

function open() {
	visible.value = true;
}
</script>

```

## 修改文案 / 接口 示例

```vue
<template>
	<div class="scope">
		<div class="h">
			<el-tag size="small" effect="dark" disable-transitions>dict</el-tag>
			<span>修改文案 / 接口</span>
		</div>

		<div class="c">
			<el-button @click="open">预览</el-button>
			<demo-code :files="['crud/dict.vue']" />

			<!-- 自定义表格组件 -->
			<cl-dialog v-model="visible" title="修改文案 / 接口" width="80%">
				<cl-crud ref="Crud">
					<cl-row>
						<cl-refresh-btn />
						<cl-add-btn />
						<cl-multi-delete-btn />

						<cl-flex1 />

						<cl-search-key />
					</cl-row>

					<cl-row>
						<cl-table ref="Table" />
					</cl-row>

					<cl-row>
						<cl-flex1 />
						<cl-pagination />
					</cl-row>

					<!-- 新增、编辑 -->
					<cl-upsert ref="Upsert" />
				</cl-crud>
			</cl-dialog>
		</div>

		<div class="f">
			<span class="date">2024-01-01</span>
		</div>
	</div>
</template>

<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';

const { dict } = useDict();

// cl-crud 配置
const Crud = useCrud(
	{
		//【很重要】配置 service，如：service.base.sys.user
		service: 'test',

		//【很重要】字典配置，文案和请求方法等
		dict: {
			// 修改请求
			// 比如说默认列表请求的是 page 接口，可以修改成 getUserList 等等，这取决于后端有没有这个接口。
			api: {
				list: 'list',
				add: 'add',
				update: 'update',
				delete: 'delete',
				info: 'info',
				page: 'page'
			},

			// 修改文案
			label: {
				op: '操作',
				add: '添加',
				delete: '移除',
				multiDelete: '批量移除',
				update: '修改',
				refresh: '刷新',
				info: '详情'
			}
		}
	},
	app => {
		app.refresh();
	}
);

// cl-table 配置
const Table = useTable({
	autoHeight: false,
	contextMenu: ['refresh'],

	columns: [
		{
			type: 'selection'
		},
		{
			label: '姓名',
			prop: 'name',
			minWidth: 140
		},
		{
			label: '手机号',
			prop: 'phone',
			minWidth: 140
		},
		{
			label: '工作',
			prop: 'occupation',
			dict: dict.get('occupation'),
			minWidth: 140
		},
		{
			label: '创建时间',
			prop: 'createTime',
			minWidth: 170,
			sortable: 'desc'
		},
		{
			type: 'op',
			buttons: ['edit', 'delete']
		}
	]
});

// cl-upsert 配置
const Upsert = useUpsert({
	items: [
		{
			label: '姓名',
			prop: 'name',
			component: {
				name: 'el-input'
			}
		},
		{
			label: '手机号',
			prop: 'phone',
			component: {
				name: 'el-input'
			}
		},
		{
			label: '工作',
			prop: 'occupation',
			component: {
				name: 'cl-select',
				props: {
					tree: true,
					checkStrictly: true,
					options: dict.get('occupation')
				}
			}
		}
	]
});

const visible = ref(false);

function open() {
	visible.value = true;
}
</script>

```

## 事件监听 示例

```vue
<template>
	<div class="scope">
		<div class="h">
			<el-tag size="small" effect="dark" disable-transitions>event</el-tag>
			<span>事件监听</span>
		</div>

		<div class="c">
			<el-button @click="open">预览</el-button>
			<demo-code :files="['crud/event.vue']" />

			<!-- 自定义表格组件 -->
			<cl-dialog v-model="visible" title="事件监听" width="80%">
				<cl-crud ref="Crud">
					<cl-row>
						<cl-refresh-btn />
						<cl-add-btn />
						<cl-multi-delete-btn />

						<cl-flex1 />

						<cl-search-key />
					</cl-row>

					<cl-row>
						<cl-table ref="Table">
							<!-- 自定义按钮 -->
							<template #slot-btn="{ scope }">
								<el-button @click="onEvent(scope.row)">自定义事件</el-button>
							</template>
						</cl-table>
					</cl-row>

					<cl-row>
						<cl-flex1 />
						<cl-pagination />
					</cl-row>

					<!-- 新增、编辑 -->
					<cl-upsert ref="Upsert" />
				</cl-crud>
			</cl-dialog>
		</div>

		<div class="f">
			<span class="date">2024-01-01</span>
		</div>
	</div>
</template>

<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useDict } from '/$/dict';
import { useCool } from '/@/cool';
import { ElMessage } from 'element-plus';

const { service } = useCool();
const { dict } = useDict();

// cl-crud 配置
const Crud = useCrud(
	{
		// 配置 service
		service: 'test',

		//【很重要】监听刷新事件，每次调用 Crud.value.refresh() 会触发
		onRefresh(params, { next }) {
			// 默认使用 next(params)，也可以自己对数据进行处理
			next({
				...params,
				status: 1
			});
		},

		// 监听删除事件，点击删除按钮触发
		onDelete(selection, { next }) {
			// 传入 ids，批量删除多个数据
			next({
				ids: selection.map(e => e.id)
			});
		}
	},
	app => {
		app.refresh();
	}
);

// cl-table 配置
const Table = useTable({
	autoHeight: false,
	contextMenu: ['refresh'],

	columns: [
		{
			type: 'selection'
		},
		{
			label: '姓名',
			prop: 'name',
			minWidth: 140
		},
		{
			label: '手机号',
			prop: 'phone',
			minWidth: 140
		},
		{
			label: '工作',
			prop: 'occupation',
			dict: dict.get('occupation'),
			minWidth: 140
		},
		{
			label: '创建时间',
			prop: 'createTime',
			minWidth: 170,
			sortable: 'desc'
		},
		{
			type: 'op',
			width: 300,
			buttons: ['edit', 'delete', 'slot-btn']
		}
	]
});

// cl-upsert 配置
const Upsert = useUpsert({
	items: [
		{
			label: '姓名',
			prop: 'name',
			component: {
				name: 'el-input'
			}
		},
		{
			label: '手机号',
			prop: 'phone',
			component: {
				name: 'el-input'
			}
		},
		{
			label: '工作',
			prop: 'occupation',
			component: {
				name: 'cl-select',
				props: {
					tree: true,
					checkStrictly: true,
					options: dict.get('occupation')
				}
			}
		}
	]
});

// 调用 Crud 方法
function onEvent(row: any) {
	ElMessage.info('自定义打开新增');

	// 打开新增表单
	Crud.value?.rowAdd();

	// 打开编辑表单
	// Crud.value?.rowEdit(row);

	// 打开删除提示框
	// Crud.value?.rowDelete(row);

	// 获取已请求的参数
	// Crud.value?.getParams();
}

const visible = ref(false);

function open() {
	visible.value = true;
}
</script>

```

## 选择表格 示例

```vue
<template>
	<div class="scope">
		<div class="h">
			<el-tag size="small" effect="dark" disable-transitions>select-table</el-tag>
			<span>选择表格</span>
		</div>

		<div class="c">
			<el-button @click="open">预览</el-button>
			<demo-code :files="['crud/select-table.vue']" />

			<!-- 自定义表格组件 -->
			<cl-form ref="Form" />
		</div>

		<div class="f">
			<span class="date">2025-02-07</span>
		</div>
	</div>
</template>

<script setup lang="ts">
import { useForm } from '@cool-vue/crud';
import { useCool } from '/@/cool';
import UserSelect from '/$/user/components/user-select.vue';

const { service } = useCool();
const Form = useForm();

const columns = [
	{
		label: '头像',
		prop: 'headImg',
		component: {
			name: 'cl-avatar'
		}
	},
	{
		label: '昵称',
		prop: 'nickName'
	},
	{
		label: '创建时间',
		prop: 'createTime'
	}
];

function open() {
	Form.value?.open({
		width: '800px',
		title: '选择表格',
		items: [
			{
				label: '多选 - default',
				prop: 'a',
				value: [],
				component: {
					vm: UserSelect,
					props: {
						multiple: true
					}
				},
				span: 12
			},
			{
				label: '单选 - default',
				prop: 'b',
				component: {
					name: 'cl-select-table',
					props: {
						pickerType: 'default',
						multiple: false,
						columns,
						service: service.base.sys.user
					}
				},
				span: 12
			},
			{
				label: '多选 - text',
				prop: 'c',
				value: [],
				component: {
					name: 'cl-select-table',
					props: {
						pickerType: 'text',
						multiple: true,
						columns,
						service: service.base.sys.user
					}
				},
				span: 12
			},
			{
				label: '单选 - text',
				prop: 'd',
				component: {
					name: 'cl-select-table',
					props: {
						pickerType: 'text',
						multiple: false,
						columns,
						service: service.base.sys.user
					}
				},
				span: 12
			},
			{
				label: '多选 - table',
				prop: 'e',
				value: [],
				component: {
					name: 'cl-select-table',
					props: {
						pickerType: 'table',
						multiple: true,
						columns,
						service: service.base.sys.user
					}
				}
			}
		]
	});
}
</script>

```

## Service 配置 示例

```vue
<template>
	<div class="scope">
		<div class="h">
			<el-tag size="small" effect="dark" disable-transitions>service</el-tag>
			<span>Service 配置</span>
		</div>

		<div class="c">
			<el-button @click="open">预览</el-button>
			<demo-code :files="['crud/service.vue']" />

			<!-- 自定义表格组件 -->
			<cl-dialog v-model="visible" title="Service 配置" width="80%">
				<cl-crud ref="Crud">
					<cl-row>
						<cl-refresh-btn />
						<cl-add-btn />
						<cl-multi-delete-btn />

						<cl-flex1 />

						<cl-search-key />
					</cl-row>

					<cl-row>
						<cl-table ref="Table" />
					</cl-row>

					<cl-row>
						<cl-flex1 />
						<cl-pagination />
					</cl-row>

					<!-- 新增、编辑 -->
					<cl-upsert ref="Upsert" />
				</cl-crud>
			</cl-dialog>
		</div>

		<div class="f">
			<span class="date">2024-01-01</span>
		</div>
	</div>
</template>

<script setup lang="ts">
import { useCrud, useTable, useUpsert } from '@cool-vue/crud';
import { ref } from 'vue';
import { useCool } from '/@/cool';
import { useDict } from '/$/dict';

//【很重要】service 是所有请求的集合，是一个对象（刷新页面和保存代码会自动读取后端的所有接口）
const { service, route } = useCool();
console.log('service', service);

const { dict } = useDict();

// cl-crud 配置
const Crud = useCrud(
	{
		//【很重要】配置 service，如：service.base.sys.user
		// 不需要到具体的方法，如：service.base.sys.user.page，这是错误的！

		// 实际用法
		// service: service.base.sys.user,

		// 测试示例
		service: 'test'

		// 自定义配置1，添加本地 service 文件。
		// 【很重要】参考 /src/modules/demo/service/test.ts
		// 【很重要】必须放在目录 modules/*/service/ 下，才会自动注入到 service 中
		// service: service.test

		// 自定义配置2，针对一些特殊场景
		// service: {
		// 	page(params: any) {
		// 		// params 请求参数
		// 		//【很重要】必须返回一个 Promise 格式
		// 		return Promise.resolve({
		// 			list: [],
		// 			pagination: {
		// 				total: 1,
		// 				page: 1,
		// 				size: 20
		// 			}
		// 		});
		// 	}

		// 	// add、delete、update、info、list 也是如此配置
		// }
	},
	app => {
		// 首次调用刷新接口。在弹窗的情况下可以注释，用 Crud.value?.refresh() 方式手动调用
		app.refresh();

		// 带参数
		// app.refresh({
		// 	userId: route.query.id
		// });
	}
);

// cl-table 配置
const Table = useTable({
	autoHeight: false,
	contextMenu: ['refresh'],

	columns: [
		{
			type: 'selection'
		},
		{
			label: '姓名',
			prop: 'name',
			minWidth: 140
		},
		{
			label: '手机号',
			prop: 'phone',
			minWidth: 140
		},
		{
			label: '工作',
			prop: 'occupation',
			dict: dict.get('occupation'),
			minWidth: 140
		},
		{
			label: '创建时间',
			prop: 'createTime',
			minWidth: 170,
			sortable: 'desc'
		},
		{
			type: 'op',
			buttons: ['edit', 'delete']
		}
	]
});

// cl-upsert 配置
const Upsert = useUpsert({
	items: [
		{
			label: '姓名',
			prop: 'name',
			component: {
				name: 'el-input'
			}
		},
		{
			label: '手机号',
			prop: 'phone',
			component: {
				name: 'el-input'
			}
		},
		{
			label: '工作',
			prop: 'occupation',
			component: {
				name: 'cl-select',
				props: {
					tree: true,
					checkStrictly: true,
					options: dict.get('occupation')
				}
			}
		}
	]
});

const visible = ref(false);

function open() {
	visible.value = true;
}
</script>

```

## 选择成员 示例

```vue
<template>
	<div class="scope">
		<div class="h">
			<el-tag size="small" effect="dark" disable-transitions>user-select</el-tag>
			<span>选择成员</span>
		</div>

		<div class="c">
			<el-button @click="open">预览</el-button>
			<demo-code :files="['crud/user-select.vue']" />

			<!-- 自定义表格组件 -->
			<cl-form ref="Form" />
		</div>

		<div class="f">
			<span class="date">2025-02-07</span>
		</div>
	</div>
</template>

<script setup lang="ts">
import { useForm } from '@cool-vue/crud';

const Form = useForm();

function open() {
	Form.value?.open({
		title: '选择成员',
		items: [
			{
				label: '单选',
				prop: 'userId',
				component: {
					name: 'cl-user-select',
					props: {
						multiple: false,
						onChange(val) {
							console.log(val);
						}
					}
				},
				required: true
			},
			{
				label: '多选',
				prop: 'userIds',
				component: {
					name: 'cl-user-select',
					props: {
						multiple: true,
						onChange(val) {
							console.log(val);
						}
					}
				},
				required: true
			}
		]
	});
}
</script>

```