This commit is contained in:
SGK\17962 2025-07-16 16:11:16 +08:00
parent 0ddabbb182
commit 4ca675870d
26 changed files with 1443 additions and 2 deletions

View File

@ -219,6 +219,7 @@ const local: App.I18n.Schema = {
tool_gen: 'Code Generation',
system_user: 'User Management',
system_dict: 'Dict Management',
system_busitype: 'Business Type Management',
system_tenant: 'Tenant Management',
'system_tenant-package': 'Tenant Package Management',
system_config: 'Config Management',

View File

@ -13,7 +13,8 @@ const local: App.I18n.Schema = {
updateTitle: '系统版本更新通知',
updateContent: '检测到系统有新版本发布,是否立即刷新页面?',
updateConfirm: '立即刷新',
updateCancel: '稍后再说'
updateCancel: '稍后再说',
// businessType: '业务类型'
},
common: {
action: '操作',
@ -219,6 +220,7 @@ const local: App.I18n.Schema = {
tool_gen: '代码生成',
system_user: '用户管理',
system_dict: '字典管理',
system_busitype: '业务类型管理',
system_tenant: '租户管理',
'system_tenant-package': '租户套餐',
system_config: '参数设置',

View File

@ -31,6 +31,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
monitor_operlog: () => import("@/views/monitor/operlog/index.vue"),
"mps_private-ebank-new": () => import("@/views/mps/private-ebank-new/index.vue"),
"mps_private-test": () => import("@/views/mps/private-test/index.vue"),
subcategory: () => import("@/views/subcategory/index.vue"),
system_client: () => import("@/views/system/client/index.vue"),
system_config: () => import("@/views/system/config/index.vue"),
system_dept: () => import("@/views/system/dept/index.vue"),

View File

@ -191,6 +191,15 @@ export const generatedRoutes: GeneratedRoute[] = [
hideInMenu: true
}
},
{
name: 'subcategory',
path: '/subcategory',
component: 'layout.base$view.subcategory',
meta: {
title: 'subcategory',
i18nKey: 'route.subcategory'
}
},
{
name: 'system',
path: '/system',

View File

@ -185,6 +185,7 @@ const routeMap: RouteMap = {
"mps_private-ebank-new": "/mps/private-ebank-new",
"mps_private-test": "/mps/private-test",
"social-callback": "/social-callback",
"subcategory": "/subcategory",
"system": "/system",
"system_client": "/system/client",
"system_config": "/system/config",

View File

@ -0,0 +1,36 @@
import { request } from '@/service/request';
/** 获取业务子类列表 */
export function fetchGetSubcategoryList (params?: Api.Business.SubcategorySearchParams) {
return request<Api.Business.SubcategoryList>({
url: '/system/subcategory/list',
method: 'get',
params
});
}
/** 新增业务子类 */
export function fetchCreateSubcategory (data: Api.Business.SubcategoryOperateParams) {
return request<boolean>({
url: '/system/subcategory',
method: 'post',
data
});
}
/** 修改业务子类 */
export function fetchUpdateSubcategory (data: Api.Business.SubcategoryOperateParams) {
return request<boolean>({
url: '/system/subcategory',
method: 'put',
data
});
}
/** 批量删除业务子类 */
export function fetchBatchDeleteSubcategory (ids: CommonType.IdType[]) {
return request<boolean>({
url: `/system/subcategory/${ids.join(',')}`,
method: 'delete'
});
}

View File

@ -212,4 +212,28 @@ declare namespace Api {
home: import('@elegant-router/types').LastLevelRouteKey;
}
}
namespace System {
import PaginatingQueryRecord = Api.Common.PaginatingQueryRecord;
/** Business Type */
interface BusinessType {
id: number;
typeName: string;
// 其他字段...
}
type BusinessTypeList = PaginatingQueryRecord<BusinessType>
interface BusinessTypeSearchParams extends Common.CommonSearchParams {
typeName?: string;
}
interface BusinessTypeOperateParams {
[id: string]: any;
typeName: string;
businessType?: string;
// 其他操作参数...
}
}
}

View File

@ -0,0 +1,54 @@
/**
* namespace System
*
* backend api module: "System"
*/
declare namespace Api {
namespace Business {
/** subcategory */
type Subcategory = Common.CommonRecord<{
/** 主键ID */
id: CommonType.IdType;
/** 所属大类ID */
categoryId: CommonType.IdType;
/** 业务类型ID */
typeId: CommonType.IdType;
/** 业务子类名称 */
name: string;
/** 关联部门ID */
deptId: CommonType.IdType;
/** 计价规则说明 */
pricingRule: string;
}>;
/** subcategory search params */
type SubcategorySearchParams = CommonType.RecordNullable<
Pick<
Api.Business.Subcategory,
| 'categoryId'
| 'typeId'
| 'name'
| 'deptId'
| 'pricingRule'
> &
Api.Common.CommonSearchParams
>;
/** subcategory operate params */
type SubcategoryOperateParams = CommonType.RecordNullable<
Pick<
Api.Business.Subcategory,
| 'id'
| 'categoryId'
| 'typeId'
| 'name'
| 'deptId'
| 'pricingRule'
>
>;
/** subcategory list */
type SubcategoryList = Api.Common.PaginatingQueryRecord<Subcategory>;
}
}

View File

@ -643,6 +643,21 @@ declare namespace App {
addDept: string;
editDept: string;
};
busitype: {
title: string;
busiTypeName: string;
busiTypeCode: string;
status: string;
remark: string;
createTime: string;
form: {
busiTypeId: FormMsg;
busiTypeName: FormMsg;
busiTypeCode: FormMsg;
};
addBusiType: string;
editBusiType: string;
};
dict: {
title: string;
dictTypeTitle: string;

View File

@ -11,6 +11,8 @@ declare module 'vue' {
AppProvider: typeof import('./../components/common/app-provider.vue')['default']
BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
BooleanTag: typeof import('./../components/custom/boolean-tag.vue')['default']
BusinessTypeList: typeof import('@/views/subcategory/index.vue')['default']
BusinessTypeOperateDrawer: typeof import('@/views/subcategory/modules/operate-drawer.vue')['default']
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
CountTo: typeof import('./../components/custom/count-to.vue')['default']
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']

View File

@ -39,6 +39,7 @@ declare module "@elegant-router/types" {
"mps_private-ebank-new": "/mps/private-ebank-new";
"mps_private-test": "/mps/private-test";
"social-callback": "/social-callback";
"subcategory": "/subcategory";
"system": "/system";
"system_client": "/system/client";
"system_config": "/system/config";
@ -102,6 +103,7 @@ declare module "@elegant-router/types" {
| "monitor"
| "mps"
| "social-callback"
| "subcategory"
| "system"
| "tool"
| "user-center"
@ -138,6 +140,7 @@ declare module "@elegant-router/types" {
| "monitor_operlog"
| "mps_private-ebank-new"
| "mps_private-test"
| "subcategory"
| "system_client"
| "system_config"
| "system_dept"

View File

@ -0,0 +1,285 @@
<script setup lang="tsx">
import { ref, watch } from 'vue';
import { NDivider } from 'naive-ui';
import { fetchBatchDeleteSubcategory, fetchGetSubcategoryList } from '@/service/api/business/businessType';
import { useAppStore } from '@/store/modules/app';
import { useAuth } from '@/hooks/business/auth';
import { useDownload } from '@/hooks/business/download';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { $t } from '@/locales';
import ButtonIcon from '@/components/custom/button-icon.vue';
import SubcategoryOperateDrawer from './modules/operate-drawer.vue';
import SubcategorySearch from './modules/search.vue';
import { useBoolean } from '~/packages/hooks';
// import SubcategoryImportModal from './modules/subcategory-import-modal.vue';
defineOptions({
name: 'SubcategoryList'
});
const appStore = useAppStore();
const { download } = useDownload();
const { hasAuth } = useAuth();
const { bool: importVisible, setTrue: openImportModal } = useBoolean();
const {
columns,
columnChecks,
data,
getData,
getDataByPage,
loading,
mobilePagination,
searchParams,
resetSearchParams
} = useTable({
apiFn: fetchGetSubcategoryList,
apiParams: {
pageNum: 1,
pageSize: 10,
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
categoryId: null,
typeId: null,
name: null,
deptId: null,
pricingRule: null,
params: {}
},
columns: () => [
{
type: 'selection',
align: 'center',
width: 48
},
{
key: 'index',
title: $t('common.index'),
align: 'center',
width: 64,
resizable: true
},
{
key: 'id',
title: '主键ID',
align: 'center',
minWidth: 120,
ellipsis: true,
resizable: true
},
{
key: 'categoryId',
title: '所属大类ID',
align: 'center',
minWidth: 120,
ellipsis: true,
resizable: true
},
{
key: 'typeId',
title: '业务类型ID',
align: 'center',
minWidth: 120,
ellipsis: true,
resizable: true
},
{
key: 'name',
title: '业务子类名称',
align: 'center',
minWidth: 120,
ellipsis: true,
resizable: true
},
{
key: 'deptId',
title: '关联部门ID',
align: 'center',
minWidth: 120,
ellipsis: true,
resizable: true
},
{
key: 'pricingRule',
title: '计价规则说明',
align: 'center',
minWidth: 120,
ellipsis: true,
resizable: true
},
{
key: 'operate',
title: $t('common.operate'),
fixed: 'right',
width: 130,
render: row => {
const divider = () => {
if (!hasAuth('system:subcategory:edit') || !hasAuth('system:subcategory:remove')) {
return null;
}
return <NDivider vertical />;
};
const editBtn = () => {
if (!hasAuth('system:subcategory:edit')) {
return null;
}
return (
<ButtonIcon
text
type="primary"
icon="material-symbols:drive-file-rename-outline-outline"
tooltipContent={$t('common.edit')}
onClick={() => edit(row.id!)}
/>
);
};
const deleteBtn = () => {
if (!hasAuth('system:subcategory:remove')) {
return null;
}
return (
<ButtonIcon
text
type="error"
icon="material-symbols:delete-outline"
tooltipContent={$t('common.delete')}
popconfirmContent={$t('common.confirmDelete')}
onPositiveClick={() => handleDelete(row.id!)}
/>
);
};
return (
<div class="flex-center gap-8px">
{editBtn()}
{divider()}
{deleteBtn()}
</div>
);
}
}
]
});
const scrollX = ref(0);
//
const calculateTotalWidth = () => {
let totalWidth = 0;
const visibleColumns = columns.value;
// eslint-disable-next-line no-plusplus
for (let i = 0; i < visibleColumns.length; i++) {
const column = visibleColumns[i];
//
// column.width
let width = column.width;
// 使minWidth
if (!width) {
width = column.minWidth || 120;
}
//
width = typeof width === 'string' ? Number.parseInt(width, 10) : width;
totalWidth += width;
}
// 50px
return totalWidth + 50;
};
//
watch(
columns,
() => {
scrollX.value = calculateTotalWidth();
},
{ deep: true }
);
const { drawerVisible, operateType, editingData, handleAdd, handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } =
useTableOperate(data, getData);
async function handleBatchDelete() {
// request
const { error } = await fetchBatchDeleteSubcategory(checkedRowKeys.value);
if (error) return;
onBatchDeleted();
}
async function handleDelete(id: CommonType.IdType) {
// request
const { error } = await fetchBatchDeleteSubcategory([id]);
if (error) return;
onDeleted();
}
function edit(id: CommonType.IdType) {
handleEdit('id', id);
}
function handleImport() {
openImportModal();
}
function handleExport() {
download('/system/subcategory/export', searchParams, `业务子类_${new Date().getTime()}.xlsx`);
}
</script>
<template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<SubcategorySearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage" />
<NCard title="业务子类列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
<template #header-extra>
<TableHeaderOperation
v-model:columns="columnChecks"
:disabled-delete="checkedRowKeys.length === 0"
:loading="loading"
:show-add="hasAuth('system:subcategory:add')"
:show-delete="hasAuth('system:subcategory:remove')"
:show-export="hasAuth('system:subcategory:export')"
@add="handleAdd"
@delete="handleBatchDelete"
@export="handleExport"
@refresh="getData"
>
<template #after>
<NButton v-if="hasAuth('mps:privateEbankNew:export')" size="small" ghost @click="handleImport">
<template #icon>
<icon-material-symbols:upload-rounded class="text-icon" />
</template>
{{ $t('common.import') }}
</NButton>
</template>
</TableHeaderOperation>
</template>
<!--scroll-x : 所有表格列宽度之和(包含操作列)+操作列宽度-->
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
:columns="columns"
:data="data"
size="small"
:flex-height="!appStore.isMobile"
:scroll-x="scrollX"
:loading="loading"
remote
:row-key="row => row.id"
:pagination="mobilePagination"
class="sm:h-full"
@update-resize-widths="scrollX = calculateTotalWidth()"
/>
<SubcategoryImportModal v-model:visible="importVisible" @submitted="getDataByPage" />
<SubcategoryOperateDrawer
v-model:visible="drawerVisible"
:operate-type="operateType"
:row-data="editingData"
@submitted="getDataByPage"
/>
</NCard>
</div>
</template>
<style scoped></style>

View File

@ -0,0 +1,158 @@
<script setup lang="ts">
import { computed, reactive, watch } from 'vue';
import { fetchCreateSubcategory, fetchUpdateSubcategory } from '@/service/api/business/businessType';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
defineOptions({
name: 'SubcategoryOperateDrawer'
});
interface Props {
/** the type of operation */
operateType: NaiveUI.TableOperateType;
/** the edit row data */
rowData?: Api.Business.Subcategory | null;
}
const props = defineProps<Props>();
interface Emits {
(e: 'submitted'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const { formRef, validate, restoreValidation } = useNaiveForm();
const { createRequiredRule } = useFormRules();
const title = computed(() => {
const titles: Record<NaiveUI.TableOperateType, string> = {
add: '新增业务子类',
edit: '编辑业务子类'
};
return titles[props.operateType];
});
type Model = Api.Business.SubcategoryOperateParams;
const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
categoryId: undefined,
typeId: undefined,
name: '',
deptId: undefined,
pricingRule: '',
};
}
type RuleKey = Extract<
keyof Model,
| 'typeId'
| 'deptId'
| 'pricingRule'
| 'createDept'
| 'createBy'
| 'createTime'
| 'updateBy'
| 'updateTime'
>;
const rules: Record<RuleKey, App.Global.FormRule> = {
typeId: createRequiredRule('业务类型ID不能为空'),
deptId: createRequiredRule('关联部门ID不能为空'),
pricingRule: createRequiredRule('计价规则说明不能为空'),
createDept: createRequiredRule('创建部门不能为空'),
createBy: createRequiredRule('创建人不能为空'),
createTime: createRequiredRule('创建时间不能为空'),
updateBy: createRequiredRule('更新人不能为空'),
updateTime: createRequiredRule('更新时间不能为空')
};
function handleUpdateModelWhenEdit() {
if (props.operateType === 'add') {
Object.assign(model, createDefaultModel());
return;
}
if (props.operateType === 'edit' && props.rowData) {
Object.assign(model, props.rowData);
}
}
function closeDrawer() {
visible.value = false;
}
async function handleSubmit() {
await validate();
const { id, categoryId, typeId, name, deptId, pricingRule } = model;
// request
if (props.operateType === 'add') {
const { error } = await fetchCreateSubcategory({ categoryId, typeId, name, deptId, pricingRule });
if (error) return;
}
if (props.operateType === 'edit') {
const { error } = await fetchUpdateSubcategory({ id, categoryId, typeId, name, deptId, pricingRule });
if (error) return;
}
window.$message?.success($t('common.updateSuccess'));
closeDrawer();
emit('submitted');
}
watch(visible, () => {
if (visible.value) {
handleUpdateModelWhenEdit();
restoreValidation();
}
});
</script>
<template>
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="800" class="max-w-90%">
<NDrawerContent :title="title" :native-scrollbar="false" closable>
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem label="所属大类ID" path="categoryId">
<NInput v-model:value="model.categoryId" placeholder="请输入所属大类ID" />
</NFormItem>
<NFormItem label="业务类型ID" path="typeId">
<NInput v-model:value="model.typeId" placeholder="请输入业务类型ID" />
</NFormItem>
<NFormItem label="业务子类名称" path="name">
<NInput v-model:value="model.name" placeholder="请输入业务子类名称" />
</NFormItem>
<NFormItem label="关联部门ID" path="deptId">
<NInput v-model:value="model.deptId" placeholder="请输入关联部门ID" />
</NFormItem>
<NFormItem label="计价规则说明" path="pricingRule">
<NInput
v-model:value="model.pricingRule"
:rows="3"
type="textarea"
placeholder="请输入计价规则说明"
/>
</NFormItem>
</NForm>
<template #footer>
<NSpace :size="16">
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
</NSpace>
</template>
</NDrawerContent>
</NDrawer>
</template>
<style scoped></style>

View File

@ -0,0 +1,79 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useNaiveForm } from '@/hooks/common/form';
import { $t } from '@/locales';
defineOptions({
name: 'SubcategorySearch'
});
interface Emits {
(e: 'reset'): void;
(e: 'search'): void;
}
const emit = defineEmits<Emits>();
const { formRef, validate, restoreValidation } = useNaiveForm();
const model = defineModel<Api.System.SubcategorySearchParams>('model', { required: true });
async function reset() {
Object.assign(model.value.params!, {});
await restoreValidation();
emit('reset');
}
async function search() {
await validate();
emit('search');
}
</script>
<template>
<NCard :bordered="false" size="small" class="card-wrapper">
<NCollapse>
<NCollapseItem :title="$t('common.search')" name="user-search">
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80">
<NGrid responsive="screen" item-responsive>
<NFormItemGi span="24 s:12 m:6" label="所属大类ID" path="categoryId" class="pr-24px">
<NInput v-model:value="model.categoryId" placeholder="请输入所属大类ID" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="业务类型ID" path="typeId" class="pr-24px">
<NInput v-model:value="model.typeId" placeholder="请输入业务类型ID" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="业务子类名称" path="name" class="pr-24px">
<NInput v-model:value="model.name" placeholder="请输入业务子类名称" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="关联部门ID" path="deptId" class="pr-24px">
<NInput v-model:value="model.deptId" placeholder="请输入关联部门ID" />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" label="计价规则说明" path="pricingRule" class="pr-24px">
<NInput v-model:value="model.pricingRule" placeholder="请输入计价规则说明" />
</NFormItemGi>
<NFormItemGi span="24" class="pr-24px">
<NSpace class="w-full" justify="end">
<NButton @click="reset">
<template #icon>
<icon-ic-round-refresh class="text-icon" />
</template>
{{ $t('common.reset') }}
</NButton>
<NButton type="primary" ghost @click="search">
<template #icon>
<icon-ic-round-search class="text-icon" />
</template>
{{ $t('common.search') }}
</NButton>
</NSpace>
</NFormItemGi>
</NGrid>
</NForm>
</NCollapseItem>
</NCollapse>
</NCard>
</template>
<style scoped></style>

View File

@ -105,7 +105,7 @@ spring.data:
# 数据库索引
database: 0
# redis 密码必须配置
password: 123456
# password: 123456
# 连接超时时间
timeout: 10s
# 是否开启ssl

View File

@ -0,0 +1,134 @@
package org.dromara.business.controller;
import java.util.List;
import java.util.ArrayList;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.dromara.business.domain.vo.BusinessSubcategoryImportVo;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;
import org.dromara.common.excel.core.ExcelResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.business.domain.vo.BusinessSubcategoryVo;
import org.dromara.business.domain.bo.BusinessSubcategoryBo;
import org.dromara.business.service.IBusinessSubcategoryService;
//import org.dromara.business.domain.vo.BusinessSubcategoryImportVo;
import org.dromara.business.listener.BusinessSubcategoryImportListener;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 业务子类
*
* @author Lion Li
* @date 2025-07-15
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/subcategory")
public class BusinessSubcategoryController extends BaseController {
private final IBusinessSubcategoryService businessSubcategoryService;
/**
* 查询业务子类列表
*/
@SaCheckPermission("system:subcategory:list")
@GetMapping("/list")
public TableDataInfo<BusinessSubcategoryVo> list(BusinessSubcategoryBo bo, PageQuery pageQuery) {
return businessSubcategoryService.queryPageList(bo, pageQuery);
}
/**
* 导出业务子类列表
*/
@SaCheckPermission("system:subcategory:export")
@Log(title = "业务子类", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(BusinessSubcategoryBo bo, HttpServletResponse response) {
List<BusinessSubcategoryVo> list = businessSubcategoryService.queryList(bo);
ExcelUtil.exportExcel(list, "业务子类", BusinessSubcategoryVo.class, response);
}
/**
* 获取导入模板
*/
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
ExcelUtil.exportExcel(new ArrayList<>(), "业务子类", BusinessSubcategoryImportVo.class, response);
}
/**
* 导入数据
*
* @param file 导入文件
* @param updateSupport 是否更新已存在数据
*/
@Log(title = "业务子类", businessType = BusinessType.IMPORT)
@SaCheckPermission("system:subcategory:import")
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Void> importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception {
ExcelResult<BusinessSubcategoryImportVo> result = ExcelUtil.importExcel(file.getInputStream(), BusinessSubcategoryImportVo.class, new BusinessSubcategoryImportListener(updateSupport));
return R.ok(result.getAnalysis());
}
/**
* 获取业务子类详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:subcategory:query")
@GetMapping("/{id}")
public R<BusinessSubcategoryVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(businessSubcategoryService.queryById(id));
}
/**
* 新增业务子类
*/
@SaCheckPermission("system:subcategory:add")
@Log(title = "业务子类", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody BusinessSubcategoryBo bo) {
return toAjax(businessSubcategoryService.insertByBo(bo));
}
/**
* 修改业务子类
*/
@SaCheckPermission("system:subcategory:edit")
@Log(title = "业务子类", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody BusinessSubcategoryBo bo) {
return toAjax(businessSubcategoryService.updateByBo(bo));
}
/**
* 删除业务子类
*
* @param ids 主键串
*/
@SaCheckPermission("system:subcategory:remove")
@Log(title = "业务子类", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(businessSubcategoryService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@ -0,0 +1,56 @@
package org.dromara.business.domain;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 业务子类对象 business_subcategory
*
* @author Lion Li
* @date 2025-07-15
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("business_subcategory")
public class BusinessSubcategory extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 所属大类ID
*/
private Long categoryId;
/**
* 业务类型ID
*/
private Long typeId;
/**
* 业务子类名称
*/
private String name;
/**
* 关联部门ID
*/
private Long deptId;
/**
* 计价规则说明
*/
private String pricingRule;
}

View File

@ -0,0 +1,57 @@
package org.dromara.business.domain.bo;
import org.dromara.business.domain.BusinessSubcategory;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 业务子类业务对象 business_subcategory
*
* @author Lion Li
* @date 2025-07-15
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = BusinessSubcategory.class, reverseConvertGenerate = false)
public class BusinessSubcategoryBo extends BaseEntity {
/**
* 主键ID
*/
private Long id;
/**
* 所属大类ID
*/
private Long categoryId;
/**
* 业务类型ID
*/
@NotNull(message = "业务类型ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long typeId;
/**
* 业务子类名称
*/
private String name;
/**
* 关联部门ID
*/
@NotNull(message = "关联部门ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long deptId;
/**
* 计价规则说明
*/
@NotBlank(message = "计价规则说明不能为空", groups = { AddGroup.class, EditGroup.class })
private String pricingRule;
}

View File

@ -0,0 +1,64 @@
package org.dromara.business.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.business.domain.BusinessSubcategory;
import java.io.Serial;
import java.io.Serializable;
/**
* 业务子类视图对象 business_subcategory
*
* @author Lion Li
* @date 2025-07-15
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = BusinessSubcategory.class)
public class BusinessSubcategoryImportVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 所属大类ID
*/
@ExcelProperty(value = "所属大类ID")
private Long categoryId;
/**
* 业务类型ID
*/
@ExcelProperty(value = "业务类型ID")
private Long typeId;
/**
* 业务子类名称
*/
@ExcelProperty(value = "业务子类名称")
private String name;
/**
* 关联部门ID
*/
@ExcelProperty(value = "关联部门ID")
private Long deptId;
/**
* 计价规则说明
*/
@ExcelProperty(value = "计价规则说明")
private String pricingRule;
}

View File

@ -0,0 +1,68 @@
package org.dromara.business.domain.vo;
import org.dromara.business.domain.BusinessSubcategory;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 业务子类视图对象 business_subcategory
*
* @author Lion Li
* @date 2025-07-15
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = BusinessSubcategory.class)
public class BusinessSubcategoryVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 所属大类ID
*/
@ExcelProperty(value = "所属大类ID")
private Long categoryId;
/**
* 业务类型ID
*/
@ExcelProperty(value = "业务类型ID")
private Long typeId;
/**
* 业务子类名称
*/
@ExcelProperty(value = "业务子类名称")
private String name;
/**
* 关联部门ID
*/
@ExcelProperty(value = "关联部门ID")
private Long deptId;
/**
* 计价规则说明
*/
@ExcelProperty(value = "计价规则说明")
private String pricingRule;
}

View File

@ -0,0 +1,118 @@
package org.dromara.business.listener;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.http.HtmlUtil;
import cn.idev.excel.context.AnalysisContext;
import cn.idev.excel.event.AnalysisEventListener;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.dromara.business.domain.bo.BusinessSubcategoryBo;
import org.dromara.business.domain.vo.BusinessSubcategoryImportVo;
import org.dromara.business.service.IBusinessSubcategoryService;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.excel.core.ExcelListener;
import org.dromara.common.excel.core.ExcelResult;
import org.dromara.common.satoken.utils.LoginHelper;
import java.util.List;
/**
* 系统用户自定义导入
*
* @author Lion Li
*/
@Slf4j
public class BusinessSubcategoryImportListener extends AnalysisEventListener<BusinessSubcategoryImportVo> implements ExcelListener<BusinessSubcategoryImportVo> {
private final IBusinessSubcategoryService mpsPrivateEbankNewService;
private final Boolean isUpdateSupport;
private final Long operUserId;
private int successNum = 0;
private int failureNum = 0;
private final StringBuilder successMsg = new StringBuilder();
private final StringBuilder failureMsg = new StringBuilder();
public BusinessSubcategoryImportListener(Boolean isUpdateSupport) {
this.mpsPrivateEbankNewService = SpringUtils.getBean(IBusinessSubcategoryService.class);
this.isUpdateSupport = isUpdateSupport;
this.operUserId = LoginHelper.getUserId();
}
@Override
public void invoke(BusinessSubcategoryImportVo BusinessSubcategoryImportVo, AnalysisContext context) {
//TODO 自行实现验重逻辑
//MpsPrivateEbankNewVo mpsPrivateEbankNewVo1 = this.mpsPrivateEbankNewService.queryById(mpsPrivateEbankNewVo.getDataId());
try {
// TODO 验证是否存在(业务去重),模板默认不存在自行调整
if (true) {
BusinessSubcategoryBo BusinessSubcategoryBo = BeanUtil.toBean(BusinessSubcategoryImportVo, BusinessSubcategoryBo.class);
ValidatorUtils.validate(BusinessSubcategoryBo);
this.mpsPrivateEbankNewService.insertByBo(BusinessSubcategoryBo);
successNum++;
successMsg.append("<br/>").append(successNum).append("、数据 ").append(BusinessSubcategoryBo.getId()).append(" 导入成功");
} else if (isUpdateSupport) { //存在时是否更新,mps暂定不允许更新,各业务模块自行调整
BusinessSubcategoryBo BusinessSubcategoryBo = BeanUtil.toBean(BusinessSubcategoryImportVo, BusinessSubcategoryBo.class);
ValidatorUtils.validate(BusinessSubcategoryBo);
this.mpsPrivateEbankNewService.insertByBo(BusinessSubcategoryBo);
successNum++;
successMsg.append("<br/>").append(successNum).append("、数据 ").append(BusinessSubcategoryBo.getId()).append(" 更新成功");
} else {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、数据 ").append(BusinessSubcategoryImportVo.getName()).append(" 重复");
}
} catch (Exception e) {
failureNum++;
String msg = "<br/>" + failureNum + "、数据 " + HtmlUtil.cleanHtmlTag((BusinessSubcategoryImportVo.getName())) + " 导入失败:";
String message = e.getMessage();
if (e instanceof ConstraintViolationException cvException) {
message = StreamUtils.join(cvException.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
}
failureMsg.append(msg).append(message);
log.error(msg, e);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
@Override
public ExcelResult<BusinessSubcategoryImportVo> getExcelResult() {
return new ExcelResult<>() {
@Override
public String getAnalysis() {
if (failureNum > 0) {
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
throw new ServiceException(failureMsg.toString());
} else {
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
}
return successMsg.toString();
}
@Override
public List<BusinessSubcategoryImportVo> getList() {
return null;
}
@Override
public List<String> getErrorList() {
return null;
}
};
}
}

View File

@ -0,0 +1,36 @@
package org.dromara.business.mapper;
import org.dromara.business.domain.BusinessSubcategory;
import org.dromara.business.domain.vo.BusinessSubcategoryVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.annotation.DataColumn;
import org.dromara.common.mybatis.annotation.DataPermission;
/**
* 业务子类Mapper接口
*
* @author Lion Li
* @date 2025-07-15
*/
public interface BusinessSubcategoryMapper extends BaseMapperPlus<BusinessSubcategory, BusinessSubcategoryVo> {
@DataPermission({
@DataColumn(key = "deptName", value = "create_dept"),
@DataColumn(key = "userName", value = "create_by")
})
default Page<BusinessSubcategoryVo> selectPageBusinessSubcategoryList(Page<BusinessSubcategory> page, Wrapper<BusinessSubcategory> queryWrapper) {
return this.selectVoPage(page, queryWrapper);
}
//@DataPermission({
// @DataColumn(key = "deptName", value = "create_dept"),
// @DataColumn(key = "userName", value = "create_by")
//})
// List<SysUserExportVo> selectBusinessSubcategoryExportList(@Param(Constants.WRAPPER) Wrapper<BusinessSubcategory> queryWrapper);
}

View File

@ -0,0 +1,68 @@
package org.dromara.business.service;
import org.dromara.business.domain.vo.BusinessSubcategoryVo;
import org.dromara.business.domain.bo.BusinessSubcategoryBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 业务子类Service接口
*
* @author Lion Li
* @date 2025-07-15
*/
public interface IBusinessSubcategoryService {
/**
* 查询业务子类
*
* @param id 主键
* @return 业务子类
*/
BusinessSubcategoryVo queryById(Long id);
/**
* 分页查询业务子类列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 业务子类分页列表
*/
TableDataInfo<BusinessSubcategoryVo> queryPageList(BusinessSubcategoryBo bo, PageQuery pageQuery);
/**
* 查询符合条件的业务子类列表
*
* @param bo 查询条件
* @return 业务子类列表
*/
List<BusinessSubcategoryVo> queryList(BusinessSubcategoryBo bo);
/**
* 新增业务子类
*
* @param bo 业务子类
* @return 是否新增成功
*/
Boolean insertByBo(BusinessSubcategoryBo bo);
/**
* 修改业务子类
*
* @param bo 业务子类
* @return 是否修改成功
*/
Boolean updateByBo(BusinessSubcategoryBo bo);
/**
* 校验并批量删除业务子类信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
}

View File

@ -0,0 +1,136 @@
package org.dromara.business.service.impl;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.dromara.business.domain.bo.BusinessSubcategoryBo;
import org.dromara.business.domain.vo.BusinessSubcategoryVo;
import org.dromara.business.domain.BusinessSubcategory;
import org.dromara.business.mapper.BusinessSubcategoryMapper;
import org.dromara.business.service.IBusinessSubcategoryService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 业务子类Service业务层处理
*
* @author Lion Li
* @date 2025-07-15
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class BusinessSubcategoryServiceImpl implements IBusinessSubcategoryService {
private final BusinessSubcategoryMapper baseMapper;
/**
* 查询业务子类
*
* @param id 主键
* @return 业务子类
*/
@Override
public BusinessSubcategoryVo queryById(Long id){
return baseMapper.selectVoById(id);
}
/**
* 分页查询业务子类列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 业务子类分页列表
*/
@Override
public TableDataInfo<BusinessSubcategoryVo> queryPageList(BusinessSubcategoryBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<BusinessSubcategory> lqw = buildQueryWrapper(bo);
Page<BusinessSubcategoryVo> result = baseMapper.selectPageBusinessSubcategoryList(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的业务子类列表
*
* @param bo 查询条件
* @return 业务子类列表
*/
@Override
public List<BusinessSubcategoryVo> queryList(BusinessSubcategoryBo bo) {
LambdaQueryWrapper<BusinessSubcategory> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<BusinessSubcategory> buildQueryWrapper(BusinessSubcategoryBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<BusinessSubcategory> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(BusinessSubcategory::getId);
lqw.eq(bo.getCategoryId() != null, BusinessSubcategory::getCategoryId, bo.getCategoryId());
lqw.eq(bo.getTypeId() != null, BusinessSubcategory::getTypeId, bo.getTypeId());
lqw.like(StringUtils.isNotBlank(bo.getName()), BusinessSubcategory::getName, bo.getName());
lqw.eq(bo.getDeptId() != null, BusinessSubcategory::getDeptId, bo.getDeptId());
lqw.eq(StringUtils.isNotBlank(bo.getPricingRule()), BusinessSubcategory::getPricingRule, bo.getPricingRule());
return lqw;
}
/**
* 新增业务子类
*
* @param bo 业务子类
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(BusinessSubcategoryBo bo) {
BusinessSubcategory add = MapstructUtils.convert(bo, BusinessSubcategory.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改业务子类
*
* @param bo 业务子类
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(BusinessSubcategoryBo bo) {
BusinessSubcategory update = MapstructUtils.convert(bo, BusinessSubcategory.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(BusinessSubcategory entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除业务子类信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.system.mapper.BusinessSubcategoryMapper">
</mapper>

View File

@ -0,0 +1,27 @@
-- 业务大类表
CREATE TABLE `business_category` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` VARCHAR(100) NOT NULL COMMENT '业务类型名称',
`create_dept` VARCHAR(50) DEFAULT NULL COMMENT '创建部门',
`create_by` VARCHAR(50) DEFAULT NULL COMMENT '创建人',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(50) DEFAULT NULL COMMENT '更新人',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='业务大类表';
-- 业务子类表
CREATE TABLE `business_subcategory` (
`id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`category_id` INT(11) NOT NULL COMMENT '所属大类ID',
`type_id` INT(11) DEFAULT NULL COMMENT '业务类型ID',
`name` VARCHAR(100) NOT NULL COMMENT '业务子类名称',
`dept_id` INT(11) DEFAULT NULL COMMENT '关联部门ID',
`pricing_rule` TEXT COMMENT '计价规则说明',
`create_dept` VARCHAR(50) DEFAULT NULL COMMENT '创建部门',
`create_by` VARCHAR(50) DEFAULT NULL COMMENT '创建人',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(50) DEFAULT NULL COMMENT '更新人',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='业务子类表';