..
This commit is contained in:
@@ -408,6 +408,13 @@
|
||||
<artifactId>juel</artifactId>
|
||||
<version>2.1.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.bytedeco</groupId>-->
|
||||
<!-- <artifactId>ffmpeg-platform</artifactId>-->
|
||||
<!-- <version>7.1.1-1.5.12</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
</dependencies>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
||||
@@ -22,6 +22,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* 文件下载工具类,使用本类前,对参数校验的异常使用CommonResponseUtil.renderError()方法进行渲染
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.budwk.app.flow.entity.ProcessInstance;
|
||||
import com.budwk.app.flow.entity.ProcessTask;
|
||||
import com.budwk.app.flow.entity.ProcessTaskActor;
|
||||
import com.budwk.app.flow.enums.ProcessEventTypeEnum;
|
||||
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
@@ -21,17 +23,20 @@ import java.util.List;
|
||||
* 流程任务开始监听器
|
||||
*/
|
||||
@IocBean
|
||||
@Slf4j
|
||||
public class ProcessTaskStartEventListener implements ProcessEventListener {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private GlobalMessageSendService globalMessageSendService;
|
||||
|
||||
@Override
|
||||
public void onEvent(ProcessEvent event) {
|
||||
if (event.getEventType() == ProcessEventTypeEnum.PROCESS_TASK_START) {
|
||||
Long sourceId = event.getSourceId();
|
||||
ProcessTask task = dao.fetch(ProcessTask.class, sourceId);
|
||||
sendMessage( task);
|
||||
sendMessage(task);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +59,9 @@ public class ProcessTaskStartEventListener implements ProcessEventListener {
|
||||
|
||||
for (ProcessTaskActor taskActor : taskActors) {
|
||||
// 模拟发送消息
|
||||
String message = StrUtil.format("{}您有一条待办任务,实例:{},任务:{}", taskActor.getActorName(), instanceName, taskDisplayName);
|
||||
System.out.println(message);
|
||||
String message = StrUtil.format("您有一条待办任务,实例:{},处理环节:{}", taskActor.getActorName(), instanceName, taskDisplayName);
|
||||
log.info(message);
|
||||
globalMessageSendService.sendMessage(taskDisplayName, message, 2, List.of(taskActor.getActorId()), null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.budwk.app.sys.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
@@ -11,8 +12,11 @@ import com.budwk.app.sys.enums.SysFileEngineTypeEnum;
|
||||
import com.budwk.app.sys.models.Sys_file;
|
||||
import com.budwk.app.sys.services.SysConfigService;
|
||||
import com.budwk.app.sys.services.SysFileService;
|
||||
import com.budwk.app.sys.utils.SysFileMinIoUtil;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
@@ -23,19 +27,21 @@ import org.nutz.mvc.upload.UploadAdaptor;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/sys/file")
|
||||
@Ok("json:full")
|
||||
@Api(tags = "文件管理")
|
||||
@Slf4j
|
||||
public class SysFileController {
|
||||
|
||||
@Inject
|
||||
private SysFileService sysFileService;
|
||||
|
||||
@Inject
|
||||
private SysConfigService sysConfigService;
|
||||
private SysFileMinIoUtil sysFileMinIoUtil;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/sys/file/index.html")
|
||||
@@ -110,7 +116,7 @@ public class SysFileController {
|
||||
@SaCheckLogin
|
||||
@ApiOperation("文件管理-转换HTML")
|
||||
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
|
||||
public Result convertHtml(TempFile file){
|
||||
public Result convertHtml(TempFile file) {
|
||||
return Result.success().addData(sysFileService.convertHtml(file));
|
||||
}
|
||||
|
||||
@@ -141,5 +147,64 @@ public class SysFileController {
|
||||
return Result.success(files);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@ApiOperation("文件管理-视频播放")
|
||||
@Ok("void")
|
||||
public void videoPlay(String id, HttpServletRequest request, HttpServletResponse response) {
|
||||
Sys_file sys_file = sysFileService.detail(id);
|
||||
byte[] bytes = SysFileMinIoUtil.getFileBytes(sys_file.getBucket(), sys_file.getStoragePath());
|
||||
|
||||
|
||||
// 设置响应头
|
||||
String fileName = sys_file.getName();
|
||||
String contentType = null;
|
||||
|
||||
switch (FileUtil.extName(fileName).toLowerCase()) {
|
||||
case "mp4" -> contentType = "video/mp4";
|
||||
case "avi" -> contentType = "video/x-msvideo";
|
||||
case "mkv" -> contentType = "video/x-matroska";
|
||||
case "mov" -> contentType = "video/quicktime";
|
||||
case "wmv" -> contentType = "video/x-ms-wmv";
|
||||
default -> contentType = "application/octet-stream";
|
||||
}
|
||||
|
||||
response.setContentType(contentType);
|
||||
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
|
||||
|
||||
String rangeHeader = request.getHeader(HttpHeaders.RANGE);
|
||||
if (StrUtil.isNotBlank(rangeHeader) && rangeHeader.startsWith("bytes=")) {
|
||||
// 处理Range请求
|
||||
String[] ranges = rangeHeader.substring(6).split("-");
|
||||
long start = StrUtil.isNotBlank(ranges[0]) ? Long.parseLong(ranges[0]) : 0;
|
||||
long end = (ranges.length > 1 && StrUtil.isNotBlank(ranges[1])) ?
|
||||
Long.parseLong(ranges[1]) : bytes.length - 1;
|
||||
|
||||
if (start >= bytes.length || end >= bytes.length) {
|
||||
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + bytes.length);
|
||||
response.setStatus(416);
|
||||
return;
|
||||
}
|
||||
|
||||
int contentLength = (int) (end - start + 1);
|
||||
response.setStatus(206);
|
||||
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(contentLength));
|
||||
response.setHeader(HttpHeaders.CONTENT_RANGE, StrUtil.format("bytes {}-{}/{}", start, end, bytes.length));
|
||||
|
||||
try (OutputStream out = response.getOutputStream()) {
|
||||
out.write(bytes, (int) start, contentLength);
|
||||
} catch (IOException e) {
|
||||
log.error("视频播放失败", e);
|
||||
}
|
||||
} else {
|
||||
// 非Range请求
|
||||
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(bytes.length));
|
||||
try (OutputStream out = response.getOutputStream()) {
|
||||
out.write(bytes);
|
||||
} catch (IOException e) {
|
||||
log.error("视频播放失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public class SysHomeController {
|
||||
String userAgentStr = req.getHeader("User-Agent");
|
||||
UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
|
||||
if (!userAgent.isMobile()) {
|
||||
return "beetl:/platform/sys/home/index.html";
|
||||
return "beetl:/layouts/v4/home.html";
|
||||
} else {
|
||||
return "beetl:/platform/zhghh5/sys/home/index.html";
|
||||
}
|
||||
@@ -222,86 +222,6 @@ public class SysHomeController {
|
||||
// return Result.success(hasPermisiionList);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@ApiOperation("待办列表")
|
||||
@Ok("json")
|
||||
public Result listTodo(@Valid PageForm pageForm) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
task.id,
|
||||
inst.processInstanceName,
|
||||
inst.processInstanceInitiatorName,
|
||||
inst.processInstanceNodeName,
|
||||
task.formUrl AS taskFormUrl,
|
||||
task.formMobileUrl AS taskFormMobileUrl
|
||||
FROM
|
||||
`bpm_process_task` task
|
||||
INNER JOIN bpm_process_instance inst ON inst.id = task.processInstanceId
|
||||
WHERE
|
||||
JSON_CONTAINS( task.assignments, @loginName )
|
||||
AND task.taskStatus = 'ACTIVE'
|
||||
GROUP BY
|
||||
task.id
|
||||
ORDER BY
|
||||
task.createdOn DESC
|
||||
""");
|
||||
sql.setParam("loginName", "\"" + SecurityUtil.getUserLoginname() + "\"");
|
||||
Pagination pagination = sysUserService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@ApiOperation("已办列表")
|
||||
@Ok("json")
|
||||
public Result listDone(@Valid PageForm pageForm) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
task.id,
|
||||
inst.processInstanceName,
|
||||
inst.processInstanceInitiatorName,
|
||||
inst.processInstanceNodeName,
|
||||
task.formUrl AS taskFormUrl,
|
||||
task.formMobileUrl AS taskFormMobileUrl
|
||||
FROM
|
||||
`bpm_process_task` task
|
||||
INNER JOIN bpm_process_instance inst ON inst.id = task.processInstanceId
|
||||
WHERE
|
||||
JSON_CONTAINS( task.assignments, @loginName )
|
||||
AND task.taskStatus in ('COMPLETE','TRANSFER')
|
||||
GROUP BY
|
||||
task.id
|
||||
ORDER BY
|
||||
task.createdOn DESC
|
||||
""");
|
||||
sql.setParam("loginName", "\"" + SecurityUtil.getUserLoginname() + "\"");
|
||||
Pagination pagination = sysUserService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@ApiOperation("我的发起")
|
||||
@Ok("json")
|
||||
public Result listMyInitiation(@Valid PageForm pageForm) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
id,
|
||||
processInstanceName,
|
||||
processInstanceInitiatorName,
|
||||
processInstanceNodeName,
|
||||
processInstanceUrl
|
||||
FROM
|
||||
bpm_process_instance
|
||||
WHERE
|
||||
processInstanceInitiatorLoginName = @loginName
|
||||
ORDER BY processInstanceInitiationTime DESC
|
||||
""");
|
||||
sql.setParam("loginName", SecurityUtil.getUserLoginname());
|
||||
Pagination pagination = sysUserService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckLogin
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.budwk.app.sys.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
import io.swagger.annotations.Api;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
@@ -13,6 +14,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/v4")
|
||||
@Api(value = "V4首页")
|
||||
public class SysHomeV4Controller {
|
||||
|
||||
@Inject
|
||||
@@ -27,9 +29,6 @@ public class SysHomeV4Controller {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 首页门户
|
||||
*/
|
||||
@At("/home")
|
||||
@Ok("beetl:/layouts/v4/home.html")
|
||||
@SaCheckLogin
|
||||
@@ -37,13 +36,6 @@ public class SysHomeV4Controller {
|
||||
|
||||
}
|
||||
|
||||
@At("/home2")
|
||||
@Ok("beetl:/layouts/v4/home2.html")
|
||||
@SaCheckLogin
|
||||
public void home2() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用中心
|
||||
*/
|
||||
@@ -64,6 +56,27 @@ public class SysHomeV4Controller {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 待办中心
|
||||
*/
|
||||
@At("/todo")
|
||||
@Ok("beetl:/layouts/v4/todo.html")
|
||||
@SaCheckLogin
|
||||
public void todo(){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 消息中心
|
||||
*/
|
||||
@At("/msg")
|
||||
@Ok("beetl:/layouts/v4/msg.html")
|
||||
@SaCheckLogin
|
||||
public void msg(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 子系统
|
||||
* @param appId 应用ID
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.budwk.app.sys.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
@@ -339,7 +340,11 @@ public class SysUserController {
|
||||
@SaCheckLogin
|
||||
public Result subAppMenus(@Param("appId") String appId) {
|
||||
List<Sys_menu> menus = sysUserService.getMenus(SecurityUtil.getUserId());
|
||||
List<Sys_menu> list = Sys_menu.createTreeMenus(menus, appId);
|
||||
List<Sys_menu> list = SysMenuUtil.createTreeMenus(menus, appId);
|
||||
if (ObjectUtil.isEmpty(list)) {
|
||||
List<Sys_menu> self = menus.stream().filter(menu -> menu.getId().equals(appId)).toList();
|
||||
return Result.success().addData(self);
|
||||
}
|
||||
return Result.success(list);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.budwk.app.sys.controller.v4;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.message.models.GlobalMessage;
|
||||
import com.budwk.app.zhgh.dayofficework.message.models.GlobalMessageReceiver;
|
||||
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageSendService;
|
||||
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/v4/msg")
|
||||
@Api(value = "消息中心", tags = "消息中心接口")
|
||||
@Ok("json:full")
|
||||
public class SysV4MsgController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private GlobalMessageService globalMessageService;
|
||||
@Inject
|
||||
private GlobalMessageSendService globalMessageSendService;
|
||||
|
||||
@At
|
||||
@ApiOperation("获取消息列表")
|
||||
@SaCheckLogin
|
||||
public Result pageData(PageForm pageForm, @Param("isRead") int isRead, @Param("type") Integer type, HttpServletRequest req) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
m.id,
|
||||
m.title,
|
||||
m.content,
|
||||
m.type,
|
||||
r.isRead,
|
||||
r.readTime
|
||||
FROM
|
||||
global_message m
|
||||
INNER JOIN global_message_receiver r ON m.id = r.messageId
|
||||
$condition
|
||||
""");
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("m.status", "=", 2);
|
||||
cnd.and("r.receiverId", "=", SecurityUtil.getUserId());
|
||||
|
||||
if (isRead == 0) {
|
||||
cnd.and("r.isRead", "=", 0);
|
||||
} else if (isRead == 1) {
|
||||
cnd.and("r.isRead", "=", 1);
|
||||
}
|
||||
|
||||
|
||||
cnd.andEX("m.type", "=", type);
|
||||
cnd.desc("m.sendTime");
|
||||
sql.setCondition(cnd);
|
||||
|
||||
String title = "审批待办";
|
||||
String content = "您有一个新的审批任务需要处理,请及时登录系统查看。";
|
||||
List<String> receiverIds = Arrays.asList("17a7f8ad3ee947b4a26175049a9c253d");
|
||||
|
||||
// 自动发送到所有启用的渠道
|
||||
// globalMessageSendService.sendMessage(title, content, 2, receiverIds, null);
|
||||
|
||||
Pagination pagination = globalMessageService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At("/stats")
|
||||
@ApiOperation("获取消息统计信息")
|
||||
@SaCheckLogin
|
||||
public Result getStats() {
|
||||
List<GlobalMessage> messageList = dao.query(GlobalMessage.class, Cnd.where(GlobalMessage::getStatus, "=", 2));
|
||||
List<String> messageIds = messageList.stream().map(GlobalMessage::getId).toList();
|
||||
|
||||
// 查询总数
|
||||
int totalCount = dao.count(GlobalMessageReceiver.class, Cnd.where(GlobalMessageReceiver::getReceiverId, "=", SecurityUtil.getUserId()).and(GlobalMessageReceiver::getMessageId, "in", messageIds));
|
||||
|
||||
// 查询未读数
|
||||
int unreadCount = dao.count(GlobalMessageReceiver.class, Cnd.where(GlobalMessageReceiver::getReceiverId, "=", SecurityUtil.getUserId()).and(GlobalMessageReceiver::getIsRead, "=", 0).and(GlobalMessageReceiver::getMessageId, "in", messageIds));
|
||||
|
||||
// 查询已读数
|
||||
int readCount = totalCount - unreadCount;
|
||||
|
||||
NutMap stats = NutMap.NEW()
|
||||
.addv("total", totalCount)
|
||||
.addv("unread", unreadCount)
|
||||
.addv("read", readCount);
|
||||
// .addv("typeStats", typeStats);
|
||||
|
||||
return Result.success(stats);
|
||||
|
||||
}
|
||||
|
||||
@At("/detail/?")
|
||||
@ApiOperation("获取消息详情")
|
||||
@SaCheckLogin
|
||||
public Result getDetail(String messageId) {
|
||||
GlobalMessageReceiver receiver = dao.fetch(GlobalMessageReceiver.class, Cnd.where(GlobalMessageReceiver::getMessageId, "=", messageId).and(GlobalMessageReceiver::getReceiverId, "=", SecurityUtil.getUserId()));
|
||||
dao.fetchLinks(receiver, "globalMessage");
|
||||
|
||||
// 标记为已读
|
||||
if (receiver != null && !receiver.getIsRead()) {
|
||||
receiver.setIsRead(true);
|
||||
receiver.setReadTime(new Date());
|
||||
dao.update(receiver);
|
||||
}
|
||||
return Result.success(receiver);
|
||||
}
|
||||
|
||||
@At("/read/?")
|
||||
@ApiOperation("标记消息为已读")
|
||||
public Result markAsRead(String messageId) {
|
||||
GlobalMessageReceiver receiver = dao.fetch(GlobalMessageReceiver.class, Cnd.where("messageId", "=", messageId).and("receiverId", "=", SecurityUtil.getUserId()));
|
||||
if (receiver != null && !receiver.getIsRead()) {
|
||||
receiver.setIsRead(true);
|
||||
receiver.setReadTime(new Date());
|
||||
dao.update(receiver);
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At("/read/batch")
|
||||
@ApiOperation("批量标记消息为已读")
|
||||
@SaCheckLogin
|
||||
public Result batchMarkAsRead(@Param("messageIds") String[] messageIds) {
|
||||
List<GlobalMessageReceiver> receivers = dao.query(GlobalMessageReceiver.class, Cnd.where("messageId", "in", messageIds).and("receiverId", "=", SecurityUtil.getUserId()));
|
||||
for (GlobalMessageReceiver receiver : receivers) {
|
||||
receiver.setIsRead(true);
|
||||
receiver.setReadTime(new Date());
|
||||
}
|
||||
dao.update(receivers);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At("/read/all")
|
||||
@ApiOperation("标记所有消息为已读")
|
||||
@SaCheckLogin
|
||||
public Result markAllAsRead() {
|
||||
List<GlobalMessageReceiver> receivers = dao.query(GlobalMessageReceiver.class, Cnd.where("receiverId", "=", SecurityUtil.getUserId()));
|
||||
for (GlobalMessageReceiver receiver : receivers) {
|
||||
receiver.setIsRead(true);
|
||||
receiver.setReadTime(new Date());
|
||||
}
|
||||
dao.update(receivers);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -132,21 +132,4 @@ public class Sys_menu extends BaseModel implements Serializable {
|
||||
|
||||
//子菜单
|
||||
private List<Sys_menu> children = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 前端菜单数据
|
||||
*
|
||||
* @param menus
|
||||
* @param parentId
|
||||
* @return
|
||||
*/
|
||||
public static List<Sys_menu> createTreeMenus(List<Sys_menu> menus, String parentId) {
|
||||
List<Sys_menu> filterMenus = menus.stream().filter(v -> StringUtils.defaultString(v.getParentId(), "").equals(StringUtils.defaultString(parentId, ""))).collect(Collectors.toList());
|
||||
for (Sys_menu menu : filterMenus) {
|
||||
List<Sys_menu> childMenus = createTreeMenus(menus, menu.getId());
|
||||
menu.setChildren(childMenus);
|
||||
}
|
||||
return new ArrayList<>(filterMenus);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
|
||||
|
||||
@Override
|
||||
public Sys_file detail(String id) {
|
||||
return null;
|
||||
return fetch(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -181,13 +181,13 @@ public class SysFileServiceImpl extends BaseServiceImpl<Sys_file> implements Sys
|
||||
String[] parts = srcValue.split(";base64,");
|
||||
String base64Content = parts[1];
|
||||
|
||||
try{
|
||||
try {
|
||||
//转为在线地址 base64很喜欢被信息中心拦截
|
||||
byte[] imageBytes = Base64.getDecoder().decode(base64Content);
|
||||
String imgUrl = storageFile(SysFileEngineTypeEnum.MINIO.getValue(), genFileKey(R.UU32(), R.UU32() + ".png"), imageBytes, false);
|
||||
String replacement = "<img src=\"" + imgUrl + "\">";
|
||||
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
|
||||
}catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
matcher.appendReplacement(sb, Matcher.quoteReplacement("<img src=>"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.budwk.app.base.exception.UnknownAccountException;
|
||||
import com.budwk.app.base.interceptor.sLog.SLogService;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.base.utils.PwdUtil;
|
||||
import com.budwk.app.base.utils.SysMenuUtil;
|
||||
import com.budwk.app.sys.models.*;
|
||||
import com.budwk.app.sys.services.SysMenuService;
|
||||
import com.budwk.app.sys.services.SysRoleService;
|
||||
@@ -126,27 +127,27 @@ public class SysUserServiceImpl extends BaseServiceImpl<Sys_user> implements Sys
|
||||
List<Sys_module> userModules = dao().query(Sys_module.class, Cnd.where("id", "in", allModuleIds.toArray()).asc(Sys_module::getSortNum));
|
||||
|
||||
//设置pc菜单到模块
|
||||
if (Lang.isNotEmpty(pcModuleIds)) {
|
||||
//用户的pc端模块
|
||||
List<Sys_module> pcModules = userModules.stream().filter(module -> pcModuleIds.contains(module.getId())).toList();
|
||||
//pc菜单转为树结构
|
||||
List<Sys_menu> pcTreeMenus = Sys_menu.createTreeMenus(pcMenus, null);
|
||||
for (Sys_module pcModule : pcModules) {
|
||||
pcModule.setMenus(pcTreeMenus.stream().filter(menu -> menu.getModuleId().equals(pcModule.getId())).collect(Collectors.toList()));
|
||||
}
|
||||
user.setPcModuleMenus(pcModules);
|
||||
}
|
||||
|
||||
//设置h5菜单到模块
|
||||
if (Lang.isNotEmpty(h5ModuleIds)) {
|
||||
//用户的h5端模块
|
||||
List<Sys_module> h5Modules = userModules.stream().filter(module -> h5ModuleIds.contains(module.getId())).toList();
|
||||
List<Sys_menu> h5TreeMenus = Sys_menu.createTreeMenus(h5Menus, null);
|
||||
for (Sys_module h5Module : h5Modules) {
|
||||
h5Module.setMenus(h5TreeMenus.stream().filter(menu -> menu.getModuleId().equals(h5Module.getId())).collect(Collectors.toList()));
|
||||
}
|
||||
user.setH5ModuleMenus(h5Modules);
|
||||
}
|
||||
// if (Lang.isNotEmpty(pcModuleIds)) {
|
||||
// //用户的pc端模块
|
||||
// List<Sys_module> pcModules = userModules.stream().filter(module -> pcModuleIds.contains(module.getId())).toList();
|
||||
// //pc菜单转为树结构
|
||||
// List<Sys_menu> pcTreeMenus = SysMenuUtil.createTreeMenus(pcMenus, null);
|
||||
// for (Sys_module pcModule : pcModules) {
|
||||
// pcModule.setMenus(pcTreeMenus.stream().filter(menu -> menu.getModuleId().equals(pcModule.getId())).collect(Collectors.toList()));
|
||||
// }
|
||||
// user.setPcModuleMenus(pcModules);
|
||||
// }
|
||||
//
|
||||
// //设置h5菜单到模块
|
||||
// if (Lang.isNotEmpty(h5ModuleIds)) {
|
||||
// //用户的h5端模块
|
||||
// List<Sys_module> h5Modules = userModules.stream().filter(module -> h5ModuleIds.contains(module.getId())).toList();
|
||||
// List<Sys_menu> h5TreeMenus = SysMenuUtil.createTreeMenus(h5Menus, null);
|
||||
// for (Sys_module h5Module : h5Modules) {
|
||||
// h5Module.setMenus(h5TreeMenus.stream().filter(menu -> menu.getModuleId().equals(h5Module.getId())).collect(Collectors.toList()));
|
||||
// }
|
||||
// user.setH5ModuleMenus(h5Modules);
|
||||
// }
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.web.commons.auth.service;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.budwk.app.base.utils.SysMenuUtil;
|
||||
import com.budwk.app.sys.models.Sys_menu;
|
||||
import com.budwk.app.sys.models.Sys_user;
|
||||
import com.budwk.app.sys.services.SysUserService;
|
||||
@@ -64,7 +65,7 @@ public class AuthService {
|
||||
|
||||
public Object getSubAppMenus(String appId) {
|
||||
List<Sys_menu> menus = sysUserService.getMenus(SecurityUtil.getUserId());
|
||||
List<Sys_menu> pcTreeMenus = Sys_menu.createTreeMenus(menus, appId);
|
||||
List<Sys_menu> pcTreeMenus = SysMenuUtil.createTreeMenus(menus, appId);
|
||||
return pcTreeMenus;
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -5,6 +5,8 @@ import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.sys.models.Sys_file;
|
||||
import com.budwk.app.sys.utils.SysFileMinIoUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.edu.models.EduChapters;
|
||||
import com.budwk.app.zhgh.dayofficework.edu.models.EduCourses;
|
||||
import com.budwk.app.zhgh.dayofficework.edu.models.EduVideos;
|
||||
@@ -132,6 +134,15 @@ public class EduCoursesController {
|
||||
@SLog(tag = "理论学习课程", msg = "添加课程视频")
|
||||
public Result insert(EduVideos eduVideos) {
|
||||
dao.insert(eduVideos);
|
||||
String url = eduVideos.getUrl();
|
||||
String id = url.split("id=")[1];
|
||||
Sys_file file = dao.fetch(Sys_file.class, id);
|
||||
byte[] bytes = SysFileMinIoUtil.getFileBytes(file.getBucket(), file.getStoragePath());
|
||||
|
||||
// 获取视频时长
|
||||
|
||||
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
+9
-15
@@ -115,20 +115,19 @@ public class H5EduController {
|
||||
}
|
||||
|
||||
// 获取课程总视频数
|
||||
// int totalVideos = dao.count("edu_videos v",
|
||||
// "LEFT JOIN edu_chapters c ON v.chapterId = c.id",
|
||||
// Cnd.where("c.courseId", "=", courseId));
|
||||
int totalVideos = 10;
|
||||
List<EduChapters> chapters = dao.query(EduChapters.class, Cnd.where(EduChapters::getCourseId, "=", courseId));
|
||||
List<String> chapterIds = chapters.stream().map(EduChapters::getId).toList();
|
||||
int totalVideos = dao.count(EduVideos.class, Cnd.where(EduVideos::getChapterId, "in", chapterIds));
|
||||
|
||||
// 获取已完成的视频ID列表
|
||||
List<EduStudyRecords> completedRecords = dao.query(EduStudyRecords.class,
|
||||
Cnd.where("userId", "=", userId)
|
||||
.and("courseId", "=", courseId)
|
||||
.and("completed", "=", true));
|
||||
Cnd.where(EduStudyRecords::getUserId, "=", userId)
|
||||
.and(EduStudyRecords::getCourseId, "=", courseId)
|
||||
.and(EduStudyRecords::getIsCompleted, "=", 1));
|
||||
|
||||
List<String> completedVideoIds = completedRecords.stream()
|
||||
.map(EduStudyRecords::getVideoId)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
.map(EduStudyRecords::getVideoId)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
|
||||
int completedVideos = completedVideoIds.size();
|
||||
|
||||
@@ -196,13 +195,8 @@ public class H5EduController {
|
||||
return Result.error("观看时长不能为空或小于0");
|
||||
}
|
||||
|
||||
String userId = SecurityUtil.getUserId();
|
||||
if (userId == null) {
|
||||
return Result.error("用户未登录");
|
||||
}
|
||||
|
||||
EduStudyRecords record = eduStudyRecordsService.saveOrUpdateWatchProgress(
|
||||
userId, videoId, courseId, watchedDuration);
|
||||
SecurityUtil.getUserId(), videoId, courseId, watchedDuration);
|
||||
|
||||
return Result.success(record);
|
||||
} catch (Exception e) {
|
||||
|
||||
+5
-7
@@ -8,8 +8,6 @@ import java.util.List;
|
||||
/**
|
||||
* @ClassName EduStudyRecordsService
|
||||
* @Description 学习记录服务接口
|
||||
* @Author AI Assistant
|
||||
* @Date 2024/01/01
|
||||
*/
|
||||
public interface EduStudyRecordsService extends BaseService<EduStudyRecords> {
|
||||
|
||||
@@ -79,17 +77,17 @@ public interface EduStudyRecordsService extends BaseService<EduStudyRecords> {
|
||||
// Getters and Setters
|
||||
public int getTotalVideos() { return totalVideos; }
|
||||
public void setTotalVideos(int totalVideos) { this.totalVideos = totalVideos; }
|
||||
|
||||
|
||||
public int getCompletedVideos() { return completedVideos; }
|
||||
public void setCompletedVideos(int completedVideos) { this.completedVideos = completedVideos; }
|
||||
|
||||
|
||||
public int getTotalDuration() { return totalDuration; }
|
||||
public void setTotalDuration(int totalDuration) { this.totalDuration = totalDuration; }
|
||||
|
||||
|
||||
public int getWatchedDuration() { return watchedDuration; }
|
||||
public void setWatchedDuration(int watchedDuration) { this.watchedDuration = watchedDuration; }
|
||||
|
||||
|
||||
public double getCompletionRate() { return completionRate; }
|
||||
public void setCompletionRate(double completionRate) { this.completionRate = completionRate; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,5 +1,6 @@
|
||||
package com.budwk.app.zhgh.dayofficework.edu.service.impl;
|
||||
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.edu.models.EduStudyRecords;
|
||||
import com.budwk.app.zhgh.dayofficework.edu.models.EduVideos;
|
||||
@@ -17,8 +18,6 @@ import java.util.List;
|
||||
/**
|
||||
* @ClassName EduStudyRecordsServiceImpl
|
||||
* @Description 学习记录服务实现类
|
||||
* @Author AI Assistant
|
||||
* @Date 2024/01/01
|
||||
*/
|
||||
@Slf4j
|
||||
@IocBean(args = {"refer:dao"})
|
||||
@@ -74,7 +73,7 @@ public class EduStudyRecordsServiceImpl extends BaseServiceImpl<EduStudyRecords>
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("保存观看进度失败", e);
|
||||
throw new RuntimeException("保存观看进度失败", e);
|
||||
throw new BaseException("保存观看进度失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import lombok.experimental.Accessors;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Table("global_message")
|
||||
@@ -74,6 +75,11 @@ public class GlobalMessage extends BaseModel {
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer status;
|
||||
|
||||
@Column
|
||||
@Comment("发送时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date sendTime;
|
||||
|
||||
@Column
|
||||
@Comment("发送成功")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
|
||||
+2
-1
@@ -54,6 +54,7 @@ public class GlobalMessageReceiver extends BaseModel {
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date readTime;
|
||||
|
||||
|
||||
@One(target = GlobalMessage.class, field = "messageId")
|
||||
private GlobalMessage globalMessage;
|
||||
|
||||
}
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.budwk.app.zhgh.dayofficework.message.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.message.models.GlobalMessage;
|
||||
|
||||
public interface GlobalMessageService extends BaseService<GlobalMessage> {
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.budwk.app.zhgh.dayofficework.message.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.message.models.GlobalMessage;
|
||||
import com.budwk.app.zhgh.dayofficework.message.service.GlobalMessageService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class GlobalMessageServiceImpl extends BaseServiceImpl<GlobalMessage> implements GlobalMessageService {
|
||||
public GlobalMessageServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
}
|
||||
+15
-6
@@ -6,6 +6,7 @@ import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvActivity;
|
||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvBank;
|
||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvOption;
|
||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
|
||||
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvActivityService;
|
||||
@@ -49,7 +50,7 @@ public class QsvActivityController {
|
||||
public Result pageData(@Valid PageForm pageForm, Integer year, String title) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("YEAR(startTime)", "=", year);
|
||||
cnd.and(Cnd.likeEX("title",title));
|
||||
cnd.and(Cnd.likeEX("title", title));
|
||||
Pagination pagination = qsvActivityService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
@@ -79,7 +80,7 @@ public class QsvActivityController {
|
||||
return Result.success(activity);
|
||||
}
|
||||
|
||||
// 删除问卷
|
||||
// 删除问卷
|
||||
@At
|
||||
@SaCheckPermission("qsv.activity")
|
||||
@ApiOperation("删除问卷")
|
||||
@@ -90,7 +91,7 @@ public class QsvActivityController {
|
||||
}
|
||||
|
||||
|
||||
// 保存问卷题目
|
||||
// 保存问卷题目
|
||||
@At
|
||||
@SaCheckPermission("qsv.activity")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@@ -143,15 +144,23 @@ public class QsvActivityController {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
// 查询问卷题目
|
||||
// 查询问卷题目
|
||||
@At
|
||||
@SaCheckPermission("qsv.activity")
|
||||
@ApiOperation("查询问卷题目")
|
||||
@SLog(type = "qsv.activity", tag = "查询问卷题目", msg = "查询问卷题目")
|
||||
public Result listSubjects(@Valid String activityId) {
|
||||
List<QsvSubject> subjects = dao.query(QsvSubject.class, Cnd.where("activityId", "=", activityId).asc("sortNum"));
|
||||
dao.fetchLinks(subjects, "options",Cnd.NEW().asc("sortNum"));
|
||||
dao.fetchLinks(subjects, "options", Cnd.NEW().asc("sortNum"));
|
||||
return Result.success(subjects);
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("qsv.activity")
|
||||
@ApiOperation("查询问卷题库")
|
||||
public Result listBank(@Valid String category) {
|
||||
List<QsvBank> list = dao.query(QsvBank.class, Cnd.where(QsvBank::getCategory, "=", category));
|
||||
return Result.success(list);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.budwk.app.zhgh.dayofficework.qsv.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvBank;
|
||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvSubject;
|
||||
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvBankService;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.Arrays;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/qsv/bank")
|
||||
@Ok("json:full")
|
||||
@ApiOperation("问卷调查题库管理")
|
||||
public class QsvBankController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
@Inject
|
||||
private QsvBankService qsvBankService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/qsv/bank/index.html")
|
||||
@SaCheckPermission("qsv.bank")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("qsv.bank")
|
||||
public Result pageData(PageForm pageForm) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
|
||||
cnd.where().andLike(QsvBank::getTitle, pageForm.getSearchKeyword());
|
||||
}
|
||||
Pagination pagination = qsvBankService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("qsv.bank")
|
||||
@ApiOperation("保存题库")
|
||||
public Result save(@Param("qsvBank") QsvBank qsvBank) {
|
||||
dao.insert(qsvBank);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("qsv.bank")
|
||||
@ApiOperation("修改题库")
|
||||
public Result update(@Param("qsvBank") QsvBank qsvBank) {
|
||||
dao.updateIgnoreNull(qsvBank);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
@At
|
||||
@SaCheckPermission("qsv.bank")
|
||||
public Result findOne(@Valid String id) {
|
||||
QsvBank qsvBank = dao.fetch(QsvBank.class, id);
|
||||
return Result.success(qsvBank);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("qsv.bank")
|
||||
@ApiOperation("删除题库")
|
||||
public Result delete(@Valid String id) {
|
||||
dao.delete(QsvBank.class, id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("qsv.bank")
|
||||
@ApiOperation("保存题库题目")
|
||||
public Result saveSubjects(@Valid String id, @Param("subjects") QsvSubject[] subjects) {
|
||||
QsvBank qsvBank = dao.fetch(QsvBank.class, id);
|
||||
qsvBank.setSubjects(Arrays.asList(subjects));
|
||||
dao.updateIgnoreNull(qsvBank);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
package com.budwk.app.zhgh.dayofficework.qsv.h5controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
@@ -25,13 +26,13 @@ public class H5QsvController {
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhghh5/dayofficework/qsv/index.html")
|
||||
@SaCheckLogin
|
||||
@SaCheckPermission("h5.qsv")
|
||||
public void index() {
|
||||
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckLogin
|
||||
@SaCheckPermission("h5.qsv")
|
||||
public Result pageData(@Valid PageForm pageForm, @Valid String category) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("category", "=", category);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.budwk.app.zhgh.dayofficework.qsv.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Table("qsv_bank")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("问卷调查题库")
|
||||
public class QsvBank extends BaseModel {
|
||||
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("题库名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String title;
|
||||
|
||||
@Column
|
||||
@Comment("说明描述")
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
private String description;
|
||||
|
||||
@Column
|
||||
@Comment("所属模块(quiz, survey, vote)")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String category;
|
||||
|
||||
@Column
|
||||
@Comment("题目")
|
||||
@ColDefine(type = ColType.MYSQL_JSON)
|
||||
private List<QsvSubject> subjects;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.budwk.app.zhgh.dayofficework.qsv.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvBank;
|
||||
|
||||
public interface QsvBankService extends BaseService<QsvBank> {
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.budwk.app.zhgh.dayofficework.qsv.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.qsv.models.QsvBank;
|
||||
import com.budwk.app.zhgh.dayofficework.qsv.service.QsvBankService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class QsvBankServiceImpl extends BaseServiceImpl<QsvBank> implements QsvBankService {
|
||||
public QsvBankServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -46,8 +46,8 @@ public class SuggestionBoxMineController {
|
||||
|
||||
@At
|
||||
@Ok("beetl:/platform/zhghh5/democratic/suggestionBox/mine/index.html")
|
||||
@SaCheckLogin
|
||||
public void h5Index() {
|
||||
@SaCheckPermission("h5.suggestionBox.mine")
|
||||
public void h5() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -24,6 +24,7 @@ import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@@ -48,8 +49,8 @@ public class SuggestionBoxWriteController {
|
||||
|
||||
@At
|
||||
@Ok("beetl:/platform/zhghh5/democratic/suggestionBox/write/index.html")
|
||||
@SaCheckLogin
|
||||
public void h5Index(){
|
||||
@SaCheckPermission("h5.suggestionBox.write")
|
||||
public void h5(){
|
||||
|
||||
}
|
||||
|
||||
@@ -58,6 +59,7 @@ public class SuggestionBoxWriteController {
|
||||
@ApiOperation("保存申请")
|
||||
@SLog(tag = "建言献策-填写申请", msg = "保存申请")
|
||||
public Result save(@Param("data") SuggestionBox suggestionBox) {
|
||||
suggestionBox.setSubmitTime(new Date());
|
||||
dao.insertOrUpdate(suggestionBox);
|
||||
return Result.success();
|
||||
}
|
||||
@@ -69,6 +71,7 @@ public class SuggestionBoxWriteController {
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SLog(tag = "建言献策-填写申请", msg = "提交申请")
|
||||
public Result submit(@Param("data") SuggestionBox suggestionBox) {
|
||||
suggestionBox.setSubmitTime(new Date());
|
||||
dao.insertOrUpdate(suggestionBox);
|
||||
// 开启流程实例
|
||||
Dict args = Dict.create();
|
||||
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
package com.budwk.app.zhgh.democratic.suggestionBox.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
@@ -43,8 +42,8 @@ public class SuggestionXghController {
|
||||
|
||||
@At
|
||||
@Ok("beetl:/platform/zhghh5/democratic/suggestionBox/xgh/index.html")
|
||||
@SaCheckPermission("suggestionBox.xgh")
|
||||
public void h5Index() {
|
||||
@SaCheckPermission("h5.suggestionBox.xgh")
|
||||
public void h5() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -44,11 +44,11 @@ function pjaxReplace(url, data = {}) {
|
||||
}
|
||||
$.pjax({
|
||||
url: url,
|
||||
container: "#container",
|
||||
container: "#sub-app-container-main-content-body",
|
||||
maxCacheLength: 0,
|
||||
push: false,
|
||||
replace: true,
|
||||
fragment: "#container",
|
||||
fragment: "#sub-app-container-main-content-body",
|
||||
timeout: 8000,
|
||||
data
|
||||
})
|
||||
|
||||
@@ -48,13 +48,13 @@ const commonUtil = {
|
||||
|
||||
let loading = null
|
||||
|
||||
if(isMobile){
|
||||
if (isMobile) {
|
||||
loading = vant.Toast.loading({
|
||||
duration: 0,
|
||||
forbidClick: true,
|
||||
message: '导出中,请耐心等待'
|
||||
});
|
||||
}else{
|
||||
} else {
|
||||
loading = ELEMENT.Loading.service({
|
||||
lock: true,
|
||||
text: "导出中,请耐心等待",
|
||||
@@ -64,7 +64,7 @@ const commonUtil = {
|
||||
}
|
||||
|
||||
Vue.prototype.$axios
|
||||
.post(url, data, { responseType: "blob" })
|
||||
.post(url, data, {responseType: "blob"})
|
||||
.then((response) => {
|
||||
if (response.data.type === "application/json") {
|
||||
try {
|
||||
@@ -102,9 +102,9 @@ const commonUtil = {
|
||||
loading.close()
|
||||
})
|
||||
.finally(() => {
|
||||
if(isMobile){
|
||||
if (isMobile) {
|
||||
loading.clear()
|
||||
}else{
|
||||
} else {
|
||||
loading.close()
|
||||
}
|
||||
})
|
||||
@@ -234,6 +234,7 @@ const commonUtil = {
|
||||
function doSm2Encrypt(msgString) {
|
||||
return sm2.doEncrypt(msgString, publicKey, cipherMode)
|
||||
}
|
||||
|
||||
// SM2数组加密
|
||||
function doSm2ArrayEncrypt(msgString) {
|
||||
return sm2.doEncrypt(msgString, publicKey, cipherMode)
|
||||
@@ -243,6 +244,24 @@ const commonUtil = {
|
||||
doSm2Encrypt,
|
||||
doSm2ArrayEncrypt
|
||||
}
|
||||
},
|
||||
|
||||
// pjax跳转
|
||||
pjaxPush(url, data = {}) {
|
||||
const {pathname, search} = location
|
||||
if (url === pathname + search) {
|
||||
return
|
||||
}
|
||||
$.pjax({
|
||||
url: url,
|
||||
container: "#sub-app-container-main-content-body",
|
||||
maxCacheLength: 0,
|
||||
push: false,
|
||||
replace: true,
|
||||
fragment: "#sub-app-container-main-content-body",
|
||||
timeout: 8000,
|
||||
data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +278,8 @@ function GetQueryString(name) {
|
||||
}
|
||||
|
||||
function clearAllTimers() {
|
||||
const maxTimeoutId = setTimeout(function () {}, 0)
|
||||
const maxTimeoutId = setTimeout(function () {
|
||||
}, 0)
|
||||
for (let i = 0; i <= maxTimeoutId; i++) {
|
||||
clearTimeout(i)
|
||||
clearInterval(i)
|
||||
@@ -291,10 +311,10 @@ function base64ToFile(base64Data, filename) {
|
||||
}
|
||||
|
||||
// 使用Blob对象创建File对象
|
||||
const blob = new Blob([uInt8Array], { type: contentType })
|
||||
const blob = new Blob([uInt8Array], {type: contentType})
|
||||
blob.lastModifiedDate = new Date()
|
||||
blob.name = filename
|
||||
return new File([blob], filename, { type: contentType })
|
||||
return new File([blob], filename, {type: contentType})
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,72 @@
|
||||
/**
|
||||
* Minified by jsDelivr using clean-css v5.3.3.
|
||||
* Original file: /npm/nprogress@0.2.0/nprogress.css
|
||||
*
|
||||
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
||||
*/
|
||||
#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translate(0,-4px);-ms-transform:rotate(3deg) translate(0,-4px);transform:rotate(3deg) translate(0,-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:solid 2px transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0)}100%{-webkit-transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}
|
||||
/*# sourceMappingURL=/sm/4400c1e6b434bc414f3870cc8d155b3335f1f32d1a07381e06cc8a8bd869de73.map */
|
||||
#nprogress {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#nprogress .bar {
|
||||
background: #29d;
|
||||
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
/* Fancy blur effect */
|
||||
#nprogress .peg {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
width: 100px;
|
||||
height: 100%;
|
||||
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
|
||||
opacity: 1.0;
|
||||
|
||||
-webkit-transform: rotate(3deg) translate(0px, -4px);
|
||||
-ms-transform: rotate(3deg) translate(0px, -4px);
|
||||
transform: rotate(3deg) translate(0px, -4px);
|
||||
}
|
||||
|
||||
/* Remove these to get rid of the spinner */
|
||||
#nprogress .spinner {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
#nprogress .spinner-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
box-sizing: border-box;
|
||||
|
||||
border: solid 2px transparent;
|
||||
border-top-color: #29d;
|
||||
border-left-color: #29d;
|
||||
border-radius: 50%;
|
||||
|
||||
-webkit-animation: nprogress-spinner 400ms linear infinite;
|
||||
animation: nprogress-spinner 400ms linear infinite;
|
||||
}
|
||||
|
||||
.nprogress-custom-parent {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nprogress-custom-parent #nprogress .spinner,
|
||||
.nprogress-custom-parent #nprogress .bar {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@-webkit-keyframes nprogress-spinner {
|
||||
0% { -webkit-transform: rotate(0deg); }
|
||||
100% { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes nprogress-spinner {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@@ -1,179 +1,252 @@
|
||||
<template>
|
||||
<div class="guava-main-content">
|
||||
<div v-show="v === 'index'" key="index" class="transition-item">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<el-card v-if="v === 'edit'" key="edit" shadow="never" class="animated-card">
|
||||
<template #header>
|
||||
<el-row type="flex">
|
||||
<el-col :span="12">
|
||||
<el-button icon="el-icon-back" style="font-size: 16px; padding: 0" type="text" @click="v = 'index'">返回</el-button>
|
||||
</el-col>
|
||||
<el-col :span="12" style="display: flex; justify-content: flex-end">
|
||||
<slot name="edit_func"></slot>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<div :class="{ card_scroll: edit_scroll }">
|
||||
<slot name="edit"></slot>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card v-if="v === 'view'" key="view" shadow="never" class="animated-card">
|
||||
<template #header>
|
||||
<el-button icon="el-icon-back" style="font-size: 16px; padding: 0" type="text" @click="v = 'index'">返回</el-button>
|
||||
</template>
|
||||
<div :class="{ card_scroll: !view_page_scroll }">
|
||||
<slot name="view"></slot>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card v-if="v === 'approval'" key="approval" shadow="never" class="animated-card">
|
||||
<template #header>
|
||||
<el-button icon="el-icon-back" style="font-size: 16px; padding: 0" type="text" @click="v = 'index'">返回</el-button>
|
||||
</template>
|
||||
<div :class="{ card_scroll: approval_scroll }">
|
||||
<slot name="approval"></slot>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card v-if="v === 'public'" key="public" shadow="never" class="animated-card">
|
||||
<div slot="header" class="clearfix">
|
||||
<el-button icon="el-icon-back" style="font-size: 16px; padding: 0" type="text" @click="back()">返回</el-button>
|
||||
</div>
|
||||
<div :class="{ card_scroll: public_card_scroll }">
|
||||
<slot name="public"></slot>
|
||||
</div>
|
||||
</el-card>
|
||||
<div class="guava-main-content">
|
||||
<div v-show="v === 'index'" key="index" class="transition-item">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
<div v-if="v === 'edit'" key="edit" class="page-container animated-card">
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<el-button icon="el-icon-back" style="font-size: 16px; padding: 0" type="text" @click="v = 'index'">返回</el-button>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<slot name="edit_func"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
<slot name="edit"></slot>
|
||||
</div>
|
||||
<div class="page-footer" v-if="$slots.edit_func">
|
||||
<slot name="edit_func"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="v === 'view'" key="view" class="page-container animated-card">
|
||||
<div class="page-header">
|
||||
<el-button icon="el-icon-back" style="font-size: 16px; padding: 0" type="text" @click="v = 'index'">返回</el-button>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
<slot name="view"></slot>
|
||||
</div>
|
||||
<div class="page-footer" v-if="$slots.view_footer">
|
||||
<slot name="view_footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="v === 'approval'" key="approval" class="page-container animated-card">
|
||||
<div class="page-header">
|
||||
<el-button icon="el-icon-back" style="font-size: 16px; padding: 0" type="text" @click="v = 'index'">返回</el-button>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
<slot name="approval"></slot>
|
||||
</div>
|
||||
<div class="page-footer" v-if="$slots.approval_footer">
|
||||
<slot name="approval_footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="v === 'public'" key="public" class="page-container animated-card">
|
||||
<div class="page-header">
|
||||
<el-button icon="el-icon-back" style="font-size: 16px; padding: 0" type="text" @click="back()">返回</el-button>
|
||||
</div>
|
||||
<div class="page-content">
|
||||
<slot name="public"></slot>
|
||||
</div>
|
||||
<div class="page-footer" v-if="$slots.public_footer">
|
||||
<slot name="public_footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
module.exports = {
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: "index"
|
||||
},
|
||||
//为true的时候页面不会滚动 card-body滚动 edit view 自己来控制
|
||||
edit_scroll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
view_scroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
approval_scroll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
//view 页面是否滚动
|
||||
view_page_scroll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
public_card_scroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: "index"
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
v: "index"
|
||||
}
|
||||
//为true的时候页面不会滚动 card-body滚动 edit view 自己来控制
|
||||
edit_scroll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
watch: {
|
||||
v(newValue, oldValue) {
|
||||
this.$emit("vchange", { newValue, oldValue })
|
||||
this.$emit("input", newValue)
|
||||
}
|
||||
view_scroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
methods: {
|
||||
index(callback = () => {}) {
|
||||
this.v = "index"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
},
|
||||
edit(callback = () => {}) {
|
||||
this.v = "edit"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
},
|
||||
view(callback = () => {}) {
|
||||
this.v = "view"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
},
|
||||
approval(callback = () => {}) {
|
||||
this.v = "approval"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
},
|
||||
public(callback = () => {}) {
|
||||
this.v = "public"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
},
|
||||
back(callback = () => {}) {
|
||||
this.v = "index"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
}
|
||||
approval_scroll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//view 页面是否滚动
|
||||
view_page_scroll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
public_card_scroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
v: "index"
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(newVal) {
|
||||
this.v = newVal
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
v(newValue, oldValue) {
|
||||
this.$emit("vchange", { newValue, oldValue })
|
||||
this.$emit("input", newValue)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
index(callback = () => {}) {
|
||||
this.v = "index"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
},
|
||||
edit(callback = () => {}) {
|
||||
this.v = "edit"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
},
|
||||
view(callback = () => {}) {
|
||||
this.v = "view"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
},
|
||||
approval(callback = () => {}) {
|
||||
this.v = "approval"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
},
|
||||
public(callback = () => {}) {
|
||||
this.v = "public"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
},
|
||||
back(callback = () => {}) {
|
||||
this.v = "index"
|
||||
this.$nextTick(() => {
|
||||
callback()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
.guava-main-content {
|
||||
min-height: 100%;
|
||||
/*height: 100vh;
|
||||
overflow: hidden;*/
|
||||
}
|
||||
|
||||
.page-container {
|
||||
height: calc(100vh - 84px);
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation-name: cardAnimation;
|
||||
animation-duration: 0.3s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
flex-shrink: 0;
|
||||
padding: 12px 24px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
background: #fff;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.header-right button{
|
||||
padding: 9px 15px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
.page-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px 24px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保持滚动功能 */
|
||||
.page-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.page-content::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.page-content::-webkit-scrollbar-thumb {
|
||||
background: rgba(0,0,0,0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.page-content::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.page-footer{
|
||||
flex-shrink: 0;
|
||||
padding: 12px 10px;
|
||||
border-top: 1px solid rgb(235, 238, 245);
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
@keyframes cardAnimation {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.animated-card {
|
||||
animation-name: cardAnimation;
|
||||
animation-duration: 0.5s;
|
||||
animation-fill-mode: both;
|
||||
margin-top: 0 !important;
|
||||
animation-name: cardAnimation;
|
||||
animation-duration: 0.3s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
@keyframes cardAnimation {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.card_scroll {
|
||||
max-height: calc(100vh - 56px - 40px - 30px - 50px - 34px);
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card_scroll {
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.card_scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card_scroll::-webkit-scrollbar-vertical {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card_scroll::-webkit-scrollbar-horizontal {
|
||||
display: none;
|
||||
.transition-item {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -112,6 +112,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
|
||||
.content-body {
|
||||
min-height: 400px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@@ -137,7 +138,8 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
</div>
|
||||
|
||||
<el-scrollbar>
|
||||
<el-menu unique-opened :default-active="activeMenuIndex" :default-openeds="openedMenus" @select="menuSelect">
|
||||
<el-menu unique-opened :default-active="activeMenuIndex" :default-openeds="openedMenus"
|
||||
@select="menuSelect">
|
||||
<menu-list :menus="leftMenus" :depth="0"></menu-list>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
@@ -145,11 +147,6 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
|
||||
<!-- 右侧主体区域 -->
|
||||
<main class="sub-app-container-main-content" id="sub-app-container-main-content">
|
||||
<!-- <header class="content-header">-->
|
||||
<!-- <h1 class="content-title"></h1>-->
|
||||
<!-- <!– <p class="content-subtitle"></p>–>-->
|
||||
<!-- </header>-->
|
||||
|
||||
<div class="content-body" id="sub-app-container-main-content-body">${layoutContent!}</div>
|
||||
</main>
|
||||
|
||||
@@ -169,7 +166,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
})
|
||||
|
||||
$(document).on("pjax:send", function () {
|
||||
NProgress.configure({ parent: ".sub-app-container-main-content" })
|
||||
NProgress.configure({parent: ".sub-app-container-main-content"})
|
||||
NProgress.start()
|
||||
$("#sub-app-container-main-content").hide()
|
||||
})
|
||||
@@ -190,7 +187,8 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
try {
|
||||
const app = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app"))
|
||||
$("#sub-app-container #sidebar-menu .menu-header .menu-title").text(app?.name)
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
@@ -209,36 +207,41 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
"menu-list": {
|
||||
name: "menu-list",
|
||||
template:
|
||||
/*language=HTML*/
|
||||
`
|
||||
<div>
|
||||
<template v-for="(item, index) in menus">
|
||||
<el-submenu :index="item.id" v-if="item.children && item.children.length > 0">
|
||||
<template #title>
|
||||
<i :class="item.icon" v-if="item.icon" :size="18"></i>
|
||||
<i v-else class="el-icon-paperclip"></i>
|
||||
<span>{{ item.name }}</span>
|
||||
</template>
|
||||
<menu-list :menus="item.children" :depth="depth + 1" />
|
||||
</el-submenu>
|
||||
<el-menu-item :index="item.id" v-else>
|
||||
<a :href="item.href" data-pjax>
|
||||
<i :class="item.icon" v-if="item.icon" :size="18"></i>
|
||||
<i v-else :class="item.icon ? item.icon : 'el-icon-menu'"></i>
|
||||
<span>{{ item.name }}</span>
|
||||
</a>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
/*language=HTML*/
|
||||
`
|
||||
<div>
|
||||
<template v-for="(item, index) in menus">
|
||||
<el-submenu :index="item.id" v-if="item.children && item.children.length > 0">
|
||||
<template #title>
|
||||
<i :class="item.icon" v-if="item.icon" :size="18"></i>
|
||||
<i v-else class="el-icon-paperclip"></i>
|
||||
<span>{{ item.name }}</span>
|
||||
</template>
|
||||
<menu-list :menus="item.children" :depth="depth + 1"/>
|
||||
</el-submenu>
|
||||
<el-menu-item :index="item.id" v-else>
|
||||
<a :href="item.href" data-pjax>
|
||||
<i :class="item.icon" v-if="item.icon" :size="18"></i>
|
||||
<i v-else :class="item.icon ? item.icon : 'el-icon-menu'"></i>
|
||||
<span>{{ item.name }}</span>
|
||||
</a>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</div>
|
||||
`,
|
||||
props: ["menus", "depth"]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 菜单数据
|
||||
menus: [],
|
||||
// 当前激活的菜单索引
|
||||
activeMenuIndex: null,
|
||||
openedMenus: []
|
||||
// 当前打开的菜单索引
|
||||
openedMenus: [],
|
||||
// 默认选中菜单是否加载中
|
||||
defaultSelectLoading: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -260,12 +263,13 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
const app = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app"))
|
||||
window.sessionStorage.setItem("zhgh_sub_app_left_menu_active_index-" + app.id, index)
|
||||
window.sessionStorage.setItem("zhgh_sub_app_left_menu_opens-" + app.id, JSON.stringify(indexPath))
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
}
|
||||
},
|
||||
// 获取菜单
|
||||
getMenus(appId) {
|
||||
if (!appId) return
|
||||
$.get("/platform/sys/user/subAppMenus", { appId }).then((res) => {
|
||||
this.$axios.post("/platform/sys/user/subAppMenus", {appId}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.menus = res.data
|
||||
window.sessionStorage.setItem("zhgh_sub_app_menus", JSON.stringify(res.data))
|
||||
@@ -276,6 +280,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
})
|
||||
},
|
||||
|
||||
// 设置应用信息
|
||||
setAppInfo(app) {
|
||||
if (app) {
|
||||
// 储存到缓存
|
||||
@@ -294,26 +299,73 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
this.openedMenus = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app_left_menu_opens-" + app.id)) || []
|
||||
console.log(this.openedMenus)
|
||||
console.log(this.activeMenuIndex)
|
||||
} catch (e) {}
|
||||
|
||||
if (!this.activeMenuIndex) {
|
||||
this.defaultSelect()
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
}
|
||||
},
|
||||
|
||||
// 默认选中
|
||||
defaultSelect() {
|
||||
// 查找第一个有href的子菜单
|
||||
function findFirstChildWithHref(menus) {
|
||||
for (const menu of menus) {
|
||||
// 如果当前菜单有href,返回它
|
||||
if (menu.href) {
|
||||
return {
|
||||
menu: menu,
|
||||
parentIds: [menu.parentId]
|
||||
};
|
||||
}
|
||||
|
||||
// 如果有子菜单,递归查找
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
const result = findFirstChildWithHref(menu.children);
|
||||
if (result) {
|
||||
// 添加当前菜单的ID到父ID集合
|
||||
result.parentIds.push(menu.id);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = findFirstChildWithHref(this.menus)
|
||||
if (result) {
|
||||
this.activeMenuIndex = result.menu.id
|
||||
this.openedMenus = result.parentIds
|
||||
this.$nextTick(() => {
|
||||
this.menuSelect(this.activeMenuIndex, this.openedMenus)
|
||||
// 使用pjax跳转
|
||||
commonUtil.pjaxPush(result.menu.href)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
init() {
|
||||
// 页面加载时获取到正确的菜单
|
||||
const pathname = window.location.pathname
|
||||
if (!pathname.startsWith("/platform/v4/subApp")) {
|
||||
this.$axios.post("/platform/sys/user/rootMenuByPath", {pathname}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.setAppInfo(res.data)
|
||||
this.getMenus(res.data?.id)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const appId = GetQueryString("appId")
|
||||
if (!appId) return
|
||||
this.getMenus(appId)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
// 页面加载时获取到正确的菜单
|
||||
const pathname = window.location.pathname
|
||||
if (!pathname.startsWith("/platform/v4/subApp")) {
|
||||
$.get("/platform/sys/user/rootMenuByPath", { pathname }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.setAppInfo(res.data)
|
||||
this.getMenus(res.data?.id)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const appId = GetQueryString("appId")
|
||||
if (!appId) {
|
||||
return
|
||||
}
|
||||
this.getMenus(appId)
|
||||
}
|
||||
this.init()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -165,12 +165,12 @@
|
||||
} else {
|
||||
window.location.href = "/platform/h5/home"
|
||||
}
|
||||
func && func()
|
||||
func && typeof func === "function" && func()
|
||||
}
|
||||
|
||||
Vue.mixin({
|
||||
methods: {
|
||||
historyBack: function(func) {
|
||||
historyBack: function(func = ()=>{}) {
|
||||
historyBack(func);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
<script src="${base!}/assets/platform/plugins/jquery/jquery.js"></script>
|
||||
<!-- pjax是异步加载html片段的工具,模拟前端路由机制 -->
|
||||
<script src="${base!}/assets/platform/plugins/pjax/jquery.pjax.js"></script>
|
||||
<!--nprogress 配合pjax使用-->
|
||||
<link ref="stylesheet" href="${base!}/assets/platform/plugins/nprogress/nprogress.css"/>
|
||||
<!-- nprogress 配合pjax使用 -->
|
||||
<link rel="stylesheet" href="${base!}/assets/platform/plugins/nprogress/nprogress.css" />
|
||||
<script src="${base!}/assets/platform/plugins/nprogress/nprogress.js"></script>
|
||||
|
||||
<!-- 农历插件 -->
|
||||
@@ -97,6 +97,8 @@
|
||||
ELEMENT.Dialog.props.closeOnClickModal.default = false
|
||||
ELEMENT.Dialog.props.top.default = "50px"
|
||||
ELEMENT.Table.props.border.default = true
|
||||
ELEMENT.TableColumn.props.headerAlign.default = 'center'
|
||||
ELEMENT.TableColumn.props.align.default = 'center'
|
||||
ELEMENT.TableColumn.props.showOverflowTooltip = { type: Boolean, default: true }
|
||||
Vue.use(ELEMENT, {
|
||||
zIndex: 20000
|
||||
@@ -603,7 +605,7 @@
|
||||
</div>
|
||||
|
||||
<nav class="v4-nav">
|
||||
<a href="/platform/v4/home2" data-pjax class="v4-nav-item">
|
||||
<a href="/platform/v4/home" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-home"></i>
|
||||
首页
|
||||
</a>
|
||||
@@ -615,10 +617,14 @@
|
||||
<i class="fa fa-th-large"></i>
|
||||
服务中心
|
||||
</a>
|
||||
<a href="/flow/todoCenter" data-pjax class="v4-nav-item">
|
||||
<a href="/platform/v4/todo" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-th-large"></i>
|
||||
待办中心
|
||||
</a>
|
||||
<a href="/platform/v4/msg" data-pjax class="v4-nav-item">
|
||||
<i class="fa fa-th-large"></i>
|
||||
消息中心
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -663,7 +669,7 @@
|
||||
var currentPath = window.location.pathname
|
||||
var footer = $("#v4-footer")
|
||||
|
||||
if (currentPath === "/platform/v4/home2") {
|
||||
if (currentPath === "/platform/v4/home") {
|
||||
footer.show()
|
||||
} else {
|
||||
footer.hide()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,936 +0,0 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
/* 主容器 */
|
||||
.oa-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.oa-main-content {
|
||||
margin: 0 auto;
|
||||
padding-bottom: 20px;
|
||||
background: url(/assets/platform/img/v4/home-bg.png);
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
/* 背景图和搜索统计区域容器 */
|
||||
.oa-hero-section {
|
||||
position: relative;
|
||||
height: 350px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.oa-background-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.oa-background-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 搜索和统计区域 */
|
||||
.oa-search-stats-section {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 500px;
|
||||
gap: 40px;
|
||||
align-items: center;
|
||||
width: 1500px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 搜索区域 */
|
||||
.oa-search-section {
|
||||
text-align: left;
|
||||
border-radius: 20px;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.oa-search-container {
|
||||
display: flex;
|
||||
max-width: 600px;
|
||||
background: white;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.oa-search-input {
|
||||
flex: 1;
|
||||
padding: 15px 25px;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 16px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.oa-search-btn {
|
||||
background: #ff6b35;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 15px 30px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.oa-search-btn:hover {
|
||||
background: #e55a2b;
|
||||
}
|
||||
|
||||
/* 数字提醒区域 */
|
||||
.oa-stats-section {
|
||||
background: rgba(0, 109, 185, 0.7);
|
||||
padding: 20px 25px;
|
||||
}
|
||||
|
||||
.oa-stats-title {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 15px 0;
|
||||
text-align: left;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.oa-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.oa-stat-card {
|
||||
background: #ffffff;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.oa-stat-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #4a90e2, #357abd);
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.oa-stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oa-stat-card:hover::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.oa-stat-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.oa-stat-icon {
|
||||
font-size: 28px;
|
||||
color: #4a90e2;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.oa-stat-card:hover .oa-stat-icon {
|
||||
transform: scale(1.1);
|
||||
color: #357abd;
|
||||
}
|
||||
|
||||
.oa-stat-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.oa-stat-number {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #2c3e50;
|
||||
line-height: 1;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.oa-stat-card:hover .oa-stat-number {
|
||||
color: #4a90e2;
|
||||
}
|
||||
|
||||
.oa-stat-label {
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
text-align: left;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* 活动中心 */
|
||||
.oa-box-1-section {
|
||||
display: grid;
|
||||
grid-template-columns: 400px 1fr;
|
||||
gap: 30px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
width: 1500px;
|
||||
margin: 20px auto 0;
|
||||
}
|
||||
|
||||
/* 左侧用户信息 */
|
||||
.oa-user-panel {
|
||||
background: rgba(0, 109, 185, 0.9);
|
||||
color: white;
|
||||
padding: 25px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
max-height: 410px;
|
||||
}
|
||||
|
||||
.oa-user-panel::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.oa-user-panel > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.oa-user-avatar {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36px;
|
||||
margin-bottom: 15px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.oa-user-avatar:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.oa-user-name {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.oa-user-info {
|
||||
line-height: 1.6;
|
||||
margin-bottom: 20px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.oa-user-info .info-item {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1.6;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.oa-user-panel .info-label {
|
||||
font-weight: 600;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
min-width: 50px;
|
||||
max-width: 60px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.oa-user-panel .info-value {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.oa-user-homepage-btn {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
padding: 10px 20px;
|
||||
border-radius: 25px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.oa-user-homepage-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* 右侧活动列表 */
|
||||
.oa-activity-content {
|
||||
padding: 20px;
|
||||
background: #ffffff;
|
||||
max-height: 410px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0; /* 允许flex子项收缩 */
|
||||
overflow: hidden; /* 防止内容溢出 */
|
||||
}
|
||||
|
||||
.oa-activity-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 15px;
|
||||
flex-shrink: 0; /* 防止头部被压缩 */
|
||||
}
|
||||
|
||||
.oa-activity-tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.oa-activity-tab {
|
||||
padding: 10px 0;
|
||||
font-size: 20px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease;
|
||||
position: relative;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.oa-activity-tab:hover {
|
||||
color: #4a90e2;
|
||||
}
|
||||
|
||||
.oa-activity-tab.active {
|
||||
color: #4a90e2;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.oa-activity-tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: #4a90e2;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
|
||||
/* 活动轮播样式 */
|
||||
.oa-activity-carousel {
|
||||
flex: 1;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.oa-activity-carousel .el-carousel,.oa-activity-carousel .el-carousel .el-carousel__container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.oa-activity-carousel .el-carousel__item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.oa-activity-card {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oa-activity-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.oa-activity-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.oa-activity-card:hover .oa-activity-image img {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
/* 图片上的文字叠加层 */
|
||||
.oa-activity-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
|
||||
padding: 40px 20px 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.oa-activity-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.oa-activity-meta {
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.5;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* 无活动时的提示区域 */
|
||||
.oa-no-activity {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
background: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
color: #6c757d;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.oa-no-activity-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
color: #dee2e6;
|
||||
}
|
||||
|
||||
.oa-no-activity-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.oa-no-activity-desc {
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* box2 - 系统通知公告 */
|
||||
.oa-box-2-section {
|
||||
width: 1500px;
|
||||
margin: 30px auto 0;
|
||||
background: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.oa-notice-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.oa-notice-header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.oa-notice-header-content::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.oa-notice-header-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.oa-notice-header-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.oa-notice-header-more {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.oa-header-more-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.oa-header-more-btn:hover {
|
||||
background: #f5f5f5;
|
||||
color: #4a90e2;
|
||||
}
|
||||
|
||||
.oa-header-more-btn i {
|
||||
font-size: 12px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.oa-header-more-btn:hover i {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
|
||||
.oa-notice-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.oa-notice-list {
|
||||
display: grid;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.oa-notice-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 15px 20px;
|
||||
background: #ffffff;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.oa-notice-item:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.oa-notice-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.oa-notice-item-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: #666;
|
||||
margin-right: 12px;
|
||||
margin-top: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.oa-notice-item-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.oa-notice-item-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.oa-notice-item-desc {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.oa-notice-item-date {
|
||||
margin-left: 15px;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="oa-container" id="v4-home-app">
|
||||
<!-- 顶部导航 -->
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div class="oa-main-content">
|
||||
<!-- 背景图和搜索统计区域容器 -->
|
||||
<div class="oa-hero-section">
|
||||
<!-- 背景图 -->
|
||||
<div class="oa-background-image">
|
||||
<img
|
||||
src="https://i.cpu.edu.cn/mnews/_upload/article/images/ed/dc/eff0e4b84ee6b2d7e770ed1de071/0f515087-45e3-4dbf-acf4-e7096d6cbdc0.jpg"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 搜索和统计区域 -->
|
||||
<div class="oa-search-stats-section">
|
||||
<!-- 搜索区域 -->
|
||||
<div class="oa-search-section">
|
||||
<!-- <h1 class="oa-search-title">精业济群</h1>-->
|
||||
<div class="oa-search-container">
|
||||
<input type="text" class="oa-search-input" placeholder="请输入您要查询的关键字"
|
||||
v-model="searchQuery" />
|
||||
<button class="oa-search-btn" @click="performSearch">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数字提醒 -->
|
||||
<div class="oa-stats-section">
|
||||
<h3 class="oa-stats-title">提醒</h3>
|
||||
<div class="oa-stats-grid">
|
||||
<div class="oa-stat-card" @click="handleStatClick('todo')">
|
||||
<div class="oa-stat-top">
|
||||
<div class="oa-stat-icon">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</div>
|
||||
<div class="oa-stat-number">{{ stats.todo }}</div>
|
||||
</div>
|
||||
<div class="oa-stat-label">待办</div>
|
||||
</div>
|
||||
<div class="oa-stat-card" @click="handleStatClick('done')">
|
||||
<div class="oa-stat-top">
|
||||
<div class="oa-stat-icon">
|
||||
<i class="fa fa-check-circle"></i>
|
||||
</div>
|
||||
<div class="oa-stat-number">{{ stats.done }}</div>
|
||||
</div>
|
||||
<div class="oa-stat-label">已办</div>
|
||||
</div>
|
||||
<div class="oa-stat-card" @click="handleStatClick('notification')">
|
||||
<div class="oa-stat-top">
|
||||
<div class="oa-stat-icon">
|
||||
<i class="fa fa-bell"></i>
|
||||
</div>
|
||||
<div class="oa-stat-number">{{ stats.notifications }}</div>
|
||||
</div>
|
||||
<div class="oa-stat-label">消息</div>
|
||||
</div>
|
||||
<div class="oa-stat-card" @click="handleStatClick('started')">
|
||||
<div class="oa-stat-top">
|
||||
<div class="oa-stat-icon">
|
||||
<i class="fa fa-file-text"></i>
|
||||
</div>
|
||||
<div class="oa-stat-number">{{ stats.started }}</div>
|
||||
</div>
|
||||
<div class="oa-stat-label">申请</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- box1 -->
|
||||
<div class="oa-box-1-section">
|
||||
<!-- 左侧用户信息 -->
|
||||
<div class="oa-user-panel">
|
||||
<div class="oa-user-avatar">
|
||||
<i class="fa fa-user"></i>
|
||||
</div>
|
||||
<div class="oa-user-name">${@auth.getPrincipalProperty('username')}</div>
|
||||
<div class="oa-user-info">
|
||||
<div class="info-item">
|
||||
<span class="info-label">工号:</span>
|
||||
<span class="info-value">${@auth.getPrincipalProperty('loginname')}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">性别:</span>
|
||||
<span class="info-value">${@auth.getPrincipalProperty('sex')}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">单位:</span>
|
||||
<span class="info-value">
|
||||
<!--#if(!isEmpty(@auth.getPrincipalProperty('unit'))){#-->
|
||||
${@auth.getPrincipalProperty('unit').getName()}
|
||||
<!--#}#-->
|
||||
</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">工会:</span>
|
||||
<span class="info-value">
|
||||
<!--#if(!isEmpty(@auth.getPrincipalProperty('union'))){#-->
|
||||
${@auth.getPrincipalProperty('union').getName()}
|
||||
<!--#}#-->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/platform/v4/personCenter" class="oa-user-homepage-btn" target="_blank">
|
||||
<i class="fa fa-home"></i>
|
||||
个人主页
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 右侧活动列表 -->
|
||||
<div class="oa-activity-content">
|
||||
<div class="oa-activity-header">
|
||||
<div class="oa-activity-tabs">
|
||||
<div class="oa-activity-tab" :class="{active: activeTab === 'recent'}"
|
||||
@click="setActiveTab('recent')">最新活动
|
||||
</div>
|
||||
<div class="oa-activity-tab" :class="{active: activeTab === 'history'}"
|
||||
@click="setActiveTab('history')">历史活动
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oa-activity-carousel">
|
||||
<div v-if="currentActivityList.length === 0" class="oa-no-activity">
|
||||
<div class="oa-no-activity-icon">
|
||||
<i class="fa fa-calendar-o"></i>
|
||||
</div>
|
||||
<div class="oa-no-activity-title">暂无活动信息</div>
|
||||
<div class="oa-no-activity-desc">{{ activeTab === 'recent' ? '当前没有最新活动' : '当前没有历史活动'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<el-carousel v-else :autoplay="true" arrow="always" indicator-position="none">
|
||||
<el-carousel-item v-for="activity in currentActivityList" :key="activity.id">
|
||||
<div class="oa-activity-card">
|
||||
<div class="oa-activity-image">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?w=800&auto=format&fit=crop"
|
||||
:alt="activity.name" />
|
||||
<div class="oa-activity-overlay">
|
||||
<div class="oa-activity-title">{{ activity.name }}</div>
|
||||
<div class="oa-activity-meta">{{ activity.startDate }} ~ {{ activity.endDate
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- box2 - 系统通知公告 -->
|
||||
<div class="oa-box-2-section">
|
||||
<div class="oa-notice-header">
|
||||
<div class="oa-notice-header-content">
|
||||
<div class="oa-notice-header-icon">
|
||||
<i class="fa fa-bullhorn"></i>
|
||||
</div>
|
||||
<h2 class="oa-notice-header-title">系统通知公告</h2>
|
||||
</div>
|
||||
<div class="oa-notice-header-more">
|
||||
<button class="oa-header-more-btn">
|
||||
更多
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oa-notice-content">
|
||||
<div class="oa-notice-list">
|
||||
<div v-for="notice in noticeList" :key="notice.id" class="oa-notice-item" @click="viewNotice(notice)">
|
||||
<div class="oa-notice-item-icon">
|
||||
<i class="fa fa-bullhorn"></i>
|
||||
</div>
|
||||
<div class="oa-notice-item-content">
|
||||
<div class="oa-notice-item-title">{{ notice.title }}</div>
|
||||
<div class="oa-notice-item-desc">{{ notice.content }}</div>
|
||||
</div>
|
||||
<div class="oa-notice-item-date">{{ notice.publishDate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
const vue = new Vue({
|
||||
el: "#v4-home-app",
|
||||
data() {
|
||||
return {
|
||||
searchQuery: "",
|
||||
activeTab: "recent",
|
||||
noticeTab: "system",
|
||||
currentIndex: 0,
|
||||
stats: {
|
||||
todo: 3,
|
||||
done: 0,
|
||||
notifications: 7,
|
||||
started: 9
|
||||
},
|
||||
activityList: [],
|
||||
noticeList: [
|
||||
{
|
||||
id: 1,
|
||||
type: 'system',
|
||||
title: '系统维护通知',
|
||||
content: '系统将于本周六晚上22:00-24:00进行例行维护,期间可能影响正常使用,请提前做好相关准备。',
|
||||
publishDate: '2024-01-15',
|
||||
isRead: false
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'announcement',
|
||||
title: '新功能上线公告',
|
||||
content: '移动端应用已正式上线,支持手机端办公审批,欢迎大家下载使用。',
|
||||
publishDate: '2024-01-14',
|
||||
isRead: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'urgent',
|
||||
title: '紧急安全提醒',
|
||||
content: '近期发现钓鱼邮件攻击,请勿点击可疑链接,如有疑问请联系IT部门。',
|
||||
publishDate: '2024-01-13',
|
||||
isRead: false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
recentActivities() {
|
||||
const now = new Date()
|
||||
return this.activityList
|
||||
.filter(activity => new Date(activity.endDate) >= now)
|
||||
.sort((a, b) => new Date(b.startDate) - new Date(a.startDate))
|
||||
},
|
||||
historyActivities() {
|
||||
const now = new Date()
|
||||
return this.activityList
|
||||
.filter(activity => new Date(activity.endDate) < now)
|
||||
.sort((a, b) => new Date(b.startDate) - new Date(a.startDate))
|
||||
},
|
||||
currentActivityList() {
|
||||
return this.activeTab === "recent" ? this.recentActivities : this.historyActivities
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 初始化数据
|
||||
this.getActivity()
|
||||
this.getStatistics()
|
||||
},
|
||||
methods: {
|
||||
performSearch() {
|
||||
if (this.searchQuery.trim()) {
|
||||
console.log("搜索:", this.searchQuery)
|
||||
// 实现搜索逻辑
|
||||
}
|
||||
},
|
||||
setActiveTab(tab) {
|
||||
this.activeTab = tab
|
||||
this.currentIndex = 0 // 切换标签时重置索引
|
||||
},
|
||||
getActivity() {
|
||||
$.get("/platform/home/listHomeActivity").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.activityList = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
viewNotice(notice) {
|
||||
console.log('查看通知:', notice.title)
|
||||
// 标记为已读
|
||||
notice.isRead = true
|
||||
// 这里可以添加查看详情的逻辑
|
||||
},
|
||||
|
||||
handleStatClick(val){
|
||||
if(['todo', 'done', 'application']){
|
||||
window.open('/flow/todoCenter?status=' + val)
|
||||
}
|
||||
},
|
||||
async getStatistics() {
|
||||
try {
|
||||
const res = await $.post("/flow/todoCenter/statistics")
|
||||
if (res.code === 0) {
|
||||
const { todoCount, doneCount, startedCount } = res.data
|
||||
this.stats.todo = todoCount
|
||||
this.stats.done = doneCount
|
||||
this.stats.started = startedCount
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取统计数据失败")
|
||||
console.error("获取统计数据失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#}#-->
|
||||
|
||||
@@ -0,0 +1,444 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.message-center {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.statistics-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border: none;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.stat-card .el-card__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.all-icon {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.unread-icon {
|
||||
background-color: rgba(245, 108, 108, 0.1);
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.read-icon {
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 搜索区域样式 */
|
||||
.filter-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-section .el-card__body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form .el-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 消息列表区域样式 */
|
||||
.message-section .el-card__header {
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 优化后的 tabs 样式 */
|
||||
.message-header .el-tabs__header {
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.message-header .el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
.message-header .el-tabs__item {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 15px;
|
||||
color: #606266;
|
||||
padding: 0 20px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.message-header .el-tabs__item.is-active {
|
||||
color: var(--color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.message-header .el-tabs__active-bar {
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.message-title {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<div class="message-center">
|
||||
<!-- 统计数据区域 -->
|
||||
<el-row :gutter="20" class="statistics-section">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon all-icon">
|
||||
<i class="el-icon-message"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{stats.total}}</div>
|
||||
<div class="stat-label">全部消息</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon unread-icon">
|
||||
<i class="el-icon-bell"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{stats.unread}}</div>
|
||||
<div class="stat-label">未读消息</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="never" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon read-icon">
|
||||
<i class="el-icon-finished"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{stats.read}}</div>
|
||||
<div class="stat-label">已读消息</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<el-card shadow="never" class="filter-section">
|
||||
<el-form :inline="true" :model="filters" class="search-form">
|
||||
<el-form-item>
|
||||
<el-select v-model="filters.type" placeholder="消息类型" clearable @change="loadMessages"
|
||||
style="width: 150px;">
|
||||
<el-option label="全部类型" value=""></el-option>
|
||||
<el-option label="系统公告" value="1"></el-option>
|
||||
<el-option label="消息通知" value="2"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="pageForm.isRead == 0">
|
||||
<el-button size="medium" type="primary" @click="markAllAsRead" :loading="loading">
|
||||
<i class="el-icon-check"></i> 全部标记已读
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 消息列表区域 -->
|
||||
<el-card shadow="never" class="message-section">
|
||||
<div slot="header" class="message-header">
|
||||
<el-tabs v-model="pageForm.isRead" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="全部消息" name="-1">
|
||||
<span slot="label">
|
||||
<i class="el-icon-message"></i>
|
||||
全部消息
|
||||
</span>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="未读消息" name="0">
|
||||
<span slot="label">
|
||||
<i class="el-icon-bell"></i>
|
||||
未读消息
|
||||
</span>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="已读消息" name="1">
|
||||
<span slot="label">
|
||||
<i class="el-icon-finished"></i>
|
||||
已读消息
|
||||
</span>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 表格展示 -->
|
||||
<el-table v-loading="loading" :data="messages" style="width: 100%" :key="currentTab"
|
||||
empty-text="暂无消息"
|
||||
:header-cell-style="{backgroundColor: '#f5f7fa'}">
|
||||
<el-table-column prop="type" label="消息类型" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="mini" :type="scope.row.type === 1 ? 'danger' : 'primary'">
|
||||
{{getMessageTypeName(scope.row.type)}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span class="message-title">{{scope.row.title}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="content" label="内容预览" min-width="250" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
{{getContentPreview(scope.row.content)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="接收时间" width="180">
|
||||
<template slot-scope="scope">
|
||||
{{formatTime(scope.row.createdAt)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="isRead" label="状态" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="mini" :type="scope.row.isRead ? 'success' : 'warning'">
|
||||
{{scope.row.isRead ? '已读' : '未读'}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="primary" size="mini" @click="viewMessage(scope.row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container" v-if="messages.length > 0">
|
||||
<el-pagination
|
||||
background
|
||||
@current-change="handlePageChange"
|
||||
:current-page="pageForm.pageNumber"
|
||||
:page-size="pageForm.pageSize"
|
||||
layout="prev, pager, next, jumper"
|
||||
:total="pageForm.totalCount"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 消息详情弹窗 -->
|
||||
<el-dialog
|
||||
:visible.sync="detailVisible"
|
||||
:title="currentMessage?.globalMessage?.title"
|
||||
width="60%"
|
||||
class="message-detail-dialog"
|
||||
>
|
||||
<div v-if="currentMessage.id">
|
||||
<div style="text-align: center; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid #eee;">
|
||||
<div style="font-size: 18px; font-weight: 500; margin-bottom: 8px;">{{currentMessage.title}}</div>
|
||||
<div style="font-size: 14px; color: #666;">
|
||||
<span>{{getMessageTypeName(currentMessage?.globalMessage?.type)}}</span>
|
||||
<span style="margin: 0 8px;">•</span>
|
||||
<span>{{formatTime(currentMessage.createdAt)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="line-height: 1.8; font-size: 15px;" v-html="currentMessage?.globalMessage?.content"></div>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="detailVisible = false">关闭</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#app',
|
||||
data() {
|
||||
return {
|
||||
currentTab: 'all',
|
||||
loading: false,
|
||||
messages: [],
|
||||
stats: {
|
||||
total: 0,
|
||||
unread: 0,
|
||||
read: 0
|
||||
},
|
||||
filters: {
|
||||
type: ''
|
||||
},
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0
|
||||
},
|
||||
detailVisible: false,
|
||||
currentMessage: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadStats() {
|
||||
try {
|
||||
const resp = await this.$axios.get('/platform/v4/msg/stats')
|
||||
if (resp.code === 0) {
|
||||
this.stats = resp.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载统计数据失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
async loadMessages() {
|
||||
this.loading = true
|
||||
try {
|
||||
const resp = await this.$axios.post('/platform/v4/msg/pageData', this.pageForm)
|
||||
if (resp.code === 0) {
|
||||
this.messages = resp.data.list
|
||||
this.pageForm.totalCount = resp.data.totalCount
|
||||
} else {
|
||||
this.$message.error(resp.msg)
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('加载消息失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
async viewMessage(message) {
|
||||
this.$axios.post(`/platform/v4/msg/detail/` + message.id).then(async res => {
|
||||
if (res.code === 0) {
|
||||
this.currentMessage = res.data
|
||||
this.detailVisible = true
|
||||
// 如果是未读消息,标记为已读
|
||||
if (!message.isRead) {
|
||||
await this.markAsRead(message.id)
|
||||
this.loadStats()
|
||||
this.loadMessages()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
async markAsRead(messageId) {
|
||||
try {
|
||||
await this.$axios.post(`/platform/v4/msg/read/` + messageId)
|
||||
} catch (error) {
|
||||
console.error('标记已读失败:', error)
|
||||
}
|
||||
},
|
||||
|
||||
async markAllAsRead() {
|
||||
this.$confirm('确定要将所有未读消息标记为已读吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
this.loading = true
|
||||
try {
|
||||
const resp = await this.$axios.post('/platform/v4/msg/read/all')
|
||||
if (resp.code === 0) {
|
||||
this.$message.success('操作成功')
|
||||
this.loadMessages()
|
||||
this.loadStats()
|
||||
} else {
|
||||
this.$message.error(resp.msg)
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error('操作失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleTabClick(tab) {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.loadMessages()
|
||||
},
|
||||
|
||||
handlePageChange(pageNumber) {
|
||||
this.pageForm.pageNumber = pageNumber
|
||||
this.loadMessages()
|
||||
},
|
||||
|
||||
getMessageTypeName(type) {
|
||||
return type === 1 ? '系统公告' : '消息通知'
|
||||
},
|
||||
|
||||
getContentPreview(content) {
|
||||
if (!content) return ''
|
||||
// 移除HTML标签
|
||||
const text = content.replace(/<[^>]*>/g, '')
|
||||
return text.length > 50 ? text.substring(0, 50) + '...' : text
|
||||
},
|
||||
|
||||
formatTime(timestamp) {
|
||||
return this.$moment(timestamp).format('YYYY-MM-DD HH:mm')
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.loadStats()
|
||||
this.loadMessages()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,448 @@
|
||||
<!--#
|
||||
layout("/layouts/v4/baseLayout.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.task-todo-center {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 统计卡片样式 */
|
||||
.statistics-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.stat-card .el-card__body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.todo-icon {
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.done-icon {
|
||||
background-color: rgba(103, 194, 58, 0.1);
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.started-icon {
|
||||
background-color: rgba(230, 162, 60, 0.1);
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.stat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 搜索区域样式 */
|
||||
.filter-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-section .el-card__body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-form .el-form-item {
|
||||
margin-bottom: 0;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
/* 任务列表区域样式 */
|
||||
.task-section .el-card__header {
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 优化后的 tabs 样式 */
|
||||
.task-header .el-tabs__header {
|
||||
margin: 0;
|
||||
padding: 0 20px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__nav-wrap::after {
|
||||
height: 1px;
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 15px;
|
||||
color: #606266;
|
||||
padding: 0 20px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__item.is-active {
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-header .el-tabs__active-bar {
|
||||
height: 3px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.task-title {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
/* 表格行样式 */
|
||||
.el-table .hover-row {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<div class="task-todo-center">
|
||||
<!-- 统计数据区域 -->
|
||||
<el-row :gutter="20" class="statistics-section">
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon todo-icon">
|
||||
<i class="el-icon-s-claim"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{todoCount}}</div>
|
||||
<div class="stat-label">待办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon done-icon">
|
||||
<i class="el-icon-finished"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{doneCount}}</div>
|
||||
<div class="stat-label">已办任务</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-icon started-icon">
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{startedCount}}</div>
|
||||
<div class="stat-label">我发起的</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 搜索筛选区域 -->
|
||||
<el-card shadow="never" class="filter-section">
|
||||
<el-form :inline="true" :model="pageForm" class="search-form">
|
||||
<el-form-item>
|
||||
<el-input v-model="pageForm.searchKeyword" placeholder="请输入关键词搜索"
|
||||
prefix-icon="el-icon-search"
|
||||
clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="pageForm.category" placeholder="所有流程分类" clearable>
|
||||
<el-option v-for="type in categoryOptions" :key="type.id" :label="type.name"
|
||||
:value="type.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search">搜索</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 任务列表区域 -->
|
||||
<el-card shadow="never" class="task-section">
|
||||
<div slot="header" class="task-header">
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="待办任务" name="todo"></el-tab-pane>
|
||||
<el-tab-pane label="已办任务" name="done"></el-tab-pane>
|
||||
<el-tab-pane label="我发起的" name="started"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!-- 表格展示 -->
|
||||
<el-table v-loading="loading" :data="tasks" style="width: 100%" :key="activeTab"
|
||||
:header-cell-style="{backgroundColor: '#f5f7fa'}">
|
||||
<el-table-column prop="processInstanceName" label="流程名称" min-width="180" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span class="task-title">{{scope.row.variable?.instanceName}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskName" label="任务节点" min-width="150"
|
||||
show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="categoryName" label="流程分类" min-width="150" show-overflow-tooltip>
|
||||
<template slot-scope="{row}">{{categoryOptions.find(item => item.id === row.category)?.name}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="initiatorName" label="申请人" min-width="120">
|
||||
<template slot-scope="{row}">{{row.variable.initiatorName}}</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="时间" min-width="180">-->
|
||||
<!-- <template slot-scope="scope">-->
|
||||
<!-- <div v-if="activeTab === 'todo'">-->
|
||||
<!-- <i class="el-icon-time"></i>-->
|
||||
<!-- {{scope.row.createdAt}}-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-else-if="activeTab === 'done'">-->
|
||||
<!-- <i class="el-icon-check"></i>-->
|
||||
<!-- {{scope.row.finishTime}}-->
|
||||
<!-- </div>-->
|
||||
<!-- <div v-else-if="activeTab === 'started'">-->
|
||||
<!-- <i class="el-icon-s-promotion"></i>-->
|
||||
<!-- {{scope.row.createdAt}}-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<el-table-column v-if="activeTab === 'started'" label="状态" width="100">
|
||||
<template slot-scope="{row}">{{processStatusMap[row.state]?.text}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100px" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="primary" size="mini" @click="openView(scope.row)">查看</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 空状态展示 -->
|
||||
<el-empty v-if="tasks.length === 0" :description="getEmptyText()"></el-empty>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container" v-if="tasks.length > 0">
|
||||
<el-pagination
|
||||
background
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="pageForm.pageNumber"
|
||||
:page-size="pageForm.pageSize"
|
||||
layout="prev, pager, next, jumper"
|
||||
:total="pageForm.totalCount"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
// 统计数据
|
||||
todoCount: 0,
|
||||
doneCount: 0,
|
||||
startedCount: 0,
|
||||
|
||||
// 查询表单
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
todoCount: 0,
|
||||
keyword: "",
|
||||
category: ""
|
||||
},
|
||||
|
||||
|
||||
// 流程类型选项
|
||||
categoryOptions: [],
|
||||
|
||||
// 任务列表
|
||||
tasks: [],
|
||||
loading: false,
|
||||
|
||||
// 标签页
|
||||
activeTab: "todo",
|
||||
|
||||
// 当前任务
|
||||
currentTask: null,
|
||||
dialogVisible: false,
|
||||
|
||||
processStatusMap: {
|
||||
10: {text: "进行中", class: "doing"},
|
||||
20: {text: "已完成", class: "finished"},
|
||||
30: {text: "已撤回", class: "withdraw"},
|
||||
40: {text: "强行终止", class: "interrupt"},
|
||||
45: {text: "已拒绝", class: "reject"},
|
||||
50: {text: "挂起", class: "pending"},
|
||||
99: {text: "已废弃", class: "abandon"}
|
||||
},
|
||||
|
||||
// 任务状态枚举
|
||||
taskStateEnum: {
|
||||
DOING: 10,
|
||||
FINISHED: 20,
|
||||
WITHDRAW: 30,
|
||||
INTERRUPT: 40,
|
||||
PENDING: 50,
|
||||
ABANDON: 99
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.initData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
// 初始化数据
|
||||
async initData() {
|
||||
await Promise.all([this.getStatistics(), this.listCategory(), this.getTasks()])
|
||||
},
|
||||
|
||||
// 获取统计数据
|
||||
async getStatistics() {
|
||||
try {
|
||||
const res = await $.post("/flow/todoCenter/statistics")
|
||||
if (res.code === 0) {
|
||||
const {todoCount, doneCount, startedCount} = res.data
|
||||
this.todoCount = todoCount
|
||||
this.doneCount = doneCount
|
||||
this.startedCount = startedCount
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取统计数据失败")
|
||||
console.error("获取统计数据失败:", error)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取流程类型
|
||||
async listCategory() {
|
||||
this.$axios.post("/flow/category/list").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.categoryOptions = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取任务列表
|
||||
async getTasks() {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await $.post("/flow/todoCenter/" + this.activeTab, this.pageForm)
|
||||
if (res.code === 0) {
|
||||
this.tasks = res.data.list
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
this.dialogVisible = false
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.error("获取任务列表失败")
|
||||
console.error("获取任务列表失败:", error)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 处理标签页点击
|
||||
handleTabClick(tab) {
|
||||
this.activeTab = tab.name
|
||||
this.pageForm.pageNumber = 1
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 搜索
|
||||
search() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.getTasks()
|
||||
this.getStatistics()
|
||||
},
|
||||
|
||||
// 重置搜索
|
||||
reset() {
|
||||
this.pageForm.searchKeyword = ""
|
||||
this.pageForm.category = ""
|
||||
this.search()
|
||||
},
|
||||
|
||||
// 处理页码变化
|
||||
handleCurrentChange(val) {
|
||||
this.pageForm.pageNumber = val
|
||||
this.getTasks()
|
||||
},
|
||||
|
||||
// 处理任务
|
||||
async openView(task) {
|
||||
console.log(task)
|
||||
const {taskId, taskKey, taskState, instanceId, businessNo, formKey} = task
|
||||
if (!formKey) {
|
||||
this.$message.warning("当前流程没有配置地址")
|
||||
return
|
||||
}
|
||||
window.open(formKey + "?taskId=" + taskId + "bizId=" + businessNo + "taskKey=" + taskKey)
|
||||
},
|
||||
|
||||
// 获取空状态文本
|
||||
getEmptyText() {
|
||||
switch (this.activeTab) {
|
||||
case "todo":
|
||||
return "您当前没有需要办理的任务,辛苦了"
|
||||
case "done":
|
||||
return "您当前没有已办理的任务记录"
|
||||
case "started":
|
||||
return "您当前没有发起的流程"
|
||||
default:
|
||||
return "暂无数据"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -1,93 +0,0 @@
|
||||
const activity = {
|
||||
template: /*language=HTML*/ `
|
||||
<div class="home-activity">
|
||||
<el-card shadow="never">
|
||||
<div class="card-title" slot="header">最新活动</div>
|
||||
<div v-if="activityOptions && activityOptions.length > 0" v-for="item in activityOptions"
|
||||
:key="item.id">
|
||||
<div style="width: 100%">
|
||||
<div style="background-color: #fff; position: relative; margin-bottom: 10px">
|
||||
<div style="display: flex; justify-content: center; align-items: center;border-bottom: 1px solid var(--border-color-lighter);padding-bottom: 8px">
|
||||
<div
|
||||
style="position: relative;flex-shrink: 1;width: 150px; height: 90px;margin-right: 10px; ">
|
||||
<el-image :src="item.cover" alt="" fit="cover"
|
||||
style="width: 100%; height: 100%; border-radius: 8px;">
|
||||
<div slot="error" class="image-slot">
|
||||
<i class="el-icon-picture-outline"></i>
|
||||
</div>
|
||||
</el-image>
|
||||
<el-tag class="act-item-tag" type="danger" size="mini"
|
||||
v-if="$moment().unix() > $moment(item.endDate).unix()">
|
||||
已结束
|
||||
</el-tag>
|
||||
|
||||
<el-tag class="act-item-tag" type="success" size="mini"
|
||||
v-else-if="$moment().unix() < $moment(item.endDate).unix()">
|
||||
进行中
|
||||
</el-tag>
|
||||
</div>
|
||||
<div style="flex: 1;height: 90px;display: flex;flex-direction: column;justify-content: space-between;">
|
||||
<div style="color: #111">
|
||||
<i class="el-icon-star-on" style="color: red; font-size: 17px"></i>{{item.name}}
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #999">
|
||||
开始时间:{{$moment(item.startDate).format('MM-DD HH:mm')}}
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #999">
|
||||
结束时间:{{$moment(item.endDate).format('MM-DD HH:mm')}}
|
||||
</div>
|
||||
</div>
|
||||
<div @click="enterActivity(item)">
|
||||
<el-button size="mini" type="primary">查看<i class="el-icon-s-promotion"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-if="activityOptions.length === 0" description="暂无活动"></el-empty>
|
||||
</el-card>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
activityOptions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
listActivity() {
|
||||
this.$axios.post("/platform/home/listHomeActivity").then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.activityOptions = resp.data
|
||||
}
|
||||
})
|
||||
},
|
||||
enterActivity(item) {
|
||||
this.$store.dispatch("pjaxRoute", item.url)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listActivity()
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
.home-activity {
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
.home-activity .el-card {
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.home-activity .el-empty {
|
||||
padding: 40px 0 !important;
|
||||
}
|
||||
|
||||
.home-activity .act-item-tag{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
`
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
const appModule = {
|
||||
template: /*language=HTML*/ `
|
||||
<div class="home-app-module">
|
||||
<el-card shadow="never">
|
||||
<div class="card-title" slot="header">平台入口</div>
|
||||
<div style="display: flex;gap: 20px">
|
||||
<template v-for="item in entranceModules">
|
||||
<div :key="item.id" class="v-item-handle" @click="enterEntranceModules(item)" >
|
||||
<el-image :src="item.icon" style="height: 50px;width: 50px;border-radius: 50%"></el-image>
|
||||
<span class="v-item-handle-label">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
`,
|
||||
store,
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
entranceModules() {
|
||||
return this.$store.getters.pcModules
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
enterEntranceModules(item) {
|
||||
this.$store.commit("setActiveMenuModuleId", item.id)
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
style: /*language=CSS*/ `
|
||||
.v-item-handle {
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
border: 1px solid #f3efef;
|
||||
/*/ / background-color: rgb(248, 248, 248);*/
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
transition: all 500ms;
|
||||
color: #0779e4;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.v-item-handle-label {
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.v-item-handle .el-image {
|
||||
transition: width 0.3s ease,
|
||||
height 0.3s ease;
|
||||
transform-origin: center; /* 缩放的中心点 */
|
||||
}
|
||||
|
||||
.v-item-handle:hover .el-image {
|
||||
width: 60px !important;
|
||||
height: 60px !important;
|
||||
}
|
||||
|
||||
.v-item-handle:hover .fa {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.v-item-handle span {
|
||||
transition: all 500ms;
|
||||
color: #000;
|
||||
font-size: 16px;
|
||||
}
|
||||
`
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<style>
|
||||
.el-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
.image-slot {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f5f7fa;
|
||||
color: #909399;
|
||||
}
|
||||
.el-icon-picture-outline {
|
||||
font-size: 30px;
|
||||
}
|
||||
.el-card {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.layout-grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(100px, 1fr));
|
||||
grid-template-rows: 200px 200px auto;
|
||||
gap: 10px;
|
||||
width: calc(100% - 0px);
|
||||
height: calc(100vh - 126px);
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.layout-grid-container .grid-item {
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.appModule-grid {
|
||||
grid-column: 1 / 3; /* 元素1占据第1和第2列 */
|
||||
grid-row: 1 / 2; /* 元素1占据第1行 */
|
||||
}
|
||||
|
||||
.activity-grid {
|
||||
grid-column: 3 / 4; /* 元素2占据第3列 */
|
||||
grid-row: 1 / 3; /* 元素2跨越第1和第2行 */
|
||||
}
|
||||
|
||||
.quickEntry-grid {
|
||||
grid-column: 1 / 3; /* 元素3占据第1和第2列 */
|
||||
grid-row: 2 / 3; /* 元素3占据第2行 */
|
||||
}
|
||||
|
||||
.news-grid,
|
||||
.todo-grid,
|
||||
.user-grid {
|
||||
grid-column: span 1; /* 每个元素占据1列 */
|
||||
grid-row: 3 / 4; /* 所有元素都在第3行 */
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak style="height: 100%">
|
||||
<div class="layout-grid-container">
|
||||
<app-module class="appModule-grid grid-item"></app-module>
|
||||
<quick-entry class="quickEntry-grid grid-item"></quick-entry>
|
||||
<activity class="activity-grid grid-item"></activity>
|
||||
<news class="news-grid grid-item"></news>
|
||||
<todo class="todo-grid grid-item"></todo>
|
||||
<user-info class="user-grid grid-item"></user-info>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
<!--#include("userInfo.js"){}#-->
|
||||
<!--#include("todo.js"){}#-->
|
||||
<!--#include("quickEntry.js"){}#-->
|
||||
<!--#include("activity.js"){}#-->
|
||||
<!--#include("appModule.js"){}#-->
|
||||
<!--#include("news.js"){}#-->
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
components: {
|
||||
"icon-selector": httpVueLoader("/components/plugins/sysIconSelector/index.vue?v=" + new Date().getTime()),
|
||||
"user-info": userInfo,
|
||||
todo,
|
||||
"quick-entry": quickEntry,
|
||||
activity,
|
||||
"app-module": appModule,
|
||||
news: news
|
||||
},
|
||||
store,
|
||||
data: function () {
|
||||
return {
|
||||
quickEntries: [],
|
||||
activityOptions: [],
|
||||
processInstanceOptions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
listQuickEntry() {
|
||||
this.$axios.post("/platform/home/listQuickEntry").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.quickEntries = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
listActivity() {
|
||||
this.$axios.post("/platform/h5/listHomeActivity").then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.activityOptions = resp.data
|
||||
}
|
||||
})
|
||||
},
|
||||
enterActivity(item) {
|
||||
this.$store.dispatch("pjaxRoute", item.url)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listQuickEntry()
|
||||
this.listActivity()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--# } #-->
|
||||
@@ -1,81 +0,0 @@
|
||||
const news = {
|
||||
template: /*language=HTML*/ `
|
||||
<div class="home-news">
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane v-for="(item,index) in newsData" :key="index" :label="item.label" :name="item.label" v-if="item.value.length > 0">
|
||||
<div v-for="(n,i) in item.value" :key="i" @click="openUrl(n.href)">
|
||||
<span v-if="n.time" class="news_time">{{ n.time }}</span>
|
||||
<span class="news_text">{{ n.text }}</span>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
newsData: [],
|
||||
activeName: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getNews() {
|
||||
const resp = await this.$axios.post("/platform/home/getNews")
|
||||
if (resp.data.length > 0) {
|
||||
this.activeName = resp.data[0].label
|
||||
}
|
||||
this.newsData = resp.data
|
||||
},
|
||||
openUrl(href) {
|
||||
const url = this.isHttpOrHttps(href) ? href : "https://gonghui.njupt.edu.cn/" + href
|
||||
window.open(url)
|
||||
},
|
||||
isHttpOrHttps(url) {
|
||||
return /^(http:\/\/|https:\/\/)/i.test(url)
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.getNews()
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
.home-news {
|
||||
background: #ffffff;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.home-news .el-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.home-news .el-tabs__content {
|
||||
height: calc(100% - 55px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.home-news .el-tab-pane {
|
||||
line-height: 38px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.home-news .el-tab-pane .news_time {
|
||||
color: white;
|
||||
padding: 4px 10px;
|
||||
border-radius: 10px;
|
||||
background: #1867b0;
|
||||
}
|
||||
|
||||
.home-news .el-tab-pane div {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.home-news .el-tab-pane .news_text:hover {
|
||||
color: #1867b0;
|
||||
}
|
||||
`
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
const quickEntry = {
|
||||
template: /*language=HTML*/ `
|
||||
<div class="home-quick-entry">
|
||||
<el-card shadow="never">
|
||||
<div class="card-title" slot="header">快速入口</div>
|
||||
<div class="quickEntries">
|
||||
<div v-for="item in quickEntries" :key="item.id" @click="$store.dispatch('pjaxRoute',item.href)" class="quickEntryItem">
|
||||
<svg-icon :name="item.icon" :size="40" style="color: var(--color-primary)"></svg-icon>
|
||||
<span>{{item.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
quickEntries: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
listQuickEntry() {
|
||||
this.$axios.post("/platform/home/listQuickEntry", { platform: "PC" }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.quickEntries = res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.listQuickEntry()
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
.home-quick-entry .el-card {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.home-quick-entry .el-card .el-card__body {
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 53.5px);
|
||||
}
|
||||
|
||||
|
||||
.home-quick-entry .quickEntries {
|
||||
overflow-y: auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.home-quick-entry .quickEntries .quickEntryItem img {
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.home-quick-entry .quickEntries .quickEntryItem {
|
||||
font-size: 16px;
|
||||
height: 90px;
|
||||
max-height: 90px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
transition: transform 0.3s ease-in-out,
|
||||
box-shadow 0.3s ease-in-out;
|
||||
background: #f3f3f3;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.home-quick-entry .quickEntries .quickEntryItem span {
|
||||
margin-top: auto;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.home-quick-entry .quickEntries .quickEntryItem:hover {
|
||||
cursor: pointer;
|
||||
background: #f3f3f3;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--color-primary);
|
||||
box-shadow:
|
||||
0 0 12px rgba(33, 112, 80, 0.4), /* 外发光 */
|
||||
inset 0 0 8px rgba(33, 112, 80, 0.2); /* 内发光 */
|
||||
background: rgba(33, 112, 80, 0.05); /* 悬浮背景微透主题色 */
|
||||
}
|
||||
|
||||
.home-quick-entry .quickEntries .quickEntryItem:hover .svg-icon {
|
||||
transform: scale(1.1); /* 图标放大10% */
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.home-quick-entry .quickEntries .quickEntryItem:hover span {
|
||||
color: var(--color-primary); /* 使用主题色或自定义颜色 */
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
50% {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
const todo = {
|
||||
template: /*language=HTML*/ `
|
||||
<div class="home-todo">
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="待处理" name="listTodo"></el-tab-pane>
|
||||
<el-tab-pane label="已处理" name="listDone"></el-tab-pane>
|
||||
<el-tab-pane label="我的发起" name="listMyInitiation"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-table :data="tableData" :border="false" size="small" class="mt10" height="calc(100% - 111px)">
|
||||
<el-table-column label="序号" type="index"></el-table-column>
|
||||
<el-table-column label="标题" prop="processInstanceName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="发起人" prop="processInstanceInitiatorName"></el-table-column>
|
||||
<el-table-column label="当前环节" prop="processInstanceNodeName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template slot-scope="{row}">
|
||||
<el-link type="primary" @click="openView(row)">查看</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</div>
|
||||
`,
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
activeName: "listTodo",
|
||||
pageForm: {
|
||||
searchName: "",
|
||||
searchKeyword: "",
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "",
|
||||
pageOrderBy: "",
|
||||
audit: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.pageData()
|
||||
},
|
||||
openView(row) {
|
||||
if (this.activeName === "listMyInitiation") {
|
||||
this.$store.dispatch("pjaxRoute", row.processInstanceUrl)
|
||||
} else {
|
||||
this.$store.dispatch("pjaxRoute", row.taskFormUrl)
|
||||
}
|
||||
},
|
||||
pageData() {
|
||||
this.$axios.post("/platform/home/" + this.activeName, this.pageForm).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
this.tableData = resp.data.list
|
||||
this.pageForm.totalCount = resp.data.totalCount
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.handleClick()
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
.home-todo {
|
||||
background: #ffffff;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
overflow-y: auto;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.home-todo .el-tabs {
|
||||
/*height: 100%;*/
|
||||
}
|
||||
|
||||
.home-todo .el-tabs__content {
|
||||
height: calc(100% - 55px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
/*.home-todo .el-tabs__item{*/
|
||||
/* color: #000000;*/
|
||||
/*}*/
|
||||
|
||||
`
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
<!--#include("/platform/zhgh/staffmanage/member/common/info/memberInfo.js"){}#-->
|
||||
const userInfo = {
|
||||
template: /*language=HTML*/ `
|
||||
<div class="home-user-info">
|
||||
<el-card shadow="never">
|
||||
<div class="card-title" slot="header">个人信息</div>
|
||||
<div class="info">
|
||||
<div>👨🏫我的姓名:{{ $store.state.user.username }}</div>
|
||||
<div>🆔我的工号:{{ $store.state.user.loginname }}</div>
|
||||
<div>⭕我的性别:{{ $store.state.user.sex }}</div>
|
||||
<div>🟢我的单位:{{ $store.state.user.unit.name }}</div>
|
||||
<div>🟤我的工会:{{ $store.state.user.union.name }}</div>
|
||||
<div>📕系统角色:
|
||||
<span style="font-size: 14px;color: #666;" v-for="role in $store.state.user.roles">
|
||||
{{role.name}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-dialog :visible.sync="signatureDialogVisible" title="我的签字" width="35%">
|
||||
<div class="flx">
|
||||
<div v-if="!showSignature" class="p10">
|
||||
<el-image style="width: 100%;border: 1px dashed #000" :src="userSignatureUrl"
|
||||
v-if="userSignatureUrl">
|
||||
<template slot="error">获取签名失败</template>
|
||||
</el-image>
|
||||
<el-empty v-else description="暂无签名"></el-empty>
|
||||
</div>
|
||||
<div class="flx-center-center" style="flex-direction: column;">
|
||||
<qrcode :options="{ width: 126 }" :value="signatureAddress" class="signature-qrcode"></qrcode>
|
||||
<el-button icon="el-icon-refresh" size="small" type="primary" @click="getUserSignature(true)">
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-alert title="扫描二维码可重新签名,手机上签名成功后可点击刷新查看。" type="success"></el-alert>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :visible.sync="memberInfoDialogVisible" title="我的信息">
|
||||
<member-info :id="$store.state.user.id"></member-info>
|
||||
</el-dialog>
|
||||
</div>
|
||||
`,
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
showSignature: false,
|
||||
userSignatureUrl: null,
|
||||
signatureDialogVisible: false,
|
||||
signatureAddress: window.location.origin + "/platform/signature/scanPcCode?origin=user",
|
||||
memberInfoDialogVisible: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"member-info": MEMBER_INFO
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
openSignature() {
|
||||
this.signatureDialogVisible = true
|
||||
this.getUserSignature()
|
||||
},
|
||||
getUserSignature(isRefresh = false) {
|
||||
this.$axios.post("/platform/signature/get").then((res) => {
|
||||
if (res.code === 0) {
|
||||
if (isRefresh) {
|
||||
this.$message.success("刷新成功")
|
||||
} else {
|
||||
if (!res.data.signature) {
|
||||
this.$message.warning("未获取到签字信息")
|
||||
}
|
||||
}
|
||||
this.userSignatureUrl = res.data.signature
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
.home-user-info .el-card {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.home-user-info .info {
|
||||
line-height: 40px;
|
||||
color: black;
|
||||
}
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
const subjectBank = {
|
||||
template: /*language=HTML*/ `
|
||||
<el-dialog :visible.sync="visible" title="题库" append-to-body width="700px">
|
||||
|
||||
</el-dialog>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
category: null,
|
||||
list: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(category) {
|
||||
debugger
|
||||
this.visible = true
|
||||
this.category = category
|
||||
this.listBank()
|
||||
},
|
||||
listBank() {
|
||||
this.$axios.post('/platform/qsv/activity/listBank', {category: this.category}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.list = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
|
||||
`
|
||||
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<!--#include('optionImg.js'){}#-->
|
||||
<!--#include('setting.js'){}#-->
|
||||
<!--#include('txtImport.js'){}#-->
|
||||
<!--#include('bank.js'){}#-->
|
||||
|
||||
const subjectForm = {
|
||||
template: /*language=HTML*/ `
|
||||
@@ -193,6 +194,11 @@ const subjectForm = {
|
||||
|
||||
<div style="border-top: 1px solid var(--border-color-lighter);padding: 12px;text-align: right;">
|
||||
<!-- <el-button @click="visible = false">取消</el-button>-->
|
||||
|
||||
<el-button type="primary" icon="el-icon-plus" @click="openBank">
|
||||
从题库选择
|
||||
</el-button>
|
||||
|
||||
<el-button type="primary" icon="el-icon-upload" @click="openTxtImport">
|
||||
从文本导入
|
||||
</el-button>
|
||||
@@ -207,12 +213,14 @@ const subjectForm = {
|
||||
<option-img ref="optionImgRef" @confirm="onOptionImgConfirm"></option-img>
|
||||
<setting ref="settingRef" @confirm="onSettingConfirm"></setting>
|
||||
<txt-import ref="txtImportRef" @confirm="onTxtImportConfirm"></txt-import>
|
||||
<bank ref="bankRef" @confirm="onBankConfirm"></bank>
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
'option-img': optionImg,
|
||||
'setting': setting,
|
||||
'txt-import': txtImport
|
||||
'txt-import': txtImport,
|
||||
'bank': subjectBank
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -375,6 +383,16 @@ const subjectForm = {
|
||||
this.$message.success('成功导入' + importedQuestions.length + '道题目');
|
||||
},
|
||||
|
||||
// 打开题库
|
||||
openBank(){
|
||||
this.$refs.bankRef.onOpen(this.activity.category)
|
||||
},
|
||||
|
||||
// 从题库选择
|
||||
onBankConfirm(subject){
|
||||
|
||||
},
|
||||
|
||||
//保存
|
||||
saveQuestionnaire() {
|
||||
$
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
const basicForm = {
|
||||
template: /*language=HTML*/ `
|
||||
<el-dialog :visible.sync="dialogVisible" title="基础设置" width="70%">
|
||||
<div style="overflow-y: auto">
|
||||
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="150px">
|
||||
<el-form-item label="类型" prop="category">
|
||||
<el-radio-group v-model="formData.category" size="small">
|
||||
<el-radio v-for="item in dict.type.ACTIVITY_QSV_CATEGORY" :label="item.code"
|
||||
:key="item.code"
|
||||
border>
|
||||
{{item.label}}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="formData.title" placeholder="请输入标题" maxlength="50"
|
||||
show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="说明" prop="description">
|
||||
<text-editor v-model="formData.description" :height="150"></text-editor>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
`,
|
||||
dicts: ["ACTIVITY_QSV_CATEGORY", "ACTIVITY_QSV_MODE", "ACTIVITY_QSV_REPEAT_MODE", "ACTIVITY_QSV_SCORE_MODE"],
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
formData: {
|
||||
category: ''
|
||||
},
|
||||
formRules: {
|
||||
category: [{required: true, message: "请选择类型", trigger: "change"}],
|
||||
title: [{required: true, message: "请输入标题", trigger: "change"}],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(id) {
|
||||
this.dialogVisible = true
|
||||
if (id) {
|
||||
$.post("/platform/qsv/bank/findOne", {id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.formData = res.data
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.formData = {}
|
||||
}
|
||||
},
|
||||
onSubmit() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (valid) {
|
||||
$.post("/platform/qsv/bank/" + (this.formData.id ? "update" : "save"), {qsvBank: JSON.stringify(this.formData)}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success(res.msg)
|
||||
this.dialogVisible = false
|
||||
this.$emit("refresh", null)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app">
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search @search="doSearch">
|
||||
<search-item label="标题">
|
||||
<el-input placeholder="标题" v-model="pageForm.title" clearable></el-input>
|
||||
</search-item>
|
||||
</search>
|
||||
|
||||
</el-card>
|
||||
<el-card shadow="never" class="mt10">
|
||||
<table-tool label="管理列表">
|
||||
<el-button @click="$refs.basicFormRef.onOpen()" size="small" type="primary" class="mr5">新增</el-button>
|
||||
</table-tool>
|
||||
|
||||
<el-table :data="tableData" ref="tableRef" @sort-change="pageOrder">
|
||||
<el-table-column label="序号" width="50" type="index" :index="indexMethod"></el-table-column>
|
||||
<el-table-column label="标题" prop="title" sortable></el-table-column>
|
||||
<el-table-column label="类型" prop="category" sortable width="200">
|
||||
<template scope="{row}">
|
||||
<dict-tag :options="dict.type.ACTIVITY_QSV_CATEGORY" :value="row.category"></dict-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" fixed="right" width="450px">
|
||||
<template scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="mini" type="primary" @click="openSubject(row.id)">题目设置</el-button>
|
||||
<el-button size="mini" type="danger" @click="onDelete(row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<template #edit>
|
||||
<subject-form ref="subjectFormRef" @refresh="pageData"></subject-form>
|
||||
</template>
|
||||
</guava>
|
||||
|
||||
<basic-form ref="basicFormRef" @refresh="pageData"></basic-form>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<!--#include('basicForm.js'){}#-->
|
||||
<!--#include('subjectForm.js'){}#-->
|
||||
new Vue({
|
||||
el: "#app",
|
||||
dicts: ["ACTIVITY_QSV_CATEGORY"],
|
||||
mixins: [initTableMixins],
|
||||
components: {
|
||||
"basic-form": basicForm,
|
||||
"subject-form": subjectForm
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 编辑
|
||||
openEdit(row) {
|
||||
this.$refs.basicFormRef.onOpen(row.id)
|
||||
},
|
||||
|
||||
// 题目设置
|
||||
openSubject(id) {
|
||||
this.$refs.guava.edit(() => {
|
||||
this.$refs.subjectFormRef.onOpen(id)
|
||||
})
|
||||
},
|
||||
|
||||
// 删除
|
||||
onDelete(id) {
|
||||
this.$confirm("您确认删除吗, 是否继续?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
$.post("/platform/qsv/activity/delete", {id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,551 @@
|
||||
<!--#include('../activity/optionImg.js'){}#-->
|
||||
<!--#include('../activity/setting.js'){}#-->
|
||||
<!--#include('../activity/txtImport.js'){}#-->
|
||||
|
||||
const subjectForm = {
|
||||
template: /*language=HTML*/ `
|
||||
<div class="subject-form-dialog">
|
||||
<el-form ref="form" :model="formData" :rules="formRules" label-width="120px">
|
||||
<div style="min-height:50vh;overflow-y: auto">
|
||||
<draggable v-model="subjects" handle=".drag-handler">
|
||||
<transition-group>
|
||||
<div v-for="(subject, subjectIndex) in subjects" :key="subject.id" class="subject-item">
|
||||
<div class="subject-header">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<i class="el-icon-rank drag-handler"></i>
|
||||
<span style="font-weight: bold; margin-right: 10px;">第 {{subjectIndex + 1}} 题</span>
|
||||
<el-input
|
||||
v-model="subject.title"
|
||||
placeholder="请输入题目"
|
||||
style="flex: 1">
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<div class="subject-meta">
|
||||
<div>
|
||||
题目类型:
|
||||
<el-select
|
||||
v-model="subject.type"
|
||||
@change="subjectTypeChange(subject, subjectIndex)"
|
||||
placeholder="请选择题目类型"
|
||||
style="width: 200px;">
|
||||
<el-option label="单选题" value="radio">
|
||||
<i class="el-icon-circle-check subject-type-icon"></i>单选题
|
||||
</el-option>
|
||||
<el-option label="多选题" value="checkbox">
|
||||
<i class="el-icon-check subject-type-icon"></i>多选题
|
||||
</el-option>
|
||||
<el-option label="填空题" value="text"
|
||||
v-if="activity.category!=='QUIZ'">
|
||||
<i class="el-icon-edit subject-type-icon"></i>填空题
|
||||
</el-option>
|
||||
<el-option label="判断题" value="judge">
|
||||
<i class="el-icon-right subject-type-icon"></i>判断题
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<div v-if="subject.type === 'checkbox'">
|
||||
最大选择数:
|
||||
<el-input-number v-model="subject.maxMulti"
|
||||
placeholder="最多可选数量"></el-input-number>
|
||||
</div>
|
||||
<!-- <el-input-->
|
||||
<!-- v-model="subject.hint"-->
|
||||
<!-- placeholder="题目提示信息"-->
|
||||
<!-- style="width: 200px;">-->
|
||||
<!-- <template slot="prepend">提示</template>-->
|
||||
<!-- </el-input>-->
|
||||
|
||||
<div v-if="activity.category==='QUIZ'">
|
||||
题目分数:
|
||||
<el-input-number
|
||||
v-model="subject.score"
|
||||
:min="0"
|
||||
:max="100"
|
||||
placeholder="分数">
|
||||
<template slot="prepend">分数</template>
|
||||
</el-input-number>
|
||||
</div>
|
||||
|
||||
<div v-if="activity.category==='QUIZ' && activity.mode==='SCHEDULED'">
|
||||
显示日期:
|
||||
<el-date-picker
|
||||
v-model="subject.displayDate"
|
||||
type="date"
|
||||
placeholder="显示日期"
|
||||
format="yyyy-MM-dd"
|
||||
value-format="yyyy-MM-dd">
|
||||
</el-date-picker>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template>
|
||||
<draggable v-model="subject.options" handle=".option-drag-handle"
|
||||
v-if="subject.type !== 'text'">
|
||||
<transition-group>
|
||||
<div v-for="(option, optionIndex) in subject.options"
|
||||
:key="option.id"
|
||||
class="option-item">
|
||||
<i class="el-icon-rank option-drag-handle"></i>
|
||||
<div class="option-content">
|
||||
<el-input
|
||||
v-model="option.text"
|
||||
:placeholder="'选项'+optionIndex + 1">
|
||||
<template slot="prepend">选项{{optionIndex + 1}}</template>
|
||||
</el-input>
|
||||
<!-- <el-input-->
|
||||
<!-- v-model="option.hint"-->
|
||||
<!-- placeholder="选项提示"-->
|
||||
<!-- style="margin-top: 5px;">-->
|
||||
<!-- </el-input>-->
|
||||
<div v-if="option.imageUrl" style="margin-top: 10px;">
|
||||
<img :src="option.imageUrl" class="image-preview" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="option-tools">
|
||||
<template v-if="activity.category!=='QUIZ'">
|
||||
<div class="option-img-box" v-if="option.imgUrl">
|
||||
<i class="el-icon-remove"
|
||||
@click="removeOptionImg(subjectIndex,optionIndex)"></i>
|
||||
<img :src="option.imgUrl" alt=""
|
||||
@click="openOptionImg(subjectIndex,optionIndex,option)">
|
||||
</div>
|
||||
<i v-if="!option.imgUrl" class="el-icon-picture-outline"
|
||||
style="font-size: 40px;cursor: pointer;" title="上传图片"
|
||||
@click="openOptionImg(subjectIndex,optionIndex,option)"></i>
|
||||
</template>
|
||||
|
||||
<el-switch
|
||||
v-if="activity.category==='QUIZ'"
|
||||
v-model="option.isCorrect"
|
||||
active-text="正确答案">
|
||||
</el-switch>
|
||||
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-setting"
|
||||
size="mini"
|
||||
@click="openSetting(subjectIndex,optionIndex,option)">
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
size="mini"
|
||||
@click="removeOption(subject, optionIndex)">
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</draggable>
|
||||
<div class="subject-toolbar">
|
||||
<div>
|
||||
<el-button
|
||||
v-if="subject.type !== 'text'"
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
size="small"
|
||||
@click="addOption(subject)">
|
||||
添加选项
|
||||
</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
size="small"
|
||||
@click="removeSubject(subjectIndex)">
|
||||
删除题目
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- <div>-->
|
||||
<!-- <el-switch-->
|
||||
<!-- v-model="subject.required"-->
|
||||
<!-- active-text="必答题">-->
|
||||
<!-- </el-switch>-->
|
||||
<!-- <el-switch-->
|
||||
<!-- v-model="subject.showDate"-->
|
||||
<!-- class="date-visible"-->
|
||||
<!-- active-text="显示日期">-->
|
||||
<!-- </el-switch>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="meta-info">
|
||||
<!-- 创建时间:{{ formatDate(subject.createTime) }}-->
|
||||
<!-- <el-button-->
|
||||
<!-- type="danger"-->
|
||||
<!-- icon="el-icon-delete"-->
|
||||
<!-- size="mini"-->
|
||||
<!-- style="float: right;"-->
|
||||
<!-- @click="removeSubject(index)">-->
|
||||
<!-- 删除题目-->
|
||||
<!-- </el-button>-->
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</draggable>
|
||||
<el-empty description="描述文字" v-if="subjects.length===0"></el-empty>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<div style="border-top: 1px solid var(--border-color-lighter);padding: 12px;text-align: right;">
|
||||
<!-- <el-button @click="visible = false">取消</el-button>-->
|
||||
<el-button type="primary" icon="el-icon-upload" @click="openTxtImport">
|
||||
从文本导入
|
||||
</el-button>
|
||||
<el-button type="primary" icon="el-icon-plus" @click="addSubject">
|
||||
添加题目
|
||||
</el-button>
|
||||
<el-button type="primary" icon="el-icon-check" @click="saveQuestionnaire">
|
||||
保存题目
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<option-img ref="optionImgRef" @confirm="onOptionImgConfirm"></option-img>
|
||||
<setting ref="settingRef" @confirm="onSettingConfirm"></setting>
|
||||
<txt-import ref="txtImportRef" @confirm="onTxtImportConfirm"></txt-import>
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
'option-img': optionImg,
|
||||
'setting': setting,
|
||||
'txt-import': txtImport
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: null,
|
||||
visible: false,
|
||||
formData: {},
|
||||
subjects: [],
|
||||
previewDialogVisible: false,
|
||||
activity: {},
|
||||
formRules: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(id) {
|
||||
this.visible = true
|
||||
if (id) {
|
||||
this.id = id
|
||||
$.post("/platform/qsv/bank/findOne", {id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.activity = res.data
|
||||
this.subjects = res.data.subjects || []
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
//生成随机id
|
||||
generateId() {
|
||||
return Date.now() + Math.random().toString(36).substr(2, 9)
|
||||
},
|
||||
|
||||
//添加题目
|
||||
addSubject() {
|
||||
this.subjects.push({
|
||||
id: this.generateId(),
|
||||
title: "",
|
||||
hint: "",
|
||||
type: "radio",
|
||||
score: 0,
|
||||
displayDate: "",
|
||||
options: [
|
||||
{
|
||||
id: this.generateId(),
|
||||
text: "选项1",
|
||||
hint: "",
|
||||
imgUrl: null,
|
||||
isCorrect: false
|
||||
},
|
||||
{
|
||||
id: this.generateId(),
|
||||
text: "选项2",
|
||||
hint: "",
|
||||
imgUrl: null,
|
||||
isCorrect: false
|
||||
}
|
||||
],
|
||||
})
|
||||
},
|
||||
|
||||
//题目类型切换
|
||||
subjectTypeChange(subject, subjectIndex) {
|
||||
if (subject.type === 'text') {
|
||||
subject.options = []
|
||||
}
|
||||
},
|
||||
|
||||
//删除题目
|
||||
removeSubject(index) {
|
||||
this.$confirm("确认删除该题目?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
.then(() => {
|
||||
this.subjects.splice(index, 1)
|
||||
this.$message.success("删除成功")
|
||||
})
|
||||
.catch(() => {
|
||||
})
|
||||
},
|
||||
|
||||
//添加选项
|
||||
addOption(subject) {
|
||||
subject.options.push({
|
||||
id: this.generateId(),
|
||||
text: "选项" + subject.options.length + 1,
|
||||
hint: "",
|
||||
imgUrl: null,
|
||||
isCorrect: false
|
||||
})
|
||||
},
|
||||
|
||||
//删除选项
|
||||
removeOption(subject, optionIndex) {
|
||||
subject.options.splice(optionIndex, 1)
|
||||
},
|
||||
|
||||
//打开选项上传图片
|
||||
openOptionImg(subjectIndex, optionIndex, option) {
|
||||
this.$refs.optionImgRef.onOpen(option.imgUrl, {
|
||||
subjectIndex,
|
||||
optionIndex
|
||||
})
|
||||
},
|
||||
|
||||
//选项图片上传成功回调
|
||||
onOptionImgConfirm({img, ext}) {
|
||||
this.$set(this.subjects[ext.subjectIndex].options[ext.optionIndex], "imgUrl", img)
|
||||
},
|
||||
|
||||
//删除选项图片
|
||||
removeOptionImg(subjectIndex, optionIndex) {
|
||||
this.$set(this.subjects[subjectIndex].options[optionIndex], "imgUrl", null)
|
||||
console.log(this.subjects[subjectIndex])
|
||||
},
|
||||
|
||||
//打开选项设置
|
||||
openSetting(subjectIndex, optionIndex, option) {
|
||||
this.$refs.settingRef.onOpen(option, {
|
||||
subjectIndex,
|
||||
optionIndex
|
||||
})
|
||||
},
|
||||
|
||||
//选项设置确认
|
||||
onSettingConfirm({option, ext}) {
|
||||
this.subjects[ext.subjectIndex].options[ext.optionIndex] = option
|
||||
console.log(this.subjects[ext.subjectIndex].options[ext.optionIndex])
|
||||
|
||||
// this.$set(this.subjects[ext.subjectIndex].options[ext.optionIndex], "isCorrect", option.isCorrect)
|
||||
// this.$set(this.subjects[ext.subjectIndex].options[ext.optionIndex], "hint", option.hint)
|
||||
},
|
||||
|
||||
//从文本导入
|
||||
openTxtImport() {
|
||||
this.$refs.txtImportRef.visible = true;
|
||||
},
|
||||
|
||||
//处理导入的题目
|
||||
onTxtImportConfirm(importedQuestions) {
|
||||
if (!importedQuestions || importedQuestions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算新题目的起始序号
|
||||
const startSortNum = this.subjects.length + 1;
|
||||
|
||||
// 更新导入题目的序号
|
||||
importedQuestions.forEach((question, index) => {
|
||||
question.sortNum = startSortNum + index;
|
||||
});
|
||||
|
||||
// 将导入的题目添加到现有题目列表
|
||||
this.subjects.push(...importedQuestions);
|
||||
|
||||
this.$message.success('成功导入' + importedQuestions.length + '道题目');
|
||||
},
|
||||
|
||||
//保存
|
||||
saveQuestionnaire() {
|
||||
this.$axios.post("/platform/qsv/bank/saveSubjects", {
|
||||
id: this.activity.id,
|
||||
subjects: JSON.stringify(this.subjects)
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.visible = false
|
||||
this.$message.success(res.msg)
|
||||
this.$emit("refresh", null)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
/*.subject-form-dialog,.subject-form-dialog .el-dialog__body {*/
|
||||
/* background: rgb(248, 249, 250);*/
|
||||
/*}*/
|
||||
|
||||
::v-deep .questionnaire-editor {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
::v-deep .editor-header {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
::v-deep .subject-item {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
border: 1px dashed #d9d9d9;
|
||||
}
|
||||
|
||||
::v-deep .subject-item:hover {
|
||||
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1);
|
||||
/*transform: translateY(-1px);*/
|
||||
}
|
||||
|
||||
::v-deep .subject-header {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
::v-deep .subject-toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 15px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
::v-deep .toolbar {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
::v-deep .toolbar-btn {
|
||||
margin: 5px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::v-deep .option-item {
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
::v-deep .option-drag-handle {
|
||||
cursor: move;
|
||||
margin-right: 10px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
::v-deep .option-content {
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
::v-deep .option-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
::v-deep .preview-dialog {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
::v-deep .date-visible {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
::v-deep .subject-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
background: #f8f9fa;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::v-deep .image-preview {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
margin-top: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
::v-deep .drag-handler {
|
||||
cursor: move;
|
||||
color: #909399;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
::v-deep .meta-info {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
::v-deep .subject-type-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
|
||||
::v-deep .option-img-box {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0
|
||||
}
|
||||
|
||||
::v-deep .option-img-box:hover .el-icon-remove {
|
||||
display: block;
|
||||
}
|
||||
|
||||
::v-deep .option-img-box img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
::v-deep .option-img-box .el-icon-remove {
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
right: -7px;
|
||||
color: red;
|
||||
display: none;
|
||||
}
|
||||
|
||||
::v-deep .option-img-box .el-icon-remove:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
`
|
||||
}
|
||||
@@ -42,18 +42,18 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
<div v-else-if="videoInfo" class="video-player">
|
||||
<video
|
||||
ref="videoElement"
|
||||
class="video-element"
|
||||
:src="videoInfo.url"
|
||||
controls
|
||||
preload="metadata"
|
||||
@loadedmetadata="onVideoLoaded"
|
||||
@canplay="onVideoCanPlay"
|
||||
@play="onVideoPlay"
|
||||
@pause="onVideoPause"
|
||||
@timeupdate="onTimeUpdate"
|
||||
@ended="onVideoEnded"
|
||||
@error="onVideoError"
|
||||
ref="videoElement"
|
||||
class="video-element"
|
||||
:src="videoSrc"
|
||||
controls
|
||||
preload="metadata"
|
||||
@loadedmetadata="onVideoLoaded"
|
||||
@canplay="onVideoCanPlay"
|
||||
@play="onVideoPlay"
|
||||
@pause="onVideoPause"
|
||||
@timeupdate="onTimeUpdate"
|
||||
@ended="onVideoEnded"
|
||||
@error="onVideoError"
|
||||
>
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
@@ -63,227 +63,241 @@ layout("/layouts/platform_h5.html"){
|
||||
</div>
|
||||
|
||||
<!-- 进度保存提示 -->
|
||||
<van-toast v-model="showProgressToast" message="进度已保存" :duration="1000" />
|
||||
<van-toast v-model="showProgressToast" message="进度已保存" :duration="1000"/>
|
||||
|
||||
<!-- 完成学习弹窗 -->
|
||||
<van-dialog
|
||||
v-model="showCompletionDialog"
|
||||
title="恭喜完成学习!"
|
||||
message="您已完成本视频的学习,学习记录已保存。"
|
||||
show-cancel-button
|
||||
cancel-button-text="继续观看"
|
||||
confirm-button-text="返回课程"
|
||||
@confirm="backToCourse"
|
||||
v-model="showCompletionDialog"
|
||||
title="恭喜完成学习!"
|
||||
message="您已完成本视频的学习,学习记录已保存。"
|
||||
show-cancel-button
|
||||
cancel-button-text="继续观看"
|
||||
confirm-button-text="返回课程"
|
||||
@confirm="backToCourse"
|
||||
></van-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
videoInfo: null,
|
||||
loading: true,
|
||||
videoId: null,
|
||||
courseId: null,
|
||||
videoTitle: '视频播放',
|
||||
isPlaying: false,
|
||||
progressSaveTimer: null,
|
||||
showProgressToast: false,
|
||||
showCompletionDialog: false,
|
||||
savedProgress: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
pageBack(){
|
||||
this.historyBack(this.clearProgressSave)
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
videoInfo: null,
|
||||
loading: true,
|
||||
videoId: null,
|
||||
courseId: null,
|
||||
videoTitle: '视频播放',
|
||||
isPlaying: false,
|
||||
progressSaveTimer: null,
|
||||
showProgressToast: false,
|
||||
showCompletionDialog: false,
|
||||
savedProgress: 0,
|
||||
progressSet: false // 标记进度是否已设置,避免死循环
|
||||
}
|
||||
},
|
||||
|
||||
// 加载视频信息
|
||||
async loadVideoInfo() {
|
||||
if (!this.videoId) {
|
||||
this.$toast.fail('缺少视频ID参数');
|
||||
this.loading = false;
|
||||
return;
|
||||
computed: {
|
||||
videoSrc() {
|
||||
if (this.videoInfo && this.videoInfo.url) {
|
||||
const id = this.videoInfo.url.split('id=')[1];
|
||||
console.log('/platform/sys/file/videoPlay?id=' + id)
|
||||
return '/platform/sys/file/videoPlay?id=' + id;
|
||||
}
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
try {
|
||||
const { code, data, msg } = await this.$axios.post('/platform/h5/edu/video/detail', {
|
||||
videoId: this.videoId
|
||||
});
|
||||
methods: {
|
||||
pageBack() {
|
||||
this.historyBack(this.clearProgressSave)
|
||||
},
|
||||
|
||||
if (code === 0 && data) {
|
||||
this.videoInfo = data;
|
||||
this.$nextTick(() => {
|
||||
this.loadWatchProgress();
|
||||
// 加载视频信息
|
||||
async loadVideoInfo() {
|
||||
if (!this.videoId) {
|
||||
this.$toast.fail('缺少视频ID参数');
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const {code, data, msg} = await this.$axios.post('/platform/h5/edu/video/detail', {
|
||||
videoId: this.videoId
|
||||
});
|
||||
} else {
|
||||
this.$toast.fail(msg || '加载视频信息失败');
|
||||
|
||||
if (code === 0 && data) {
|
||||
this.videoInfo = data;
|
||||
this.$nextTick(() => {
|
||||
this.loadWatchProgress();
|
||||
});
|
||||
} else {
|
||||
this.$toast.fail(msg || '加载视频信息失败');
|
||||
}
|
||||
} catch (error) {
|
||||
this.$toast.fail('网络错误');
|
||||
}
|
||||
} catch (error) {
|
||||
this.$toast.fail('网络错误');
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
// 视频加载完成
|
||||
onVideoLoaded() {
|
||||
console.log('视频元数据加载完成');
|
||||
},
|
||||
// 视频加载完成
|
||||
onVideoLoaded() {
|
||||
console.log('视频元数据加载完成');
|
||||
},
|
||||
|
||||
// 视频可以播放
|
||||
onVideoCanPlay() {
|
||||
console.log('视频可以播放');
|
||||
// 视频可以播放时,设置观看进度
|
||||
this.setWatchProgress();
|
||||
},
|
||||
// 视频可以播放
|
||||
onVideoCanPlay() {
|
||||
console.log('视频可以播放');
|
||||
// 视频可以播放时,设置观看进度
|
||||
this.setWatchProgress();
|
||||
},
|
||||
|
||||
// 视频开始播放
|
||||
onVideoPlay() {
|
||||
this.isPlaying = true;
|
||||
this.startProgressSave();
|
||||
},
|
||||
// 视频开始播放
|
||||
onVideoPlay() {
|
||||
this.isPlaying = true;
|
||||
this.startProgressSave();
|
||||
},
|
||||
|
||||
// 视频暂停
|
||||
onVideoPause() {
|
||||
this.isPlaying = false;
|
||||
this.saveProgress();
|
||||
},
|
||||
|
||||
// 时间更新
|
||||
onTimeUpdate() {
|
||||
console.log('时间更新')
|
||||
// 可以在这里添加进度更新逻辑
|
||||
},
|
||||
|
||||
// 视频播放结束
|
||||
onVideoEnded() {
|
||||
this.isPlaying = false;
|
||||
this.markVideoCompleted();
|
||||
},
|
||||
|
||||
// 视频加载错误
|
||||
onVideoError() {
|
||||
this.$toast.fail('视频加载失败');
|
||||
},
|
||||
|
||||
// 加载观看进度
|
||||
async loadWatchProgress() {
|
||||
if (!this.videoId || !this.courseId) return;
|
||||
|
||||
try {
|
||||
const { code, data } = await this.$axios.post('/platform/h5/edu/study/record', {
|
||||
videoId: this.videoId
|
||||
});
|
||||
|
||||
if (code === 0 && data && data.watchedDuration > 0) {
|
||||
this.savedProgress = data.watchedDuration;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载观看进度失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 设置观看进度
|
||||
setWatchProgress() {
|
||||
if (this.savedProgress > 0) {
|
||||
const video = this.$refs.videoElement;
|
||||
if (video && video.readyState >= 3) {
|
||||
// 使用setTimeout确保DOM更新完成
|
||||
setTimeout(() => {
|
||||
try {
|
||||
video.currentTime = this.savedProgress;
|
||||
console.log('设置观看进度成功:', this.savedProgress);
|
||||
} catch (error) {
|
||||
console.error('设置观看进度失败:', error);
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
console.log('视频还未准备好,readyState:', video ? video.readyState : 'video不存在');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 保存观看进度
|
||||
async saveProgress() {
|
||||
const video = this.$refs.videoElement;
|
||||
if (!video || !this.videoId || !this.courseId) return;
|
||||
|
||||
try {
|
||||
await $.post('/platform/h5/edu/study/progress/save', {
|
||||
videoId: this.videoId,
|
||||
courseId: this.courseId,
|
||||
watchedDuration: Math.floor(video.currentTime)
|
||||
});
|
||||
|
||||
this.showProgressToast = true;
|
||||
} catch (error) {
|
||||
console.error('保存进度失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 开始定时保存进度
|
||||
startProgressSave() {
|
||||
this.clearProgressSave();
|
||||
this.progressSaveTimer = setInterval(() => {
|
||||
// 视频暂停
|
||||
onVideoPause() {
|
||||
this.isPlaying = false;
|
||||
this.saveProgress();
|
||||
}, 10000); // 每10秒保存一次
|
||||
},
|
||||
},
|
||||
|
||||
// 清除定时保存
|
||||
clearProgressSave() {
|
||||
if (this.progressSaveTimer) {
|
||||
clearInterval(this.progressSaveTimer);
|
||||
this.progressSaveTimer = null;
|
||||
}
|
||||
},
|
||||
// 时间更新
|
||||
onTimeUpdate() {
|
||||
console.log('时间更新')
|
||||
// 可以在这里添加进度更新逻辑
|
||||
},
|
||||
|
||||
// 标记视频完成
|
||||
async markVideoCompleted() {
|
||||
if (!this.videoId || !this.courseId) return;
|
||||
// 视频播放结束
|
||||
onVideoEnded() {
|
||||
this.isPlaying = false;
|
||||
this.markVideoCompleted();
|
||||
},
|
||||
|
||||
try {
|
||||
const { code } = await $.post('/platform/h5/edu/study/complete', {
|
||||
videoId: this.videoId,
|
||||
courseId: this.courseId
|
||||
});
|
||||
// 视频加载错误
|
||||
onVideoError() {
|
||||
this.$toast.fail('视频加载失败');
|
||||
},
|
||||
|
||||
if (code === 0) {
|
||||
this.showCompletionDialog = true;
|
||||
// 加载观看进度
|
||||
async loadWatchProgress() {
|
||||
if (!this.videoId || !this.courseId) return;
|
||||
|
||||
try {
|
||||
const {code, data} = await this.$axios.post('/platform/h5/edu/study/record', {
|
||||
videoId: this.videoId
|
||||
});
|
||||
|
||||
if (code === 0 && data && data.watchedDuration > 0) {
|
||||
this.savedProgress = data.watchedDuration;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载观看进度失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 设置观看进度
|
||||
setWatchProgress() {
|
||||
if (this.savedProgress > 0 && !this.progressSet) {
|
||||
const video = this.$refs.videoElement;
|
||||
if (video && video.readyState >= 3) {
|
||||
// 使用setTimeout确保DOM更新完成
|
||||
setTimeout(() => {
|
||||
try {
|
||||
this.progressSet = true; // 标记进度已设置,避免重复设置
|
||||
video.currentTime = this.savedProgress;
|
||||
console.log('设置观看进度成功:', this.savedProgress);
|
||||
} catch (error) {
|
||||
console.error('设置观看进度失败:', error);
|
||||
}
|
||||
}, 100);
|
||||
} else {
|
||||
console.log('视频还未准备好,readyState:', video ? video.readyState : 'video不存在');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 保存观看进度
|
||||
async saveProgress() {
|
||||
const video = this.$refs.videoElement;
|
||||
if (!video || !this.videoId || !this.courseId) return;
|
||||
|
||||
try {
|
||||
await $.post('/platform/h5/edu/study/progress/save', {
|
||||
videoId: this.videoId,
|
||||
courseId: this.courseId,
|
||||
watchedDuration: Math.floor(video.currentTime)
|
||||
});
|
||||
|
||||
this.showProgressToast = true;
|
||||
} catch (error) {
|
||||
console.error('保存进度失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 开始定时保存进度
|
||||
startProgressSave() {
|
||||
this.clearProgressSave();
|
||||
this.progressSaveTimer = setInterval(() => {
|
||||
this.saveProgress();
|
||||
}, 10000); // 每10秒保存一次
|
||||
},
|
||||
|
||||
// 清除定时保存
|
||||
clearProgressSave() {
|
||||
if (this.progressSaveTimer) {
|
||||
clearInterval(this.progressSaveTimer);
|
||||
this.progressSaveTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
// 标记视频完成
|
||||
async markVideoCompleted() {
|
||||
if (!this.videoId || !this.courseId) return;
|
||||
|
||||
try {
|
||||
const {code} = await $.post('/platform/h5/edu/study/complete', {
|
||||
videoId: this.videoId,
|
||||
courseId: this.courseId
|
||||
});
|
||||
|
||||
if (code === 0) {
|
||||
this.showCompletionDialog = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('标记完成失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 返回课程
|
||||
backToCourse() {
|
||||
if (this.courseId) {
|
||||
|
||||
} else {
|
||||
this.historyBack();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('标记完成失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 返回课程
|
||||
backToCourse() {
|
||||
if (this.courseId) {
|
||||
mounted() {
|
||||
// 获取URL参数
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
this.videoId = urlParams.get('videoId');
|
||||
this.courseId = urlParams.get('courseId');
|
||||
this.videoTitle = decodeURIComponent(urlParams.get('title') || '视频播放');
|
||||
|
||||
} else {
|
||||
this.historyBack();
|
||||
}
|
||||
this.loadVideoInfo();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
// 页面销毁前保存进度
|
||||
this.saveProgress();
|
||||
this.clearProgressSave();
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// 获取URL参数
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
this.videoId = urlParams.get('videoId');
|
||||
this.courseId = urlParams.get('courseId');
|
||||
this.videoTitle = decodeURIComponent(urlParams.get('title') || '视频播放');
|
||||
|
||||
this.loadVideoInfo();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
// 页面销毁前保存进度
|
||||
this.saveProgress();
|
||||
this.clearProgressSave();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
|
||||
@@ -156,7 +156,7 @@ layout("/layouts/platform_h5.html"){
|
||||
if (this.timerInterval) {
|
||||
clearInterval(this.timerInterval)
|
||||
}
|
||||
pjaxReplace('platform/h5/qsv')
|
||||
historyBack()
|
||||
},
|
||||
|
||||
//答题计时器
|
||||
|
||||
@@ -27,8 +27,8 @@ const SUGGESTION_BOX_INFO = {
|
||||
</van-cell>
|
||||
<van-cell title="办理时间">{{ task.finishTime }}</van-cell>
|
||||
<van-cell title="办理结果">
|
||||
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
|
||||
:value="task.ext.caseFilingResult"></dict-tag>
|
||||
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
|
||||
:value="task.ext.submitType"></dict-tag>
|
||||
</van-cell>
|
||||
<van-cell title="办理意见" v-if="!task.ext.isFirstTaskNode" class="direction-column-cell">
|
||||
<div v-html="task.taskFormData.tf_opinion"></div>
|
||||
|
||||
@@ -20,7 +20,7 @@ layout("/layouts/platform_h5.html"){
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="申请人">{{row.userName}}</table-column>
|
||||
<table-column label="申请时间">{{row.submitTime}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName}}</table-column>
|
||||
<table-column label="当前节点">{{row.taskName}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
@@ -91,7 +91,7 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
// 编辑
|
||||
onEdit(row) {
|
||||
this.$pjaxReplace("/platform/h5/suggestionBox/write?bizId=" + row.id + "&taskId=" + row.startTaskId)
|
||||
this.$pjaxReplace("/platform/suggestionBox/write/h5?bizId=" + row.id + "&taskId=" + (row.startTaskId || ''))
|
||||
},
|
||||
|
||||
// 撤回
|
||||
|
||||
@@ -73,7 +73,7 @@ layout("/layouts/platform_h5.html"){
|
||||
this.$axios.post('/platform/suggestionBox/write/save', {data: JSON.stringify(this.formData)}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success("保存成功");
|
||||
pjaxReplace("/platform/h5/suggestionBox/mine")
|
||||
pjaxReplace("/platform/suggestionBox/mine/h5")
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
@@ -96,7 +96,7 @@ layout("/layouts/platform_h5.html"){
|
||||
this.$axios.post('/platform/suggestionBox/write/submit', {data: JSON.stringify(this.formData)}).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$toast.success("提交成功");
|
||||
pjaxReplace("/platform/h5/suggestionBox/mine")
|
||||
pjaxReplace("/platform/suggestionBox/mine/h5")
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
|
||||
@@ -265,6 +265,11 @@ const apps = {
|
||||
|
||||
// 显示应用菜单弹框
|
||||
showAppMenus(app, menus) {
|
||||
if(app.href && menus.length === 0){
|
||||
this.$pjaxReplace(app.href)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!menus || menus.length === 0) {
|
||||
this.$toast('该应用暂无可用功能')
|
||||
return
|
||||
|
||||
@@ -92,7 +92,7 @@ const todo = {
|
||||
</div>
|
||||
|
||||
<div v-else class="empty-section">
|
||||
<van-empty :description="getEmptyText()"/>
|
||||
<van-empty :description="getEmptyText()"></van-empty>
|
||||
</div>
|
||||
</van-list>
|
||||
</van-pull-refresh>
|
||||
|
||||
Reference in New Issue
Block a user