mirror of
https://github.com/getrebuild/rebuild.git
synced 2024-09-20 07:25:54 +08:00
RB-525 Announcement in feeds (#105)
* feat: announcement * TestCase pass
This commit is contained in:
parent
e870b0adf4
commit
1bbf977ac0
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
|
@ -14,7 +14,9 @@
|
|||
"editor.fontSize": 12,
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.autoFixOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"eslint.options": {
|
||||
"configFile": "./.eslintrc.json"
|
||||
},
|
||||
|
@ -26,6 +28,4 @@
|
|||
"prettier.eslintIntegration": true,
|
||||
"workbench.editor.enablePreview": false,
|
||||
"java.configuration.updateBuildConfiguration": "disabled"
|
||||
}
|
||||
// node and eslint(-g)
|
||||
// Plugins: Beautify and ESLint
|
||||
}
|
|
@ -92,7 +92,7 @@ public class LoginToken extends BaseApi {
|
|||
* @return
|
||||
*/
|
||||
public static String checkUser(String user, String password) {
|
||||
if (!Application.getUserStore().exists(user)) {
|
||||
if (!Application.getUserStore().existsUser(user)) {
|
||||
return Languages.lang("InputWrong", "UsernameOrPassword");
|
||||
}
|
||||
|
||||
|
|
|
@ -24,12 +24,14 @@ import cn.devezhao.persist4j.engine.ID;
|
|||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.service.bizz.UserHelper;
|
||||
import com.rebuild.server.service.notification.MessageBuilder;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* @author devezhao
|
||||
|
@ -163,4 +165,23 @@ public class FeedsHelper {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化动态内容
|
||||
*
|
||||
* @param content
|
||||
* @return
|
||||
*/
|
||||
public static String formatContent(String content) {
|
||||
Matcher atMatcher = MessageBuilder.AT_PATTERN.matcher(content);
|
||||
while (atMatcher.find()) {
|
||||
String at = atMatcher.group();
|
||||
ID user = ID.valueOf(at.substring(1));
|
||||
if (user.getEntityCode() == EntityHelper.User && Application.getUserStore().existsUser(user)) {
|
||||
String fullName = Application.getUserStore().getUser(user).getFullName();
|
||||
content = content.replace(at, String.format("<a data-id=\"%s\">@%s</a>", user, fullName));
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ public enum FeedsType {
|
|||
|
||||
ACTIVITY(1, "动态"),
|
||||
FOLLOWUP(2, "跟进"),
|
||||
ANNOUNCEMENT(3, "公告"),
|
||||
|
||||
;
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.rebuild.utils.JSONable;
|
|||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -77,6 +78,7 @@ public class LanguageBundle implements JSONable {
|
|||
|
||||
/**
|
||||
* @return
|
||||
* @see Locale#forLanguageTag(String)
|
||||
*/
|
||||
public String locale() {
|
||||
return locale;
|
||||
|
|
|
@ -111,7 +111,7 @@ public class UserService extends SystemEntityService {
|
|||
record.setString("password", EncryptUtils.toSHA256Hex(password));
|
||||
}
|
||||
|
||||
if (record.hasValue("email") && Application.getUserStore().exists(record.getString("email"))) {
|
||||
if (record.hasValue("email") && Application.getUserStore().existsUser(record.getString("email"))) {
|
||||
throw new DataSpecificationException(Languages.lang("Repeated", "Email"));
|
||||
}
|
||||
|
||||
|
@ -133,7 +133,7 @@ public class UserService extends SystemEntityService {
|
|||
* @throws DataSpecificationException
|
||||
*/
|
||||
private void checkLoginName(String loginName) throws DataSpecificationException {
|
||||
if (Application.getUserStore().exists(loginName)) {
|
||||
if (Application.getUserStore().existsUser(loginName)) {
|
||||
throw new DataSpecificationException("登陆名重复");
|
||||
}
|
||||
if (!CommonsUtils.isPlainText(loginName) || BlackList.isBlack(loginName)) {
|
||||
|
|
|
@ -31,6 +31,7 @@ import cn.devezhao.persist4j.engine.ID;
|
|||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
@ -89,17 +90,34 @@ public class UserStore {
|
|||
* @param emailOrName
|
||||
* @return
|
||||
*/
|
||||
public boolean exists(String emailOrName) {
|
||||
public boolean existsUser(String emailOrName) {
|
||||
return existsName(emailOrName) || existsEmail(emailOrName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param userId
|
||||
* @return
|
||||
*/
|
||||
public boolean exists(ID userId) {
|
||||
public boolean existsUser(ID userId) {
|
||||
return USERs.containsKey(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bizzId
|
||||
* @return
|
||||
*/
|
||||
public boolean existsAny(ID bizzId) {
|
||||
if (bizzId.getEntityCode() == EntityHelper.User) {
|
||||
return USERs.containsKey(bizzId);
|
||||
} else if (bizzId.getEntityCode() == EntityHelper.Role) {
|
||||
return ROLEs.containsKey(bizzId);
|
||||
} else if (bizzId.getEntityCode() == EntityHelper.Department) {
|
||||
return DEPTs.containsKey(bizzId);
|
||||
} else if (bizzId.getEntityCode() == EntityHelper.Team) {
|
||||
return TEAMs.containsKey(bizzId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param username
|
||||
|
@ -249,7 +267,7 @@ public class UserStore {
|
|||
final ID deptId = (ID) o[6];
|
||||
final ID roleId = (ID) o[7];
|
||||
|
||||
final User oldUser = exists(userId) ? getUser(userId) : null;
|
||||
final User oldUser = existsUser(userId) ? getUser(userId) : null;
|
||||
if (oldUser != null) {
|
||||
Role role = oldUser.getOwningRole();
|
||||
if (role != null) {
|
||||
|
|
|
@ -139,7 +139,7 @@ public class MessageBuilder {
|
|||
|
||||
final ID id = ID.valueOf(atid);
|
||||
if (id.getEntityCode() == EntityHelper.User) {
|
||||
if (Application.getUserStore().exists(id)) {
|
||||
if (Application.getUserStore().existsUser(id)) {
|
||||
return Application.getUserStore().getUser(id).getFullName();
|
||||
} else {
|
||||
return "[无效用户]";
|
||||
|
|
|
@ -210,7 +210,8 @@ public class RequestWatchHandler extends HandlerInterceptorAdapter {
|
|||
reqUrl = reqUrl.replaceFirst(ServerListener.getContextPath(), "");
|
||||
return reqUrl.startsWith("/gw/") || reqUrl.startsWith("/assets/") || reqUrl.startsWith("/error/")
|
||||
|| reqUrl.startsWith("/t/") || reqUrl.startsWith("/s/")
|
||||
|| reqUrl.startsWith("/setup/") || reqUrl.startsWith("/language/");
|
||||
|| reqUrl.startsWith("/setup/") || reqUrl.startsWith("/language/")
|
||||
|| reqUrl.startsWith("/commons/announcements");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -67,7 +67,7 @@ public class UserControll extends BaseEntityControll {
|
|||
@RequestMapping("check-user-status")
|
||||
public void checkUserStatus(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ID id = getIdParameterNotNull(request, "id");
|
||||
if (!Application.getUserStore().exists(id)) {
|
||||
if (!Application.getUserStore().existsUser(id)) {
|
||||
writeFailure(response);
|
||||
return;
|
||||
}
|
||||
|
|
118
src/main/java/com/rebuild/web/feeds/AnnouncementControll.java
Normal file
118
src/main/java/com/rebuild/web/feeds/AnnouncementControll.java
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
rebuild - Building your business-systems freely.
|
||||
Copyright (C) 2018-2019 devezhao <zhaofang123@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.rebuild.web.feeds;
|
||||
|
||||
import cn.devezhao.commons.CalendarUtils;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.business.feeds.FeedsHelper;
|
||||
import com.rebuild.server.business.feeds.FeedsScope;
|
||||
import com.rebuild.server.service.bizz.UserHelper;
|
||||
import com.rebuild.utils.AppUtils;
|
||||
import com.rebuild.web.BaseControll;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 动态公告
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 2019/12/19
|
||||
*/
|
||||
@Controller
|
||||
public class AnnouncementControll extends BaseControll {
|
||||
|
||||
@RequestMapping("/commons/announcements")
|
||||
public void list(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
ID user = AppUtils.getRequestUser(request);
|
||||
int fromWhere = 0;
|
||||
|
||||
// 1=动态页 2=首页 4=登录页
|
||||
String refererUrl = request.getHeader("Referer");
|
||||
if (refererUrl.contains("/user/login")) {
|
||||
fromWhere = 4;
|
||||
} else if (refererUrl.contains("/dashboard/home")) {
|
||||
fromWhere = 2;
|
||||
} else if (refererUrl.contains("/feeds/")) {
|
||||
fromWhere = 1;
|
||||
}
|
||||
|
||||
Object[][] array = Application.createQueryNoFilter(
|
||||
"select content,contentMore,scope,createdBy,createdOn,feedsId from Feeds where type = 3")
|
||||
.array();
|
||||
|
||||
List<JSON> as = new ArrayList<>();
|
||||
long timeNow = CalendarUtils.now().getTime();
|
||||
for (Object[] o : array) {
|
||||
JSONObject options = JSON.parseObject((String) o[1]);
|
||||
|
||||
// 不在指定位置
|
||||
|
||||
int whereMask = options.getIntValue("showWhere");
|
||||
if ((fromWhere & whereMask) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 不在公示时间
|
||||
Date timeStart = StringUtils.isBlank(options.getString("timeStart")) ? null : CalendarUtils.parse(options.getString("timeStart"));
|
||||
if (timeStart != null && timeNow < timeStart.getTime()) {
|
||||
continue;
|
||||
}
|
||||
Date timeEnd = StringUtils.isBlank(options.getString("timeEnd")) ? null : CalendarUtils.parse(options.getString("timeEnd"));
|
||||
if (timeEnd != null && timeNow > timeEnd.getTime()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 不可见
|
||||
boolean allow = false;
|
||||
String scope = (String) o[2];
|
||||
if (FeedsScope.ALL.name().equalsIgnoreCase(scope)) {
|
||||
allow = true;
|
||||
} else if (FeedsScope.SELF.name().equalsIgnoreCase(scope) && o[3].equals(user)) {
|
||||
allow = true;
|
||||
} else if (ID.isId(scope) && user != null) {
|
||||
ID teamId = ID.valueOf(scope);
|
||||
if (Application.getUserStore().existsAny(teamId)) {
|
||||
allow = Application.getUserStore().getTeam(teamId).isMember(user);
|
||||
}
|
||||
}
|
||||
|
||||
if (allow) {
|
||||
JSONObject a = new JSONObject();
|
||||
a.put("content", FeedsHelper.formatContent((String) o[0]));
|
||||
a.put("publishOn", CalendarUtils.getUTCDateTimeFormat().format(o[4]));
|
||||
a.put("publishBy", UserHelper.getName((ID) o[3]));
|
||||
a.put("id", o[5]);
|
||||
as.add(a);
|
||||
}
|
||||
}
|
||||
|
||||
writeSuccess(response, as);
|
||||
}
|
||||
}
|
|
@ -29,11 +29,9 @@ import com.rebuild.server.Application;
|
|||
import com.rebuild.server.business.feeds.FeedsHelper;
|
||||
import com.rebuild.server.business.feeds.FeedsScope;
|
||||
import com.rebuild.server.configuration.portals.FieldValueWrapper;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.metadata.MetadataHelper;
|
||||
import com.rebuild.server.metadata.entity.EasyMeta;
|
||||
import com.rebuild.server.service.bizz.UserHelper;
|
||||
import com.rebuild.server.service.notification.MessageBuilder;
|
||||
import com.rebuild.server.service.query.AdvFilterParser;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import com.rebuild.web.BasePageControll;
|
||||
|
@ -50,7 +48,6 @@ import java.util.ArrayList;
|
|||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
/**
|
||||
* 列表相关
|
||||
|
@ -120,7 +117,7 @@ public class FeedsListControll extends BasePageControll {
|
|||
}
|
||||
}
|
||||
|
||||
String sql = "select feedsId,createdBy,createdOn,modifiedOn,content,images,attachments,scope,type,relatedRecord from Feeds where " + sqlWhere;
|
||||
String sql = "select feedsId,createdBy,createdOn,modifiedOn,content,images,attachments,scope,type,relatedRecord,contentMore from Feeds where " + sqlWhere;
|
||||
if ("older".equalsIgnoreCase(sort)) {
|
||||
sql += " order by createdOn asc";
|
||||
} else if ("modified".equalsIgnoreCase(sort)) {
|
||||
|
@ -157,6 +154,11 @@ public class FeedsListControll extends BasePageControll {
|
|||
item.put("related", mixValue);
|
||||
}
|
||||
|
||||
// 更多内容
|
||||
if (o[10] != null) {
|
||||
item.put("contentMore", JSON.parse((String) o[10]));
|
||||
}
|
||||
|
||||
list.add(item);
|
||||
}
|
||||
writeSuccess(response,
|
||||
|
@ -211,7 +213,7 @@ public class FeedsListControll extends BasePageControll {
|
|||
item.put("createdOn", CalendarUtils.getUTCDateTimeFormat().format(o[2]));
|
||||
item.put("createdOnFN", Moment.moment((Date) o[2]).fromNow());
|
||||
item.put("modifedOn", CalendarUtils.getUTCDateTimeFormat().format(o[3]));
|
||||
item.put("content", formatContent((String) o[4]));
|
||||
item.put("content", FeedsHelper.formatContent((String) o[4]));
|
||||
if (o[5] != null) {
|
||||
item.put("images", JSON.parse((String) o[5]));
|
||||
}
|
||||
|
@ -226,21 +228,4 @@ public class FeedsListControll extends BasePageControll {
|
|||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param content
|
||||
* @return
|
||||
*/
|
||||
private String formatContent(String content) {
|
||||
Matcher atMatcher = MessageBuilder.AT_PATTERN.matcher(content);
|
||||
while (atMatcher.find()) {
|
||||
String at = atMatcher.group();
|
||||
ID user = ID.valueOf(at.substring(1));
|
||||
if (user.getEntityCode() == EntityHelper.User && Application.getUserStore().exists(user)) {
|
||||
String fullName = Application.getUserStore().getUser(user).getFullName();
|
||||
content = content.replace(at, String.format("<a data-id=\"%s\">@%s</a>", user, fullName));
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ public class LoginControll extends BasePageControll {
|
|||
LOG.error("Can't decode User from alt : " + alt, ex);
|
||||
}
|
||||
|
||||
if (altUser != null && Application.getUserStore().exists(altUser)) {
|
||||
if (altUser != null && Application.getUserStore().existsUser(altUser)) {
|
||||
loginSuccessed(request, response, altUser, true);
|
||||
|
||||
String nexturl = StringUtils.defaultIfBlank(request.getParameter("nexturl"), DEFAULT_HOME);
|
||||
|
|
|
@ -338,6 +338,7 @@
|
|||
<field name="feedsId" type="primary" />
|
||||
<field name="type" type="small-int" nullable="false" updatable="false" default-value="1" description="类型" />
|
||||
<field name="content" type="text" nullable="false" max-length="3000" description="内容" />
|
||||
<field name="contentMore" type="text" max-length="3000" description="不同类型的扩展内容, JSON格式KV" />
|
||||
<field name="images" type="string" max-length="700" description="图片" extra-attrs="{displayType:'IMAGE'}" />
|
||||
<field name="attachments" type="string" max-length="700" description="附件" extra-attrs="{displayType:'FILE'}" />
|
||||
<field name="relatedRecord" type="any-reference" description="相关业务记录" />
|
||||
|
|
|
@ -498,6 +498,7 @@ create table if not exists `feeds` (
|
|||
`FEEDS_ID` char(20) not null,
|
||||
`TYPE` smallint(6) not null default '1' comment '类型',
|
||||
`CONTENT` text(3000) not null comment '内容',
|
||||
`CONTENT_MORE` text(3000) comment '不同类型的扩展内容, JSON格式KV',
|
||||
`IMAGES` varchar(700) comment '图片',
|
||||
`ATTACHMENTS` varchar(700) comment '附件',
|
||||
`RELATED_RECORD` char(20) comment '相关业务记录',
|
||||
|
@ -588,4 +589,4 @@ INSERT INTO `classification` (`DATA_ID`, `NAME`, `DESCRIPTION`, `OPEN_LEVEL`, `I
|
|||
|
||||
-- DB Version
|
||||
INSERT INTO `system_config` (`CONFIG_ID`, `ITEM`, `VALUE`)
|
||||
VALUES ('021-9000000000000001', 'DBVer', 18);
|
||||
VALUES ('021-9000000000000001', 'DBVer', 19);
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
-- Database upgrade scripts for rebuild 1.x
|
||||
-- Each upgraded starts with `-- #VERSION`
|
||||
|
||||
-- #19 Announcement
|
||||
alter table `feeds`
|
||||
add column `CONTENT_MORE` text(3000) comment '不同类型的扩展内容, JSON格式KV';
|
||||
|
||||
-- #18 Folder scope
|
||||
alter table `attachment_folder`
|
||||
add column `SCOPE` varchar(20) default 'ALL' comment '哪些人可见, 可选值: ALL/SELF/$TeamID',
|
||||
|
|
|
@ -206,6 +206,7 @@
|
|||
|
||||
.rich-content>.related {
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rich-content>.img-field {
|
||||
|
@ -459,9 +460,9 @@
|
|||
}
|
||||
|
||||
.related-select {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 2px;
|
||||
margin-top: 10px;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.fixed-icon {
|
||||
|
@ -493,6 +494,7 @@
|
|||
border-top: 1px solid #4285f4;
|
||||
width: 32px;
|
||||
transition: margin-left linear 0.1s;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.arrow_box:after,
|
||||
|
@ -519,4 +521,36 @@
|
|||
border-bottom-color: #4285f4;
|
||||
border-width: 7px;
|
||||
margin-left: -7px;
|
||||
}
|
||||
|
||||
.announcement-options {
|
||||
margin-top: 10px;
|
||||
border-radius: 2px;
|
||||
padding-top: 18px;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
.announcement-options dt {
|
||||
color: #777;
|
||||
font-weight: normal;
|
||||
text-align: right;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.announcement-options .input-group {
|
||||
max-width: 430px;
|
||||
}
|
||||
|
||||
.announcement-options .input-group-prepend {
|
||||
height: 37px;
|
||||
}
|
||||
|
||||
.rich-content>.mores {
|
||||
font-size: 12px;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.rich-content>.mores span {
|
||||
color: #666;
|
||||
}
|
|
@ -21471,7 +21471,9 @@ fieldset[disabled] .btn-color.btn-evernote:hover {
|
|||
.alert .message {
|
||||
display: table-cell;
|
||||
padding: 1.385rem 2.1542rem 1.385rem .231rem;
|
||||
border-left-width: 0
|
||||
border-left-width: 0;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
@media (max-width:575.98px) {
|
||||
|
|
|
@ -3090,4 +3090,50 @@ a.icon-link>.zmdi {
|
|||
color: #999;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.announcement-wrapper>div {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
color: #fff;
|
||||
padding: 15px 25px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.announcement-wrapper>div:hover {
|
||||
cursor: pointer;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.announcement-wrapper>div+div {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.announcement-wrapper>div .icon {
|
||||
float: left;
|
||||
font-size: 1.65rem;
|
||||
}
|
||||
|
||||
.announcement-wrapper>div p {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
padding-left: 15px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.announcement-contents {
|
||||
line-height: 1.7;
|
||||
font-size: 1rem;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.announcement-contents img.emoji {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
line-height: 1;
|
||||
font-size: 0;
|
||||
}
|
|
@ -228,15 +228,14 @@ class DlgDashSettings extends RbFormHandler {
|
|||
<input className="form-control form-control-sm" value={this.state.title || ''} placeholder="默认仪表盘" data-id="title" onChange={this.handleChange} maxLength="40" />
|
||||
</div>
|
||||
</div>
|
||||
{rb.isAdminUser !== true ? null :
|
||||
<div className="form-group row">
|
||||
<label className="col-sm-3 col-form-label text-sm-right"></label>
|
||||
<div className="col-sm-7">
|
||||
<div className="shareTo--wrap">
|
||||
<Share2 ref={(c) => this._shareTo = c} noSwitch={true} shareTo={this.props.shareTo} />
|
||||
</div>
|
||||
{rb.isAdminUser && <div className="form-group row">
|
||||
<label className="col-sm-3 col-form-label text-sm-right"></label>
|
||||
<div className="col-sm-7">
|
||||
<div className="shareTo--wrap">
|
||||
<Share2 ref={(c) => this._shareTo = c} noSwitch={true} shareTo={this.props.shareTo} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className="form-group row footer">
|
||||
<div className="col-sm-7 offset-sm-3">
|
||||
|
|
65
src/main/webapp/assets/js/feeds/announcement.jsx
Normal file
65
src/main/webapp/assets/js/feeds/announcement.jsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
/* eslint-disable react/prop-types */
|
||||
|
||||
const EMOJIS = { '赞': 'fs_zan.png', '握手': 'fs_woshou.png', '耶': 'fs_ye.png', '抱拳': 'fs_baoquan.png', 'OK': 'fs_ok.png', '拍手': 'fs_paishou.png', '拜托': 'fs_baituo.png', '差评': 'fs_chaping.png', '微笑': 'fs_weixiao.png', '撇嘴': 'fs_piezui.png', '花痴': 'fs_huachi.png', '发呆': 'fs_fadai.png', '得意': 'fs_deyi.png', '大哭': 'fs_daku.png', '害羞': 'fs_haixiu.png', '闭嘴': 'fs_bizui.png', '睡着': 'fs_shuizhao.png', '敬礼': 'fs_jingli.png', '崇拜': 'fs_chongbai.png', '抱抱': 'fs_baobao.png', '忍住不哭': 'fs_renzhubuku.png', '尴尬': 'fs_ganga.png', '发怒': 'fs_fanu.png', '调皮': 'fs_tiaopi.png', '开心': 'fs_kaixin.png', '惊讶': 'fs_jingya.png', '呵呵': 'fs_hehe.png', '思考': 'fs_sikao.png', '哭笑不得': 'fs_kuxiaobude.png', '抓狂': 'fs_zhuakuang.png', '呕吐': 'fs_outu.png', '偷笑': 'fs_touxiao.png', '笑哭了': 'fs_xiaokule.png', '白眼': 'fs_baiyan.png', '傲慢': 'fs_aoman.png', '饥饿': 'fs_jie.png', '困': 'fs_kun.png', '吓': 'fs_xia.png', '流汗': 'fs_liuhan.png', '憨笑': 'fs_hanxiao.png', '悠闲': 'fs_youxian.png', '奋斗': 'fs_fendou.png', '咒骂': 'fs_zhouma.png', '疑问': 'fs_yiwen.png', '嘘': 'fs_xu.png', '晕': 'fs_yun.png', '惊恐': 'fs_jingkong.png', '衰': 'fs_shuai.png', '骷髅': 'fs_kulou.png', '敲打': 'fs_qiaoda.png', '再见': 'fs_zaijian.png', '无语': 'fs_wuyu.png', '抠鼻': 'fs_koubi.png', '鼓掌': 'fs_guzhang.png', '糗大了': 'fs_qiudale.png', '猥琐的笑': 'fs_weisuodexiao.png', '哼': 'fs_heng.png', '不爽': 'fs_bushuang.png', '打哈欠': 'fs_dahaqian.png', '鄙视': 'fs_bishi.png', '委屈': 'fs_weiqu.png', '安慰': 'fs_anwei.png', '坏笑': 'fs_huaixiao.png', '亲亲': 'fs_qinqin.png', '冷汗': 'fs_lenghan.png', '可怜': 'fs_kelian.png', '生病': 'fs_shengbing.png', '愉快': 'fs_yukuai.png', '幸灾乐祸': 'fs_xingzailehuo.png', '大便': 'fs_dabian.png', '干杯': 'fs_ganbei.png', '钱': 'fs_qian.png' }
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const converEmoji = function (text) {
|
||||
let es = text.match(/\[(.+?)\]/g)
|
||||
if (!es) return text
|
||||
es.forEach((e) => {
|
||||
let img = EMOJIS[e.substr(1, e.length - 2)]
|
||||
if (img) {
|
||||
img = `<img class="emoji" src="${rb.baseUrl}/assets/img/emoji/${img}"/>`
|
||||
text = text.replace(e, img)
|
||||
}
|
||||
})
|
||||
return text.replace(/\n/g, '<br />')
|
||||
}
|
||||
|
||||
// 公告
|
||||
class AnnouncementModal extends React.Component {
|
||||
state = { ...this.props }
|
||||
render() {
|
||||
const contentHtml = converEmoji(this.props.content.replace(/\n/g, '<br>'))
|
||||
return <div className="modal" tabIndex={this.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">
|
||||
<button className="close" type="button" onClick={this.hide}><i className="zmdi zmdi-close" /></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="text-break announcement-contents" dangerouslySetInnerHTML={{ __html: contentHtml }} />
|
||||
<div>
|
||||
<span className="float-left text-muted fs-12">由 {this.props.publishBy} 发布于 {this.props.publishOn}</span>
|
||||
<span className="float-right"><a href={`${rb.baseUrl}/app/list-and-view?id=${this.props.id}`}>前往动态查看</a></span>
|
||||
<span className="clearfi"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
let root = $(this._dlg).modal({ show: true, keyboard: true }).on('hidden.bs.modal', () => {
|
||||
root.modal('dispose')
|
||||
$unmount(root.parent())
|
||||
})
|
||||
}
|
||||
hide = () => $(this._dlg).modal('hide')
|
||||
}
|
||||
|
||||
var $showAnnouncement = function () {
|
||||
$.get(`${rb.baseUrl}/commons/announcements`, (res) => {
|
||||
if (res.error_code !== 0 || !res.data || res.data.length === 0) return
|
||||
let as = res.data.map((item, idx) => {
|
||||
return <div className="bg-primary" key={'a-' + idx} title="查看详情"
|
||||
onClick={() => renderRbcomp(<AnnouncementModal {...item} />)}>
|
||||
<i className="icon zmdi zmdi-notifications-active" />
|
||||
<p>{item.content}</p>
|
||||
</div>
|
||||
})
|
||||
renderRbcomp(<React.Fragment>{as}</React.Fragment>, $('.announcement-wrapper'))
|
||||
})
|
||||
}
|
||||
|
||||
$(document).ready(() => $showAnnouncement())
|
|
@ -3,7 +3,7 @@
|
|||
/* global converEmoji, FeedsEditor */
|
||||
|
||||
const FeedsSorts = { newer: '最近发布', older: '最早发布', modified: '最近修改' }
|
||||
const FeedsTypes = { 1: '动态', 2: '跟进' }
|
||||
const FeedsTypes = { 1: '动态', 2: '跟进', 3: '公告' }
|
||||
|
||||
// ~ 动态列表
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
@ -373,12 +373,21 @@ function __renderRichContent(e) {
|
|||
// 表情和换行不在后台转换,因为不同客户端所需的格式不同
|
||||
const contentHtml = converEmoji(e.content.replace(/\n/g, '<br>'))
|
||||
return <div className="rich-content">
|
||||
<div className="texts"
|
||||
<div className="texts text-break"
|
||||
dangerouslySetInnerHTML={{ __html: contentHtml }}
|
||||
/>
|
||||
{e.related && <div className="related">
|
||||
<span className="text-muted"><i className={`icon zmdi zmdi-${e.related.icon}`} /> {e.related.entityLabel} - </span>
|
||||
<a target="_blank" href={`${rb.baseUrl}/app/list-and-view?id=${e.related.id}`}>{e.related.text}</a>
|
||||
{e.related && <div className="mores">
|
||||
<div>
|
||||
<span><i className={`icon zmdi zmdi-${e.related.icon}`} /> {e.related.entityLabel} : </span>
|
||||
<a target="_blank" href={`${rb.baseUrl}/app/list-and-view?id=${e.related.id}`} title="查看相关记录">{e.related.text}</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{e.type === 3 && <div className="mores">
|
||||
{e.contentMore.showWhere > 0
|
||||
&& <div><span>公示位置 : </span> {__findMaskTexts(e.contentMore.showWhere, ANN_OPTIONS).join('、')}</div>}
|
||||
{(e.contentMore.timeStart || e.contentMore.timeEnd)
|
||||
&& <div><span>公示时间 : </span> {e.contentMore.timeStart || ''} 至 {e.contentMore.timeEnd}</div>}
|
||||
</div>
|
||||
}
|
||||
{(e.images || []).length > 0 && <div className="img-field">
|
||||
|
@ -403,6 +412,15 @@ function __renderRichContent(e) {
|
|||
</div>
|
||||
}
|
||||
|
||||
const ANN_OPTIONS = [[1, '动态页'], [2, '首页'], [4, '登录页']]
|
||||
function __findMaskTexts(mask, options) {
|
||||
let texts = []
|
||||
options.forEach((item) => {
|
||||
if ((item[0] & mask) !== 0) texts.push(item[1])
|
||||
})
|
||||
return texts
|
||||
}
|
||||
|
||||
// 点赞
|
||||
function _handleLike(id, comp) {
|
||||
event.preventDefault()
|
||||
|
|
|
@ -7,18 +7,24 @@ class FeedsPost extends React.Component {
|
|||
state = { ...this.props, type: 1 }
|
||||
|
||||
render() {
|
||||
const activeType = this.state.type
|
||||
const activeClass = 'text-primary text-bold'
|
||||
return <div className="feeds-post">
|
||||
<ul className="list-unstyled list-inline mb-1 pl-1">
|
||||
<ul className="list-unstyled list-inline mb-1 pl-1" ref={(c) => this._activeType = c}>
|
||||
<li className="list-inline-item">
|
||||
<a onClick={() => this.setState({ type: 1 })} className={`${this.state.type === 1 && 'text-primary'}`}>动态</a>
|
||||
<a onClick={() => this.setState({ type: 1 })} className={`${activeType === 1 ? activeClass : ''}`}>动态</a>
|
||||
</li>
|
||||
<li className="list-inline-item">
|
||||
<a onClick={() => this.setState({ type: 2 })} className={`${this.state.type === 2 && 'text-primary'}`}>跟进</a>
|
||||
<a onClick={() => this.setState({ type: 2 })} className={`${activeType === 2 ? activeClass : ''}`}>跟进</a>
|
||||
</li>
|
||||
{rb.isAdminUser && <li className="list-inline-item">
|
||||
<a onClick={() => this.setState({ type: 3 })} className={`${activeType === 3 ? activeClass : ''}`}>公告</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<div className="arrow_box" style={{ marginLeft: this.state.type === 2 ? 53 : 8 }}></div>
|
||||
<div className="arrow_box" ref={(c) => this._activeArrow = c}></div>
|
||||
<div>
|
||||
<FeedsEditor ref={(c) => this._editor = c} type={this.state.type} />
|
||||
<FeedsEditor ref={(c) => this._editor = c} type={activeType} />
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<div className="float-right">
|
||||
|
@ -39,6 +45,13 @@ class FeedsPost extends React.Component {
|
|||
</div>
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.type !== this.state.type) {
|
||||
let pos = $(this._activeType).find('.text-primary').position()
|
||||
$(this._activeArrow).css('margin-left', pos.left - 31)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount = () => $('#rb-feeds').attr('class', '')
|
||||
|
||||
_selectScope = (e) => {
|
||||
|
@ -61,12 +74,15 @@ class FeedsPost extends React.Component {
|
|||
|
||||
_post = () => {
|
||||
let _data = this._editor.vals()
|
||||
if (!_data) return
|
||||
if (!_data.content) { RbHighbar.create('请输入动态内容'); return }
|
||||
|
||||
_data.scope = this.state.scope
|
||||
if (_data.scope === 'GROUP') {
|
||||
if (!this.__group) { RbHighbar.create('请选择团队'); return }
|
||||
_data.scope = this.__group.id
|
||||
}
|
||||
|
||||
_data.type = this.state.type
|
||||
_data.metadata = { entity: 'Feeds', id: this.props.id }
|
||||
|
||||
|
@ -107,7 +123,7 @@ class FeedsEditor extends React.Component {
|
|||
}
|
||||
|
||||
return (<React.Fragment>
|
||||
<div className={`rich-editor ${this.state.focus && 'active'}`}>
|
||||
<div className={`rich-editor ${this.state.focus ? 'active' : ''}`}>
|
||||
<textarea ref={(c) => this._editor = c} placeholder={this.props.placeholder} maxLength="2000"
|
||||
onFocus={() => this.setState({ focus: true })}
|
||||
onBlur={() => this.setState({ focus: false })}
|
||||
|
@ -135,7 +151,8 @@ class FeedsEditor extends React.Component {
|
|||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.type === 2 && <SelectRelated ref={(c) => this._selectRelated = c} initValue={this.props.related} />}
|
||||
{this.state.type === 2 && <SelectRelated ref={(c) => this._selectRelated = c} initValue={this.state.related} />}
|
||||
{this.state.type === 3 && <AnnouncementOptions ref={(c) => this._announcementOptions = c} initValue={this.state.contentMore} />}
|
||||
{((this.state.images || []).length > 0 || (this.state.files || []).length > 0) && <div className="attachment">
|
||||
<div className="img-field">
|
||||
{(this.state.images || []).map((item) => {
|
||||
|
@ -239,6 +256,10 @@ class FeedsEditor extends React.Component {
|
|||
attachments: this.state.files
|
||||
}
|
||||
if (this.state.type === 2 && this._selectRelated) vals.relatedRecord = this._selectRelated.val()
|
||||
else if (this.state.type === 3 && this._announcementOptions) {
|
||||
vals.contentMore = this._announcementOptions.val()
|
||||
if (!vals.contentMore) return
|
||||
}
|
||||
return vals
|
||||
}
|
||||
focus = () => $(this._editor).selectRange(9999, 9999) // Move to last
|
||||
|
@ -246,6 +267,7 @@ class FeedsEditor extends React.Component {
|
|||
$(this._editor).val('')
|
||||
autosize.update(this._editor)
|
||||
if (this._selectRelated) this._selectRelated.reset()
|
||||
if (this._announcementOptions) this._announcementOptions.reset()
|
||||
this.setState({ files: null, images: null })
|
||||
}
|
||||
}
|
||||
|
@ -330,8 +352,8 @@ class SelectRelated extends React.Component {
|
|||
|
||||
// 编辑时
|
||||
if (this.props.initValue) {
|
||||
$(this._entity).val(this.props.initValue[4]).trigger('change')
|
||||
let option = new Option(this.props.initValue[1], this.props.initValue[0], true, true)
|
||||
$(this._entity).val(this.props.initValue.entity).trigger('change')
|
||||
let option = new Option(this.props.initValue.text, this.props.initValue.id, true, true)
|
||||
$(this._record).append(option)
|
||||
}
|
||||
})
|
||||
|
@ -367,19 +389,96 @@ class SelectRelated extends React.Component {
|
|||
reset = () => $(this._record).val(null).trigger('change')
|
||||
}
|
||||
|
||||
const EMOJIS = { '赞': 'fs_zan.png', '握手': 'fs_woshou.png', '耶': 'fs_ye.png', '抱拳': 'fs_baoquan.png', 'OK': 'fs_ok.png', '拍手': 'fs_paishou.png', '拜托': 'fs_baituo.png', '差评': 'fs_chaping.png', '微笑': 'fs_weixiao.png', '撇嘴': 'fs_piezui.png', '花痴': 'fs_huachi.png', '发呆': 'fs_fadai.png', '得意': 'fs_deyi.png', '大哭': 'fs_daku.png', '害羞': 'fs_haixiu.png', '闭嘴': 'fs_bizui.png', '睡着': 'fs_shuizhao.png', '敬礼': 'fs_jingli.png', '崇拜': 'fs_chongbai.png', '抱抱': 'fs_baobao.png', '忍住不哭': 'fs_renzhubuku.png', '尴尬': 'fs_ganga.png', '发怒': 'fs_fanu.png', '调皮': 'fs_tiaopi.png', '开心': 'fs_kaixin.png', '惊讶': 'fs_jingya.png', '呵呵': 'fs_hehe.png', '思考': 'fs_sikao.png', '哭笑不得': 'fs_kuxiaobude.png', '抓狂': 'fs_zhuakuang.png', '呕吐': 'fs_outu.png', '偷笑': 'fs_touxiao.png', '笑哭了': 'fs_xiaokule.png', '白眼': 'fs_baiyan.png', '傲慢': 'fs_aoman.png', '饥饿': 'fs_jie.png', '困': 'fs_kun.png', '吓': 'fs_xia.png', '流汗': 'fs_liuhan.png', '憨笑': 'fs_hanxiao.png', '悠闲': 'fs_youxian.png', '奋斗': 'fs_fendou.png', '咒骂': 'fs_zhouma.png', '疑问': 'fs_yiwen.png', '嘘': 'fs_xu.png', '晕': 'fs_yun.png', '惊恐': 'fs_jingkong.png', '衰': 'fs_shuai.png', '骷髅': 'fs_kulou.png', '敲打': 'fs_qiaoda.png', '再见': 'fs_zaijian.png', '无语': 'fs_wuyu.png', '抠鼻': 'fs_koubi.png', '鼓掌': 'fs_guzhang.png', '糗大了': 'fs_qiudale.png', '猥琐的笑': 'fs_weisuodexiao.png', '哼': 'fs_heng.png', '不爽': 'fs_bushuang.png', '打哈欠': 'fs_dahaqian.png', '鄙视': 'fs_bishi.png', '委屈': 'fs_weiqu.png', '安慰': 'fs_anwei.png', '坏笑': 'fs_huaixiao.png', '亲亲': 'fs_qinqin.png', '冷汗': 'fs_lenghan.png', '可怜': 'fs_kelian.png', '生病': 'fs_shengbing.png', '愉快': 'fs_yukuai.png', '幸灾乐祸': 'fs_xingzailehuo.png', '大便': 'fs_dabian.png', '干杯': 'fs_ganbei.png', '钱': 'fs_qian.png' }
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const converEmoji = function (text) {
|
||||
let es = text.match(/\[(.+?)\]/g)
|
||||
if (!es) return text
|
||||
es.forEach((e) => {
|
||||
let img = EMOJIS[e.substr(1, e.length - 2)]
|
||||
if (img) {
|
||||
img = `<img class="emoji" src="${rb.baseUrl}/assets/img/emoji/${img}"/>`
|
||||
text = text.replace(e, img)
|
||||
// 公告选项
|
||||
class AnnouncementOptions extends React.Component {
|
||||
state = { ...this.props }
|
||||
|
||||
render() {
|
||||
return <div className="announcement-options">
|
||||
<dl className="row mb-1">
|
||||
<dt className="col-12 col-lg-3">同时公示在</dt>
|
||||
<dd className="col-12 col-lg-9 mb-0" ref={(c) => this._showWhere = c}>
|
||||
<label className="custom-control custom-checkbox custom-control-inline">
|
||||
<input className="custom-control-input" name="showOn" type="checkbox" value={1} disabled={this.props.readonly} />
|
||||
<span className="custom-control-label">动态页</span>
|
||||
</label>
|
||||
<label className="custom-control custom-checkbox custom-control-inline">
|
||||
<input className="custom-control-input" name="showOn" type="checkbox" value={2} disabled={this.props.readonly} />
|
||||
<span className="custom-control-label">首页</span>
|
||||
</label>
|
||||
<label className="custom-control custom-checkbox custom-control-inline">
|
||||
<input className="custom-control-input" name="showOn" type="checkbox" value={4} disabled={this.props.readonly} />
|
||||
<span className="custom-control-label">登录页 <i className="zmdi zmdi-help zicon down-3" data-toggle="tooltip" title="选择登录页公示请注意不要发布敏感信息" /></span>
|
||||
</label>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl className="row">
|
||||
<dt className="col-12 col-lg-3 pt-2">公示时间</dt>
|
||||
<dd className="col-12 col-lg-9" ref={(c) => this._showTime = c}>
|
||||
<div className="input-group">
|
||||
<input type="text" className="form-control form-control-sm" placeholder="现在" />
|
||||
<div className="input-group-prepend input-group-append">
|
||||
<span className="input-group-text">至</span>
|
||||
</div>
|
||||
<input type="text" className="form-control form-control-sm" placeholder="选择结束时间" />
|
||||
</div>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
$(this._showTime).find('.form-control').datetimepicker({
|
||||
componentIcon: 'zmdi zmdi-calendar',
|
||||
navIcons: {
|
||||
rightIcon: 'zmdi zmdi-chevron-right',
|
||||
leftIcon: 'zmdi zmdi-chevron-left'
|
||||
},
|
||||
format: 'yyyy-mm-dd hh:ii:ss',
|
||||
minView: 0,
|
||||
weekStart: 1,
|
||||
autoclose: true,
|
||||
language: 'zh',
|
||||
showMeridian: false,
|
||||
keyboardNavigation: false,
|
||||
minuteStep: 5
|
||||
})
|
||||
|
||||
$(this._showWhere).find('.zicon').tooltip()
|
||||
|
||||
const initValue = this.props.initValue
|
||||
if (initValue) {
|
||||
$(this._showTime).find('.form-control:eq(0)').val(initValue.timeStart || '')
|
||||
$(this._showTime).find('.form-control:eq(1)').val(initValue.timeEnd || '')
|
||||
$(this._showWhere).find('input').each(function () {
|
||||
if ((~~$(this).val() & initValue.showWhere) !== 0) $(this).prop('checked', true)
|
||||
})
|
||||
}
|
||||
})
|
||||
return text.replace(/\n/g, '<br />')
|
||||
}
|
||||
componentWillUnmount() {
|
||||
$(this._showTime).find('.form-control').datetimepicker('remove')
|
||||
}
|
||||
|
||||
val() {
|
||||
let timeStart = $(this._showTime).find('.form-control:eq(0)').val()
|
||||
let timeEnd = $(this._showTime).find('.form-control:eq(1)').val()
|
||||
if (!timeEnd) {
|
||||
RbHighbar.create('请选择结束时间')
|
||||
return
|
||||
}
|
||||
let where = 0
|
||||
$(this._showWhere).find('input:checked').each(function () { where += ~~$(this).val() })
|
||||
|
||||
return {
|
||||
timeStart: timeStart || null,
|
||||
timeEnd: timeEnd,
|
||||
showWhere: where
|
||||
}
|
||||
}
|
||||
reset() {
|
||||
$(this._showTime).find('.form-control').val('')
|
||||
$(this._showWhere).find('input').prop('checked', false)
|
||||
}
|
||||
}
|
||||
|
||||
// ~~ 编辑动态
|
||||
|
@ -395,7 +494,8 @@ class FeedsEditDlg extends RbModalHandler {
|
|||
type: this.props.type,
|
||||
images: this.props.images,
|
||||
files: this.props.attachments,
|
||||
related: this.props.related
|
||||
related: this.props.related,
|
||||
contentMore: this.props.contentMore
|
||||
}
|
||||
return <RbModal ref={(c) => this._dlg = c} title="编辑动态" disposeOnHide={true}>
|
||||
<div className="m-1"><FeedsEditor ref={(c) => this._editor = c} {..._data} /></div>
|
||||
|
|
|
@ -303,7 +303,7 @@ class RbFormElement extends React.Component {
|
|||
}
|
||||
const editable = EDIT_ON_VIEW && props.onView && !props.readonly
|
||||
return <div className={`form-group row type-${props.type} ${editable ? 'editable' : ''}`} data-field={props.field}>
|
||||
<label ref={(c) => this._fieldLabel = c} className={`col-12 col-form-label text-sm-right col-sm-${colWidths[0]} ${!props.onView && !props.nullable && 'required'}`}>{props.label}</label>
|
||||
<label ref={(c) => this._fieldLabel = c} className={`col-12 col-sm-${colWidths[0]} col-form-label text-sm-right ${(!props.onView && !props.nullable) ? 'required' : ''}`}>{props.label}</label>
|
||||
<div ref={(c) => this._fieldText = c} className={'col-12 col-sm-' + colWidths[1]}>
|
||||
{(!props.onView || (editable && this.state.editMode)) ? this.renderElement() : this.renderViewElement()}
|
||||
{!props.onView && props.tip && <p className="form-text">{props.tip}</p>}
|
||||
|
@ -950,7 +950,7 @@ class RbFormMultiSelect extends RbFormElement {
|
|||
return <div className="mt-1" ref={(c) => this._fieldValue__wrap = c}>
|
||||
{(this.props.options || []).length === 0 && <div className="text-danger">选项未配置</div>}
|
||||
{(this.props.options || []).map((item) => {
|
||||
return <label key={name + item.mask} className="custom-control custom-checkbox custom-control-inline">
|
||||
return <label key={name + item.mask} className="custom-control custom-checkbox custom-control-inline">
|
||||
<input className="custom-control-input" name={name} type="checkbox" checked={(this.state.value & item.mask) !== 0} value={item.mask}
|
||||
onChange={this.changeValue} disabled={this.props.readonly} />
|
||||
<span className="custom-control-label">{item.text}</span>
|
||||
|
|
|
@ -31,11 +31,11 @@ $(function () {
|
|||
setTimeout(__globalSearch, 200)
|
||||
}
|
||||
|
||||
if (rb.isAdminUser === true) {
|
||||
if (rb.isAdminUser) {
|
||||
$('html').addClass('admin')
|
||||
if (rb.isAdminVerified !== true) $('.admin-verified').remove()
|
||||
if (location.href.indexOf('/admin/') > -1) $('.admin-settings').remove()
|
||||
else if (rb.isAdminVerified === true) $('.admin-settings a i').addClass('text-danger')
|
||||
else if (rb.isAdminVerified) $('.admin-settings a i').addClass('text-danger')
|
||||
} else {
|
||||
$('.admin-show').remove()
|
||||
}
|
||||
|
@ -458,4 +458,4 @@ var $pgt = {
|
|||
RecordList: 'RecordList',
|
||||
SlaveView: 'SlaveView',
|
||||
SlaveList: 'SlaveList'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
<%@ include file="/_include/spinner.jsp"%>
|
||||
</div>
|
||||
<div class="rb-content">
|
||||
<div class="announcement-wrapper"></div>
|
||||
<div class="main-content container-fluid p-0">
|
||||
<div class="tools-bar">
|
||||
<div class="row">
|
||||
|
@ -59,5 +60,6 @@
|
|||
<script src="${baseUrl}/assets/js/rb-approval.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/charts/dashboard.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/settings-share2.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/feeds/announcement.jsx" type="text/babel"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<jsp:param value="nav_entity-Feeds" name="activeNav"/>
|
||||
</jsp:include>
|
||||
<div class="rb-content">
|
||||
<div class="announcement-wrapper"></div>
|
||||
<div class="main-content container container-smart">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 col-12">
|
||||
|
@ -65,6 +66,7 @@
|
|||
<ul class="list-unstyled">
|
||||
<li data-type="1"><a>动态</a></li>
|
||||
<li data-type="2"><a>跟进</a></li>
|
||||
<li data-type="3"><a>公告</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -102,6 +104,7 @@
|
|||
</div>
|
||||
<%@ include file="/_include/Foot.jsp"%>
|
||||
<script src="${baseUrl}/assets/lib/jquery.textarea.js"></script>
|
||||
<script src="${baseUrl}/assets/js/feeds/announcement.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/feeds/feeds-post.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/feeds/feeds-list.jsx" type="text/babel"></script>
|
||||
<script src="${baseUrl}/assets/js/feeds/feeds.jsx" type="text/babel"></script>
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
<div class="rb-wrapper rb-login">
|
||||
<div class="rb-bgimg"></div>
|
||||
<div class="rb-content">
|
||||
<div class="announcement-wrapper"></div>
|
||||
<div class="main-content container-fluid">
|
||||
<div class="splash-container mb-0">
|
||||
<div class="card card-border-color card-border-color-primary">
|
||||
|
@ -175,5 +176,6 @@ $(document).ready(function() {
|
|||
}
|
||||
})
|
||||
</script>
|
||||
<script src="${baseUrl}/assets/js/feeds/announcement.jsx" type="text/babel"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -23,6 +23,7 @@ import cn.devezhao.persist4j.PersistManagerFactory;
|
|||
import cn.devezhao.persist4j.engine.PersistManagerFactoryImpl;
|
||||
import cn.devezhao.persist4j.metadata.impl.ConfigurationMetadataFactory;
|
||||
import cn.devezhao.persist4j.util.support.Table;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import org.dom4j.Element;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
@ -45,7 +46,8 @@ public class SchemaGen {
|
|||
CTX = new ClassPathXmlApplicationContext(new String[] { "application-ctx.xml", });
|
||||
PMF = CTX.getBean(PersistManagerFactoryImpl.class);
|
||||
|
||||
genAll();
|
||||
// genAll();
|
||||
gen(EntityHelper.Feeds);
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import cn.devezhao.persist4j.engine.ID;
|
|||
import com.rebuild.server.Application;
|
||||
import com.rebuild.server.TestSupportWithUser;
|
||||
import com.rebuild.server.metadata.EntityHelper;
|
||||
import com.rebuild.server.service.bizz.UserService;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -54,6 +55,11 @@ public class FeedsHelperTest extends TestSupportWithUser {
|
|||
FeedsHelper.checkReadable(feedsId, SIMPLE_USER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatContent() {
|
||||
FeedsHelper.formatContent("123 @" + UserService.ADMIN_USER);
|
||||
}
|
||||
|
||||
private ID createFeeds() {
|
||||
Record feeds = EntityHelper.forNew(EntityHelper.Feeds, SIMPLE_USER);
|
||||
feeds.setString("content", "你好,测试动态 @RB示例用户 @admin");
|
||||
|
|
|
@ -23,7 +23,7 @@ import org.junit.Test;
|
|||
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author devezhao
|
||||
|
@ -37,8 +37,10 @@ public class LanguagesTest extends TestSupport {
|
|||
System.out.println(Languages.instance.getCurrentBundle());
|
||||
System.out.println(Languages.instance.getBundle(Locale.getDefault()));
|
||||
|
||||
assertEquals(Locale.US.toString(), Languages.instance.getBundle(Locale.US).locale());
|
||||
assertEquals(Locale.JAPAN.toString(), Languages.instance.getBundle(Locale.JAPAN).locale());
|
||||
assertEquals(Locale.US,
|
||||
Locale.forLanguageTag(Languages.instance.getBundle(Locale.US).locale()));
|
||||
assertEquals(Locale.JAPAN,
|
||||
Locale.forLanguageTag(Languages.instance.getBundle(Locale.JAPAN).locale()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -23,8 +23,9 @@ import com.rebuild.server.TestSupport;
|
|||
import com.rebuild.server.service.bizz.privileges.User;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
|
@ -56,25 +57,30 @@ public class UserStoreTest extends TestSupport {
|
|||
|
||||
@Test
|
||||
public void testExists() throws Exception {
|
||||
assertTrue(Application.getUserStore().exists("admin"));
|
||||
assertTrue(!Application.getUserStore().exists("not_exists"));
|
||||
assertTrue(Application.getUserStore().existsUser("admin"));
|
||||
assertFalse(Application.getUserStore().existsUser("not_exists"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMemberToTeam() {
|
||||
Application.getSessionStore().set(SIMPLE_USER);
|
||||
try {
|
||||
Application.getBean(TeamService.class).createMembers(SIMPLE_TEAM, Arrays.asList(SIMPLE_USER));
|
||||
Application.getBean(TeamService.class).createMembers(SIMPLE_TEAM, Collections.singletonList(SIMPLE_USER));
|
||||
User user = Application.getUserStore().getUser(SIMPLE_USER);
|
||||
System.out.println(user.getOwningTeams());
|
||||
|
||||
assertTrue(!user.getOwningTeams().isEmpty());
|
||||
assertFalse(user.getOwningTeams().isEmpty());
|
||||
assertTrue(Application.getUserStore().getTeam(SIMPLE_TEAM).isMember(SIMPLE_USER));
|
||||
|
||||
Application.getBean(TeamService.class).deleteMembers(SIMPLE_TEAM, Arrays.asList(SIMPLE_USER));
|
||||
Application.getBean(TeamService.class).deleteMembers(SIMPLE_TEAM, Collections.singletonList(SIMPLE_USER));
|
||||
System.out.println(user.getOwningTeams());
|
||||
} finally {
|
||||
Application.getSessionStore().clean();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void existsAny() {
|
||||
Application.getUserStore().existsAny(RoleService.ADMIN_ROLE);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue