<template>
<div class="crud-page">
<FilterForm
class="common-search"
:model-value="searchModel"
:config="searchFormConfig"
:loading="gridOptions.loading"
@query="handleQuery"
@reset="handleReset"
/>
<div class="common-table">
<div class="toolbar">
<FkButton type="primary" @click="handleCreate">新建</FkButton>
</div>
<VxeGrid ref="gridRef" v-bind="gridOptions">
<template #action="{ row }">
<FkButton type="text" @click="handleEdit(row)">编辑</FkButton>
<FkButton type="text" status="danger" @click="handleDelete(row)">删除</FkButton>
</template>
</VxeGrid>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, reactive, useTemplateRef } from 'vue';
import { FilterForm, VxeGrid, createDynamicFormPop, pop } from '@erp/biz';
import { type CrudItem, type QueryParams, createItem, deleteItem, updateItem } from './mock-api';
import { getGridOptions, getSearchForm } from './crud-page';
import type { DynamicFormI, VxeGridInstance } from '@erp/biz';
const searchFormConfig = getSearchForm();
const searchModel = reactive<QueryParams>({
keyword: '',
status: '',
});
const gridRef = useTemplateRef<VxeGridInstance>('gridRef');
const gridOptions = getGridOptions(searchModel);
function reloadGrid() {
gridRef.value?.commitProxy('reload');
}
function handleQuery() {
reloadGrid();
}
function handleReset() {
searchModel.keyword = '';
searchModel.status = '';
reloadGrid();
}
function createFormConfig(mode: 'create' | 'edit') {
return {
title: mode === 'create' ? '新建' : '编辑',
showSide: false,
cols: 2,
groups: [
{
label: '基础信息',
fields: [
{
key: 'name',
label: '名称',
type: 'text',
required: true,
rules: { required: true, message: '请输入名称', type: 'string' },
},
{
key: 'code',
label: '编码',
type: 'text',
required: true,
rules: { required: true, message: '请输入编码', type: 'string' },
},
{
key: 'status',
label: '状态',
type: 'select',
required: true,
options: [
{ label: '启用', value: '启用' },
{ label: '禁用', value: '禁用' },
],
},
{
key: 'remark',
label: '备注',
type: 'textarea',
},
],
},
],
} as DynamicFormI;
}
async function handleCreate() {
const result = await createDynamicFormPop<Omit<CrudItem, 'id'>>({
vm: { name: '', code: '', status: '启用', remark: '' },
config: createFormConfig('create'),
modalConfig: { title: '新建', width: 620, id: 'crud-template-create' },
});
if (!result) return;
await createItem(result);
pop.success('创建成功');
reloadGrid();
}
async function handleEdit(row: CrudItem) {
const result = await createDynamicFormPop<Omit<CrudItem, 'id'>>({
vm: {
name: row.name,
code: row.code,
status: row.status,
remark: row.remark || '',
},
config: createFormConfig('edit'),
modalConfig: { title: '编辑', width: 620, id: `crud-template-edit-${row.id}` },
});
if (!result) return;
await updateItem(row.id, result);
pop.success('保存成功');
reloadGrid();
}
async function handleDelete(row: CrudItem) {
await pop.confirm(`确定删除「${row.name}」吗?`, {
title: '删除确认',
okButtonProps: { status: 'danger' },
});
const loading = pop.loading('正在删除...');
await deleteItem(row.id);
loading.close();
pop.success('删除成功');
reloadGrid();
}
onMounted(() => {
reloadGrid();
});
</script>
<style scoped lang="scss">
.crud-page {
height: 800px;
width: 100%;
padding: 16px 16px 0;
display: flex;
flex-direction: column;
.common-search {
width: 100%;
padding: 12px;
border-radius: 4px;
background-color: var(--color-fill-1);
}
.common-table {
flex: 1;
width: 100%;
margin-top: 12px;
}
.toolbar {
margin-bottom: 10px;
}
}
</style>import { reactive } from 'vue';
import { mergeGridProps } from '@erp/biz';
import { queryList } from './mock-api';
import type { SearchFormI, VxeGridProps } from '@erp/biz';
import type { CrudItem, QueryParams } from './mock-api';
export function getSearchForm() {
const config: SearchFormI = {
gridProps: {
cols: { xxl: 4, xl: 4, lg: 3, md: 2, sm: 2, xs: 2 },
colGap: 14,
rowGap: 10,
},
labelLayout: 'inner',
fields: [
{ key: 'keyword', label: '关键字', type: 'text' },
{
key: 'status',
label: '状态',
type: 'select',
options: [
{ label: '全部', value: '' },
{ label: '启用', value: '启用' },
{ label: '禁用', value: '禁用' },
],
},
],
};
return config;
}
export function getGridOptions(model: QueryParams) {
const gridOptions: VxeGridProps<CrudItem> = reactive(
mergeGridProps({
id: 'crud-template-grid',
height: 360,
columns: [
{ type: 'seq', width: 60, fixed: 'left', title: '#' },
{ field: 'name', title: '名称', minWidth: 160 },
{ field: 'code', title: '编码', minWidth: 160 },
{ field: 'status', title: '状态', width: 100 },
{ field: 'remark', title: '备注', minWidth: 220 },
{
title: '操作',
width: 180,
fixed: 'right',
slots: { default: 'action' },
},
],
proxyConfig: {
response: { result: 'list', total: 'total' },
ajax: {
query({ page }) {
return queryList({
...model,
page: page.current,
pageSize: page.pageSize,
});
},
},
},
}),
);
return gridOptions;
}export type Status = '启用' | '禁用';
export interface CrudItem {
id: number;
name: string;
code: string;
status: Status;
remark?: string;
}
export interface QueryParams {
keyword?: string;
status?: '' | Status;
page?: number;
pageSize?: number;
}
const db: CrudItem[] = [
{ id: 1, name: '订单模板', code: 'order_tpl', status: '启用', remark: '订单业务模板' },
{ id: 2, name: '商品模板', code: 'goods_tpl', status: '禁用', remark: '商品业务模板' },
{ id: 3, name: '客户模板', code: 'customer_tpl', status: '启用', remark: '客户业务模板' },
];
function wait(ms = 300) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export async function queryList(params: QueryParams) {
await wait(300);
const keyword = (params.keyword || '').trim();
const status = params.status || '';
const page = params.page || 1;
const pageSize = params.pageSize || 10;
const filtered = db.filter(item => {
const hitKeyword = !keyword || item.name.includes(keyword) || item.code.includes(keyword);
const hitStatus = !status || item.status === status;
return hitKeyword && hitStatus;
});
const start = (page - 1) * pageSize;
return {
list: filtered.slice(start, start + pageSize),
total: filtered.length,
};
}
export async function createItem(payload: Omit<CrudItem, 'id'>) {
await wait(300);
const id = Math.max(...db.map(row => row.id), 0) + 1;
const row: CrudItem = { id, ...payload };
db.unshift(row);
return row;
}
export async function updateItem(id: number, payload: Omit<CrudItem, 'id'>) {
await wait(300);
const index = db.findIndex(row => row.id === id);
if (index < 0) {
throw new Error('数据不存在');
}
db[index] = { id, ...payload };
return db[index];
}
export async function deleteItem(id: number) {
await wait(300);
const index = db.findIndex(row => row.id === id);
if (index >= 0) {
db.splice(index, 1);
}
return true;
}