feat: naive system init

This commit is contained in:
xingyu4j
2025-10-16 16:35:02 +08:00
parent bac9b0d747
commit 757eb72018
97 changed files with 13178 additions and 7 deletions

View File

@@ -0,0 +1,355 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { z } from '#/adapter/form';
import { getSimpleDictTypeList } from '#/api/system/dict/type';
// ============================== 字典类型 ==============================
/** 类型新增/修改的表单 */
export function useTypeFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'name',
label: '字典名称',
component: 'Input',
componentProps: {
placeholder: '请输入字典名称',
},
rules: 'required',
},
{
fieldName: 'type',
label: '字典类型',
component: 'Input',
componentProps: (values) => {
return {
placeholder: '请输入字典类型',
disabled: !!values.id,
};
},
rules: 'required',
dependencies: {
triggerFields: [''],
},
},
{
fieldName: 'status',
label: '状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
buttonStyle: 'solid',
optionType: 'button',
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
{
fieldName: 'remark',
label: '备注',
component: 'Input',
componentProps: {
type: 'textarea',
placeholder: '请输入备注',
},
},
];
}
/** 类型列表的搜索表单 */
export function useTypeGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'name',
label: '字典名称',
component: 'Input',
componentProps: {
placeholder: '请输入字典名称',
clearable: true,
},
},
{
fieldName: 'type',
label: '字典类型',
component: 'Input',
componentProps: {
placeholder: '请输入字典类型',
clearable: true,
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
clearable: true,
},
},
];
}
/** 类型列表的字段 */
export function useTypeGridColumns(): VxeTableGridOptions['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'id',
title: '字典编号',
minWidth: 100,
},
{
field: 'name',
title: '字典名称',
minWidth: 200,
},
{
field: 'type',
title: '字典类型',
minWidth: 220,
},
{
field: 'status',
title: '状态',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{
field: 'remark',
title: '备注',
minWidth: 180,
},
{
field: 'createTime',
title: '创建时间',
minWidth: 180,
formatter: 'formatDateTime',
},
{
title: '操作',
minWidth: 140,
fixed: 'right',
slots: { default: 'actions' },
},
];
}
// ============================== 字典数据 ==============================
// TODO @芋艿:后续针对 antd增加
/**
* 颜色选项
*/
const colorOptions = [
{ value: '', label: '无' },
{ value: 'processing', label: '主要' },
{ value: 'success', label: '成功' },
{ value: 'default', label: '默认' },
{ value: 'warning', label: '警告' },
{ value: 'error', label: '危险' },
{ value: 'pink', label: 'pink' },
{ value: 'red', label: 'red' },
{ value: 'orange', label: 'orange' },
{ value: 'green', label: 'green' },
{ value: 'cyan', label: 'cyan' },
{ value: 'blue', label: 'blue' },
{ value: 'purple', label: 'purple' },
];
/** 数据新增/修改的表单 */
export function useDataFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'id',
component: 'Input',
dependencies: {
triggerFields: [''],
show: () => false,
},
},
{
fieldName: 'dictType',
label: '字典类型',
component: 'ApiSelect',
componentProps: (values) => {
return {
api: getSimpleDictTypeList,
placeholder: '请输入字典类型',
labelField: 'name',
valueField: 'type',
disabled: !!values.id,
};
},
rules: 'required',
dependencies: {
triggerFields: [''],
},
},
{
fieldName: 'label',
label: '数据标签',
component: 'Input',
componentProps: {
placeholder: '请输入数据标签',
},
rules: 'required',
},
{
fieldName: 'value',
label: '数据键值',
component: 'Input',
componentProps: {
placeholder: '请输入数据键值',
},
rules: 'required',
},
{
fieldName: 'sort',
label: '显示排序',
component: 'InputNumber',
componentProps: {
placeholder: '请输入显示排序',
},
rules: 'required',
},
{
fieldName: 'status',
label: '状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
buttonStyle: 'solid',
optionType: 'button',
},
rules: z.number().default(CommonStatusEnum.ENABLE),
},
{
fieldName: 'colorType',
label: '颜色类型',
component: 'Select',
componentProps: {
options: colorOptions,
placeholder: '请选择颜色类型',
},
},
{
fieldName: 'cssClass',
label: 'CSS Class',
component: 'Input',
componentProps: {
placeholder: '请输入 CSS Class',
},
help: '输入 hex 模式的颜色, 例如 #108ee9',
},
{
fieldName: 'remark',
label: '备注',
component: 'Input',
componentProps: {
type: 'textarea',
placeholder: '请输入备注',
},
},
];
}
/** 字典数据列表搜索表单 */
export function useDataGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'label',
label: '字典标签',
component: 'Input',
componentProps: {
placeholder: '请输入字典标签',
clearable: true,
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
clearable: true,
},
},
];
}
/** 字典数据表格列 */
export function useDataGridColumns(): VxeTableGridOptions['columns'] {
return [
{ type: 'checkbox', width: 40 },
{
field: 'id',
title: '字典编码',
minWidth: 100,
},
{
field: 'label',
title: '字典标签',
minWidth: 180,
},
{
field: 'value',
title: '字典键值',
minWidth: 100,
},
{
field: 'sort',
title: '字典排序',
minWidth: 100,
},
{
field: 'status',
title: '状态',
minWidth: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{
field: 'colorType',
title: '颜色类型',
minWidth: 120,
slots: { default: 'colorType' },
},
{
field: 'cssClass',
title: 'CSS Class',
minWidth: 120,
slots: { default: 'cssClass' },
},
{
title: '创建时间',
field: 'createTime',
minWidth: 180,
formatter: 'formatDateTime',
},
{
title: '操作',
minWidth: 140,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

View File

@@ -0,0 +1,33 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { DocAlert, Page } from '@vben/common-ui';
import DataGrid from './modules/data-grid.vue';
import TypeGrid from './modules/type-grid.vue';
const searchDictType = ref<string>(); // 搜索的字典类型
function handleDictTypeSelect(dictType: string) {
searchDictType.value = dictType;
}
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert title="字典管理" url="https://doc.iocoder.cn/system-dict/" />
</template>
<div class="flex h-full">
<!-- 左侧字典类型列表 -->
<div class="w-1/2 pr-3">
<TypeGrid @select="handleDictTypeSelect" />
</div>
<!-- 右侧字典数据列表 -->
<div class="w-1/2">
<DataGrid :dict-type="searchDictType" />
</div>
</div>
</Page>
</template>

View File

@@ -0,0 +1,89 @@
<script lang="ts" setup>
import type { SystemDictDataApi } from '#/api/system/dict/data';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useVbenForm } from '#/adapter/form';
import { message } from '#/adapter/naive';
import {
createDictData,
getDictData,
updateDictData,
} from '#/api/system/dict/data';
import { $t } from '#/locales';
import { useDataFormSchema } from '../data';
defineOptions({ name: 'SystemDictDataForm' });
const emit = defineEmits(['success']);
const formData = ref<SystemDictDataApi.DictData>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['字典数据'])
: $t('ui.actionTitle.create', ['字典数据']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 90,
},
layout: 'horizontal',
schema: useDataFormSchema(),
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as SystemDictDataApi.DictData;
try {
await (formData.value?.id ? updateDictData(data) : createDictData(data));
// 关闭并提示
await modalApi.close();
emit('success');
message.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
// 加载数据
const data = modalApi.getData<SystemDictDataApi.DictData>();
if (!data || !data.id) {
// 设置 dictType
await formApi.setValues(data);
return;
}
modalApi.lock();
try {
formData.value = await getDictData(data.id);
// 设置到 values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle">
<Form class="mx-4" />
</Modal>
</template>

View File

@@ -0,0 +1,208 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemDictDataApi } from '#/api/system/dict/data';
import { ref, watch } from 'vue';
import { confirm, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { message } from '#/adapter/naive';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDictData,
deleteDictDataList,
exportDictData,
getDictDataPage,
} from '#/api/system/dict/data';
import { $t } from '#/locales';
import { useDataGridColumns, useDataGridFormSchema } from '../data';
import DataForm from './data-form.vue';
const props = defineProps({
dictType: {
type: String,
default: undefined,
},
});
const [DataFormModal, dataFormModalApi] = useVbenModal({
connectedComponent: DataForm,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 导出表格 */
async function handleExport() {
const data = await exportDictData(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '字典数据.xls', source: data });
}
/** 创建字典数据 */
function handleCreate() {
dataFormModalApi.setData({ dictType: props.dictType }).open();
}
/** 编辑字典数据 */
function handleEdit(row: SystemDictDataApi.DictData) {
dataFormModalApi.setData(row).open();
}
/** 删除字典数据 */
async function handleDelete(row: SystemDictDataApi.DictData) {
const hideLoading = message.loading(
$t('ui.actionMessage.deleting', [row.label]),
{ duration: 0 },
);
try {
await deleteDictData(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.label]));
handleRefresh();
} finally {
hideLoading.destroy();
}
}
/** 批量删除字典数据 */
async function handleDeleteBatch() {
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const hideLoading = message.loading($t('ui.actionMessage.deleting'), {
duration: 0,
});
try {
await deleteDictDataList(checkedIds.value);
checkedIds.value = [];
message.success($t('ui.actionMessage.deleteSuccess'));
handleRefresh();
} finally {
hideLoading.destroy();
}
}
const checkedIds = ref<number[]>([]);
function handleRowCheckboxChange({
records,
}: {
records: SystemDictDataApi.DictData[];
}) {
checkedIds.value = records.map((item) => item.id!);
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useDataGridFormSchema(),
},
gridOptions: {
columns: useDataGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getDictDataPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
dictType: props.dictType,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<SystemDictDataApi.DictData>,
gridEvents: {
checkboxAll: handleRowCheckboxChange,
checkboxChange: handleRowCheckboxChange,
},
});
/** 监听 dictType 变化,重新查询 */
watch(
() => props.dictType,
() => {
if (props.dictType) {
handleRefresh();
}
},
);
</script>
<template>
<div class="flex h-full flex-col">
<DataFormModal @success="handleRefresh" />
<Grid table-title="字典数据列表">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['字典数据']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['system:dict:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['system:dict:export'],
onClick: handleExport,
},
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'error',
icon: ACTION_ICON.DELETE,
disabled: isEmpty(checkedIds),
auth: ['system:dict:delete'],
onClick: handleDeleteBatch,
},
]"
/>
</template>
<template #colorType="{ row }">
<Tag :color="row.colorType">{{ row.colorType }}</Tag>
</template>
<template #cssClass="{ row }">
<Tag :color="row.cssClass">{{ row.cssClass }}</Tag>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'primary',
text: true,
icon: ACTION_ICON.EDIT,
auth: ['system:dict:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'error',
text: true,
icon: ACTION_ICON.DELETE,
auth: ['system:dict:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.label]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</div>
</template>

View File

@@ -0,0 +1,85 @@
<script lang="ts" setup>
import type { SystemDictTypeApi } from '#/api/system/dict/type';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useVbenForm } from '#/adapter/form';
import { message } from '#/adapter/naive';
import {
createDictType,
getDictType,
updateDictType,
} from '#/api/system/dict/type';
import { $t } from '#/locales';
import { useTypeFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<SystemDictTypeApi.DictType>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['字典类型'])
: $t('ui.actionTitle.create', ['字典类型']);
});
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 80,
},
layout: 'horizontal',
schema: useTypeFormSchema(),
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as SystemDictTypeApi.DictType;
try {
await (formData.value?.id ? updateDictType(data) : createDictType(data));
// 关闭并提示
await modalApi.close();
emit('success');
message.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
// 加载数据
const data = modalApi.getData<SystemDictTypeApi.DictType>();
if (!data || !data.id) {
return;
}
modalApi.lock();
try {
formData.value = await getDictType(data.id);
// 设置到 values
await formApi.setValues(formData.value);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal :title="getTitle">
<Form class="mx-4" />
</Modal>
</template>

View File

@@ -0,0 +1,189 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { SystemDictTypeApi } from '#/api/system/dict/type';
import { ref } from 'vue';
import { confirm, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { message } from '#/adapter/naive';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteDictType,
deleteDictTypeList,
exportDictType,
getDictTypePage,
} from '#/api/system/dict/type';
import { $t } from '#/locales';
import { useTypeGridColumns, useTypeGridFormSchema } from '../data';
import TypeForm from './type-form.vue';
const emit = defineEmits(['select']);
const [TypeFormModal, typeFormModalApi] = useVbenModal({
connectedComponent: TypeForm,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 导出表格 */
async function handleExport() {
const data = await exportDictType(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '字典类型.xls', source: data });
}
/** 创建字典类型 */
function handleCreate() {
typeFormModalApi.setData(null).open();
}
/** 编辑字典类型 */
function handleEdit(row: SystemDictTypeApi.DictType) {
typeFormModalApi.setData(row).open();
}
/** 删除字典类型 */
async function handleDelete(row: SystemDictTypeApi.DictType) {
const hideLoading = message.loading(
$t('ui.actionMessage.deleting', [row.name]),
{ duration: 0 },
);
try {
await deleteDictType(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
handleRefresh();
} finally {
hideLoading.destroy();
}
}
/** 批量删除字典类型 */
async function handleDeleteBatch() {
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const hideLoading = message.loading($t('ui.actionMessage.deletingBatch'), {
duration: 0,
});
try {
await deleteDictTypeList(checkedIds.value);
checkedIds.value = [];
message.success($t('ui.actionMessage.deleteSuccess'), { duration: 0 });
handleRefresh();
} finally {
hideLoading.destroy();
}
}
const checkedIds = ref<number[]>([]);
function handleRowCheckboxChange({
records,
}: {
records: SystemDictTypeApi.DictType[];
}) {
checkedIds.value = records.map((item) => item.id!);
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useTypeGridFormSchema(),
},
gridOptions: {
columns: useTypeGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getDictTypePage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
isCurrent: true,
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<SystemDictTypeApi.DictType>,
gridEvents: {
cellClick: ({ row }: { row: SystemDictTypeApi.DictType }) => {
emit('select', row.type);
},
checkboxAll: handleRowCheckboxChange,
checkboxChange: handleRowCheckboxChange,
},
});
</script>
<template>
<div class="h-full">
<TypeFormModal @success="handleRefresh" />
<Grid table-title="字典类型列表">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['字典类型']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['system:dict:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['system:dict:export'],
onClick: handleExport,
},
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'error',
icon: ACTION_ICON.DELETE,
disabled: isEmpty(checkedIds),
auth: ['system:dict:delete'],
onClick: handleDeleteBatch,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'primary',
text: true,
icon: ACTION_ICON.EDIT,
auth: ['system:dict:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'error',
text: true,
icon: ACTION_ICON.DELETE,
auth: ['system:dict:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</div>
</template>