enh: Grouping/Category

This commit is contained in:
RB 2022-11-21 23:11:27 +08:00
parent 37cd265246
commit 2c3e47258e
9 changed files with 170 additions and 66 deletions

2
@rbv

@ -1 +1 @@
Subproject commit bd85df4926ea0f249117ad90f7a0cee621834ce4
Subproject commit c3184f8196c0835c70942a2b9723838eebb2ebb6

View file

@ -7,6 +7,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.configuration.general;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Query;
@ -16,59 +17,67 @@ import com.alibaba.fastjson.JSONArray;
import com.rebuild.core.Application;
import com.rebuild.core.configuration.ConfigBean;
import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.impl.EasyEntityConfigProps;
import com.rebuild.core.support.general.FieldValueHelper;
import com.rebuild.utils.JSONUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang.StringUtils;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.*;
/**
* 列表字段分类数据
* 列表字段分类分组数据
*
* @author ZHAO
* @since 07/23/2022
*/
public class DataListCategory {
public static final DataListCategory instance = new DataListCategory();
private DataListCategory() {
}
/**
* @param entity
* @param user
* @return
*/
public static JSON datas(Entity entity, ID user) {
public JSON datas(Entity entity, ID user) {
final Field categoryField = getFieldOfCategory(entity);
if (categoryField == null) return null;
DisplayType dt = EasyMetaFactory.getDisplayType(categoryField);
EasyField easyField = EasyMetaFactory.valueOf(categoryField);
DisplayType dt = easyField.getDisplayType();
List<Object[]> clist = new ArrayList<>();
List<Object[]> dataList = new ArrayList<>();
if (dt == DisplayType.MULTISELECT || dt == DisplayType.PICKLIST) {
ConfigBean[] entries = MultiSelectManager.instance.getPickListRaw(categoryField, true);
for (ConfigBean e : entries) {
Object id = e.getID("id");
if (dt == DisplayType.MULTISELECT) id = e.getLong("mask");
clist.add(new Object[] { e.getString("text"), id });
dataList.add(new Object[] { e.getString("text"), id });
}
} else {
// TODO 考虑支持更多分组字段类型例如日期但要考虑日期格式
String sql;
if (dt == DisplayType.N2NREFERENCE) {
sql = MessageFormat.format(
"select distinct referenceId from NreferenceItem where belongEntity = ''{0}'' and belongField = ''{1}''",
entity.getName(), categoryField.getName());
} else {
String wrapField = categoryField.getName();
if (dt == DisplayType.DATETIME) {
wrapField = String.format("DATE_FORMAT(%s, '%%Y-%%m-%%d')", wrapField);
}
sql = MessageFormat.format(
"select distinct {0} from {1} where {0} is not null", categoryField.getName(), entity.getName());
"select {0} from {1} where {2} is not null group by {0}",
wrapField, entity.getName(), categoryField.getName());
}
Query query = user == null
@ -76,18 +85,38 @@ public class DataListCategory {
: Application.getQueryFactory().createQuery(sql, user);
Object[][] array = query.array();
String cf = EasyMetaFactory.valueOf(entity).getExtraAttr(EasyEntityConfigProps.ADV_LIST_SHOWCATEGORY);
String[] ff = cf.split(":");
String format = ff.length > 1 ? ff[1] : null;
Set<Object> unique = new HashSet<>();
for (Object[] o : array) {
Object id = o[0];
Object label = FieldValueHelper.getLabelNotry((ID) id);
clist.add(new Object[] { label, id });
}
Object label;
// TODO 分类数据 code 排序
clist.sort(Comparator.comparing(o -> o[0].toString()));
if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) {
format = StringUtils.defaultIfBlank(format, CalendarUtils.UTC_DATE_FORMAT);
if (id instanceof Date) {
label = CalendarUtils.format(format, (Date) id);
} else {
label = id.toString().substring(0, format.length());
}
id = label;
} else {
label = FieldValueHelper.getLabelNotry((ID) id);
}
if (unique.contains(id)) continue;
unique.add(id);
dataList.add(new Object[] { label, id });
}
}
JSONArray res = new JSONArray();
for (Object[] o : clist) {
for (Object[] o : dataList) {
res.add(JSONUtils.toJSONObject(
new String[] { "label", "id", "count" },
new Object[] { o[0], o[1], 0 } ));
@ -102,9 +131,10 @@ public class DataListCategory {
* @param entity
* @return
*/
public static Field getFieldOfCategory(Entity entity) {
public Field getFieldOfCategory(Entity entity) {
String categoryField = EasyMetaFactory.valueOf(entity).getExtraAttr(EasyEntityConfigProps.ADV_LIST_SHOWCATEGORY);
if (StringUtils.isBlank(categoryField) || !entity.containsField(categoryField)) return null;
return entity.getField(categoryField);
if (categoryField != null) categoryField = categoryField.split(":")[0];
if (categoryField != null && entity.containsField(categoryField)) return entity.getField(categoryField);
return null;
}
}

View file

@ -189,22 +189,22 @@ public class ProtocolFilterParser {
*/
protected String parseCategory(String entity, String value) {
Entity rootEntity = MetadataHelper.getEntity(entity);
Field classField = DataListCategory.getFieldOfCategory(rootEntity);
if (classField == null) return "(9=9)";
Field categoryField = DataListCategory.instance.getFieldOfCategory(rootEntity);
if (categoryField == null) return "(9=9)";
DisplayType dt = EasyMetaFactory.getDisplayType(classField);
DisplayType dt = EasyMetaFactory.getDisplayType(categoryField);
if (dt == DisplayType.MULTISELECT) {
return String.format("%s && %d", classField.getName(), ObjectUtils.toInt(value));
return String.format("%s && %d", categoryField.getName(), ObjectUtils.toInt(value));
} else if (dt == DisplayType.N2NREFERENCE) {
return String.format(
"exists (select recordId from NreferenceItem where ^%s = recordId and belongField = '%s' and referenceId = '%s')",
rootEntity.getPrimaryField().getName(), classField.getName(), StringEscapeUtils.escapeSql(value));
rootEntity.getPrimaryField().getName(), categoryField.getName(), StringEscapeUtils.escapeSql(value));
} else {
return String.format("%s = '%s'", classField.getName(), StringEscapeUtils.escapeSql(value));
return String.format("%s = '%s'", categoryField.getName(), StringEscapeUtils.escapeSql(value));
}
}
/**
* @param relatedExpr
* @param mainid

View file

@ -69,7 +69,7 @@ public class WidgetController extends BaseController implements ShareTo {
@GetMapping("widget-category-data")
public RespBody getCategoryData(@PathVariable String entity, HttpServletRequest request) {
JSON data = DataListCategory.datas(
JSON data = DataListCategory.instance.datas(
MetadataHelper.getEntity(entity), getRequestUser(request));
return RespBody.ok(data);
}

View file

@ -2216,7 +2216,6 @@
"请至少添加 1 个查询字段":"请至少添加 1 个查询字段",
"请填写固定值":"请填写固定值",
"@%s 退回了你的 %s 审批,请重新审核":"@%s 退回了你的 %s 审批,请重新审核",
"显示侧栏分类":"显示侧栏分类",
"转换明细记录需选择主记录":"转换明细记录需选择主记录",
"@%s 驳回了你的 %s 审批,请重新提交":"@%s 驳回了你的 %s 审批,请重新提交",
"如有多个类型请使用逗号或空格分开":"如有多个类型请使用逗号或空格分开",
@ -2265,7 +2264,7 @@
"退回至":"退回至",
"提交模式":"提交模式",
"启用提交模式必须选择审批流程":"启用提交模式必须选择审批流程",
"显示侧栏“分类”":"显示侧栏“分类”",
"显示侧栏“分组”":"显示侧栏“分组”",
"显示侧栏“常用查询”":"显示侧栏“常用查询”",
"仅提交不审批。选择的审批流程至少配置一个审批人,否则会提交失败":"仅提交不审批。选择的审批流程至少配置一个审批人,否则会提交失败",
"需要先添加 [记录转换](../transforms) 才能在此处选择":"需要先添加 [记录转换](../transforms) 才能在此处选择",
@ -2273,7 +2272,6 @@
"显示侧栏“图表”":"显示侧栏“图表”",
"选择的审批流程至少配置一个审批人":"选择的审批流程至少配置一个审批人",
"需要先添加 [审批流程](../approvals) 才能在此处选择":"需要先添加 [审批流程](../approvals) 才能在此处选择",
"Category":"分类",
"管理员撤销":"管理员撤销",
"部分功能可能需要商业版才能正常运行":"部分功能可能需要商业版才能正常运行",
"默认大小":"默认大小",
@ -2379,5 +2377,8 @@
"APP SECRET 已重置":"APP SECRET 已重置",
"修改 API 秘钥":"修改 API 秘钥",
"自动合并单元格":"自动合并单元格",
"重置后第三方应用需更换新的 APP SECRET 使用":"重置后第三方应用需更换新的 APP SECRET 使用"
"重置后第三方应用需更换新的 APP SECRET 使用":"重置后第三方应用需更换新的 APP SECRET 使用",
"分组":"分组",
"字段格式":"字段格式",
"分组字段":"分组字段"
}

View file

@ -44,6 +44,12 @@
.form-group .switch-button-xs {
margin-top: 7px;
}
.advListShowCategory-set {
border-radius: 2px;
border: 1px solid #eee;
padding: 15px;
margin-top: 10px;
}
</style>
</head>
<body>

View file

@ -99,7 +99,8 @@ function modeSave(newOption, next) {
})
}
const CLASS_TYPES = ['PICKLIST', 'MULTISELECT', 'CLASSIFICATION', 'REFERENCE', 'N2NREFERENCE']
// const CATE_TYPES = ['PICKLIST', 'MULTISELECT', 'CLASSIFICATION', 'DATE', 'DATETIME', 'REFERENCE', 'N2NREFERENCE']
const CATE_TYPES = ['PICKLIST', 'MULTISELECT', 'CLASSIFICATION', 'REFERENCE', 'N2NREFERENCE']
// 模式选项
class DlgMode1Option extends RbFormHandler {
@ -109,7 +110,7 @@ class DlgMode1Option extends RbFormHandler {
<div className="form">
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('显示侧栏常用查询')}</label>
<div className="col-sm-7">
<div className="col-sm-9">
<div className="switch-button switch-button-xs">
<input type="checkbox" id="advListHideFilters" defaultChecked={wpc.extConfig && !wpc.extConfig.advListHideFilters} />
<span>
@ -119,8 +120,8 @@ class DlgMode1Option extends RbFormHandler {
</div>
</div>
<div className="form-group row bosskey-show">
<label className="col-sm-3 col-form-label text-sm-right">{$L('显示侧栏')}</label>
<div className="col-sm-7">
<label className="col-sm-3 col-form-label text-sm-right">{$L('显示侧栏')}</label>
<div className="col-sm-9">
<div className="switch-button switch-button-xs">
<input type="checkbox" id="advListShowCategory" defaultChecked={wpc.extConfig && wpc.extConfig.advListShowCategory} />
<span>
@ -128,23 +129,41 @@ class DlgMode1Option extends RbFormHandler {
</span>
</div>
<div className="clearfix"></div>
<div className={`J_advListShowCategory mt-2 ${this.state.advListShowCategory ? '' : 'hide'}`}>
<select className="form-control form-control-sm">
{this.state.advListShowCategoryFields &&
this.state.advListShowCategoryFields.map((item) => {
return (
<option key={item.name} value={item.name}>
{item.label}
</option>
)
})}
</select>
<div className={`advListShowCategory-set ${this.state.advListShowCategory ? '' : 'hide'}`}>
<div className="row">
<div className="col-8">
<label className="mb-1">{$L('分组字段')}</label>
<select className="form-control form-control-sm">
{this.state.advListShowCategoryFields &&
this.state.advListShowCategoryFields.map((item) => {
return (
<option key={item.name} value={item.name}>
{item.label}
</option>
)
})}
</select>
</div>
<div className={`col-4 pl-0 ${this.state.advListShowCategoryFormats ? '' : 'hide'}`}>
<label className="mb-1">{$L('字段格式')}</label>
<select className="form-control form-control-sm" disabled>
{this.state.advListShowCategoryFormats &&
this.state.advListShowCategoryFormats.map((item) => {
return (
<option key={item[0]} value={item[0]}>
{item[1]}
</option>
)
})}
</select>
</div>
</div>
</div>
</div>
</div>
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('显示侧栏图表')}</label>
<div className="col-sm-7">
<div className="col-sm-9">
<div className="switch-button switch-button-xs">
<input type="checkbox" id="advListHideCharts" defaultChecked={wpc.extConfig && !wpc.extConfig.advListHideCharts} />
<span>
@ -155,7 +174,7 @@ class DlgMode1Option extends RbFormHandler {
</div>
<div className="form-group row bosskey-show">
<label className="col-sm-3 col-form-label text-sm-right">{$L('显示顶部查询面板')}</label>
<div className="col-sm-7">
<div className="col-sm-9">
<div className="switch-button switch-button-xs">
<input type="checkbox" id="advListFilterPane" defaultChecked={wpc.extConfig && wpc.extConfig.advListFilterPane} />
<span>
@ -165,7 +184,7 @@ class DlgMode1Option extends RbFormHandler {
</div>
</div>
<div className="form-group row footer">
<div className="col-sm-7 offset-sm-3" ref={(c) => (this._btns = c)}>
<div className="col-sm-9 offset-sm-3" ref={(c) => (this._btns = c)}>
<button className="btn btn-primary" type="button" onClick={this.save}>
{$L('确定')}
</button>
@ -180,8 +199,9 @@ class DlgMode1Option extends RbFormHandler {
}
componentDidMount() {
let $catFields, $catFormats
const that = this
let $class2
$('#advListShowCategory').on('change', function () {
if ($val(this)) {
that.setState({ advListShowCategory: true })
@ -189,19 +209,63 @@ class DlgMode1Option extends RbFormHandler {
that.setState({ advListShowCategory: null })
}
if (!$class2) {
$class2 = $('.J_advListShowCategory select')
if (!$catFields) {
$catFields = $('.advListShowCategory-set select:eq(0)')
$catFormats = $('.advListShowCategory-set select:eq(1)')
$.get(`/commons/metadata/fields?entity=${wpc.entityName}&deep=2`, (res) => {
const _data = []
res.data.forEach((item) => {
if (CLASS_TYPES.includes(item.type)) _data.push(item)
})
res.data &&
res.data.forEach((item) => {
if (CATE_TYPES.includes(item.type) && !(item.name.includes('owningDept.') || item.name.includes('owningUser.'))) {
_data.push(item)
}
})
// FIELD:[FORMAT]
let set = wpc.extConfig && wpc.extConfig.advListShowCategory ? wpc.extConfig.advListShowCategory : null
if (set) set = set.split(':')
wpc.extConfig.advListShowCategory
that.setState({ advListShowCategoryFields: _data }, () => {
$class2
.select2({ placeholder: $L('选择分类字段') })
.val((wpc.extConfig && wpc.extConfig.advListShowCategory) || null)
.trigger('change')
$catFields
.select2({
placeholder: $L('选择分类字段'),
allowClear: false,
})
.on('change', () => {
const s = $catFields.val()
const found = _data.find((x) => x.name === s)
let formats
if (found && found.type === 'CLASSIFICATION') {
formats = [
[0, $L('%d 级分类', 1)],
[1, $L('%d 级分类', 2)],
[2, $L('%d 级分类', 3)],
[3, $L('%d 级分类', 4)],
]
} else if (found && (found.type === 'DATE' || found.type === 'DATETIME')) {
formats = [
['yyyy', 'YYYY'],
['yyyy-MM', 'YYYY-MM'],
['yyyy-MM-dd', 'YYYY-MM-DD'],
]
}
that.setState({ advListShowCategoryFormats: formats }, () => {
$catFormats.val(null).trigger('change')
})
})
$catFormats.select2({ placeholder: $L('默认') })
if (set) {
$catFields.val(set[0]).trigger('change')
setTimeout(() => {
if (set[1]) $catFormats.val(set[1]).trigger('change')
}, 200)
}
})
})
}
@ -216,10 +280,13 @@ class DlgMode1Option extends RbFormHandler {
const o = {
advListHideFilters: !$val('#advListHideFilters'),
advListHideCharts: !$val('#advListHideCharts'),
advListShowCategory: this.state.advListShowCategory ? $val('.J_advListShowCategory select') : null,
advListFilterPane: $val('#advListFilterPane'),
}
if (this.state.advListShowCategory) {
o.advListShowCategory = `${$val('.advListShowCategory-set select:eq(0)')}:${$val('.advListShowCategory-set select:eq(1)') || ''}`
}
this.disabled(true)
modeSave(o, () => {
this.hide()

View file

@ -16,7 +16,7 @@
<div class="tab-container">
<ul class="nav nav-tabs">
<li class="nav-item" th:if="${advListHideFilters != 'true'}"><a class="nav-link" href="#asideFilters" data-toggle="tab">[[${bundle.L('常用查询')}]]</a></li>
<li class="nav-item" th:if="${advListShowCategory}"><a class="nav-link J_load-category" href="#asideCategory" data-toggle="tab">[[${bundle.L('Category')}]]</a></li>
<li class="nav-item" th:if="${advListShowCategory}"><a class="nav-link J_load-category" href="#asideCategory" data-toggle="tab">[[${bundle.L('分组')}]]</a></li>
<li class="nav-item" th:if="${advListHideCharts != 'true'}"><a class="nav-link J_load-charts" href="#asideWidgets" data-toggle="tab">[[${bundle.L('图表')}]]</a></li>
</ul>
<div class="tab-content rb-scroller">

View file

@ -16,7 +16,7 @@
<div class="tab-container">
<ul class="nav nav-tabs">
<li class="nav-item" th:if="${advListHideFilters != 'true'}"><a class="nav-link" href="#asideFilters" data-toggle="tab">[[${bundle.L('常用查询')}]]</a></li>
<li class="nav-item" th:if="${advListShowCategory}"><a class="nav-link J_load-category" href="#asideCategory" data-toggle="tab">[[${bundle.L('Category')}]]</a></li>
<li class="nav-item" th:if="${advListShowCategory}"><a class="nav-link J_load-category" href="#asideCategory" data-toggle="tab">[[${bundle.L('分组')}]]</a></li>
<li class="nav-item" th:if="${advListHideCharts != 'true'}"><a class="nav-link J_load-charts" href="#asideWidgets" data-toggle="tab">[[${bundle.L('图表')}]]</a></li>
</ul>
<div class="tab-content rb-scroller">