Ann read state 85 (#705)

* be: 通过地址定位

* be: Classificatio

* be: style

* dash darkmode

* fix: OverDateOperator

* bump lib

* feat: feed read status; sync H5

* Update @rbv

* be: style

* v3.6: 目标值为多引用时保持 `ID[]`

* Update @rbv

* Update trigger-design.js

* fix: class import state

* be: style

---------

Co-authored-by: devezhao <zhaofang123@gmail.com>
This commit is contained in:
REBUILD 企业管理系统 2024-01-04 21:25:36 +08:00 committed by GitHub
parent 64f2d1168b
commit d47dfb007f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 478 additions and 222 deletions

2
@rbv

@ -1 +1 @@
Subproject commit 94c40c80ab261ef0c5f3255421992543f2c0cc80
Subproject commit aee43fb969653ab507afca2c12896f8e7bc5989a

View file

@ -326,7 +326,7 @@
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.1.0</version>
<version>8.2.0</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
@ -381,7 +381,7 @@
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.11.0</version>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
@ -473,7 +473,7 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.21</version>
<version>5.8.22</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>

View file

@ -323,6 +323,7 @@ public class EntityHelper {
public static final int FeedsComment = 41;
public static final int FeedsLike = 42;
public static final int FeedsMention = 43;
public static final int FeedsStatus = 44;
// 项目

View file

@ -1,51 +0,0 @@
/*!
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/
package com.rebuild.core.service.trigger.aviator;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorType;
import java.util.Collection;
import java.util.Map;
/**
* Wrap {@link java.util.Collection} or Array
*
* @author devezhao
* @since 2021/4/12
*/
public class AviatorArray extends AviatorObject {
private static final long serialVersionUID = 5226071138800445209L;
final private Object arrayValue;
public AviatorArray(Collection<?> value) {
super();
this.arrayValue = value;
}
public AviatorArray(Object[] value) {
super();
this.arrayValue = value;
}
@Override
public int innerCompare(AviatorObject other, Map<String, Object> env) {
return 0;
}
@Override
public AviatorType getAviatorType() {
return AviatorType.JavaType;
}
@Override
public Object getValue(Map<String, Object> env) {
return this.arrayValue;
}
}

View file

@ -9,6 +9,7 @@ package com.rebuild.core.service.trigger.aviator;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorType;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
import java.util.Map;
@ -19,6 +20,7 @@ import java.util.Map;
* @author devezhao
* @since 2021/4/12
*/
@Slf4j
public class AviatorDate extends AviatorObject {
private static final long serialVersionUID = 2930549924386648595L;
@ -28,6 +30,7 @@ public class AviatorDate extends AviatorObject {
public static final String DU_HOUR = "H";
public static final String DU_MINUTE = "I";
public static final String DU_SECOND = "S";
public static final String DU_WEEKDAY = "W";
final private Date dateValue;
@ -38,7 +41,13 @@ public class AviatorDate extends AviatorObject {
@Override
public int innerCompare(AviatorObject other, Map<String, Object> env) {
return 0;
Object $date = other.getValue(env);
if ($date instanceof Date) {
return dateValue.compareTo((Date) $date);
}
log.warn("Could not compare " + desc(env) + " with " + other.desc(env));
return -1;
}
@Override

View file

@ -10,6 +10,7 @@ package com.rebuild.core.service.trigger.aviator;
import cn.devezhao.persist4j.engine.ID;
import com.googlecode.aviator.runtime.type.AviatorObject;
import com.googlecode.aviator.runtime.type.AviatorType;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
@ -19,6 +20,7 @@ import java.util.Map;
* @author devezhao
* @since 2023/4/16
*/
@Slf4j
public class AviatorId extends AviatorObject {
private static final long serialVersionUID = 7227725706972057446L;
@ -31,9 +33,15 @@ public class AviatorId extends AviatorObject {
@Override
public int innerCompare(AviatorObject other, Map<String, Object> env) {
Object $id = other.getValue(env);
if (idValue.equals($id)) {
return 0;
}
log.warn("Could not compare " + desc(env) + " with " + other.desc(env));
return -1;
}
@Override
public AviatorType getAviatorType() {
return AviatorType.JavaType;

View file

@ -44,15 +44,17 @@ public class AviatorUtils {
} catch (Exception ignored) {
}
AVIATOR.addOpFunction(OperatorType.ADD, new OverOperatorType.DateAdd());
AVIATOR.addOpFunction(OperatorType.SUB, new OverOperatorType.DateSub());
AVIATOR.addOpFunction(OperatorType.LE, new OverOperatorType.DateCompareLE());
AVIATOR.addOpFunction(OperatorType.LT, new OverOperatorType.DateCompareLT());
AVIATOR.addOpFunction(OperatorType.GE, new OverOperatorType.DateCompareGE());
AVIATOR.addOpFunction(OperatorType.GT, new OverOperatorType.DateCompareGT());
AVIATOR.addOpFunction(OperatorType.EQ, new OverOperatorType.DateCompareEQ());
AVIATOR.addOpFunction(OperatorType.NEQ, new OverOperatorType.DateCompareNEQ());
// 重载操作符
AVIATOR.addOpFunction(OperatorType.ADD, new OverDateOperator.DateAdd());
AVIATOR.addOpFunction(OperatorType.SUB, new OverDateOperator.DateSub());
AVIATOR.addOpFunction(OperatorType.LE, new OverDateOperator.DateCompareLE());
AVIATOR.addOpFunction(OperatorType.LT, new OverDateOperator.DateCompareLT());
AVIATOR.addOpFunction(OperatorType.GE, new OverDateOperator.DateCompareGE());
AVIATOR.addOpFunction(OperatorType.GT, new OverDateOperator.DateCompareGT());
AVIATOR.addOpFunction(OperatorType.EQ, new OverDateOperator.DateCompareEQ());
AVIATOR.addOpFunction(OperatorType.NEQ, new OverDateOperator.DateCompareNEQ());
// 自定义函数
addCustomFunction(new DateDiffFunction());
addCustomFunction(new DateAddFunction());
addCustomFunction(new DateSubFunction());

View file

@ -10,7 +10,7 @@ package com.rebuild.core.service.trigger.aviator;
import cn.devezhao.commons.CalendarUtils;
import com.googlecode.aviator.lexer.token.OperatorType;
import com.googlecode.aviator.runtime.function.AbstractFunction;
import com.googlecode.aviator.runtime.type.AviatorBoolean;
import com.googlecode.aviator.runtime.function.FunctionUtils;
import com.googlecode.aviator.runtime.type.AviatorLong;
import com.googlecode.aviator.runtime.type.AviatorObject;
@ -18,14 +18,14 @@ import java.util.Date;
import java.util.Map;
/**
* 操作符重载
* 日期相关操作符重载
*
* @author RB
* @since 2023/12/6
*/
public class OverOperatorType {
public class OverDateOperator {
private OverOperatorType() {}
private OverDateOperator() {}
// -- 计算
@ -49,7 +49,7 @@ public class OverOperatorType {
} else if ($argv2 instanceof Date && $argv1 instanceof Number) {
return opDate((Date) $argv2, ((Number) $argv1).intValue());
} else {
return arg1.add(arg2, env); // Use default
return arg1.add(arg2, env);
}
}
}
@ -77,7 +77,7 @@ public class OverOperatorType {
int diff = CalendarUtils.getDayLeft((Date) $argv1, (Date) $argv2);
return AviatorLong.valueOf(diff);
} else {
return arg1.add(arg2, env); // Use default
return arg1.sub(arg2, env);
}
}
}
@ -90,10 +90,15 @@ public class OverOperatorType {
// -- 比较
/**
* 日期比较
* 日期比较: LE `<=`
*/
static abstract class DateCompare extends AbstractFunction {
private static final long serialVersionUID = -4160230503581309424L;
static class DateCompareLE extends AbstractFunction {
private static final long serialVersionUID = 1321662048697121893L;
@Override
public String getName() {
return OperatorType.LE.getToken();
}
@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
Object $argv1 = arg1.getValue(env);
@ -102,86 +107,141 @@ public class OverOperatorType {
if ($argv1 instanceof Date && $argv2 instanceof Date) {
long v1 = ((Date) $argv1).getTime();
long v2 = ((Date) $argv2).getTime();
boolean b = compare(v1, v2);
return AviatorBoolean.valueOf(b);
return FunctionUtils.wrapReturn(v1 <= v2);
} else {
return arg1.add(arg2, env); // Use default
int s = arg1.compare(arg2, env);
return FunctionUtils.wrapReturn(s <= 0);
}
}
}
abstract protected boolean compare(long v1, long v2);
}
// LE `<=`
static class DateCompareLE extends DateCompare {
private static final long serialVersionUID = 1321662048697121893L;
@Override
public String getName() {
return OperatorType.LE.getToken();
}
@Override
protected boolean compare(long v1, long v2) {
return v1 <= v2;
}
}
// LT `<`
static class DateCompareLT extends DateCompare {
/**
* 日期比较: LT `<`
*/
static class DateCompareLT extends AbstractFunction {
private static final long serialVersionUID = 8197857653882782806L;
@Override
public String getName() {
return OperatorType.LT.getToken();
}
@Override
protected boolean compare(long v1, long v2) {
return v1 < v2;
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
Object $argv1 = arg1.getValue(env);
Object $argv2 = arg2.getValue(env);
if ($argv1 instanceof Date && $argv2 instanceof Date) {
long v1 = ((Date) $argv1).getTime();
long v2 = ((Date) $argv2).getTime();
return FunctionUtils.wrapReturn(v1 < v2);
} else {
int s = arg1.compare(arg2, env);
return FunctionUtils.wrapReturn(s < 0);
}
}
// GE `>=`
static class DateCompareGE extends DateCompare {
}
/**
* 日期比较: GE `>=`
*/
static class DateCompareGE extends AbstractFunction {
private static final long serialVersionUID = -7966630104916265372L;
@Override
public String getName() {
return OperatorType.GE.getToken();
}
@Override
protected boolean compare(long v1, long v2) {
return v1 >= v2;
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
Object $argv1 = arg1.getValue(env);
Object $argv2 = arg2.getValue(env);
if ($argv1 instanceof Date && $argv2 instanceof Date) {
long v1 = ((Date) $argv1).getTime();
long v2 = ((Date) $argv2).getTime();
return FunctionUtils.wrapReturn(v1 >= v2);
} else {
int s = arg1.compare(arg2, env);
return FunctionUtils.wrapReturn(s >= 0);
}
}
// GT `>`
static class DateCompareGT extends DateCompare {
}
/**
* 日期比较: GT `>`
*/
static class DateCompareGT extends AbstractFunction {
private static final long serialVersionUID = 5214573679573440753L;
@Override
public String getName() {
return OperatorType.GT.getToken();
}
@Override
protected boolean compare(long v1, long v2) {
return v1 > v2;
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
Object $argv1 = arg1.getValue(env);
Object $argv2 = arg2.getValue(env);
if ($argv1 instanceof Date && $argv2 instanceof Date) {
long v1 = ((Date) $argv1).getTime();
long v2 = ((Date) $argv2).getTime();
return FunctionUtils.wrapReturn(v1 > v2);
} else {
int s = arg1.compare(arg2, env);
return FunctionUtils.wrapReturn(s > 0);
}
}
// EQ `==`
static class DateCompareEQ extends DateCompare {
}
/**
* 日期比较: EQ `==`
*/
static class DateCompareEQ extends AbstractFunction {
private static final long serialVersionUID = -6142749075506832977L;
@Override
public String getName() {
return OperatorType.EQ.getToken();
}
@Override
protected boolean compare(long v1, long v2) {
return v1 == v2;
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
Object $argv1 = arg1.getValue(env);
Object $argv2 = arg2.getValue(env);
if ($argv1 instanceof Date && $argv2 instanceof Date) {
long v1 = ((Date) $argv1).getTime();
long v2 = ((Date) $argv2).getTime();
return FunctionUtils.wrapReturn(v1 == v2);
} else {
int s = arg1.compare(arg2, env);
return FunctionUtils.wrapReturn(s == 0);
}
}
// NEQ `!=`
static class DateCompareNEQ extends DateCompare {
}
/**
* 日期比较: NEQ `!=`
*/
static class DateCompareNEQ extends AbstractFunction {
private static final long serialVersionUID = -838391653977975466L;
@Override
public String getName() {
return OperatorType.NEQ.getToken();
}
@Override
protected boolean compare(long v1, long v2) {
return v1 != v2;
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
Object $argv1 = arg1.getValue(env);
Object $argv2 = arg2.getValue(env);
if ($argv1 instanceof Date && $argv2 instanceof Date) {
long v1 = ((Date) $argv1).getTime();
long v2 = ((Date) $argv2).getTime();
return FunctionUtils.wrapReturn(v1 != v2);
} else {
int s = arg1.compare(arg2, env);
return FunctionUtils.wrapReturn(s != 0);
}
}
}
}

View file

@ -27,6 +27,7 @@ import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyDateTime;
import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.easymeta.MultiValue;
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
import com.rebuild.core.privileges.UserService;
import com.rebuild.core.privileges.bizz.InternalPermission;
@ -52,6 +53,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -458,9 +460,15 @@ public class FieldWriteback extends FieldAggregation {
value = StringUtils.EMPTY;
}
} else if (isMultiField) {
// v3.6: 目标值为多引用时保持 `ID[]`
if (easyVarField.getDisplayType() == DisplayType.N2NREFERENCE
&& targetFieldEasy.getDisplayType() == DisplayType.N2NREFERENCE) {
value = StringUtils.join((ID[]) value, MultiValue.MV_SPLIT);
} else {
// force `TEXT`
EasyField fakeTextField = EasyMetaFactory.valueOf(MetadataHelper.getField("User", "fullName"));
value = easyVarField.convertCompatibleValue(value, fakeTextField);
}
} else if (value instanceof ID || forceUseQuote) {
value = value.toString();
}
@ -530,11 +538,13 @@ public class FieldWriteback extends FieldAggregation {
} else if (dt == DisplayType.N2NREFERENCE) {
String[] ids = value.toString().split(",");
List<String> idsList = new ArrayList<>();
Set<ID> idsSet = new LinkedHashSet<>();
for (String id : ids) {
if (ID.isId(id)) idsList.add(id);
id = id.trim();
if (ID.isId(id)) idsSet.add(ID.valueOf(id));
}
if (ids.length == idsList.size()) newValue = value.toString();
// v3.6: 目标值为多引用时保持 `ID[]`
newValue = idsSet.toArray(new ID[0]);
} else if (dt == DisplayType.BOOL) {

View file

@ -229,6 +229,7 @@ public class QiniuCloud {
*/
@SuppressWarnings("deprecation")
public long stats() {
// https://developer.qiniu.com/kodo/3906/statistic-interface
String time = CalendarUtils.getPlainDateFormat().format(CalendarUtils.now());
String url = String.format(
"%s/v6/space?bucket=%s&begin=%s000000&end=%s235959&g=day",

View file

@ -8,18 +8,22 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.feeds;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.api.RespBody;
import com.rebuild.core.Application;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.service.feeds.FeedsHelper;
import com.rebuild.core.service.feeds.FeedsScope;
import com.rebuild.utils.AppUtils;
import com.rebuild.web.BaseController;
import com.rebuild.web.IdParam;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@ -37,7 +41,7 @@ import java.util.List;
public class AnnouncementController extends BaseController {
@GetMapping("/commons/announcements")
public RespBody list(HttpServletRequest request) {
public RespBody announcementList(HttpServletRequest request) {
final ID user = AppUtils.getRequestUser(request);
int fromWhere = getIntParameter(request, "from", 0);
@ -67,22 +71,19 @@ public class AnnouncementController extends BaseController {
// 不在指定位置
int whereMask = options.getIntValue("showWhere");
if ((fromWhere & whereMask) == 0) {
continue;
}
if ((fromWhere & whereMask) == 0) continue;
// 不在展示时间
Date timeStart = parseTime(options.getString("timeStart"));
if (timeStart != null && timeNow < timeStart.getTime()) {
continue;
}
if (timeStart != null && timeNow < timeStart.getTime()) continue;
Date timeEnd = parseTime(options.getString("timeEnd"));
if (timeEnd != null && timeNow > timeEnd.getTime()) {
continue;
}
if (timeEnd != null && timeNow > timeEnd.getTime()) continue;
// 不可见
boolean allow = false;
String scope = (String) o[2];
if (FeedsScope.ALL.name().equalsIgnoreCase(scope)) {
allow = true;
@ -101,6 +102,17 @@ public class AnnouncementController extends BaseController {
a.put("publishOn", CalendarUtils.getUTCDateTimeFormat().format(o[4]).substring(0, 16));
a.put("publishBy", UserHelper.getName((ID) o[3]));
a.put("id", o[5]);
// v3.6 已读
if (options.getBooleanValue("reqRead")) a.put("readState", 1);
if (user != null) {
Object[] status = Application.createQueryNoFilter(
"select createdOn from FeedsStatus where createdBy = ? and feedsId = ?")
.setParameter(1, user)
.setParameter(2, o[5])
.unique();
if (status != null) a.put("readState", status[0]);
}
as.add(a);
}
}
@ -108,6 +120,15 @@ public class AnnouncementController extends BaseController {
return RespBody.ok(as);
}
@PostMapping("/commons/announcements/make-read")
public RespBody announcementMakeRead(@IdParam ID aid, HttpServletRequest request) {
final ID user = getRequestUser(request);
Record status = EntityHelper.forNew(EntityHelper.FeedsStatus, user);
status.setID("feedsId", aid);
Application.getCommonsService().create(status);
return RespBody.ok();
}
private Date parseTime(String time) {
if (StringUtils.isBlank(time)) return null;
return CalendarUtils.parse(time + (time.length() == 16 ? ":00" : ""));

View file

@ -242,7 +242,21 @@ public class FeedsListController extends BaseController {
// 更多内容
if (o[10] != null) {
item.put("contentMore", JSON.parse((String) o[10]));
JSON more = (JSON) JSON.parse((String) o[10]);
item.put("contentMore", more);
// 公告已读
if ((int) o[8] == FeedsType.ANNOUNCEMENT.getMask() && ((JSONObject) more).getBooleanValue("reqRead")) {
Object[][] status = Application.createQueryNoFilter(
"select createdBy,createdOn from FeedsStatus where feedsId = ? order by createdOn")
.setParameter(1, o[0])
.array();
List<Object> statusList = new ArrayList<>();
for (Object[] s : status) {
statusList.add(new Object[] { s[0], UserHelper.getName((ID) s[0]), s[1] });
}
item.put("readStatus", statusList);
}
}
return item;

View file

@ -197,8 +197,8 @@ public class ReferenceSearchController extends EntityController {
int openLevel = ClassificationManager.instance.getOpenLevel(fieldMeta);
String sqlWhere = String.format(
"dataId = '%s' and level = %d and (fullName like '%%%s%%' or quickCode like '%%%s%%') order by fullName",
useClassification.toLiteral(), openLevel, q, q);
"dataId = '%s' and level = %d and (fullName like '%%%s%%' or quickCode like '%%%s%%' or code like '%s%%') order by code,fullName",
useClassification.toLiteral(), openLevel, q, q, q);
List<Object> result = resultSearch(
sqlWhere, MetadataHelper.getEntity(EntityHelper.ClassificationData), 10);

View file

@ -463,6 +463,15 @@
<index field-list="user,feedsId,commentId"/>
</entity>
<entity name="FeedsStatus" type-code="044" description="动态扩展" queryable="false" parent="false">
<field name="statusId" type="primary"/>
<field name="feedsId" type="reference" ref-entity="Feeds" nullable="false" updatable="false" description="哪个动态" cascade="delete"/>
<field name="content" type="text" description="扩展内容"/>
<field name="createdBy" type="reference" ref-entity="User" nullable="false" creatable="false" updatable="false" description="创建人"/>
<field name="createdOn" type="timestamp" nullable="false" creatable="false" updatable="false" description="创建时间"/>
<index field-list="feedsId,createdBy,createdOn"/>
</entity>
<entity name="ProjectConfig" type-code="050" description="项目" queryable="false">
<field name="configId" type="primary"/>
<field name="projectName" type="string" max-length="100" nullable="false" description="项目名称"/>

View file

@ -665,6 +665,17 @@ create table if not exists `feeds_mention` (
index IX0_feeds_mention (`USER`, `FEEDS_ID`, `COMMENT_ID`)
)Engine=InnoDB;
-- ************ Entity [FeedsStatus] DDL ************
create table if not exists `feeds_status` (
`STATUS_ID` char(20) not null,
`FEEDS_ID` char(20) not null comment '哪个动态',
`CONTENT` text(65535) comment '扩展内容',
`CREATED_BY` char(20) not null comment '创建人',
`CREATED_ON` timestamp not null default current_timestamp comment '创建时间',
primary key (`STATUS_ID`),
index IX0_feeds_status (`FEEDS_ID`, `CREATED_BY`, `CREATED_ON`)
)Engine=InnoDB;
-- ************ Entity [ProjectConfig] DDL ************
create table if not exists `project_config` (
`CONFIG_ID` char(20) not null,
@ -886,4 +897,4 @@ insert into `project_plan_config` (`CONFIG_ID`, `PROJECT_ID`, `PLAN_NAME`, `SEQ`
-- DB Version (see `db-upgrade.sql`)
insert into `system_config` (`CONFIG_ID`, `ITEM`, `VALUE`)
values ('021-9000000000000001', 'DBVer', 54);
values ('021-9000000000000001', 'DBVer', 55);

View file

@ -1,6 +1,17 @@
-- Database upgrade scripts for rebuild 1.x and 2.x
-- Each upgraded starts with `-- #VERSION`
-- #55 (v3.6)
create table if not exists `feeds_status` (
`STATUS_ID` char(20) not null,
`FEEDS_ID` char(20) not null comment '哪个动态',
`CONTENT` text(65535) comment '扩展内容',
`CREATED_BY` char(20) not null comment '创建人',
`CREATED_ON` timestamp not null default current_timestamp comment '创建时间',
primary key (`STATUS_ID`),
index IX0_feeds_status (`FEEDS_ID`, `CREATED_BY`, `CREATED_ON`)
)Engine=InnoDB;
-- #54 (v3.5)
alter table `feeds`
add column `AUTO_LOCATION` varchar(100) comment '发布位置';

View file

@ -22,7 +22,7 @@
<div class="col-12 col-md-6">
<div class="dataTables_filter">
<div class="input-group input-search">
<input class="form-control" type="text" th:placeholder="${bundle.L('查询')}" maxlength="40" data-quickFields="&user,ipAddr" />
<input class="form-control" type="text" th:placeholder="${bundle.L('快速查询')}" maxlength="40" data-quickFields="&user,ipAddr" />
<span class="input-group-btn">
<button class="btn btn-secondary" type="button"><i class="icon zmdi zmdi-search"></i></button>
</span>

View file

@ -25,7 +25,7 @@
</div>
</div>
<div class="input-group input-search">
<input class="form-control" type="text" th:placeholder="${bundle.L('查询')}" maxlength="40" data-quickFields="recordId,recordName" />
<input class="form-control" type="text" th:placeholder="${bundle.L('快速查询')}" maxlength="40" data-quickFields="recordId,recordName" />
<span class="input-group-btn">
<button class="btn btn-secondary" type="button"><i class="icon zmdi zmdi-search"></i></button>
</span>

View file

@ -14,22 +14,36 @@
overflow: hidden;
text-overflow: ellipsis;
}
.table tr.group > td {
.table.group-table > thead > tr > th {
border-bottom-width: 0;
}
.table tr.group-title > td {
padding: 0;
}
.table tr.group > td > h5 {
padding: 8px 10px;
.table tr.group-title > td > h5 {
padding: 10px;
padding-left: 28px;
margin: 0;
background-color: #dee2e6;
background-color: #eee;
border-radius: 0;
position: relative;
}
.table tr.group > td > h5,
.table tr.group > td > h5 * {
color: #444;
.table tr.group-title > td > h5,
.table tr.group-title > td > h5 * {
color: #777;
font-weight: normal;
line-height: 1;
}
.table.group > thead > tr > th {
border-bottom-width: 0;
.table tr.group-title > td > h5::before {
content: '';
position: absolute;
top: 10px;
left: 9px;
width: 12px;
height: 12px;
background-color: #dee2e6;
border-radius: 50px;
border: 2px solid #fff;
}
</style>
</head>

View file

@ -102,9 +102,3 @@ See LICENSE and COMMERCIAL in the project root for license information.
padding: 7px;
display: block;
}
.modal-body .tab-container {
margin-left: -5px;
margin-right: -5px;
margin-top: -15px;
}

View file

@ -184,17 +184,8 @@ body.fullscreen .dash-right .J_darkmode {
display: inline-block;
}
body.fullscreen.darkmode {
background: #0a1b3b url(../img/datav-bg.png) no-repeat 0 0;
background-size: 100% 100%;
}
body.fullscreen.darkmode .grid-stack .grid-stack-item .grid-stack-item-content {
background-color: rgba(6, 30, 93, 0.5);
}
body.fullscreen.darkmode .grid-stack .grid-stack-item.fullscreen .grid-stack-item-content {
background-color: #0a1b3b;
body.fullscreen .announcement-wrapper {
display: none;
}
.grid-stack-item.fullscreen {
@ -238,3 +229,46 @@ body.fullscreen .tools-bar h4 {
.grid-stack-item.bgcolor .rb-spinner svg {
stroke: #fff;
}
/* darkmode */
body.fullscreen.darkmode {
background: #191a2c;
}
body.fullscreen.darkmode .grid-stack .grid-stack-item .grid-stack-item-content,
body.fullscreen.darkmode .chart-box.ApprovalList .progress-wrap,
body.fullscreen.darkmode .table-striped tbody tr:nth-of-type(odd) {
background-color: #24273e;
}
body.fullscreen.darkmode .chart .table,
body.fullscreen.darkmode .chart .table td > a {
background-color: transparent;
color: #fff;
}
body.fullscreen.darkmode .chart .table th {
background-color: #1e2033;
}
body.fullscreen.darkmode .chart .table thead,
body.fullscreen.darkmode .chart .table tbody,
body.fullscreen.darkmode .chart .table th,
body.fullscreen.darkmode .chart .table td,
body.fullscreen.darkmode .chart .table {
border-color: #191a2c;
}
body.fullscreen.darkmode .chart .table tr:hover,
body.fullscreen.darkmode .chart .table th:hover,
body.fullscreen.darkmode .chart .table tr.highlight,
body.fullscreen.darkmode .tcs-selected {
background: unset;
}
body.fullscreen.darkmode .ApprovalList thead th,
body.fullscreen.darkmode .ProjectTasks thead th,
body.fullscreen.darkmode .FeedsSchedule thead th {
padding-top: 7px;
}

View file

@ -33,7 +33,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
-webkit-appearance: none;
font-size: 1rem;
line-height: 1.6;
height: 55px;
height: 56px;
}
.rich-editor .action-btns {
@ -694,13 +694,13 @@ See LICENSE and COMMERCIAL in the project root for license information.
}
#collapseUser .aside-tree li > a {
padding-top: 5px;
padding-bottom: 5px;
padding-top: 6px;
padding-bottom: 6px;
}
#collapseUser .aside-tree li > a .avatar {
width: 26px;
height: 26px;
width: 24px;
height: 24px;
border-radius: 50%;
margin-right: 6px;
font-size: 0;
@ -714,3 +714,14 @@ See LICENSE and COMMERCIAL in the project root for license information.
min-height: 36px;
padding-top: 9px;
}
.read-status .user-show {
margin: 0;
margin-right: 3px;
margin-bottom: 3px;
}
.read-status .user-show .avatar {
width: 24px;
height: 24px;
}

View file

@ -134,7 +134,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
display: inline-block;
width: auto;
line-height: 1;
padding: 9px 20px;
padding: 9px 18px;
font-size: 1rem;
cursor: pointer;
border-radius: 6px !important;

View file

@ -1172,10 +1172,10 @@ a.link.link-icon::after {
cursor: default;
background-color: rgba(0, 0, 0, 0.3);
text-align: center;
line-height: 18px;
display: none;
width: 18px;
height: 18px;
height: 19px;
line-height: 19px;
}
.img-field.avatar label.img-upload b {
@ -3006,7 +3006,7 @@ form {
}
.user-show + .user-show {
margin-left: 10px;
margin-left: 6px;
}
.user-show:hover {
@ -4016,8 +4016,8 @@ a.icon-link > .zmdi {
line-height: 1.7;
font-size: 1.1rem;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
margin-bottom: 10px;
padding-bottom: 15px;
margin-bottom: 15px;
}
.announcement-contents img.emoji {

View file

@ -158,14 +158,6 @@ See LICENSE and COMMERCIAL in the project root for license information.
width: auto;
}
.file-field.attachments > .img-thumbnail b {
top: 0;
right: 0;
left: unset;
width: 28px;
padding-top: 12px;
}
#task-comment .comments,
#task-comment .comments .comment-reply {
margin: 0;

View file

@ -186,7 +186,7 @@ class HistoryViewport extends React.Component {
}
return (
<table className="table table-fixed group">
<table className="table table-fixed group-table">
<thead>
<tr>
<th width="25%">{$L('字段')}</th>
@ -198,7 +198,7 @@ class HistoryViewport extends React.Component {
{this.state.dataList.map((item, idx) => {
return (
<RF key={idx}>
<tr className="group">
<tr className="group-title">
<td colSpan="3">
<h5>{WrapHtml($L('**%s** %s %s', item[2].split(' UTC')[0], item[3], RevTypes[item[1]]))}</h5>
</td>

View file

@ -572,7 +572,7 @@ class ChartTreemap extends BaseChart {
series: [
{
data: data.data,
type: 'treemap',
type: 'treemap', // sunburst
width: '100%',
height: '100%',
top: window.render_preview_chart ? 0 : 15, // In preview

View file

@ -140,8 +140,12 @@ $(document).ready(() => {
$('.J_dash-fullscreen').on('click', () => {
const $body = $(document.body)
if ($body.hasClass('fullscreen')) $fullscreen.exit()
else $fullscreen.open()
if ($body.hasClass('fullscreen')) {
$fullscreen.exit()
} else {
$fullscreen.open()
$body.addClass('darkmode')
}
$body.toggleClass('fullscreen')
})

View file

@ -10,9 +10,12 @@ class AnnouncementModal extends React.Component {
state = { ...this.props }
render() {
const contentHtml = $converEmoji(this.props.content.replace(/\n/g, '<br/>'))
const props = this.props
const state = this.state
const contentHtml = $converEmoji(props.content.replace(/\n/g, '<br/>'))
return (
<div className="modal" tabIndex={this.state.tabIndex || -1} ref={(c) => (this._dlg = c)}>
<div className="modal" tabIndex={state.tabIndex || -1} ref={(c) => (this._dlg = c)}>
<div className="modal-dialog modal-dialog-centered">
<div className="modal-content">
<div className="modal-header pb-0">
@ -23,10 +26,25 @@ class AnnouncementModal extends React.Component {
<div className="modal-body">
<div className="text-break announcement-contents" dangerouslySetInnerHTML={{ __html: contentHtml }} />
<div>
<span className="float-left text-muted">{$L(' %s 发布于 %s', this.props.publishBy, this.props.publishOn)}</span>
<span className="float-left text-muted">{$L(' %s 发布于 %s', props.publishBy, props.publishOn)}</span>
<span className="float-right">
<a href={`${rb.baseUrl}/app/redirect?id=${this.props.id}`}>{$L('前往动态查看')}</a>
<a href={`${rb.baseUrl}/app/redirect?id=${props.id}`}>{$L('前往动态查看')}</a>
</span>
{state.readState && (
<span className="float-right mr-2">
{state.readState === 1 && (
<a className="text-primary" onClick={() => this._makeRead()}>
<i className="icon zmdi zmdi-check text-bold" /> {$L('标记已读')}
</a>
)}
{state.readState !== 1 && (
<a className="text-muted">
<i className="icon zmdi zmdi-check text-bold" /> {$L('已读')}
</a>
)}
<span className="text-muted text-bold ml-2">·</span>
</span>
)}
<span className="clearfix" />
</div>
</div>
@ -46,6 +64,21 @@ class AnnouncementModal extends React.Component {
}
hide = () => $(this._dlg).modal('hide')
_makeRead() {
if (!rb.currentUser) {
location.href = `${rb.baseUrl}/app/redirect?id=${this.props.id}`
return
}
$.post(`/commons/announcements/make-read?id=${this.props.id}`, (res) => {
if (res.error_code === 0) {
this.setState({ readState: 2 })
} else {
RbHighbar.error(res.error_msg)
}
})
}
}
const $showAnnouncement = function () {

View file

@ -211,7 +211,8 @@ class FeedsList extends React.Component {
const firstFetch = !s.data
// s.focusFeed 首次加载有效
$.post(`/feeds/feeds-list?pageNo=${s.pageNo}&sort=${s.sort || ''}&type=${s.tabType}&foucs=${firstFetch ? s.focusFeed : ''}`, JSON.stringify(filter), (res) => {
const url = `/feeds/feeds-list?pageNo=${s.pageNo}&sort=${s.sort || ''}&type=${s.tabType}&foucs=${firstFetch && s.focusFeed ? s.focusFeed : ''}`
$.post(url, JSON.stringify(filter), (res) => {
const _data = res.data || { data: [], total: 0 }
this.state.pageNo === 1 && this._Pagination.setState({ rowsTotal: _data.total, pageNo: 1 })
this.setState({ data: _data.data, focusFeed: firstFetch ? s.focusFeed : null }, () => {
@ -588,6 +589,13 @@ function __renderRichContent(e) {
}
}
let showTimeTip
if (e.type === 3 && contentMore.timeEnd) {
showTimeTip = <span className="badge badge-success ml-1">{$L('公示中')}</span>
if ($expired(contentMore.timeEnd)) showTimeTip = null
else if (contentMore.timeStart && moment().isBefore($moment(contentMore.timeStart))) showTimeTip = null
}
let AL = e.autoLocation ? e.autoLocation.split('$$$$') : false
if (AL) {
AL = {
@ -650,6 +658,18 @@ function __renderRichContent(e) {
{(contentMore.timeStart || contentMore.timeEnd) && (
<div>
<span>{$L('公示时间')} : </span> {contentMore.timeStart || ''} {$L('至')} {contentMore.timeEnd}
{showTimeTip}
</div>
)}
{e.readStatus && (
<div>
<span>{$L('已读用户')} : </span>
{e.readStatus.length > 0 ? <RF>{$L('%d 人已读', e.readStatus.length)}</RF> : <span className="text-muted">{$L('无')}</span>}
<div className="read-status fs-0">
{e.readStatus.map((item) => {
return <UserShow key={item[0]} id={item[0]} name={item[1]} noLink />
})}
</div>
</div>
)}
</div>

View file

@ -519,6 +519,15 @@ class AnnouncementOptions extends React.Component {
</div>
</dd>
</dl>
<dl className="row mb-1">
<dt className="col-12 col-lg-3">{$L('要求已读')}</dt>
<dd className="col-12 col-lg-9 mb-0" ref={(c) => (this._$reqRead = c)}>
<label className="custom-control custom-checkbox custom-control-inline">
<input className="custom-control-input" name="reqRead" type="checkbox" disabled={this.props.readonly} />
<span className="custom-control-label">{$L('是')}</span>
</label>
</dd>
</dl>
</div>
)
}
@ -540,6 +549,9 @@ class AnnouncementOptions extends React.Component {
.each(function () {
if ((~~$(this).val() & initValue.showWhere) !== 0) $(this).prop('checked', true)
})
$(this._$reqRead)
.find('input')
.prop('checked', initValue.reqRead === true)
}
}
@ -566,12 +578,14 @@ class AnnouncementOptions extends React.Component {
timeStart: timeStart || null,
timeEnd: timeEnd,
showWhere: where,
reqRead: $(this._$reqRead).find('input')[0].checked,
}
}
reset() {
$(this._$showTime).find('.form-control').val('')
$(this._$showWhere).find('input').prop('checked', false)
$(this._$reqRead).find('input').prop('checked', false)
}
}

View file

@ -37,7 +37,7 @@ class GroupList extends React.Component {
<li key={`item-${item.id}`} data-id={item.id} className={this.state.active === item.id ? 'active' : ''}>
<a className="text-truncate" onClick={() => this._handleActive(item.id)}>
{this._showAvatar && <img src={`${rb.baseUrl}/account/user-avatar/${item.id}`} className="avatar" alt="Avatar" />}
{item.name}
<span className="down-1 d-inline-block">{item.name}</span>
</a>
<i className={`zmdi zmdi-star-outline ${item.star && 'star'}`} onClick={() => this._handleStar(item)} title={$L('星标')} />
</li>

View file

@ -316,12 +316,20 @@ class BaiduMap extends React.Component {
that._map = map
// 初始位置
if (that.props.lnglat && that.props.lnglat.lng && that.props.lnglat.lat) {
that.center(that.props.lnglat)
const _lnglat = that.props.lnglat
if (_lnglat) {
if (_lnglat.lng && _lnglat.lat) {
that.center(_lnglat)
} else if (_lnglat.text) {
const geoc = new _BMapGL.Geocoder()
geoc.getPoint(_lnglat.text, function (point) {
that.center(point)
})
}
} else {
const geo = new _BMapGL.Geolocation()
geo.enableSDKLocation()
geo.getCurrentPosition(function (e) {
const geol = new _BMapGL.Geolocation()
geol.enableSDKLocation()
geol.getCurrentPosition(function (e) {
if (this.getStatus() === window.BMAP_STATUS_SUCCESS) {
map.centerAndZoom(e.point, 14)
} else {

View file

@ -341,7 +341,7 @@ class DlgImports extends RbModalHandler {
render() {
return (
<RbModal title={$L('导入分类数据')} ref={(c) => (this._dlg = c)}>
<div className="tab-container">
<div className="tab-container" style={{ marginTop: -10 }}>
<ul className="nav nav-tabs">
<li className="nav-item">
<a className="nav-link active" href="#FILE" data-toggle="tab">
@ -481,8 +481,8 @@ class DlgImports extends RbModalHandler {
_checkState(taskid) {
if (!this.__mp) {
const mp_parent = $(this._dlg._element).find('.modal-body').attr('id')
this.__mp = new Mprogress({ template: 1, start: true, parent: `#${mp_parent}` })
const mp_parent = $(this._dlg._element).find('.modal-body').attr('id', $random('node-'))
this.__mp = new Mprogress({ template: 1, start: true, parent: `#` + $(mp_parent).attr('id') })
}
$.get(`/commons/task/state?taskid=${taskid}`, (res) => {
@ -517,12 +517,13 @@ class DlgEditItem extends RbAlert {
return (
<form className="rbalert-form-sm">
<div className="form-group">
<label className="text-bold">{$L('分类项名称')}</label>
<label className="text-bold">{$L('名称')}</label>
<input type="text" className="form-control form-control-sm" name="name" value={this.state.name || ''} onChange={this.handleChange} maxLength="50" />
</div>
<div className="form-group">
<label className="text-bold">{$L('编码')}</label>
<input type="text" className="form-control form-control-sm" name="code" value={this.state.code || ''} onChange={this.handleChange} maxLength="50" placeholder={$L('无')} />
<p className="form-text">{$L('编码可用于排序和搜素')}</p>
</div>
<div className="form-group">
<label className="custom-control custom-control-sm custom-checkbox custom-control-inline mt-0 mb-0">
@ -553,6 +554,11 @@ class DlgEditItem extends RbAlert {
itemCode: this.state.code,
itemHide: this.state.hide,
}
if (!_data.itemName) {
RbHighbar.create($L('请输入名称'))
return
}
typeof this.props.onConfirm === 'function' && this.props.onConfirm(_data)
this.hide()
}

View file

@ -23,7 +23,7 @@ class EntityNew2 extends RbModalHandler {
render() {
return (
<RbModal ref={(c) => (this._dlg = c)} title={$L('添加实体')} className="entity-new2">
<div className="tab-container" ref={(c) => (this._$container = c)}>
<div className="tab-container" style={{ marginTop: -10 }} ref={(c) => (this._$container = c)}>
<ul className="nav nav-tabs">
<li className="nav-item">
<a className="nav-link active" href="#MANUAL" data-toggle="tab">
@ -166,13 +166,13 @@ class EntityNew2 extends RbModalHandler {
<div className="tab-pane" id="EXCEL">
<div className="form">
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('上传数据文件')}</label>
<label className="col-sm-3 col-form-label text-sm-right">{$L('上传文件')}</label>
<div className="col-sm-7">
<div className="file-select">
<input type="file" className="inputfile" accept=".xlsx,.xls,.csv" data-local="temp" ref={(c) => (this._$uploadfile = c)} />
<label htmlFor="upload-input" className="btn-secondary mb-0" ref={(c) => (this._$uploadbtn = c)}>
<i className="zmdi zmdi-upload"></i>
<span>{$L('上传文件')}</span>
<span>{$L('选择文件')}</span>
</label>
</div>
{this.state.excelfile && (

View file

@ -94,7 +94,11 @@ class MessageList extends React.Component {
{$L('查看')}
</a>
)}
{item[3] && <a className="read-mark text-muted">{$L('标记已读')}</a>}
{item[3] && (
<a className="read-mark text-muted">
<i className="icon zmdi zmdi-check text-bold" /> {$L('标记已读')}
</a>
)}
</span>
</li>
)

View file

@ -104,7 +104,7 @@ class DlgEdit extends RbFormHandler {
</div>
</div>
{!this.props.id && (
<React.Fragment>
<RF>
<div className="form-group row">
<label className="col-sm-3 col-form-label text-sm-right">{$L('项目 ID')}</label>
<div className="col-sm-7">
@ -121,7 +121,7 @@ class DlgEdit extends RbFormHandler {
</label>
</div>
</div>
</React.Fragment>
</RF>
)}
<div className="form-group row footer">
<div className="col-sm-7 offset-sm-3" ref={(c) => (this._btns = c)}>

View file

@ -660,7 +660,7 @@ class TaskViewModal extends React.Component {
location.hash = '#!/View/'
})
$dlg.modal({ show: true })
$dlg.modal({ show: true, backdrop: !false })
// fix: 打开视图卡顿
setTimeout(() => this.setState({ _taskUrl: `${rb.baseUrl}/project/task/${this.state.taskid}` }), 200)
}
@ -676,6 +676,7 @@ class TaskViewModal extends React.Component {
refreshTask(planChanged) {
const ref = __TaskRefs[this.state.taskid]
if (!ref) return
if (planChanged) {
ref.props.$$$parent.refreshTasks()
__PlanRefs[planChanged] && __PlanRefs[planChanged].refreshTasks()

View file

@ -771,7 +771,7 @@ class UserSelector extends React.Component {
// ~~ 用户显示
const UserShow = function (props) {
const viewUrl = props.id ? `#!/View/User/${props.id}` : null
const viewUrl = props.id && props.noLink !== true ? `#!/View/User/${props.id}` : null
const avatarUrl = `${rb.baseUrl}/account/user-avatar/${props.id}`
return (

View file

@ -61,7 +61,7 @@ $(document).ready(() => {
message={
<div>
<span className="mr-1">{$L('预计执行时间 (最多显示近 9 )')} : </span>
<code>{res.data.slice(0, 10).join(', ')}</code>
<code>{res.data.slice(0, 9).join(', ')}</code>
</div>
}
/>,

View file

@ -33,7 +33,8 @@
<script>
$(document).ready(() => {
const $frame = $('.frame-wrap iframe')
$frame.attr('src', $decode($urlp('url')))
const url = $urlp('url') || $urlp('src')
$frame.attr('src', $decode(url))
$addResizeHandler(() => {
$frame.css({ minHeight: $(window).height() - 61 })

View file

@ -8,12 +8,12 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service.trigger.aviator;
import cn.devezhao.commons.CalendarUtils;
import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.exception.ExpressionRuntimeException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
@ -82,11 +82,13 @@ class AviatorUtilsTest {
@Test
void testDateOp() {
Map<String, Object> env = new HashMap<>();
env.put("date1", CalendarUtils.now());
Map<String, Object> env = AviatorEvaluator.newEnv("date1", CalendarUtils.now());
AviatorUtils.eval("p(date1 + 8)", env, true);
AviatorUtils.eval("p(date1 - 8)", env, true);
AviatorUtils.eval("p(date1 - date1)", env, true);
AviatorUtils.eval("p(1 + 1)", env, true);
AviatorUtils.eval("p(1 - 1)", env, true);
// BAD
Assertions.assertThrows(ExpressionRuntimeException.class,
@ -95,14 +97,26 @@ class AviatorUtilsTest {
@Test
void testDateCompare() {
Map<String, Object> env = new HashMap<>();
env.put("date1", CalendarUtils.now());
env.put("date2", CalendarUtils.addDay(1));
AviatorUtils.eval("p(date1 == date1)", env, true);
AviatorUtils.eval("p(date1 != date2)", env, true);
AviatorUtils.eval("p(date1 > date2)", env, true);
AviatorUtils.eval("p(date1 < date2)", env, true);
AviatorUtils.eval("p(date1 <= date1)", env, true);
AviatorUtils.eval("p(date2 <= date2)", env, true);
Map<String, Object> env = AviatorEvaluator.newEnv(
"date1", CalendarUtils.now(),
"date2", CalendarUtils.addDay(1));
Assertions.assertTrue((Boolean) AviatorUtils.eval("date1 == date1", env));
Assertions.assertTrue((Boolean) AviatorUtils.eval("date1 != date2", env));
Assertions.assertFalse((Boolean) AviatorUtils.eval("date1 > date1", env));
Assertions.assertTrue((Boolean) AviatorUtils.eval("date1 >= date1", env));
Assertions.assertFalse((Boolean) AviatorUtils.eval("date1 < date1", env));
Assertions.assertTrue((Boolean) AviatorUtils.eval("date1 <= date1", env));
Assertions.assertTrue((Boolean) AviatorUtils.eval("date1 == date1", env));
Assertions.assertTrue((Boolean) AviatorUtils.eval("date1 != date2", env));
Assertions.assertTrue((Boolean) AviatorUtils.eval("1.0 == 1", env));
Assertions.assertTrue((Boolean) AviatorUtils.eval("1 != 2", env));
Assertions.assertFalse((Boolean) AviatorUtils.eval("1 > 2", env));
Assertions.assertTrue((Boolean) AviatorUtils.eval("2 >= 2", env));
Assertions.assertFalse((Boolean) AviatorUtils.eval("3.1 < 2", env));
Assertions.assertTrue((Boolean) AviatorUtils.eval("4.56 <= 4.56", env));
Assertions.assertTrue((Boolean) AviatorUtils.eval("12.34560 == 12.3456", env));
Assertions.assertTrue((Boolean) AviatorUtils.eval("1 != 2", env));
}
}