refactor: 统一字段命名并优化字典树查询逻辑

将字段名从下划线命名法改为驼峰命名法,如 `sort_no` 改为 `sortNo`。同时优化了字典树查询逻辑,使用 `Map<String, Object>` 替代 `Dict` 实体类,简化了数据处理流程。
This commit is contained in:
user 2025-03-21 00:02:14 +08:00
parent 1e9ab24918
commit 77c9943703
9 changed files with 145 additions and 90 deletions

View File

@ -59,7 +59,7 @@ public abstract class BaseRepository {
for (Map.Entry<String, Object> entry : params.entrySet()) { for (Map.Entry<String, Object> entry : params.entrySet()) {
try { try {
if (entry.getValue() == null) continue; if (entry.getValue() == null) continue;
String key = entry.getKey(); String key = entry.getKey().replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase();
Map<String, Object> condition; Map<String, Object> condition;
if (entry.getValue() instanceof String) { if (entry.getValue() instanceof String) {
condition = objectMapper.readValue((String) entry.getValue(), Map.class); condition = objectMapper.readValue((String) entry.getValue(), Map.class);
@ -102,7 +102,11 @@ public abstract class BaseRepository {
params = preprocessParams(params); params = preprocessParams(params);
String orderBy = pageable.getSort().isEmpty() String orderBy = pageable.getSort().isEmpty()
? "" ? ""
: pageable.getSort().stream().map(order -> order.getProperty() + " " + order.getDirection()).collect(Collectors.joining(", ")); : pageable
.getSort()
.stream()
.map(order -> order.getProperty().replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase() + " " + order.getDirection())
.collect(Collectors.joining(", "));
long page = pageable.getPageNumber(); long page = pageable.getPageNumber();
long size = pageable.getPageSize(); long size = pageable.getPageSize();
Map<String, Object> bindMap = new HashMap<>(); Map<String, Object> bindMap = new HashMap<>();
@ -113,7 +117,8 @@ public abstract class BaseRepository {
for (Map.Entry<String, Object> entry : params.entrySet()) { for (Map.Entry<String, Object> entry : params.entrySet()) {
try { try {
if (entry.getValue() == null) continue; if (entry.getValue() == null) continue;
String key = entry.getKey(); String keyOrg = entry.getKey();
String key = keyOrg.replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase();
Map<String, Object> condition; Map<String, Object> condition;
if (entry.getValue() instanceof String) { if (entry.getValue() instanceof String) {
condition = objectMapper.readValue((String) entry.getValue(), Map.class); condition = objectMapper.readValue((String) entry.getValue(), Map.class);
@ -128,7 +133,7 @@ public abstract class BaseRepository {
.append(fields.isEmpty() ? "" : ", ") .append(fields.isEmpty() ? "" : ", ")
.append(key) .append(key)
.append(" as ") .append(" as ")
.append(name.toString().isEmpty() ? key : name.toString()); .append(name.toString().isEmpty() ? keyOrg : name.toString());
} }
String operator = condition.get("op").toString(); String operator = condition.get("op").toString();
Object value = condition.get("value"); Object value = condition.get("value");
@ -167,6 +172,7 @@ public abstract class BaseRepository {
} }
// 执行查询并返回结果 // 执行查询并返回结果
//return executeSpec.fetch().all().map(this::convertKeysToCamelCase);
return executeSpec.fetch().all(); return executeSpec.fetch().all();
} }
} }

View File

@ -3,7 +3,9 @@ package com.vxnet.pms.service;
import com.vxnet.pms.domain.Dict; import com.vxnet.pms.domain.Dict;
import com.vxnet.pms.repository.DictRepository; import com.vxnet.pms.repository.DictRepository;
import com.vxnet.pms.security.SecurityUtils; import com.vxnet.pms.security.SecurityUtils;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
@ -71,35 +73,6 @@ public class DictService {
); );
} }
public Mono<Long> countDicts(String table, Map<String, Object> params) {
return SecurityUtils.getCurrentOrganization().flatMap(organization -> dictRepository.countRows(organization, table, params));
}
public Flux<Map<String, Object>> getDicts(String table, Map<String, Object> params, Pageable pageable) {
return SecurityUtils.getCurrentOrganization()
.map(organization -> dictRepository.getRows(organization, table, params, pageable))
.flatMapMany(dicts -> dicts);
}
public Mono<Long> countRootDicts(Map<String, Object> params) {
return SecurityUtils.getCurrentOrganization().flatMap(organization -> dictRepository.countRootDicts(organization, params));
}
public Flux<Dict> getDictTree(Map<String, Object> params, Pageable pageable) {
return SecurityUtils.getCurrentOrganization()
.map(organization -> dictRepository.findRootDicts(organization, params, pageable))
.flatMapMany(rootDicts ->
rootDicts.flatMap(rootDict ->
Mono.just(rootDict)
.zipWith(getChildren(rootDict.getOrganization(), rootDict.getNumber()).collectList())
.map(tuple -> {
rootDict.setChildren(tuple.getT2());
return rootDict;
})
)
);
}
private Flux<Dict> getChildren(String organization, String number) { private Flux<Dict> getChildren(String organization, String number) {
return dictRepository return dictRepository
.findByParentNumber(organization, number) .findByParentNumber(organization, number)
@ -112,4 +85,46 @@ public class DictService {
}) })
); );
} }
public Mono<Long> countDicts(String table, Map<String, Object> params) {
return SecurityUtils.getCurrentOrganization().flatMap(organization -> dictRepository.countRows(organization, table, params));
}
public Flux<Map<String, Object>> getDicts(String table, Map<String, Object> params, Pageable pageable) {
return SecurityUtils.getCurrentOrganization()
.map(organization -> dictRepository.getRows(organization, table, params, pageable))
.flatMapMany(dicts -> dicts);
}
public Flux<Map<String, Object>> getDictsTree(String table, Map<String, Object> params, Pageable pageable) {
return SecurityUtils.getCurrentOrganization()
.flatMapMany(organization ->
dictRepository
.getRows(organization, table, params, pageable)
.flatMap(dict -> {
params.put("parentNumber[op]", "=");
params.put("parentNumber[value]", dict.get("number").toString());
return Mono.just(dict)
.zipWith(getRowsChildren(organization, table, params).collectList())
.map(tuple -> {
dict.put("children", tuple.getT2());
return dict;
});
})
);
}
private Flux<Map<String, Object>> getRowsChildren(String organization, String table, Map<String, Object> params) {
return dictRepository
.getRows(organization, table, params, PageRequest.of(0, Integer.MAX_VALUE))
.flatMap(dict -> {
params.put("parentNumber[value]", dict.get("number").toString());
return Mono.just(dict)
.zipWith(getRowsChildren(organization, table, params).collectList())
.map(tuple -> {
dict.put("children", tuple.getT2());
return dict;
});
});
}
} }

View File

@ -41,7 +41,7 @@ public class CompanyResource {
"property", "property",
"remark", "remark",
"status", "status",
"sort_no", "sortNo",
"createdBy", "createdBy",
"createdDate", "createdDate",
"updatedBy", "updatedBy",

View File

@ -32,8 +32,8 @@ public class DictResource {
"property", "property",
"remark", "remark",
"status", "status",
"parent_number", "parentNumber",
"sort_no", "sortNo",
"createdBy", "createdBy",
"createdDate", "createdDate",
"updatedBy", "updatedBy",
@ -82,16 +82,18 @@ public class DictResource {
} }
@GetMapping("/dicts/tree") @GetMapping("/dicts/tree")
public Mono<ResponseEntity<Flux<Dict>>> getDictTree( public Mono<ResponseEntity<Flux<Map<String, Object>>>> getDictsTree(
@RequestParam Map<String, Object> params, @RequestParam Map<String, Object> params,
@org.springdoc.core.annotations.ParameterObject Pageable pageable @org.springdoc.core.annotations.ParameterObject Pageable pageable
) { ) {
String table = "jhi_dict";
if (!onlyContainsAllowedProperties(pageable)) { if (!onlyContainsAllowedProperties(pageable)) {
return Mono.just(ResponseEntity.badRequest().build()); return Mono.just(ResponseEntity.badRequest().build());
} }
return dictService return dictService
.countRootDicts(params) .countDicts(table, params)
.map(total -> ResponseEntity.ok().header("X-Total-Count", String.valueOf(total)).body(dictService.getDictTree(params, pageable)) .map(total ->
ResponseEntity.ok().header("X-Total-Count", String.valueOf(total)).body(dictService.getDictsTree(table, params, pageable))
); );
} }
} }

View File

@ -39,8 +39,8 @@ public class StockResource {
"property", "property",
"remark", "remark",
"status", "status",
"parent_number", "parentNumber",
"sort_no", "sortNo",
"createdBy", "createdBy",
"createdDate", "createdDate",
"updatedBy", "updatedBy",

View File

@ -41,18 +41,18 @@ export default defineComponent({
const [parentDictsRes, statusDictsRes] = await Promise.all([ const [parentDictsRes, statusDictsRes] = await Promise.all([
axios.get('api/dicts', { axios.get('api/dicts', {
params: { params: {
number: { name: 'number' }, number: { name: '' },
name: { name: 'name' }, name: { name: '' },
parent_number: { op: 'IFNULL', value: '' }, parentNumber: { op: 'IFNULL', value: '' },
status: { op: '=', value: '1' }, status: { op: '=', value: '1' },
}, },
}), }),
axios.get('api/dicts', { axios.get('api/dicts', {
params: { params: {
number: { name: 'number' }, number: { name: '' },
name: { name: 'name' }, name: { name: '' },
property: { name: 'property' }, property: { name: '' },
parent_number: { op: '=', value: 'StatusType' }, parentNumber: { op: '=', value: 'StatusType' },
status: { op: '=', value: '1' }, status: { op: '=', value: '1' },
}, },
}), }),

View File

@ -26,12 +26,23 @@ export default defineComponent({
const statusDicts = ref([]); const statusDicts = ref([]);
const showFilter = ref(false); const showFilter = ref(false);
const filterParams = ref({ const filterParams = ref({
number: null, number: { op: '=', value: null },
name: null, name: { op: '=', value: null },
property: null, property: { op: '=', value: null },
status: null, status: { op: '=', value: null },
}); });
const operatorSelect = [
{ value: '=', text: t$('entity.operator.equal') },
{ value: '>', text: t$('entity.operator.greaterThan') },
{ value: '<', text: t$('entity.operator.lessThan') },
{ value: '>=', text: t$('entity.operator.greaterOrEqual') },
{ value: '<=', text: t$('entity.operator.lessOrEqual') },
{ value: '!=', text: t$('entity.operator.notEqual') },
{ value: 'like', text: t$('entity.operator.like') },
{ value: 'not like', text: t$('entity.operator.notLike') },
];
// 父级字典选项 // 父级字典选项
const numberOptions = ref<SelectOption[]>([]); const numberOptions = ref<SelectOption[]>([]);
const isLoadingNumberOptions = ref(false); const isLoadingNumberOptions = ref(false);
@ -94,9 +105,9 @@ export default defineComponent({
isLoadingNumberOptions.value = true; isLoadingNumberOptions.value = true;
try { try {
const params = { const params = {
number: { name: 'number' }, number: { name: '' },
name: { name: 'name' }, name: { name: '' },
parent_number: { op: 'IFNULL', value: '' }, parentNumber: { op: 'IFNULL', value: '' },
status: { op: '=', value: '1' }, status: { op: '=', value: '1' },
}; };
//// 如果有查询条件,添加到请求参数中 //// 如果有查询条件,添加到请求参数中
@ -126,7 +137,7 @@ export default defineComponent({
handleSyncList(); handleSyncList();
}; };
const propOrder = ref('sort_no'); const propOrder = ref('sortNo');
const reverse = ref(false); const reverse = ref(false);
const sort = () => { const sort = () => {
@ -151,30 +162,40 @@ export default defineComponent({
const handleSyncList = async () => { const handleSyncList = async () => {
isFetching.value = true; isFetching.value = true;
try { try {
const processedParams = showFilter.value let params = Object.entries(dict.value).reduce((acc, [key]) => {
? Object.entries(filterParams.value).reduce((acc, [key, value]) => { acc[`${key}[name]`] = '';
acc[key] = value === '' ? null : value; if (key === 'parentNumber') {
acc[`${key}[op]`] = 'IFNULL';
acc[`${key}[value]`] = '';
}
return acc; return acc;
}, {}) }, {});
: {}; if (showFilter.value) {
params = Object.entries(filterParams.value).reduce((acc, [key, filterParam]) => {
if (filterParam.value !== null && filterParam.value !== '') {
acc[`${key}[op]`] = filterParam.op;
acc[`${key}[value]`] = filterParam.value;
}
return acc;
}, params);
}
console.log('params', params);
// 获取分页数据 // 获取分页数据
const paginationQuery = { const paginationQuery = {
page: page.value - 1, page: page.value - 1,
size: itemsPerPage.value, size: itemsPerPage.value,
sort: sort(), sort: sort(),
}; };
// 获取状态字典列表 // 获取状态字典列表
const [dictsRes, statusDictsRes] = await Promise.all([ const [dictsRes, statusDictsRes] = await Promise.all([
axios.get(`api/dicts/tree?${buildPaginationQuery(paginationQuery)}`, { axios.get(`api/dicts/tree?${buildPaginationQuery(paginationQuery)}`, {
params: processedParams, params: params,
}), }),
axios.get('api/dicts', { axios.get('api/dicts', {
params: { params: {
number: { name: 'number' }, number: { name: '' },
name: { name: 'name' }, name: { name: '' },
parent_number: { op: '=', value: 'StatusType' }, parentNumber: { op: '=', value: 'StatusType' },
status: { op: '=', value: '1' }, status: { op: '=', value: '1' },
}, },
}), }),
@ -246,6 +267,7 @@ export default defineComponent({
propOrder, propOrder,
reverse, reverse,
changeOrder, changeOrder,
operatorSelect,
handlePageSizeChange, handlePageSizeChange,
}; };
}, },

View File

@ -31,32 +31,38 @@
<vue-select <vue-select
class="form-control" class="form-control"
id="filter-number" id="filter-number"
v-model="filterParams.number" v-model="filterParams.number.value"
:options="numberOptions" :options="numberOptions"
:is-loading="isLoadingNumberOptions" :is-loading="isLoadingNumberOptions"
:placeholder="$t('entity.action.select')" :placeholder="$t('entity.action.select')"
@search="loadNumberOptions" @search="loadNumberOptions"
@select="option => (filterParams.number = option.value)" @select="option => (filterParams.number.value = option.value)"
/> />
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-control-label" for="filter-name">{{ $t('jewpmsApp.dict.name') }}</label> <label class="form-control-label" for="filter-name">{{ $t('jewpmsApp.dict.name') }}</label>
<input type="text" class="form-control" id="filter-name" v-model="filterParams.name" /> <b-input-group class="form-control-group">
<b-form-select id="filter-op" v-model="filterParams.name.op" :options="operatorSelect"></b-form-select>
<b-form-input id="filter-name" v-model="filterParams.name.value"></b-form-input>
</b-input-group>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-control-label" for="filter-property">{{ $t('jewpmsApp.dict.property') }}</label> <label class="form-control-label" for="filter-property">{{ $t('jewpmsApp.dict.property') }}</label>
<input type="text" class="form-control" id="filter-property" v-model="filterParams.property" /> <b-input-group class="form-control-group">
<b-form-select id="filter-op" v-model="filterParams.property.op" :options="operatorSelect"></b-form-select>
<b-form-input id="filter-property" v-model="filterParams.property.value"></b-form-input>
</b-input-group>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-control-label">{{ $t('jewpmsApp.dict.status') }}</label> <label class="form-control-label">{{ $t('jewpmsApp.dict.status') }}</label>
<div class="form-check form-check-inline"> <b-input-group class="form-control-group">
<input class="form-check-input" type="radio" id="status-active" :value="1" v-model="filterParams.status" /> <b-form-select id="filter-op" v-model="filterParams.status.op" :options="operatorSelect"></b-form-select>
<label class="form-check-label" for="status-active">生效</label> <b-form-select id="filter-status" v-model="filterParams.status.value">
</div> <option :value="null">{{ $t('entity.action.select') }}</option>
<div class="form-check form-check-inline"> <option :value="1">生效</option>
<input class="form-check-input" type="radio" id="status-inactive" :value="0" v-model="filterParams.status" /> <option :value="0">失效</option>
<label class="form-check-label" for="status-inactive">失效</label> </b-form-select>
</div> </b-input-group>
</div> </div>
</div> </div>
</div> </div>

View File

@ -70,23 +70,27 @@ export default defineComponent({
const handleSyncList = async () => { const handleSyncList = async () => {
isFetching.value = true; isFetching.value = true;
try { try {
const processedParams = showFilter.value let params = Object.entries(stock.value).reduce((acc, [key]) => {
? Object.entries(filterParams.value).reduce((acc, [key, filterParam]) => { acc[`${key}[name]`] = '';
return acc;
}, {});
if (showFilter.value) {
params = Object.entries(filterParams.value).reduce((acc, [key, filterParam]) => {
if (filterParam.value !== null && filterParam.value !== '') { if (filterParam.value !== null && filterParam.value !== '') {
acc[`${key}[op]`] = filterParam.op; acc[`${key}[op]`] = filterParam.op;
acc[`${key}[value]`] = filterParam.value; acc[`${key}[value]`] = filterParam.value;
} }
return acc; return acc;
}, {}) }, params);
: {}; }
const stocksRes = await axios.get( const stocksRes = await axios.get(
`api/stocks?${buildPaginationQuery({ `api/stocks?${buildPaginationQuery({
page: page.value - 1, page: page.value - 1,
size: itemsPerPage.value, size: itemsPerPage.value,
sort: sort(), sort: sort(),
...processedParams,
})}`, })}`,
{ params: params },
); );
stocks.value = stocksRes.data; stocks.value = stocksRes.data;
totalItems.value = stocksRes.headers['x-total-count'] || stocksRes.data.length; totalItems.value = stocksRes.headers['x-total-count'] || stocksRes.data.length;