form: n2n supports

This commit is contained in:
devezhao 2020-11-15 19:34:07 +08:00
parent 797cf6839d
commit 3f3e09315d
14 changed files with 574 additions and 367 deletions

View file

@ -257,7 +257,7 @@
<dependency>
<groupId>com.github.devezhao</groupId>
<artifactId>persist4j</artifactId>
<version>3854adcc15</version>
<version>6957ebec45</version>
</dependency>
<dependency>
<groupId>cglib</groupId>

View file

@ -372,6 +372,7 @@ public class FormsBuilder extends FormsManager {
if (el.get("value") == null) {
if (dt == DisplayType.SERIES) {
el.put("value", autoValue);
} else {
Object defVal = FieldDefaultValueHelper.exprDefaultValue(fieldMeta);
if (defVal != null) {
@ -389,6 +390,10 @@ public class FormsBuilder extends FormsManager {
defVal = null;
}
}
// 多引用
else if (dt == DisplayType.N2NREFERENCE) {
defVal = FieldValueWrapper.instance.wrapN2NReference(defVal, EasyMeta.valueOf(fieldMeta));
}
el.put("value", defVal);
}

View file

@ -61,7 +61,8 @@ public class RecentlyUsedHelper {
List<ID> data = new ArrayList<>();
for (int i = 0; i < limit && i < cached.size(); i++) {
final ID raw = cached.get(i);
if (!(raw.getEntityCode() == EntityHelper.ClassificationData || Application.getPrivilegesManager().allowRead(user, raw))) {
if (!(raw.getEntityCode() == EntityHelper.ClassificationData
|| Application.getPrivilegesManager().allowRead(user, raw))) {
continue;
}

View file

@ -24,8 +24,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -34,6 +36,7 @@ import java.util.regex.Pattern;
*
* @author devezhao
* @since 2019/8/20
* @see FieldValueWrapper
*/
public class FieldDefaultValueHelper {
@ -115,6 +118,15 @@ public class FieldDefaultValueHelper {
} else if (dt == DisplayType.REFERENCE || dt == DisplayType.CLASSIFICATION) {
return ID.valueOf(valueExpr);
} else if (dt == DisplayType.N2NREFERENCE) {
String[] ids = valueExpr.split(",");
List<ID> idArray = new ArrayList<>();
for (String id : ids) {
if (ID.isId(id)) idArray.add(ID.valueOf(id));
}
return idArray.toArray(new ID[0]);
} else if (dt == DisplayType.BOOL) {
return BooleanUtils.toBoolean(valueExpr);

View file

@ -13,6 +13,7 @@ import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.metadata.MetadataException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application;
import com.rebuild.core.configuration.general.ClassificationManager;
@ -118,6 +119,8 @@ public class FieldValueWrapper {
return wrapDecimal(value, field);
} else if (dt == DisplayType.REFERENCE) {
return wrapReference(value, field);
} else if (dt == DisplayType.N2NREFERENCE) {
return wrapN2NReference(value, field);
} else if (dt == DisplayType.BOOL) {
return wrapBool(value, field);
} else if (dt == DisplayType.PICKLIST) {
@ -189,7 +192,7 @@ public class FieldValueWrapper {
* @return
* @see #wrapMixValue(ID, String)
*/
public JSON wrapReference(Object value, EasyMeta field) {
public JSONObject wrapReference(Object value, EasyMeta field) {
Object text = ((ID) value).getLabelRaw();
if (text == null) {
text = getLabelNotry((ID) value);
@ -202,6 +205,22 @@ public class FieldValueWrapper {
return wrapMixValue((ID) value, text == null ? null : text.toString());
}
/**
* @param value
* @param field
* @return
* @see #wrapReference(Object, EasyMeta)
*/
public JSONArray wrapN2NReference(Object value, EasyMeta field) {
ID[] ids = (ID[]) value;
JSONArray idArray = new JSONArray();
for (ID id : ids) {
idArray.add(wrapReference(id, field));
}
return idArray;
}
/**
* @param value
* @param field

View file

@ -8,20 +8,21 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.general;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.api.RespBody;
import com.rebuild.core.service.general.RecentlyUsedHelper;
import com.rebuild.core.support.general.FieldValueWrapper;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.BaseController;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 最近搜索针对引用字段
@ -30,36 +31,39 @@ import javax.servlet.http.HttpServletResponse;
* @author devezhao zhaofang123@gmail.com
* @since 2019/04/25
*/
@Controller
@RestController
@RequestMapping("/commons/search/")
public class RecentlyUsedSearchController extends BaseController {
@GetMapping("recently")
public void fetchRecently(HttpServletRequest request, HttpServletResponse response) {
public JSON fetchRecently(HttpServletRequest request) {
String entity = getParameterNotNull(request, "entity");
String type = getParameter(request, "type");
ID[] recently = RecentlyUsedHelper.gets(getRequestUser(request), entity, type);
writeSuccess(response, formatSelect2(recently, "最近使用"));
return formatSelect2(recently, getLang(request, "RecentlyUsed"));
}
@PostMapping("recently-add")
public void addRecently(HttpServletRequest request, HttpServletResponse response) {
public RespBody addRecently(HttpServletRequest request) {
ID id = getIdParameterNotNull(request, "id");
String type = getParameter(request, "type");
RecentlyUsedHelper.add(getRequestUser(request), id, type);
writeSuccess(response);
return RespBody.ok();
}
@PostMapping("recently-clean")
public void cleanRecently(HttpServletRequest request, HttpServletResponse response) {
public RespBody cleanRecently(HttpServletRequest request) {
String entity = getParameterNotNull(request, "entity");
String type = getParameter(request, "type");
RecentlyUsedHelper.clean(getRequestUser(request), entity, type);
writeSuccess(response);
return RespBody.ok();
}
/**
* 格式化成前端 select2 组件数据格式
* 格式化成前端 `select2` 组件数据格式
*
* @param idLabels
* @param groupName select2 分组 null 表示无分组
@ -74,8 +78,8 @@ public class RecentlyUsedSearchController extends BaseController {
}
data.add(JSONUtils.toJSONObject(
new String[]{"id", "text"},
new String[]{id.toLiteral(), label}));
new String[] { "id", "text" },
new String[] { id.toLiteral(), label }));
}
if (groupName != null) {

View file

@ -12,6 +12,7 @@ import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.dialect.FieldType;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.rebuild.api.RespBody;
import com.rebuild.core.Application;
import com.rebuild.core.configuration.general.ClassificationManager;
import com.rebuild.core.configuration.general.DataListManager;
@ -21,18 +22,19 @@ import com.rebuild.core.metadata.impl.DisplayType;
import com.rebuild.core.metadata.impl.EasyMeta;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.UserService;
import com.rebuild.core.service.NoRecordFoundException;
import com.rebuild.core.service.general.RecentlyUsedHelper;
import com.rebuild.core.service.query.ParseHelper;
import com.rebuild.core.support.general.FieldValueWrapper;
import com.rebuild.core.support.general.ProtocolFilterParser;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.EntityController;
import org.apache.commons.lang.ArrayUtils;
import com.rebuild.web.EntityParam;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
@ -48,22 +50,19 @@ import java.util.*;
* @see RecentlyUsedSearchController
* @since 08/24/2018
*/
@Controller
@RestController
@RequestMapping("/commons/search/")
public class ReferenceSearchController extends EntityController {
// 快速搜索引用字段
@GetMapping({"reference", "quick"})
public void referenceSearch(HttpServletRequest request, HttpServletResponse response) {
public JSON referenceSearch(@EntityParam Entity entity, HttpServletRequest request) {
final ID user = getRequestUser(request);
final String entity = getParameterNotNull(request, "entity");
final String field = getParameterNotNull(request, "field");
Entity metaEntity = MetadataHelper.getEntity(entity);
Field referenceField = metaEntity.getField(field);
if (referenceField.getType() != FieldType.REFERENCE) {
writeSuccess(response, JSONUtils.EMPTY_ARRAY);
return;
Field referenceField = entity.getField(field);
if (!(referenceField.getType() == FieldType.REFERENCE || referenceField.getType() == FieldType.REFERENCE_LIST)) {
return JSONUtils.EMPTY_ARRAY;
}
// 查询引用字段的实体
@ -83,59 +82,49 @@ public class ReferenceSearchController extends EntityController {
}
if (recently == null || recently.length == 0) {
writeSuccess(response, JSONUtils.EMPTY_ARRAY);
return JSONUtils.EMPTY_ARRAY;
} else {
writeSuccess(response, RecentlyUsedSearchController.formatSelect2(recently, null));
return RecentlyUsedSearchController.formatSelect2(recently, null);
}
return;
}
// 查询字段
Set<String> searchFields = ParseHelper.buildQuickFields(searchEntity, getParameter(request, "quickFields"));
if (searchFields.isEmpty()) {
LOG.warn("No fields of search found : " + searchEntity);
writeSuccess(response, JSONUtils.EMPTY_ARRAY);
return;
}
q = StringEscapeUtils.escapeSql(q);
String like = " like '%" + q + "%'";
String searchWhere = StringUtils.join(searchFields.iterator(), like + " or ") + like;
if (protocolFilter != null) {
searchWhere = "(" + searchWhere + ") and (" + protocolFilter + ')';
}
List<Object> result = resultSearch(searchWhere, searchEntity, true);
writeSuccess(response, result);
return buildResultSearch(searchEntity, getParameter(request, "quickFields"), q);
}
// 搜索指定实体的指定字段
@GetMapping("search")
public void search(HttpServletRequest request, HttpServletResponse response) {
public JSON search(@EntityParam Entity searchEntity, HttpServletRequest request) {
final ID user = getRequestUser(request);
final String entity = getParameterNotNull(request, "entity");
String q = getParameter(request, "q");
// 为空则加载最近使用的
if (StringUtils.isBlank(q)) {
String type = getParameter(request, "type");
ID[] recently = RecentlyUsedHelper.gets(user, entity, type);
ID[] recently = RecentlyUsedHelper.gets(user, searchEntity.getName(), type);
if (recently.length == 0) {
writeSuccess(response, JSONUtils.EMPTY_ARRAY);
return JSONUtils.EMPTY_ARRAY;
} else {
writeSuccess(response, RecentlyUsedSearchController.formatSelect2(recently, null));
return RecentlyUsedSearchController.formatSelect2(recently, null);
}
return;
}
final Entity searchEntity = MetadataHelper.getEntity(entity);
return buildResultSearch(searchEntity, getParameter(request, "quickFields"), q);
}
/**
* 构建查询
*
* @param searchEntity
* @param quickFields
* @param q
* @return
*/
private JSON buildResultSearch(Entity searchEntity, String quickFields, String q) {
// 查询字段
Set<String> searchFields = ParseHelper.buildQuickFields(searchEntity, getParameter(request, "quickFields"));
Set<String> searchFields = ParseHelper.buildQuickFields(searchEntity, quickFields);
if (searchFields.isEmpty()) {
LOG.warn("No fields of search found : " + searchEntity);
writeSuccess(response, ArrayUtils.EMPTY_STRING_ARRAY);
return;
return JSONUtils.EMPTY_ARRAY;
}
q = StringEscapeUtils.escapeSql(q);
@ -143,17 +132,16 @@ public class ReferenceSearchController extends EntityController {
String searchWhere = StringUtils.join(searchFields.iterator(), like + " or ") + like;
List<Object> result = resultSearch(searchWhere, searchEntity, true);
writeSuccess(response, result);
return (JSON) JSON.toJSON(result);
}
// 搜索分类字段
@GetMapping("classification")
public void searchClassification(HttpServletRequest request, HttpServletResponse response) {
public JSON searchClassification(@EntityParam Entity entity, HttpServletRequest request) {
final ID user = getRequestUser(request);
final String entity = getParameterNotNull(request, "entity");
final String field = getParameterNotNull(request, "field");
Field fieldMeta = MetadataHelper.getField(entity, field);
Field fieldMeta = entity.getField(field);
ID useClassification = ClassificationManager.instance.getUseClassification(fieldMeta, false);
String q = getParameter(request, "q");
@ -162,12 +150,12 @@ public class ReferenceSearchController extends EntityController {
String type = "d" + useClassification;
ID[] recently = RecentlyUsedHelper.gets(user, "ClassificationData", type);
if (recently.length == 0) {
writeSuccess(response, JSONUtils.EMPTY_ARRAY);
return JSONUtils.EMPTY_ARRAY;
} else {
writeSuccess(response, RecentlyUsedSearchController.formatSelect2(recently, null));
return RecentlyUsedSearchController.formatSelect2(recently, null);
}
return;
}
q = StringEscapeUtils.escapeSql(q);
int openLevel = ClassificationManager.instance.getOpenLevel(fieldMeta);
@ -176,7 +164,7 @@ public class ReferenceSearchController extends EntityController {
useClassification.toLiteral(), openLevel, q, q);
List<Object> result = resultSearch(sqlWhere, MetadataHelper.getEntity(EntityHelper.ClassificationData), false);
writeSuccess(response, result);
return (JSON) JSON.toJSON(result);
}
/**
@ -224,22 +212,34 @@ public class ReferenceSearchController extends EntityController {
// 获取记录的名称字段值
@GetMapping("read-labels")
public void referenceLabel(HttpServletRequest request, HttpServletResponse response) {
public RespBody referenceLabel(HttpServletRequest request) {
String ids = getParameter(request, "ids", null);
if (ids == null) {
writeSuccess(response);
return;
if (StringUtils.isBlank(ids)) {
return RespBody.ok();
}
// 不存在的记录不返回
boolean ignoreMiss = getBoolParameter(request, "ignoreMiss", false);
Map<String, String> labels = new HashMap<>();
for (String id : ids.split("\\|")) {
if (!ID.isId(id)) {
continue;
for (String id : ids.split("[|,]")) {
if (!ID.isId(id)) continue;
String label;
if (ignoreMiss) {
try {
label = FieldValueWrapper.getLabel(ID.valueOf(id));
labels.put(id, label);
} catch (NoRecordFoundException ignored) {
}
} else {
label = FieldValueWrapper.getLabelNotry(ID.valueOf(id));
labels.put(id, label);
}
String label = FieldValueWrapper.getLabelNotry(ID.valueOf(id));
labels.put(id, label);
}
writeSuccess(response, labels);
return RespBody.ok(labels);
}
/**

View file

@ -1211,6 +1211,10 @@
"NoInitEntityTips": "暂无可用业务实体。此安装步骤不是必须的,你仍可以继续安装",
"SelectInitEntityTips": "你可以选择需要的业务实体使用,或在安装完成后再进行添加",
"CantSelectInitEntityTips": "由于使用了已存在的 RB 数据库,因此此步骤不可用,你仍可以继续安装",
"DialPhone": "拨打电话",
"UnConf": "未配置",
"SomeUnConf": "{0}未配置",
"RecentlyUsed": "Recently",
"s.__": "状态",
"s.ApprovalState.DRAFT": "草稿",

View file

@ -298,7 +298,7 @@
<input
type="text"
autocomplete="off"
maxlength="200"
maxlength="420"
class="form-control form-control-sm J_defaultValue"
th:data-o="${fieldDefaultValue}"
th:value="${fieldDefaultValue}"

View file

@ -700,6 +700,17 @@ body.view-body {
color: #4285f4;
}
.select2-container--default .select2-selection--multiple::after {
content: '\f2f9';
font-family: 'Material-Design-Iconic-Font';
font-size: 1.6rem;
font-weight: 400;
line-height: 35px;
color: #404040;
position: absolute;
right: 10px;
}
textarea.row2x {
height: 52px !important;
resize: none;
@ -998,11 +1009,11 @@ a {
vertical-align: middle;
}
.datetime-field .input-group-append button {
.input-group.has-append .input-group-append button {
min-width: 38px;
}
.datetime-field .clean {
.input-group.has-append .clean {
position: absolute;
right: 45px;
top: 0;

View file

@ -9,7 +9,7 @@ const wpc = window.__PageConfig
const __gExtConfig = {}
const SHOW_REPEATABLE = ['TEXT', 'DATE', 'DATETIME', 'EMAIL', 'URL', 'PHONE', 'REFERENCE', 'CLASSIFICATION']
const SHOW_DEFAULTVALUE = ['TEXT', 'NTEXT', 'EMAIL', 'PHONE', 'URL', 'NUMBER', 'DECIMAL', 'DATE', 'DATETIME', 'BOOL', 'CLASSIFICATION', 'REFERENCE']
const SHOW_DEFAULTVALUE = ['TEXT', 'NTEXT', 'EMAIL', 'PHONE', 'URL', 'NUMBER', 'DECIMAL', 'DATE', 'DATETIME', 'BOOL', 'CLASSIFICATION', 'REFERENCE', 'N2NREFERENCE']
$(document).ready(function () {
const dt = wpc.fieldType
@ -28,7 +28,14 @@ $(document).ready(function () {
}
if (data.fieldLabel === '') return RbHighbar.create($L('PlsInputSome,FieldName'))
const dv = dt === 'CLASSIFICATION' || dt === 'REFERENCE' ? $('.J_defaultValue').attr('data-value-id') : $val('.J_defaultValue')
// 默认值
let dv = $val('.J_defaultValue')
if (dt === 'CLASSIFICATION' || dt === 'REFERENCE' || dt === 'N2NREFERENCE') {
dv = $('.J_defaultValue').attr('data-value-id') || ''
const odv = $('.J_defaultValue').attr('data-o') || ''
if (dv === odv) dv = null
}
if (dv) {
if (checkDefaultValue(dv, dt) === false) return
else data.defaultValue = dv
@ -114,6 +121,8 @@ $(document).ready(function () {
_handleClassification(extConfig)
} else if (dt === 'REFERENCE') {
_handleReference()
} else if (dt === 'N2NREFERENCE') {
_handleReference(true)
} else if (dt === 'BOOL') {
const $dv = $('.J_defaultValue')
if ($dv.data('o')) $dv.val($dv.data('o'))
@ -253,65 +262,6 @@ class AdvDateDefaultValue extends RbAlert {
}
}
const _handleReference = function () {
const referenceEntity = $('.J_referenceEntity').data('refentity')
let dataFilter = (wpc.extConfig || {}).referenceDataFilter
const saveFilter = function (res) {
if (res && res.items && res.items.length > 0) {
$('#referenceDataFilter').text(`${$L('AdvFiletrSeted')} (${res.items.length})`)
dataFilter = res
} else {
$('#referenceDataFilter').text($L('ClickSet'))
dataFilter = null
}
__gExtConfig.referenceDataFilter = dataFilter
}
dataFilter && saveFilter(dataFilter)
let advFilter
$('#referenceDataFilter').click(() => {
if (advFilter) {
advFilter.show()
} else {
renderRbcomp(<AdvFilter title={$L('SetAdvFiletr')} inModal={true} canNoFilters={true} entity={referenceEntity} filter={dataFilter} confirm={saveFilter} />, null, function () {
advFilter = this
})
}
})
// 默认值
const $dv = $('.J_defaultValue')
const $dvClear = $('.J_defaultValue-clear')
let _ReferenceSearcher
function _showSearcher() {
if (_ReferenceSearcher) {
_ReferenceSearcher.show()
} else {
const searchUrl = `${rb.baseUrl}/commons/search/reference-search?field=${wpc.fieldName}.${wpc.entityName}`
// eslint-disable-next-line react/jsx-no-undef
renderRbcomp(<ReferenceSearcher url={searchUrl} title={$L('SelectSome,DefaultValue')} />, function () {
_ReferenceSearcher = this
})
}
}
const $append = $(`<button class="btn btn-secondary mw-auto" type="button" title="${$L('SelectSome,DefaultValue')}"><i class="icon zmdi zmdi-search"></i></button>`).appendTo(
'.J_defaultValue-append'
)
$append.click(() => _showSearcher())
window.referenceSearch__call = function (s) {
s = s[0]
$dv.attr('data-value-id', s).val(s)
_loadRefLabel($dv, $dvClear)
_ReferenceSearcher.hide()
}
_loadRefLabel($dv, $dvClear)
}
const _handlePicklist = function (dt) {
$.get(`/admin/field/picklist-gets?entity=${wpc.entityName}&field=${wpc.fieldName}&isAll=false`, function (res) {
if (res.data.length === 0) {
@ -382,7 +332,10 @@ const _handleFile = function (extConfig) {
const _handleClassification = function (extConfig) {
const $dv = $('.J_defaultValue')
const $dvClear = $('.J_defaultValue-clear')
const $dvClear = $('.J_defaultValue-clear').click(() => {
$dv.attr('data-value-id', '').val('')
$dvClear.addClass('hide')
})
let _ClassificationSelector
function _showSelector(data) {
@ -422,19 +375,103 @@ const _handleClassification = function (extConfig) {
$append.click(() => _showSelector(res.data))
})
_loadRefLabel($dv, $dvClear)
_loadRefsLabel($dv, $dvClear)
}
const _loadRefLabel = function ($dv, $dvClear) {
const _handleReference = function (isN2N) {
const referenceEntity = $('.J_referenceEntity').data('refentity')
// 数据过滤
let dataFilter = (wpc.extConfig || {}).referenceDataFilter
const saveFilter = function (res) {
if (res && res.items && res.items.length > 0) {
$('#referenceDataFilter').text(`${$L('AdvFiletrSeted')} (${res.items.length})`)
dataFilter = res
} else {
$('#referenceDataFilter').text($L('ClickSet'))
dataFilter = null
}
__gExtConfig.referenceDataFilter = dataFilter
}
dataFilter && saveFilter(dataFilter)
let advFilter
$('#referenceDataFilter').click(() => {
if (advFilter) {
advFilter.show()
} else {
renderRbcomp(<AdvFilter title={$L('SetAdvFiletr')} inModal={true} canNoFilters={true} entity={referenceEntity} filter={dataFilter} confirm={saveFilter} />, null, function () {
advFilter = this
})
}
})
// 默认值
const $dv = $('.J_defaultValue')
const $dvClear = $('.J_defaultValue-clear').click(() => {
$dv.attr('data-value-id', '').val('')
$dvClear.addClass('hide')
})
let _ReferenceSearcher
function _showSearcher() {
if (_ReferenceSearcher) {
_ReferenceSearcher.show()
} else {
const searchUrl = `${rb.baseUrl}/commons/search/reference-search?field=${wpc.fieldName}.${wpc.entityName}`
// eslint-disable-next-line react/jsx-no-undef
renderRbcomp(<ReferenceSearcher url={searchUrl} title={$L('SelectSome,DefaultValue')} />, function () {
_ReferenceSearcher = this
})
}
}
const $append = $(`<button class="btn btn-secondary mw-auto" type="button" title="${$L('SelectSome,DefaultValue')}"><i class="icon zmdi zmdi-search"></i></button>`).appendTo(
'.J_defaultValue-append'
)
$dv.attr('readonly', true)
$append.click(() => _showSearcher())
window.referenceSearch__call = function (selected) {
let val
if (isN2N) {
let keepVal = $dv.attr('data-value-id')
if (keepVal) keepVal = keepVal.split(',')
else keepVal = []
selected.forEach((s) => {
if (!keepVal.contains(s)) keepVal.push(s)
})
val = keepVal.slice(0, 20).join(',')
} else {
val = selected[0]
}
$dv.attr('data-value-id', val).val(val)
_loadRefsLabel($dv, $dvClear)
_ReferenceSearcher.hide()
}
_loadRefsLabel($dv, $dvClear)
}
const _loadRefsLabel = function ($dv, $dvClear) {
const dvid = $dv.val()
if (dvid) {
$.get(`/commons/search/read-labels?ids=${dvid}`, (res) => {
if (res.data && res.data[dvid]) $dv.val(res.data[dvid])
$.get(`/commons/search/read-labels?ids=${dvid}&ignoreMiss=true`, (res) => {
if (res.data) {
const ids = []
const labels = []
for (let k in res.data) {
ids.push(k)
labels.push(res.data[k])
}
$dv.attr('data-value-id', ids.join(','))
$dv.val(labels.join(', '))
}
})
$dvClear.removeClass('hide').click(() => {
$dv.attr('data-value-id', '').val('')
$dvClear.addClass('hide')
})
$dvClear && $dvClear.removeClass('hide')
}
}

View file

@ -193,3 +193,105 @@ class ReferenceSearcher extends RbModal {
window.referenceSearch__dlg = this
}
}
// 删除确认
// eslint-disable-next-line no-unused-vars
class DeleteConfirm extends RbAlert {
constructor(props) {
super(props)
this.state = { enableCascades: false }
}
render() {
let message = this.props.message
if (!message) message = this.props.ids ? $L('DeleteSelectedSomeConfirm').replace('%d', this.props.ids.length) : $L('DeleteRecordConfirm')
return (
<div className="modal rbalert" ref={(c) => (this._dlg = c)} tabIndex="-1">
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="modal-header pb-0">
<button className="close" type="button" onClick={() => this.hide()}>
<span className="zmdi zmdi-close" />
</button>
</div>
<div className="modal-body">
<div className="text-center ml-6 mr-6">
<div className="text-danger">
<span className="modal-main-icon zmdi zmdi-alert-triangle" />
</div>
<div className="mt-3 text-bold">{message}</div>
{!this.props.entity ? null : (
<div className="mt-2">
<label className="custom-control custom-control-sm custom-checkbox custom-control-inline mb-2">
<input className="custom-control-input" type="checkbox" checked={this.state.enableCascade === true} onChange={() => this.enableCascade()} />
<span className="custom-control-label"> {$L('DeleteCasTips')}</span>
</label>
<div className={' ' + (this.state.enableCascade ? '' : 'hide')}>
<select className="form-control form-control-sm" ref={(c) => (this._cascades = c)} multiple>
{(this.state.cascadesEntity || []).map((item) => {
return (
<option key={`opt-${item[0]}`} value={item[0]}>
{item[1]}
</option>
)
})}
</select>
</div>
</div>
)}
<div className="mt-4 mb-3" ref={(c) => (this._btns = c)}>
<button className="btn btn-space btn-secondary" type="button" onClick={() => this.hide()}>
{$L('Cancel')}
</button>
<button className="btn btn-space btn-danger" type="button" onClick={() => this.handleDelete()}>
{$L('Delete')}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
enableCascade() {
this.setState({ enableCascade: !this.state.enableCascade })
if (!this.state.cascadesEntity) {
$.get(`/commons/metadata/references?entity=${this.props.entity}&permission=D`, (res) => {
this.setState({ cascadesEntity: res.data }, () => {
this.__select2 = $(this._cascades)
.select2({
placeholder: $L('SelectCasEntity'),
width: '88%',
})
.val(null)
.trigger('change')
})
})
}
}
handleDelete() {
let ids = this.props.ids || this.props.id
if (!ids || ids.length === 0) return
if (typeof ids === 'object') ids = ids.join(',')
const cascades = this.__select2 ? this.__select2.val().join(',') : ''
const $btns = $(this._btns).find('.btn').button('loading')
$.post(`/app/entity/record-delete?id=${ids}&cascades=${cascades}`, (res) => {
if (res.error_code === 0) {
if (res.data.deleted === res.data.requests) RbHighbar.success($L('SomeSuccess', 'Delete'))
else if (res.data.deleted === 0) RbHighbar.error($L('NotDeleteTips'))
else RbHighbar.success($L('SuccessDeletedXItems').replace('%d', res.data.deleted))
this.hide()
typeof this.props.deleteAfter === 'function' && this.props.deleteAfter()
} else {
RbHighbar.error(res.error_msg)
$btns.button('reset')
}
})
}
}

View file

@ -156,6 +156,7 @@ class RbFormModal extends React.Component {
// -- Usage
/**
* @param {*} props
* @param {*} newDlg
*/
static create(props, newDlg) {
if (newDlg === true) {
@ -418,7 +419,7 @@ class RbFormElement extends React.Component {
{editable && this.state.editMode && (
<div className="edit-oper">
<div className="btn-group shadow-sm">
<button type="button" className="btn btn-secondary" onClick={this.handleEditConfirm}>
<button type="button" className="btn btn-secondary" onClick={() => this.handleEditConfirm()}>
<i className="icon zmdi zmdi-check"></i>
</button>
<button type="button" className="btn btn-secondary" onClick={() => this.toggleEditMode(false)}>
@ -432,35 +433,6 @@ class RbFormElement extends React.Component {
)
}
// 渲染表单
renderElement() {
const value = arguments.length > 0 ? arguments[0] : this.state.value
return (
<input
ref={(c) => (this._fieldValue = c)}
className={`form-control form-control-sm ${this.state.hasError ? 'is-invalid' : ''}`}
title={this.state.hasError}
type="text"
value={value || ''}
onChange={this.handleChange}
onBlur={this.props.readonly ? null : this.checkValue}
readOnly={this.props.readonly}
maxLength={this.props.maxLength || 200}
/>
)
}
// 渲染视图
renderViewElement() {
let text = arguments.length > 0 ? arguments[0] : this.state.value
if (text && $empty(text)) text = null
return (
<React.Fragment>
<div className="form-control-plaintext">{text || <span className="text-muted"></span>}</div>
</React.Fragment>
)
}
componentDidMount() {
const props = this.props
if (!props.onView) {
@ -475,6 +447,97 @@ class RbFormElement extends React.Component {
this.onEditModeChanged(true)
}
/**
* 渲染表单
*/
renderElement() {
const value = arguments.length > 0 ? arguments[0] : this.state.value
return (
<input
ref={(c) => (this._fieldValue = c)}
className={`form-control form-control-sm ${this.state.hasError ? 'is-invalid' : ''}`}
title={this.state.hasError}
type="text"
value={value || ''}
onChange={this.handleChange}
onBlur={this.props.readonly ? null : this.checkValue}
readOnly={this.props.readonly}
maxLength={this.props.maxLength || 200}
/>
)
}
/**
* 渲染视图
*/
renderViewElement() {
let text = arguments.length > 0 ? arguments[0] : this.state.value
if (text && $empty(text)) text = null
return (
<React.Fragment>
<div className="form-control-plaintext">{text || <span className="text-muted"></span>}</div>
</React.Fragment>
)
}
/**
* 修改值表单组件字段值变化应调用此方法
*
* @param {*} e
* @param {*} checkValue
*/
handleChange(e, checkValue) {
const val = e.target.value
this.setState({ value: val }, () => checkValue === true && this.checkValue())
}
/**
* 清空值
*/
handleClear() {
this.setState({ value: '' }, () => this.checkValue())
}
/**
* 检查值
*/
checkValue() {
const err = this.isValueError()
const errMsg = err ? this.props.label + err : null
if (this.isValueUnchanged() && !this.props.$$$parent.isNew) {
if (err) this.props.$$$parent.setFieldValue(this.props.field, this.state.value, errMsg)
else this.props.$$$parent.setFieldUnchanged(this.props.field)
} else {
this.setState({ hasError: err })
this.props.$$$parent.setFieldValue(this.props.field, this.state.value, errMsg)
}
}
/**
* 无效值检查
*/
isValueError() {
if (this.props.nullable === false) {
const v = this.state.value
if (v && $.type(v) === 'array') return v.length === 0 ? $L('SomeNotEmpty').replace('{0}', '') : null
else return !v ? $L('SomeNotEmpty').replace('{0}', '') : null
}
}
/**
* 是否未修改
*/
isValueUnchanged() {
const oldv = this.state.newValue === undefined ? this.props.value : this.state.newValue
return $same(oldv, this.state.value)
}
/**
* 视图编辑-编辑状态改变
*
* @param {*} destroy
*/
onEditModeChanged(destroy) {
if (destroy) {
if (this.__select2) {
@ -490,18 +553,11 @@ class RbFormElement extends React.Component {
}
}
// 修改值表单组件字段值变化应调用此方法
handleChange(e, checkValue) {
const val = e.target.value
this.setState({ value: val }, () => checkValue === true && this.checkValue())
}
// 清空值
handleClear() {
this.setState({ value: '' }, () => this.checkValue())
}
// 编辑单个字段值
/**
* 视图编辑-编辑模式
*
* @param {*} mode
*/
toggleEditMode(mode) {
this.setState({ editMode: mode }, () => {
if (this.state.editMode) {
@ -514,44 +570,18 @@ class RbFormElement extends React.Component {
})
}
handleEditConfirm = () => {
/**
* 视图编辑-确认
*/
handleEditConfirm() {
this.props.$$$parent.saveSingleFieldValue && this.props.$$$parent.saveSingleFieldValue(this)
}
// 检查值
checkValue() {
const err = this.isValueError()
const errMsg = err ? this.props.label + err : null
if (this.isValueUnchanged() && !this.props.$$$parent.isNew) {
if (err) this.props.$$$parent.setFieldValue(this.props.field, this.state.value, errMsg)
else this.props.$$$parent.setFieldUnchanged(this.props.field)
} else {
this.setState({ hasError: err })
this.props.$$$parent.setFieldValue(this.props.field, this.state.value, errMsg)
}
}
// 无效值检查
isValueError() {
if (this.props.nullable === false) {
const v = this.state.value
if (v && $.type(v) === 'array') return v.length === 0 ? $L('SomeNotEmpty').replace('{0}', '') : null
else return !v ? $L('SomeNotEmpty').replace('{0}', '') : null
}
}
// 未修改
isValueUnchanged() {
const oldv = this.state.newValue === undefined ? this.props.value : this.state.newValue
return $same(oldv, this.state.value)
}
// Getter / Setter
// Getter
setValue(val) {
this.handleChange({ target: { value: val } }, true)
}
// Setter
getValue() {
return this.state.value
}
@ -570,7 +600,8 @@ class RbFormUrl extends RbFormText {
renderViewElement() {
if (!this.state.value) return super.renderViewElement()
const clickUrl = rb.baseUrl + '/commons/url-safe?url=' + encodeURIComponent(this.state.value)
const clickUrl = `${rb.baseUrl}/commons/url-safe?url=${encodeURIComponent(this.state.value)}`
return (
<div className="form-control-plaintext">
<a href={clickUrl} className="link" target="_blank" rel="noopener noreferrer">
@ -594,9 +625,10 @@ class RbFormEMail extends RbFormText {
renderViewElement() {
if (!this.state.value) return super.renderViewElement()
return (
<div className="form-control-plaintext">
<a title={$L('SendEmail')} href={'mailto:' + this.state.value} className="link">
<a title={$L('SendEmail')} href={`mailto:${this.state.value}`} className="link">
{this.state.value}
</a>
</div>
@ -617,9 +649,10 @@ class RbFormPhone extends RbFormText {
renderViewElement() {
if (!this.state.value) return super.renderViewElement()
return (
<div className="form-control-plaintext">
<a title="拨打电话" href={'tel:' + this.state.value} className="link">
<a title={$L('DialPhone')} href={`tel:${this.state.value}`} className="link">
{this.state.value}
</a>
</div>
@ -646,7 +679,6 @@ class RbFormNumber extends RbFormText {
if (!!this.state.value && $isTrue(this.props.notNegative) && parseFloat(this.state.value) < 0) return $L('SomeNotNegative').replace('{0}', '')
return null
}
_isValueError() {
return super.isValueError()
}
@ -707,6 +739,7 @@ class RbFormTextarea extends RbFormElement {
renderViewElement() {
if (!this.state.value) return super.renderViewElement()
return (
<div className="form-control-plaintext" ref={(c) => (this._textarea = c)}>
{this.state.value.split('\n').map((line, idx) => {
@ -736,8 +769,9 @@ class RbFormDateTime extends RbFormElement {
renderElement() {
if (this.props.readonly) return super.renderElement()
return (
<div className="input-group datetime-field">
<div className="input-group has-append">
<input
ref={(c) => (this._fieldValue = c)}
className={'form-control form-control-sm ' + (this.state.hasError ? 'is-invalid' : '')}
@ -783,6 +817,7 @@ class RbFormDateTime extends RbFormElement {
const val = $(this).val()
that.handleChange({ target: { value: val } }, true)
})
$(this._fieldValue__icon).click(() => this.__datetimepicker.datetimepicker('show'))
}
}
@ -812,7 +847,7 @@ class RbFormImage extends RbFormElement {
return (
<span key={'img-' + item}>
<a title={$fileCutName(item)} className="img-thumbnail img-upload">
<img src={`${rb.baseUrl}/filex/img/${item}?imageView2/2/w/100/interlace/1/q/100`} />
<img src={`${rb.baseUrl}/filex/img/${item}?imageView2/2/w/100/interlace/1/q/100`} alt="IMG" />
{!this.props.readonly && (
<b title={$L('Remove')} onClick={() => this.removeItem(item)}>
<span className="zmdi zmdi-close"></span>
@ -845,7 +880,7 @@ class RbFormImage extends RbFormElement {
return (
<span key={'img-' + item}>
<a title={$fileCutName(item)} onClick={() => (parent || window).RbPreview.create(value, idx)} className="img-thumbnail img-upload zoom-in">
<img src={`${rb.baseUrl}/filex/img/${item}?imageView2/2/w/100/interlace/1/q/100`} />
<img src={`${rb.baseUrl}/filex/img/${item}?imageView2/2/w/100/interlace/1/q/100`} alt="IMG" />
</a>
</span>
)
@ -980,13 +1015,13 @@ class RbFormPickList extends RbFormElement {
renderElement() {
if (this.props.readonly) return super.renderElement(__findOptionText(this.state.options, this.props.value))
const name = `${this.state.field}-opt-`
const keyName = `${this.state.field}-opt-`
return (
<select ref={(c) => (this._fieldValue = c)} className="form-control form-control-sm" value={this.state.value || ''} onChange={this.handleChange}>
<option value=""></option>
{this.state.options.map((item) => {
return (
<option key={`${name}${item.id}`} value={item.id}>
<option key={`${keyName}${item.id}`} value={item.id}>
{item.text}
</option>
)
@ -1037,10 +1072,10 @@ class RbFormReference extends RbFormElement {
const hasDataFilter = this.props.referenceDataFilter && (this.props.referenceDataFilter.items || []).length > 0
return (
<div className="input-group datetime-field">
<select ref={(c) => (this._fieldValue = c)} className="form-control form-control-sm" title={hasDataFilter ? $L('FieldUseDataFilter') : null} />
<div className="input-group has-append">
<select ref={(c) => (this._fieldValue = c)} className="form-control form-control-sm" title={hasDataFilter ? $L('FieldUseDataFilter') : null} multiple={this._multiple === true} />
<div className="input-group-append">
<button className="btn btn-secondary" type="button" onClick={this.showSearcher}>
<button className="btn btn-secondary" type="button" onClick={() => this.showSearcher()}>
<i className="icon zmdi zmdi-search" />
</button>
</div>
@ -1054,6 +1089,7 @@ class RbFormReference extends RbFormElement {
if (typeof value === 'string') return <div className="form-control-plaintext">{value}</div>
if (!value.id) return <div className="form-control-plaintext">{value.text}</div>
return (
<div className="form-control-plaintext">
<a href={`#!/View/${value.entity}/${value.id}`} onClick={this._clickView}>
@ -1062,6 +1098,9 @@ class RbFormReference extends RbFormElement {
</div>
)
}
_renderViewElement() {
super.renderViewElement()
}
_clickView = (e) => window.RbViewPage && window.RbViewPage.clickView(e.target)
@ -1089,14 +1128,15 @@ class RbFormReference extends RbFormElement {
const val = this.state.value
if (val) {
const o = new Option(val.text, val.id, true, true)
this.__select2.append(o).trigger('change')
this.setValue(val)
// const o = new Option(val.text, val.id, true, true)
// this.__select2.append(o).trigger('change')
}
const that = this
this.__select2.on('change', function (e) {
const v = e.target.value
if (v) {
const v = $(e.target).val()
if (v && typeof v === 'string') {
$.post(`/commons/search/recently-add?id=${v}`)
that.triggerAutoFillin(v)
}
@ -1125,21 +1165,15 @@ class RbFormReference extends RbFormElement {
const o = new Option(val.text, val.id, true, true)
this.__select2.append(o)
this.handleChange({ target: { value: val.id } }, true)
} else this.__select2.val(null).trigger('change')
} else {
this.__select2.val(null).trigger('change')
}
}
showSearcher = () => {
showSearcher() {
const that = this
window.referenceSearch__call = function (selected) {
selected = selected[0]
if ($(that._fieldValue).find(`option[value="${selected}"]`).length > 0) {
that.__select2.val(selected).trigger('change')
} else {
$.get(`/commons/search/read-labels?ids=${selected}`, (res) => {
const o = new Option(res.data[selected], selected, true, true)
that.__select2.append(o).trigger('change')
})
}
that.showSearcher_call(selected, that)
that.__searcher.hide()
}
@ -1153,6 +1187,77 @@ class RbFormReference extends RbFormElement {
})
}
}
showSearcher_call(selected, that) {
const first = selected[0]
if ($(that._fieldValue).find(`option[value="${first}"]`).length > 0) {
that.__select2.val(first).trigger('change')
} else {
$.get(`/commons/search/read-labels?ids=${first}`, (res) => {
const o = new Option(res.data[first], first, true, true)
that.__select2.append(o).trigger('change')
})
}
}
}
class RbFormN2NReference extends RbFormReference {
constructor(props) {
super(props)
this._multiple = true
}
renderViewElement() {
const value = this.state.value
if ($empty(value)) return super._renderViewElement()
if (typeof value === 'string') return <div className="form-control-plaintext">{value}</div>
if (!value.id) return <div className="form-control-plaintext">{value.text}</div>
return (
<div className="form-control-plaintext">
<a href={`#!/View/${value.entity}/${value.id}`} onClick={this._clickView}>
{value.text}
</a>
</div>
)
}
isValueUnchanged() {
let oldvArray = this.state.newValue || this.props.value || []
let oldv = []
oldvArray.forEach((s) => oldv.push(s.id))
return $same(oldv.join(','), this.state.value)
}
handleChange(e, checkValue) {
let val = e.target.value
if (typeof val === 'object') val = val.join(',')
this.setState({ value: val }, () => checkValue === true && this.checkValue())
}
setValue(val) {
if (val && val.length > 0) {
const ids = []
val.forEach((item) => {
const o = new Option(item.text, item.id, true, true)
this.__select2.append(o)
ids.push(item.id)
})
this.handleChange({ target: { value: ids.join(',') } }, true)
} else {
this.__select2.val(null).trigger('change')
}
}
showSearcher_call(selected, that) {
$.get(`/commons/search/read-labels?ids=${selected.join(',')}`, (res) => {
const val = []
for (let k in res.data) {
val.push({ id: k, text: res.data[k] })
}
that.setValue(val)
})
}
}
class RbFormClassification extends RbFormElement {
@ -1162,8 +1267,9 @@ class RbFormClassification extends RbFormElement {
renderElement() {
if (this.props.readonly) return super.renderElement(this.props.value ? this.props.value.text : null)
return (
<div className="input-group datetime-field">
<div className="input-group has-append">
<select ref={(c) => (this._fieldValue = c)} className="form-control form-control-sm" />
<div className="input-group-append">
<button className="btn btn-secondary" type="button" onClick={this.showSelector}>
@ -1233,7 +1339,9 @@ class RbFormClassification extends RbFormElement {
_setClassificationValue(s) {
if (!s.id) return
const data = this.__cached || {}
if (data[s.id]) {
this.__select2.val(s.id).trigger('change')
} else if (this._fieldValue) {
@ -1251,16 +1359,16 @@ class RbFormMultiSelect extends RbFormElement {
}
renderElement() {
const name = `checkbox-${this.props.field}`
const keyName = `checkbox-${this.props.field}-`
return (
<div className="mt-1" ref={(c) => (this._fieldValue__wrap = c)}>
{(this.props.options || []).length === 0 && <div className="text-danger">{$L('NoData')}</div>}
{(this.props.options || []).length === 0 && <div className="text-danger">{$L('UnConf')}</div>}
{(this.props.options || []).map((item) => {
return (
<label key={name + item.mask} className="custom-control custom-checkbox custom-control-inline">
<label key={keyName + item.mask} className="custom-control custom-checkbox custom-control-inline">
<input
className="custom-control-input"
name={name}
name={keyName}
type="checkbox"
checked={(this.state.value & item.mask) !== 0}
value={item.mask}
@ -1278,6 +1386,7 @@ class RbFormMultiSelect extends RbFormElement {
renderViewElement() {
const value = this.state.value
if (!value) return super.renderViewElement()
return (
<div className="form-control-plaintext">
{__findMultiTexts(this.props.options, value).map((item) => {
@ -1298,15 +1407,17 @@ class RbFormMultiSelect extends RbFormElement {
.each(function () {
maskValue += ~~$(this).val()
})
this.handleChange({ target: { value: maskValue === 0 ? null : maskValue } }, true)
}
}
const BoolOptions = {
T: $L('True'),
F: $L('False'),
}
class RbFormBool extends RbFormElement {
_Options = {
T: $L('True'),
F: $L('False'),
}
constructor(props) {
super(props)
}
@ -1324,7 +1435,7 @@ class RbFormBool extends RbFormElement {
onChange={this.changeValue}
disabled={this.props.readonly}
/>
<span className="custom-control-label">{$L('True')}</span>
<span className="custom-control-label">{this._Options['T']}</span>
</label>
<label className="custom-control custom-radio custom-control-inline">
<input
@ -1336,14 +1447,14 @@ class RbFormBool extends RbFormElement {
onChange={this.changeValue}
disabled={this.props.readonly}
/>
<span className="custom-control-label">{$L('False')}</span>
<span className="custom-control-label">{this._Options['F']}</span>
</label>
</div>
)
}
renderViewElement() {
return super.renderViewElement(this.state.value ? BoolOptions[this.state.value] : null)
return super.renderViewElement(this.state.value ? this._Options[this.state.value] : null)
}
changeValue = (e) => {
@ -1370,6 +1481,7 @@ class RbFormBarcode extends RbFormElement {
renderViewElement() {
if (!this.state.value) return super.renderViewElement()
const codeUrl = `${rb.baseUrl}/commons/barcode/render${this.props.barcodeType === 'QRCODE' ? '-qr' : ''}?t=${$encode(this.state.value)}`
return (
<div className="img-field barcode">
@ -1407,7 +1519,7 @@ class RbFormAvatar extends RbFormElement {
return (
<div className="img-field avatar">
<a className="img-thumbnail img-upload">
<img src={aUrl} />
<img src={aUrl} alt="Avatar" />
</a>
</div>
)
@ -1462,12 +1574,12 @@ class RbFormDivider extends React.Component {
render() {
return (
<div className="form-line hover" ref={(c) => (this._element = c)}>
<fieldset>{this.props.label && <legend onClick={this.toggle}>{this.props.label}</legend>}</fieldset>
<fieldset>{this.props.label && <legend onClick={() => this.toggle()}>{this.props.label}</legend>}</fieldset>
</div>
)
}
toggle = () => {
toggle() {
if (this.props.onView) {
let $next = $(this._element).parent()
while (($next = $next.next()).length > 0) {
@ -1514,6 +1626,8 @@ var detectElement = function (item) {
return <RbFormPickList {...item} />
} else if (item.type === 'REFERENCE') {
return <RbFormReference {...item} />
} else if (item.type === 'N2NREFERENCE') {
return <RbFormN2NReference {...item} />
} else if (item.type === 'CLASSIFICATION') {
return <RbFormClassification {...item} />
} else if (item.type === 'MULTISELECT') {
@ -1552,108 +1666,6 @@ const __findMultiTexts = function (options, maskValue) {
return texts
}
// 删除确认
// eslint-disable-next-line no-unused-vars
class DeleteConfirm extends RbAlert {
constructor(props) {
super(props)
this.state = { enableCascades: false }
}
render() {
let message = this.props.message
if (!message) message = this.props.ids ? $L('DeleteSelectedSomeConfirm').replace('%d', this.props.ids.length) : $L('DeleteRecordConfirm')
return (
<div className="modal rbalert" ref={(c) => (this._dlg = c)} tabIndex="-1">
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="modal-header pb-0">
<button className="close" type="button" onClick={() => this.hide()}>
<span className="zmdi zmdi-close" />
</button>
</div>
<div className="modal-body">
<div className="text-center ml-6 mr-6">
<div className="text-danger">
<span className="modal-main-icon zmdi zmdi-alert-triangle" />
</div>
<div className="mt-3 text-bold">{message}</div>
{!this.props.entity ? null : (
<div className="mt-2">
<label className="custom-control custom-control-sm custom-checkbox custom-control-inline mb-2">
<input className="custom-control-input" type="checkbox" checked={this.state.enableCascade === true} onChange={() => this.enableCascade()} />
<span className="custom-control-label"> {$L('DeleteCasTips')}</span>
</label>
<div className={' ' + (this.state.enableCascade ? '' : 'hide')}>
<select className="form-control form-control-sm" ref={(c) => (this._cascades = c)} multiple>
{(this.state.cascadesEntity || []).map((item) => {
return (
<option key={'option-' + item[0]} value={item[0]}>
{item[1]}
</option>
)
})}
</select>
</div>
</div>
)}
<div className="mt-4 mb-3" ref={(c) => (this._btns = c)}>
<button className="btn btn-space btn-secondary" type="button" onClick={() => this.hide()}>
{$L('Cancel')}
</button>
<button className="btn btn-space btn-danger" type="button" onClick={() => this.handleDelete()}>
{$L('Delete')}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
enableCascade() {
this.setState({ enableCascade: !this.state.enableCascade })
if (!this.state.cascadesEntity) {
$.get(`/commons/metadata/references?entity=${this.props.entity}&permission=D`, (res) => {
this.setState({ cascadesEntity: res.data }, () => {
this.__select2 = $(this._cascades)
.select2({
placeholder: $L('SelectCasEntity'),
width: '88%',
})
.val(null)
.trigger('change')
})
})
}
}
handleDelete() {
let ids = this.props.ids || this.props.id
if (!ids || ids.length === 0) return
if (typeof ids === 'object') ids = ids.join(',')
const cascades = this.__select2 ? this.__select2.val().join(',') : ''
const btns = $(this._btns).find('.btn').button('loading')
$.post(`/app/entity/record-delete?id=${ids}&cascades=${cascades}`, (res) => {
if (res.error_code === 0) {
if (res.data.deleted === res.data.requests) RbHighbar.success($L('SomeSuccess', 'Delete'))
else if (res.data.deleted === 0) RbHighbar.error($L('NotDeleteTips'))
else RbHighbar.success($L('SuccessDeletedXItems').replace('%d', res.data.deleted))
this.hide()
typeof this.props.deleteAfter === 'function' && this.props.deleteAfter()
} else {
RbHighbar.error(res.error_msg)
btns.button('reset')
}
})
}
}
// ~ 重复记录查看
class RepeatedViewer extends RbModalHandler {
constructor(props) {
@ -1677,7 +1689,7 @@ class RepeatedViewer extends RbModalHandler {
<tbody>
{data.map((item, idx) => {
if (idx === 0) return null
return this.renderLine(item, idx)
return this.renderRow(item, idx)
})}
</tbody>
</table>
@ -1685,7 +1697,7 @@ class RepeatedViewer extends RbModalHandler {
)
}
renderLine(item, idx) {
renderRow(item, idx) {
return (
<tr key={`row-${idx}`}>
{item.map((o, i) => {

View file

@ -492,7 +492,7 @@ var $initReferenceSelect2 = function (el, field) {
return $(el).select2({
placeholder: $L('SelectSome').replace('{0}', field.label),
minimumInputLength: 0,
maximumSelectionLength: 2,
maximumSelectionLength: $(el).attr('multiple') ? 999 : 2,
ajax: {
url: '/commons/search/' + (field.searchType || 'reference'),
delay: 300,