View related filter (#466)

* start 2.10

* view tab filter

* flag star
This commit is contained in:
RB 2022-05-15 18:13:38 +08:00 committed by GitHub
parent 7ba44f5a28
commit 18680ce4a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 210 additions and 40 deletions

View file

@ -10,7 +10,7 @@
</parent>
<groupId>com.rebuild</groupId>
<artifactId>rebuild</artifactId>
<version>2.9.0-beta2</version>
<version>2.10.0-dev</version>
<name>rebuild</name>
<description>Building your business-systems freely!</description>
<!-- UNCOMMENT USE TOMCAT -->

View file

@ -65,11 +65,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
/**
* Rebuild Version
*/
public static final String VER = "2.9.0-beta2";
public static final String VER = "2.10.0-dev";
/**
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
*/
public static final int BUILD = 2090002;
public static final int BUILD = 210000;
static {
// Driver for DB

View file

@ -24,10 +24,7 @@ import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.JSONUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* 视图-相关项/新建相关
@ -72,6 +69,42 @@ public class ViewAddonsManager extends BaseLayoutManager {
return vtabs;
}
/**
* 获取显示项过滤条件
*
* @param entity
* @param user
* @return
*/
public Map<String, JSONObject> getViewTabFilters(String entity, ID user) {
final ConfigBean config = getLayout(user, entity, TYPE_TAB);
if (config == null) return Collections.emptyMap();
// compatible: v2.2
JSON configJson = config.getJSON("config");
if (configJson instanceof JSONArray) {
configJson = JSONUtils.toJSONObject("items", configJson);
}
JSONArray items = ((JSONObject) configJson).getJSONArray("items");
if (items == null || items.isEmpty()) return Collections.emptyMap();
Map<String, JSONObject> filters = new HashMap<>();
for (Object o : items) {
// compatible: v2.8
if (!(o instanceof JSONArray)) continue;
JSONArray item = (JSONArray) o;
if (item.size() < 3) continue;
String entityKey = item.getString(0);
JSONObject filter = item.getJSONObject(2);
if (filter != null) filters.put(entityKey, filter);
}
return filters;
}
/**
* 新建项
*

View file

@ -14,11 +14,14 @@ import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.dialect.FieldType;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application;
import com.rebuild.core.configuration.general.ViewAddonsManager;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.service.approval.ApprovalState;
import com.rebuild.core.service.feeds.FeedsType;
import com.rebuild.core.service.query.AdvFilterParser;
import com.rebuild.core.service.query.ParseHelper;
import com.rebuild.core.service.query.QueryHelper;
import com.rebuild.core.support.general.FieldValueHelper;
@ -54,16 +57,20 @@ public class RelatedListController extends BaseController {
String related = getParameterNotNull(request, "related");
String q = getParameter(request, "q");
String sort = getParameter(request, "sort", "modifiedOn:desc");
String sql = buildBaseSql(mainid, related, q, false, user, null);
String sql = buildMainSql(mainid, related, q, false, user);
Entity relatedEntity = MetadataHelper.getEntity(related.split("\\.")[0]);
String sort = getParameter(request, "sort", "modifiedOn:desc");
// 名称字段排序
if ("NAME".equalsIgnoreCase(sort)) {
sort = relatedEntity.getNameField().getName() + ":asc";
}
sql += " order by " + sort.replace(":", " ");
int pn = NumberUtils.toInt(getParameter(request, "pageNo"), 1);
int ps = NumberUtils.toInt(getParameter(request, "pageSize"), 200);
Entity relatedEntity = MetadataHelper.getEntity(related.split("\\.")[0]);
Object[][] array = QueryHelper.createQuery(sql, relatedEntity).setLimit(ps, pn * ps - ps).array();
List<Object> res = new ArrayList<>();
@ -91,12 +98,16 @@ public class RelatedListController extends BaseController {
@GetMapping("related-counts")
public Map<String, Integer> relatedCounts(@IdParam(name = "mainid") ID mainid, HttpServletRequest request) {
final ID user = getRequestUser(request);
String[] relateds = getParameterNotNull(request, "relateds").split(",");
final ID user = getRequestUser(request);
// 附件过滤条件
Map<String, JSONObject> vtabFilters = ViewAddonsManager.instance.getViewTabFilters(
MetadataHelper.getEntity(mainid.getEntityCode()).getName(), user);
Map<String, Integer> countMap = new HashMap<>();
for (String related : relateds) {
String sql = buildMainSql(mainid, related, null, true, user);
String sql = buildBaseSql(mainid, related, null, true, user, vtabFilters);
if (sql != null) {
// 任务是获取了全部的相关记录因此总数可能与实际显示的条目数量不一致
@ -108,8 +119,9 @@ public class RelatedListController extends BaseController {
return countMap;
}
private String buildMainSql(ID recordOfMain, String relatedExpr, String q, boolean count, ID user) {
// Entity.Field
private String buildBaseSql(ID mainid, String relatedExpr, String q, boolean count, ID user,
Map<String, JSONObject> vtabFilters) {
// formatted: Entity.Field
String[] ef = relatedExpr.split("\\.");
Entity relatedEntity = MetadataHelper.getEntity(ef[0]);
@ -119,7 +131,7 @@ public class RelatedListController extends BaseController {
relatedFields.add(ef[1]);
} else {
// v1.9 之前会把所有相关的查出来
Entity mainEntity = MetadataHelper.getEntity(recordOfMain.getEntityCode());
Entity mainEntity = MetadataHelper.getEntity(mainid.getEntityCode());
for (Field field : relatedEntity.getFields()) {
if ((field.getType() == FieldType.REFERENCE || field.getType() == FieldType.ANY_REFERENCE)
&& ArrayUtils.contains(field.getReferenceEntities(), mainEntity)) {
@ -130,12 +142,26 @@ public class RelatedListController extends BaseController {
if (relatedFields.isEmpty()) return null;
String mainWhere = "(" + StringUtils.join(relatedFields, " = ''{0}'' or ") + " = ''{0}'')";
mainWhere = MessageFormat.format(mainWhere, recordOfMain);
String where = MessageFormat.format(
"(" + StringUtils.join(relatedFields, " = ''{0}'' or ") + " = ''{0}'')", mainid);
if (vtabFilters == null) {
vtabFilters = ViewAddonsManager.instance.getViewTabFilters(
MetadataHelper.getEntity(mainid.getEntityCode()).getName(), user);
}
// 附件过滤条件
JSONObject hasFilter = vtabFilters.get(relatedExpr);
if (ParseHelper.validAdvFilter(hasFilter)) {
String filterSql = new AdvFilterParser(hasFilter).toSqlWhere();
if (filterSql != null) {
where += " and " + filterSql;
}
}
// @see FeedsListController#fetchFeeds
if (relatedEntity.getEntityCode() == EntityHelper.Feeds) {
mainWhere += String.format(" and (type = %d or type = %d)",
where += String.format(" and (type = %d or type = %d)",
FeedsType.FOLLOWUP.getMask(),
FeedsType.SCHEDULE.getMask());
@ -144,7 +170,7 @@ public class RelatedListController extends BaseController {
for (Team t : Application.getUserStore().getUser(user).getOwningTeams()) {
in.add(String.format("scope = '%s'", t.getIdentity()));
}
mainWhere += " and ( " + StringUtils.join(in, " or ") + " )";
where += " and ( " + StringUtils.join(in, " or ") + " )";
}
if (StringUtils.isNotBlank(q)) {
@ -153,7 +179,7 @@ public class RelatedListController extends BaseController {
if (!searchFields.isEmpty()) {
String like = " like '%" + StringEscapeUtils.escapeSql(q) + "%'";
String searchWhere = " and ( " + StringUtils.join(searchFields.iterator(), like + " or ") + like + " )";
mainWhere += searchWhere;
where += searchWhere;
}
}
@ -173,7 +199,7 @@ public class RelatedListController extends BaseController {
}
}
sql.append(" from ").append(relatedEntity.getName()).append(" where ").append(mainWhere);
sql.append(" from ").append(relatedEntity.getName()).append(" where ").append(where);
return sql.toString();
}
}

View file

@ -27,6 +27,7 @@
<option value="SERIES">[[${bundle.L('自动编号')}]]</option>
<option value="DATE">[[${bundle.L('日期')}]]</option>
<option value="DATETIME">[[${bundle.L('日期时间')}]]</option>
<option value="TIME">[[${bundle.L('时间')}]]</option>
<option value="PICKLIST">[[${bundle.L('下拉列表')}]]</option>
<option value="CLASSIFICATION">[[${bundle.L('分类')}]]</option>
<option value="MULTISELECT">[[${bundle.L('多选')}]]</option>
@ -40,7 +41,6 @@
<option value="SIGN">[[${bundle.L('签名')}]]</option>
<option value="BOOL">[[${bundle.L('布尔')}]]</option>
<optgroup th:label="${bundle.L('保留类型')}" class="bosskey-show">
<option value="TIME">[[${bundle.L('时间')}]]</option>
<option value="STATE">[[${bundle.L('状态')}]]</option>
</optgroup>
</select>

View file

@ -25,6 +25,11 @@
.set-items .item > span {
margin: 0 4px;
}
.set-items .item.star > span::after {
content: '*';
margin-left: 4px;
font-weight: bold;
}
.axis-target .ui-sortable-placeholder.ui-state-highlight {
width: 7px;
height: 0;

View file

@ -3,6 +3,13 @@
<head>
<th:block th:replace="~{/_include/header}" />
<title>[[${bundle.L('视图配置')}]]</title>
<style>
.dd-item.star .dd3-content::after {
content: '*';
margin-left: 4px;
font-weight: bold;
}
</style>
</head>
<body class="dialog">
<div class="main-content">
@ -10,7 +17,7 @@
<div class="col-6 sortable-swap">
<h5 class="sortable-box-title">[[${bundle.L('已显示')}]]</h5>
<div class="sortable-box rb-scroller">
<ol class="dd-list J_config"></ol>
<ol class="dd-list J_config" th:_title="${bundle.L('无')}"></ol>
</div>
<i class="zmdi zmdi-swap"></i>
</div>

View file

@ -1794,6 +1794,14 @@ th.column-fixed {
height: 300px;
}
.sortable-box .dd-list:empty::before {
content: attr(_title);
color: #777;
font-style: italic;
padding: 7px;
display: block;
}
.sortable-box.h380 {
height: 388px;
}

View file

@ -8,7 +8,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
$(document).ready(function () {
const entity = $urlp('entity')
const $btn = $('.btn-primary').click(function () {
const $btn = $('.btn-primary').on('click', function () {
const fieldLabel = $val('#fieldLabel'),
type = $val('#type'),
comments = $val('#comments'),
@ -62,7 +62,7 @@ $(document).ready(function () {
let referenceLoaded = false
let classificationLoaded = false
$('#type').change(function () {
$('#type').on('change', function () {
parent.RbModal.resize()
$('.J_dt-REFERENCE, .J_dt-N2NREFERENCE, .J_dt-CLASSIFICATION, .J_dt-STATE').addClass('hide')

View file

@ -83,7 +83,7 @@ $(document).ready(function () {
'SERIES': $L('自动编号'),
'DATE': $L('日期'),
'DATETIME': $L('日期时间'),
// 'TIME': $L('时间'),
'TIME': $L('时间'),
'PICKLIST': $L('下拉列表'),
'CLASSIFICATION': $L('分类'),
'MULTISELECT': $L('多选'),

View file

@ -28,6 +28,8 @@ $(document).ready(function () {
const field = fields.find((x) => x.name === item.field)
render_set({ ...item, name: item.field, specLabel: item.label, label: field ? field.label : `[${item.field.toUpperCase()}]` })
})
refreshConfigStar()
}
parent.RbModal.resize()
@ -66,6 +68,8 @@ $(document).ready(function () {
else RbHighbar.error(res.error_msg)
})
})
$('.J_tips').on('closed.bs.alert', () => parent.RbModal.resize())
})
// 支持的计算类型
@ -121,6 +125,7 @@ const render_set = function (item) {
$item.attr({
'data-label': s.label || '',
})
refreshConfigStar()
}}
/>,
null,
@ -134,3 +139,11 @@ const render_set = function (item) {
}
})
}
const refreshConfigStar = function () {
$('.set-items > span').each(function () {
const $this = $(this)
if ($this.attr('data-label')) $this.find('.item').addClass('star')
else $this.find('.item').removeClass('star')
})
}

View file

@ -6,6 +6,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
*/
const _configLabels = {}
const _configFilters = {}
$(document).ready(function () {
const entity = $urlp('entity'),
@ -24,10 +25,14 @@ $(document).ready(function () {
// compatible: v2.8
if (typeof this !== 'string') {
key = this[0]
_configLabels[key] = this[1]
_configLabels[key] = this[1] // label
_configFilters[key] = this[2] // filter
}
$(`.unset-list li[data-key="${key}"]`).trigger('click')
})
refreshConfigStar()
$('#relatedAutoExpand').attr('checked', res.data.config.autoExpand === true)
$('#relatedAutoHide').attr('checked', res.data.config.autoHide === true)
}
@ -37,12 +42,13 @@ $(document).ready(function () {
}
})
const $btn = $('.J_save').click(function () {
const $btn = $('.J_save').on('click', () => {
let config = []
$('.J_config>li').each(function () {
const $this = $(this)
config.push([$this.data('key'), $this.attr('data-label') || ''])
const key = $(this).data('key')
config.push([key, _configLabels[key] || null, _configFilters[key] || null])
})
config = {
items: config,
autoExpand: $val('#relatedAutoExpand'),
@ -50,7 +56,7 @@ $(document).ready(function () {
}
$btn.button('loading')
$.post(url, JSON.stringify(config), function (res) {
$.post(url, JSON.stringify(config), (res) => {
$btn.button('reset')
if (res.error_code === 0) parent.location.reload()
else RbHighbar.error(res.error_msg)
@ -58,26 +64,47 @@ $(document).ready(function () {
})
})
const refreshConfigStar = function () {
$('.dd-list.J_config .dd-item').each(function () {
const key = $(this).data('key')
if (_configLabels[key] || _configFilters[key]) {
$(this).addClass('star')
} else {
$(this).removeClass('star')
}
})
}
const ShowStyles_Comps = {}
// 不支持条件
const _NO_FILTERS = ['ProjectTask', '', 'Feeds', 'Attachment']
// eslint-disable-next-line no-undef
render_item_after = function ($item) {
const key = $item.data('key')
const $a = $(`<a class="mr-1" title="${$L('显示样式')}"><i class="zmdi zmdi-edit"></i></a>`)
$item.find('.dd3-action>a').before($a)
$a.on('click', function () {
$a.on('click', () => {
if (ShowStyles_Comps[key]) {
ShowStyles_Comps[key].show()
} else {
const entity = key.split('.')[0]
renderRbcomp(
// eslint-disable-next-line react/jsx-no-undef
<ShowStyles
<ShowStyles2
label={_configLabels[key]}
onConfirm={(s) => {
$item.attr({
'data-label': s.label || '',
})
_configLabels[key] = s.label
refreshConfigStar()
}}
filter={_configFilters[key]}
filterShow={$urlp('type') === 'TAB' && !_NO_FILTERS.includes(entity)}
filterEntity={entity}
filterConfirm={(s) => {
_configFilters[key] = s
refreshConfigStar()
}}
/>,
null,
@ -88,3 +115,41 @@ render_item_after = function ($item) {
}
})
}
// eslint-disable-next-line no-undef
class ShowStyles2 extends ShowStyles {
constructor(props) {
super(props)
this.state = { filter: props.filter }
}
renderExtras() {
const fsl = this.state.filter && this.state.filter.items ? this.state.filter.items.length : 0
return (
this.props.filterShow && (
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('附加过滤条件')}</label>
<div className="col-sm-7">
<a className="btn btn-sm btn-link pl-0 text-left down-2" onClick={() => this.showFilter()}>
{fsl > 0 ? `${$L('已设置条件')} (${fsl})` : $L('点击设置')}
</a>
<p className="form-text mb-0 mt-0">{$L('符合过滤条件的数据才会在相关项列表中显示')}</p>
</div>
</div>
)
)
}
showFilter() {
parent._showFilterForAddons &&
parent._showFilterForAddons({
entity: this.props.filterEntity,
filter: this.state.filter,
onConfirm: (s) => {
if (s.items.length === 0) s = null // No items
this.props.filterConfirm(s)
this.setState({ filter: s })
},
})
}
}

View file

@ -431,3 +431,9 @@ class LightAttachmentList extends RelatedList {
})
}
}
// for view-addons.js
// eslint-disable-next-line no-unused-vars
var _showFilterForAddons = function (opt) {
renderRbcomp(<AdvFilter entity={opt.entity} filter={opt.filter} confirm={opt.onConfirm} title={$L('附加过滤条件')} inModal canNoFilters />)
}

View file

@ -26,6 +26,8 @@ class ShowStyles extends React.Component {
<input className="form-control form-control-sm" placeholder={$L('默认')} defaultValue={this.props.label || ''} maxLength="50" ref={(c) => (this._$label = c)} />
</div>
</div>
{this.renderExtras()}
<div className="form-group row footer">
<div className="col-sm-7 offset-sm-3">
<button className="btn btn-primary btn-space" type="button" onClick={() => this.saveProps()}>
@ -44,6 +46,10 @@ class ShowStyles extends React.Component {
)
}
renderExtras() {
return null
}
componentDidMount() {
$(this._$dlg).modal({ show: true, keyboard: true })
}

View file

@ -129,5 +129,6 @@
<script th:src="@{/assets/js/rb-view.append.js}" type="text/babel"></script>
<script th:src="@{/assets/js/rb-assignshare.js}" type="text/babel"></script>
<script th:src="@{/assets/js/rb-approval.js}" type="text/babel"></script>
<script th:src="@{/assets/js/rb-advfilter.js}" type="text/babel"></script>
</body>
</html>

View file

@ -3,7 +3,7 @@
<head>
<th:block th:replace="~{/_include/header}" />
<title>[[${bundle.L('导航菜单')}]]</title>
<style type="text/css">
<style>
.dd3-content > .zmdi {
position: absolute;
width: 28px;
@ -43,7 +43,7 @@
<div class="row m-0">
<div class="col-5 mt-2 pr-0">
<div class="sortable-box h380 rb-scroller">
<ol class="dd-list J_config"></ol>
<ol class="dd-list J_config" th:_title="${bundle.L('无')}"></ol>
</div>
<div class="actions">
<button type="button" class="btn btn-secondary btn-sm J_add-menu">+ [[${bundle.L('添加菜单项')}]]</button>